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]
12rustflags = ["-D", "warnings"]
.github/actions/run_tests/action.yml+1−5
@@ -19,16 +19,12 @@ runs: · 18 unmodified lines
19 19
20 - name: Limit target directory size20 - name: Limit target directory size
21 shell: bash -euxo pipefail {0}21 shell: bash -euxo pipefail {0}
22 run: script/clear-target-dir-if-larger-than 7022 run: script/clear-target-dir-if-larger-than 100
23 23
24 - name: Run check24 - name: Run check
25 env:
26 RUSTFLAGS: -D warnings
27 shell: bash -euxo pipefail {0}25 shell: bash -euxo pipefail {0}
28 run: cargo check --tests --workspace26 run: cargo check --tests --workspace
29 27
30 - name: Run tests28 - name: Run tests
31 env:
32 RUSTFLAGS: -D warnings
33 shell: bash -euxo pipefail {0}29 shell: bash -euxo pipefail {0}
34 run: cargo nextest run --workspace --no-fail-fast30 run: cargo nextest run --workspace --no-fail-fast
.github/workflows/ci.yml+4−3
@@ -29,6 +29,9 @@ jobs: · 28 unmodified lines
29 clean: false29 clean: false
30 submodules: "recursive"30 submodules: "recursive"
31 31
32 - name: Set up default .cargo/config.toml
33 run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
34
32 - name: Run rustfmt35 - name: Run rustfmt
33 uses: ./.github/actions/check_formatting36 uses: ./.github/actions/check_formatting
34 37
@@ -87,7 +90,7 @@ jobs: · 52 unmodified lines
87 submodules: "recursive"90 submodules: "recursive"
88 91
89 - name: Limit target directory size92 - name: Limit target directory size
90 run: script/clear-target-dir-if-larger-than 7093 run: script/clear-target-dir-if-larger-than 100
91 94
92 - name: Determine version and release channel95 - name: Determine version and release channel
93 if: ${{ startsWith(github.ref, 'refs/tags/v') }}96 if: ${{ startsWith(github.ref, 'refs/tags/v') }}
@@ -131,8 +134,6 @@ jobs: · 37 unmodified lines
131 134
132 - uses: softprops/action-gh-release@v1135 - uses: softprops/action-gh-release@v1
133 name: Upload app bundle to release136 name: Upload app bundle to release
134 # TODO kb seems that zed.dev relies on GitHub releases for release version tracking.
135 # Find alternatives for `nightly` or just go on with more releases?
136 if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}137 if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
137 with:138 with:
138 draft: true139 draft: true
.github/workflows/release_nightly.yml+1−1
@@ -79,7 +79,7 @@ jobs: · 78 unmodified lines
79 submodules: "recursive"79 submodules: "recursive"
80 80
81 - name: Limit target directory size81 - name: Limit target directory size
82 run: script/clear-target-dir-if-larger-than 7082 run: script/clear-target-dir-if-larger-than 100
83 83
84 - name: Set release channel to nightly84 - name: Set release channel to nightly
85 run: |85 run: |
Cargo.lock+64−18
@@ -1186,12 +1186,14 @@ version = "0.1.0" · 1185 unmodified lines
1186dependencies = [1186dependencies = [
1187 "anyhow",1187 "anyhow",
1188 "async-broadcast",1188 "async-broadcast",
1189 "async-trait",
1189 "audio2",1190 "audio2",
1190 "client2",1191 "client2",
1191 "collections",1192 "collections",
1192 "fs2",1193 "fs2",
1193 "futures 0.3.28",1194 "futures 0.3.28",
1194 "gpui2",1195 "gpui2",
1196 "image",
1195 "language2",1197 "language2",
1196 "live_kit_client2",1198 "live_kit_client2",
1197 "log",1199 "log",
@@ -1203,7 +1205,10 @@ dependencies = [ · 5 unmodified lines
1203 "serde_derive",1205 "serde_derive",
1204 "serde_json",1206 "serde_json",
1205 "settings2",1207 "settings2",
1208 "smallvec",
1209 "ui2",
1206 "util",1210 "util",
1211 "workspace2",
1207]1212]
1208 1213
1209[[package]]1214[[package]]
@@ -1398,7 +1403,7 @@ dependencies = [ · 188 unmodified lines
1398 "smol",1403 "smol",
1399 "sum_tree",1404 "sum_tree",
1400 "tempfile",1405 "tempfile",
1401 "text",1406 "text2",
1402 "thiserror",1407 "thiserror",
1403 "time",1408 "time",
1404 "tiny_http",1409 "tiny_http",
@@ -1664,7 +1669,7 @@ dependencies = [ · 259 unmodified lines
1664 1669
1665[[package]]1670[[package]]
1666name = "collab"1671name = "collab"
1667version = "0.28.0"1672version = "0.29.0"
1668dependencies = [1673dependencies = [
1669 "anyhow",1674 "anyhow",
1670 "async-trait",1675 "async-trait",
@@ -1869,7 +1874,7 @@ dependencies = [ · 198 unmodified lines
1869 "editor2",1874 "editor2",
1870 "feature_flags2",1875 "feature_flags2",
1871 "futures 0.3.28",1876 "futures 0.3.28",
1872 "fuzzy",1877 "fuzzy2",
1873 "gpui2",1878 "gpui2",
1874 "language2",1879 "language2",
1875 "lazy_static",1880 "lazy_static",
@@ -6763,7 +6768,6 @@ dependencies = [ · 4887 unmodified lines
6763 "anyhow",6768 "anyhow",
6764 "client2",6769 "client2",
6765 "collections",6770 "collections",
6766 "context_menu",
6767 "db2",6771 "db2",
6768 "editor2",6772 "editor2",
6769 "futures 0.3.28",6773 "futures 0.3.28",
@@ -8032,6 +8036,35 @@ dependencies = [ · 1262 unmodified lines
8032 "workspace",8036 "workspace",
8033]8037]
8034 8038
8039[[package]]
8040name = "search2"
8041version = "0.1.0"
8042dependencies = [
8043 "anyhow",
8044 "bitflags 1.3.2",
8045 "client2",
8046 "collections",
8047 "editor2",
8048 "futures 0.3.28",
8049 "gpui2",
8050 "language2",
8051 "log",
8052 "menu2",
8053 "postage",
8054 "project2",
8055 "serde",
8056 "serde_derive",
8057 "serde_json",
8058 "settings2",
8059 "smallvec",
8060 "smol",
8061 "theme2",
8062 "ui2",
8063 "unindent",
8064 "util",
8065 "workspace2",
8066]
8067
8035[[package]]8068[[package]]
8036name = "security-framework"8069name = "security-framework"
8037version = "2.9.2"8070version = "2.9.2"
@@ -8855,6 +8888,13 @@ version = "1.1.0" · 817 unmodified lines
8855source = "registry+https://github.com/rust-lang/crates.io-index"8888source = "registry+https://github.com/rust-lang/crates.io-index"
8856checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"8889checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
8857 8890
8891[[package]]
8892name = "story"
8893version = "0.1.0"
8894dependencies = [
8895 "gpui2",
8896]
8897
8858[[package]]8898[[package]]
8859name = "storybook2"8899name = "storybook2"
8860version = "0.1.0"8900version = "0.1.0"
@@ -8876,6 +8916,7 @@ dependencies = [ · 15 unmodified lines
8876 "settings2",8916 "settings2",
8877 "simplelog",8917 "simplelog",
8878 "smallvec",8918 "smallvec",
8919 "story",
8879 "strum",8920 "strum",
8880 "theme",8921 "theme",
8881 "theme2",8922 "theme2",
@@ -8883,17 +8924,6 @@ dependencies = [ · 1 unmodified line
8883 "util",8924 "util",
8884]8925]
8885 8926
8886[[package]]
8887name = "storybook3"
8888version = "0.1.0"
8889dependencies = [
8890 "anyhow",
8891 "gpui2",
8892 "settings2",
8893 "theme2",
8894 "ui2",
8895]
8896
8897[[package]]8927[[package]]
8898name = "stringprep"8928name = "stringprep"
8899version = "0.1.4"8929version = "0.1.4"
@@ -9397,6 +9427,7 @@ dependencies = [ · 497 unmodified lines
9397 "serde_derive",9427 "serde_derive",
9398 "serde_json",9428 "serde_json",
9399 "settings2",9429 "settings2",
9430 "story",
9400 "toml 0.5.11",9431 "toml 0.5.11",
9401 "util",9432 "util",
9402 "uuid 1.4.1",9433 "uuid 1.4.1",
@@ -9899,7 +9930,7 @@ dependencies = [ · 496 unmodified lines
9899[[package]]9930[[package]]
9900name = "tree-sitter"9931name = "tree-sitter"
9901version = "0.20.10"9932version = "0.20.10"
9902source = "git+https://github.com/tree-sitter/tree-sitter?rev=35a6052fbcafc5e5fc0f9415b8652be7dcaf7222#35a6052fbcafc5e5fc0f9415b8652be7dcaf7222"9933source = "git+https://github.com/tree-sitter/tree-sitter?rev=3b0159d25559b603af566ade3c83d930bf466db1#3b0159d25559b603af566ade3c83d930bf466db1"
9903dependencies = [9934dependencies = [
9904 "cc",9935 "cc",
9905 "regex",9936 "regex",
@@ -10147,6 +10178,15 @@ dependencies = [ · 241 unmodified lines
10147 "tree-sitter",10178 "tree-sitter",
10148]10179]
10149 10180
10181[[package]]
10182name = "tree-sitter-uiua"
10183version = "0.3.3"
10184source = "git+https://github.com/shnarazk/tree-sitter-uiua?rev=9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2#9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"
10185dependencies = [
10186 "cc",
10187 "tree-sitter",
10188]
10189
10150[[package]]10190[[package]]
10151name = "tree-sitter-vue"10191name = "tree-sitter-vue"
10152version = "0.0.1"10192version = "0.0.1"
@@ -10240,6 +10280,7 @@ dependencies = [ · 87 unmodified lines
10240 "serde",10280 "serde",
10241 "settings2",10281 "settings2",
10242 "smallvec",10282 "smallvec",
10283 "story",
10243 "strum",10284 "strum",
10244 "theme2",10285 "theme2",
10245]10286]
@@ -11354,6 +11395,7 @@ version = "0.1.0" · 1108 unmodified lines
11354dependencies = [11395dependencies = [
11355 "anyhow",11396 "anyhow",
11356 "async-recursion 1.0.5",11397 "async-recursion 1.0.5",
11398 "async-trait",
11357 "bincode",11399 "bincode",
11358 "call2",11400 "call2",
11359 "client2",11401 "client2",
@@ -11466,7 +11508,7 @@ dependencies = [ · 106 unmodified lines
11466 11508
11467[[package]]11509[[package]]
11468name = "zed"11510name = "zed"
11469version = "0.114.0"11511version = "0.115.0"
11470dependencies = [11512dependencies = [
11471 "activity_indicator",11513 "activity_indicator",
11472 "ai",11514 "ai",
@@ -11583,6 +11625,7 @@ dependencies = [ · 110 unmodified lines
11583 "tree-sitter-svelte",11625 "tree-sitter-svelte",
11584 "tree-sitter-toml",11626 "tree-sitter-toml",
11585 "tree-sitter-typescript",11627 "tree-sitter-typescript",
11628 "tree-sitter-uiua",
11586 "tree-sitter-vue",11629 "tree-sitter-vue",
11587 "tree-sitter-yaml",11630 "tree-sitter-yaml",
11588 "unindent",11631 "unindent",
@@ -11614,9 +11657,11 @@ dependencies = [ · 25 unmodified lines
11614 "async-recursion 0.3.2",11657 "async-recursion 0.3.2",
11615 "async-tar",11658 "async-tar",
11616 "async-trait",11659 "async-trait",
11660 "audio2",
11617 "auto_update2",11661 "auto_update2",
11618 "backtrace",11662 "backtrace",
11619 "call2",11663 "call2",
11664 "channel2",
11620 "chrono",11665 "chrono",
11621 "cli",11666 "cli",
11622 "client2",11667 "client2",
@@ -11634,7 +11679,6 @@ dependencies = [ · 11 unmodified lines
11634 "fs2",11679 "fs2",
11635 "fsevent",11680 "fsevent",
11636 "futures 0.3.28",11681 "futures 0.3.28",
11637 "fuzzy",
11638 "go_to_line2",11682 "go_to_line2",
11639 "gpui2",11683 "gpui2",
11640 "ignore",11684 "ignore",
@@ -11662,6 +11706,7 @@ dependencies = [ · 21 unmodified lines
11662 "rsa 0.4.0",11706 "rsa 0.4.0",
11663 "rust-embed",11707 "rust-embed",
11664 "schemars",11708 "schemars",
11709 "search2",
11665 "serde",11710 "serde",
11666 "serde_derive",11711 "serde_derive",
11667 "serde_json",11712 "serde_json",
@@ -11704,6 +11749,7 @@ dependencies = [ · 36 unmodified lines
11704 "tree-sitter-svelte",11749 "tree-sitter-svelte",
11705 "tree-sitter-toml",11750 "tree-sitter-toml",
11706 "tree-sitter-typescript",11751 "tree-sitter-typescript",
11752 "tree-sitter-uiua",
11707 "tree-sitter-vue",11753 "tree-sitter-vue",
11708 "tree-sitter-yaml",11754 "tree-sitter-yaml",
11709 "unindent",11755 "unindent",
Cargo.toml+7−3
@@ -90,6 +90,7 @@ members = [ · 89 unmodified lines
90 "crates/rpc",90 "crates/rpc",
91 "crates/rpc2",91 "crates/rpc2",
92 "crates/search",92 "crates/search",
93 "crates/search2",
93 "crates/settings",94 "crates/settings",
94 "crates/settings2",95 "crates/settings2",
95 "crates/snippet",96 "crates/snippet",
@@ -97,7 +98,6 @@ members = [ · 1 unmodified line
97 "crates/sqlez_macros",98 "crates/sqlez_macros",
98 "crates/rich_text",99 "crates/rich_text",
99 "crates/storybook2",100 "crates/storybook2",
100 "crates/storybook3",
101 "crates/sum_tree",101 "crates/sum_tree",
102 "crates/terminal",102 "crates/terminal",
103 "crates/terminal2",103 "crates/terminal2",
@@ -110,6 +110,7 @@ members = [ · 6 unmodified lines
110 "crates/ui2",110 "crates/ui2",
111 "crates/util",111 "crates/util",
112 "crates/semantic_index",112 "crates/semantic_index",
113 "crates/story",
113 "crates/vim",114 "crates/vim",
114 "crates/vcs_menu",115 "crates/vcs_menu",
115 "crates/workspace2",116 "crates/workspace2",
@@ -194,8 +195,10 @@ tree-sitter-lua = "0.0.14" · 78 unmodified lines
194tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }195tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
195tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"}196tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"}
196tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"}197tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"}
198tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
199
197[patch.crates-io]200[patch.crates-io]
198tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" }201tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" }
199async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }202async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
200 203
201# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457204# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457
@@ -207,11 +210,12 @@ core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "07 · 5 unmodified lines
207 210
208[profile.dev]211[profile.dev]
209split-debuginfo = "unpacked"212split-debuginfo = "unpacked"
213debug = "limited"
210 214
211[profile.dev.package.taffy]215[profile.dev.package.taffy]
212opt-level = 3216opt-level = 3
213 217
214[profile.release]218[profile.release]
215debug = true219debug = "limited"
216lto = "thin"220lto = "thin"
217codegen-units = 1221codegen-units = 1
Procfile.zed2+4−0
@@ -0,0 +1,4 @@
1web: cd ../zed.dev && PORT=3000 npm run dev
2collab: cd crates/collab2 && RUST_LOG=${RUST_LOG:-warn,collab=info} cargo run serve
3livekit: livekit-server --dev
4postgrest: postgrest crates/collab2/admin_api.conf
assets/settings/default.json+1−1
@@ -43,7 +43,7 @@ · 42 unmodified lines
43 "calt": false43 "calt": false
44 },44 },
45 // The default font size for text in the UI45 // The default font size for text in the UI
46 "ui_font_size": 14,46 "ui_font_size": 16,
47 // The factor to grow the active pane by. Defaults to 1.047 // The factor to grow the active pane by. Defaults to 1.0
48 // which gives the same size as all other panes.48 // which gives the same size as all other panes.
49 "active_pane_magnification": 1.0,49 "active_pane_magnification": 1.0,
assets/settings/initial_user_settings.json+2−1
@@ -7,5 +7,6 @@ · 6 unmodified lines
7// custom settings, run the `open default settings` command7// custom settings, run the `open default settings` command
8// from the command palette or from `Zed` application menu.8// from the command palette or from `Zed` application menu.
9{9{
10 "buffer_font_size": 1510 "ui_font_size": 16,
11 "buffer_font_size": 16
11}12}
crates/auto_update2/src/auto_update.rs+8−3
@@ -84,8 +84,8 @@ impl Settings for AutoUpdateSetting { · 83 unmodified lines
84pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {84pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
85 AutoUpdateSetting::register(cx);85 AutoUpdateSetting::register(cx);
86 86
87 cx.observe_new_views(|wokrspace: &mut Workspace, _cx| {87 cx.observe_new_views(|workspace: &mut Workspace, _cx| {
88 wokrspace88 workspace
89 .register_action(|_, action: &Check, cx| check(action, cx))89 .register_action(|_, action: &Check, cx| check(action, cx))
90 .register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| {90 .register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| {
91 let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]);91 let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]);
@@ -94,6 +94,11 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo · 2 unmodified lines
94 })94 })
95 .detach();95 .detach();
96 });96 });
97
98 // @nate - code to trigger update notification on launch
99 // workspace.show_notification(0, _cx, |cx| {
100 // cx.build_view(|_| UpdateNotification::new(SemanticVersion::from_str("1.1.1").unwrap()))
101 // });
97 })102 })
98 .detach();103 .detach();
99 104
@@ -131,7 +136,7 @@ pub fn check(_: &Check, cx: &mut AppContext) { · 31 unmodified lines
131 }136 }
132}137}
133 138
134fn _view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {139pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
135 if let Some(auto_updater) = AutoUpdater::get(cx) {140 if let Some(auto_updater) = AutoUpdater::get(cx) {
136 let auto_updater = auto_updater.read(cx);141 let auto_updater = auto_updater.read(cx);
137 let server_url = &auto_updater.server_url;142 let server_url = &auto_updater.server_url;
crates/auto_update2/src/update_notification.rs+41−72
@@ -1,87 +1,56 @@
1use gpui::{div, Div, EventEmitter, ParentComponent, Render, SemanticVersion, ViewContext};1use gpui::{
2use menu::Cancel;2 div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render,
3use workspace::notifications::NotificationEvent;3 SemanticVersion, StatefulInteractiveElement, Styled, ViewContext,
4};
5use util::channel::ReleaseChannel;
6use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt};
4 7
5pub struct UpdateNotification {8pub struct UpdateNotification {
6 _version: SemanticVersion,9 version: SemanticVersion,
7}10}
8 11
9impl EventEmitter<NotificationEvent> for UpdateNotification {}12impl EventEmitter<DismissEvent> for UpdateNotification {}
10 13
11impl Render for UpdateNotification {14impl Render for UpdateNotification {
12 type Element = Div<Self>;15 type Element = Div;
13 16
14 fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {17 fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
15 div().child("Updated zed!")18 let app_name = cx.global::<ReleaseChannel>().display_name();
16 // let theme = theme::current(cx).clone();19
17 // let theme = &theme.update_notification;20 v_stack()
18 21 .elevation_3(cx)
19 // let app_name = cx.global::<ReleaseChannel>().display_name();22 .p_4()
20 23 .child(
21 // MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {24 h_stack()
22 // Flex::column()25 .justify_between()
23 // .with_child(26 .child(Label::new(format!(
24 // Flex::row()27 "Updated to {app_name} {}",
25 // .with_child(28 self.version
26 // Text::new(29 )))
27 // format!("Updated to {app_name} {}", self.version),30 .child(
28 // theme.message.text.clone(),31 div()
29 // )32 .id("cancel")
30 // .contained()33 .child(IconElement::new(Icon::Close))
31 // .with_style(theme.message.container)34 .cursor_pointer()
32 // .aligned()35 .on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
33 // .top()36 ),
34 // .left()37 )
35 // .flex(1., true),38 .child(
36 // )39 div()
37 // .with_child(40 .id("notes")
38 // MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {41 .child(Label::new("View the release notes"))
39 // let style = theme.dismiss_button.style_for(state);42 .cursor_pointer()
40 // Svg::new("icons/x.svg")43 .on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)),
41 // .with_color(style.color)44 )
42 // .constrained()
43 // .with_width(style.icon_width)
44 // .aligned()
45 // .contained()
46 // .with_style(style.container)
47 // .constrained()
48 // .with_width(style.button_width)
49 // .with_height(style.button_width)
50 // })
51 // .with_padding(Padding::uniform(5.))
52 // .on_click(MouseButton::Left, move |_, this, cx| {
53 // this.dismiss(&Default::default(), cx)
54 // })
55 // .aligned()
56 // .constrained()
57 // .with_height(cx.font_cache().line_height(theme.message.text.font_size))
58 // .aligned()
59 // .top()
60 // .flex_float(),
61 // ),
62 // )
63 // .with_child({
64 // let style = theme.action_message.style_for(state);
65 // Text::new("View the release notes", style.text.clone())
66 // .contained()
67 // .with_style(style.container)
68 // })
69 // .contained()
70 // })
71 // .with_cursor_style(CursorStyle::PointingHand)
72 // .on_click(MouseButton::Left, |_, _, cx| {
73 // crate::view_release_notes(&Default::default(), cx)
74 // })
75 // .into_any_named("update notification")
76 }45 }
77}46}
78 47
79impl UpdateNotification {48impl UpdateNotification {
80 pub fn new(version: SemanticVersion) -> Self {49 pub fn new(version: SemanticVersion) -> Self {
81 Self { _version: version }50 Self { version }
82 }51 }
83 52
84 pub fn _dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {53 pub fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
85 cx.emit(NotificationEvent::Dismiss);54 cx.emit(DismissEvent::Dismiss);
86 }55 }
87}56}
crates/call2/Cargo.toml+5−1
@@ -31,15 +31,19 @@ media = { path = "../media" } · 30 unmodified lines
31project = { package = "project2", path = "../project2" }31project = { package = "project2", path = "../project2" }
32settings = { package = "settings2", path = "../settings2" }32settings = { package = "settings2", path = "../settings2" }
33util = { path = "../util" }33util = { path = "../util" }
34 34ui = {package = "ui2", path = "../ui2"}
35workspace = {package = "workspace2", path = "../workspace2"}
36async-trait.workspace = true
35anyhow.workspace = true37anyhow.workspace = true
36async-broadcast = "0.4"38async-broadcast = "0.4"
37futures.workspace = true39futures.workspace = true
40image = "0.23"
38postage.workspace = true41postage.workspace = true
39schemars.workspace = true42schemars.workspace = true
40serde.workspace = true43serde.workspace = true
41serde_json.workspace = true44serde_json.workspace = true
42serde_derive.workspace = true45serde_derive.workspace = true
46smallvec.workspace = true
43 47
44[dev-dependencies]48[dev-dependencies]
45client = { package = "client2", path = "../client2", features = ["test-support"] }49client = { package = "client2", path = "../client2", features = ["test-support"] }
crates/call2/src/call2.rs+216−7
@@ -1,25 +1,32 @@
1pub mod call_settings;1pub mod call_settings;
2pub mod participant;2pub mod participant;
3pub mod room;3pub mod room;
4mod shared_screen;
4 5
5use anyhow::{anyhow, Result};6use anyhow::{anyhow, Result};
7use async_trait::async_trait;
6use audio::Audio;8use audio::Audio;
7use call_settings::CallSettings;9use call_settings::CallSettings;
8use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};10use client::{
11 proto::{self, PeerId},
12 Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE,
13};
9use collections::HashSet;14use collections::HashSet;
10use futures::{channel::oneshot, future::Shared, Future, FutureExt};15use futures::{channel::oneshot, future::Shared, Future, FutureExt};
11use gpui::{16use gpui::{
12 AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,17 AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
13 WeakModel,18 View, ViewContext, VisualContext, WeakModel, WeakView,
14};19};
20pub use participant::ParticipantLocation;
15use postage::watch;21use postage::watch;
16use project::Project;22use project::Project;
17use room::Event;23use room::Event;
24pub use room::Room;
18use settings::Settings;25use settings::Settings;
26use shared_screen::SharedScreen;
19use std::sync::Arc;27use std::sync::Arc;
20 28use util::ResultExt;
21pub use participant::ParticipantLocation;29use workspace::{item::ItemHandle, CallHandler, Pane, Workspace};
22pub use room::Room;
23 30
24pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {31pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
25 CallSettings::register(cx);32 CallSettings::register(cx);
@@ -464,7 +471,7 @@ impl ActiveCall { · 438 unmodified lines
464 &self.pending_invites471 &self.pending_invites
465 }472 }
466 473
467 pub fn report_call_event(&self, operation: &'static str, cx: &AppContext) {474 pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
468 if let Some(room) = self.room() {475 if let Some(room) = self.room() {
469 let room = room.read(cx);476 let room = room.read(cx);
470 report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx);477 report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx);
@@ -477,7 +484,7 @@ pub fn report_call_event_for_room( · 6 unmodified lines
477 room_id: u64,484 room_id: u64,
478 channel_id: Option<u64>,485 channel_id: Option<u64>,
479 client: &Arc<Client>,486 client: &Arc<Client>,
480 cx: &AppContext,487 cx: &mut AppContext,
481) {488) {
482 let telemetry = client.telemetry();489 let telemetry = client.telemetry();
483 let telemetry_settings = *TelemetrySettings::get_global(cx);490 let telemetry_settings = *TelemetrySettings::get_global(cx);
@@ -505,6 +512,208 @@ pub fn report_call_event_for_channel( · 21 unmodified lines
505 )512 )
506}513}
507 514
515pub struct Call {
516 active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
517 parent_workspace: WeakView<Workspace>,
518}
519
520impl 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)]
553impl CallHandler for Call {
554 fn peer_state(
555 &mut self,
556 leader_id: PeerId,
557 cx: &mut ViewContext<Workspace>,
558 ) -> Option<(bool, bool)> {
559 let (call, _) = self.active_call.as_ref()?;
560 let room = call.read(cx).room()?.read(cx);
561 let participant = room.remote_participant_for_peer_id(leader_id)?;
562
563 let leader_in_this_app;
564 let leader_in_this_project;
565 match participant.location {
566 ParticipantLocation::SharedProject { project_id } => {
567 leader_in_this_app = true;
568 leader_in_this_project = Some(project_id)
569 == self
570 .parent_workspace
571 .update(cx, |this, cx| this.project().read(cx).remote_id())
572 .log_err()
573 .flatten();
574 }
575 ParticipantLocation::UnsharedProject => {
576 leader_in_this_app = true;
577 leader_in_this_project = false;
578 }
579 ParticipantLocation::External => {
580 leader_in_this_app = false;
581 leader_in_this_project = false;
582 }
583 };
584
585 Some((leader_in_this_project, leader_in_this_app))
586 }
587
588 fn shared_screen_for_peer(
589 &self,
590 peer_id: PeerId,
591 pane: &View<Pane>,
592 cx: &mut ViewContext<Workspace>,
593 ) -> Option<Box<dyn ItemHandle>> {
594 let (call, _) = self.active_call.as_ref()?;
595 let room = call.read(cx).room()?.read(cx);
596 let participant = room.remote_participant_for_peer_id(peer_id)?;
597 let track = participant.video_tracks.values().next()?.clone();
598 let user = participant.user.clone();
599 for item in pane.read(cx).items_of_type::<SharedScreen>() {
600 if item.read(cx).peer_id == peer_id {
601 return Some(Box::new(item));
602 }
603 }
604
605 Some(Box::new(cx.build_view(|cx| {
606 SharedScreen::new(&track, peer_id, user.clone(), cx)
607 })))
608 }
609 fn room_id(&self, cx: &AppContext) -> Option<u64> {
610 Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id())
611 }
612 fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>> {
613 let Some((call, _)) = self.active_call.as_ref() else {
614 return Task::ready(Err(anyhow!("Cannot exit a call; not in a call")));
615 };
616
617 call.update(cx, |this, cx| this.hang_up(cx))
618 }
619 fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>> {
620 ActiveCall::global(cx).read(cx).location().cloned()
621 }
622 fn invite(
623 &mut self,
624 called_user_id: u64,
625 initial_project: Option<Model<Project>>,
626 cx: &mut AppContext,
627 ) -> Task<Result<()>> {
628 ActiveCall::global(cx).update(cx, |this, cx| {
629 this.invite(called_user_id, initial_project, cx)
630 })
631 }
632 fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>> {
633 self.active_call
634 .as_ref()
635 .map(|call| {
636 call.0.read(cx).room().map(|room| {
637 room.read(cx)
638 .remote_participants()
639 .iter()
640 .map(|participant| {
641 (participant.1.user.clone(), participant.1.peer_id.clone())
642 })
643 .collect()
644 })
645 })
646 .flatten()
647 }
648 fn is_muted(&self, cx: &AppContext) -> Option<bool> {
649 self.active_call
650 .as_ref()
651 .map(|call| {
652 call.0
653 .read(cx)
654 .room()
655 .map(|room| room.read(cx).is_muted(cx))
656 })
657 .flatten()
658 }
659 fn toggle_mute(&self, cx: &mut AppContext) {
660 self.active_call.as_ref().map(|call| {
661 call.0.update(cx, |this, cx| {
662 this.room().map(|room| {
663 let room = room.clone();
664 cx.spawn(|_, mut cx| async move {
665 room.update(&mut cx, |this, cx| this.toggle_mute(cx))??
666 .await
667 })
668 .detach_and_log_err(cx);
669 })
670 })
671 });
672 }
673 fn toggle_screen_share(&self, cx: &mut AppContext) {
674 self.active_call.as_ref().map(|call| {
675 call.0.update(cx, |this, cx| {
676 this.room().map(|room| {
677 room.update(cx, |this, cx| {
678 if this.is_screen_sharing() {
679 this.unshare_screen(cx).log_err();
680 } else {
681 let t = this.share_screen(cx);
682 cx.spawn(move |_, _| async move {
683 t.await.log_err();
684 })
685 .detach();
686 }
687 })
688 })
689 })
690 });
691 }
692 fn toggle_deafen(&self, cx: &mut AppContext) {
693 self.active_call.as_ref().map(|call| {
694 call.0.update(cx, |this, cx| {
695 this.room().map(|room| {
696 room.update(cx, |this, cx| {
697 this.toggle_deafen(cx).log_err();
698 })
699 })
700 })
701 });
702 }
703 fn is_deafened(&self, cx: &AppContext) -> Option<bool> {
704 self.active_call
705 .as_ref()
706 .map(|call| {
707 call.0
708 .read(cx)
709 .room()
710 .map(|room| room.read(cx).is_deafened())
711 })
712 .flatten()
713 .flatten()
714 }
715}
716
508#[cfg(test)]717#[cfg(test)]
509mod test {718mod test {
510 use gpui::TestAppContext;719 use gpui::TestAppContext;
crates/call2/src/participant.rs+1−1
@@ -4,7 +4,7 @@ use client::{proto, User}; · 3 unmodified lines
4use collections::HashMap;4use collections::HashMap;
5use gpui::WeakModel;5use gpui::WeakModel;
6pub use live_kit_client::Frame;6pub use live_kit_client::Frame;
7use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};7pub(crate) use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
8use project::Project;8use project::Project;
9use std::sync::Arc;9use std::sync::Arc;
10 10
crates/call2/src/room.rs+1−2
@@ -21,7 +21,7 @@ use live_kit_client::{ · 20 unmodified lines
21};21};
22use postage::{sink::Sink, stream::Stream, watch};22use postage::{sink::Sink, stream::Stream, watch};
23use project::Project;23use project::Project;
24use settings::Settings;24use settings::Settings as _;
25use std::{future::Future, mem, sync::Arc, time::Duration};25use std::{future::Future, mem, sync::Arc, time::Duration};
26use util::{post_inc, ResultExt, TryFutureExt};26use util::{post_inc, ResultExt, TryFutureExt};
27 27
@@ -1267,7 +1267,6 @@ impl Room { · 1239 unmodified lines
1267 .ok_or_else(|| anyhow!("live-kit was not initialized"))?1267 .ok_or_else(|| anyhow!("live-kit was not initialized"))?
1268 .await1268 .await
1269 };1269 };
1270
1271 let publication = publish_track.await;1270 let publication = publish_track.await;
1272 this.upgrade()1271 this.upgrade()
1273 .ok_or_else(|| anyhow!("room was dropped"))?1272 .ok_or_else(|| anyhow!("room was dropped"))?
crates/call2/src/shared_screen.rs+157−0
@@ -0,0 +1,157 @@
1use crate::participant::{Frame, RemoteVideoTrack};
2use anyhow::Result;
3use client::{proto::PeerId, User};
4use futures::StreamExt;
5use gpui::{
6 div, AppContext, Div, Element, EventEmitter, FocusHandle, FocusableView, ParentElement, Render,
7 SharedString, Task, View, ViewContext, VisualContext, WindowContext,
8};
9use std::sync::{Arc, Weak};
10use workspace::{item::Item, ItemNavHistory, WorkspaceId};
11
12pub enum Event {
13 Close,
14}
15
16pub 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
28impl 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
59impl EventEmitter<Event> for SharedScreen {}
60impl EventEmitter<workspace::item::ItemEvent> for SharedScreen {}
61
62impl FocusableView for SharedScreen {
63 fn focus_handle(&self, _: &AppContext) -> FocusHandle {
64 self.focus.clone()
65 }
66}
67impl 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
113impl 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
18gpui = { package = "gpui2", path = "../gpui2" }18gpui = { package = "gpui2", path = "../gpui2" }
19util = { path = "../util" }19util = { path = "../util" }
20rpc = { package = "rpc2", path = "../rpc2" }20rpc = { package = "rpc2", path = "../rpc2" }
21text = { path = "../text" }21text = { package = "text2", path = "../text2" }
22language = { package = "language2", path = "../language2" }22language = { package = "language2", path = "../language2" }
23settings = { package = "settings2", path = "../settings2" }23settings = { package = "settings2", path = "../settings2" }
24feature_flags = { package = "feature_flags2", path = "../feature_flags2" }24feature_flags = { package = "feature_flags2", path = "../feature_flags2" }
crates/client/src/telemetry.rs+26−12
@@ -109,6 +109,10 @@ pub enum ClickhouseEvent { · 108 unmodified lines
109 virtual_memory_in_bytes: u64,109 virtual_memory_in_bytes: u64,
110 milliseconds_since_first_event: i64,110 milliseconds_since_first_event: i64,
111 },111 },
112 App {
113 operation: &'static str,
114 milliseconds_since_first_event: i64,
115 },
112}116}
113 117
114#[cfg(debug_assertions)]118#[cfg(debug_assertions)]
@@ -168,13 +172,8 @@ impl Telemetry { · 53 unmodified lines
168 let mut state = self.state.lock();172 let mut state = self.state.lock();
169 state.installation_id = installation_id.map(|id| id.into());173 state.installation_id = installation_id.map(|id| id.into());
170 state.session_id = Some(session_id.into());174 state.session_id = Some(session_id.into());
171 let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
172 drop(state);175 drop(state);
173 176
174 if has_clickhouse_events {
175 self.flush_clickhouse_events();
176 }
177
178 let this = self.clone();177 let this = self.clone();
179 cx.spawn(|mut cx| async move {178 cx.spawn(|mut cx| async move {
180 // Avoiding calling `System::new_all()`, as there have been crashes related to it179 // Avoiding calling `System::new_all()`, as there have been crashes related to it
@@ -256,7 +255,7 @@ impl Telemetry { · 75 unmodified lines
256 milliseconds_since_first_event: self.milliseconds_since_first_event(),255 milliseconds_since_first_event: self.milliseconds_since_first_event(),
257 };256 };
258 257
259 self.report_clickhouse_event(event, telemetry_settings)258 self.report_clickhouse_event(event, telemetry_settings, false)
260 }259 }
261 260
262 pub fn report_copilot_event(261 pub fn report_copilot_event(
@@ -273,7 +272,7 @@ impl Telemetry { · 10 unmodified lines
273 milliseconds_since_first_event: self.milliseconds_since_first_event(),272 milliseconds_since_first_event: self.milliseconds_since_first_event(),
274 };273 };
275 274
276 self.report_clickhouse_event(event, telemetry_settings)275 self.report_clickhouse_event(event, telemetry_settings, false)
277 }276 }
278 277
279 pub fn report_assistant_event(278 pub fn report_assistant_event(
@@ -290,7 +289,7 @@ impl Telemetry { · 10 unmodified lines
290 milliseconds_since_first_event: self.milliseconds_since_first_event(),289 milliseconds_since_first_event: self.milliseconds_since_first_event(),
291 };290 };
292 291
293 self.report_clickhouse_event(event, telemetry_settings)292 self.report_clickhouse_event(event, telemetry_settings, false)
294 }293 }
295 294
296 pub fn report_call_event(295 pub fn report_call_event(
@@ -307,7 +306,7 @@ impl Telemetry { · 10 unmodified lines
307 milliseconds_since_first_event: self.milliseconds_since_first_event(),306 milliseconds_since_first_event: self.milliseconds_since_first_event(),
308 };307 };
309 308
310 self.report_clickhouse_event(event, telemetry_settings)309 self.report_clickhouse_event(event, telemetry_settings, false)
311 }310 }
312 311
313 pub fn report_cpu_event(312 pub fn report_cpu_event(
@@ -322,7 +321,7 @@ impl Telemetry { · 8 unmodified lines
322 milliseconds_since_first_event: self.milliseconds_since_first_event(),321 milliseconds_since_first_event: self.milliseconds_since_first_event(),
323 };322 };
324 323
325 self.report_clickhouse_event(event, telemetry_settings)324 self.report_clickhouse_event(event, telemetry_settings, false)
326 }325 }
327 326
328 pub fn report_memory_event(327 pub fn report_memory_event(
@@ -337,7 +336,21 @@ impl Telemetry { · 8 unmodified lines
337 milliseconds_since_first_event: self.milliseconds_since_first_event(),336 milliseconds_since_first_event: self.milliseconds_since_first_event(),
338 };337 };
339 338
340 self.report_clickhouse_event(event, telemetry_settings)339 self.report_clickhouse_event(event, telemetry_settings, false)
340 }
341
342 // app_events are called at app open and app close, so flush is set to immediately send
343 pub fn report_app_event(
344 self: &Arc<Self>,
345 telemetry_settings: TelemetrySettings,
346 operation: &'static str,
347 ) {
348 let event = ClickhouseEvent::App {
349 operation,
350 milliseconds_since_first_event: self.milliseconds_since_first_event(),
351 };
352
353 self.report_clickhouse_event(event, telemetry_settings, true)
341 }354 }
342 355
343 fn milliseconds_since_first_event(&self) -> i64 {356 fn milliseconds_since_first_event(&self) -> i64 {
@@ -358,6 +371,7 @@ impl Telemetry { · 14 unmodified lines
358 self: &Arc<Self>,371 self: &Arc<Self>,
359 event: ClickhouseEvent,372 event: ClickhouseEvent,
360 telemetry_settings: TelemetrySettings,373 telemetry_settings: TelemetrySettings,
374 immediate_flush: bool,
361 ) {375 ) {
362 if !telemetry_settings.metrics {376 if !telemetry_settings.metrics {
363 return;377 return;
@@ -370,7 +384,7 @@ impl Telemetry { · 6 unmodified lines
370 .push(ClickhouseEventWrapper { signed_in, event });384 .push(ClickhouseEventWrapper { signed_in, event });
371 385
372 if state.installation_id.is_some() {386 if state.installation_id.is_some() {
373 if state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {387 if immediate_flush || state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
374 drop(state);388 drop(state);
375 self.flush_clickhouse_events();389 self.flush_clickhouse_events();
376 } else {390 } else {
crates/client2/src/client2.rs+1−2
@@ -382,7 +382,7 @@ impl settings::Settings for TelemetrySettings { · 381 unmodified lines
382}382}
383 383
384impl Client {384impl Client {
385 pub fn new(http: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {385 pub fn new(http: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
386 Arc::new(Self {386 Arc::new(Self {
387 id: AtomicU64::new(0),387 id: AtomicU64::new(0),
388 peer: Peer::new(0),388 peer: Peer::new(0),
@@ -551,7 +551,6 @@ impl Client { · 162 unmodified lines
551 F: 'static + Future<Output = Result<()>>,551 F: 'static + Future<Output = Result<()>>,
552 {552 {
553 let message_type_id = TypeId::of::<M>();553 let message_type_id = TypeId::of::<M>();
554
555 let mut state = self.state.write();554 let mut state = self.state.write();
556 state555 state
557 .models_by_message_type556 .models_by_message_type
crates/client2/src/telemetry.rs+50−13
@@ -1,5 +1,6 @@
1use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};1use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
2use chrono::{DateTime, Utc};2use chrono::{DateTime, Utc};
3use futures::Future;
3use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task};4use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task};
4use lazy_static::lazy_static;5use lazy_static::lazy_static;
5use parking_lot::Mutex;6use parking_lot::Mutex;
@@ -107,6 +108,10 @@ pub enum ClickhouseEvent { · 101 unmodified lines
107 virtual_memory_in_bytes: u64,108 virtual_memory_in_bytes: u64,
108 milliseconds_since_first_event: i64,109 milliseconds_since_first_event: i64,
109 },110 },
111 App {
112 operation: &'static str,
113 milliseconds_since_first_event: i64,
114 },
110}115}
111 116
112#[cfg(debug_assertions)]117#[cfg(debug_assertions)]
@@ -122,12 +127,13 @@ const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1); · 9 unmodified lines
122const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30);127const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30);
123 128
124impl Telemetry {129impl Telemetry {
125 pub fn new(client: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {130 pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
126 let release_channel = if cx.has_global::<ReleaseChannel>() {131 let release_channel = if cx.has_global::<ReleaseChannel>() {
127 Some(cx.global::<ReleaseChannel>().display_name())132 Some(cx.global::<ReleaseChannel>().display_name())
128 } else {133 } else {
129 None134 None
130 };135 };
136
131 // TODO: Replace all hardware stuff with nested SystemSpecs json137 // TODO: Replace all hardware stuff with nested SystemSpecs json
132 let this = Arc::new(Self {138 let this = Arc::new(Self {
133 http_client: client,139 http_client: client,
@@ -147,9 +153,30 @@ impl Telemetry { · 13 unmodified lines
147 }),153 }),
148 });154 });
149 155
156 // We should only ever have one instance of Telemetry, leak the subscription to keep it alive
157 // rather than store in TelemetryState, complicating spawn as subscriptions are not Send
158 std::mem::forget(cx.on_app_quit({
159 let this = this.clone();
160 move |cx| this.shutdown_telemetry(cx)
161 }));
162
150 this163 this
151 }164 }
152 165
166 #[cfg(any(test, feature = "test-support"))]
167 fn shutdown_telemetry(self: &Arc<Self>, _: &mut AppContext) -> impl Future<Output = ()> {
168 Task::ready(())
169 }
170
171 // Skip calling this function in tests.
172 // TestAppContext ends up calling this function on shutdown and it panics when trying to find the TelemetrySettings
173 #[cfg(not(any(test, feature = "test-support")))]
174 fn shutdown_telemetry(self: &Arc<Self>, cx: &mut AppContext) -> impl Future<Output = ()> {
175 let telemetry_settings = TelemetrySettings::get_global(cx).clone();
176 self.report_app_event(telemetry_settings, "close");
177 Task::ready(())
178 }
179
153 pub fn log_file_path(&self) -> Option<PathBuf> {180 pub fn log_file_path(&self) -> Option<PathBuf> {
154 Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())181 Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
155 }182 }
@@ -163,13 +190,8 @@ impl Telemetry { · 7 unmodified lines
163 let mut state = self.state.lock();190 let mut state = self.state.lock();
164 state.installation_id = installation_id.map(|id| id.into());191 state.installation_id = installation_id.map(|id| id.into());
165 state.session_id = Some(session_id.into());192 state.session_id = Some(session_id.into());
166 let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
167 drop(state);193 drop(state);
168 194
169 if has_clickhouse_events {
170 self.flush_clickhouse_events();
171 }
172
173 let this = self.clone();195 let this = self.clone();
174 cx.spawn(|cx| async move {196 cx.spawn(|cx| async move {
175 // Avoiding calling `System::new_all()`, as there have been crashes related to it197 // Avoiding calling `System::new_all()`, as there have been crashes related to it
@@ -257,7 +279,7 @@ impl Telemetry { · 81 unmodified lines
257 milliseconds_since_first_event: self.milliseconds_since_first_event(),279 milliseconds_since_first_event: self.milliseconds_since_first_event(),
258 };280 };
259 281
260 self.report_clickhouse_event(event, telemetry_settings)282 self.report_clickhouse_event(event, telemetry_settings, false)
261 }283 }
262 284
263 pub fn report_copilot_event(285 pub fn report_copilot_event(
@@ -274,7 +296,7 @@ impl Telemetry { · 10 unmodified lines
274 milliseconds_since_first_event: self.milliseconds_since_first_event(),296 milliseconds_since_first_event: self.milliseconds_since_first_event(),
275 };297 };
276 298
277 self.report_clickhouse_event(event, telemetry_settings)299 self.report_clickhouse_event(event, telemetry_settings, false)
278 }300 }
279 301
280 pub fn report_assistant_event(302 pub fn report_assistant_event(
@@ -291,7 +313,7 @@ impl Telemetry { · 10 unmodified lines
291 milliseconds_since_first_event: self.milliseconds_since_first_event(),313 milliseconds_since_first_event: self.milliseconds_since_first_event(),
292 };314 };
293 315
294 self.report_clickhouse_event(event, telemetry_settings)316 self.report_clickhouse_event(event, telemetry_settings, false)
295 }317 }
296 318
297 pub fn report_call_event(319 pub fn report_call_event(
@@ -308,7 +330,7 @@ impl Telemetry { · 10 unmodified lines
308 milliseconds_since_first_event: self.milliseconds_since_first_event(),330 milliseconds_since_first_event: self.milliseconds_since_first_event(),
309 };331 };
310 332
311 self.report_clickhouse_event(event, telemetry_settings)333 self.report_clickhouse_event(event, telemetry_settings, false)
312 }334 }
313 335
314 pub fn report_cpu_event(336 pub fn report_cpu_event(
@@ -323,7 +345,7 @@ impl Telemetry { · 8 unmodified lines
323 milliseconds_since_first_event: self.milliseconds_since_first_event(),345 milliseconds_since_first_event: self.milliseconds_since_first_event(),
324 };346 };
325 347
326 self.report_clickhouse_event(event, telemetry_settings)348 self.report_clickhouse_event(event, telemetry_settings, false)
327 }349 }
328 350
329 pub fn report_memory_event(351 pub fn report_memory_event(
@@ -338,7 +360,21 @@ impl Telemetry { · 8 unmodified lines
338 milliseconds_since_first_event: self.milliseconds_since_first_event(),360 milliseconds_since_first_event: self.milliseconds_since_first_event(),
339 };361 };
340 362
341 self.report_clickhouse_event(event, telemetry_settings)363 self.report_clickhouse_event(event, telemetry_settings, false)
364 }
365
366 // app_events are called at app open and app close, so flush is set to immediately send
367 pub fn report_app_event(
368 self: &Arc<Self>,
369 telemetry_settings: TelemetrySettings,
370 operation: &'static str,
371 ) {
372 let event = ClickhouseEvent::App {
373 operation,
374 milliseconds_since_first_event: self.milliseconds_since_first_event(),
375 };
376
377 self.report_clickhouse_event(event, telemetry_settings, true)
342 }378 }
343 379
344 fn milliseconds_since_first_event(&self) -> i64 {380 fn milliseconds_since_first_event(&self) -> i64 {
@@ -359,6 +395,7 @@ impl Telemetry { · 14 unmodified lines
359 self: &Arc<Self>,395 self: &Arc<Self>,
360 event: ClickhouseEvent,396 event: ClickhouseEvent,
361 telemetry_settings: TelemetrySettings,397 telemetry_settings: TelemetrySettings,
398 immediate_flush: bool,
362 ) {399 ) {
363 if !telemetry_settings.metrics {400 if !telemetry_settings.metrics {
364 return;401 return;
@@ -371,7 +408,7 @@ impl Telemetry { · 6 unmodified lines
371 .push(ClickhouseEventWrapper { signed_in, event });408 .push(ClickhouseEventWrapper { signed_in, event });
372 409
373 if state.installation_id.is_some() {410 if state.installation_id.is_some() {
374 if state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {411 if immediate_flush || state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
375 drop(state);412 drop(state);
376 self.flush_clickhouse_events();413 self.flush_clickhouse_events();
377 } else {414 } else {
crates/collab/Cargo.toml+1−1
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"] · 2 unmodified lines
3default-run = "collab"3default-run = "collab"
4edition = "2021"4edition = "2021"
5name = "collab"5name = "collab"
6version = "0.28.0"6version = "0.29.0"
7publish = false7publish = false
8 8
9[[bin]]9[[bin]]
crates/collab2/Cargo.toml+1−1
@@ -10,7 +10,7 @@ publish = false · 9 unmodified lines
10name = "collab2"10name = "collab2"
11 11
12[[bin]]12[[bin]]
13name = "seed"13name = "seed2"
14required-features = ["seed-support"]14required-features = ["seed-support"]
15 15
16[dependencies]16[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
149 .user_id149 .user_id
150 };150 };
151 let client_name = name.to_string();151 let client_name = name.to_string();
152 let mut client = cx.read(|cx| Client::new(http.clone(), cx));152 let mut client = cx.update(|cx| Client::new(http.clone(), cx));
153 let server = self.server.clone();153 let server = self.server.clone();
154 let db = self.app_state.db.clone();154 let db = self.app_state.db.clone();
155 let connection_killers = self.connection_killers.clone();155 let connection_killers = self.connection_killers.clone();
@@ -221,6 +221,7 @@ impl TestServer { · 65 unmodified lines
221 fs: fs.clone(),221 fs: fs.clone(),
222 build_window_options: |_, _, _| Default::default(),222 build_window_options: |_, _, _| Default::default(),
223 node_runtime: FakeNodeRuntime::new(),223 node_runtime: FakeNodeRuntime::new(),
224 call_factory: |_, _| Box::new(workspace::TestCallHandler),
224 });225 });
225 226
226 cx.update(|cx| {227 cx.update(|cx| {
crates/collab_ui2/Cargo.toml+1−1
@@ -33,7 +33,7 @@ collections = { path = "../collections" } · 32 unmodified lines
33# drag_and_drop = { path = "../drag_and_drop" }33# drag_and_drop = { path = "../drag_and_drop" }
34editor = { package="editor2", path = "../editor2" }34editor = { package="editor2", path = "../editor2" }
35#feedback = { path = "../feedback" }35#feedback = { path = "../feedback" }
36fuzzy = { path = "../fuzzy" }36fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
37gpui = { package = "gpui2", path = "../gpui2" }37gpui = { package = "gpui2", path = "../gpui2" }
38language = { package = "language2", path = "../language2" }38language = { package = "language2", path = "../language2" }
39menu = { package = "menu2", path = "../menu2" }39menu = { package = "menu2", path = "../menu2" }
crates/collab_ui2/src/collab_panel.rs+1383−1286
@@ -1,5 +1,6 @@
1#![allow(unused)]
1// mod channel_modal;2// mod channel_modal;
2// mod contact_finder;3mod contact_finder;
3 4
4// use crate::{5// use crate::{
5// channel_view::{self, ChannelView},6// channel_view::{self, ChannelView},
@@ -15,7 +16,7 @@ · 9 unmodified lines
15// proto::{self, PeerId},16// proto::{self, PeerId},
16// Client, Contact, User, UserStore,17// Client, Contact, User, UserStore,
17// };18// };
18// use contact_finder::ContactFinder;19use contact_finder::ContactFinder;
19// use context_menu::{ContextMenu, ContextMenuItem};20// use context_menu::{ContextMenu, ContextMenuItem};
20// use db::kvp::KEY_VALUE_STORE;21// use db::kvp::KEY_VALUE_STORE;
21// use drag_and_drop::{DragAndDrop, Draggable};22// use drag_and_drop::{DragAndDrop, Draggable};
@@ -155,20 +156,30 @@ actions!( · 133 unmodified lines
155 156
156const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";157const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
157 158
158use std::sync::Arc;159use std::{iter::once, mem, sync::Arc};
159 160
161use call::ActiveCall;
162use channel::{Channel, ChannelId, ChannelStore};
163use client::{Client, Contact, User, UserStore};
160use db::kvp::KEY_VALUE_STORE;164use db::kvp::KEY_VALUE_STORE;
165use editor::Editor;
166use feature_flags::{ChannelsAlpha, FeatureFlagAppExt};
167use fuzzy::{match_strings, StringMatchCandidate};
161use gpui::{168use gpui::{
162 actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle,169 actions, div, img, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle,
163 Focusable, FocusableView, InteractiveComponent, ParentComponent, Render, View, ViewContext,170 Focusable, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render,
164 VisualContext, WeakView,171 RenderOnce, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
165};172};
166use project::Fs;173use project::Fs;
167use serde_derive::{Deserialize, Serialize};174use serde_derive::{Deserialize, Serialize};
168use settings::Settings;175use settings::Settings;
169use util::ResultExt;176use ui::{
177 h_stack, v_stack, Avatar, Button, Icon, IconButton, Label, List, ListHeader, ListItem, Tooltip,
178};
179use util::{maybe, ResultExt};
170use workspace::{180use workspace::{
171 dock::{DockPosition, Panel, PanelEvent},181 dock::{DockPosition, Panel, PanelEvent},
182 notifications::NotifyResultExt,
172 Workspace,183 Workspace,
173};184};
174 185
@@ -266,26 +277,26 @@ pub fn init(cx: &mut AppContext) { · 91 unmodified lines
266 // );277 // );
267}278}
268 279
269// #[derive(Debug)]280#[derive(Debug)]
270// pub enum ChannelEditingState {281pub enum ChannelEditingState {
271// Create {282 Create {
272// location: Option<ChannelId>,283 location: Option<ChannelId>,
273// pending_name: Option<String>,284 pending_name: Option<String>,
274// },285 },
275// Rename {286 Rename {
276// location: ChannelId,287 location: ChannelId,
277// pending_name: Option<String>,288 pending_name: Option<String>,
278// },289 },
279// }290}
280 291
281// impl ChannelEditingState {292impl ChannelEditingState {
282// fn pending_name(&self) -> Option<&str> {293 fn pending_name(&self) -> Option<&str> {
283// match self {294 match self {
284// ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(),295 ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(),
285// ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(),296 ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(),
286// }297 }
287// }298 }
288// }299}
289 300
290pub struct CollabPanel {301pub struct CollabPanel {
291 width: Option<f32>,302 width: Option<f32>,
@@ -294,22 +305,22 @@ pub struct CollabPanel { · 2 unmodified lines
294 // channel_clipboard: Option<ChannelMoveClipboard>,305 // channel_clipboard: Option<ChannelMoveClipboard>,
295 // pending_serialization: Task<Option<()>>,306 // pending_serialization: Task<Option<()>>,
296 // context_menu: ViewHandle<ContextMenu>,307 // context_menu: ViewHandle<ContextMenu>,
297 // filter_editor: ViewHandle<Editor>,308 filter_editor: View<Editor>,
298 // channel_name_editor: ViewHandle<Editor>,309 // channel_name_editor: ViewHandle<Editor>,
299 // channel_editing_state: Option<ChannelEditingState>,310 channel_editing_state: Option<ChannelEditingState>,
300 // entries: Vec<ListEntry>,311 entries: Vec<ListEntry>,
301 // selection: Option<usize>,312 // selection: Option<usize>,
302 // user_store: ModelHandle<UserStore>,313 channel_store: Model<ChannelStore>,
303 // client: Arc<Client>,314 user_store: Model<UserStore>,
304 // channel_store: ModelHandle<ChannelStore>,315 client: Arc<Client>,
305 // project: ModelHandle<Project>,316 // project: ModelHandle<Project>,
306 // match_candidates: Vec<StringMatchCandidate>,317 match_candidates: Vec<StringMatchCandidate>,
307 // list_state: ListState<Self>,318 // list_state: ListState<Self>,
308 // subscriptions: Vec<Subscription>,319 subscriptions: Vec<Subscription>,
309 // collapsed_sections: Vec<Section>,320 collapsed_sections: Vec<Section>,
310 // collapsed_channels: Vec<ChannelId>,321 collapsed_channels: Vec<ChannelId>,
311 // drag_target_channel: ChannelDragTarget,322 // drag_target_channel: ChannelDragTarget,
312 _workspace: WeakView<Workspace>,323 workspace: WeakView<Workspace>,
313 // context_menu_on_selected: bool,324 // context_menu_on_selected: bool,
314}325}
315 326
@@ -333,58 +344,58 @@ struct SerializedCollabPanel { · 17 unmodified lines
333// Dismissed,344// Dismissed,
334// }345// }
335 346
336// #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]347#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
337// enum Section {348enum Section {
338// ActiveCall,349 ActiveCall,
339// Channels,350 Channels,
340// ChannelInvites,351 ChannelInvites,
341// ContactRequests,352 ContactRequests,
342// Contacts,353 Contacts,
343// Online,354 Online,
344// Offline,355 Offline,
345// }356}
346 357
347// #[derive(Clone, Debug)]358#[derive(Clone, Debug)]
348// enum ListEntry {359enum ListEntry {
349// Header(Section),360 Header(Section),
350// CallParticipant {361 // CallParticipant {
351// user: Arc<User>,362 // user: Arc<User>,
352// peer_id: Option<PeerId>,363 // peer_id: Option<PeerId>,
353// is_pending: bool,364 // is_pending: bool,
354// },365 // },
355// ParticipantProject {366 // ParticipantProject {
356// project_id: u64,367 // project_id: u64,
357// worktree_root_names: Vec<String>,368 // worktree_root_names: Vec<String>,
358// host_user_id: u64,369 // host_user_id: u64,
359// is_last: bool,370 // is_last: bool,
360// },371 // },
361// ParticipantScreen {372 // ParticipantScreen {
362// peer_id: Option<PeerId>,373 // peer_id: Option<PeerId>,
363// is_last: bool,374 // is_last: bool,
364// },375 // },
365// IncomingRequest(Arc<User>),376 IncomingRequest(Arc<User>),
366// OutgoingRequest(Arc<User>),377 OutgoingRequest(Arc<User>),
367// ChannelInvite(Arc<Channel>),378 // ChannelInvite(Arc<Channel>),
368// Channel {379 Channel {
369// channel: Arc<Channel>,380 channel: Arc<Channel>,
370// depth: usize,381 depth: usize,
371// has_children: bool,382 has_children: bool,
372// },383 },
373// ChannelNotes {384 // ChannelNotes {
374// channel_id: ChannelId,385 // channel_id: ChannelId,
375// },386 // },
376// ChannelChat {387 // ChannelChat {
377// channel_id: ChannelId,388 // channel_id: ChannelId,
378// },389 // },
379// ChannelEditor {390 ChannelEditor {
380// depth: usize,391 depth: usize,
381// },392 },
382// Contact {393 Contact {
383// contact: Arc<Contact>,394 contact: Arc<Contact>,
384// calling: bool,395 calling: bool,
385// },396 },
386// ContactPlaceholder,397 ContactPlaceholder,
387// }398}
388 399
389// impl Entity for CollabPanel {400// impl Entity for CollabPanel {
390// type Event = Event;401// type Event = Event;
@@ -395,16 +406,11 @@ impl CollabPanel { · 4 unmodified lines
395 cx.build_view(|cx| {406 cx.build_view(|cx| {
396 // let view_id = cx.view_id();407 // let view_id = cx.view_id();
397 408
398 // let filter_editor = cx.add_view(|cx| {409 let filter_editor = cx.build_view(|cx| {
399 // let mut editor = Editor::single_line(410 let mut editor = Editor::single_line(cx);
400 // Some(Arc::new(|theme| {411 editor.set_placeholder_text("Filter channels, contacts", cx);
401 // theme.collab_panel.user_query_editor.clone()412 editor
402 // })),413 });
403 // cx,
404 // );
405 // editor.set_placeholder_text("Filter channels, contacts", cx);
406 // editor
407 // });
408 414
409 // cx.subscribe(&filter_editor, |this, _, event, cx| {415 // cx.subscribe(&filter_editor, |this, _, event, cx| {
410 // if let editor::Event::BufferEdited = event {416 // if let editor::Event::BufferEdited = event {
@@ -583,7 +589,7 @@ impl CollabPanel { · 172 unmodified lines
583 // }589 // }
584 // });590 // });
585 591
586 let this = Self {592 let mut this = Self {
587 width: None,593 width: None,
588 focus_handle: cx.focus_handle(),594 focus_handle: cx.focus_handle(),
589 // channel_clipboard: None,595 // channel_clipboard: None,
@@ -591,25 +597,25 @@ impl CollabPanel { · 1 unmodified line
591 // pending_serialization: Task::ready(None),597 // pending_serialization: Task::ready(None),
592 // context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),598 // context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
593 // channel_name_editor,599 // channel_name_editor,
594 // filter_editor,600 filter_editor,
595 // entries: Vec::default(),601 entries: Vec::default(),
596 // channel_editing_state: None,602 channel_editing_state: None,
597 // selection: None,603 // selection: None,
598 // user_store: workspace.user_store().clone(),604 channel_store: ChannelStore::global(cx),
599 // channel_store: ChannelStore::global(cx),605 user_store: workspace.user_store().clone(),
600 // project: workspace.project().clone(),606 // project: workspace.project().clone(),
601 // subscriptions: Vec::default(),607 subscriptions: Vec::default(),
602 // match_candidates: Vec::default(),608 match_candidates: Vec::default(),
603 // collapsed_sections: vec![Section::Offline],609 collapsed_sections: vec![Section::Offline],
604 // collapsed_channels: Vec::default(),610 collapsed_channels: Vec::default(),
605 _workspace: workspace.weak_handle(),611 workspace: workspace.weak_handle(),
606 // client: workspace.app_state().client.clone(),612 client: workspace.app_state().client.clone(),
607 // context_menu_on_selected: true,613 // context_menu_on_selected: true,
608 // drag_target_channel: ChannelDragTarget::None,614 // drag_target_channel: ChannelDragTarget::None,
609 // list_state,615 // list_state,
610 };616 };
611 617
612 // this.update_entries(false, cx);618 this.update_entries(false, cx);
613 619
614 // // Update the dock position when the setting changes.620 // // Update the dock position when the setting changes.
615 // let mut old_dock_position = this.position(cx);621 // let mut old_dock_position = this.position(cx);
@@ -626,10 +632,10 @@ impl CollabPanel { · 10 unmodified lines
626 // );632 // );
627 633
628 // let active_call = ActiveCall::global(cx);634 // let active_call = ActiveCall::global(cx);
629 // this.subscriptions635 this.subscriptions
630 // .push(cx.observe(&this.user_store, |this, _, cx| {636 .push(cx.observe(&this.user_store, |this, _, cx| {
631 // this.update_entries(true, cx)637 this.update_entries(true, cx)
632 // }));638 }));
633 // this.subscriptions639 // this.subscriptions
634 // .push(cx.observe(&this.channel_store, |this, _, cx| {640 // .push(cx.observe(&this.channel_store, |this, _, cx| {
635 // this.update_entries(true, cx)641 // this.update_entries(true, cx)
@@ -663,6 +669,9 @@ impl CollabPanel { · 27 unmodified lines
663 })669 })
664 }670 }
665 671
672 fn contacts(&self, cx: &AppContext) -> Option<Vec<Arc<Contact>>> {
673 Some(self.user_store.read(cx).contacts().to_owned())
674 }
666 pub async fn load(675 pub async fn load(
667 workspace: WeakView<Workspace>,676 workspace: WeakView<Workspace>,
668 mut cx: AsyncWindowContext,677 mut cx: AsyncWindowContext,
@@ -715,449 +724,449 @@ impl CollabPanel { · 46 unmodified lines
715 // );724 // );
716 // }725 // }
717 726
718 // fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {727 fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {
719 // let channel_store = self.channel_store.read(cx);728 let channel_store = self.channel_store.read(cx);
720 // let user_store = self.user_store.read(cx);729 let user_store = self.user_store.read(cx);
721 // let query = self.filter_editor.read(cx).text(cx);730 let query = self.filter_editor.read(cx).text(cx);
722 // let executor = cx.background().clone();731 let executor = cx.background_executor().clone();
723 732
724 // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());733 // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
725 // let old_entries = mem::take(&mut self.entries);734 let _old_entries = mem::take(&mut self.entries);
726 // let mut scroll_to_top = false;735 // let mut scroll_to_top = false;
727 736
728 // if let Some(room) = ActiveCall::global(cx).read(cx).room() {737 // if let Some(room) = ActiveCall::global(cx).read(cx).room() {
729 // self.entries.push(ListEntry::Header(Section::ActiveCall));738 // self.entries.push(ListEntry::Header(Section::ActiveCall));
730 // if !old_entries739 // if !old_entries
731 // .iter()740 // .iter()
732 // .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))741 // .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
733 // {742 // {
734 // scroll_to_top = true;743 // scroll_to_top = true;
735 // }744 // }
736 745
737 // if !self.collapsed_sections.contains(&Section::ActiveCall) {746 // if !self.collapsed_sections.contains(&Section::ActiveCall) {
738 // let room = room.read(cx);747 // let room = room.read(cx);
739 748
740 // if let Some(channel_id) = room.channel_id() {749 // if let Some(channel_id) = room.channel_id() {
741 // self.entries.push(ListEntry::ChannelNotes { channel_id });750 // self.entries.push(ListEntry::ChannelNotes { channel_id });
742 // self.entries.push(ListEntry::ChannelChat { channel_id })751 // self.entries.push(ListEntry::ChannelChat { channel_id })
743 // }752 // }
744 753
745 // // Populate the active user.754 // // Populate the active user.
746 // if let Some(user) = user_store.current_user() {755 // if let Some(user) = user_store.current_user() {
747 // self.match_candidates.clear();756 // self.match_candidates.clear();
748 // self.match_candidates.push(StringMatchCandidate {757 // self.match_candidates.push(StringMatchCandidate {
749 // id: 0,758 // id: 0,
750 // string: user.github_login.clone(),759 // string: user.github_login.clone(),
751 // char_bag: user.github_login.chars().collect(),760 // char_bag: user.github_login.chars().collect(),
752 // });761 // });
753 // let matches = executor.block(match_strings(762 // let matches = executor.block(match_strings(
754 // &self.match_candidates,763 // &self.match_candidates,
755 // &query,764 // &query,
756 // true,765 // true,
757 // usize::MAX,766 // usize::MAX,
758 // &Default::default(),767 // &Default::default(),
759 // executor.clone(),768 // executor.clone(),
760 // ));769 // ));
761 // if !matches.is_empty() {770 // if !matches.is_empty() {
762 // let user_id = user.id;771 // let user_id = user.id;
763 // self.entries.push(ListEntry::CallParticipant {772 // self.entries.push(ListEntry::CallParticipant {
764 // user,773 // user,
765 // peer_id: None,774 // peer_id: None,
766 // is_pending: false,775 // is_pending: false,
767 // });776 // });
768 // let mut projects = room.local_participant().projects.iter().peekable();777 // let mut projects = room.local_participant().projects.iter().peekable();
769 // while let Some(project) = projects.next() {778 // while let Some(project) = projects.next() {
770 // self.entries.push(ListEntry::ParticipantProject {779 // self.entries.push(ListEntry::ParticipantProject {
771 // project_id: project.id,780 // project_id: project.id,
772 // worktree_root_names: project.worktree_root_names.clone(),781 // worktree_root_names: project.worktree_root_names.clone(),
773 // host_user_id: user_id,782 // host_user_id: user_id,
774 // is_last: projects.peek().is_none() && !room.is_screen_sharing(),783 // is_last: projects.peek().is_none() && !room.is_screen_sharing(),
775 // });784 // });
776 // }785 // }
777 // if room.is_screen_sharing() {786 // if room.is_screen_sharing() {
778 // self.entries.push(ListEntry::ParticipantScreen {787 // self.entries.push(ListEntry::ParticipantScreen {
779 // peer_id: None,788 // peer_id: None,
780 // is_last: true,789 // is_last: true,
781 // });790 // });
782 // }791 // }
783 // }792 // }
784 // }793 // }
785 794
786 // // Populate remote participants.795 // // Populate remote participants.
787 // self.match_candidates.clear();796 // self.match_candidates.clear();
788 // self.match_candidates797 // self.match_candidates
789 // .extend(room.remote_participants().iter().map(|(_, participant)| {798 // .extend(room.remote_participants().iter().map(|(_, participant)| {
790 // StringMatchCandidate {799 // StringMatchCandidate {
791 // id: participant.user.id as usize,800 // id: participant.user.id as usize,
792 // string: participant.user.github_login.clone(),801 // string: participant.user.github_login.clone(),
793 // char_bag: participant.user.github_login.chars().collect(),802 // char_bag: participant.user.github_login.chars().collect(),
794 // }803 // }
795 // }));804 // }));
796 // let matches = executor.block(match_strings(805 // let matches = executor.block(match_strings(
797 // &self.match_candidates,806 // &self.match_candidates,
798 // &query,807 // &query,
799 // true,808 // true,
800 // usize::MAX,809 // usize::MAX,
801 // &Default::default(),810 // &Default::default(),
802 // executor.clone(),811 // executor.clone(),
803 // ));812 // ));
804 // for mat in matches {813 // for mat in matches {
805 // let user_id = mat.candidate_id as u64;814 // let user_id = mat.candidate_id as u64;
806 // let participant = &room.remote_participants()[&user_id];815 // let participant = &room.remote_participants()[&user_id];
807 // self.entries.push(ListEntry::CallParticipant {816 // self.entries.push(ListEntry::CallParticipant {
808 // user: participant.user.clone(),817 // user: participant.user.clone(),
809 // peer_id: Some(participant.peer_id),818 // peer_id: Some(participant.peer_id),
810 // is_pending: false,819 // is_pending: false,
811 // });820 // });
812 // let mut projects = participant.projects.iter().peekable();821 // let mut projects = participant.projects.iter().peekable();
813 // while let Some(project) = projects.next() {822 // while let Some(project) = projects.next() {
814 // self.entries.push(ListEntry::ParticipantProject {823 // self.entries.push(ListEntry::ParticipantProject {
815 // project_id: project.id,824 // project_id: project.id,
816 // worktree_root_names: project.worktree_root_names.clone(),825 // worktree_root_names: project.worktree_root_names.clone(),
817 // host_user_id: participant.user.id,826 // host_user_id: participant.user.id,
818 // is_last: projects.peek().is_none()827 // is_last: projects.peek().is_none()
819 // && participant.video_tracks.is_empty(),828 // && participant.video_tracks.is_empty(),
820 // });829 // });
821 // }830 // }
822 // if !participant.video_tracks.is_empty() {831 // if !participant.video_tracks.is_empty() {
823 // self.entries.push(ListEntry::ParticipantScreen {832 // self.entries.push(ListEntry::ParticipantScreen {
824 // peer_id: Some(participant.peer_id),833 // peer_id: Some(participant.peer_id),
825 // is_last: true,834 // is_last: true,
826 // });835 // });
827 // }836 // }
828 // }837 // }
829 838
830 // // Populate pending participants.839 // // Populate pending participants.
831 // self.match_candidates.clear();840 // self.match_candidates.clear();
832 // self.match_candidates841 // self.match_candidates
833 // .extend(room.pending_participants().iter().enumerate().map(842 // .extend(room.pending_participants().iter().enumerate().map(
834 // |(id, participant)| StringMatchCandidate {843 // |(id, participant)| StringMatchCandidate {
835 // id,844 // id,
836 // string: participant.github_login.clone(),845 // string: participant.github_login.clone(),
837 // char_bag: participant.github_login.chars().collect(),846 // char_bag: participant.github_login.chars().collect(),
838 // },847 // },
839 // ));848 // ));
840 // let matches = executor.block(match_strings(849 // let matches = executor.block(match_strings(
841 // &self.match_candidates,850 // &self.match_candidates,
842 // &query,851 // &query,
843 // true,852 // true,
844 // usize::MAX,853 // usize::MAX,
845 // &Default::default(),854 // &Default::default(),
846 // executor.clone(),855 // executor.clone(),
847 // ));856 // ));
848 // self.entries857 // self.entries
849 // .extend(matches.iter().map(|mat| ListEntry::CallParticipant {858 // .extend(matches.iter().map(|mat| ListEntry::CallParticipant {
850 // user: room.pending_participants()[mat.candidate_id].clone(),859 // user: room.pending_participants()[mat.candidate_id].clone(),
851 // peer_id: None,860 // peer_id: None,
852 // is_pending: true,861 // is_pending: true,
853 // }));862 // }));
854 // }863 // }
855 // }864 // }
856 865
857 // let mut request_entries = Vec::new();866 let mut request_entries = Vec::new();
858 867
859 // if cx.has_flag::<ChannelsAlpha>() {868 if cx.has_flag::<ChannelsAlpha>() {
860 // self.entries.push(ListEntry::Header(Section::Channels));869 self.entries.push(ListEntry::Header(Section::Channels));
861 870
862 // if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {871 if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {
863 // self.match_candidates.clear();872 self.match_candidates.clear();
864 // self.match_candidates873 self.match_candidates
865 // .extend(channel_store.ordered_channels().enumerate().map(874 .extend(channel_store.ordered_channels().enumerate().map(
866 // |(ix, (_, channel))| StringMatchCandidate {875 |(ix, (_, channel))| StringMatchCandidate {
867 // id: ix,876 id: ix,
868 // string: channel.name.clone(),877 string: channel.name.clone(),
869 // char_bag: channel.name.chars().collect(),878 char_bag: channel.name.chars().collect(),
870 // },879 },
871 // ));880 ));
872 // let matches = executor.block(match_strings(881 let matches = executor.block(match_strings(
873 // &self.match_candidates,882 &self.match_candidates,
874 // &query,883 &query,
875 // true,884 true,
876 // usize::MAX,885 usize::MAX,
877 // &Default::default(),886 &Default::default(),
878 // executor.clone(),887 executor.clone(),
879 // ));888 ));
880 // if let Some(state) = &self.channel_editing_state {889 if let Some(state) = &self.channel_editing_state {
881 // if matches!(state, ChannelEditingState::Create { location: None, .. }) {890 if matches!(state, ChannelEditingState::Create { location: None, .. }) {
882 // self.entries.push(ListEntry::ChannelEditor { depth: 0 });891 self.entries.push(ListEntry::ChannelEditor { depth: 0 });
883 // }892 }
884 // }893 }
885 // let mut collapse_depth = None;894 let mut collapse_depth = None;
886 // for mat in matches {895 for mat in matches {
887 // let channel = channel_store.channel_at_index(mat.candidate_id).unwrap();896 let channel = channel_store.channel_at_index(mat.candidate_id).unwrap();
888 // let depth = channel.parent_path.len();897 let depth = channel.parent_path.len();
889 898
890 // if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) {899 if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) {
891 // collapse_depth = Some(depth);900 collapse_depth = Some(depth);
892 // } else if let Some(collapsed_depth) = collapse_depth {901 } else if let Some(collapsed_depth) = collapse_depth {
893 // if depth > collapsed_depth {902 if depth > collapsed_depth {
894 // continue;903 continue;
895 // }904 }
896 // if self.is_channel_collapsed(channel.id) {905 if self.is_channel_collapsed(channel.id) {
897 // collapse_depth = Some(depth);906 collapse_depth = Some(depth);
898 // } else {907 } else {
899 // collapse_depth = None;908 collapse_depth = None;
900 // }909 }
901 // }910 }
902 911
903 // let has_children = channel_store912 let has_children = channel_store
904 // .channel_at_index(mat.candidate_id + 1)913 .channel_at_index(mat.candidate_id + 1)
905 // .map_or(false, |next_channel| {914 .map_or(false, |next_channel| {
906 // next_channel.parent_path.ends_with(&[channel.id])915 next_channel.parent_path.ends_with(&[channel.id])
907 // });916 });
908 917
909 // match &self.channel_editing_state {918 match &self.channel_editing_state {
910 // Some(ChannelEditingState::Create {919 Some(ChannelEditingState::Create {
911 // location: parent_id,920 location: parent_id,
912 // ..921 ..
913 // }) if *parent_id == Some(channel.id) => {922 }) if *parent_id == Some(channel.id) => {
914 // self.entries.push(ListEntry::Channel {923 self.entries.push(ListEntry::Channel {
915 // channel: channel.clone(),924 channel: channel.clone(),
916 // depth,925 depth,
917 // has_children: false,926 has_children: false,
918 // });927 });
919 // self.entries928 self.entries
920 // .push(ListEntry::ChannelEditor { depth: depth + 1 });929 .push(ListEntry::ChannelEditor { depth: depth + 1 });
921 // }930 }
922 // Some(ChannelEditingState::Rename {931 Some(ChannelEditingState::Rename {
923 // location: parent_id,932 location: parent_id,
924 // ..933 ..
925 // }) if parent_id == &channel.id => {934 }) if parent_id == &channel.id => {
926 // self.entries.push(ListEntry::ChannelEditor { depth });935 self.entries.push(ListEntry::ChannelEditor { depth });
927 // }936 }
928 // _ => {937 _ => {
929 // self.entries.push(ListEntry::Channel {938 self.entries.push(ListEntry::Channel {
930 // channel: channel.clone(),939 channel: channel.clone(),
931 // depth,940 depth,
932 // has_children,941 has_children,
933 // });942 });
934 // }943 }
935 // }944 }
936 // }945 }
937 // }946 }
938
939 // let channel_invites = channel_store.channel_invitations();
940 // if !channel_invites.is_empty() {
941 // self.match_candidates.clear();
942 // self.match_candidates
943 // .extend(channel_invites.iter().enumerate().map(|(ix, channel)| {
944 // StringMatchCandidate {
945 // id: ix,
946 // string: channel.name.clone(),
947 // char_bag: channel.name.chars().collect(),
948 // }
949 // }));
950 // let matches = executor.block(match_strings(
951 // &self.match_candidates,
952 // &query,
953 // true,
954 // usize::MAX,
955 // &Default::default(),
956 // executor.clone(),
957 // ));
958 // request_entries.extend(matches.iter().map(|mat| {
959 // ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())
960 // }));
961
962 // if !request_entries.is_empty() {
963 // self.entries
964 // .push(ListEntry::Header(Section::ChannelInvites));
965 // if !self.collapsed_sections.contains(&Section::ChannelInvites) {
966 // self.entries.append(&mut request_entries);
967 // }
968 // }
969 // }
970 // }
971
972 // self.entries.push(ListEntry::Header(Section::Contacts));
973
974 // request_entries.clear();
975 // let incoming = user_store.incoming_contact_requests();
976 // if !incoming.is_empty() {
977 // self.match_candidates.clear();
978 // self.match_candidates
979 // .extend(
980 // incoming
981 // .iter()
982 // .enumerate()
983 // .map(|(ix, user)| StringMatchCandidate {
984 // id: ix,
985 // string: user.github_login.clone(),
986 // char_bag: user.github_login.chars().collect(),
987 // }),
988 // );
989 // let matches = executor.block(match_strings(
990 // &self.match_candidates,
991 // &query,
992 // true,
993 // usize::MAX,
994 // &Default::default(),
995 // executor.clone(),
996 // ));
997 // request_entries.extend(
998 // matches
999 // .iter()
1000 // .map(|mat| ListEntry::IncomingRequest(incoming[mat.candidate_id].clone())),
1001 // );
1002 // }
1003
1004 // let outgoing = user_store.outgoing_contact_requests();
1005 // if !outgoing.is_empty() {
1006 // self.match_candidates.clear();
1007 // self.match_candidates
1008 // .extend(
1009 // outgoing
1010 // .iter()
1011 // .enumerate()
1012 // .map(|(ix, user)| StringMatchCandidate {
1013 // id: ix,
1014 // string: user.github_login.clone(),
1015 // char_bag: user.github_login.chars().collect(),
1016 // }),
1017 // );
1018 // let matches = executor.block(match_strings(
1019 // &self.match_candidates,
1020 // &query,
1021 // true,
1022 // usize::MAX,
1023 // &Default::default(),
1024 // executor.clone(),
1025 // ));
1026 // request_entries.extend(
1027 // matches
1028 // .iter()
1029 // .map(|mat| ListEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())),
1030 // );
1031 // }
1032
1033 // if !request_entries.is_empty() {
1034 // self.entries
1035 // .push(ListEntry::Header(Section::ContactRequests));
1036 // if !self.collapsed_sections.contains(&Section::ContactRequests) {
1037 // self.entries.append(&mut request_entries);
1038 // }
1039 // }
1040
1041 // let contacts = user_store.contacts();
1042 // if !contacts.is_empty() {
1043 // self.match_candidates.clear();
1044 // self.match_candidates
1045 // .extend(
1046 // contacts
1047 // .iter()
1048 // .enumerate()
1049 // .map(|(ix, contact)| StringMatchCandidate {
1050 // id: ix,
1051 // string: contact.user.github_login.clone(),
1052 // char_bag: contact.user.github_login.chars().collect(),
1053 // }),
1054 // );
1055
1056 // let matches = executor.block(match_strings(
1057 // &self.match_candidates,
1058 // &query,
1059 // true,
1060 // usize::MAX,
1061 // &Default::default(),
1062 // executor.clone(),
1063 // ));
1064
1065 // let (online_contacts, offline_contacts) = matches
1066 // .iter()
1067 // .partition::<Vec<_>, _>(|mat| contacts[mat.candidate_id].online);
1068
1069 // for (matches, section) in [
1070 // (online_contacts, Section::Online),
1071 // (offline_contacts, Section::Offline),
1072 // ] {
1073 // if !matches.is_empty() {
1074 // self.entries.push(ListEntry::Header(section));
1075 // if !self.collapsed_sections.contains(&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());
1116 947
1117 // if scroll_to_top {948 // let channel_invites = channel_store.channel_invitations();
1118 // self.list_state.scroll_to(ListOffset::default());949 // if !channel_invites.is_empty() {
1119 // } else {950 // self.match_candidates.clear();
1120 // // Attempt to maintain the same scroll position.951 // self.match_candidates
1121 // if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {952 // .extend(channel_invites.iter().enumerate().map(|(ix, channel)| {
1122 // let new_scroll_top = self953 // StringMatchCandidate {
1123 // .entries954 // id: ix,
1124 // .iter()955 // string: channel.name.clone(),
1125 // .position(|entry| entry == old_top_entry)956 // char_bag: channel.name.chars().collect(),
1126 // .map(|item_ix| ListOffset {957 // }
1127 // item_ix,958 // }));
1128 // offset_in_item: old_scroll_top.offset_in_item,959 // let matches = executor.block(match_strings(
1129 // })960 // &self.match_candidates,
1130 // .or_else(|| {961 // &query,
1131 // let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?;962 // true,
1132 // let item_ix = self963 // usize::MAX,
1133 // .entries964 // &Default::default(),
1134 // .iter()965 // executor.clone(),
1135 // .position(|entry| entry == entry_after_old_top)?;966 // ));
1136 // Some(ListOffset {967 // request_entries.extend(matches.iter().map(|mat| {
1137 // item_ix,968 // ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())
1138 // offset_in_item: 0.,969 // }));
1139 // })
1140 // })
1141 // .or_else(|| {
1142 // let entry_before_old_top =
1143 // old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?;
1144 // let item_ix = self
1145 // .entries
1146 // .iter()
1147 // .position(|entry| entry == entry_before_old_top)?;
1148 // Some(ListOffset {
1149 // item_ix,
1150 // offset_in_item: 0.,
1151 // })
1152 // });
1153 970
1154 // self.list_state971 // if !request_entries.is_empty() {
1155 // .scroll_to(new_scroll_top.unwrap_or(old_scroll_top));972 // self.entries
1156 // }973 // .push(ListEntry::Header(Section::ChannelInvites));
1157 // }974 // if !self.collapsed_sections.contains(&Section::ChannelInvites) {
975 // self.entries.append(&mut request_entries);
976 // }
977 // }
978 // }
979 }
980
981 self.entries.push(ListEntry::Header(Section::Contacts));
982
983 request_entries.clear();
984 let incoming = user_store.incoming_contact_requests();
985 if !incoming.is_empty() {
986 self.match_candidates.clear();
987 self.match_candidates
988 .extend(
989 incoming
990 .iter()
991 .enumerate()
992 .map(|(ix, user)| StringMatchCandidate {
993 id: ix,
994 string: user.github_login.clone(),
995 char_bag: user.github_login.chars().collect(),
996 }),
997 );
998 let matches = executor.block(match_strings(
999 &self.match_candidates,
1000 &query,
1001 true,
1002 usize::MAX,
1003 &Default::default(),
1004 executor.clone(),
1005 ));
1006 request_entries.extend(
1007 matches
1008 .iter()
1009 .map(|mat| ListEntry::IncomingRequest(incoming[mat.candidate_id].clone())),
1010 );
1011 }
1012
1013 let outgoing = user_store.outgoing_contact_requests();
1014 if !outgoing.is_empty() {
1015 self.match_candidates.clear();
1016 self.match_candidates
1017 .extend(
1018 outgoing
1019 .iter()
1020 .enumerate()
1021 .map(|(ix, user)| StringMatchCandidate {
1022 id: ix,
1023 string: user.github_login.clone(),
1024 char_bag: user.github_login.chars().collect(),
1025 }),
1026 );
1027 let matches = executor.block(match_strings(
1028 &self.match_candidates,
1029 &query,
1030 true,
1031 usize::MAX,
1032 &Default::default(),
1033 executor.clone(),
1034 ));
1035 request_entries.extend(
1036 matches
1037 .iter()
1038 .map(|mat| ListEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())),
1039 );
1040 }
1041
1042 if !request_entries.is_empty() {
1043 self.entries
1044 .push(ListEntry::Header(Section::ContactRequests));
1045 if !self.collapsed_sections.contains(&Section::ContactRequests) {
1046 self.entries.append(&mut request_entries);
1047 }
1048 }
1049
1050 let contacts = user_store.contacts();
1051 if !contacts.is_empty() {
1052 self.match_candidates.clear();
1053 self.match_candidates
1054 .extend(
1055 contacts
1056 .iter()
1057 .enumerate()
1058 .map(|(ix, contact)| StringMatchCandidate {
1059 id: ix,
1060 string: contact.user.github_login.clone(),
1061 char_bag: contact.user.github_login.chars().collect(),
1062 }),
1063 );
1064
1065 let matches = executor.block(match_strings(
1066 &self.match_candidates,
1067 &query,
1068 true,
1069 usize::MAX,
1070 &Default::default(),
1071 executor.clone(),
1072 ));
1073
1074 let (online_contacts, offline_contacts) = matches
1075 .iter()
1076 .partition::<Vec<_>, _>(|mat| contacts[mat.candidate_id].online);
1077
1078 for (matches, section) in [
1079 (online_contacts, Section::Online),
1080 (offline_contacts, Section::Offline),
1081 ] {
1082 if !matches.is_empty() {
1083 self.entries.push(ListEntry::Header(section));
1084 if !self.collapsed_sections.contains(&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 // }
1158 1167
1159 // cx.notify();1168 cx.notify();
1160 // }1169 }
1161 1170
1162 // fn render_call_participant(1171 // fn render_call_participant(
1163 // user: &User,1172 // user: &User,
@@ -1455,389 +1464,6 @@ impl CollabPanel { · 291 unmodified lines
1455 // }1464 // }
1456 // }1465 // }
1457 1466
1458 // fn render_header(
1459 // &self,
1460 // section: Section,
1461 // theme: &theme::Theme,
1462 // is_selected: bool,
1463 // is_collapsed: bool,
1464 // cx: &mut ViewContext<Self>,
1465 // ) -> AnyElement<Self> {
1466 // enum Header {}
1467 // enum LeaveCallContactList {}
1468 // enum AddChannel {}
1469
1470 // let tooltip_style = &theme.tooltip;
1471 // let mut channel_link = None;
1472 // let mut channel_tooltip_text = None;
1473 // let mut channel_icon = None;
1474 // let mut is_dragged_over = false;
1475
1476 // let text = match section {
1477 // Section::ActiveCall => {
1478 // let channel_name = maybe!({
1479 // let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?;
1480
1481 // let channel = self.channel_store.read(cx).channel_for_id(channel_id)?;
1482
1483 // channel_link = Some(channel.link());
1484 // (channel_icon, channel_tooltip_text) = match channel.visibility {
1485 // proto::ChannelVisibility::Public => {
1486 // (Some("icons/public.svg"), Some("Copy public channel link."))
1487 // }
1488 // proto::ChannelVisibility::Members => {
1489 // (Some("icons/hash.svg"), Some("Copy private channel link."))
1490 // }
1491 // };
1492
1493 // Some(channel.name.as_str())
1494 // });
1495
1496 // if let Some(name) = channel_name {
1497 // Cow::Owned(format!("{}", name))
1498 // } else {
1499 // Cow::Borrowed("Current Call")
1500 // }
1501 // }
1502 // Section::ContactRequests => Cow::Borrowed("Requests"),
1503 // Section::Contacts => Cow::Borrowed("Contacts"),
1504 // Section::Channels => Cow::Borrowed("Channels"),
1505 // Section::ChannelInvites => Cow::Borrowed("Invites"),
1506 // Section::Online => Cow::Borrowed("Online"),
1507 // Section::Offline => Cow::Borrowed("Offline"),
1508 // };
1509
1510 // enum AddContact {}
1511 // let button = match section {
1512 // Section::ActiveCall => channel_link.map(|channel_link| {
1513 // let channel_link_copy = channel_link.clone();
1514 // MouseEventHandler::new::<AddContact, _>(0, cx, |state, _| {
1515 // render_icon_button(
1516 // theme
1517 // .collab_panel
1518 // .leave_call_button
1519 // .style_for(is_selected, state),
1520 // "icons/link.svg",
1521 // )
1522 // })
1523 // .with_cursor_style(CursorStyle::PointingHand)
1524 // .on_click(MouseButton::Left, move |_, _, cx| {
1525 // let item = ClipboardItem::new(channel_link_copy.clone());
1526 // cx.write_to_clipboard(item)
1527 // })
1528 // .with_tooltip::<AddContact>(
1529 // 0,
1530 // channel_tooltip_text.unwrap(),
1531 // None,
1532 // tooltip_style.clone(),
1533 // cx,
1534 // )
1535 // }),
1536 // Section::Contacts => Some(
1537 // MouseEventHandler::new::<LeaveCallContactList, _>(0, cx, |state, _| {
1538 // render_icon_button(
1539 // theme
1540 // .collab_panel
1541 // .add_contact_button
1542 // .style_for(is_selected, state),
1543 // "icons/plus.svg",
1544 // )
1545 // })
1546 // .with_cursor_style(CursorStyle::PointingHand)
1547 // .on_click(MouseButton::Left, |_, this, cx| {
1548 // this.toggle_contact_finder(cx);
1549 // })
1550 // .with_tooltip::<LeaveCallContactList>(
1551 // 0,
1552 // "Search for new contact",
1553 // None,
1554 // tooltip_style.clone(),
1555 // cx,
1556 // ),
1557 // ),
1558 // Section::Channels => {
1559 // if cx
1560 // .global::<DragAndDrop<Workspace>>()
1561 // .currently_dragged::<Channel>(cx.window())
1562 // .is_some()
1563 // && self.drag_target_channel == ChannelDragTarget::Root
1564 // {
1565 // is_dragged_over = true;
1566 // }
1567
1568 // Some(
1569 // MouseEventHandler::new::<AddChannel, _>(0, cx, |state, _| {
1570 // render_icon_button(
1571 // theme
1572 // .collab_panel
1573 // .add_contact_button
1574 // .style_for(is_selected, state),
1575 // "icons/plus.svg",
1576 // )
1577 // })
1578 // .with_cursor_style(CursorStyle::PointingHand)
1579 // .on_click(MouseButton::Left, |_, this, cx| this.new_root_channel(cx))
1580 // .with_tooltip::<AddChannel>(
1581 // 0,
1582 // "Create a channel",
1583 // None,
1584 // tooltip_style.clone(),
1585 // cx,
1586 // ),
1587 // )
1588 // }
1589 // _ => None,
1590 // };
1591
1592 // let can_collapse = match section {
1593 // Section::ActiveCall | Section::Channels | Section::Contacts => false,
1594 // Section::ChannelInvites
1595 // | Section::ContactRequests
1596 // | Section::Online
1597 // | Section::Offline => true,
1598 // };
1599 // let icon_size = (&theme.collab_panel).section_icon_size;
1600 // let mut result = MouseEventHandler::new::<Header, _>(section as usize, cx, |state, _| {
1601 // let header_style = if can_collapse {
1602 // theme
1603 // .collab_panel
1604 // .subheader_row
1605 // .in_state(is_selected)
1606 // .style_for(state)
1607 // } else {
1608 // &theme.collab_panel.header_row
1609 // };
1610
1611 // Flex::row()
1612 // .with_children(if can_collapse {
1613 // Some(
1614 // Svg::new(if is_collapsed {
1615 // "icons/chevron_right.svg"
1616 // } else {
1617 // "icons/chevron_down.svg"
1618 // })
1619 // .with_color(header_style.text.color)
1620 // .constrained()
1621 // .with_max_width(icon_size)
1622 // .with_max_height(icon_size)
1623 // .aligned()
1624 // .constrained()
1625 // .with_width(icon_size)
1626 // .contained()
1627 // .with_margin_right(
1628 // theme.collab_panel.contact_username.container.margin.left,
1629 // ),
1630 // )
1631 // } else if let Some(channel_icon) = channel_icon {
1632 // Some(
1633 // Svg::new(channel_icon)
1634 // .with_color(header_style.text.color)
1635 // .constrained()
1636 // .with_max_width(icon_size)
1637 // .with_max_height(icon_size)
1638 // .aligned()
1639 // .constrained()
1640 // .with_width(icon_size)
1641 // .contained()
1642 // .with_margin_right(
1643 // theme.collab_panel.contact_username.container.margin.left,
1644 // ),
1645 // )
1646 // } else {
1647 // None
1648 // })
1649 // .with_child(
1650 // Label::new(text, header_style.text.clone())
1651 // .aligned()
1652 // .left()
1653 // .flex(1., true),
1654 // )
1655 // .with_children(button.map(|button| button.aligned().right()))
1656 // .constrained()
1657 // .with_height(theme.collab_panel.row_height)
1658 // .contained()
1659 // .with_style(if is_dragged_over {
1660 // theme.collab_panel.dragged_over_header
1661 // } else {
1662 // header_style.container
1663 // })
1664 // });
1665
1666 // result = result
1667 // .on_move(move |_, this, cx| {
1668 // if cx
1669 // .global::<DragAndDrop<Workspace>>()
1670 // .currently_dragged::<Channel>(cx.window())
1671 // .is_some()
1672 // {
1673 // this.drag_target_channel = ChannelDragTarget::Root;
1674 // cx.notify()
1675 // }
1676 // })
1677 // .on_up(MouseButton::Left, move |_, this, cx| {
1678 // if let Some((_, dragged_channel)) = cx
1679 // .global::<DragAndDrop<Workspace>>()
1680 // .currently_dragged::<Channel>(cx.window())
1681 // {
1682 // this.channel_store
1683 // .update(cx, |channel_store, cx| {
1684 // channel_store.move_channel(dragged_channel.id, None, cx)
1685 // })
1686 // .detach_and_log_err(cx)
1687 // }
1688 // });
1689
1690 // if can_collapse {
1691 // result = result
1692 // .with_cursor_style(CursorStyle::PointingHand)
1693 // .on_click(MouseButton::Left, move |_, this, cx| {
1694 // if can_collapse {
1695 // this.toggle_section_expanded(section, cx);
1696 // }
1697 // })
1698 // }
1699
1700 // result.into_any()
1701 // }
1702
1703 // fn render_contact(
1704 // contact: &Contact,
1705 // calling: bool,
1706 // project: &ModelHandle<Project>,
1707 // theme: &theme::Theme,
1708 // is_selected: bool,
1709 // cx: &mut ViewContext<Self>,
1710 // ) -> AnyElement<Self> {
1711 // enum ContactTooltip {}
1712
1713 // let collab_theme = &theme.collab_panel;
1714 // let online = contact.online;
1715 // let busy = contact.busy || calling;
1716 // let user_id = contact.user.id;
1717 // let github_login = contact.user.github_login.clone();
1718 // let initial_project = project.clone();
1719
1720 // let event_handler =
1721 // MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |state, cx| {
1722 // Flex::row()
1723 // .with_children(contact.user.avatar.clone().map(|avatar| {
1724 // let status_badge = if contact.online {
1725 // Some(
1726 // Empty::new()
1727 // .collapsed()
1728 // .contained()
1729 // .with_style(if busy {
1730 // collab_theme.contact_status_busy
1731 // } else {
1732 // collab_theme.contact_status_free
1733 // })
1734 // .aligned(),
1735 // )
1736 // } else {
1737 // None
1738 // };
1739 // Stack::new()
1740 // .with_child(
1741 // Image::from_data(avatar)
1742 // .with_style(collab_theme.contact_avatar)
1743 // .aligned()
1744 // .left(),
1745 // )
1746 // .with_children(status_badge)
1747 // }))
1748 // .with_child(
1749 // Label::new(
1750 // contact.user.github_login.clone(),
1751 // collab_theme.contact_username.text.clone(),
1752 // )
1753 // .contained()
1754 // .with_style(collab_theme.contact_username.container)
1755 // .aligned()
1756 // .left()
1757 // .flex(1., true),
1758 // )
1759 // .with_children(if state.hovered() {
1760 // Some(
1761 // MouseEventHandler::new::<Cancel, _>(
1762 // contact.user.id as usize,
1763 // cx,
1764 // |mouse_state, _| {
1765 // let button_style =
1766 // collab_theme.contact_button.style_for(mouse_state);
1767 // render_icon_button(button_style, "icons/x.svg")
1768 // .aligned()
1769 // .flex_float()
1770 // },
1771 // )
1772 // .with_padding(Padding::uniform(2.))
1773 // .with_cursor_style(CursorStyle::PointingHand)
1774 // .on_click(MouseButton::Left, move |_, this, cx| {
1775 // this.remove_contact(user_id, &github_login, cx);
1776 // })
1777 // .flex_float(),
1778 // )
1779 // } else {
1780 // None
1781 // })
1782 // .with_children(if calling {
1783 // Some(
1784 // Label::new("Calling", collab_theme.calling_indicator.text.clone())
1785 // .contained()
1786 // .with_style(collab_theme.calling_indicator.container)
1787 // .aligned(),
1788 // )
1789 // } else {
1790 // None
1791 // })
1792 // .constrained()
1793 // .with_height(collab_theme.row_height)
1794 // .contained()
1795 // .with_style(
1796 // *collab_theme
1797 // .contact_row
1798 // .in_state(is_selected)
1799 // .style_for(state),
1800 // )
1801 // });
1802
1803 // if online && !busy {
1804 // let room = ActiveCall::global(cx).read(cx).room();
1805 // let label = if room.is_some() {
1806 // format!("Invite {} to join call", contact.user.github_login)
1807 // } else {
1808 // format!("Call {}", contact.user.github_login)
1809 // };
1810
1811 // event_handler
1812 // .on_click(MouseButton::Left, move |_, this, cx| {
1813 // this.call(user_id, Some(initial_project.clone()), cx);
1814 // })
1815 // .with_cursor_style(CursorStyle::PointingHand)
1816 // .with_tooltip::<ContactTooltip>(
1817 // contact.user.id as usize,
1818 // label,
1819 // None,
1820 // theme.tooltip.clone(),
1821 // cx,
1822 // )
1823 // .into_any()
1824 // } else {
1825 // event_handler
1826 // .with_tooltip::<ContactTooltip>(
1827 // contact.user.id as usize,
1828 // format!(
1829 // "{} is {}",
1830 // contact.user.github_login,
1831 // if busy { "on a call" } else { "offline" }
1832 // ),
1833 // None,
1834 // theme.tooltip.clone(),
1835 // cx,
1836 // )
1837 // .into_any()
1838 // }
1839 // }
1840
1841 // fn render_contact_placeholder(1467 // fn render_contact_placeholder(
1842 // &self,1468 // &self,
1843 // theme: &theme::CollabPanel,1469 // theme: &theme::CollabPanel,
@@ -1933,335 +1559,6 @@ impl CollabPanel { · 89 unmodified lines
1933 // .into_any()1559 // .into_any()
1934 // }1560 // }
1935 1561
1936 // fn render_channel(
1937 // &self,
1938 // channel: &Channel,
1939 // depth: usize,
1940 // theme: &theme::Theme,
1941 // is_selected: bool,
1942 // has_children: bool,
1943 // ix: usize,
1944 // cx: &mut ViewContext<Self>,
1945 // ) -> AnyElement<Self> {
1946 // let channel_id = channel.id;
1947 // let collab_theme = &theme.collab_panel;
1948 // let is_public = self
1949 // .channel_store
1950 // .read(cx)
1951 // .channel_for_id(channel_id)
1952 // .map(|channel| channel.visibility)
1953 // == Some(proto::ChannelVisibility::Public);
1954 // let other_selected = self.selected_channel().map(|channel| channel.id) == Some(channel.id);
1955 // let disclosed =
1956 // has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok());
1957
1958 // let is_active = maybe!({
1959 // let call_channel = ActiveCall::global(cx)
1960 // .read(cx)
1961 // .room()?
1962 // .read(cx)
1963 // .channel_id()?;
1964 // Some(call_channel == channel_id)
1965 // })
1966 // .unwrap_or(false);
1967
1968 // const FACEPILE_LIMIT: usize = 3;
1969
1970 // enum ChannelCall {}
1971 // enum ChannelNote {}
1972 // enum NotesTooltip {}
1973 // enum ChatTooltip {}
1974 // enum ChannelTooltip {}
1975
1976 // let mut is_dragged_over = false;
1977 // if cx
1978 // .global::<DragAndDrop<Workspace>>()
1979 // .currently_dragged::<Channel>(cx.window())
1980 // .is_some()
1981 // && self.drag_target_channel == ChannelDragTarget::Channel(channel_id)
1982 // {
1983 // is_dragged_over = true;
1984 // }
1985
1986 // let has_messages_notification = channel.unseen_message_id.is_some();
1987
1988 // MouseEventHandler::new::<Channel, _>(ix, cx, |state, cx| {
1989 // let row_hovered = state.hovered();
1990
1991 // let mut select_state = |interactive: &Interactive<ContainerStyle>| {
1992 // if state.clicked() == Some(MouseButton::Left) && interactive.clicked.is_some() {
1993 // interactive.clicked.as_ref().unwrap().clone()
1994 // } else if state.hovered() || other_selected {
1995 // interactive
1996 // .hovered
1997 // .as_ref()
1998 // .unwrap_or(&interactive.default)
1999 // .clone()
2000 // } else {
2001 // interactive.default.clone()
2002 // }
2003 // };
2004
2005 // Flex::<Self>::row()
2006 // .with_child(
2007 // Svg::new(if is_public {
2008 // "icons/public.svg"
2009 // } else {
2010 // "icons/hash.svg"
2011 // })
2012 // .with_color(collab_theme.channel_hash.color)
2013 // .constrained()
2014 // .with_width(collab_theme.channel_hash.width)
2015 // .aligned()
2016 // .left(),
2017 // )
2018 // .with_child({
2019 // let style = collab_theme.channel_name.inactive_state();
2020 // Flex::row()
2021 // .with_child(
2022 // Label::new(channel.name.clone(), style.text.clone())
2023 // .contained()
2024 // .with_style(style.container)
2025 // .aligned()
2026 // .left()
2027 // .with_tooltip::<ChannelTooltip>(
2028 // ix,
2029 // "Join channel",
2030 // None,
2031 // theme.tooltip.clone(),
2032 // cx,
2033 // ),
2034 // )
2035 // .with_children({
2036 // let participants =
2037 // self.channel_store.read(cx).channel_participants(channel_id);
2038
2039 // if !participants.is_empty() {
2040 // let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
2041
2042 // let result = FacePile::new(collab_theme.face_overlap)
2043 // .with_children(
2044 // participants
2045 // .iter()
2046 // .filter_map(|user| {
2047 // Some(
2048 // Image::from_data(user.avatar.clone()?)
2049 // .with_style(collab_theme.channel_avatar),
2050 // )
2051 // })
2052 // .take(FACEPILE_LIMIT),
2053 // )
2054 // .with_children((extra_count > 0).then(|| {
2055 // Label::new(
2056 // format!("+{}", extra_count),
2057 // collab_theme.extra_participant_label.text.clone(),
2058 // )
2059 // .contained()
2060 // .with_style(collab_theme.extra_participant_label.container)
2061 // }));
2062
2063 // Some(result)
2064 // } else {
2065 // None
2066 // }
2067 // })
2068 // .with_spacing(8.)
2069 // .align_children_center()
2070 // .flex(1., true)
2071 // })
2072 // .with_child(
2073 // MouseEventHandler::new::<ChannelNote, _>(ix, cx, move |mouse_state, _| {
2074 // let container_style = collab_theme
2075 // .disclosure
2076 // .button
2077 // .style_for(mouse_state)
2078 // .container;
2079
2080 // if channel.unseen_message_id.is_some() {
2081 // Svg::new("icons/conversations.svg")
2082 // .with_color(collab_theme.channel_note_active_color)
2083 // .constrained()
2084 // .with_width(collab_theme.channel_hash.width)
2085 // .contained()
2086 // .with_style(container_style)
2087 // .with_uniform_padding(4.)
2088 // .into_any()
2089 // } else if row_hovered {
2090 // Svg::new("icons/conversations.svg")
2091 // .with_color(collab_theme.channel_hash.color)
2092 // .constrained()
2093 // .with_width(collab_theme.channel_hash.width)
2094 // .contained()
2095 // .with_style(container_style)
2096 // .with_uniform_padding(4.)
2097 // .into_any()
2098 // } else {
2099 // Empty::new().into_any()
2100 // }
2101 // })
2102 // .on_click(MouseButton::Left, move |_, this, cx| {
2103 // this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
2104 // })
2105 // .with_tooltip::<ChatTooltip>(
2106 // ix,
2107 // "Open channel chat",
2108 // None,
2109 // theme.tooltip.clone(),
2110 // cx,
2111 // )
2112 // .contained()
2113 // .with_margin_right(4.),
2114 // )
2115 // .with_child(
2116 // MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |mouse_state, cx| {
2117 // let container_style = collab_theme
2118 // .disclosure
2119 // .button
2120 // .style_for(mouse_state)
2121 // .container;
2122 // if row_hovered || channel.unseen_note_version.is_some() {
2123 // Svg::new("icons/file.svg")
2124 // .with_color(if channel.unseen_note_version.is_some() {
2125 // collab_theme.channel_note_active_color
2126 // } else {
2127 // collab_theme.channel_hash.color
2128 // })
2129 // .constrained()
2130 // .with_width(collab_theme.channel_hash.width)
2131 // .contained()
2132 // .with_style(container_style)
2133 // .with_uniform_padding(4.)
2134 // .with_margin_right(collab_theme.channel_hash.container.margin.left)
2135 // .with_tooltip::<NotesTooltip>(
2136 // ix as usize,
2137 // "Open channel notes",
2138 // None,
2139 // theme.tooltip.clone(),
2140 // cx,
2141 // )
2142 // .into_any()
2143 // } else if has_messages_notification {
2144 // Empty::new()
2145 // .constrained()
2146 // .with_width(collab_theme.channel_hash.width)
2147 // .contained()
2148 // .with_uniform_padding(4.)
2149 // .with_margin_right(collab_theme.channel_hash.container.margin.left)
2150 // .into_any()
2151 // } else {
2152 // Empty::new().into_any()
2153 // }
2154 // })
2155 // .on_click(MouseButton::Left, move |_, this, cx| {
2156 // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
2157 // }),
2158 // )
2159 // .align_children_center()
2160 // .styleable_component()
2161 // .disclosable(
2162 // disclosed,
2163 // Box::new(ToggleCollapse {
2164 // location: channel.id.clone(),
2165 // }),
2166 // )
2167 // .with_id(ix)
2168 // .with_style(collab_theme.disclosure.clone())
2169 // .element()
2170 // .constrained()
2171 // .with_height(collab_theme.row_height)
2172 // .contained()
2173 // .with_style(select_state(
2174 // collab_theme
2175 // .channel_row
2176 // .in_state(is_selected || is_active || is_dragged_over),
2177 // ))
2178 // .with_padding_left(
2179 // collab_theme.channel_row.default_style().padding.left
2180 // + collab_theme.channel_indent * depth as f32,
2181 // )
2182 // })
2183 // .on_click(MouseButton::Left, move |_, this, cx| {
2184 // if this.drag_target_channel == ChannelDragTarget::None {
2185 // if is_active {
2186 // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx)
2187 // } else {
2188 // this.join_channel(channel_id, cx)
2189 // }
2190 // }
2191 // })
2192 // .on_click(MouseButton::Right, {
2193 // let channel = channel.clone();
2194 // move |e, this, cx| {
2195 // this.deploy_channel_context_menu(Some(e.position), &channel, ix, cx);
2196 // }
2197 // })
2198 // .on_up(MouseButton::Left, move |_, this, cx| {
2199 // if let Some((_, dragged_channel)) = cx
2200 // .global::<DragAndDrop<Workspace>>()
2201 // .currently_dragged::<Channel>(cx.window())
2202 // {
2203 // this.channel_store
2204 // .update(cx, |channel_store, cx| {
2205 // channel_store.move_channel(dragged_channel.id, Some(channel_id), cx)
2206 // })
2207 // .detach_and_log_err(cx)
2208 // }
2209 // })
2210 // .on_move({
2211 // let channel = channel.clone();
2212 // move |_, this, cx| {
2213 // if let Some((_, dragged_channel)) = cx
2214 // .global::<DragAndDrop<Workspace>>()
2215 // .currently_dragged::<Channel>(cx.window())
2216 // {
2217 // if channel.id != dragged_channel.id {
2218 // this.drag_target_channel = ChannelDragTarget::Channel(channel.id);
2219 // }
2220 // cx.notify()
2221 // }
2222 // }
2223 // })
2224 // .as_draggable::<_, Channel>(
2225 // channel.clone(),
2226 // move |_, channel, cx: &mut ViewContext<Workspace>| {
2227 // let theme = &theme::current(cx).collab_panel;
2228
2229 // Flex::<Workspace>::row()
2230 // .with_child(
2231 // Svg::new("icons/hash.svg")
2232 // .with_color(theme.channel_hash.color)
2233 // .constrained()
2234 // .with_width(theme.channel_hash.width)
2235 // .aligned()
2236 // .left(),
2237 // )
2238 // .with_child(
2239 // Label::new(channel.name.clone(), theme.channel_name.text.clone())
2240 // .contained()
2241 // .with_style(theme.channel_name.container)
2242 // .aligned()
2243 // .left(),
2244 // )
2245 // .align_children_center()
2246 // .contained()
2247 // .with_background_color(
2248 // theme
2249 // .container
2250 // .background_color
2251 // .unwrap_or(gpui::color::Color::transparent_black()),
2252 // )
2253 // .contained()
2254 // .with_padding_left(
2255 // theme.channel_row.default_style().padding.left
2256 // + theme.channel_indent * depth as f32,
2257 // )
2258 // .into_any()
2259 // },
2260 // )
2261 // .with_cursor_style(CursorStyle::PointingHand)
2262 // .into_any()
2263 // }
2264
2265 // fn render_channel_notes(1562 // fn render_channel_notes(
2266 // &self,1563 // &self,
2267 // channel_id: ChannelId,1564 // channel_id: ChannelId,
@@ -2948,9 +2245,9 @@ impl CollabPanel { · 680 unmodified lines
2948 // cx.focus_self();2245 // cx.focus_self();
2949 // }2246 // }
2950 2247
2951 // fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool {2248 fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool {
2952 // self.collapsed_channels.binary_search(&channel_id).is_ok()2249 self.collapsed_channels.binary_search(&channel_id).is_ok()
2953 // }2250 }
2954 2251
2955 // fn leave_call(cx: &mut ViewContext<Self>) {2252 // fn leave_call(cx: &mut ViewContext<Self>) {
2956 // ActiveCall::global(cx)2253 // ActiveCall::global(cx)
@@ -2958,19 +2255,17 @@ impl CollabPanel { · 1 unmodified line
2958 // .detach_and_log_err(cx);2255 // .detach_and_log_err(cx);
2959 // }2256 // }
2960 2257
2961 // fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {2258 fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
2962 // if let Some(workspace) = self.workspace.upgrade(cx) {2259 if let Some(workspace) = self.workspace.upgrade() {
2963 // workspace.update(cx, |workspace, cx| {2260 workspace.update(cx, |workspace, cx| {
2964 // workspace.toggle_modal(cx, |_, cx| {2261 workspace.toggle_modal(cx, |cx| {
2965 // cx.add_view(|cx| {2262 let mut finder = ContactFinder::new(self.user_store.clone(), cx);
2966 // let mut finder = ContactFinder::new(self.user_store.clone(), cx);2263 finder.set_query(self.filter_editor.read(cx).text(cx), cx);
2967 // finder.set_query(self.filter_editor.read(cx).text(cx), cx);2264 finder
2968 // finder2265 });
2969 // })2266 });
2970 // });2267 }
2971 // });2268 }
2972 // }
2973 // }
2974 2269
2975 // fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) {2270 // fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) {
2976 // self.channel_editing_state = Some(ChannelEditingState::Create {2271 // self.channel_editing_state = Some(ChannelEditingState::Create {
@@ -3247,6 +2542,802 @@ impl CollabPanel { · 270 unmodified lines
3247 // let item = ClipboardItem::new(channel.link());2542 // let item = ClipboardItem::new(channel.link());
3248 // cx.write_to_clipboard(item)2543 // cx.write_to_clipboard(item)
3249 // }2544 // }
2545
2546 fn render_signed_out(&mut self, cx: &mut ViewContext<Self>) -> Div {
2547 v_stack().child(Button::new("Sign in to collaborate").on_click(cx.listener(
2548 |this, _, cx| {
2549 let client = this.client.clone();
2550 cx.spawn(|_, mut cx| async move {
2551 client
2552 .authenticate_and_connect(true, &cx)
2553 .await
2554 .notify_async_err(&mut cx);
2555 })
2556 .detach()
2557 },
2558 )))
2559 }
2560
2561 fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> List {
2562 let is_selected = false; // todo!() this.selection == Some(ix);
2563
2564 List::new().children(self.entries.clone().into_iter().map(|entry| {
2565 match entry {
2566 ListEntry::Header(section) => {
2567 let is_collapsed = self.collapsed_sections.contains(&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 }
3250}3341}
3251 3342
3252// fn render_tree_branch(3343// fn render_tree_branch(
@@ -3295,13 +3386,19 @@ impl CollabPanel { · 42 unmodified lines
3295// }3386// }
3296 3387
3297impl Render for CollabPanel {3388impl Render for CollabPanel {
3298 type Element = Focusable<Self, Div<Self>>;3389 type Element = Focusable<Div>;
3299 3390
3300 fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {3391 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3301 div()3392 div()
3302 .key_context("CollabPanel")3393 .key_context("CollabPanel")
3303 .track_focus(&self.focus_handle)3394 .track_focus(&self.focus_handle)
3304 .child("COLLAB PANEL")3395 .map(|el| {
3396 if self.user_store.read(cx).current_user().is_none() {
3397 el.child(self.render_signed_out(cx))
3398 } else {
3399 el.child(self.render_signed_in(cx))
3400 }
3401 })
3305 }3402 }
3306}3403}
3307 3404
crates/collab_ui2/src/collab_panel/contact_finder.rs+116−150
@@ -1,37 +1,34 @@
1use client::{ContactRequestStatus, User, UserStore};1use client::{ContactRequestStatus, User, UserStore};
2use gpui::{2use gpui::{
3 elements::*, AppContext, Entity, ModelHandle, MouseState, Task, View, ViewContext, ViewHandle,3 div, img, svg, AnyElement, AppContext, DismissEvent, Div, Entity, EventEmitter, FocusHandle,
4 FocusableView, Img, IntoElement, Model, ParentElement as _, Render, Styled, Task, View,
5 ViewContext, VisualContext, WeakView,
4};6};
5use picker::{Picker, PickerDelegate, PickerEvent};7use picker::{Picker, PickerDelegate};
6use std::sync::Arc;8use std::sync::Arc;
7use util::TryFutureExt;9use theme::ActiveTheme as _;
8use workspace::Modal;10use ui::{h_stack, v_stack, Label};
11use util::{ResultExt as _, TryFutureExt};
9 12
10pub fn init(cx: &mut AppContext) {13pub fn init(cx: &mut AppContext) {
11 Picker::<ContactFinderDelegate>::init(cx);14 //Picker::<ContactFinderDelegate>::init(cx);
12 cx.add_action(ContactFinder::dismiss)15 //cx.add_action(ContactFinder::dismiss)
13}16}
14 17
15pub struct ContactFinder {18pub struct ContactFinder {
16 picker: ViewHandle<Picker<ContactFinderDelegate>>,19 picker: View<Picker<ContactFinderDelegate>>,
17 has_focus: bool,20 has_focus: bool,
18}21}
19 22
20impl ContactFinder {23impl ContactFinder {
21 pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {24 pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
22 let picker = cx.add_view(|cx| {25 let delegate = ContactFinderDelegate {
23 Picker::new(26 parent: cx.view().downgrade(),
24 ContactFinderDelegate {27 user_store,
25 user_store,28 potential_contacts: Arc::from([]),
26 potential_contacts: Arc::from([]),29 selected_index: 0,
27 selected_index: 0,30 };
28 },31 let picker = cx.build_view(|cx| Picker::new(delegate, cx));
29 cx,
30 )
31 .with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
32 });
33
34 cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
35 32
36 Self {33 Self {
37 picker,34 picker,
@@ -41,105 +38,72 @@ impl ContactFinder { · 3 unmodified lines
41 38
42 pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {39 pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
43 self.picker.update(cx, |picker, cx| {40 self.picker.update(cx, |picker, cx| {
44 picker.set_query(query, cx);41 // todo!()
42 // picker.set_query(query, cx);
45 });43 });
46 }44 }
47
48 fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
49 cx.emit(PickerEvent::Dismiss);
50 }
51}
52
53impl Entity for ContactFinder {
54 type Event = PickerEvent;
55}45}
56 46
57impl View for ContactFinder {47impl Render for ContactFinder {
58 fn ui_name() -> &'static str {48 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
59 "ContactFinder"49 fn render_mode_button(text: &'static str) -> AnyElement {
60 }50 Label::new(text).into_any_element()
61
62 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
63 let full_theme = &theme::current(cx);
64 let theme = &full_theme.collab_panel.tabbed_modal;
65
66 fn render_mode_button(
67 text: &'static str,
68 theme: &theme::TabbedModal,
69 _cx: &mut ViewContext<ContactFinder>,
70 ) -> AnyElement<ContactFinder> {
71 let contained_text = &theme.tab_button.active_state().default;
72 Label::new(text, contained_text.text.clone())
73 .contained()
74 .with_style(contained_text.container.clone())
75 .into_any()
76 }51 }
77 52
78 Flex::column()53 v_stack()
79 .with_child(54 .child(
80 Flex::column()55 v_stack()
81 .with_child(56 .child(Label::new("Contacts"))
82 Label::new("Contacts", theme.title.text.clone())57 .child(h_stack().children([render_mode_button("Invite new contacts")]))
83 .contained()58 .bg(cx.theme().colors().element_background),
84 .with_style(theme.title.container.clone()),
85 )
86 .with_child(Flex::row().with_children([render_mode_button(
87 "Invite new contacts",
88 &theme,
89 cx,
90 )]))
91 .expanded()
92 .contained()
93 .with_style(theme.header),
94 )
95 .with_child(
96 ChildView::new(&self.picker, cx)
97 .contained()
98 .with_style(theme.body),
99 )59 )
100 .constrained()60 .child(self.picker.clone())
101 .with_max_height(theme.max_height)61 .w_96()
102 .with_max_width(theme.max_width)
103 .contained()
104 .with_style(theme.modal)
105 .into_any()
106 }62 }
107 63
108 fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {64 // fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
109 self.has_focus = true;65 // self.has_focus = true;
110 if cx.is_self_focused() {66 // if cx.is_self_focused() {
111 cx.focus(&self.picker)67 // cx.focus(&self.picker)
112 }68 // }
113 }69 // }
114 70
115 fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {71 // fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
116 self.has_focus = false;72 // self.has_focus = false;
117 }73 // }
74
75 type Element = Div;
118}76}
119 77
120impl Modal for ContactFinder {78// impl Modal for ContactFinder {
121 fn has_focus(&self) -> bool {79// fn has_focus(&self) -> bool {
122 self.has_focus80// self.has_focus
123 }81// }
124 82
125 fn dismiss_on_event(event: &Self::Event) -> bool {83// fn dismiss_on_event(event: &Self::Event) -> bool {
126 match event {84// match event {
127 PickerEvent::Dismiss => true,85// PickerEvent::Dismiss => true,
128 }86// }
129 }87// }
130}88// }
131 89
132pub struct ContactFinderDelegate {90pub struct ContactFinderDelegate {
91 parent: WeakView<ContactFinder>,
133 potential_contacts: Arc<[Arc<User>]>,92 potential_contacts: Arc<[Arc<User>]>,
134 user_store: ModelHandle<UserStore>,93 user_store: Model<UserStore>,
135 selected_index: usize,94 selected_index: usize,
136}95}
137 96
138impl PickerDelegate for ContactFinderDelegate {97impl EventEmitter<DismissEvent> for ContactFinder {}
139 fn placeholder_text(&self) -> Arc<str> {98
140 "Search collaborator by username...".into()99impl FocusableView for ContactFinder {
100 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
101 self.picker.focus_handle(cx)
141 }102 }
103}
142 104
105impl PickerDelegate for ContactFinderDelegate {
106 type ListItem = Div;
143 fn match_count(&self) -> usize {107 fn match_count(&self) -> usize {
144 self.potential_contacts.len()108 self.potential_contacts.len()
145 }109 }
@@ -152,6 +116,10 @@ impl PickerDelegate for ContactFinderDelegate { · 6 unmodified lines
152 self.selected_index = ix;116 self.selected_index = ix;
153 }117 }
154 118
119 fn placeholder_text(&self) -> Arc<str> {
120 "Search collaborator by username...".into()
121 }
122
155 fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {123 fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
156 let search_users = self124 let search_users = self
157 .user_store125 .user_store
@@ -161,7 +129,7 @@ impl PickerDelegate for ContactFinderDelegate { · 3 unmodified lines
161 async {129 async {
162 let potential_contacts = search_users.await?;130 let potential_contacts = search_users.await?;
163 picker.update(&mut cx, |picker, cx| {131 picker.update(&mut cx, |picker, cx| {
164 picker.delegate_mut().potential_contacts = potential_contacts.into();132 picker.delegate.potential_contacts = potential_contacts.into();
165 cx.notify();133 cx.notify();
166 })?;134 })?;
167 anyhow::Ok(())135 anyhow::Ok(())
@@ -191,19 +159,18 @@ impl PickerDelegate for ContactFinderDelegate { · 23 unmodified lines
191 }159 }
192 160
193 fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {161 fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
194 cx.emit(PickerEvent::Dismiss);162 //cx.emit(PickerEvent::Dismiss);
163 self.parent
164 .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
165 .log_err();
195 }166 }
196 167
197 fn render_match(168 fn render_match(
198 &self,169 &self,
199 ix: usize,170 ix: usize,
200 mouse_state: &mut MouseState,
201 selected: bool,171 selected: bool,
202 cx: &gpui::AppContext,172 cx: &mut ViewContext<Picker<Self>>,
203 ) -> AnyElement<Picker<Self>> {173 ) -> Option<Self::ListItem> {
204 let full_theme = &theme::current(cx);
205 let theme = &full_theme.collab_panel.contact_finder;
206 let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
207 let user = &self.potential_contacts[ix];174 let user = &self.potential_contacts[ix];
208 let request_status = self.user_store.read(cx).contact_request_status(user);175 let request_status = self.user_store.read(cx).contact_request_status(user);
209 176
@@ -214,48 +181,47 @@ impl PickerDelegate for ContactFinderDelegate { · 4 unmodified lines
214 ContactRequestStatus::RequestSent => Some("icons/x.svg"),181 ContactRequestStatus::RequestSent => Some("icons/x.svg"),
215 ContactRequestStatus::RequestAccepted => None,182 ContactRequestStatus::RequestAccepted => None,
216 };183 };
217 let button_style = if self.user_store.read(cx).is_contact_request_pending(user) {184 dbg!(icon_path);
218 &theme.disabled_contact_button185 Some(
219 } else {186 div()
220 &theme.contact_button187 .flex_1()
221 };188 .justify_between()
222 let style = tabbed_modal189 .children(user.avatar.clone().map(|avatar| img().data(avatar)))
223 .picker190 .child(Label::new(user.github_login.clone()))
224 .item191 .children(icon_path.map(|icon_path| svg().path(icon_path))),
225 .in_state(selected)192 )
226 .style_for(mouse_state);193 // Flex::row()
227 Flex::row()194 // .with_children(user.avatar.clone().map(|avatar| {
228 .with_children(user.avatar.clone().map(|avatar| {195 // Image::from_data(avatar)
229 Image::from_data(avatar)196 // .with_style(theme.contact_avatar)
230 .with_style(theme.contact_avatar)197 // .aligned()
231 .aligned()198 // .left()
232 .left()199 // }))
233 }))200 // .with_child(
234 .with_child(201 // Label::new(user.github_login.clone(), style.label.clone())
235 Label::new(user.github_login.clone(), style.label.clone())202 // .contained()
236 .contained()203 // .with_style(theme.contact_username)
237 .with_style(theme.contact_username)204 // .aligned()
238 .aligned()205 // .left(),
239 .left(),206 // )
240 )207 // .with_children(icon_path.map(|icon_path| {
241 .with_children(icon_path.map(|icon_path| {208 // Svg::new(icon_path)
242 Svg::new(icon_path)209 // .with_color(button_style.color)
243 .with_color(button_style.color)210 // .constrained()
244 .constrained()211 // .with_width(button_style.icon_width)
245 .with_width(button_style.icon_width)212 // .aligned()
246 .aligned()213 // .contained()
247 .contained()214 // .with_style(button_style.container)
248 .with_style(button_style.container)215 // .constrained()
249 .constrained()216 // .with_width(button_style.button_width)
250 .with_width(button_style.button_width)217 // .with_height(button_style.button_width)
251 .with_height(button_style.button_width)218 // .aligned()
252 .aligned()219 // .flex_float()
253 .flex_float()220 // }))
254 }))221 // .contained()
255 .contained()222 // .with_style(style.container)
256 .with_style(style.container)223 // .constrained()
257 .constrained()224 // .with_height(tabbed_modal.row_height)
258 .with_height(tabbed_modal.row_height)225 // .into_any()
259 .into_any()
260 }226 }
261}227}
crates/collab_ui2/src/collab_titlebar_item.rs+163−14
@@ -31,14 +31,17 @@ use std::sync::Arc; · 30 unmodified lines
31use call::ActiveCall;31use call::ActiveCall;
32use client::{Client, UserStore};32use client::{Client, UserStore};
33use gpui::{33use gpui::{
34 div, px, rems, AppContext, Component, Div, InteractiveComponent, Model, ParentComponent,34 div, px, rems, AppContext, Div, Element, InteractiveElement, IntoElement, Model, MouseButton,
35 Render, Stateful, StatefulInteractiveComponent, Styled, Subscription, ViewContext,35 ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled, Subscription,
36 VisualContext, WeakView, WindowBounds,36 ViewContext, VisualContext, WeakView, WindowBounds,
37};37};
38use project::Project;38use project::Project;
39use theme::ActiveTheme;39use theme::ActiveTheme;
40use ui::{h_stack, Button, ButtonVariant, KeyBinding, Label, TextColor, Tooltip};40use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip};
41use workspace::Workspace;41use util::ResultExt;
42use workspace::{notifications::NotifyResultExt, Workspace};
43
44use crate::face_pile::FacePile;
42 45
43// const MAX_PROJECT_NAME_LENGTH: usize = 40;46// const MAX_PROJECT_NAME_LENGTH: usize = 40;
44// const MAX_BRANCH_NAME_LENGTH: usize = 40;47// const MAX_BRANCH_NAME_LENGTH: usize = 40;
@@ -82,9 +85,44 @@ pub struct CollabTitlebarItem { · 37 unmodified lines
82}85}
83 86
84impl Render for CollabTitlebarItem {87impl Render for CollabTitlebarItem {
85 type Element = Stateful<Self, Div<Self>>;88 type Element = Stateful<Div>;
86 89
87 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {90 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
91 let is_in_room = self
92 .workspace
93 .update(cx, |this, cx| this.call_state().is_in_room(cx))
94 .unwrap_or_default();
95 let is_shared = is_in_room && self.project.read(cx).is_shared();
96 let current_user = self.user_store.read(cx).current_user();
97 let client = self.client.clone();
98 let users = self
99 .workspace
100 .update(cx, |this, cx| this.call_state().remote_participants(cx))
101 .log_err()
102 .flatten();
103 let mic_icon = if self
104 .workspace
105 .update(cx, |this, cx| this.call_state().is_muted(cx))
106 .log_err()
107 .flatten()
108 .unwrap_or_default()
109 {
110 ui::Icon::MicMute
111 } else {
112 ui::Icon::Mic
113 };
114 let speakers_icon = if self
115 .workspace
116 .update(cx, |this, cx| this.call_state().is_deafened(cx))
117 .log_err()
118 .flatten()
119 .unwrap_or_default()
120 {
121 ui::Icon::AudioOff
122 } else {
123 ui::Icon::AudioOn
124 };
125 let workspace = self.workspace.clone();
88 h_stack()126 h_stack()
89 .id("titlebar")127 .id("titlebar")
90 .justify_between()128 .justify_between()
@@ -100,7 +138,7 @@ impl Render for CollabTitlebarItem { · 9 unmodified lines
100 |s| s.pl(px(68.)),138 |s| s.pl(px(68.)),
101 )139 )
102 .bg(cx.theme().colors().title_bar_background)140 .bg(cx.theme().colors().title_bar_background)
103 .on_click(|_, event, cx| {141 .on_click(|event, cx| {
104 if event.up.click_count == 2 {142 if event.up.click_count == 2 {
105 cx.zoom_window();143 cx.zoom_window();
106 }144 }
@@ -111,31 +149,37 @@ impl Render for CollabTitlebarItem { · 4 unmodified lines
111 // TODO - Add player menu149 // TODO - Add player menu
112 .child(150 .child(
113 div()151 div()
152 .border()
153 .border_color(gpui::red())
114 .id("project_owner_indicator")154 .id("project_owner_indicator")
115 .child(155 .child(
116 Button::new("player")156 Button::new("player")
117 .variant(ButtonVariant::Ghost)157 .variant(ButtonVariant::Ghost)
118 .color(Some(TextColor::Player(0))),158 .color(Some(Color::Player(0))),
119 )159 )
120 .tooltip(move |_, cx| Tooltip::text("Toggle following", cx)),160 .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
121 )161 )
122 // TODO - Add project menu162 // TODO - Add project menu
123 .child(163 .child(
124 div()164 div()
165 .border()
166 .border_color(gpui::red())
125 .id("titlebar_project_menu_button")167 .id("titlebar_project_menu_button")
126 .child(Button::new("project_name").variant(ButtonVariant::Ghost))168 .child(Button::new("project_name").variant(ButtonVariant::Ghost))
127 .tooltip(move |_, cx| Tooltip::text("Recent Projects", cx)),169 .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
128 )170 )
129 // TODO - Add git menu171 // TODO - Add git menu
130 .child(172 .child(
131 div()173 div()
174 .border()
175 .border_color(gpui::red())
132 .id("titlebar_git_menu_button")176 .id("titlebar_git_menu_button")
133 .child(177 .child(
134 Button::new("branch_name")178 Button::new("branch_name")
135 .variant(ButtonVariant::Ghost)179 .variant(ButtonVariant::Ghost)
136 .color(Some(TextColor::Muted)),180 .color(Some(Color::Muted)),
137 )181 )
138 .tooltip(move |_, cx| {182 .tooltip(move |cx| {
139 cx.build_view(|_| {183 cx.build_view(|_| {
140 Tooltip::new("Recent Branches")184 Tooltip::new("Recent Branches")
141 .key_binding(KeyBinding::new(gpui::KeyBinding::new(185 .key_binding(KeyBinding::new(gpui::KeyBinding::new(
@@ -149,8 +193,113 @@ impl Render for CollabTitlebarItem { · 7 unmodified lines
149 .into()193 .into()
150 }),194 }),
151 ),195 ),
152 ) // self.titlebar_item196 )
153 .child(h_stack().child(Label::new("Right side titlebar item")))197 .when_some(
198 users.zip(current_user.clone()),
199 |this, (remote_participants, current_user)| {
200 let mut pile = FacePile::default();
201 pile.extend(
202 current_user
203 .avatar
204 .clone()
205 .map(|avatar| {
206 div().child(Avatar::data(avatar.clone())).into_any_element()
207 })
208 .into_iter()
209 .chain(remote_participants.into_iter().flat_map(|(user, peer_id)| {
210 user.avatar.as_ref().map(|avatar| {
211 div()
212 .child(
213 Avatar::data(avatar.clone()).into_element().into_any(),
214 )
215 .on_mouse_down(MouseButton::Left, {
216 let workspace = workspace.clone();
217 move |_, cx| {
218 workspace
219 .update(cx, |this, cx| {
220 this.open_shared_screen(peer_id, cx);
221 })
222 .log_err();
223 }
224 })
225 .into_any_element()
226 })
227 })),
228 );
229 this.child(pile.render(cx))
230 },
231 )
232 .child(div().flex_1())
233 .when(is_in_room, |this| {
234 this.child(
235 h_stack()
236 .child(
237 h_stack()
238 .child(Button::new(if is_shared { "Unshare" } else { "Share" }))
239 .child(IconButton::new("leave-call", ui::Icon::Exit).on_click({
240 let workspace = workspace.clone();
241 move |_, cx| {
242 workspace
243 .update(cx, |this, cx| {
244 this.call_state().hang_up(cx).detach();
245 })
246 .log_err();
247 }
248 })),
249 )
250 .child(
251 h_stack()
252 .child(IconButton::new("mute-microphone", mic_icon).on_click({
253 let workspace = workspace.clone();
254 move |_, cx| {
255 workspace
256 .update(cx, |this, cx| {
257 this.call_state().toggle_mute(cx);
258 })
259 .log_err();
260 }
261 }))
262 .child(IconButton::new("mute-sound", speakers_icon).on_click({
263 let workspace = workspace.clone();
264 move |_, cx| {
265 workspace
266 .update(cx, |this, cx| {
267 this.call_state().toggle_deafen(cx);
268 })
269 .log_err();
270 }
271 }))
272 .child(IconButton::new("screen-share", ui::Icon::Screen).on_click(
273 move |_, cx| {
274 workspace
275 .update(cx, |this, cx| {
276 this.call_state().toggle_screen_share(cx);
277 })
278 .log_err();
279 },
280 ))
281 .pl_2(),
282 ),
283 )
284 })
285 .map(|this| {
286 if let Some(user) = current_user {
287 this.when_some(user.avatar.clone(), |this, avatar| {
288 this.child(ui::Avatar::data(avatar))
289 })
290 } else {
291 this.child(Button::new("Sign in").on_click(move |_, cx| {
292 let client = client.clone();
293 cx.spawn(move |mut cx| async move {
294 client
295 .authenticate_and_connect(true, &cx)
296 .await
297 .notify_async_err(&mut cx);
298 })
299 .detach();
300 }))
301 }
302 })
154 }303 }
155}304}
156 305
crates/collab_ui2/src/collab_ui.rs+36−28
@@ -7,11 +7,14 @@ pub mod notification_panel; · 6 unmodified lines
7pub mod notifications;7pub mod notifications;
8mod panel_settings;8mod panel_settings;
9 9
10use std::sync::Arc;10use std::{rc::Rc, sync::Arc};
11 11
12pub use collab_panel::CollabPanel;12pub use collab_panel::CollabPanel;
13pub use collab_titlebar_item::CollabTitlebarItem;13pub use collab_titlebar_item::CollabTitlebarItem;
14use gpui::AppContext;14use gpui::{
15 point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, WindowBounds, WindowKind,
16 WindowOptions,
17};
15pub use panel_settings::{18pub use panel_settings::{
16 ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,19 ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
17};20};
@@ -23,7 +26,7 @@ use workspace::AppState; · 5 unmodified lines
23// [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]26// [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
24// );27// );
25 28
26pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) {29pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
27 CollaborationPanelSettings::register(cx);30 CollaborationPanelSettings::register(cx);
28 ChatPanelSettings::register(cx);31 ChatPanelSettings::register(cx);
29 NotificationPanelSettings::register(cx);32 NotificationPanelSettings::register(cx);
@@ -32,7 +35,7 @@ pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) { · 2 unmodified lines
32 collab_titlebar_item::init(cx);35 collab_titlebar_item::init(cx);
33 collab_panel::init(cx);36 collab_panel::init(cx);
34 // chat_panel::init(cx);37 // chat_panel::init(cx);
35 // notifications::init(&app_state, cx);38 notifications::init(&app_state, cx);
36 39
37 // cx.add_global_action(toggle_screen_sharing);40 // cx.add_global_action(toggle_screen_sharing);
38 // cx.add_global_action(toggle_mute);41 // cx.add_global_action(toggle_mute);
@@ -95,31 +98,36 @@ pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) { · 56 unmodified lines
95// }98// }
96// }99// }
97 100
98// fn notification_window_options(101fn notification_window_options(
99// screen: Rc<dyn Screen>,102 screen: Rc<dyn PlatformDisplay>,
100// window_size: Vector2F,103 window_size: Size<Pixels>,
101// ) -> WindowOptions<'static> {104) -> WindowOptions {
102// const NOTIFICATION_PADDING: f32 = 16.;105 let notification_margin_width = GlobalPixels::from(16.);
106 let notification_margin_height = GlobalPixels::from(-0.) - GlobalPixels::from(48.);
103 107
104// let screen_bounds = screen.content_bounds();108 let screen_bounds = screen.bounds();
105// WindowOptions {109 let size: Size<GlobalPixels> = window_size.into();
106// bounds: WindowBounds::Fixed(RectF::new(110
107// screen_bounds.upper_right()111 // todo!() use content bounds instead of screen.bounds and get rid of magics in point's 2nd argument.
108// + vec2f(112 let bounds = gpui::Bounds::<GlobalPixels> {
109// -NOTIFICATION_PADDING - window_size.x(),113 origin: screen_bounds.upper_right()
110// NOTIFICATION_PADDING,114 - point(
111// ),115 size.width + notification_margin_width,
112// window_size,116 notification_margin_height,
113// )),117 ),
114// titlebar: None,118 size: window_size.into(),
115// center: false,119 };
116// focus: false,120 WindowOptions {
117// show: true,121 bounds: WindowBounds::Fixed(bounds),
118// kind: WindowKind::PopUp,122 titlebar: None,
119// is_movable: false,123 center: false,
120// screen: Some(screen),124 focus: false,
121// }125 show: true,
122// }126 kind: WindowKind::PopUp,
127 is_movable: false,
128 display_id: Some(screen.id()),
129 }
130}
123 131
124// fn render_avatar<T: 'static>(132// fn render_avatar<T: 'static>(
125// avatar: Option<Arc<ImageData>>,133// avatar: Option<Arc<ImageData>>,
crates/collab_ui2/src/face_pile.rs+36−69
@@ -1,54 +1,48 @@
1// use std::ops::Range;1use gpui::{
2 div, AnyElement, Div, IntoElement as _, ParentElement as _, RenderOnce, Styled, WindowContext,
3};
2 4
3// use gpui::{5#[derive(Default)]
4// geometry::{6pub(crate) struct FacePile {
5// rect::RectF,7 faces: Vec<AnyElement>,
6// vector::{vec2f, Vector2F},8}
7// },
8// json::ToJson,
9// serde_json::{self, json},
10// AnyElement, Axis, Element, View, ViewContext,
11// };
12 9
13// pub(crate) struct FacePile<V: View> {10impl RenderOnce for FacePile {
14// overlap: f32,11 type Rendered = Div;
15// faces: Vec<AnyElement<V>>,
16// }
17 12
18// impl<V: View> FacePile<V> {13 fn render(self, _: &mut WindowContext) -> Self::Rendered {
19// pub fn new(overlap: f32) -> Self {14 let player_count = self.faces.len();
20// Self {15 let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
21// overlap,16 let isnt_last = ix < player_count - 1;
22// faces: Vec::new(),
23// }
24// }
25// }
26 17
27// impl<V: View> Element<V> for FacePile<V> {18 div().when(isnt_last, |div| div.neg_mr_1()).child(player)
28// type LayoutState = ();19 });
29// type PaintState = ();20 div().p_1().flex().items_center().children(player_list)
21 }
22}
30 23
24// impl Element for FacePile {
25// type State = ();
31// fn layout(26// fn layout(
32// &mut self,27// &mut self,
33// constraint: gpui::SizeConstraint,28// state: Option<Self::State>,
34// view: &mut V,29// cx: &mut WindowContext,
35// cx: &mut ViewContext<V>,30// ) -> (LayoutId, Self::State) {
36// ) -> (Vector2F, Self::LayoutState) {
37// debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
38
39// let mut width = 0.;31// let mut width = 0.;
40// let mut max_height = 0.;32// let mut max_height = 0.;
33// let mut faces = Vec::with_capacity(self.faces.len());
41// for face in &mut self.faces {34// for face in &mut self.faces {
42// let layout = face.layout(constraint, view, cx);35// let layout = face.layout(cx);
43// width += layout.x();36// width += layout.x();
44// max_height = f32::max(max_height, layout.y());37// max_height = f32::max(max_height, layout.y());
38// faces.push(layout);
45// }39// }
46// width -= self.overlap * self.faces.len().saturating_sub(1) as f32;40// width -= self.overlap * self.faces.len().saturating_sub(1) as f32;
47 41// (cx.request_layout(&Style::default(), faces), ())
48// (42// // (
49// Vector2F::new(width, max_height.clamp(1., constraint.max.y())),43// // Vector2F::new(width, max_height.clamp(1., constraint.max.y())),
50// (),44// // (),
51// )45// // ))
52// }46// }
53 47
54// fn paint(48// fn paint(
@@ -77,37 +71,10 @@ · 22 unmodified lines
77 71
78// ()72// ()
79// }73// }
80
81// fn rect_for_text_range(
82// &self,
83// _: Range<usize>,
84// _: RectF,
85// _: RectF,
86// _: &Self::LayoutState,
87// _: &Self::PaintState,
88// _: &V,
89// _: &ViewContext<V>,
90// ) -> Option<RectF> {
91// None
92// }
93
94// fn debug(
95// &self,
96// bounds: RectF,
97// _: &Self::LayoutState,
98// _: &Self::PaintState,
99// _: &V,
100// _: &ViewContext<V>,
101// ) -> serde_json::Value {
102// json!({
103// "type": "FacePile",
104// "bounds": bounds.to_json()
105// })
106// }
107// }74// }
108 75
109// impl<V: View> Extend<AnyElement<V>> for FacePile<V> {76impl Extend<AnyElement> for FacePile {
110// fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {77 fn extend<T: IntoIterator<Item = AnyElement>>(&mut self, children: T) {
111// self.faces.extend(children);78 self.faces.extend(children);
112// }79 }
113// }80}
crates/collab_ui2/src/notifications.rs+8−8
@@ -1,11 +1,11 @@
1// use gpui::AppContext;1use gpui::AppContext;
2// use std::sync::Arc;2use std::sync::Arc;
3// use workspace::AppState;3use workspace::AppState;
4 4
5// pub mod incoming_call_notification;5pub mod incoming_call_notification;
6// pub mod project_shared_notification;6// pub mod project_shared_notification;
7 7
8// pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {8pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
9// incoming_call_notification::init(app_state, cx);9 incoming_call_notification::init(app_state, cx);
10// project_shared_notification::init(app_state, cx);10 //project_shared_notification::init(app_state, cx);
11// }11}
crates/collab_ui2/src/notifications/incoming_call_notification.rs+198−150
@@ -1,14 +1,12 @@
1use crate::notification_window_options;1use crate::notification_window_options;
2use call::{ActiveCall, IncomingCall};2use call::{ActiveCall, IncomingCall};
3use client::proto;
4use futures::StreamExt;3use futures::StreamExt;
5use gpui::{4use gpui::{
6 elements::*,5 div, green, px, red, AppContext, Div, Element, ParentElement, Render, RenderOnce,
7 geometry::vector::vec2f,6 StatefulInteractiveElement, Styled, ViewContext, VisualContext as _, WindowHandle,
8 platform::{CursorStyle, MouseButton},
9 AnyElement, AppContext, Entity, View, ViewContext, WindowHandle,
10};7};
11use std::sync::{Arc, Weak};8use std::sync::{Arc, Weak};
9use ui::{h_stack, v_stack, Avatar, Button, Label};
12use util::ResultExt;10use util::ResultExt;
13use workspace::AppState;11use workspace::AppState;
14 12
@@ -19,23 +17,44 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { · 4 unmodified lines
19 let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new();17 let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new();
20 while let Some(incoming_call) = incoming_call.next().await {18 while let Some(incoming_call) = incoming_call.next().await {
21 for window in notification_windows.drain(..) {19 for window in notification_windows.drain(..) {
22 window.remove(&mut cx);20 window
21 .update(&mut cx, |_, cx| {
22 // todo!()
23 cx.remove_window();
24 })
25 .log_err();
23 }26 }
24 27
25 if let Some(incoming_call) = incoming_call {28 if let Some(incoming_call) = incoming_call {
26 let window_size = cx.read(|cx| {29 let unique_screens = cx.update(|cx| cx.displays()).unwrap();
27 let theme = &theme::current(cx).incoming_call_notification;30 let window_size = gpui::Size {
28 vec2f(theme.window_width, theme.window_height)31 width: px(380.),
29 });32 height: px(64.),
33 };
30 34
31 for screen in cx.platform().screens() {35 for window in unique_screens {
36 let options = notification_window_options(window, window_size);
32 let window = cx37 let window = cx
33 .add_window(notification_window_options(screen, window_size), |_| {38 .open_window(options, |cx| {
34 IncomingCallNotification::new(incoming_call.clone(), app_state.clone())39 cx.build_view(|_| {
35 });40 IncomingCallNotification::new(
36 41 incoming_call.clone(),
42 app_state.clone(),
43 )
44 })
45 })
46 .unwrap();
37 notification_windows.push(window);47 notification_windows.push(window);
38 }48 }
49
50 // for screen in cx.platform().screens() {
51 // let window = cx
52 // .add_window(notification_window_options(screen, window_size), |_| {
53 // IncomingCallNotification::new(incoming_call.clone(), app_state.clone())
54 // });
55
56 // notification_windows.push(window);
57 // }
39 }58 }
40 }59 }
41 })60 })
@@ -47,167 +66,196 @@ struct RespondToCall { · 5 unmodified lines
47 accept: bool,66 accept: bool,
48}67}
49 68
50pub struct IncomingCallNotification {69struct IncomingCallNotificationState {
51 call: IncomingCall,70 call: IncomingCall,
52 app_state: Weak<AppState>,71 app_state: Weak<AppState>,
53}72}
54 73
55impl IncomingCallNotification {74pub struct IncomingCallNotification {
75 state: Arc<IncomingCallNotificationState>,
76}
77impl IncomingCallNotificationState {
56 pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {78 pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
57 Self { call, app_state }79 Self { call, app_state }
58 }80 }
59 81
60 fn respond(&mut self, accept: bool, cx: &mut ViewContext<Self>) {82 fn respond(&self, accept: bool, cx: &mut AppContext) {
61 let active_call = ActiveCall::global(cx);83 let active_call = ActiveCall::global(cx);
62 if accept {84 if accept {
63 let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));85 let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
64 let caller_user_id = self.call.calling_user.id;
65 let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);86 let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
66 let app_state = self.app_state.clone();87 let app_state = self.app_state.clone();
67 cx.app_context()88 let cx: &mut AppContext = cx;
68 .spawn(|mut cx| async move {89 cx.spawn(|cx| async move {
69 join.await?;90 join.await?;
70 if let Some(project_id) = initial_project_id {91 if let Some(_project_id) = initial_project_id {
71 cx.update(|cx| {92 cx.update(|_cx| {
72 if let Some(app_state) = app_state.upgrade() {93 if let Some(_app_state) = app_state.upgrade() {
73 workspace::join_remote_project(94 // workspace::join_remote_project(
74 project_id,95 // project_id,
75 caller_user_id,96 // caller_user_id,
76 app_state,97 // app_state,
77 cx,98 // cx,
78 )99 // )
79 .detach_and_log_err(cx);100 // .detach_and_log_err(cx);
80 }101 }
81 });102 })
82 }103 .log_err();
83 anyhow::Ok(())104 }
84 })105 anyhow::Ok(())
85 .detach_and_log_err(cx);106 })
107 .detach_and_log_err(cx);
86 } else {108 } else {
87 active_call.update(cx, |active_call, cx| {109 active_call.update(cx, |active_call, cx| {
88 active_call.decline_incoming(cx).log_err();110 active_call.decline_incoming(cx).log_err();
89 });111 });
90 }112 }
91 }113 }
114}
92 115
93 fn render_caller(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {116impl IncomingCallNotification {
94 let theme = &theme::current(cx).incoming_call_notification;117 pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
95 let default_project = proto::ParticipantProject::default();118 Self {
96 let initial_project = self119 state: Arc::new(IncomingCallNotificationState::new(call, app_state)),
97 .call120 }
98 .initial_project
99 .as_ref()
100 .unwrap_or(&default_project);
101 Flex::row()
102 .with_children(self.call.calling_user.avatar.clone().map(|avatar| {
103 Image::from_data(avatar)
104 .with_style(theme.caller_avatar)
105 .aligned()
106 }))
107 .with_child(
108 Flex::column()
109 .with_child(
110 Label::new(
111 self.call.calling_user.github_login.clone(),
112 theme.caller_username.text.clone(),
113 )
114 .contained()
115 .with_style(theme.caller_username.container),
116 )
117 .with_child(
118 Label::new(
119 format!(
120 "is sharing a project in Zed{}",
121 if initial_project.worktree_root_names.is_empty() {
122 ""
123 } else {
124 ":"
125 }
126 ),
127 theme.caller_message.text.clone(),
128 )
129 .contained()
130 .with_style(theme.caller_message.container),
131 )
132 .with_children(if initial_project.worktree_root_names.is_empty() {
133 None
134 } else {
135 Some(
136 Label::new(
137 initial_project.worktree_root_names.join(", "),
138 theme.worktree_roots.text.clone(),
139 )
140 .contained()
141 .with_style(theme.worktree_roots.container),
142 )
143 })
144 .contained()
145 .with_style(theme.caller_metadata)
146 .aligned(),
147 )
148 .contained()
149 .with_style(theme.caller_container)
150 .flex(1., true)
151 .into_any()
152 }121 }
153 122 fn render_caller(&self, cx: &mut ViewContext<Self>) -> impl Element {
154 fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {123 h_stack()
155 enum Accept {}124 .children(
156 enum Decline {}125 self.state
157 126 .call
158 let theme = theme::current(cx);127 .calling_user
159 Flex::column()128 .avatar
160 .with_child(129 .as_ref()
161 MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {130 .map(|avatar| Avatar::data(avatar.clone())),
162 let theme = &theme.incoming_call_notification;
163 Label::new("Accept", theme.accept_button.text.clone())
164 .aligned()
165 .contained()
166 .with_style(theme.accept_button.container)
167 })
168 .with_cursor_style(CursorStyle::PointingHand)
169 .on_click(MouseButton::Left, |_, this, cx| {
170 this.respond(true, cx);
171 })
172 .flex(1., true),
173 )131 )
174 .with_child(132 .child(
175 MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {133 v_stack()
176 let theme = &theme.incoming_call_notification;134 .child(Label::new(format!(
177 Label::new("Decline", theme.decline_button.text.clone())135 "{} is sharing a project in Zed",
178 .aligned()136 self.state.call.calling_user.github_login
179 .contained()137 )))
180 .with_style(theme.decline_button.container)138 .child(self.render_buttons(cx)),
181 })
182 .with_cursor_style(CursorStyle::PointingHand)
183 .on_click(MouseButton::Left, |_, this, cx| {
184 this.respond(false, cx);
185 })
186 .flex(1., true),
187 )139 )
188 .constrained()140 // let theme = &theme::current(cx).incoming_call_notification;
189 .with_width(theme.incoming_call_notification.button_width)141 // let default_project = proto::ParticipantProject::default();
190 .into_any()142 // let initial_project = self
143 // .call
144 // .initial_project
145 // .as_ref()
146 // .unwrap_or(&default_project);
147 // Flex::row()
148 // .with_children(self.call.calling_user.avatar.clone().map(|avatar| {
149 // Image::from_data(avatar)
150 // .with_style(theme.caller_avatar)
151 // .aligned()
152 // }))
153 // .with_child(
154 // Flex::column()
155 // .with_child(
156 // Label::new(
157 // self.call.calling_user.github_login.clone(),
158 // theme.caller_username.text.clone(),
159 // )
160 // .contained()
161 // .with_style(theme.caller_username.container),
162 // )
163 // .with_child(
164 // Label::new(
165 // format!(
166 // "is sharing a project in Zed{}",
167 // if initial_project.worktree_root_names.is_empty() {
168 // ""
169 // } else {
170 // ":"
171 // }
172 // ),
173 // theme.caller_message.text.clone(),
174 // )
175 // .contained()
176 // .with_style(theme.caller_message.container),
177 // )
178 // .with_children(if initial_project.worktree_root_names.is_empty() {
179 // None
180 // } else {
181 // Some(
182 // Label::new(
183 // initial_project.worktree_root_names.join(", "),
184 // theme.worktree_roots.text.clone(),
185 // )
186 // .contained()
187 // .with_style(theme.worktree_roots.container),
188 // )
189 // })
190 // .contained()
191 // .with_style(theme.caller_metadata)
192 // .aligned(),
193 // )
194 // .contained()
195 // .with_style(theme.caller_container)
196 // .flex(1., true)
197 // .into_any()
191 }198 }
192}
193 199
194impl Entity for IncomingCallNotification {200 fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
195 type Event = ();201 h_stack()
196}202 .child(Button::new("Accept").render(cx).bg(green()).on_click({
203 let state = self.state.clone();
204 move |_, cx| state.respond(true, cx)
205 }))
206 .child(Button::new("Decline").render(cx).bg(red()).on_click({
207 let state = self.state.clone();
208 move |_, cx| state.respond(false, cx)
209 }))
197 210
198impl View for IncomingCallNotification {211 // enum Accept {}
199 fn ui_name() -> &'static str {212 // enum Decline {}
200 "IncomingCallNotification"
201 }
202 213
203 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {214 // let theme = theme::current(cx);
204 let background = theme::current(cx).incoming_call_notification.background;215 // Flex::column()
205 Flex::row()216 // .with_child(
206 .with_child(self.render_caller(cx))217 // MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
207 .with_child(self.render_buttons(cx))218 // let theme = &theme.incoming_call_notification;
208 .contained()219 // Label::new("Accept", theme.accept_button.text.clone())
209 .with_background_color(background)220 // .aligned()
210 .expanded()221 // .contained()
211 .into_any()222 // .with_style(theme.accept_button.container)
223 // })
224 // .with_cursor_style(CursorStyle::PointingHand)
225 // .on_click(MouseButton::Left, |_, this, cx| {
226 // this.respond(true, cx);
227 // })
228 // .flex(1., true),
229 // )
230 // .with_child(
231 // MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {
232 // let theme = &theme.incoming_call_notification;
233 // Label::new("Decline", theme.decline_button.text.clone())
234 // .aligned()
235 // .contained()
236 // .with_style(theme.decline_button.container)
237 // })
238 // .with_cursor_style(CursorStyle::PointingHand)
239 // .on_click(MouseButton::Left, |_, this, cx| {
240 // this.respond(false, cx);
241 // })
242 // .flex(1., true),
243 // )
244 // .constrained()
245 // .with_width(theme.incoming_call_notification.button_width)
246 // .into_any()
247 }
248}
249impl Render for IncomingCallNotification {
250 type Element = Div;
251 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
252 div().bg(red()).flex_none().child(self.render_caller(cx))
253 // Flex::row()
254 // .with_child()
255 // .with_child(self.render_buttons(cx))
256 // .contained()
257 // .with_background_color(background)
258 // .expanded()
259 // .into_any()
212 }260 }
213}261}
crates/command_palette2/src/command_palette.rs+14−23
@@ -1,17 +1,15 @@
1use collections::{CommandPaletteFilter, HashMap};1use collections::{CommandPaletteFilter, HashMap};
2use fuzzy::{StringMatch, StringMatchCandidate};2use fuzzy::{StringMatch, StringMatchCandidate};
3use gpui::{3use gpui::{
4 actions, div, prelude::*, Action, AppContext, Component, Div, EventEmitter, FocusHandle,4 actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
5 FocusableView, Keystroke, Manager, ParentComponent, Render, Styled, View, ViewContext,5 Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
6 VisualContext, WeakView,
7};6};
8use picker::{Picker, PickerDelegate};7use picker::{Picker, PickerDelegate};
9use std::{8use std::{
10 cmp::{self, Reverse},9 cmp::{self, Reverse},
11 sync::Arc,10 sync::Arc,
12};11};
13use theme::ActiveTheme;12use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, ListItem};
14use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, StyledExt};
15use util::{13use util::{
16 channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},14 channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
17 ResultExt,15 ResultExt,
@@ -69,7 +67,7 @@ impl CommandPalette { · 51 unmodified lines
69 }67 }
70}68}
71 69
72impl EventEmitter<Manager> for CommandPalette {}70impl EventEmitter<DismissEvent> for CommandPalette {}
73 71
74impl FocusableView for CommandPalette {72impl FocusableView for CommandPalette {
75 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {73 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
@@ -78,7 +76,7 @@ impl FocusableView for CommandPalette { · 2 unmodified lines
78}76}
79 77
80impl Render for CommandPalette {78impl Render for CommandPalette {
81 type Element = Div<Self>;79 type Element = Div;
82 80
83 fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {81 fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
84 v_stack().w_96().child(self.picker.clone())82 v_stack().w_96().child(self.picker.clone())
@@ -141,7 +139,7 @@ impl CommandPaletteDelegate { · 56 unmodified lines
141}139}
142 140
143impl PickerDelegate for CommandPaletteDelegate {141impl PickerDelegate for CommandPaletteDelegate {
144 type ListItem = Div<Picker<Self>>;142 type ListItem = ListItem;
145 143
146 fn placeholder_text(&self) -> Arc<str> {144 fn placeholder_text(&self) -> Arc<str> {
147 "Execute a command...".into()145 "Execute a command...".into()
@@ -269,7 +267,7 @@ impl PickerDelegate for CommandPaletteDelegate { · 121 unmodified lines
269 267
270 fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {268 fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
271 self.command_palette269 self.command_palette
272 .update(cx, |_, cx| cx.emit(Manager::Dismiss))270 .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
273 .log_err();271 .log_err();
274 }272 }
275 273
@@ -294,24 +292,16 @@ impl PickerDelegate for CommandPaletteDelegate { · 18 unmodified lines
294 ix: usize,292 ix: usize,
295 selected: bool,293 selected: bool,
296 cx: &mut ViewContext<Picker<Self>>,294 cx: &mut ViewContext<Picker<Self>>,
297 ) -> Self::ListItem {295 ) -> Option<Self::ListItem> {
298 let colors = cx.theme().colors();
299 let Some(r#match) = self.matches.get(ix) else {296 let Some(r#match) = self.matches.get(ix) else {
300 return div();297 return None;
301 };298 };
302 let Some(command) = self.commands.get(r#match.candidate_id) else {299 let Some(command) = self.commands.get(r#match.candidate_id) else {
303 return div();300 return None;
304 };301 };
305 302
306 div()303 Some(
307 .px_1()304 ListItem::new(ix).selected(selected).child(
308 .text_color(colors.text)
309 .text_ui()
310 .bg(colors.ghost_element_background)
311 .rounded_md()
312 .when(selected, |this| this.bg(colors.ghost_element_selected))
313 .hover(|this| this.bg(colors.ghost_element_hover))
314 .child(
315 h_stack()305 h_stack()
316 .justify_between()306 .justify_between()
317 .child(HighlightedLabel::new(307 .child(HighlightedLabel::new(
@@ -319,7 +309,8 @@ impl PickerDelegate for CommandPaletteDelegate { · 1 unmodified line
319 r#match.positions.clone(),309 r#match.positions.clone(),
320 ))310 ))
321 .children(KeyBinding::for_action(&*command.action, cx)),311 .children(KeyBinding::for_action(&*command.action, cx)),
322 )312 ),
313 )
323 }314 }
324}315}
325 316
crates/diagnostics2/src/diagnostics.rs+19−18
@@ -13,10 +13,10 @@ use editor::{ · 12 unmodified lines
13};13};
14use futures::future::try_join_all;14use futures::future::try_join_all;
15use gpui::{15use gpui::{
16 actions, div, AnyElement, AnyView, AppContext, Component, Context, Div, EventEmitter,16 actions, div, AnyElement, AnyView, AppContext, Context, Div, EventEmitter, FocusEvent,
17 FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView, InteractiveComponent,17 FocusHandle, Focusable, FocusableElement, FocusableView, InteractiveElement, IntoElement,
18 Model, ParentComponent, Render, SharedString, Styled, Subscription, Task, View, ViewContext,18 Model, ParentElement, Render, SharedString, Styled, Subscription, Task, View, ViewContext,
19 VisualContext, WeakView,19 VisualContext, WeakView, WindowContext,
20};20};
21use language::{21use language::{
22 Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,22 Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@@ -36,7 +36,7 @@ use std::{ · 13 unmodified lines
36};36};
37use theme::ActiveTheme;37use theme::ActiveTheme;
38pub use toolbar_controls::ToolbarControls;38pub use toolbar_controls::ToolbarControls;
39use ui::{h_stack, HighlightedLabel, Icon, IconElement, Label, TextColor};39use ui::{h_stack, Color, HighlightedLabel, Icon, IconElement, Label};
40use util::TryFutureExt;40use util::TryFutureExt;
41use workspace::{41use workspace::{
42 item::{BreadcrumbText, Item, ItemEvent, ItemHandle},42 item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
@@ -91,7 +91,7 @@ struct DiagnosticGroupState { · 48 unmodified lines
91impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {}91impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {}
92 92
93impl Render for ProjectDiagnosticsEditor {93impl Render for ProjectDiagnosticsEditor {
94 type Element = Focusable<Self, Div<Self>>;94 type Element = Focusable<Div>;
95 95
96 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {96 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
97 let child = if self.path_states.is_empty() {97 let child = if self.path_states.is_empty() {
@@ -109,8 +109,8 @@ impl Render for ProjectDiagnosticsEditor { · 11 unmodified lines
109 div()109 div()
110 .track_focus(&self.focus_handle)110 .track_focus(&self.focus_handle)
111 .size_full()111 .size_full()
112 .on_focus_in(Self::focus_in)112 .on_focus_in(cx.listener(Self::focus_in))
113 .on_action(Self::toggle_warnings)113 .on_action(cx.listener(Self::toggle_warnings))
114 .child(child)114 .child(child)
115 }115 }
116}116}
@@ -662,7 +662,7 @@ impl Item for ProjectDiagnosticsEditor { · 545 unmodified lines
662 Some("Project Diagnostics".into())662 Some("Project Diagnostics".into())
663 }663 }
664 664
665 fn tab_content<T: 'static>(&self, _detail: Option<usize>, _: &AppContext) -> AnyElement<T> {665 fn tab_content(&self, _detail: Option<usize>, _: &WindowContext) -> AnyElement {
666 render_summary(&self.summary)666 render_summary(&self.summary)
667 }667 }
668 668
@@ -742,7 +742,7 @@ impl Item for ProjectDiagnosticsEditor { · 73 unmodified lines
742 }742 }
743 743
744 fn breadcrumb_location(&self) -> ToolbarItemLocation {744 fn breadcrumb_location(&self) -> ToolbarItemLocation {
745 ToolbarItemLocation::PrimaryLeft { flex: None }745 ToolbarItemLocation::PrimaryLeft
746 }746 }
747 747
748 fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {748 fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
@@ -778,27 +778,28 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { · 29 unmodified lines
778 .bg(gpui::red())778 .bg(gpui::red())
779 .map(|stack| {779 .map(|stack| {
780 let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {780 let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
781 IconElement::new(Icon::XCircle).color(TextColor::Error)781 IconElement::new(Icon::XCircle).color(Color::Error)
782 } else {782 } else {
783 IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning)783 IconElement::new(Icon::ExclamationTriangle).color(Color::Warning)
784 };784 };
785 785
786 stack.child(div().pl_8().child(icon))786 stack.child(div().pl_8().child(icon))
787 })787 })
788 .when_some(diagnostic.source.as_ref(), |stack, source| {788 .when_some(diagnostic.source.as_ref(), |stack, source| {
789 stack.child(Label::new(format!("{source}:")).color(TextColor::Accent))789 stack.child(Label::new(format!("{source}:")).color(Color::Accent))
790 })790 })
791 .child(HighlightedLabel::new(message.clone(), highlights.clone()))791 .child(HighlightedLabel::new(message.clone(), highlights.clone()))
792 .when_some(diagnostic.code.as_ref(), |stack, code| {792 .when_some(diagnostic.code.as_ref(), |stack, code| {
793 stack.child(Label::new(code.clone()))793 stack.child(Label::new(code.clone()))
794 })794 })
795 .render()795 .into_any_element()
796 })796 })
797}797}
798 798
799pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElement<T> {799pub(crate) fn render_summary(summary: &DiagnosticSummary) -> AnyElement {
800 if summary.error_count == 0 && summary.warning_count == 0 {800 if summary.error_count == 0 && summary.warning_count == 0 {
801 Label::new("No problems").render()801 let label = Label::new("No problems");
802 label.into_any_element()
802 } else {803 } else {
803 h_stack()804 h_stack()
804 .bg(gpui::red())805 .bg(gpui::red())
@@ -806,7 +807,7 @@ pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElem · 1 unmodified line
806 .child(Label::new(summary.error_count.to_string()))807 .child(Label::new(summary.error_count.to_string()))
807 .child(IconElement::new(Icon::ExclamationTriangle))808 .child(IconElement::new(Icon::ExclamationTriangle))
808 .child(Label::new(summary.warning_count.to_string()))809 .child(Label::new(summary.warning_count.to_string()))
809 .render()810 .into_any_element()
810 }811 }
811}812}
812 813
@@ -1549,7 +1550,7 @@ mod tests { · 736 unmodified lines
1549 block_id: ix,1550 block_id: ix,
1550 editor_style: &editor::EditorStyle::default(),1551 editor_style: &editor::EditorStyle::default(),
1551 })1552 })
1552 .element_id()?1553 .inner_id()?
1553 .try_into()1554 .try_into()
1554 .ok()?,1555 .ok()?,
1555 1556
crates/diagnostics2/src/items.rs+14−14
@@ -1,13 +1,13 @@
1use collections::HashSet;1use collections::HashSet;
2use editor::{Editor, GoToDiagnostic};2use editor::{Editor, GoToDiagnostic};
3use gpui::{3use gpui::{
4 rems, Div, EventEmitter, InteractiveComponent, ParentComponent, Render, Stateful,4 rems, Div, EventEmitter, InteractiveElement, ParentElement, Render, Stateful,
5 StatefulInteractiveComponent, Styled, Subscription, View, ViewContext, WeakView,5 StatefulInteractiveElement, Styled, Subscription, View, ViewContext, WeakView,
6};6};
7use language::Diagnostic;7use language::Diagnostic;
8use lsp::LanguageServerId;8use lsp::LanguageServerId;
9use theme::ActiveTheme;9use theme::ActiveTheme;
10use ui::{h_stack, Icon, IconElement, Label, TextColor, Tooltip};10use ui::{h_stack, Color, Icon, IconElement, Label, Tooltip};
11use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};11use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
12 12
13use crate::ProjectDiagnosticsEditor;13use crate::ProjectDiagnosticsEditor;
@@ -22,30 +22,30 @@ pub struct DiagnosticIndicator { · 8 unmodified lines
22}22}
23 23
24impl Render for DiagnosticIndicator {24impl Render for DiagnosticIndicator {
25 type Element = Stateful<Self, Div<Self>>;25 type Element = Stateful<Div>;
26 26
27 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {27 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
28 let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {28 let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
29 (0, 0) => h_stack().child(IconElement::new(Icon::Check).color(TextColor::Success)),29 (0, 0) => h_stack().child(IconElement::new(Icon::Check).color(Color::Success)),
30 (0, warning_count) => h_stack()30 (0, warning_count) => h_stack()
31 .gap_1()31 .gap_1()
32 .child(IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning))32 .child(IconElement::new(Icon::ExclamationTriangle).color(Color::Warning))
33 .child(Label::new(warning_count.to_string())),33 .child(Label::new(warning_count.to_string())),
34 (error_count, 0) => h_stack()34 (error_count, 0) => h_stack()
35 .gap_1()35 .gap_1()
36 .child(IconElement::new(Icon::XCircle).color(TextColor::Error))36 .child(IconElement::new(Icon::XCircle).color(Color::Error))
37 .child(Label::new(error_count.to_string())),37 .child(Label::new(error_count.to_string())),
38 (error_count, warning_count) => h_stack()38 (error_count, warning_count) => h_stack()
39 .gap_1()39 .gap_1()
40 .child(IconElement::new(Icon::XCircle).color(TextColor::Error))40 .child(IconElement::new(Icon::XCircle).color(Color::Error))
41 .child(Label::new(error_count.to_string()))41 .child(Label::new(error_count.to_string()))
42 .child(IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning))42 .child(IconElement::new(Icon::ExclamationTriangle).color(Color::Warning))
43 .child(Label::new(warning_count.to_string())),43 .child(Label::new(warning_count.to_string())),
44 };44 };
45 45
46 h_stack()46 h_stack()
47 .id(cx.entity_id())47 .id("diagnostic-indicator")
48 .on_action(Self::go_to_next_diagnostic)48 .on_action(cx.listener(Self::go_to_next_diagnostic))
49 .rounded_md()49 .rounded_md()
50 .flex_none()50 .flex_none()
51 .h(rems(1.375))51 .h(rems(1.375))
@@ -54,14 +54,14 @@ impl Render for DiagnosticIndicator { · 2 unmodified lines
54 .bg(cx.theme().colors().ghost_element_background)54 .bg(cx.theme().colors().ghost_element_background)
55 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))55 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
56 .active(|style| style.bg(cx.theme().colors().ghost_element_active))56 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
57 .tooltip(|_, cx| Tooltip::text("Project Diagnostics", cx))57 .tooltip(|cx| Tooltip::text("Project Diagnostics", cx))
58 .on_click(|this, _, cx| {58 .on_click(cx.listener(|this, _, cx| {
59 if let Some(workspace) = this.workspace.upgrade() {59 if let Some(workspace) = this.workspace.upgrade() {
60 workspace.update(cx, |workspace, cx| {60 workspace.update(cx, |workspace, cx| {
61 ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)61 ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
62 })62 })
63 }63 }
64 })64 }))
65 .child(diagnostic_indicator)65 .child(diagnostic_indicator)
66 }66 }
67}67}
crates/diagnostics2/src/toolbar_controls.rs+6−6
@@ -1,5 +1,5 @@
1use crate::ProjectDiagnosticsEditor;1use crate::ProjectDiagnosticsEditor;
2use gpui::{div, Div, EventEmitter, ParentComponent, Render, ViewContext, WeakView};2use gpui::{div, Div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
3use ui::{Icon, IconButton, Tooltip};3use ui::{Icon, IconButton, Tooltip};
4use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};4use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
5 5
@@ -8,7 +8,7 @@ pub struct ToolbarControls { · 2 unmodified lines
8}8}
9 9
10impl Render for ToolbarControls {10impl Render for ToolbarControls {
11 type Element = Div<Self>;11 type Element = Div;
12 12
13 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {13 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
14 let include_warnings = self14 let include_warnings = self
@@ -26,14 +26,14 @@ impl Render for ToolbarControls { · 11 unmodified lines
26 26
27 div().child(27 div().child(
28 IconButton::new("toggle-warnings", Icon::ExclamationTriangle)28 IconButton::new("toggle-warnings", Icon::ExclamationTriangle)
29 .tooltip(move |_, cx| Tooltip::text(tooltip, cx))29 .tooltip(move |cx| Tooltip::text(tooltip, cx))
30 .on_click(|this: &mut Self, cx| {30 .on_click(cx.listener(|this, _, cx| {
31 if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {31 if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {
32 editor.update(cx, |editor, cx| {32 editor.update(cx, |editor, cx| {
33 editor.toggle_warnings(&Default::default(), cx);33 editor.toggle_warnings(&Default::default(), cx);
34 });34 });
35 }35 }
36 }),36 })),
37 )37 )
38 }38 }
39}39}
@@ -49,7 +49,7 @@ impl ToolbarItemView for ToolbarControls { · 9 unmodified lines
49 if let Some(pane_item) = active_pane_item.as_ref() {49 if let Some(pane_item) = active_pane_item.as_ref() {
50 if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {50 if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
51 self.editor = Some(editor.downgrade());51 self.editor = Some(editor.downgrade());
52 ToolbarItemLocation::PrimaryRight { flex: None }52 ToolbarItemLocation::PrimaryRight
53 } else {53 } else {
54 ToolbarItemLocation::Hidden54 ToolbarItemLocation::Hidden
55 }55 }
crates/editor/src/editor.rs+45−22
@@ -1001,17 +1001,18 @@ impl CompletionsMenu { · 1000 unmodified lines
1001 1001
1002 fn pre_resolve_completion_documentation(1002 fn pre_resolve_completion_documentation(
1003 &self,1003 &self,
1004 project: Option<ModelHandle<Project>>,1004 editor: &Editor,
1005 cx: &mut ViewContext<Editor>,1005 cx: &mut ViewContext<Editor>,
1006 ) {1006 ) -> Option<Task<()>> {
1007 let settings = settings::get::<EditorSettings>(cx);1007 let settings = settings::get::<EditorSettings>(cx);
1008 if !settings.show_completion_documentation {1008 if !settings.show_completion_documentation {
1009 return;1009 return None;
1010 }1010 }
1011 1011
1012 let Some(project) = project else {1012 let Some(project) = editor.project.clone() else {
1013 return;1013 return None;
1014 };1014 };
1015
1015 let client = project.read(cx).client();1016 let client = project.read(cx).client();
1016 let language_registry = project.read(cx).languages().clone();1017 let language_registry = project.read(cx).languages().clone();
1017 1018
@@ -1021,7 +1022,7 @@ impl CompletionsMenu { · 3 unmodified lines
1021 let completions = self.completions.clone();1022 let completions = self.completions.clone();
1022 let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();1023 let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
1023 1024
1024 cx.spawn(move |this, mut cx| async move {1025 Some(cx.spawn(move |this, mut cx| async move {
1025 if is_remote {1026 if is_remote {
1026 let Some(project_id) = project_id else {1027 let Some(project_id) = project_id else {
1027 log::error!("Remote project without remote_id");1028 log::error!("Remote project without remote_id");
@@ -1083,8 +1084,7 @@ impl CompletionsMenu { · 55 unmodified lines
1083 _ = this.update(&mut cx, |_, cx| cx.notify());1084 _ = this.update(&mut cx, |_, cx| cx.notify());
1084 }1085 }
1085 }1086 }
1086 })1087 }))
1087 .detach();
1088 }1088 }
1089 1089
1090 fn attempt_resolve_selected_completion_documentation(1090 fn attempt_resolve_selected_completion_documentation(
@@ -3423,7 +3423,7 @@ impl Editor { · 2332 unmodified lines
3423 to_insert,3423 to_insert,
3424 }) = self.inlay_hint_cache.spawn_hint_refresh(3424 }) = self.inlay_hint_cache.spawn_hint_refresh(
3425 reason_description,3425 reason_description,
3426 self.excerpt_visible_offsets(required_languages.as_ref(), cx),3426 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
3427 invalidate_cache,3427 invalidate_cache,
3428 cx,3428 cx,
3429 ) {3429 ) {
@@ -3442,11 +3442,15 @@ impl Editor { · 12 unmodified lines
3442 .collect()3442 .collect()
3443 }3443 }
3444 3444
3445 pub fn excerpt_visible_offsets(3445 pub fn excerpts_for_inlay_hints_query(
3446 &self,3446 &self,
3447 restrict_to_languages: Option<&HashSet<Arc<Language>>>,3447 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
3448 cx: &mut ViewContext<'_, '_, Editor>,3448 cx: &mut ViewContext<'_, '_, Editor>,
3449 ) -> HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)> {3449 ) -> HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)> {
3450 let Some(project) = self.project.as_ref() else {
3451 return HashMap::default();
3452 };
3453 let project = project.read(cx);
3450 let multi_buffer = self.buffer().read(cx);3454 let multi_buffer = self.buffer().read(cx);
3451 let multi_buffer_snapshot = multi_buffer.snapshot(cx);3455 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
3452 let multi_buffer_visible_start = self3456 let multi_buffer_visible_start = self
@@ -3466,6 +3470,14 @@ impl Editor { · 13 unmodified lines
3466 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())3470 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
3467 .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {3471 .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {
3468 let buffer = buffer_handle.read(cx);3472 let buffer = buffer_handle.read(cx);
3473 let buffer_file = project::worktree::File::from_dyn(buffer.file())?;
3474 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
3475 let worktree_entry = buffer_worktree
3476 .read(cx)
3477 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
3478 if worktree_entry.is_ignored {
3479 return None;
3480 }
3469 let language = buffer.language()?;3481 let language = buffer.language()?;
3470 if let Some(restrict_to_languages) = restrict_to_languages {3482 if let Some(restrict_to_languages) = restrict_to_languages {
3471 if !restrict_to_languages.contains(language) {3483 if !restrict_to_languages.contains(language) {
@@ -3580,7 +3592,8 @@ impl Editor { · 108 unmodified lines
3580 let id = post_inc(&mut self.next_completion_id);3592 let id = post_inc(&mut self.next_completion_id);
3581 let task = cx.spawn(|this, mut cx| {3593 let task = cx.spawn(|this, mut cx| {
3582 async move {3594 async move {
3583 let menu = if let Some(completions) = completions.await.log_err() {3595 let completions = completions.await.log_err();
3596 let (menu, pre_resolve_task) = if let Some(completions) = completions {
3584 let mut menu = CompletionsMenu {3597 let mut menu = CompletionsMenu {
3585 id,3598 id,
3586 initial_position: position,3599 initial_position: position,
@@ -3601,21 +3614,26 @@ impl Editor { · 14 unmodified lines
3601 selected_item: 0,3614 selected_item: 0,
3602 list: Default::default(),3615 list: Default::default(),
3603 };3616 };
3617
3604 menu.filter(query.as_deref(), cx.background()).await;3618 menu.filter(query.as_deref(), cx.background()).await;
3619
3605 if menu.matches.is_empty() {3620 if menu.matches.is_empty() {
3606 None3621 (None, None)
3607 } else {3622 } else {
3608 _ = this.update(&mut cx, |editor, cx| {3623 let pre_resolve_task = this
3609 menu.pre_resolve_completion_documentation(editor.project.clone(), cx);3624 .update(&mut cx, |editor, cx| {
3610 });3625 menu.pre_resolve_completion_documentation(editor, cx)
3611 Some(menu)3626 })
3627 .ok()
3628 .flatten();
3629 (Some(menu), pre_resolve_task)
3612 }3630 }
3613 } else {3631 } else {
3614 None3632 (None, None)
3615 };3633 };
3616 3634
3617 this.update(&mut cx, |this, cx| {3635 this.update(&mut cx, |this, cx| {
3618 this.completion_tasks.retain(|(task_id, _)| *task_id > id);3636 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
3619 3637
3620 let mut context_menu = this.context_menu.write();3638 let mut context_menu = this.context_menu.write();
3621 match context_menu.as_ref() {3639 match context_menu.as_ref() {
@@ -3636,10 +3654,10 @@ impl Editor { · 14 unmodified lines
3636 drop(context_menu);3654 drop(context_menu);
3637 this.discard_copilot_suggestion(cx);3655 this.discard_copilot_suggestion(cx);
3638 cx.notify();3656 cx.notify();
3639 } else if this.completion_tasks.is_empty() {3657 } else if this.completion_tasks.len() <= 1 {
3640 // If there are no more completion tasks and the last menu was3658 // If there are no more completion tasks (omitting ourself) and
3641 // empty, we should hide it. If it was already hidden, we should3659 // the last menu was empty, we should hide it. If it was already
3642 // also show the copilot suggestion when available.3660 // hidden, we should also show the copilot suggestion when available.
3643 drop(context_menu);3661 drop(context_menu);
3644 if this.hide_context_menu(cx).is_none() {3662 if this.hide_context_menu(cx).is_none() {
3645 this.update_visible_copilot_suggestion(cx);3663 this.update_visible_copilot_suggestion(cx);
@@ -3647,10 +3665,15 @@ impl Editor { · 1 unmodified line
3647 }3665 }
3648 })?;3666 })?;
3649 3667
3668 if let Some(pre_resolve_task) = pre_resolve_task {
3669 pre_resolve_task.await;
3670 }
3671
3650 Ok::<_, anyhow::Error>(())3672 Ok::<_, anyhow::Error>(())
3651 }3673 }
3652 .log_err()3674 .log_err()
3653 });3675 });
3676
3654 self.completion_tasks.push((id, task));3677 self.completion_tasks.push((id, task));
3655 }3678 }
3656 3679
crates/editor/src/inlay_hint_cache.rs+4−2
@@ -861,7 +861,7 @@ async fn fetch_and_update_hints( · 860 unmodified lines
861 let inlay_hints_fetch_task = editor861 let inlay_hints_fetch_task = editor
862 .update(&mut cx, |editor, cx| {862 .update(&mut cx, |editor, cx| {
863 if got_throttled {863 if got_throttled {
864 let query_not_around_visible_range = match editor.excerpt_visible_offsets(None, cx).remove(&query.excerpt_id) {864 let query_not_around_visible_range = match editor.excerpts_for_inlay_hints_query(None, cx).remove(&query.excerpt_id) {
865 Some((_, _, current_visible_range)) => {865 Some((_, _, current_visible_range)) => {
866 let visible_offset_length = current_visible_range.len();866 let visible_offset_length = current_visible_range.len();
867 let double_visible_range = current_visible_range867 let double_visible_range = current_visible_range
@@ -2237,7 +2237,9 @@ pub mod tests { · 1369 unmodified lines
2237 editor: &ViewHandle<Editor>,2237 editor: &ViewHandle<Editor>,
2238 cx: &mut gpui::TestAppContext,2238 cx: &mut gpui::TestAppContext,
2239 ) -> Range<Point> {2239 ) -> Range<Point> {
2240 let ranges = editor.update(cx, |editor, cx| editor.excerpt_visible_offsets(None, cx));2240 let ranges = editor.update(cx, |editor, cx| {
2241 editor.excerpts_for_inlay_hints_query(None, cx)
2242 });
2241 assert_eq!(2243 assert_eq!(
2242 ranges.len(),2244 ranges.len(),
2243 1,2245 1,
crates/editor2/src/display_map/block_map.rs+3−3
@@ -50,7 +50,7 @@ struct BlockRow(u32); · 49 unmodified lines
50#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]50#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
51struct WrapRow(u32);51struct WrapRow(u32);
52 52
53pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>;53pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement>;
54 54
55pub struct Block {55pub struct Block {
56 id: BlockId,56 id: BlockId,
@@ -69,7 +69,7 @@ where · 12 unmodified lines
69 pub position: P,69 pub position: P,
70 pub height: u8,70 pub height: u8,
71 pub style: BlockStyle,71 pub style: BlockStyle,
72 pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>,72 pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement>,
73 pub disposition: BlockDisposition,73 pub disposition: BlockDisposition,
74}74}
75 75
@@ -947,7 +947,7 @@ impl DerefMut for BlockContext<'_, '_> { · 871 unmodified lines
947}947}
948 948
949impl Block {949impl Block {
950 pub fn render(&self, cx: &mut BlockContext) -> AnyElement<Editor> {950 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
951 self.render.lock()(cx)951 self.render.lock()(cx)
952 }952 }
953 953
crates/editor2/src/editor.rs+530−558
@@ -40,11 +40,12 @@ use fuzzy::{StringMatch, StringMatchCandidate}; · 39 unmodified lines
40use git::diff_hunk_to_display;40use git::diff_hunk_to_display;
41use gpui::{41use gpui::{
42 actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement,42 actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement,
43 AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,43 AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
44 EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle,44 DispatchPhase, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontFeatures,
45 Hsla, InputHandler, KeyContext, Model, MouseButton, ParentComponent, Pixels, Render, Styled,45 FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model,
46 Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext,46 MouseButton, ParentElement, Pixels, Render, RenderOnce, SharedString, Styled, StyledText,
47 WeakView, WindowContext,47 Subscription, Task, TextRun, TextStyle, UniformListScrollHandle, View, ViewContext,
48 VisualContext, WeakView, WhiteSpace, WindowContext,
48};49};
49use highlight_matching_bracket::refresh_matching_bracket_highlights;50use highlight_matching_bracket::refresh_matching_bracket_highlights;
50use hover_popover::{hide_hover, HoverState};51use hover_popover::{hide_hover, HoverState};
@@ -54,13 +55,14 @@ use itertools::Itertools; · 3 unmodified lines
54pub use language::{char_kind, CharKind};55pub use language::{char_kind, CharKind};
55use language::{56use language::{
56 language_settings::{self, all_language_settings, InlayHintSettings},57 language_settings::{self, all_language_settings, InlayHintSettings},
57 point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, Completion, CursorShape,58 markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel,
58 Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName,59 Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language,
59 OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,60 LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal,
61 TransactionId,
60};62};
61use lazy_static::lazy_static;63use lazy_static::lazy_static;
62use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};64use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
63use lsp::{DiagnosticSeverity, Documentation, LanguageServerId};65use lsp::{DiagnosticSeverity, LanguageServerId};
64use movement::TextLayoutDetails;66use movement::TextLayoutDetails;
65use multi_buffer::ToOffsetUtf16;67use multi_buffer::ToOffsetUtf16;
66pub use multi_buffer::{68pub use multi_buffer::{
@@ -97,12 +99,12 @@ use text::{OffsetUtf16, Rope}; · 30 unmodified lines
97use theme::{99use theme::{
98 ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,100 ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
99};101};
100use ui::{v_stack, HighlightedLabel, IconButton, StyledExt, Tooltip};102use ui::{h_stack, v_stack, HighlightedLabel, IconButton, Popover, StyledExt, Tooltip};
101use util::{post_inc, RangeExt, ResultExt, TryFutureExt};103use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
102use workspace::{104use workspace::{
103 item::{ItemEvent, ItemHandle},105 item::{ItemEvent, ItemHandle},
104 searchable::SearchEvent,106 searchable::SearchEvent,
105 ItemNavHistory, SplitDirection, ViewId, Workspace,107 ItemNavHistory, Pane, SplitDirection, ViewId, Workspace,
106};108};
107 109
108const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);110const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
@@ -115,70 +117,70 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis · 6 unmodified lines
115 117
116pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);118pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
117 119
118// pub fn render_parsed_markdown<Tag: 'static>(120pub fn render_parsed_markdown(
119// parsed: &language::ParsedMarkdown,121 element_id: impl Into<ElementId>,
120// editor_style: &EditorStyle,122 parsed: &language::ParsedMarkdown,
121// workspace: Option<WeakView<Workspace>>,123 editor_style: &EditorStyle,
122// cx: &mut ViewContext<Editor>,124 workspace: Option<WeakView<Workspace>>,
123// ) -> Text {125 cx: &mut ViewContext<Editor>,
124// enum RenderedMarkdown {}126) -> InteractiveText {
125 127 let code_span_background_color = cx
126// let parsed = parsed.clone();128 .theme()
127// let view_id = cx.view_id();129 .colors()
128// let code_span_background_color = editor_style.document_highlight_read_background;130 .editor_document_highlight_read_background;
129 131
130// let mut region_id = 0;132 let highlights = gpui::combine_highlights(
131 133 parsed.highlights.iter().filter_map(|(range, highlight)| {
132// todo!()134 let highlight = highlight.to_highlight_style(&editor_style.syntax)?;
133// // Text::new(parsed.text, editor_style.text.clone())135 Some((range.clone(), highlight))
134// // .with_highlights(136 }),
135// // parsed137 parsed
136// // .highlights138 .regions
137// // .iter()139 .iter()
138// // .filter_map(|(range, highlight)| {140 .zip(&parsed.region_ranges)
139// // let highlight = highlight.to_highlight_style(&editor_style.syntax)?;141 .filter_map(|(region, range)| {
140// // Some((range.clone(), highlight))142 if region.code {
141// // })143 Some((
142// // .collect::<Vec<_>>(),144 range.clone(),
143// // )145 HighlightStyle {
144// // .with_custom_runs(parsed.region_ranges, move |ix, bounds, cx| {146 background_color: Some(code_span_background_color),
145// // region_id += 1;147 ..Default::default()
146// // let region = parsed.regions[ix].clone();148 },
147 149 ))
148// // if let Some(link) = region.link {150 } else {
149// // cx.scene().push_cursor_region(CursorRegion {151 None
150// // bounds,152 }
151// // style: CursorStyle::PointingHand,153 }),
152// // });154 );
153// // cx.scene().push_mouse_region(155 let runs = text_runs_for_highlights(&parsed.text, &editor_style.text, highlights);
154// // MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds)156
155// // .on_down::<Editor, _>(MouseButton::Left, move |_, _, cx| match &link {157 // todo!("add the ability to change cursor style for link ranges")
156// // markdown::Link::Web { url } => cx.platform().open_url(url),158 let mut links = Vec::new();
157// // markdown::Link::Path { path } => {159 let mut link_ranges = Vec::new();
158// // if let Some(workspace) = &workspace {160 for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
159// // _ = workspace.update(cx, |workspace, cx| {161 if let Some(link) = region.link.clone() {
160// // workspace.open_abs_path(path.clone(), false, cx).detach();162 links.push(link);
161// // });163 link_ranges.push(range.clone());
162// // }164 }
163// // }165 }
164// // }),166
165// // );167 InteractiveText::new(
166// // }168 element_id,
167 169 StyledText::new(parsed.text.clone()).with_runs(runs),
168// // if region.code {170 )
169// // cx.draw_quad(Quad {171 .on_click(link_ranges, move |clicked_range_ix, cx| {
170// // bounds,172 match &links[clicked_range_ix] {
171// // background: Some(code_span_background_color),173 markdown::Link::Web { url } => cx.open_url(url),
172// // corner_radii: (2.0).into(),174 markdown::Link::Path { path } => {
173// // order: todo!(),175 if let Some(workspace) = &workspace {
174// // content_mask: todo!(),176 _ = workspace.update(cx, |workspace, cx| {
175// // border_color: todo!(),177 workspace.open_abs_path(path.clone(), false, cx).detach();
176// // border_widths: todo!(),178 });
177// // });179 }
178// // }180 }
179// // })181 }
180// // .with_soft_wrap(true)182 })
181// }183}
182 184
183#[derive(PartialEq, Clone, Deserialize, Default, Action)]185#[derive(PartialEq, Clone, Deserialize, Default, Action)]
184pub struct SelectNext {186pub struct SelectNext {
@@ -529,8 +531,6 @@ pub fn init(cx: &mut AppContext) { · 344 unmodified lines
529 // cx.register_action_type(Editor::context_menu_next);531 // cx.register_action_type(Editor::context_menu_next);
530 // cx.register_action_type(Editor::context_menu_last);532 // cx.register_action_type(Editor::context_menu_last);
531 533
532 hover_popover::init(cx);
533
534 workspace::register_project_item::<Editor>(cx);534 workspace::register_project_item::<Editor>(cx);
535 workspace::register_followable_item::<Editor>(cx);535 workspace::register_followable_item::<Editor>(cx);
536 workspace::register_deserializable_item::<Editor>(cx);536 workspace::register_deserializable_item::<Editor>(cx);
@@ -663,6 +663,7 @@ pub struct Editor { · 126 unmodified lines
663 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,663 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
664 gutter_width: Pixels,664 gutter_width: Pixels,
665 style: Option<EditorStyle>,665 style: Option<EditorStyle>,
666 editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
666}667}
667 668
668pub struct EditorSnapshot {669pub struct EditorSnapshot {
@@ -905,12 +906,16 @@ impl ContextMenu { · 236 unmodified lines
905 &self,906 &self,
906 cursor_position: DisplayPoint,907 cursor_position: DisplayPoint,
907 style: &EditorStyle,908 style: &EditorStyle,
909 max_height: Pixels,
908 workspace: Option<WeakView<Workspace>>,910 workspace: Option<WeakView<Workspace>>,
909 cx: &mut ViewContext<Editor>,911 cx: &mut ViewContext<Editor>,
910 ) -> (DisplayPoint, AnyElement<Editor>) {912 ) -> (DisplayPoint, AnyElement) {
911 match self {913 match self {
912 ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)),914 ContextMenu::Completions(menu) => (
913 ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),915 cursor_position,
916 menu.render(style, max_height, workspace, cx),
917 ),
918 ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, max_height, cx),
914 }919 }
915 }920 }
916}921}
@@ -966,20 +971,22 @@ impl CompletionsMenu { · 49 unmodified lines
966 971
967 fn pre_resolve_completion_documentation(972 fn pre_resolve_completion_documentation(
968 &self,973 &self,
969 project: Option<Model<Project>>,974 _editor: &Editor,
970 cx: &mut ViewContext<Editor>,975 _cx: &mut ViewContext<Editor>,
971 ) {976 ) -> Option<Task<()>> {
972 // todo!("implementation below ");977 // todo!("implementation below ");
978 None
973 }979 }
974 // ) {980 // {
975 // let settings = EditorSettings::get_global(cx);981 // let settings = EditorSettings::get_global(cx);
976 // if !settings.show_completion_documentation {982 // if !settings.show_completion_documentation {
977 // return;983 // return None;
978 // }984 // }
979 985
980 // let Some(project) = project else {986 // let Some(project) = editor.project.clone() else {
981 // return;987 // return None;
982 // };988 // };
989
983 // let client = project.read(cx).client();990 // let client = project.read(cx).client();
984 // let language_registry = project.read(cx).languages().clone();991 // let language_registry = project.read(cx).languages().clone();
985 992
@@ -989,7 +996,7 @@ impl CompletionsMenu { · 3 unmodified lines
989 // let completions = self.completions.clone();996 // let completions = self.completions.clone();
990 // let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();997 // let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
991 998
992 // cx.spawn(move |this, mut cx| async move {999 // Some(cx.spawn(move |this, mut cx| async move {
993 // if is_remote {1000 // if is_remote {
994 // let Some(project_id) = project_id else {1001 // let Some(project_id) = project_id else {
995 // log::error!("Remote project without remote_id");1002 // log::error!("Remote project without remote_id");
@@ -1051,8 +1058,7 @@ impl CompletionsMenu { · 55 unmodified lines
1051 // _ = this.update(&mut cx, |_, cx| cx.notify());1058 // _ = this.update(&mut cx, |_, cx| cx.notify());
1052 // }1059 // }
1053 // }1060 // }
1054 // })1061 // }))
1055 // .detach();
1056 // }1062 // }
1057 1063
1058 fn attempt_resolve_selected_completion_documentation(1064 fn attempt_resolve_selected_completion_documentation(
@@ -1221,210 +1227,156 @@ impl CompletionsMenu { · 162 unmodified lines
1221 fn render(1227 fn render(
1222 &self,1228 &self,
1223 style: &EditorStyle,1229 style: &EditorStyle,
1230 max_height: Pixels,
1224 workspace: Option<WeakView<Workspace>>,1231 workspace: Option<WeakView<Workspace>>,
1225 cx: &mut ViewContext<Editor>,1232 cx: &mut ViewContext<Editor>,
1226 ) -> AnyElement<Editor> {1233 ) -> AnyElement {
1227 todo!("old implementation below")1234 let settings = EditorSettings::get_global(cx);
1228 }1235 let show_completion_documentation = settings.show_completion_documentation;
1229 1236
1230 // enum CompletionTag {}1237 let widest_completion_ix = self
1238 .matches
1239 .iter()
1240 .enumerate()
1241 .max_by_key(|(_, mat)| {
1242 let completions = self.completions.read();
1243 let completion = &completions[mat.candidate_id];
1244 let documentation = &completion.documentation;
1245
1246 let mut len = completion.label.text.chars().count();
1247 if let Some(Documentation::SingleLine(text)) = documentation {
1248 if show_completion_documentation {
1249 len += text.chars().count();
1250 }
1251 }
1231 1252
1232 // let settings = EditorSettings>(cx);1253 len
1233 // let show_completion_documentation = settings.show_completion_documentation;1254 })
1255 .map(|(ix, _)| ix);
1234 1256
1235 // let widest_completion_ix = self1257 let completions = self.completions.clone();
1236 // .matches1258 let matches = self.matches.clone();
1237 // .iter()1259 let selected_item = self.selected_item;
1238 // .enumerate()1260 let style = style.clone();
1239 // .max_by_key(|(_, mat)| {
1240 // let completions = self.completions.read();
1241 // let completion = &completions[mat.candidate_id];
1242 // let documentation = &completion.documentation;
1243 1261
1244 // let mut len = completion.label.text.chars().count();1262 let multiline_docs = {
1245 // if let Some(Documentation::SingleLine(text)) = documentation {1263 let mat = &self.matches[selected_item];
1246 // if show_completion_documentation {1264 let multiline_docs = match &self.completions.read()[mat.candidate_id].documentation {
1247 // len += text.chars().count();1265 Some(Documentation::MultiLinePlainText(text)) => {
1248 // }1266 Some(div().child(SharedString::from(text.clone())))
1249 // }1267 }
1268 Some(Documentation::MultiLineMarkdown(parsed)) => Some(div().child(
1269 render_parsed_markdown("completions_markdown", parsed, &style, workspace, cx),
1270 )),
1271 _ => None,
1272 };
1273 multiline_docs.map(|div| {
1274 div.id("multiline_docs")
1275 .max_h(max_height)
1276 .flex_1()
1277 .px_1p5()
1278 .py_1()
1279 .min_w(px(260.))
1280 .max_w(px(640.))
1281 .w(px(500.))
1282 .text_ui()
1283 .overflow_y_scroll()
1284 // Prevent a mouse down on documentation from being propagated to the editor,
1285 // because that would move the cursor.
1286 .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
1287 })
1288 };
1289 let list = uniform_list(
1290 cx.view().clone(),
1291 "completions",
1292 matches.len(),
1293 move |editor, range, cx| {
1294 let start_ix = range.start;
1295 let completions_guard = completions.read();
1250 1296
1251 // len1297 matches[range]
1252 // })1298 .iter()
1253 // .map(|(ix, _)| ix);1299 .enumerate()
1300 .map(|(ix, mat)| {
1301 let item_ix = start_ix + ix;
1302 let candidate_id = mat.candidate_id;
1303 let completion = &completions_guard[candidate_id];
1254 1304
1255 // let completions = self.completions.clone();1305 let documentation = if show_completion_documentation {
1256 // let matches = self.matches.clone();1306 &completion.documentation
1257 // let selected_item = self.selected_item;1307 } else {
1258 1308 &None
1259 // let list = UniformList::new(self.list.clone(), matches.len(), cx, {1309 };
1260 // let style = style.clone();
1261 // move |_, range, items, cx| {
1262 // let start_ix = range.start;
1263 // let completions_guard = completions.read();
1264
1265 // for (ix, mat) in matches[range].iter().enumerate() {
1266 // let item_ix = start_ix + ix;
1267 // let candidate_id = mat.candidate_id;
1268 // let completion = &completions_guard[candidate_id];
1269
1270 // let documentation = if show_completion_documentation {
1271 // &completion.documentation
1272 // } else {
1273 // &None
1274 // };
1275 1310
1276 // items.push(1311 let highlights = gpui::combine_highlights(
1277 // MouseEventHandler::new::<CompletionTag, _>(1312 mat.ranges().map(|range| (range, FontWeight::BOLD.into())),
1278 // mat.candidate_id,1313 styled_runs_for_code_label(&completion.label, &style.syntax).map(
1279 // cx,1314 |(range, mut highlight)| {
1280 // |state, _| {1315 // Ignore font weight for syntax highlighting, as we'll use it
1281 // let item_style = if item_ix == selected_item {1316 // for fuzzy matches.
1282 // style.autocomplete.selected_item1317 highlight.font_weight = None;
1283 // } else if state.hovered() {1318 (range, highlight)
1284 // style.autocomplete.hovered_item1319 },
1285 // } else {1320 ),
1286 // style.autocomplete.item1321 );
1287 // };1322 let completion_label = StyledText::new(completion.label.text.clone())
1288 1323 .with_runs(text_runs_for_highlights(
1289 // let completion_label =1324 &completion.label.text,
1290 // Text::new(completion.label.text.clone(), style.text.clone())1325 &style.text,
1291 // .with_soft_wrap(false)1326 highlights,
1292 // .with_highlights(1327 ));
1293 // combine_syntax_and_fuzzy_match_highlights(1328 let documentation_label =
1294 // &completion.label.text,1329 if let Some(Documentation::SingleLine(text)) = documentation {
1295 // style.text.color.into(),1330 Some(SharedString::from(text.clone()))
1296 // styled_runs_for_code_label(1331 } else {
1297 // &completion.label,1332 None
1298 // &style.syntax,1333 };
1299 // ),1334
1300 // &mat.positions,1335 div()
1301 // ),1336 .id(mat.candidate_id)
1302 // );1337 .min_w(px(220.))
1303 1338 .max_w(px(540.))
1304 // if let Some(Documentation::SingleLine(text)) = documentation {1339 .whitespace_nowrap()
1305 // Flex::row()1340 .overflow_hidden()
1306 // .with_child(completion_label)1341 .text_ui()
1307 // .with_children((|| {1342 .px_1()
1308 // let text_style = TextStyle {1343 .rounded(px(4.))
1309 // color: style.autocomplete.inline_docs_color,1344 .bg(cx.theme().colors().ghost_element_background)
1310 // font_size: style.text.font_size1345 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1311 // * style.autocomplete.inline_docs_size_percent,1346 .when(item_ix == selected_item, |div| {
1312 // ..style.text.clone()1347 div.bg(cx.theme().colors().ghost_element_selected)
1313 // };1348 })
1314 1349 .on_mouse_down(
1315 // let label = Text::new(text.clone(), text_style)1350 MouseButton::Left,
1316 // .aligned()1351 cx.listener(move |editor, event, cx| {
1317 // .constrained()1352 cx.stop_propagation();
1318 // .dynamically(move |constraint, _, _| {1353 editor
1319 // gpui::SizeConstraint {1354 .confirm_completion(
1320 // min: constraint.min,1355 &ConfirmCompletion {
1321 // max: vec2f(1356 item_ix: Some(item_ix),
1322 // constraint.max.x(),1357 },
1323 // constraint.min.y(),1358 cx,
1324 // ),1359 )
1325 // }1360 .map(|task| task.detach_and_log_err(cx));
1326 // });1361 }),
1327 1362 )
1328 // if Some(item_ix) == widest_completion_ix {1363 .child(completion_label)
1329 // Some(1364 .children(documentation_label)
1330 // label1365 })
1331 // .contained()1366 .collect()
1332 // .with_style(1367 },
1333 // style1368 )
1334 // .autocomplete1369 .max_h(max_height)
1335 // .inline_docs_container,1370 .track_scroll(self.scroll_handle.clone())
1336 // )1371 .with_width_from_item(widest_completion_ix);
1337 // .into_any(),1372
1338 // )1373 Popover::new()
1339 // } else {1374 .child(list)
1340 // Some(label.flex_float().into_any())1375 .when_some(multiline_docs, |popover, multiline_docs| {
1341 // }1376 popover.aside(multiline_docs)
1342 // })())1377 })
1343 // .into_any()1378 .into_any_element()
1344 // } else {1379 }
1345 // completion_label.into_any()
1346 // }
1347 // .contained()
1348 // .with_style(item_style)
1349 // .constrained()
1350 // .dynamically(
1351 // move |constraint, _, _| {
1352 // if Some(item_ix) == widest_completion_ix {
1353 // constraint
1354 // } else {
1355 // gpui::SizeConstraint {
1356 // min: constraint.min,
1357 // max: constraint.min,
1358 // }
1359 // }
1360 // },
1361 // )
1362 // },
1363 // )
1364 // .with_cursor_style(CursorStyle::PointingHand)
1365 // .on_down(MouseButton::Left, move |_, this, cx| {
1366 // this.confirm_completion(
1367 // &ConfirmCompletion {
1368 // item_ix: Some(item_ix),
1369 // },
1370 // cx,
1371 // )
1372 // .map(|task| task.detach());
1373 // })
1374 // .constrained()
1375 // .with_min_width(style.autocomplete.completion_min_width)
1376 // .with_max_width(style.autocomplete.completion_max_width)
1377 // .into_any(),
1378 // );
1379 // }
1380 // }
1381 // })
1382 // .with_width_from_item(widest_completion_ix);
1383
1384 // enum MultiLineDocumentation {}
1385
1386 // Flex::row()
1387 // .with_child(list.flex(1., false))
1388 // .with_children({
1389 // let mat = &self.matches[selected_item];
1390 // let completions = self.completions.read();
1391 // let completion = &completions[mat.candidate_id];
1392 // let documentation = &completion.documentation;
1393
1394 // match documentation {
1395 // Some(Documentation::MultiLinePlainText(text)) => Some(
1396 // Flex::column()
1397 // .scrollable::<MultiLineDocumentation>(0, None, cx)
1398 // .with_child(
1399 // Text::new(text.clone(), style.text.clone()).with_soft_wrap(true),
1400 // )
1401 // .contained()
1402 // .with_style(style.autocomplete.alongside_docs_container)
1403 // .constrained()
1404 // .with_max_width(style.autocomplete.alongside_docs_max_width)
1405 // .flex(1., false),
1406 // ),
1407
1408 // Some(Documentation::MultiLineMarkdown(parsed)) => Some(
1409 // Flex::column()
1410 // .scrollable::<MultiLineDocumentation>(0, None, cx)
1411 // .with_child(render_parsed_markdown::<MultiLineDocumentation>(
1412 // parsed, &style, workspace, cx,
1413 // ))
1414 // .contained()
1415 // .with_style(style.autocomplete.alongside_docs_container)
1416 // .constrained()
1417 // .with_max_width(style.autocomplete.alongside_docs_max_width)
1418 // .flex(1., false),
1419 // ),
1420
1421 // _ => None,
1422 // }
1423 // })
1424 // .contained()
1425 // .with_style(style.autocomplete.container)
1426 // .into_any()
1427 // }
1428 1380
1429 pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {1381 pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
1430 let mut matches = if let Some(query) = query {1382 let mut matches = if let Some(query) = query {
@@ -1540,14 +1492,17 @@ impl CodeActionsMenu { · 109 unmodified lines
1540 &self,1492 &self,
1541 mut cursor_position: DisplayPoint,1493 mut cursor_position: DisplayPoint,
1542 style: &EditorStyle,1494 style: &EditorStyle,
1495 max_height: Pixels,
1543 cx: &mut ViewContext<Editor>,1496 cx: &mut ViewContext<Editor>,
1544 ) -> (DisplayPoint, AnyElement<Editor>) {1497 ) -> (DisplayPoint, AnyElement) {
1545 let actions = self.actions.clone();1498 let actions = self.actions.clone();
1546 let selected_item = self.selected_item;1499 let selected_item = self.selected_item;
1500
1547 let element = uniform_list(1501 let element = uniform_list(
1502 cx.view().clone(),
1548 "code_actions_menu",1503 "code_actions_menu",
1549 self.actions.len(),1504 self.actions.len(),
1550 move |editor, range, cx| {1505 move |this, range, cx| {
1551 actions[range.clone()]1506 actions[range.clone()]
1552 .iter()1507 .iter()
1553 .enumerate()1508 .enumerate()
@@ -1569,18 +1524,22 @@ impl CodeActionsMenu { · 15 unmodified lines
1569 .bg(colors.element_hover)1524 .bg(colors.element_hover)
1570 .text_color(colors.text_accent)1525 .text_color(colors.text_accent)
1571 })1526 })
1572 .on_mouse_down(MouseButton::Left, move |editor: &mut Editor, _, cx| {1527 .on_mouse_down(
1573 cx.stop_propagation();1528 MouseButton::Left,
1574 editor1529 cx.listener(move |editor, _, cx| {
1575 .confirm_code_action(1530 cx.stop_propagation();
1576 &ConfirmCodeAction {1531 editor
1577 item_ix: Some(item_ix),1532 .confirm_code_action(
1578 },1533 &ConfirmCodeAction {
1579 cx,1534 item_ix: Some(item_ix),
1580 )1535 },
1581 .map(|task| task.detach_and_log_err(cx));1536 cx,
1582 })1537 )
1583 .child(action.lsp_action.title.clone())1538 .map(|task| task.detach_and_log_err(cx));
1539 }),
1540 )
1541 // TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
1542 .child(SharedString::from(action.lsp_action.title.clone()))
1584 })1543 })
1585 .collect()1544 .collect()
1586 },1545 },
@@ -1588,6 +1547,8 @@ impl CodeActionsMenu { · 1 unmodified line
1588 .elevation_1(cx)1547 .elevation_1(cx)
1589 .px_2()1548 .px_2()
1590 .py_1()1549 .py_1()
1550 .max_h(max_height)
1551 .track_scroll(self.scroll_handle.clone())
1591 .with_width_from_item(1552 .with_width_from_item(
1592 self.actions1553 self.actions
1593 .iter()1554 .iter()
@@ -1595,7 +1556,7 @@ impl CodeActionsMenu { · 1 unmodified line
1595 .max_by_key(|(_, action)| action.lsp_action.title.chars().count())1556 .max_by_key(|(_, action)| action.lsp_action.title.chars().count())
1596 .map(|(ix, _)| ix),1557 .map(|(ix, _)| ix),
1597 )1558 )
1598 .render();1559 .into_any_element();
1599 1560
1600 if self.deployed_from_indicator {1561 if self.deployed_from_indicator {
1601 *cursor_position.column_mut() = 0;1562 *cursor_position.column_mut() = 0;
@@ -1943,6 +1904,7 @@ impl Editor { · 341 unmodified lines
1943 pixel_position_of_newest_cursor: None,1904 pixel_position_of_newest_cursor: None,
1944 gutter_width: Default::default(),1905 gutter_width: Default::default(),
1945 style: None,1906 style: None,
1907 editor_actions: Default::default(),
1946 _subscriptions: vec![1908 _subscriptions: vec![
1947 cx.observe(&buffer, Self::on_buffer_changed),1909 cx.observe(&buffer, Self::on_buffer_changed),
1948 cx.subscribe(&buffer, Self::on_buffer_event),1910 cx.subscribe(&buffer, Self::on_buffer_event),
@@ -2074,10 +2036,14 @@ impl Editor { · 125 unmodified lines
2074 &self.buffer2036 &self.buffer
2075 }2037 }
2076 2038
2077 fn workspace(&self) -> Option<View<Workspace>> {2039 pub fn workspace(&self) -> Option<View<Workspace>> {
2078 self.workspace.as_ref()?.0.upgrade()2040 self.workspace.as_ref()?.0.upgrade()
2079 }2041 }
2080 2042
2043 pub fn pane(&self, cx: &AppContext) -> Option<View<Pane>> {
2044 self.workspace()?.read(cx).pane_for(&self.handle.upgrade()?)
2045 }
2046
2081 pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> {2047 pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> {
2082 self.buffer().read(cx).title(cx)2048 self.buffer().read(cx).title(cx)
2083 }2049 }
@@ -2319,6 +2285,7 @@ impl Editor { · 235 unmodified lines
2319 2285
2320 self.blink_manager.update(cx, BlinkManager::pause_blinking);2286 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2321 cx.emit(EditorEvent::SelectionsChanged { local });2287 cx.emit(EditorEvent::SelectionsChanged { local });
2288 cx.emit(SearchEvent::MatchesInvalidated);
2322 2289
2323 if self.selections.disjoint_anchors().len() == 1 {2290 if self.selections.disjoint_anchors().len() == 1 {
2324 cx.emit(SearchEvent::ActiveMatchChanged)2291 cx.emit(SearchEvent::ActiveMatchChanged)
@@ -3437,7 +3404,7 @@ impl Editor { · 1112 unmodified lines
3437 to_insert,3404 to_insert,
3438 }) = self.inlay_hint_cache.spawn_hint_refresh(3405 }) = self.inlay_hint_cache.spawn_hint_refresh(
3439 reason_description,3406 reason_description,
3440 self.excerpt_visible_offsets(required_languages.as_ref(), cx),3407 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
3441 invalidate_cache,3408 invalidate_cache,
3442 cx,3409 cx,
3443 ) {3410 ) {
@@ -3456,11 +3423,15 @@ impl Editor { · 12 unmodified lines
3456 .collect()3423 .collect()
3457 }3424 }
3458 3425
3459 pub fn excerpt_visible_offsets(3426 pub fn excerpts_for_inlay_hints_query(
3460 &self,3427 &self,
3461 restrict_to_languages: Option<&HashSet<Arc<Language>>>,3428 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
3462 cx: &mut ViewContext<Editor>,3429 cx: &mut ViewContext<Editor>,
3463 ) -> HashMap<ExcerptId, (Model<Buffer>, clock::Global, Range<usize>)> {3430 ) -> HashMap<ExcerptId, (Model<Buffer>, clock::Global, Range<usize>)> {
3431 let Some(project) = self.project.as_ref() else {
3432 return HashMap::default();
3433 };
3434 let project = project.read(cx);
3464 let multi_buffer = self.buffer().read(cx);3435 let multi_buffer = self.buffer().read(cx);
3465 let multi_buffer_snapshot = multi_buffer.snapshot(cx);3436 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
3466 let multi_buffer_visible_start = self3437 let multi_buffer_visible_start = self
@@ -3480,6 +3451,15 @@ impl Editor { · 13 unmodified lines
3480 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())3451 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
3481 .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {3452 .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {
3482 let buffer = buffer_handle.read(cx);3453 let buffer = buffer_handle.read(cx);
3454 let buffer_file = project::worktree::File::from_dyn(buffer.file())?;
3455 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
3456 let worktree_entry = buffer_worktree
3457 .read(cx)
3458 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
3459 if worktree_entry.is_ignored {
3460 return None;
3461 }
3462
3483 let language = buffer.language()?;3463 let language = buffer.language()?;
3484 if let Some(restrict_to_languages) = restrict_to_languages {3464 if let Some(restrict_to_languages) = restrict_to_languages {
3485 if !restrict_to_languages.contains(language) {3465 if !restrict_to_languages.contains(language) {
@@ -3594,7 +3574,8 @@ impl Editor { · 108 unmodified lines
3594 let id = post_inc(&mut self.next_completion_id);3574 let id = post_inc(&mut self.next_completion_id);
3595 let task = cx.spawn(|this, mut cx| {3575 let task = cx.spawn(|this, mut cx| {
3596 async move {3576 async move {
3597 let menu = if let Some(completions) = completions.await.log_err() {3577 let completions = completions.await.log_err();
3578 let (menu, pre_resolve_task) = if let Some(completions) = completions {
3598 let mut menu = CompletionsMenu {3579 let mut menu = CompletionsMenu {
3599 id,3580 id,
3600 initial_position: position,3581 initial_position: position,
@@ -3617,20 +3598,24 @@ impl Editor { · 16 unmodified lines
3617 };3598 };
3618 menu.filter(query.as_deref(), cx.background_executor().clone())3599 menu.filter(query.as_deref(), cx.background_executor().clone())
3619 .await;3600 .await;
3601
3620 if menu.matches.is_empty() {3602 if menu.matches.is_empty() {
3621 None3603 (None, None)
3622 } else {3604 } else {
3623 _ = this.update(&mut cx, |editor, cx| {3605 let pre_resolve_task = this
3624 menu.pre_resolve_completion_documentation(editor.project.clone(), cx);3606 .update(&mut cx, |editor, cx| {
3625 });3607 menu.pre_resolve_completion_documentation(editor, cx)
3626 Some(menu)3608 })
3609 .ok()
3610 .flatten();
3611 (Some(menu), pre_resolve_task)
3627 }3612 }
3628 } else {3613 } else {
3629 None3614 (None, None)
3630 };3615 };
3631 3616
3632 this.update(&mut cx, |this, cx| {3617 this.update(&mut cx, |this, cx| {
3633 this.completion_tasks.retain(|(task_id, _)| *task_id > id);3618 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
3634 3619
3635 let mut context_menu = this.context_menu.write();3620 let mut context_menu = this.context_menu.write();
3636 match context_menu.as_ref() {3621 match context_menu.as_ref() {
@@ -3662,142 +3647,147 @@ impl Editor { · 25 unmodified lines
3662 }3647 }
3663 })?;3648 })?;
3664 3649
3650 if let Some(pre_resolve_task) = pre_resolve_task {
3651 pre_resolve_task.await;
3652 }
3653
3665 Ok::<_, anyhow::Error>(())3654 Ok::<_, anyhow::Error>(())
3666 }3655 }
3667 .log_err()3656 .log_err()
3668 });3657 });
3658
3669 self.completion_tasks.push((id, task));3659 self.completion_tasks.push((id, task));
3670 }3660 }
3671 3661
3672 // pub fn confirm_completion(3662 pub fn confirm_completion(
3673 // &mut self,3663 &mut self,
3674 // action: &ConfirmCompletion,3664 action: &ConfirmCompletion,
3675 // cx: &mut ViewContext<Self>,3665 cx: &mut ViewContext<Self>,
3676 // ) -> Option<Task<Result<()>>> {3666 ) -> Option<Task<Result<()>>> {
3677 // use language::ToOffset as _;3667 use language::ToOffset as _;
3678 3668
3679 // let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? {3669 let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
3680 // menu3670 menu
3681 // } else {3671 } else {
3682 // return None;3672 return None;
3683 // };3673 };
3684 3674
3685 // let mat = completions_menu3675 let mat = completions_menu
3686 // .matches3676 .matches
3687 // .get(action.item_ix.unwrap_or(completions_menu.selected_item))?;3677 .get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
3688 // let buffer_handle = completions_menu.buffer;3678 let buffer_handle = completions_menu.buffer;
3689 // let completions = completions_menu.completions.read();3679 let completions = completions_menu.completions.read();
3690 // let completion = completions.get(mat.candidate_id)?;3680 let completion = completions.get(mat.candidate_id)?;
3691 3681
3692 // let snippet;3682 let snippet;
3693 // let text;3683 let text;
3694 // if completion.is_snippet() {3684 if completion.is_snippet() {
3695 // snippet = Some(Snippet::parse(&completion.new_text).log_err()?);3685 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
3696 // text = snippet.as_ref().unwrap().text.clone();3686 text = snippet.as_ref().unwrap().text.clone();
3697 // } else {3687 } else {
3698 // snippet = None;3688 snippet = None;
3699 // text = completion.new_text.clone();3689 text = completion.new_text.clone();
3700 // };3690 };
3701 // let selections = self.selections.all::<usize>(cx);3691 let selections = self.selections.all::<usize>(cx);
3702 // let buffer = buffer_handle.read(cx);3692 let buffer = buffer_handle.read(cx);
3703 // let old_range = completion.old_range.to_offset(buffer);3693 let old_range = completion.old_range.to_offset(buffer);
3704 // let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();3694 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
3705
3706 // let newest_selection = self.selections.newest_anchor();
3707 // if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
3708 // return None;
3709 // }
3710 3695
3711 // let lookbehind = newest_selection3696 let newest_selection = self.selections.newest_anchor();
3712 // .start3697 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
3713 // .text_anchor3698 return None;
3714 // .to_offset(buffer)3699 }
3715 // .saturating_sub(old_range.start);
3716 // let lookahead = old_range
3717 // .end
3718 // .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
3719 // let mut common_prefix_len = old_text
3720 // .bytes()
3721 // .zip(text.bytes())
3722 // .take_while(|(a, b)| a == b)
3723 // .count();
3724
3725 // let snapshot = self.buffer.read(cx).snapshot(cx);
3726 // let mut range_to_replace: Option<Range<isize>> = None;
3727 // let mut ranges = Vec::new();
3728 // for selection in &selections {
3729 // if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
3730 // let start = selection.start.saturating_sub(lookbehind);
3731 // let end = selection.end + lookahead;
3732 // if selection.id == newest_selection.id {
3733 // range_to_replace = Some(
3734 // ((start + common_prefix_len) as isize - selection.start as isize)
3735 // ..(end as isize - selection.start as isize),
3736 // );
3737 // }
3738 // ranges.push(start + common_prefix_len..end);
3739 // } else {
3740 // common_prefix_len = 0;
3741 // ranges.clear();
3742 // ranges.extend(selections.iter().map(|s| {
3743 // if s.id == newest_selection.id {
3744 // range_to_replace = Some(
3745 // old_range.start.to_offset_utf16(&snapshot).0 as isize
3746 // - selection.start as isize
3747 // ..old_range.end.to_offset_utf16(&snapshot).0 as isize
3748 // - selection.start as isize,
3749 // );
3750 // old_range.clone()
3751 // } else {
3752 // s.start..s.end
3753 // }
3754 // }));
3755 // break;
3756 // }
3757 // }
3758 // let text = &text[common_prefix_len..];
3759 3700
3760 // cx.emit(Event::InputHandled {3701 let lookbehind = newest_selection
3761 // utf16_range_to_replace: range_to_replace,3702 .start
3762 // text: text.into(),3703 .text_anchor
3763 // });3704 .to_offset(buffer)
3705 .saturating_sub(old_range.start);
3706 let lookahead = old_range
3707 .end
3708 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
3709 let mut common_prefix_len = old_text
3710 .bytes()
3711 .zip(text.bytes())
3712 .take_while(|(a, b)| a == b)
3713 .count();
3764 3714
3765 // self.transact(cx, |this, cx| {3715 let snapshot = self.buffer.read(cx).snapshot(cx);
3766 // if let Some(mut snippet) = snippet {3716 let mut range_to_replace: Option<Range<isize>> = None;
3767 // snippet.text = text.to_string();3717 let mut ranges = Vec::new();
3768 // for tabstop in snippet.tabstops.iter_mut().flatten() {3718 for selection in &selections {
3769 // tabstop.start -= common_prefix_len as isize;3719 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
3770 // tabstop.end -= common_prefix_len as isize;3720 let start = selection.start.saturating_sub(lookbehind);
3771 // }3721 let end = selection.end + lookahead;
3722 if selection.id == newest_selection.id {
3723 range_to_replace = Some(
3724 ((start + common_prefix_len) as isize - selection.start as isize)
3725 ..(end as isize - selection.start as isize),
3726 );
3727 }
3728 ranges.push(start + common_prefix_len..end);
3729 } else {
3730 common_prefix_len = 0;
3731 ranges.clear();
3732 ranges.extend(selections.iter().map(|s| {
3733 if s.id == newest_selection.id {
3734 range_to_replace = Some(
3735 old_range.start.to_offset_utf16(&snapshot).0 as isize
3736 - selection.start as isize
3737 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
3738 - selection.start as isize,
3739 );
3740 old_range.clone()
3741 } else {
3742 s.start..s.end
3743 }
3744 }));
3745 break;
3746 }
3747 }
3748 let text = &text[common_prefix_len..];
3772 3749
3773 // this.insert_snippet(&ranges, snippet, cx).log_err();3750 cx.emit(EditorEvent::InputHandled {
3774 // } else {3751 utf16_range_to_replace: range_to_replace,
3775 // this.buffer.update(cx, |buffer, cx| {3752 text: text.into(),
3776 // buffer.edit(3753 });
3777 // ranges.iter().map(|range| (range.clone(), text)),
3778 // this.autoindent_mode.clone(),
3779 // cx,
3780 // );
3781 // });
3782 // }
3783 3754
3784 // this.refresh_copilot_suggestions(true, cx);3755 self.transact(cx, |this, cx| {
3785 // });3756 if let Some(mut snippet) = snippet {
3757 snippet.text = text.to_string();
3758 for tabstop in snippet.tabstops.iter_mut().flatten() {
3759 tabstop.start -= common_prefix_len as isize;
3760 tabstop.end -= common_prefix_len as isize;
3761 }
3786 3762
3787 // let project = self.project.clone()?;3763 this.insert_snippet(&ranges, snippet, cx).log_err();
3788 // let apply_edits = project.update(cx, |project, cx| {3764 } else {
3789 // project.apply_additional_edits_for_completion(3765 this.buffer.update(cx, |buffer, cx| {
3790 // buffer_handle,3766 buffer.edit(
3791 // completion.clone(),3767 ranges.iter().map(|range| (range.clone(), text)),
3792 // true,3768 this.autoindent_mode.clone(),
3793 // cx,3769 cx,
3794 // )3770 );
3795 // });3771 });
3796 // Some(cx.foreground().spawn(async move {3772 }
3797 // apply_edits.await?;3773
3798 // Ok(())3774 this.refresh_copilot_suggestions(true, cx);
3799 // }))3775 });
3800 // }3776
3777 let project = self.project.clone()?;
3778 let apply_edits = project.update(cx, |project, cx| {
3779 project.apply_additional_edits_for_completion(
3780 buffer_handle,
3781 completion.clone(),
3782 true,
3783 cx,
3784 )
3785 });
3786 Some(cx.foreground_executor().spawn(async move {
3787 apply_edits.await?;
3788 Ok(())
3789 }))
3790 }
3801 3791
3802 pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {3792 pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
3803 let mut context_menu = self.context_menu.write();3793 let mut context_menu = self.context_menu.write();
@@ -4353,19 +4343,19 @@ impl Editor { · 549 unmodified lines
4353 style: &EditorStyle,4343 style: &EditorStyle,
4354 is_active: bool,4344 is_active: bool,
4355 cx: &mut ViewContext<Self>,4345 cx: &mut ViewContext<Self>,
4356 ) -> Option<AnyElement<Self>> {4346 ) -> Option<IconButton> {
4357 if self.available_code_actions.is_some() {4347 if self.available_code_actions.is_some() {
4358 Some(4348 Some(
4359 IconButton::new("code_actions_indicator", ui::Icon::Bolt)4349 IconButton::new("code_actions_indicator", ui::Icon::Bolt).on_click(cx.listener(
4360 .on_click(|editor: &mut Editor, cx| {4350 |editor, e, cx| {
4361 editor.toggle_code_actions(4351 editor.toggle_code_actions(
4362 &ToggleCodeActions {4352 &ToggleCodeActions {
4363 deployed_from_indicator: true,4353 deployed_from_indicator: true,
4364 },4354 },
4365 cx,4355 cx,
4366 );4356 );
4367 })4357 },
4368 .render(),4358 )),
4369 )4359 )
4370 } else {4360 } else {
4371 None4361 None
@@ -4380,7 +4370,7 @@ impl Editor { · 8 unmodified lines
4380 line_height: Pixels,4370 line_height: Pixels,
4381 gutter_margin: Pixels,4371 gutter_margin: Pixels,
4382 cx: &mut ViewContext<Self>,4372 cx: &mut ViewContext<Self>,
4383 ) -> Vec<Option<AnyElement<Self>>> {4373 ) -> Vec<Option<IconButton>> {
4384 fold_data4374 fold_data
4385 .iter()4375 .iter()
4386 .enumerate()4376 .enumerate()
@@ -4393,16 +4383,15 @@ impl Editor { · 6 unmodified lines
4393 FoldStatus::Foldable => ui::Icon::ChevronDown,4383 FoldStatus::Foldable => ui::Icon::ChevronDown,
4394 };4384 };
4395 IconButton::new(ix as usize, icon)4385 IconButton::new(ix as usize, icon)
4396 .on_click(move |editor: &mut Editor, cx| match fold_status {4386 .on_click(cx.listener(move |editor, e, cx| match fold_status {
4397 FoldStatus::Folded => {4387 FoldStatus::Folded => {
4398 editor.unfold_at(&UnfoldAt { buffer_row }, cx);4388 editor.unfold_at(&UnfoldAt { buffer_row }, cx);
4399 }4389 }
4400 FoldStatus::Foldable => {4390 FoldStatus::Foldable => {
4401 editor.fold_at(&FoldAt { buffer_row }, cx);4391 editor.fold_at(&FoldAt { buffer_row }, cx);
4402 }4392 }
4403 })4393 }))
4404 .color(ui::TextColor::Muted)4394 .color(ui::Color::Muted)
4405 .render()
4406 })4395 })
4407 })4396 })
4408 .flatten()4397 .flatten()
@@ -4421,12 +4410,14 @@ impl Editor { · 12 unmodified lines
4421 &self,4410 &self,
4422 cursor_position: DisplayPoint,4411 cursor_position: DisplayPoint,
4423 style: &EditorStyle,4412 style: &EditorStyle,
4413 max_height: Pixels,
4424 cx: &mut ViewContext<Editor>,4414 cx: &mut ViewContext<Editor>,
4425 ) -> Option<(DisplayPoint, AnyElement<Editor>)> {4415 ) -> Option<(DisplayPoint, AnyElement)> {
4426 self.context_menu.read().as_ref().map(|menu| {4416 self.context_menu.read().as_ref().map(|menu| {
4427 menu.render(4417 menu.render(
4428 cursor_position,4418 cursor_position,
4429 style,4419 style,
4420 max_height,
4430 self.workspace.as_ref().map(|(w, _)| w.clone()),4421 self.workspace.as_ref().map(|(w, _)| w.clone()),
4431 cx,4422 cx,
4432 )4423 )
@@ -7781,7 +7772,7 @@ impl Editor { · 3348 unmodified lines
7781 }7772 }
7782 div()7773 div()
7783 .pl(cx.anchor_x)7774 .pl(cx.anchor_x)
7784 .child(rename_editor.render_with(EditorElement::new(7775 .child(EditorElement::new(
7785 &rename_editor,7776 &rename_editor,
7786 EditorStyle {7777 EditorStyle {
7787 background: cx.theme().system().transparent,7778 background: cx.theme().system().transparent,
@@ -7789,11 +7780,13 @@ impl Editor { · 1 unmodified line
7789 text: text_style,7780 text: text_style,
7790 scrollbar_width: cx.editor_style.scrollbar_width,7781 scrollbar_width: cx.editor_style.scrollbar_width,
7791 syntax: cx.editor_style.syntax.clone(),7782 syntax: cx.editor_style.syntax.clone(),
7792 diagnostic_style:7783 diagnostic_style: cx
7793 cx.editor_style.diagnostic_style.clone(),7784 .editor_style
7785 .diagnostic_style
7786 .clone(),
7794 },7787 },
7795 )))7788 ))
7796 .render()7789 .into_any_element()
7797 }7790 }
7798 }),7791 }),
7799 disposition: BlockDisposition::Below,7792 disposition: BlockDisposition::Below,
@@ -9207,6 +9200,26 @@ impl Editor { · 1407 unmodified lines
9207 cx.emit(EditorEvent::Blurred);9200 cx.emit(EditorEvent::Blurred);
9208 cx.notify();9201 cx.notify();
9209 }9202 }
9203
9204 pub fn register_action<A: Action>(
9205 &mut self,
9206 listener: impl Fn(&A, &mut WindowContext) + 'static,
9207 ) -> &mut Self {
9208 let listener = Arc::new(listener);
9209
9210 self.editor_actions.push(Box::new(move |cx| {
9211 let view = cx.view().clone();
9212 let cx = cx.window_context();
9213 let listener = listener.clone();
9214 cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
9215 let action = action.downcast_ref().unwrap();
9216 if phase == DispatchPhase::Bubble {
9217 listener(action, cx)
9218 }
9219 })
9220 }));
9221 self
9222 }
9210}9223}
9211 9224
9212pub trait CollaborationHub {9225pub trait CollaborationHub {
@@ -9396,7 +9409,9 @@ impl Render for Editor { · 183 unmodified lines
9396 font_weight: FontWeight::NORMAL,9409 font_weight: FontWeight::NORMAL,
9397 font_style: FontStyle::Normal,9410 font_style: FontStyle::Normal,
9398 line_height: relative(1.).into(),9411 line_height: relative(1.).into(),
9412 background_color: None,
9399 underline: None,9413 underline: None,
9414 white_space: WhiteSpace::Normal,
9400 },9415 },
9401 9416
9402 EditorMode::AutoHeight { max_lines } => todo!(),9417 EditorMode::AutoHeight { max_lines } => todo!(),
@@ -9409,7 +9424,9 @@ impl Render for Editor { · 6 unmodified lines
9409 font_weight: FontWeight::NORMAL,9424 font_weight: FontWeight::NORMAL,
9410 font_style: FontStyle::Normal,9425 font_style: FontStyle::Normal,
9411 line_height: relative(settings.buffer_line_height.value()),9426 line_height: relative(settings.buffer_line_height.value()),
9427 background_color: None,
9412 underline: None,9428 underline: None,
9429 white_space: WhiteSpace::Normal,
9413 },9430 },
9414 };9431 };
9415 9432
@@ -9996,11 +10013,11 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend · 580 unmodified lines
9996 .ml(cx.anchor_x)10013 .ml(cx.anchor_x)
9997 }))10014 }))
9998 .cursor_pointer()10015 .cursor_pointer()
9999 .on_click(move |_, _, cx| {10016 .on_click(cx.listener(move |_, _, cx| {
10000 cx.write_to_clipboard(ClipboardItem::new(message.clone()));10017 cx.write_to_clipboard(ClipboardItem::new(message.clone()));
10001 })10018 }))
10002 .tooltip(|_, cx| Tooltip::text("Copy diagnostic message", cx))10019 .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx))
10003 .render()10020 .into_any_element()
10004 })10021 })
10005}10022}
10006 10023
@@ -10047,120 +10064,75 @@ pub fn diagnostic_style( · 40 unmodified lines
10047 }10064 }
10048}10065}
10049 10066
10050pub fn combine_syntax_and_fuzzy_match_highlights(10067pub fn text_runs_for_highlights(
10051 text: &str,10068 text: &str,
10052 default_style: HighlightStyle,10069 default_style: &TextStyle,
10053 syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>,10070 highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
10054 match_indices: &[usize],10071) -> Vec<TextRun> {
10055) -> Vec<(Range<usize>, HighlightStyle)> {10072 let mut runs = Vec::new();
10056 let mut result = Vec::new();10073 let mut ix = 0;
10057 let mut match_indices = match_indices.iter().copied().peekable();10074 for (range, highlight) in highlights {
10058 10075 if ix < range.start {
10059 for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())])10076 runs.push(default_style.clone().to_run(range.start - ix));
10060 {10077 }
10061 syntax_highlight.font_weight = None;10078 runs.push(
10062 10079 default_style
10063 // Add highlights for any fuzzy match characters before the next10080 .clone()
10064 // syntax highlight range.10081 .highlight(highlight)
10065 while let Some(&match_index) = match_indices.peek() {10082 .to_run(range.len()),
10066 if match_index >= range.start {10083 );
10067 break;10084 ix = range.end;
10068 }
10069 match_indices.next();
10070 let end_index = char_ix_after(match_index, text);
10071 let mut match_style = default_style;
10072 match_style.font_weight = Some(FontWeight::BOLD);
10073 result.push((match_index..end_index, match_style));
10074 }
10075
10076 if range.start == usize::MAX {
10077 break;
10078 }
10079
10080 // Add highlights for any fuzzy match characters within the
10081 // syntax highlight range.
10082 let mut offset = range.start;
10083 while let Some(&match_index) = match_indices.peek() {
10084 if match_index >= range.end {
10085 break;
10086 }
10087
10088 match_indices.next();
10089 if match_index > offset {
10090 result.push((offset..match_index, syntax_highlight));
10091 }
10092
10093 let mut end_index = char_ix_after(match_index, text);
10094 while let Some(&next_match_index) = match_indices.peek() {
10095 if next_match_index == end_index && next_match_index < range.end {
10096 end_index = char_ix_after(next_match_index, text);
10097 match_indices.next();
10098 } else {
10099 break;
10100 }
10101 }
10102
10103 let mut match_style = syntax_highlight;
10104 match_style.font_weight = Some(FontWeight::BOLD);
10105 result.push((match_index..end_index, match_style));
10106 offset = end_index;
10107 }
10108
10109 if offset < range.end {
10110 result.push((offset..range.end, syntax_highlight));
10111 }
10112 }10085 }
10113 10086 if ix < text.len() {
10114 fn char_ix_after(ix: usize, text: &str) -> usize {10087 runs.push(default_style.to_run(text.len() - ix));
10115 ix + text[ix..].chars().next().unwrap().len_utf8()
10116 }10088 }
10117 10089 runs
10118 result
10119}10090}
10120 10091
10121// pub fn styled_runs_for_code_label<'a>(10092pub fn styled_runs_for_code_label<'a>(
10122// label: &'a CodeLabel,10093 label: &'a CodeLabel,
10123// syntax_theme: &'a theme::SyntaxTheme,10094 syntax_theme: &'a theme::SyntaxTheme,
10124// ) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {10095) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
10125// let fade_out = HighlightStyle {10096 let fade_out = HighlightStyle {
10126// fade_out: Some(0.35),10097 fade_out: Some(0.35),
10127// ..Default::default()10098 ..Default::default()
10128// };10099 };
10100
10101 let mut prev_end = label.filter_range.end;
10102 label
10103 .runs
10104 .iter()
10105 .enumerate()
10106 .flat_map(move |(ix, (range, highlight_id))| {
10107 let style = if let Some(style) = highlight_id.style(syntax_theme) {
10108 style
10109 } else {
10110 return Default::default();
10111 };
10112 let mut muted_style = style;
10113 muted_style.highlight(fade_out);
10129 10114
10130// let mut prev_end = label.filter_range.end;10115 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
10131// label10116 if range.start >= label.filter_range.end {
10132// .runs10117 if range.start > prev_end {
10133// .iter()10118 runs.push((prev_end..range.start, fade_out));
10134// .enumerate()10119 }
10135// .flat_map(move |(ix, (range, highlight_id))| {10120 runs.push((range.clone(), muted_style));
10136// let style = if let Some(style) = highlight_id.style(syntax_theme) {10121 } else if range.end <= label.filter_range.end {
10137// style10122 runs.push((range.clone(), style));
10138// } else {10123 } else {
10139// return Default::default();10124 runs.push((range.start..label.filter_range.end, style));
10140// };10125 runs.push((label.filter_range.end..range.end, muted_style));
10141// let mut muted_style = style;10126 }
10142// muted_style.highlight(fade_out);10127 prev_end = cmp::max(prev_end, range.end);
10143
10144// let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
10145// if range.start >= label.filter_range.end {
10146// if range.start > prev_end {
10147// runs.push((prev_end..range.start, fade_out));
10148// }
10149// runs.push((range.clone(), muted_style));
10150// } else if range.end <= label.filter_range.end {
10151// runs.push((range.clone(), style));
10152// } else {
10153// runs.push((range.start..label.filter_range.end, style));
10154// runs.push((label.filter_range.end..range.end, muted_style));
10155// }
10156// prev_end = cmp::max(prev_end, range.end);
10157 10128
10158// if ix + 1 == label.runs.len() && label.text.len() > prev_end {10129 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
10159// runs.push((prev_end..label.text.len(), fade_out));10130 runs.push((prev_end..label.text.len(), fade_out));
10160// }10131 }
10161 10132
10162// runs10133 runs
10163// })10134 })
10135}
10164 10136
10165pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a {10137pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a {
10166 let mut index = 0;10138 let mut index = 0;
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
3048 position: snapshot.anchor_after(Point::new(2, 0)),3048 position: snapshot.anchor_after(Point::new(2, 0)),
3049 disposition: BlockDisposition::Below,3049 disposition: BlockDisposition::Below,
3050 height: 1,3050 height: 1,
3051 render: Arc::new(|_| div().render()),3051 render: Arc::new(|_| div().into_any()),
3052 }],3052 }],
3053 Some(Autoscroll::fit()),3053 Some(Autoscroll::fit()),
3054 cx,3054 cx,
@@ -6740,75 +6740,6 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { · 3685 unmodified lines
6740// );6740// );
6741// }6741// }
6742 6742
6743#[test]
6744fn test_combine_syntax_and_fuzzy_match_highlights() {
6745 let string = "abcdefghijklmnop";
6746 let syntax_ranges = [
6747 (
6748 0..3,
6749 HighlightStyle {
6750 color: Some(Hsla::red()),
6751 ..Default::default()
6752 },
6753 ),
6754 (
6755 4..8,
6756 HighlightStyle {
6757 color: Some(Hsla::green()),
6758 ..Default::default()
6759 },
6760 ),
6761 ];
6762 let match_indices = [4, 6, 7, 8];
6763 assert_eq!(
6764 combine_syntax_and_fuzzy_match_highlights(
6765 string,
6766 Default::default(),
6767 syntax_ranges.into_iter(),
6768 &match_indices,
6769 ),
6770 &[
6771 (
6772 0..3,
6773 HighlightStyle {
6774 color: Some(Hsla::red()),
6775 ..Default::default()
6776 },
6777 ),
6778 (
6779 4..5,
6780 HighlightStyle {
6781 color: Some(Hsla::green()),
6782 font_weight: Some(gpui::FontWeight::BOLD),
6783 ..Default::default()
6784 },
6785 ),
6786 (
6787 5..6,
6788 HighlightStyle {
6789 color: Some(Hsla::green()),
6790 ..Default::default()
6791 },
6792 ),
6793 (
6794 6..8,
6795 HighlightStyle {
6796 color: Some(Hsla::green()),
6797 font_weight: Some(gpui::FontWeight::BOLD),
6798 ..Default::default()
6799 },
6800 ),
6801 (
6802 8..9,
6803 HighlightStyle {
6804 font_weight: Some(gpui::FontWeight::BOLD),
6805 ..Default::default()
6806 },
6807 ),
6808 ]
6809 );
6810}
6811
6812#[gpui::test]6743#[gpui::test]
6813async fn go_to_prev_overlapping_diagnostic(6744async fn go_to_prev_overlapping_diagnostic(
6814 executor: BackgroundExecutor,6745 executor: BackgroundExecutor,
crates/editor2/src/element.rs+844−837
@@ -5,7 +5,9 @@ use crate::{ · 4 unmodified lines
5 },5 },
6 editor_settings::ShowScrollbar,6 editor_settings::ShowScrollbar,
7 git::{diff_hunk_to_display, DisplayDiffHunk},7 git::{diff_hunk_to_display, DisplayDiffHunk},
8 hover_popover::hover_at,8 hover_popover::{
9 self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
10 },
9 link_go_to_definition::{11 link_go_to_definition::{
10 go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,12 go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
11 update_inlay_link_and_hover_points, GoToDefinitionTrigger,13 update_inlay_link_and_hover_points, GoToDefinitionTrigger,
@@ -19,11 +21,11 @@ use anyhow::Result; · 7 unmodified lines
19use collections::{BTreeMap, HashMap};21use collections::{BTreeMap, HashMap};
20use gpui::{22use gpui::{
21 div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,23 div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
22 BorrowWindow, Bounds, Component, ContentMask, Corners, DispatchPhase, Edges, Element,24 BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
23 ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveComponent, LineLayout,25 ElementInputHandler, Entity, EntityId, Hsla, InteractiveElement, IntoElement, LineLayout,
24 MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels,26 MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce,
25 ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveComponent, Style, Styled,27 ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
26 TextRun, TextStyle, View, ViewContext, WindowContext, WrappedLine,28 TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
27};29};
28use itertools::Itertools;30use itertools::Itertools;
29use language::language_settings::ShowWhitespaceSetting;31use language::language_settings::ShowWhitespaceSetting;
@@ -112,18 +114,202 @@ impl SelectionLayout { · 82 unmodified lines
112}114}
113 115
114pub struct EditorElement {116pub struct EditorElement {
115 editor_id: EntityId,117 editor: View<Editor>,
116 style: EditorStyle,118 style: EditorStyle,
117}119}
118 120
119impl EditorElement {121impl EditorElement {
120 pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {122 pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
121 Self {123 Self {
122 editor_id: editor.entity_id(),124 editor: editor.clone(),
123 style,125 style,
124 }126 }
125 }127 }
126 128
129 fn register_actions(&self, cx: &mut WindowContext) {
130 let view = &self.editor;
131 self.editor.update(cx, |editor, cx| {
132 for action in editor.editor_actions.iter() {
133 (action)(cx)
134 }
135 });
136 register_action(view, cx, Editor::move_left);
137 register_action(view, cx, Editor::move_right);
138 register_action(view, cx, Editor::move_down);
139 register_action(view, cx, Editor::move_up);
140 // on_action(cx, Editor::new_file); todo!()
141 // on_action(cx, Editor::new_file_in_direction); todo!()
142 register_action(view, cx, Editor::cancel);
143 register_action(view, cx, Editor::newline);
144 register_action(view, cx, Editor::newline_above);
145 register_action(view, cx, Editor::newline_below);
146 register_action(view, cx, Editor::backspace);
147 register_action(view, cx, Editor::delete);
148 register_action(view, cx, Editor::tab);
149 register_action(view, cx, Editor::tab_prev);
150 register_action(view, cx, Editor::indent);
151 register_action(view, cx, Editor::outdent);
152 register_action(view, cx, Editor::delete_line);
153 register_action(view, cx, Editor::join_lines);
154 register_action(view, cx, Editor::sort_lines_case_sensitive);
155 register_action(view, cx, Editor::sort_lines_case_insensitive);
156 register_action(view, cx, Editor::reverse_lines);
157 register_action(view, cx, Editor::shuffle_lines);
158 register_action(view, cx, Editor::convert_to_upper_case);
159 register_action(view, cx, Editor::convert_to_lower_case);
160 register_action(view, cx, Editor::convert_to_title_case);
161 register_action(view, cx, Editor::convert_to_snake_case);
162 register_action(view, cx, Editor::convert_to_kebab_case);
163 register_action(view, cx, Editor::convert_to_upper_camel_case);
164 register_action(view, cx, Editor::convert_to_lower_camel_case);
165 register_action(view, cx, Editor::delete_to_previous_word_start);
166 register_action(view, cx, Editor::delete_to_previous_subword_start);
167 register_action(view, cx, Editor::delete_to_next_word_end);
168 register_action(view, cx, Editor::delete_to_next_subword_end);
169 register_action(view, cx, Editor::delete_to_beginning_of_line);
170 register_action(view, cx, Editor::delete_to_end_of_line);
171 register_action(view, cx, Editor::cut_to_end_of_line);
172 register_action(view, cx, Editor::duplicate_line);
173 register_action(view, cx, Editor::move_line_up);
174 register_action(view, cx, Editor::move_line_down);
175 register_action(view, cx, Editor::transpose);
176 register_action(view, cx, Editor::cut);
177 register_action(view, cx, Editor::copy);
178 register_action(view, cx, Editor::paste);
179 register_action(view, cx, Editor::undo);
180 register_action(view, cx, Editor::redo);
181 register_action(view, cx, Editor::move_page_up);
182 register_action(view, cx, Editor::move_page_down);
183 register_action(view, cx, Editor::next_screen);
184 register_action(view, cx, Editor::scroll_cursor_top);
185 register_action(view, cx, Editor::scroll_cursor_center);
186 register_action(view, cx, Editor::scroll_cursor_bottom);
187 register_action(view, cx, |editor, _: &LineDown, cx| {
188 editor.scroll_screen(&ScrollAmount::Line(1.), cx)
189 });
190 register_action(view, cx, |editor, _: &LineUp, cx| {
191 editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
192 });
193 register_action(view, cx, |editor, _: &HalfPageDown, cx| {
194 editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
195 });
196 register_action(view, cx, |editor, _: &HalfPageUp, cx| {
197 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
198 });
199 register_action(view, cx, |editor, _: &PageDown, cx| {
200 editor.scroll_screen(&ScrollAmount::Page(1.), cx)
201 });
202 register_action(view, cx, |editor, _: &PageUp, cx| {
203 editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
204 });
205 register_action(view, cx, Editor::move_to_previous_word_start);
206 register_action(view, cx, Editor::move_to_previous_subword_start);
207 register_action(view, cx, Editor::move_to_next_word_end);
208 register_action(view, cx, Editor::move_to_next_subword_end);
209 register_action(view, cx, Editor::move_to_beginning_of_line);
210 register_action(view, cx, Editor::move_to_end_of_line);
211 register_action(view, cx, Editor::move_to_start_of_paragraph);
212 register_action(view, cx, Editor::move_to_end_of_paragraph);
213 register_action(view, cx, Editor::move_to_beginning);
214 register_action(view, cx, Editor::move_to_end);
215 register_action(view, cx, Editor::select_up);
216 register_action(view, cx, Editor::select_down);
217 register_action(view, cx, Editor::select_left);
218 register_action(view, cx, Editor::select_right);
219 register_action(view, cx, Editor::select_to_previous_word_start);
220 register_action(view, cx, Editor::select_to_previous_subword_start);
221 register_action(view, cx, Editor::select_to_next_word_end);
222 register_action(view, cx, Editor::select_to_next_subword_end);
223 register_action(view, cx, Editor::select_to_beginning_of_line);
224 register_action(view, cx, Editor::select_to_end_of_line);
225 register_action(view, cx, Editor::select_to_start_of_paragraph);
226 register_action(view, cx, Editor::select_to_end_of_paragraph);
227 register_action(view, cx, Editor::select_to_beginning);
228 register_action(view, cx, Editor::select_to_end);
229 register_action(view, cx, Editor::select_all);
230 register_action(view, cx, |editor, action, cx| {
231 editor.select_all_matches(action, cx).log_err();
232 });
233 register_action(view, cx, Editor::select_line);
234 register_action(view, cx, Editor::split_selection_into_lines);
235 register_action(view, cx, Editor::add_selection_above);
236 register_action(view, cx, Editor::add_selection_below);
237 register_action(view, cx, |editor, action, cx| {
238 editor.select_next(action, cx).log_err();
239 });
240 register_action(view, cx, |editor, action, cx| {
241 editor.select_previous(action, cx).log_err();
242 });
243 register_action(view, cx, Editor::toggle_comments);
244 register_action(view, cx, Editor::select_larger_syntax_node);
245 register_action(view, cx, Editor::select_smaller_syntax_node);
246 register_action(view, cx, Editor::move_to_enclosing_bracket);
247 register_action(view, cx, Editor::undo_selection);
248 register_action(view, cx, Editor::redo_selection);
249 register_action(view, cx, Editor::go_to_diagnostic);
250 register_action(view, cx, Editor::go_to_prev_diagnostic);
251 register_action(view, cx, Editor::go_to_hunk);
252 register_action(view, cx, Editor::go_to_prev_hunk);
253 register_action(view, cx, Editor::go_to_definition);
254 register_action(view, cx, Editor::go_to_definition_split);
255 register_action(view, cx, Editor::go_to_type_definition);
256 register_action(view, cx, Editor::go_to_type_definition_split);
257 register_action(view, cx, Editor::fold);
258 register_action(view, cx, Editor::fold_at);
259 register_action(view, cx, Editor::unfold_lines);
260 register_action(view, cx, Editor::unfold_at);
261 register_action(view, cx, Editor::fold_selected_ranges);
262 register_action(view, cx, Editor::show_completions);
263 register_action(view, cx, Editor::toggle_code_actions);
264 // on_action(cx, Editor::open_excerpts); todo!()
265 register_action(view, cx, Editor::toggle_soft_wrap);
266 register_action(view, cx, Editor::toggle_inlay_hints);
267 register_action(view, cx, hover_popover::hover);
268 register_action(view, cx, Editor::reveal_in_finder);
269 register_action(view, cx, Editor::copy_path);
270 register_action(view, cx, Editor::copy_relative_path);
271 register_action(view, cx, Editor::copy_highlight_json);
272 register_action(view, cx, |editor, action, cx| {
273 editor
274 .format(action, cx)
275 .map(|task| task.detach_and_log_err(cx));
276 });
277 register_action(view, cx, Editor::restart_language_server);
278 register_action(view, cx, Editor::show_character_palette);
279 register_action(view, cx, |editor, action, cx| {
280 editor
281 .confirm_completion(action, cx)
282 .map(|task| task.detach_and_log_err(cx));
283 });
284 register_action(view, cx, |editor, action, cx| {
285 editor
286 .confirm_code_action(action, cx)
287 .map(|task| task.detach_and_log_err(cx));
288 });
289 register_action(view, cx, |editor, action, cx| {
290 editor
291 .rename(action, cx)
292 .map(|task| task.detach_and_log_err(cx));
293 });
294 register_action(view, cx, |editor, action, cx| {
295 editor
296 .confirm_rename(action, cx)
297 .map(|task| task.detach_and_log_err(cx));
298 });
299 register_action(view, cx, |editor, action, cx| {
300 editor
301 .find_all_references(action, cx)
302 .map(|task| task.detach_and_log_err(cx));
303 });
304 register_action(view, cx, Editor::next_copilot_suggestion);
305 register_action(view, cx, Editor::previous_copilot_suggestion);
306 register_action(view, cx, Editor::copilot_suggest);
307 register_action(view, cx, Editor::context_menu_first);
308 register_action(view, cx, Editor::context_menu_prev);
309 register_action(view, cx, Editor::context_menu_next);
310 register_action(view, cx, Editor::context_menu_last);
311 }
312
127 fn mouse_down(313 fn mouse_down(
128 editor: &mut Editor,314 editor: &mut Editor,
129 event: &MouseDownEvent,315 event: &MouseDownEvent,
@@ -349,7 +535,7 @@ impl EditorElement { · 219 unmodified lines
349 gutter_bounds: Bounds<Pixels>,535 gutter_bounds: Bounds<Pixels>,
350 text_bounds: Bounds<Pixels>,536 text_bounds: Bounds<Pixels>,
351 layout: &LayoutState,537 layout: &LayoutState,
352 cx: &mut ViewContext<Editor>,538 cx: &mut WindowContext,
353 ) {539 ) {
354 let bounds = gutter_bounds.union(&text_bounds);540 let bounds = gutter_bounds.union(&text_bounds);
355 let scroll_top =541 let scroll_top =
@@ -459,8 +645,7 @@ impl EditorElement { · 103 unmodified lines
459 &mut self,645 &mut self,
460 bounds: Bounds<Pixels>,646 bounds: Bounds<Pixels>,
461 layout: &mut LayoutState,647 layout: &mut LayoutState,
462 editor: &mut Editor,648 cx: &mut WindowContext,
463 cx: &mut ViewContext<Editor>,
464 ) {649 ) {
465 let line_height = layout.position_map.line_height;650 let line_height = layout.position_map.line_height;
466 651
@@ -488,13 +673,14 @@ impl EditorElement { · 21 unmodified lines
488 }673 }
489 }674 }
490 675
491 for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() {676 for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
492 if let Some(fold_indicator) = fold_indicator.as_mut() {677 if let Some(mut fold_indicator) = fold_indicator {
678 let mut fold_indicator = fold_indicator.into_any_element();
493 let available_space = size(679 let available_space = size(
494 AvailableSpace::MinContent,680 AvailableSpace::MinContent,
495 AvailableSpace::Definite(line_height * 0.55),681 AvailableSpace::Definite(line_height * 0.55),
496 );682 );
497 let fold_indicator_size = fold_indicator.measure(available_space, editor, cx);683 let fold_indicator_size = fold_indicator.measure(available_space, cx);
498 684
499 let position = point(685 let position = point(
500 bounds.size.width - layout.gutter_padding,686 bounds.size.width - layout.gutter_padding,
@@ -505,32 +691,29 @@ impl EditorElement { · 4 unmodified lines
505 (line_height - fold_indicator_size.height) / 2.,691 (line_height - fold_indicator_size.height) / 2.,
506 );692 );
507 let origin = bounds.origin + position + centering_offset;693 let origin = bounds.origin + position + centering_offset;
508 fold_indicator.draw(origin, available_space, editor, cx);694 fold_indicator.draw(origin, available_space, cx);
509 }695 }
510 }696 }
511 697
512 if let Some(indicator) = layout.code_actions_indicator.as_mut() {698 if let Some(indicator) = layout.code_actions_indicator.take() {
699 let mut button = indicator.button.into_any_element();
513 let available_space = size(700 let available_space = size(
514 AvailableSpace::MinContent,701 AvailableSpace::MinContent,
515 AvailableSpace::Definite(line_height),702 AvailableSpace::Definite(line_height),
516 );703 );
517 let indicator_size = indicator.element.measure(available_space, editor, cx);704 let indicator_size = button.measure(available_space, cx);
705
518 let mut x = Pixels::ZERO;706 let mut x = Pixels::ZERO;
519 let mut y = indicator.row as f32 * line_height - scroll_top;707 let mut y = indicator.row as f32 * line_height - scroll_top;
520 // Center indicator.708 // Center indicator.
521 x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;709 x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;
522 y += (line_height - indicator_size.height) / 2.;710 y += (line_height - indicator_size.height) / 2.;
523 indicator711
524 .element712 button.draw(bounds.origin + point(x, y), available_space, cx);
525 .draw(bounds.origin + point(x, y), available_space, editor, cx);
526 }713 }
527 }714 }
528 715
529 fn paint_diff_hunks(716 fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
530 bounds: Bounds<Pixels>,
531 layout: &LayoutState,
532 cx: &mut ViewContext<Editor>,
533 ) {
534 // todo!()717 // todo!()
535 // let diff_style = &theme::current(cx).editor.diff.clone();718 // let diff_style = &theme::current(cx).editor.diff.clone();
536 // let line_height = layout.position_map.line_height;719 // let line_height = layout.position_map.line_height;
@@ -618,14 +801,19 @@ impl EditorElement { · 81 unmodified lines
618 &mut self,801 &mut self,
619 text_bounds: Bounds<Pixels>,802 text_bounds: Bounds<Pixels>,
620 layout: &mut LayoutState,803 layout: &mut LayoutState,
621 editor: &mut Editor,804 cx: &mut WindowContext,
622 cx: &mut ViewContext<Editor>,
623 ) {805 ) {
624 let scroll_position = layout.position_map.snapshot.scroll_position();806 let scroll_position = layout.position_map.snapshot.scroll_position();
625 let start_row = layout.visible_display_row_range.start;807 let start_row = layout.visible_display_row_range.start;
626 let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);808 let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
627 let line_end_overshoot = 0.15 * layout.position_map.line_height;809 let line_end_overshoot = 0.15 * layout.position_map.line_height;
628 let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;810 let whitespace_setting = self
811 .editor
812 .read(cx)
813 .buffer
814 .read(cx)
815 .settings_at(0, cx)
816 .show_whitespaces;
629 817
630 cx.with_content_mask(818 cx.with_content_mask(
631 Some(ContentMask {819 Some(ContentMask {
@@ -674,20 +862,22 @@ impl EditorElement { · 42 unmodified lines
674 div()862 div()
675 .id(fold.id)863 .id(fold.id)
676 .size_full()864 .size_full()
677 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())865 .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
678 .on_click(move |editor: &mut Editor, _, cx| {866 .on_click(cx.listener_for(
679 editor.unfold_ranges(867 &self.editor,
680 [fold_range.start..fold_range.end],868 move |editor: &mut Editor, _, cx| {
681 true,869 editor.unfold_ranges(
682 false,870 [fold_range.start..fold_range.end],
683 cx,871 true,
684 );872 false,
685 cx.stop_propagation();873 cx,
686 })874 );
875 cx.stop_propagation();
876 },
877 ))
687 .draw(878 .draw(
688 fold_bounds.origin,879 fold_bounds.origin,
689 fold_bounds.size,880 fold_bounds.size,
690 editor,
691 cx,881 cx,
692 |fold_element_state, cx| {882 |fold_element_state, cx| {
693 if fold_element_state.is_active() {883 if fold_element_state.is_active() {
@@ -748,7 +938,7 @@ impl EditorElement { · 54 unmodified lines
748 invisible_display_ranges.push(selection.range.clone());938 invisible_display_ranges.push(selection.range.clone());
749 }939 }
750 940
751 if !selection.is_local || editor.show_local_cursors(cx) {941 if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) {
752 let cursor_position = selection.head;942 let cursor_position = selection.head;
753 if layout943 if layout
754 .visible_display_row_range944 .visible_display_row_range
@@ -800,12 +990,14 @@ impl EditorElement { · 45 unmodified lines
800 * layout.position_map.line_height990 * layout.position_map.line_height
801 - layout.position_map.scroll_position.y;991 - layout.position_map.scroll_position.y;
802 if selection.is_newest {992 if selection.is_newest {
803 editor.pixel_position_of_newest_cursor = Some(point(993 self.editor.update(cx, |editor, _| {
804 text_bounds.origin.x + x + block_width / 2.,994 editor.pixel_position_of_newest_cursor = Some(point(
805 text_bounds.origin.y995 text_bounds.origin.x + x + block_width / 2.,
806 + y996 text_bounds.origin.y
807 + layout.position_map.line_height / 2.,997 + y
808 ));998 + layout.position_map.line_height / 2.,
999 ))
1000 });
809 }1001 }
810 cursors.push(Cursor {1002 cursors.push(Cursor {
811 color: selection_style.cursor,1003 color: selection_style.cursor,
@@ -840,17 +1032,11 @@ impl EditorElement { · 28 unmodified lines
840 }1032 }
841 });1033 });
842 1034
843 if let Some((position, context_menu)) = layout.context_menu.as_mut() {1035 cx.with_z_index(1, |cx| {
844 cx.with_z_index(1, |cx| {1036 if let Some((position, mut context_menu)) = layout.context_menu.take() {
845 let line_height = self.style.text.line_height_in_pixels(cx.rem_size());1037 let available_space =
846 let available_space = size(1038 size(AvailableSpace::MinContent, AvailableSpace::MinContent);
847 AvailableSpace::MinContent,1039 let context_menu_size = context_menu.measure(available_space, cx);
848 AvailableSpace::Definite(
849 (12. * line_height)
850 .min((text_bounds.size.height - line_height) / 2.),
851 ),
852 );
853 let context_menu_size = context_menu.measure(available_space, editor, cx);
854 1040
855 let cursor_row_layout = &layout.position_map.line_layouts1041 let cursor_row_layout = &layout.position_map.line_layouts
856 [(position.row() - start_row) as usize]1042 [(position.row() - start_row) as usize]
@@ -871,84 +1057,77 @@ impl EditorElement { · 14 unmodified lines
871 }1057 }
872 1058
873 if list_origin.y + list_height > text_bounds.lower_right().y {1059 if list_origin.y + list_height > text_bounds.lower_right().y {
874 list_origin.y -= layout.position_map.line_height - list_height;1060 list_origin.y -= layout.position_map.line_height + list_height;
875 }1061 }
876 1062
877 context_menu.draw(list_origin, available_space, editor, cx);1063 cx.break_content_mask(|cx| {
878 })1064 context_menu.draw(list_origin, available_space, cx)
879 }1065 });
1066 }
880 1067
881 // if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {1068 if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() {
882 // cx.scene().push_stacking_context(None, None);1069 let available_space =
883 1070 size(AvailableSpace::MinContent, AvailableSpace::MinContent);
884 // // This is safe because we check on layout whether the required row is available1071
885 // let hovered_row_layout =1072 // This is safe because we check on layout whether the required row is available
886 // &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;1073 let hovered_row_layout = &layout.position_map.line_layouts
887 1074 [(position.row() - start_row) as usize]
888 // // Minimum required size: Take the first popover, and add 1.5 times the minimum popover1075 .line;
889 // // height. This is the size we will use to decide whether to render popovers above or below1076
890 // // the hovered line.1077 // Minimum required size: Take the first popover, and add 1.5 times the minimum popover
891 // let first_size = hover_popovers[0].size();1078 // height. This is the size we will use to decide whether to render popovers above or below
892 // let height_to_reserve = first_size.y1079 // the hovered line.
893 // + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.position_map.line_height;1080 let first_size = hover_popovers[0].measure(available_space, cx);
894 1081 let height_to_reserve = first_size.height
895 // // Compute Hovered Point1082 + 1.5 * MIN_POPOVER_LINE_HEIGHT * layout.position_map.line_height;
896 // let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left;1083
897 // let y = position.row() as f32 * layout.position_map.line_height - scroll_top;1084 // Compute Hovered Point
898 // let hovered_point = content_origin + point(x, y);1085 let x = hovered_row_layout.x_for_index(position.column() as usize)
899 1086 - layout.position_map.scroll_position.x;
900 // if hovered_point.y - height_to_reserve > 0.0 {1087 let y = position.row() as f32 * layout.position_map.line_height
901 // // There is enough space above. Render popovers above the hovered point1088 - layout.position_map.scroll_position.y;
902 // let mut current_y = hovered_point.y;1089 let hovered_point = content_origin + point(x, y);
903 // for hover_popover in hover_popovers {1090
904 // let size = hover_popover.size();1091 if hovered_point.y - height_to_reserve > Pixels::ZERO {
905 // let mut popover_origin = point(hovered_point.x, current_y - size.y);1092 // There is enough space above. Render popovers above the hovered point
906 1093 let mut current_y = hovered_point.y;
907 // let x_out_of_bounds = bounds.max_x - (popover_origin.x + size.x);1094 for mut hover_popover in hover_popovers {
908 // if x_out_of_bounds < 0.0 {1095 let size = hover_popover.measure(available_space, cx);
909 // popover_origin.set_x(popover_origin.x + x_out_of_bounds);1096 let mut popover_origin =
910 // }1097 point(hovered_point.x, current_y - size.height);
911 1098
912 // hover_popover.paint(1099 let x_out_of_bounds =
913 // popover_origin,1100 text_bounds.upper_right().x - (popover_origin.x + size.width);
914 // Bounds::<Pixels>::from_points(1101 if x_out_of_bounds < Pixels::ZERO {
915 // gpui::Point::<Pixels>::zero(),1102 popover_origin.x = popover_origin.x + x_out_of_bounds;
916 // point(f32::MAX, f32::MAX),1103 }
917 // ), // Let content bleed outside of editor1104
918 // editor,1105 cx.break_content_mask(|cx| {
919 // cx,1106 hover_popover.draw(popover_origin, available_space, cx)
920 // );1107 });
921 1108
922 // current_y = popover_origin.y - HOVER_POPOVER_GAP;1109 current_y = popover_origin.y - HOVER_POPOVER_GAP;
923 // }1110 }
924 // } else {1111 } else {
925 // // There is not enough space above. Render popovers below the hovered point1112 // There is not enough space above. Render popovers below the hovered point
926 // let mut current_y = hovered_point.y + layout.position_map.line_height;1113 let mut current_y = hovered_point.y + layout.position_map.line_height;
927 // for hover_popover in hover_popovers {1114 for mut hover_popover in hover_popovers {
928 // let size = hover_popover.size();1115 let size = hover_popover.measure(available_space, cx);
929 // let mut popover_origin = point(hovered_point.x, current_y);1116 let mut popover_origin = point(hovered_point.x, current_y);
930 1117
931 // let x_out_of_bounds = bounds.max_x - (popover_origin.x + size.x);1118 let x_out_of_bounds =
932 // if x_out_of_bounds < 0.0 {1119 text_bounds.upper_right().x - (popover_origin.x + size.width);
933 // popover_origin.set_x(popover_origin.x + x_out_of_bounds);1120 if x_out_of_bounds < Pixels::ZERO {
934 // }1121 popover_origin.x = popover_origin.x + x_out_of_bounds;
935 1122 }
936 // hover_popover.paint(1123
937 // popover_origin,1124 hover_popover.draw(popover_origin, available_space, cx);
938 // Bounds::<Pixels>::from_points(1125
939 // gpui::Point::<Pixels>::zero(),1126 current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
940 // point(f32::MAX, f32::MAX),1127 }
941 // ), // Let content bleed outside of editor1128 }
942 // editor,1129 }
943 // cx,1130 })
944 // );
945
946 // current_y = popover_origin.y + size.y + HOVER_POPOVER_GAP;
947 // }
948 // }
949
950 // cx.scene().pop_stacking_context();
951 // }
952 },1131 },
953 )1132 )
954 }1133 }
@@ -1165,7 +1344,7 @@ impl EditorElement { · 210 unmodified lines
1165 layout: &LayoutState,1344 layout: &LayoutState,
1166 content_origin: gpui::Point<Pixels>,1345 content_origin: gpui::Point<Pixels>,
1167 bounds: Bounds<Pixels>,1346 bounds: Bounds<Pixels>,
1168 cx: &mut ViewContext<Editor>,1347 cx: &mut WindowContext,
1169 ) {1348 ) {
1170 let start_row = layout.visible_display_row_range.start;1349 let start_row = layout.visible_display_row_range.start;
1171 let end_row = layout.visible_display_row_range.end;1350 let end_row = layout.visible_display_row_range.end;
@@ -1217,14 +1396,13 @@ impl EditorElement { · 45 unmodified lines
1217 &mut self,1396 &mut self,
1218 bounds: Bounds<Pixels>,1397 bounds: Bounds<Pixels>,
1219 layout: &mut LayoutState,1398 layout: &mut LayoutState,
1220 editor: &mut Editor,1399 cx: &mut WindowContext,
1221 cx: &mut ViewContext<Editor>,
1222 ) {1400 ) {
1223 let scroll_position = layout.position_map.snapshot.scroll_position();1401 let scroll_position = layout.position_map.snapshot.scroll_position();
1224 let scroll_left = scroll_position.x * layout.position_map.em_width;1402 let scroll_left = scroll_position.x * layout.position_map.em_width;
1225 let scroll_top = scroll_position.y * layout.position_map.line_height;1403 let scroll_top = scroll_position.y * layout.position_map.line_height;
1226 1404
1227 for block in &mut layout.blocks {1405 for block in layout.blocks.drain(..) {
1228 let mut origin = bounds.origin1406 let mut origin = bounds.origin
1229 + point(1407 + point(
1230 Pixels::ZERO,1408 Pixels::ZERO,
@@ -1233,13 +1411,11 @@ impl EditorElement { · 2 unmodified lines
1233 if !matches!(block.style, BlockStyle::Sticky) {1411 if !matches!(block.style, BlockStyle::Sticky) {
1234 origin += point(-scroll_left, Pixels::ZERO);1412 origin += point(-scroll_left, Pixels::ZERO);
1235 }1413 }
1236 block1414 block.element.draw(origin, block.available_space, cx);
1237 .element
1238 .draw(origin, block.available_space, editor, cx);
1239 }1415 }
1240 }1416 }
1241 1417
1242 fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> Pixels {1418 fn column_pixels(&self, column: usize, cx: &WindowContext) -> Pixels {
1243 let style = &self.style;1419 let style = &self.style;
1244 let font_size = style.text.font_size.to_pixels(cx.rem_size());1420 let font_size = style.text.font_size.to_pixels(cx.rem_size());
1245 let layout = cx1421 let layout = cx
@@ -1260,7 +1436,7 @@ impl EditorElement { · 14 unmodified lines
1260 layout.width1436 layout.width
1261 }1437 }
1262 1438
1263 fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> Pixels {1439 fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels {
1264 let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;1440 let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
1265 self.column_pixels(digit_count, cx)1441 self.column_pixels(digit_count, cx)
1266 }1442 }
@@ -1415,7 +1591,7 @@ impl EditorElement { · 148 unmodified lines
1415 }1591 }
1416 1592
1417 fn layout_lines(1593 fn layout_lines(
1418 &mut self,1594 &self,
1419 rows: Range<u32>,1595 rows: Range<u32>,
1420 line_number_layouts: &[Option<ShapedLine>],1596 line_number_layouts: &[Option<ShapedLine>],
1421 snapshot: &EditorSnapshot,1597 snapshot: &EditorSnapshot,
@@ -1471,483 +1647,457 @@ impl EditorElement { · 49 unmodified lines
1471 1647
1472 fn compute_layout(1648 fn compute_layout(
1473 &mut self,1649 &mut self,
1474 editor: &mut Editor,
1475 cx: &mut ViewContext<'_, Editor>,
1476 mut bounds: Bounds<Pixels>,1650 mut bounds: Bounds<Pixels>,
1651 cx: &mut WindowContext,
1477 ) -> LayoutState {1652 ) -> LayoutState {
1478 // let mut size = constraint.max;1653 self.editor.update(cx, |editor, cx| {
1479 // if size.x.is_infinite() {1654 // let mut size = constraint.max;
1480 // unimplemented!("we don't yet handle an infinite width constraint on buffer elements");1655 // if size.x.is_infinite() {
1481 // }1656 // unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
1482 1657 // }
1483 let snapshot = editor.snapshot(cx);1658
1484 let style = self.style.clone();1659 let snapshot = editor.snapshot(cx);
1660 let style = self.style.clone();
1661
1662 let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
1663 let font_size = style.text.font_size.to_pixels(cx.rem_size());
1664 let line_height = style.text.line_height_in_pixels(cx.rem_size());
1665 let em_width = cx
1666 .text_system()
1667 .typographic_bounds(font_id, font_size, 'm')
1668 .unwrap()
1669 .size
1670 .width;
1671 let em_advance = cx
1672 .text_system()
1673 .advance(font_id, font_size, 'm')
1674 .unwrap()
1675 .width;
1676
1677 let gutter_padding;
1678 let gutter_width;
1679 let gutter_margin;
1680 if snapshot.show_gutter {
1681 let descent = cx.text_system().descent(font_id, font_size).unwrap();
1682
1683 let gutter_padding_factor = 3.5;
1684 gutter_padding = (em_width * gutter_padding_factor).round();
1685 gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
1686 gutter_margin = -descent;
1687 } else {
1688 gutter_padding = Pixels::ZERO;
1689 gutter_width = Pixels::ZERO;
1690 gutter_margin = Pixels::ZERO;
1691 };
1485 1692
1486 let font_id = cx.text_system().font_id(&style.text.font()).unwrap();1693 editor.gutter_width = gutter_width;
1487 let font_size = style.text.font_size.to_pixels(cx.rem_size());1694 let text_width = bounds.size.width - gutter_width;
1488 let line_height = style.text.line_height_in_pixels(cx.rem_size());1695 let overscroll = size(em_width, px(0.));
1489 let em_width = cx1696 let snapshot = {
1490 .text_system()1697 editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
1491 .typographic_bounds(font_id, font_size, 'm')1698
1492 .unwrap()1699 let editor_width = text_width - gutter_margin - overscroll.width - em_width;
1493 .size1700 let wrap_width = match editor.soft_wrap_mode(cx) {
1494 .width;1701 SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
1495 let em_advance = cx1702 SoftWrap::EditorWidth => editor_width,
1496 .text_system()1703 SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
1497 .advance(font_id, font_size, 'm')1704 };
1498 .unwrap()
1499 .width;
1500
1501 let gutter_padding;
1502 let gutter_width;
1503 let gutter_margin;
1504 if snapshot.show_gutter {
1505 let descent = cx.text_system().descent(font_id, font_size).unwrap();
1506
1507 let gutter_padding_factor = 3.5;
1508 gutter_padding = (em_width * gutter_padding_factor).round();
1509 gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
1510 gutter_margin = -descent;
1511 } else {
1512 gutter_padding = Pixels::ZERO;
1513 gutter_width = Pixels::ZERO;
1514 gutter_margin = Pixels::ZERO;
1515 };
1516 1705
1517 editor.gutter_width = gutter_width;1706 if editor.set_wrap_width(Some(wrap_width), cx) {
1518 let text_width = bounds.size.width - gutter_width;1707 editor.snapshot(cx)
1519 let overscroll = size(em_width, px(0.));1708 } else {
1520 let snapshot = {1709 snapshot
1521 editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);1710 }
1522
1523 let editor_width = text_width - gutter_margin - overscroll.width - em_width;
1524 let wrap_width = match editor.soft_wrap_mode(cx) {
1525 SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
1526 SoftWrap::EditorWidth => editor_width,
1527 SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
1528 };1711 };
1529 1712
1530 if editor.set_wrap_width(Some(wrap_width), cx) {1713 let wrap_guides = editor
1531 editor.snapshot(cx)1714 .wrap_guides(cx)
1715 .iter()
1716 .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
1717 .collect::<SmallVec<[_; 2]>>();
1718
1719 let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
1720 // todo!("this should happen during layout")
1721 let editor_mode = snapshot.mode;
1722 if let EditorMode::AutoHeight { max_lines } = editor_mode {
1723 todo!()
1724 // size.set_y(
1725 // scroll_height
1726 // .min(constraint.max_along(Axis::Vertical))
1727 // .max(constraint.min_along(Axis::Vertical))
1728 // .max(line_height)
1729 // .min(line_height * max_lines as f32),
1730 // )
1731 } else if let EditorMode::SingleLine = editor_mode {
1732 bounds.size.height = line_height.min(bounds.size.height);
1733 }
1734 // todo!()
1735 // else if size.y.is_infinite() {
1736 // // size.set_y(scroll_height);
1737 // }
1738 //
1739 let gutter_size = size(gutter_width, bounds.size.height);
1740 let text_size = size(text_width, bounds.size.height);
1741
1742 let autoscroll_horizontally =
1743 editor.autoscroll_vertically(bounds.size.height, line_height, cx);
1744 let mut snapshot = editor.snapshot(cx);
1745
1746 let scroll_position = snapshot.scroll_position();
1747 // The scroll position is a fractional point, the whole number of which represents
1748 // the top of the window in terms of display rows.
1749 let start_row = scroll_position.y as u32;
1750 let height_in_lines = f32::from(bounds.size.height / line_height);
1751 let max_row = snapshot.max_point().row();
1752
1753 // Add 1 to ensure selections bleed off screen
1754 let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
1755
1756 let start_anchor = if start_row == 0 {
1757 Anchor::min()
1532 } else {1758 } else {
1533 snapshot1759 snapshot
1534 }1760 .buffer_snapshot
1535 };1761 .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
1762 };
1763 let end_anchor = if end_row > max_row {
1764 Anchor::max()
1765 } else {
1766 snapshot
1767 .buffer_snapshot
1768 .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
1769 };
1536 1770
1537 let wrap_guides = editor1771 let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
1538 .wrap_guides(cx)1772 let mut active_rows = BTreeMap::new();
1539 .iter()1773 let is_singleton = editor.is_singleton(cx);
1540 .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
1541 .collect::<SmallVec<[_; 2]>>();
1542
1543 let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
1544 // todo!("this should happen during layout")
1545 let editor_mode = snapshot.mode;
1546 if let EditorMode::AutoHeight { max_lines } = editor_mode {
1547 todo!()
1548 // size.set_y(
1549 // scroll_height
1550 // .min(constraint.max_along(Axis::Vertical))
1551 // .max(constraint.min_along(Axis::Vertical))
1552 // .max(line_height)
1553 // .min(line_height * max_lines as f32),
1554 // )
1555 } else if let EditorMode::SingleLine = editor_mode {
1556 bounds.size.height = line_height.min(bounds.size.height);
1557 }
1558 // todo!()
1559 // else if size.y.is_infinite() {
1560 // // size.set_y(scroll_height);
1561 // }
1562 //
1563 let gutter_size = size(gutter_width, bounds.size.height);
1564 let text_size = size(text_width, bounds.size.height);
1565
1566 let autoscroll_horizontally =
1567 editor.autoscroll_vertically(bounds.size.height, line_height, cx);
1568 let mut snapshot = editor.snapshot(cx);
1569
1570 let scroll_position = snapshot.scroll_position();
1571 // The scroll position is a fractional point, the whole number of which represents
1572 // the top of the window in terms of display rows.
1573 let start_row = scroll_position.y as u32;
1574 let height_in_lines = f32::from(bounds.size.height / line_height);
1575 let max_row = snapshot.max_point().row();
1576
1577 // Add 1 to ensure selections bleed off screen
1578 let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
1579
1580 let start_anchor = if start_row == 0 {
1581 Anchor::min()
1582 } else {
1583 snapshot
1584 .buffer_snapshot
1585 .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
1586 };
1587 let end_anchor = if end_row > max_row {
1588 Anchor::max()
1589 } else {
1590 snapshot
1591 .buffer_snapshot
1592 .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
1593 };
1594 1774
1595 let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();1775 let highlighted_rows = editor.highlighted_rows();
1596 let mut active_rows = BTreeMap::new();1776 let highlighted_ranges = editor.background_highlights_in_range(
1597 let is_singleton = editor.is_singleton(cx);1777 start_anchor..end_anchor,
1778 &snapshot.display_snapshot,
1779 cx.theme().colors(),
1780 );
1598 1781
1599 let highlighted_rows = editor.highlighted_rows();1782 let mut newest_selection_head = None;
1600 let highlighted_ranges = editor.background_highlights_in_range(1783
1601 start_anchor..end_anchor,1784 if editor.show_local_selections {
1602 &snapshot.display_snapshot,1785 let mut local_selections: Vec<Selection<Point>> = editor
1603 cx.theme().colors(),1786 .selections
1604 );1787 .disjoint_in_range(start_anchor..end_anchor, cx);
1788 local_selections.extend(editor.selections.pending(cx));
1789 let mut layouts = Vec::new();
1790 let newest = editor.selections.newest(cx);
1791 for selection in local_selections.drain(..) {
1792 let is_empty = selection.start == selection.end;
1793 let is_newest = selection == newest;
1794
1795 let layout = SelectionLayout::new(
1796 selection,
1797 editor.selections.line_mode,
1798 editor.cursor_shape,
1799 &snapshot.display_snapshot,
1800 is_newest,
1801 true,
1802 );
1803 if is_newest {
1804 newest_selection_head = Some(layout.head);
1805 }
1605 1806
1606 let mut newest_selection_head = None;1807 for row in cmp::max(layout.active_rows.start, start_row)
1607 1808 ..=cmp::min(layout.active_rows.end, end_row)
1608 if editor.show_local_selections {1809 {
1609 let mut local_selections: Vec<Selection<Point>> = editor1810 let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
1610 .selections1811 *contains_non_empty_selection |= !is_empty;
1611 .disjoint_in_range(start_anchor..end_anchor, cx);1812 }
1612 local_selections.extend(editor.selections.pending(cx));1813 layouts.push(layout);
1613 let mut layouts = Vec::new();
1614 let newest = editor.selections.newest(cx);
1615 for selection in local_selections.drain(..) {
1616 let is_empty = selection.start == selection.end;
1617 let is_newest = selection == newest;
1618
1619 let layout = SelectionLayout::new(
1620 selection,
1621 editor.selections.line_mode,
1622 editor.cursor_shape,
1623 &snapshot.display_snapshot,
1624 is_newest,
1625 true,
1626 );
1627 if is_newest {
1628 newest_selection_head = Some(layout.head);
1629 }1814 }
1630 1815
1631 for row in cmp::max(layout.active_rows.start, start_row)1816 selections.push((style.local_player, layouts));
1632 ..=cmp::min(layout.active_rows.end, end_row)
1633 {
1634 let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
1635 *contains_non_empty_selection |= !is_empty;
1636 }
1637 layouts.push(layout);
1638 }1817 }
1639 1818
1640 selections.push((style.local_player, layouts));1819 if let Some(collaboration_hub) = &editor.collaboration_hub {
1641 }1820 // When following someone, render the local selections in their color.
1642 1821 if let Some(leader_id) = editor.leader_peer_id {
1643 if let Some(collaboration_hub) = &editor.collaboration_hub {1822 if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
1644 // When following someone, render the local selections in their color.1823 if let Some(participant_index) = collaboration_hub
1645 if let Some(leader_id) = editor.leader_peer_id {1824 .user_participant_indices(cx)
1646 if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {1825 .get(&collaborator.user_id)
1647 if let Some(participant_index) = collaboration_hub1826 {
1648 .user_participant_indices(cx)1827 if let Some((local_selection_style, _)) = selections.first_mut() {
1649 .get(&collaborator.user_id)1828 *local_selection_style = cx
1650 {1829 .theme()
1651 if let Some((local_selection_style, _)) = selections.first_mut() {1830 .players()
1652 *local_selection_style = cx1831 .color_for_participant(participant_index.0);
1653 .theme()1832 }
1654 .players()
1655 .color_for_participant(participant_index.0);
1656 }1833 }
1657 }1834 }
1658 }1835 }
1659 }
1660 1836
1661 let mut remote_selections = HashMap::default();1837 let mut remote_selections = HashMap::default();
1662 for selection in snapshot.remote_selections_in_range(1838 for selection in snapshot.remote_selections_in_range(
1663 &(start_anchor..end_anchor),1839 &(start_anchor..end_anchor),
1664 collaboration_hub.as_ref(),1840 collaboration_hub.as_ref(),
1665 cx,1841 cx,
1666 ) {1842 ) {
1667 let selection_style = if let Some(participant_index) = selection.participant_index {1843 let selection_style = if let Some(participant_index) = selection.participant_index {
1668 cx.theme()1844 cx.theme()
1669 .players()1845 .players()
1670 .color_for_participant(participant_index.0)1846 .color_for_participant(participant_index.0)
1671 } else {1847 } else {
1672 cx.theme().players().absent()1848 cx.theme().players().absent()
1673 };1849 };
1674 1850
1675 // Don't re-render the leader's selections, since the local selections1851 // Don't re-render the leader's selections, since the local selections
1676 // match theirs.1852 // match theirs.
1677 if Some(selection.peer_id) == editor.leader_peer_id {1853 if Some(selection.peer_id) == editor.leader_peer_id {
1678 continue;1854 continue;
1855 }
1856
1857 remote_selections
1858 .entry(selection.replica_id)
1859 .or_insert((selection_style, Vec::new()))
1860 .1
1861 .push(SelectionLayout::new(
1862 selection.selection,
1863 selection.line_mode,
1864 selection.cursor_shape,
1865 &snapshot.display_snapshot,
1866 false,
1867 false,
1868 ));
1679 }1869 }
1680 1870
1681 remote_selections1871 selections.extend(remote_selections.into_values());
1682 .entry(selection.replica_id)
1683 .or_insert((selection_style, Vec::new()))
1684 .1
1685 .push(SelectionLayout::new(
1686 selection.selection,
1687 selection.line_mode,
1688 selection.cursor_shape,
1689 &snapshot.display_snapshot,
1690 false,
1691 false,
1692 ));
1693 }1872 }
1694 1873
1695 selections.extend(remote_selections.into_values());1874 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
1696 }1875 let show_scrollbars = match scrollbar_settings.show {
1697 1876 ShowScrollbar::Auto => {
1698 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;1877 // Git
1699 let show_scrollbars = match scrollbar_settings.show {1878 (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
1700 ShowScrollbar::Auto => {1879 ||
1701 // Git1880 // Selections
1702 (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())1881 (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty())
1703 ||1882 // Scrollmanager
1704 // Selections1883 || editor.scroll_manager.scrollbars_visible()
1705 (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty())1884 }
1706 // Scrollmanager1885 ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
1707 || editor.scroll_manager.scrollbars_visible()1886 ShowScrollbar::Always => true,
1708 }1887 ShowScrollbar::Never => false,
1709 ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),1888 };
1710 ShowScrollbar::Always => true,
1711 ShowScrollbar::Never => false,
1712 };
1713 1889
1714 let head_for_relative = newest_selection_head.unwrap_or_else(|| {1890 let head_for_relative = newest_selection_head.unwrap_or_else(|| {
1715 let newest = editor.selections.newest::<Point>(cx);1891 let newest = editor.selections.newest::<Point>(cx);
1716 SelectionLayout::new(1892 SelectionLayout::new(
1717 newest,1893 newest,
1718 editor.selections.line_mode,1894 editor.selections.line_mode,
1719 editor.cursor_shape,1895 editor.cursor_shape,
1720 &snapshot.display_snapshot,1896 &snapshot.display_snapshot,
1721 true,1897 true,
1722 true,1898 true,
1723 )1899 )
1724 .head1900 .head
1725 });1901 });
1726 1902
1727 let (line_numbers, fold_statuses) = self.shape_line_numbers(1903 let (line_numbers, fold_statuses) = self.shape_line_numbers(
1728 start_row..end_row,1904 start_row..end_row,
1729 &active_rows,1905 &active_rows,
1730 head_for_relative,1906 head_for_relative,
1731 is_singleton,1907 is_singleton,
1732 &snapshot,1908 &snapshot,
1733 cx,1909 cx,
1734 );1910 );
1735 1911
1736 let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);1912 let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
1737 1913
1738 let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);1914 let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
1739 1915
1740 let mut max_visible_line_width = Pixels::ZERO;1916 let mut max_visible_line_width = Pixels::ZERO;
1741 let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);1917 let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
1742 for line_with_invisibles in &line_layouts {1918 for line_with_invisibles in &line_layouts {
1743 if line_with_invisibles.line.width > max_visible_line_width {1919 if line_with_invisibles.line.width > max_visible_line_width {
1744 max_visible_line_width = line_with_invisibles.line.width;1920 max_visible_line_width = line_with_invisibles.line.width;
1921 }
1745 }1922 }
1746 }
1747 1923
1748 let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)1924 let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
1749 .unwrap()1925 .unwrap()
1750 .width;1926 .width;
1751 let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;1927 let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
1752 1928
1753 let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| {1929 let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| {
1754 self.layout_blocks(1930 self.layout_blocks(
1755 start_row..end_row,1931 start_row..end_row,
1756 &snapshot,1932 &snapshot,
1757 bounds.size.width,1933 bounds.size.width,
1758 scroll_width,1934 scroll_width,
1759 gutter_padding,1935 gutter_padding,
1760 gutter_width,1936 gutter_width,
1761 em_width,1937 em_width,
1762 gutter_width + gutter_margin,1938 gutter_width + gutter_margin,
1763 line_height,1939 line_height,
1764 &style,1940 &style,
1765 &line_layouts,1941 &line_layouts,
1766 editor,1942 editor,
1767 cx,1943 cx,
1768 )1944 )
1769 });1945 });
1770
1771 let scroll_max = point(
1772 f32::from((scroll_width - text_size.width) / em_width).max(0.0),
1773 max_row as f32,
1774 );
1775 1946
1776 let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);1947 let scroll_max = point(
1948 f32::from((scroll_width - text_size.width) / em_width).max(0.0),
1949 max_row as f32,
1950 );
1777 1951
1778 let autoscrolled = if autoscroll_horizontally {1952 let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
1779 editor.autoscroll_horizontally(1953
1780 start_row,1954 let autoscrolled = if autoscroll_horizontally {
1781 text_size.width,1955 editor.autoscroll_horizontally(
1782 scroll_width,1956 start_row,
1783 em_width,1957 text_size.width,
1784 &line_layouts,1958 scroll_width,
1785 cx,1959 em_width,
1786 )1960 &line_layouts,
1787 } else {1961 cx,
1788 false1962 )
1789 };1963 } else {
1964 false
1965 };
1790 1966
1791 if clamped || autoscrolled {1967 if clamped || autoscrolled {
1792 snapshot = editor.snapshot(cx);1968 snapshot = editor.snapshot(cx);
1793 }1969 }
1794 1970
1795 let mut context_menu = None;1971 let mut context_menu = None;
1796 let mut code_actions_indicator = None;1972 let mut code_actions_indicator = None;
1797 if let Some(newest_selection_head) = newest_selection_head {1973 if let Some(newest_selection_head) = newest_selection_head {
1798 if (start_row..end_row).contains(&newest_selection_head.row()) {1974 if (start_row..end_row).contains(&newest_selection_head.row()) {
1799 if editor.context_menu_visible() {1975 if editor.context_menu_visible() {
1800 context_menu =1976 let max_height = (12. * line_height).min((bounds.size.height - line_height) / 2.);
1801 editor.render_context_menu(newest_selection_head, &self.style, cx);1977 context_menu =
1802 }1978 editor.render_context_menu(newest_selection_head, &self.style, max_height, cx);
1979 }
1803 1980
1804 let active = matches!(1981 let active = matches!(
1805 editor.context_menu.read().as_ref(),1982 editor.context_menu.read().as_ref(),
1806 Some(crate::ContextMenu::CodeActions(_))1983 Some(crate::ContextMenu::CodeActions(_))
1807 );1984 );
1808 1985
1809 code_actions_indicator = editor1986 code_actions_indicator = editor
1810 .render_code_actions_indicator(&style, active, cx)1987 .render_code_actions_indicator(&style, active, cx)
1811 .map(|element| CodeActionsIndicator {1988 .map(|element| CodeActionsIndicator {
1812 row: newest_selection_head.row(),1989 row: newest_selection_head.row(),
1813 element,1990 button: element,
1814 });1991 });
1992 }
1815 }1993 }
1816 }
1817 1994
1818 let visible_rows = start_row..start_row + line_layouts.len() as u32;1995 let visible_rows = start_row..start_row + line_layouts.len() as u32;
1819 // todo!("hover")1996 let max_size = size(
1820 // let mut hover = editor.hover_state.render(1997 (120. * em_width) // Default size
1821 // &snapshot,1998 .min(bounds.size.width / 2.) // Shrink to half of the editor width
1822 // &style,1999 .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
1823 // visible_rows,2000 (16. * line_height) // Default size
1824 // editor.workspace.as_ref().map(|(w, _)| w.clone()),2001 .min(bounds.size.height / 2.) // Shrink to half of the editor height
1825 // cx,2002 .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
1826 // );2003 );
1827 // let mode = editor.mode;2004
1828 2005 let mut hover = editor.hover_state.render(
1829 let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| {2006 &snapshot,
1830 editor.render_fold_indicators(
1831 fold_statuses,
1832 &style,2007 &style,
1833 editor.gutter_hovered,2008 visible_rows,
1834 line_height,2009 max_size,
1835 gutter_margin,2010 editor.workspace.as_ref().map(|(w, _)| w.clone()),
1836 cx,2011 cx,
1837 )2012 );
1838 });
1839
1840 // todo!("context_menu")
1841 // if let Some((_, context_menu)) = context_menu.as_mut() {
1842 // context_menu.layout(
1843 // SizeConstraint {
1844 // min: gpui::Point::<Pixels>::zero(),
1845 // max: point(
1846 // cx.window_size().x * 0.7,
1847 // (12. * line_height).min((size.y - line_height) / 2.),
1848 // ),
1849 // },
1850 // editor,
1851 // cx,
1852 // );
1853 // }
1854
1855 // todo!("hover popovers")
1856 // if let Some((_, hover_popovers)) = hover.as_mut() {
1857 // for hover_popover in hover_popovers.iter_mut() {
1858 // hover_popover.layout(
1859 // SizeConstraint {
1860 // min: gpui::Point::<Pixels>::zero(),
1861 // max: point(
1862 // (120. * em_width) // Default size
1863 // .min(size.x / 2.) // Shrink to half of the editor width
1864 // .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
1865 // (16. * line_height) // Default size
1866 // .min(size.y / 2.) // Shrink to half of the editor height
1867 // .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
1868 // ),
1869 // },
1870 // editor,
1871 // cx,
1872 // );
1873 // }
1874 // }
1875 2013
1876 let invisible_symbol_font_size = font_size / 2.;2014 let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| {
1877 let tab_invisible = cx2015 editor.render_fold_indicators(
1878 .text_system()2016 fold_statuses,
1879 .shape_line(2017 &style,
1880 "→".into(),2018 editor.gutter_hovered,
1881 invisible_symbol_font_size,2019 line_height,
1882 &[TextRun {2020 gutter_margin,
1883 len: "→".len(),2021 cx,
1884 font: self.style.text.font(),2022 )
1885 color: cx.theme().colors().editor_invisible,2023 });
1886 background_color: None,
1887 underline: None,
1888 }],
1889 )
1890 .unwrap();
1891 let space_invisible = cx
1892 .text_system()
1893 .shape_line(
1894 "•".into(),
1895 invisible_symbol_font_size,
1896 &[TextRun {
1897 len: "•".len(),
1898 font: self.style.text.font(),
1899 color: cx.theme().colors().editor_invisible,
1900 background_color: None,
1901 underline: None,
1902 }],
1903 )
1904 .unwrap();
1905 2024
1906 LayoutState {2025 let invisible_symbol_font_size = font_size / 2.;
1907 mode: editor_mode,2026 let tab_invisible = cx
1908 position_map: Arc::new(PositionMap {2027 .text_system()
1909 size: bounds.size,2028 .shape_line(
1910 scroll_position: point(2029 "→".into(),
1911 scroll_position.x * em_width,2030 invisible_symbol_font_size,
1912 scroll_position.y * line_height,2031 &[TextRun {
1913 ),2032 len: "→".len(),
1914 scroll_max,2033 font: self.style.text.font(),
1915 line_layouts,2034 color: cx.theme().colors().editor_invisible,
1916 line_height,2035 background_color: None,
1917 em_width,2036 underline: None,
1918 em_advance,2037 }],
1919 snapshot,2038 )
1920 }),2039 .unwrap();
1921 visible_anchor_range: start_anchor..end_anchor,2040 let space_invisible = cx
1922 visible_display_row_range: start_row..end_row,2041 .text_system()
1923 wrap_guides,2042 .shape_line(
1924 gutter_size,2043 "•".into(),
1925 gutter_padding,2044 invisible_symbol_font_size,
1926 text_size,2045 &[TextRun {
1927 scrollbar_row_range,2046 len: "•".len(),
1928 show_scrollbars,2047 font: self.style.text.font(),
1929 is_singleton,2048 color: cx.theme().colors().editor_invisible,
1930 max_row,2049 background_color: None,
1931 gutter_margin,2050 underline: None,
1932 active_rows,2051 }],
1933 highlighted_rows,2052 )
1934 highlighted_ranges,2053 .unwrap();
1935 line_numbers,2054
1936 display_hunks,2055 LayoutState {
1937 blocks,2056 mode: editor_mode,
1938 selections,2057 position_map: Arc::new(PositionMap {
1939 context_menu,2058 size: bounds.size,
1940 code_actions_indicator,2059 scroll_position: point(
1941 fold_indicators,2060 scroll_position.x * em_width,
1942 tab_invisible,2061 scroll_position.y * line_height,
1943 space_invisible,2062 ),
1944 // hover_popovers: hover,2063 scroll_max,
1945 }2064 line_layouts,
2065 line_height,
2066 em_width,
2067 em_advance,
2068 snapshot,
2069 }),
2070 visible_anchor_range: start_anchor..end_anchor,
2071 visible_display_row_range: start_row..end_row,
2072 wrap_guides,
2073 gutter_size,
2074 gutter_padding,
2075 text_size,
2076 scrollbar_row_range,
2077 show_scrollbars,
2078 is_singleton,
2079 max_row,
2080 gutter_margin,
2081 active_rows,
2082 highlighted_rows,
2083 highlighted_ranges,
2084 line_numbers,
2085 display_hunks,
2086 blocks,
2087 selections,
2088 context_menu,
2089 code_actions_indicator,
2090 fold_indicators,
2091 tab_invisible,
2092 space_invisible,
2093 hover_popovers: hover,
2094 }
2095 })
1946 }2096 }
1947 2097
1948 #[allow(clippy::too_many_arguments)]2098 #[allow(clippy::too_many_arguments)]
1949 fn layout_blocks(2099 fn layout_blocks(
1950 &mut self,2100 &self,
1951 rows: Range<u32>,2101 rows: Range<u32>,
1952 snapshot: &EditorSnapshot,2102 snapshot: &EditorSnapshot,
1953 editor_width: Pixels,2103 editor_width: Pixels,
@@ -2028,12 +2178,10 @@ impl EditorElement { · 74 unmodified lines
2028 let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);2178 let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
2029 2179
2030 IconButton::new(block_id, ui::Icon::ArrowUpRight)2180 IconButton::new(block_id, ui::Icon::ArrowUpRight)
2031 .on_click(move |editor: &mut Editor, cx| {2181 .on_click(cx.listener_for(&self.editor, move |editor, e, cx| {
2032 editor.jump(jump_path.clone(), jump_position, jump_anchor, cx);2182 editor.jump(jump_path.clone(), jump_position, jump_anchor, cx);
2033 })2183 }))
2034 .tooltip(move |_, cx| {2184 .tooltip(|cx| Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx))
2035 Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx)
2036 })
2037 });2185 });
2038 2186
2039 let element = if *starts_new_buffer {2187 let element = if *starts_new_buffer {
@@ -2043,15 +2191,20 @@ impl EditorElement { · 3 unmodified lines
2043 // Can't use .and_then() because `.file_name()` and `.parent()` return references :(2191 // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
2044 if let Some(path) = path {2192 if let Some(path) = path {
2045 filename = path.file_name().map(|f| f.to_string_lossy().to_string());2193 filename = path.file_name().map(|f| f.to_string_lossy().to_string());
2046 parent_path =2194 parent_path = path
2047 path.parent().map(|p| p.to_string_lossy().to_string() + "/");2195 .parent()
2196 .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
2048 }2197 }
2049 2198
2050 h_stack()2199 h_stack()
2051 .id("path header block")2200 .id("path header block")
2052 .size_full()2201 .size_full()
2053 .bg(gpui::red())2202 .bg(gpui::red())
2054 .child(filename.unwrap_or_else(|| "untitled".to_string()))2203 .child(
2204 filename
2205 .map(SharedString::from)
2206 .unwrap_or_else(|| "untitled".into()),
2207 )
2055 .children(parent_path)2208 .children(parent_path)
2056 .children(jump_icon) // .p_x(gutter_padding)2209 .children(jump_icon) // .p_x(gutter_padding)
2057 } else {2210 } else {
@@ -2063,11 +2216,11 @@ impl EditorElement { · 5 unmodified lines
2063 .child("⋯")2216 .child("⋯")
2064 .children(jump_icon) // .p_x(gutter_padding)2217 .children(jump_icon) // .p_x(gutter_padding)
2065 };2218 };
2066 element.render()2219 element.into_any()
2067 }2220 }
2068 };2221 };
2069 2222
2070 let size = element.measure(available_space, editor, cx);2223 let size = element.measure(available_space, cx);
2071 (element, size)2224 (element, size)
2072 };2225 };
2073 2226
@@ -2126,47 +2279,61 @@ impl EditorElement { · 52 unmodified lines
2126 gutter_bounds: Bounds<Pixels>,2279 gutter_bounds: Bounds<Pixels>,
2127 text_bounds: Bounds<Pixels>,2280 text_bounds: Bounds<Pixels>,
2128 layout: &LayoutState,2281 layout: &LayoutState,
2129 cx: &mut ViewContext<Editor>,2282 cx: &mut WindowContext,
2130 ) {2283 ) {
2131 let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);2284 let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
2132 2285
2133 cx.on_mouse_event({2286 cx.on_mouse_event({
2134 let position_map = layout.position_map.clone();2287 let position_map = layout.position_map.clone();
2135 move |editor, event: &ScrollWheelEvent, phase, cx| {2288 let editor = self.editor.clone();
2289
2290 move |event: &ScrollWheelEvent, phase, cx| {
2136 if phase != DispatchPhase::Bubble {2291 if phase != DispatchPhase::Bubble {
2137 return;2292 return;
2138 }2293 }
2139 2294
2140 if Self::scroll(editor, event, &position_map, bounds, cx) {2295 let should_cancel = editor.update(cx, |editor, cx| {
2296 Self::scroll(editor, event, &position_map, bounds, cx)
2297 });
2298 if should_cancel {
2141 cx.stop_propagation();2299 cx.stop_propagation();
2142 }2300 }
2143 }2301 }
2144 });2302 });
2303
2145 cx.on_mouse_event({2304 cx.on_mouse_event({
2146 let position_map = layout.position_map.clone();2305 let position_map = layout.position_map.clone();
2147 move |editor, event: &MouseDownEvent, phase, cx| {2306 let editor = self.editor.clone();
2307
2308 move |event: &MouseDownEvent, phase, cx| {
2148 if phase != DispatchPhase::Bubble {2309 if phase != DispatchPhase::Bubble {
2149 return;2310 return;
2150 }2311 }
2151 2312
2152 if Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx) {2313 let should_cancel = editor.update(cx, |editor, cx| {
2314 Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx)
2315 });
2316
2317 if should_cancel {
2153 cx.stop_propagation()2318 cx.stop_propagation()
2154 }2319 }
2155 }2320 }
2156 });2321 });
2322
2157 cx.on_mouse_event({2323 cx.on_mouse_event({
2158 let position_map = layout.position_map.clone();2324 let position_map = layout.position_map.clone();
2159 move |editor, event: &MouseUpEvent, phase, cx| {2325 let editor = self.editor.clone();
2160 if phase != DispatchPhase::Bubble {2326 move |event: &MouseUpEvent, phase, cx| {
2161 return;2327 let should_cancel = editor.update(cx, |editor, cx| {
2162 }2328 Self::mouse_up(editor, event, &position_map, text_bounds, cx)
2329 });
2163 2330
2164 if Self::mouse_up(editor, event, &position_map, text_bounds, cx) {2331 if should_cancel {
2165 cx.stop_propagation()2332 cx.stop_propagation()
2166 }2333 }
2167 }2334 }
2168 });2335 });
2169 // todo!()2336 //todo!()
2170 // on_down(MouseButton::Right, {2337 // on_down(MouseButton::Right, {
2171 // let position_map = layout.position_map.clone();2338 // let position_map = layout.position_map.clone();
2172 // move |event, editor, cx| {2339 // move |event, editor, cx| {
@@ -2183,12 +2350,17 @@ impl EditorElement { · 10 unmodified lines
2183 // });2350 // });
2184 cx.on_mouse_event({2351 cx.on_mouse_event({
2185 let position_map = layout.position_map.clone();2352 let position_map = layout.position_map.clone();
2186 move |editor, event: &MouseMoveEvent, phase, cx| {2353 let editor = self.editor.clone();
2354 move |event: &MouseMoveEvent, phase, cx| {
2187 if phase != DispatchPhase::Bubble {2355 if phase != DispatchPhase::Bubble {
2188 return;2356 return;
2189 }2357 }
2190 2358
2191 if Self::mouse_moved(editor, event, &position_map, text_bounds, gutter_bounds, cx) {2359 let stop_propogating = editor.update(cx, |editor, cx| {
2360 Self::mouse_moved(editor, event, &position_map, text_bounds, gutter_bounds, cx)
2361 });
2362
2363 if stop_propogating {
2192 cx.stop_propagation()2364 cx.stop_propagation()
2193 }2365 }
2194 }2366 }
@@ -2267,7 +2439,7 @@ impl LineWithInvisibles { · 72 unmodified lines
2267 len: line_chunk.len(),2439 len: line_chunk.len(),
2268 font: text_style.font(),2440 font: text_style.font(),
2269 color: text_style.color,2441 color: text_style.color,
2270 background_color: None,2442 background_color: text_style.background_color,
2271 underline: text_style.underline,2443 underline: text_style.underline,
2272 });2444 });
2273 2445
@@ -2317,7 +2489,7 @@ impl LineWithInvisibles { · 43 unmodified lines
2317 content_origin: gpui::Point<Pixels>,2489 content_origin: gpui::Point<Pixels>,
2318 whitespace_setting: ShowWhitespaceSetting,2490 whitespace_setting: ShowWhitespaceSetting,
2319 selection_ranges: &[Range<DisplayPoint>],2491 selection_ranges: &[Range<DisplayPoint>],
2320 cx: &mut ViewContext<Editor>,2492 cx: &mut WindowContext,
2321 ) {2493 ) {
2322 let line_height = layout.position_map.line_height;2494 let line_height = layout.position_map.line_height;
2323 let line_y = line_height * row as f32 - layout.position_map.scroll_position.y;2495 let line_y = line_height * row as f32 - layout.position_map.scroll_position.y;
@@ -2349,7 +2521,7 @@ impl LineWithInvisibles { · 25 unmodified lines
2349 row: u32,2521 row: u32,
2350 line_height: Pixels,2522 line_height: Pixels,
2351 whitespace_setting: ShowWhitespaceSetting,2523 whitespace_setting: ShowWhitespaceSetting,
2352 cx: &mut ViewContext<Editor>,2524 cx: &mut WindowContext,
2353 ) {2525 ) {
2354 let allowed_invisibles_regions = match whitespace_setting {2526 let allowed_invisibles_regions = match whitespace_setting {
2355 ShowWhitespaceSetting::None => return,2527 ShowWhitespaceSetting::None => return,
@@ -2392,41 +2564,42 @@ enum Invisible { · 36 unmodified lines
2392 Whitespace { line_offset: usize },2564 Whitespace { line_offset: usize },
2393}2565}
2394 2566
2395impl Element<Editor> for EditorElement {2567impl Element for EditorElement {
2396 type ElementState = ();2568 type State = ();
2397
2398 fn element_id(&self) -> Option<gpui::ElementId> {
2399 Some(self.editor_id.into())
2400 }
2401 2569
2402 fn layout(2570 fn layout(
2403 &mut self,2571 &mut self,
2404 editor: &mut Editor,2572 element_state: Option<Self::State>,
2405 element_state: Option<Self::ElementState>,2573 cx: &mut gpui::WindowContext,
2406 cx: &mut gpui::ViewContext<Editor>,2574 ) -> (gpui::LayoutId, Self::State) {
2407 ) -> (gpui::LayoutId, Self::ElementState) {2575 self.editor.update(cx, |editor, cx| {
2408 editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.2576 editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.
2409 2577
2410 let rem_size = cx.rem_size();2578 let rem_size = cx.rem_size();
2411 let mut style = Style::default();2579 let mut style = Style::default();
2412 style.size.width = relative(1.).into();2580 style.size.width = relative(1.).into();
2413 style.size.height = match editor.mode {2581 style.size.height = match editor.mode {
2414 EditorMode::SingleLine => self.style.text.line_height_in_pixels(cx.rem_size()).into(),2582 EditorMode::SingleLine => {
2415 EditorMode::AutoHeight { .. } => todo!(),2583 self.style.text.line_height_in_pixels(cx.rem_size()).into()
2416 EditorMode::Full => relative(1.).into(),2584 }
2417 };2585 EditorMode::AutoHeight { .. } => todo!(),
2418 let layout_id = cx.request_layout(&style, None);2586 EditorMode::Full => relative(1.).into(),
2419 (layout_id, ())2587 };
2588 let layout_id = cx.request_layout(&style, None);
2589
2590 (layout_id, ())
2591 })
2420 }2592 }
2421 2593
2422 fn paint(2594 fn paint(
2423 &mut self,2595 mut self,
2424 bounds: Bounds<gpui::Pixels>,2596 bounds: Bounds<gpui::Pixels>,
2425 editor: &mut Editor,2597 element_state: &mut Self::State,
2426 element_state: &mut Self::ElementState,2598 cx: &mut gpui::WindowContext,
2427 cx: &mut gpui::ViewContext<Editor>,
2428 ) {2599 ) {
2429 let mut layout = self.compute_layout(editor, cx, bounds);2600 let editor = self.editor.clone();
2601
2602 let mut layout = self.compute_layout(bounds, cx);
2430 let gutter_bounds = Bounds {2603 let gutter_bounds = Bounds {
2431 origin: bounds.origin,2604 origin: bounds.origin,
2432 size: layout.gutter_size,2605 size: layout.gutter_size,
@@ -2436,43 +2609,46 @@ impl Element<Editor> for EditorElement { · 3 unmodified lines
2436 size: layout.text_size,2609 size: layout.text_size,
2437 };2610 };
2438 2611
2439 let dispatch_context = editor.dispatch_context(cx);2612 let focus_handle = editor.focus_handle(cx);
2440 cx.with_key_dispatch(2613 let dispatch_context = self.editor.read(cx).dispatch_context(cx);
2441 dispatch_context,2614 cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| {
2442 Some(editor.focus_handle.clone()),2615 self.register_actions(cx);
2443 |_, cx| {2616
2444 register_actions(cx);2617 // We call with_z_index to establish a new stacking context.
2445 2618 cx.with_z_index(0, |cx| {
2446 // We call with_z_index to establish a new stacking context.2619 cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
2447 cx.with_z_index(0, |cx| {2620 // Paint mouse listeners first, so any elements we paint on top of the editor
2448 cx.with_content_mask(Some(ContentMask { bounds }), |cx| {2621 // take precedence.
2449 // Paint mouse listeners first, so any elements we paint on top of the editor2622 self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
2450 // take precedence.2623 let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx);
2451 self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);2624 cx.handle_input(&focus_handle, input_handler);
2452 let input_handler = ElementInputHandler::new(bounds, cx);2625
2453 cx.handle_input(&editor.focus_handle, input_handler);2626 self.paint_background(gutter_bounds, text_bounds, &layout, cx);
2454 2627 if layout.gutter_size.width > Pixels::ZERO {
2455 self.paint_background(gutter_bounds, text_bounds, &layout, cx);2628 self.paint_gutter(gutter_bounds, &mut layout, cx);
2456 if layout.gutter_size.width > Pixels::ZERO {2629 }
2457 self.paint_gutter(gutter_bounds, &mut layout, editor, cx);2630 self.paint_text(text_bounds, &mut layout, cx);
2458 }
2459 self.paint_text(text_bounds, &mut layout, editor, cx);
2460 2631
2461 if !layout.blocks.is_empty() {2632 if !layout.blocks.is_empty() {
2462 cx.with_element_id(Some("editor_blocks"), |cx| {2633 cx.with_element_id(Some("editor_blocks"), |cx| {
2463 self.paint_blocks(bounds, &mut layout, editor, cx);2634 self.paint_blocks(bounds, &mut layout, cx);
2464 })2635 })
2465 }2636 }
2466 });
2467 });2637 });
2468 },2638 });
2469 )2639 })
2470 }2640 }
2471}2641}
2472 2642
2473impl Component<Editor> for EditorElement {2643impl IntoElement for EditorElement {
2474 fn render(self) -> AnyElement<Editor> {2644 type Element = Self;
2475 AnyElement::new(self)2645
2646 fn element_id(&self) -> Option<gpui::ElementId> {
2647 self.editor.element_id()
2648 }
2649
2650 fn into_element(self) -> Self::Element {
2651 self
2476 }2652 }
2477}2653}
2478 2654
@@ -3097,17 +3273,17 @@ pub struct LayoutState { · 618 unmodified lines
3097 show_scrollbars: bool,3273 show_scrollbars: bool,
3098 is_singleton: bool,3274 is_singleton: bool,
3099 max_row: u32,3275 max_row: u32,
3100 context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,3276 context_menu: Option<(DisplayPoint, AnyElement)>,
3101 code_actions_indicator: Option<CodeActionsIndicator>,3277 code_actions_indicator: Option<CodeActionsIndicator>,
3102 // hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>,3278 hover_popovers: Option<(DisplayPoint, Vec<AnyElement>)>,
3103 fold_indicators: Vec<Option<AnyElement<Editor>>>,3279 fold_indicators: Vec<Option<IconButton>>,
3104 tab_invisible: ShapedLine,3280 tab_invisible: ShapedLine,
3105 space_invisible: ShapedLine,3281 space_invisible: ShapedLine,
3106}3282}
3107 3283
3108struct CodeActionsIndicator {3284struct CodeActionsIndicator {
3109 row: u32,3285 row: u32,
3110 element: AnyElement<Editor>,3286 button: IconButton,
3111}3287}
3112 3288
3113struct PositionMap {3289struct PositionMap {
@@ -3192,7 +3368,7 @@ impl PositionMap { · 78 unmodified lines
3192 3368
3193struct BlockLayout {3369struct BlockLayout {
3194 row: u32,3370 row: u32,
3195 element: AnyElement<Editor>,3371 element: AnyElement,
3196 available_space: Size<AvailableSpace>,3372 available_space: Size<AvailableSpace>,
3197 style: BlockStyle,3373 style: BlockStyle,
3198}3374}
@@ -3897,187 +4073,18 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 { · 698 unmodified lines
3897// }4073// }
3898// }4074// }
3899 4075
3900fn register_actions(cx: &mut ViewContext<Editor>) {4076pub fn register_action<T: Action>(
3901 register_action(cx, Editor::move_left);4077 view: &View<Editor>,
3902 register_action(cx, Editor::move_right);4078 cx: &mut WindowContext,
3903 register_action(cx, Editor::move_down);
3904 register_action(cx, Editor::move_up);
3905 // on_action(cx, Editor::new_file); todo!()
3906 // on_action(cx, Editor::new_file_in_direction); todo!()
3907 register_action(cx, Editor::cancel);
3908 register_action(cx, Editor::newline);
3909 register_action(cx, Editor::newline_above);
3910 register_action(cx, Editor::newline_below);
3911 register_action(cx, Editor::backspace);
3912 register_action(cx, Editor::delete);
3913 register_action(cx, Editor::tab);
3914 register_action(cx, Editor::tab_prev);
3915 register_action(cx, Editor::indent);
3916 register_action(cx, Editor::outdent);
3917 register_action(cx, Editor::delete_line);
3918 register_action(cx, Editor::join_lines);
3919 register_action(cx, Editor::sort_lines_case_sensitive);
3920 register_action(cx, Editor::sort_lines_case_insensitive);
3921 register_action(cx, Editor::reverse_lines);
3922 register_action(cx, Editor::shuffle_lines);
3923 register_action(cx, Editor::convert_to_upper_case);
3924 register_action(cx, Editor::convert_to_lower_case);
3925 register_action(cx, Editor::convert_to_title_case);
3926 register_action(cx, Editor::convert_to_snake_case);
3927 register_action(cx, Editor::convert_to_kebab_case);
3928 register_action(cx, Editor::convert_to_upper_camel_case);
3929 register_action(cx, Editor::convert_to_lower_camel_case);
3930 register_action(cx, Editor::delete_to_previous_word_start);
3931 register_action(cx, Editor::delete_to_previous_subword_start);
3932 register_action(cx, Editor::delete_to_next_word_end);
3933 register_action(cx, Editor::delete_to_next_subword_end);
3934 register_action(cx, Editor::delete_to_beginning_of_line);
3935 register_action(cx, Editor::delete_to_end_of_line);
3936 register_action(cx, Editor::cut_to_end_of_line);
3937 register_action(cx, Editor::duplicate_line);
3938 register_action(cx, Editor::move_line_up);
3939 register_action(cx, Editor::move_line_down);
3940 register_action(cx, Editor::transpose);
3941 register_action(cx, Editor::cut);
3942 register_action(cx, Editor::copy);
3943 register_action(cx, Editor::paste);
3944 register_action(cx, Editor::undo);
3945 register_action(cx, Editor::redo);
3946 register_action(cx, Editor::move_page_up);
3947 register_action(cx, Editor::move_page_down);
3948 register_action(cx, Editor::next_screen);
3949 register_action(cx, Editor::scroll_cursor_top);
3950 register_action(cx, Editor::scroll_cursor_center);
3951 register_action(cx, Editor::scroll_cursor_bottom);
3952 register_action(cx, |editor, _: &LineDown, cx| {
3953 editor.scroll_screen(&ScrollAmount::Line(1.), cx)
3954 });
3955 register_action(cx, |editor, _: &LineUp, cx| {
3956 editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
3957 });
3958 register_action(cx, |editor, _: &HalfPageDown, cx| {
3959 editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
3960 });
3961 register_action(cx, |editor, _: &HalfPageUp, cx| {
3962 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
3963 });
3964 register_action(cx, |editor, _: &PageDown, cx| {
3965 editor.scroll_screen(&ScrollAmount::Page(1.), cx)
3966 });
3967 register_action(cx, |editor, _: &PageUp, cx| {
3968 editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
3969 });
3970 register_action(cx, Editor::move_to_previous_word_start);
3971 register_action(cx, Editor::move_to_previous_subword_start);
3972 register_action(cx, Editor::move_to_next_word_end);
3973 register_action(cx, Editor::move_to_next_subword_end);
3974 register_action(cx, Editor::move_to_beginning_of_line);
3975 register_action(cx, Editor::move_to_end_of_line);
3976 register_action(cx, Editor::move_to_start_of_paragraph);
3977 register_action(cx, Editor::move_to_end_of_paragraph);
3978 register_action(cx, Editor::move_to_beginning);
3979 register_action(cx, Editor::move_to_end);
3980 register_action(cx, Editor::select_up);
3981 register_action(cx, Editor::select_down);
3982 register_action(cx, Editor::select_left);
3983 register_action(cx, Editor::select_right);
3984 register_action(cx, Editor::select_to_previous_word_start);
3985 register_action(cx, Editor::select_to_previous_subword_start);
3986 register_action(cx, Editor::select_to_next_word_end);
3987 register_action(cx, Editor::select_to_next_subword_end);
3988 register_action(cx, Editor::select_to_beginning_of_line);
3989 register_action(cx, Editor::select_to_end_of_line);
3990 register_action(cx, Editor::select_to_start_of_paragraph);
3991 register_action(cx, Editor::select_to_end_of_paragraph);
3992 register_action(cx, Editor::select_to_beginning);
3993 register_action(cx, Editor::select_to_end);
3994 register_action(cx, Editor::select_all);
3995 register_action(cx, |editor, action, cx| {
3996 editor.select_all_matches(action, cx).log_err();
3997 });
3998 register_action(cx, Editor::select_line);
3999 register_action(cx, Editor::split_selection_into_lines);
4000 register_action(cx, Editor::add_selection_above);
4001 register_action(cx, Editor::add_selection_below);
4002 register_action(cx, |editor, action, cx| {
4003 editor.select_next(action, cx).log_err();
4004 });
4005 register_action(cx, |editor, action, cx| {
4006 editor.select_previous(action, cx).log_err();
4007 });
4008 register_action(cx, Editor::toggle_comments);
4009 register_action(cx, Editor::select_larger_syntax_node);
4010 register_action(cx, Editor::select_smaller_syntax_node);
4011 register_action(cx, Editor::move_to_enclosing_bracket);
4012 register_action(cx, Editor::undo_selection);
4013 register_action(cx, Editor::redo_selection);
4014 register_action(cx, Editor::go_to_diagnostic);
4015 register_action(cx, Editor::go_to_prev_diagnostic);
4016 register_action(cx, Editor::go_to_hunk);
4017 register_action(cx, Editor::go_to_prev_hunk);
4018 register_action(cx, Editor::go_to_definition);
4019 register_action(cx, Editor::go_to_definition_split);
4020 register_action(cx, Editor::go_to_type_definition);
4021 register_action(cx, Editor::go_to_type_definition_split);
4022 register_action(cx, Editor::fold);
4023 register_action(cx, Editor::fold_at);
4024 register_action(cx, Editor::unfold_lines);
4025 register_action(cx, Editor::unfold_at);
4026 register_action(cx, Editor::fold_selected_ranges);
4027 register_action(cx, Editor::show_completions);
4028 register_action(cx, Editor::toggle_code_actions);
4029 // on_action(cx, Editor::open_excerpts); todo!()
4030 register_action(cx, Editor::toggle_soft_wrap);
4031 register_action(cx, Editor::toggle_inlay_hints);
4032 register_action(cx, Editor::reveal_in_finder);
4033 register_action(cx, Editor::copy_path);
4034 register_action(cx, Editor::copy_relative_path);
4035 register_action(cx, Editor::copy_highlight_json);
4036 register_action(cx, |editor, action, cx| {
4037 editor
4038 .format(action, cx)
4039 .map(|task| task.detach_and_log_err(cx));
4040 });
4041 register_action(cx, Editor::restart_language_server);
4042 register_action(cx, Editor::show_character_palette);
4043 // on_action(cx, Editor::confirm_completion); todo!()
4044 register_action(cx, |editor, action, cx| {
4045 editor
4046 .confirm_code_action(action, cx)
4047 .map(|task| task.detach_and_log_err(cx));
4048 });
4049 register_action(cx, |editor, action, cx| {
4050 editor
4051 .rename(action, cx)
4052 .map(|task| task.detach_and_log_err(cx));
4053 });
4054 register_action(cx, |editor, action, cx| {
4055 editor
4056 .confirm_rename(action, cx)
4057 .map(|task| task.detach_and_log_err(cx));
4058 });
4059 register_action(cx, |editor, action, cx| {
4060 editor
4061 .find_all_references(action, cx)
4062 .map(|task| task.detach_and_log_err(cx));
4063 });
4064 register_action(cx, Editor::next_copilot_suggestion);
4065 register_action(cx, Editor::previous_copilot_suggestion);
4066 register_action(cx, Editor::copilot_suggest);
4067 register_action(cx, Editor::context_menu_first);
4068 register_action(cx, Editor::context_menu_prev);
4069 register_action(cx, Editor::context_menu_next);
4070 register_action(cx, Editor::context_menu_last);
4071}
4072
4073fn register_action<T: Action>(
4074 cx: &mut ViewContext<Editor>,
4075 listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,4079 listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
4076) {4080) {
4077 cx.on_action(TypeId::of::<T>(), move |editor, action, phase, cx| {4081 let view = view.clone();
4082 cx.on_action(TypeId::of::<T>(), move |action, phase, cx| {
4078 let action = action.downcast_ref().unwrap();4083 let action = action.downcast_ref().unwrap();
4079 if phase == DispatchPhase::Bubble {4084 if phase == DispatchPhase::Bubble {
4080 listener(editor, action, cx);4085 view.update(cx, |editor, cx| {
4086 listener(editor, action, cx);
4087 })
4081 }4088 }
4082 })4089 })
4083}4090}
crates/editor2/src/hover_popover.rs+929−946
@@ -1,15 +1,21 @@
1use crate::{1use crate::{
2 display_map::InlayOffset,2 display_map::{InlayOffset, ToDisplayPoint},
3 link_go_to_definition::{InlayHighlight, RangeInEditor},3 link_go_to_definition::{InlayHighlight, RangeInEditor},
4 Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,4 Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
5 ExcerptId, RangeToAnchorExt,5 ExcerptId, RangeToAnchorExt,
6};6};
7use futures::FutureExt;7use futures::FutureExt;
8use gpui::{AnyElement, AppContext, Model, Task, ViewContext, WeakView};8use gpui::{
9 actions, div, px, AnyElement, AppContext, CursorStyle, InteractiveElement, IntoElement, Model,
10 MouseButton, ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled,
11 Task, ViewContext, WeakView,
12};
9use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};13use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};
14use lsp::DiagnosticSeverity;
10use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};15use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
11use settings::Settings;16use settings::Settings;
12use std::{ops::Range, sync::Arc, time::Duration};17use std::{ops::Range, sync::Arc, time::Duration};
18use ui::Tooltip;
13use util::TryFutureExt;19use util::TryFutureExt;
14use workspace::Workspace;20use workspace::Workspace;
15 21
@@ -17,22 +23,17 @@ pub const HOVER_DELAY_MILLIS: u64 = 350; · 1 unmodified line
17pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;23pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
18 24
19pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;25pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
20pub const MIN_POPOVER_LINE_HEIGHT: f32 = 4.;26pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.);
21pub const HOVER_POPOVER_GAP: f32 = 10.;27pub const HOVER_POPOVER_GAP: Pixels = px(10.);
22 28
23// actions!(editor, [Hover]);29actions!(Hover);
24 30
25pub fn init(cx: &mut AppContext) {31/// Bindable action which uses the most recent selection head to trigger a hover
26 // cx.add_action(hover);32pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
33 let head = editor.selections.newest_display(cx).head();
34 show_hover(editor, head, true, cx);
27}35}
28 36
29// todo!()
30// /// Bindable action which uses the most recent selection head to trigger a hover
31// pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
32// let head = editor.selections.newest_display(cx).head();
33// show_hover(editor, head, true, cx);
34// }
35
36/// The internal hover action dispatches between `show_hover` or `hide_hover`37/// The internal hover action dispatches between `show_hover` or `hide_hover`
37/// depending on whether a point to hover over is provided.38/// depending on whether a point to hover over is provided.
38pub fn hover_at(editor: &mut Editor, point: Option<DisplayPoint>, cx: &mut ViewContext<Editor>) {39pub fn hover_at(editor: &mut Editor, point: Option<DisplayPoint>, cx: &mut ViewContext<Editor>) {
@@ -74,64 +75,63 @@ pub fn find_hovered_hint_part( · 35 unmodified lines
74}75}
75 76
76pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) {77pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) {
77 todo!()78 if EditorSettings::get_global(cx).hover_popover_enabled {
78 // if EditorSettings::get_global(cx).hover_popover_enabled {79 if editor.pending_rename.is_some() {
79 // if editor.pending_rename.is_some() {80 return;
80 // return;81 }
81 // }82
82 83 let Some(project) = editor.project.clone() else {
83 // let Some(project) = editor.project.clone() else {84 return;
84 // return;85 };
85 // };86
86 87 if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {
87 // if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {88 if let RangeInEditor::Inlay(range) = symbol_range {
88 // if let RangeInEditor::Inlay(range) = symbol_range {89 if range == &inlay_hover.range {
89 // if range == &inlay_hover.range {90 // Hover triggered from same location as last time. Don't show again.
90 // // Hover triggered from same location as last time. Don't show again.91 return;
91 // return;92 }
92 // }93 }
93 // }94 hide_hover(editor, cx);
94 // hide_hover(editor, cx);95 }
95 // }96
96 97 let task = cx.spawn(|this, mut cx| {
97 // let task = cx.spawn(|this, mut cx| {98 async move {
98 // async move {99 cx.background_executor()
99 // cx.background_executor()100 .timer(Duration::from_millis(HOVER_DELAY_MILLIS))
100 // .timer(Duration::from_millis(HOVER_DELAY_MILLIS))101 .await;
101 // .await;102 this.update(&mut cx, |this, _| {
102 // this.update(&mut cx, |this, _| {103 this.hover_state.diagnostic_popover = None;
103 // this.hover_state.diagnostic_popover = None;104 })?;
104 // })?;105
105 106 let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
106 // let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;107 let blocks = vec![inlay_hover.tooltip];
107 // let blocks = vec![inlay_hover.tooltip];108 let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
108 // let parsed_content = parse_blocks(&blocks, &language_registry, None).await;109
109 110 let hover_popover = InfoPopover {
110 // let hover_popover = InfoPopover {111 project: project.clone(),
111 // project: project.clone(),112 symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
112 // symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),113 blocks,
113 // blocks,114 parsed_content,
114 // parsed_content,115 };
115 // };116
116 117 this.update(&mut cx, |this, cx| {
117 // this.update(&mut cx, |this, cx| {118 // Highlight the selected symbol using a background highlight
118 // // Highlight the selected symbol using a background highlight119 this.highlight_inlay_background::<HoverState>(
119 // this.highlight_inlay_background::<HoverState>(120 vec![inlay_hover.range],
120 // vec![inlay_hover.range],121 |theme| theme.element_hover, // todo!("use a proper background here")
121 // |theme| theme.editor.hover_popover.highlight,122 cx,
122 // cx,123 );
123 // );124 this.hover_state.info_popover = Some(hover_popover);
124 // this.hover_state.info_popover = Some(hover_popover);125 cx.notify();
125 // cx.notify();126 })?;
126 // })?;127
127 128 anyhow::Ok(())
128 // anyhow::Ok(())129 }
129 // }130 .log_err()
130 // .log_err()131 });
131 // });132
132 133 editor.hover_state.info_task = Some(task);
133 // editor.hover_state.info_task = Some(task);134 }
134 // }
135}135}
136 136
137/// Hides the type information popup.137/// Hides the type information popup.
@@ -420,43 +420,42 @@ impl HoverState { · 282 unmodified lines
420 snapshot: &EditorSnapshot,420 snapshot: &EditorSnapshot,
421 style: &EditorStyle,421 style: &EditorStyle,
422 visible_rows: Range<u32>,422 visible_rows: Range<u32>,
423 max_size: Size<Pixels>,
423 workspace: Option<WeakView<Workspace>>,424 workspace: Option<WeakView<Workspace>>,
424 cx: &mut ViewContext<Editor>,425 cx: &mut ViewContext<Editor>,
425 ) -> Option<(DisplayPoint, Vec<AnyElement<Editor>>)> {426 ) -> Option<(DisplayPoint, Vec<AnyElement>)> {
426 todo!("old version below")427 // If there is a diagnostic, position the popovers based on that.
428 // Otherwise use the start of the hover range
429 let anchor = self
430 .diagnostic_popover
431 .as_ref()
432 .map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start)
433 .or_else(|| {
434 self.info_popover
435 .as_ref()
436 .map(|info_popover| match &info_popover.symbol_range {
437 RangeInEditor::Text(range) => &range.start,
438 RangeInEditor::Inlay(range) => &range.inlay_position,
439 })
440 })?;
441 let point = anchor.to_display_point(&snapshot.display_snapshot);
442
443 // Don't render if the relevant point isn't on screen
444 if !self.visible() || !visible_rows.contains(&point.row()) {
445 return None;
446 }
447
448 let mut elements = Vec::new();
449
450 if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() {
451 elements.push(diagnostic_popover.render(style, max_size, cx));
452 }
453 if let Some(info_popover) = self.info_popover.as_mut() {
454 elements.push(info_popover.render(style, max_size, workspace, cx));
455 }
456
457 Some((point, elements))
427 }458 }
428 // // If there is a diagnostic, position the popovers based on that.
429 // // Otherwise use the start of the hover range
430 // let anchor = self
431 // .diagnostic_popover
432 // .as_ref()
433 // .map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start)
434 // .or_else(|| {
435 // self.info_popover
436 // .as_ref()
437 // .map(|info_popover| match &info_popover.symbol_range {
438 // RangeInEditor::Text(range) => &range.start,
439 // RangeInEditor::Inlay(range) => &range.inlay_position,
440 // })
441 // })?;
442 // let point = anchor.to_display_point(&snapshot.display_snapshot);
443
444 // // Don't render if the relevant point isn't on screen
445 // if !self.visible() || !visible_rows.contains(&point.row()) {
446 // return None;
447 // }
448
449 // let mut elements = Vec::new();
450
451 // if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() {
452 // elements.push(diagnostic_popover.render(style, cx));
453 // }
454 // if let Some(info_popover) = self.info_popover.as_mut() {
455 // elements.push(info_popover.render(style, workspace, cx));
456 // }
457
458 // Some((point, elements))
459 // }
460}459}
461 460
462#[derive(Debug, Clone)]461#[derive(Debug, Clone)]
@@ -467,35 +466,36 @@ pub struct InfoPopover { · 4 unmodified lines
467 parsed_content: ParsedMarkdown,466 parsed_content: ParsedMarkdown,
468}467}
469 468
470// impl InfoPopover {469impl InfoPopover {
471// pub fn render(470 pub fn render(
472// &mut self,471 &mut self,
473// style: &EditorStyle,472 style: &EditorStyle,
474// workspace: Option<WeakView<Workspace>>,473 max_size: Size<Pixels>,
475// cx: &mut ViewContext<Editor>,474 workspace: Option<WeakView<Workspace>>,
476// ) -> AnyElement<Editor> {475 cx: &mut ViewContext<Editor>,
477// MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {476 ) -> AnyElement {
478// Flex::column()477 div()
479// .scrollable::<HoverBlock>(0, None, cx)478 .id("info_popover")
480// .with_child(crate::render_parsed_markdown::<HoverBlock>(479 .overflow_y_scroll()
481// &self.parsed_content,480 .bg(gpui::red())
482// style,481 .max_w(max_size.width)
483// workspace,482 .max_h(max_size.height)
484// cx,483 // Prevent a mouse move on the popover from being propagated to the editor,
485// ))484 // because that would dismiss the popover.
486// .contained()485 .on_mouse_move(|_, cx| cx.stop_propagation())
487// .with_style(style.hover_popover.container)486 // Prevent a mouse down on the popover from being propagated to the editor,
488// })487 // because that would move the cursor.
489// .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.488 .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
490// .with_cursor_style(CursorStyle::Arrow)489 .child(crate::render_parsed_markdown(
491// .with_padding(Padding {490 "content",
492// bottom: HOVER_POPOVER_GAP,491 &self.parsed_content,
493// top: HOVER_POPOVER_GAP,492 style,
494// ..Default::default()493 workspace,
495// })494 cx,
496// .into_any()495 ))
497// }496 .into_any_element()
498// }497 }
498}
499 499
500#[derive(Debug, Clone)]500#[derive(Debug, Clone)]
501pub struct DiagnosticPopover {501pub struct DiagnosticPopover {
@@ -504,57 +504,40 @@ pub struct DiagnosticPopover { · 2 unmodified lines
504}504}
505 505
506impl DiagnosticPopover {506impl DiagnosticPopover {
507 pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement<Editor> {507 pub fn render(
508 todo!()508 &self,
509 // enum PrimaryDiagnostic {}509 style: &EditorStyle,
510 510 max_size: Size<Pixels>,
511 // let mut text_style = style.hover_popover.prose.clone();511 cx: &mut ViewContext<Editor>,
512 // text_style.font_size = style.text.font_size;512 ) -> AnyElement {
513 // let diagnostic_source_style = style.hover_popover.diagnostic_source_highlight.clone();513 let text = match &self.local_diagnostic.diagnostic.source {
514 514 Some(source) => format!("{source}: {}", self.local_diagnostic.diagnostic.message),
515 // let text = match &self.local_diagnostic.diagnostic.source {515 None => self.local_diagnostic.diagnostic.message.clone(),
516 // Some(source) => Text::new(516 };
517 // format!("{source}: {}", self.local_diagnostic.diagnostic.message),517
518 // text_style,518 let container_bg = crate::diagnostic_style(
519 // )519 self.local_diagnostic.diagnostic.severity,
520 // .with_highlights(vec![(0..source.len(), diagnostic_source_style)]),520 true,
521 521 &style.diagnostic_style,
522 // None => Text::new(self.local_diagnostic.diagnostic.message.clone(), text_style),522 );
523 // };523
524 524 div()
525 // let container_style = match self.local_diagnostic.diagnostic.severity {525 .id("diagnostic")
526 // DiagnosticSeverity::HINT => style.hover_popover.info_container,526 .overflow_y_scroll()
527 // DiagnosticSeverity::INFORMATION => style.hover_popover.info_container,527 .bg(container_bg)
528 // DiagnosticSeverity::WARNING => style.hover_popover.warning_container,528 .max_w(max_size.width)
529 // DiagnosticSeverity::ERROR => style.hover_popover.error_container,529 .max_h(max_size.height)
530 // _ => style.hover_popover.container,530 .cursor(CursorStyle::PointingHand)
531 // };531 .tooltip(move |cx| Tooltip::for_action("Go To Diagnostic", &crate::GoToDiagnostic, cx))
532 532 // Prevent a mouse move on the popover from being propagated to the editor,
533 // let tooltip_style = theme::current(cx).tooltip.clone();533 // because that would dismiss the popover.
534 534 .on_mouse_move(|_, cx| cx.stop_propagation())
535 // MouseEventHandler::new::<DiagnosticPopover, _>(0, cx, |_, _| {535 // Prevent a mouse down on the popover from being propagated to the editor,
536 // text.with_soft_wrap(true)536 // because that would move the cursor.
537 // .contained()537 .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
538 // .with_style(container_style)538 .on_click(cx.listener(|editor, _, cx| editor.go_to_diagnostic(&Default::default(), cx)))
539 // })539 .child(SharedString::from(text))
540 // .with_padding(Padding {540 .into_any_element()
541 // top: HOVER_POPOVER_GAP,
542 // bottom: HOVER_POPOVER_GAP,
543 // ..Default::default()
544 // })
545 // .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.
546 // .on_click(MouseButton::Left, |_, this, cx| {
547 // this.go_to_diagnostic(&Default::default(), cx)
548 // })
549 // .with_cursor_style(CursorStyle::PointingHand)
550 // .with_tooltip::<PrimaryDiagnostic>(
551 // 0,
552 // "Go To Diagnostic".to_string(),
553 // Some(Box::new(crate::GoToDiagnostic)),
554 // tooltip_style,
555 // cx,
556 // )
557 // .into_any()
558 }541 }
559 542
560 pub fn activation_info(&self) -> (usize, Anchor) {543 pub fn activation_info(&self) -> (usize, Anchor) {
@@ -567,763 +550,763 @@ impl DiagnosticPopover { · 6 unmodified lines
567 }550 }
568}551}
569 552
570// #[cfg(test)]553#[cfg(test)]
571// mod tests {554mod tests {
572// use super::*;555 use super::*;
573// use crate::{556 use crate::{
574// editor_tests::init_test,557 editor_tests::init_test,
575// element::PointForPosition,558 element::PointForPosition,
576// inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},559 inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
577// link_go_to_definition::update_inlay_link_and_hover_points,560 link_go_to_definition::update_inlay_link_and_hover_points,
578// test::editor_lsp_test_context::EditorLspTestContext,561 test::editor_lsp_test_context::EditorLspTestContext,
579// InlayId,562 InlayId,
580// };563 };
581// use collections::BTreeSet;564 use collections::BTreeSet;
582// use gpui::fonts::{HighlightStyle, Underline, Weight};565 use gpui::{FontWeight, HighlightStyle, UnderlineStyle};
583// use indoc::indoc;566 use indoc::indoc;
584// use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};567 use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
585// use lsp::LanguageServerId;568 use lsp::LanguageServerId;
586// use project::{HoverBlock, HoverBlockKind};569 use project::{HoverBlock, HoverBlockKind};
587// use smol::stream::StreamExt;570 use smol::stream::StreamExt;
588// use unindent::Unindent;571 use unindent::Unindent;
589// use util::test::marked_text_ranges;572 use util::test::marked_text_ranges;
590 573
591// #[gpui::test]574 #[gpui::test]
592// async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) {575 async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) {
593// init_test(cx, |_| {});576 init_test(cx, |_| {});
594 577
595// let mut cx = EditorLspTestContext::new_rust(578 let mut cx = EditorLspTestContext::new_rust(
596// lsp::ServerCapabilities {579 lsp::ServerCapabilities {
597// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),580 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
598// ..Default::default()581 ..Default::default()
599// },582 },
600// cx,583 cx,
601// )584 )
602// .await;585 .await;
603 586
604// // Basic hover delays and then pops without moving the mouse587 // Basic hover delays and then pops without moving the mouse
605// cx.set_state(indoc! {"588 cx.set_state(indoc! {"
606// fn ˇtest() { println!(); }589 fn ˇtest() { println!(); }
607// "});590 "});
608// let hover_point = cx.display_point(indoc! {"591 let hover_point = cx.display_point(indoc! {"
609// fn test() { printˇln!(); }592 fn test() { printˇln!(); }
610// "});593 "});
611 594
612// cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));595 cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
613// assert!(!cx.editor(|editor, _| editor.hover_state.visible()));596 assert!(!cx.editor(|editor, _| editor.hover_state.visible()));
614 597
615// // After delay, hover should be visible.598 // After delay, hover should be visible.
616// let symbol_range = cx.lsp_range(indoc! {"599 let symbol_range = cx.lsp_range(indoc! {"
617// fn test() { «println!»(); }600 fn test() { «println!»(); }
618// "});601 "});
619// let mut requests =602 let mut requests =
620// cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {603 cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
621// Ok(Some(lsp::Hover {604 Ok(Some(lsp::Hover {
622// contents: lsp::HoverContents::Markup(lsp::MarkupContent {605 contents: lsp::HoverContents::Markup(lsp::MarkupContent {
623// kind: lsp::MarkupKind::Markdown,606 kind: lsp::MarkupKind::Markdown,
624// value: "some basic docs".to_string(),607 value: "some basic docs".to_string(),
625// }),608 }),
626// range: Some(symbol_range),609 range: Some(symbol_range),
627// }))610 }))
628// });611 });
629// cx.foreground()612 cx.background_executor
630// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));613 .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
631// requests.next().await;614 requests.next().await;
632 615
633// cx.editor(|editor, _| {616 cx.editor(|editor, _| {
634// assert!(editor.hover_state.visible());617 assert!(editor.hover_state.visible());
635// assert_eq!(618 assert_eq!(
636// editor.hover_state.info_popover.clone().unwrap().blocks,619 editor.hover_state.info_popover.clone().unwrap().blocks,
637// vec![HoverBlock {620 vec![HoverBlock {
638// text: "some basic docs".to_string(),621 text: "some basic docs".to_string(),
639// kind: HoverBlockKind::Markdown,622 kind: HoverBlockKind::Markdown,
640// },]623 },]
641// )624 )
642// });625 });
643 626
644// // Mouse moved with no hover response dismisses627 // Mouse moved with no hover response dismisses
645// let hover_point = cx.display_point(indoc! {"628 let hover_point = cx.display_point(indoc! {"
646// fn teˇst() { println!(); }629 fn teˇst() { println!(); }
647// "});630 "});
648// let mut request = cx631 let mut request = cx
649// .lsp632 .lsp
650// .handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });633 .handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
651// cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));634 cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
652// cx.foreground()635 cx.background_executor
653// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));636 .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
654// request.next().await;637 request.next().await;
655// cx.editor(|editor, _| {638 cx.editor(|editor, _| {
656// assert!(!editor.hover_state.visible());639 assert!(!editor.hover_state.visible());
657// });640 });
658// }641 }
659 642
660// #[gpui::test]643 #[gpui::test]
661// async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) {644 async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) {
662// init_test(cx, |_| {});645 init_test(cx, |_| {});
663 646
664// let mut cx = EditorLspTestContext::new_rust(647 let mut cx = EditorLspTestContext::new_rust(
665// lsp::ServerCapabilities {648 lsp::ServerCapabilities {
666// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),649 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
667// ..Default::default()650 ..Default::default()
668// },651 },
669// cx,652 cx,
670// )653 )
671// .await;654 .await;
672 655
673// // Hover with keyboard has no delay656 // Hover with keyboard has no delay
674// cx.set_state(indoc! {"657 cx.set_state(indoc! {"
675// fˇn test() { println!(); }658 fˇn test() { println!(); }
676// "});659 "});
677// cx.update_editor(|editor, cx| hover(editor, &Hover, cx));660 cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
678// let symbol_range = cx.lsp_range(indoc! {"661 let symbol_range = cx.lsp_range(indoc! {"
679// «fn» test() { println!(); }662 «fn» test() { println!(); }
680// "});663 "});
681// cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {664 cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
682// Ok(Some(lsp::Hover {665 Ok(Some(lsp::Hover {
683// contents: lsp::HoverContents::Markup(lsp::MarkupContent {666 contents: lsp::HoverContents::Markup(lsp::MarkupContent {
684// kind: lsp::MarkupKind::Markdown,667 kind: lsp::MarkupKind::Markdown,
685// value: "some other basic docs".to_string(),668 value: "some other basic docs".to_string(),
686// }),669 }),
687// range: Some(symbol_range),670 range: Some(symbol_range),
688// }))671 }))
689// })672 })
690// .next()673 .next()
691// .await;674 .await;
692 675
693// cx.condition(|editor, _| editor.hover_state.visible()).await;676 cx.condition(|editor, _| editor.hover_state.visible()).await;
694// cx.editor(|editor, _| {677 cx.editor(|editor, _| {
695// assert_eq!(678 assert_eq!(
696// editor.hover_state.info_popover.clone().unwrap().blocks,679 editor.hover_state.info_popover.clone().unwrap().blocks,
697// vec![HoverBlock {680 vec![HoverBlock {
698// text: "some other basic docs".to_string(),681 text: "some other basic docs".to_string(),
699// kind: HoverBlockKind::Markdown,682 kind: HoverBlockKind::Markdown,
700// }]683 }]
701// )684 )
702// });685 });
703// }686 }
704 687
705// #[gpui::test]688 #[gpui::test]
706// async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) {689 async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) {
707// init_test(cx, |_| {});690 init_test(cx, |_| {});
708 691
709// let mut cx = EditorLspTestContext::new_rust(692 let mut cx = EditorLspTestContext::new_rust(
710// lsp::ServerCapabilities {693 lsp::ServerCapabilities {
711// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),694 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
712// ..Default::default()695 ..Default::default()
713// },696 },
714// cx,697 cx,
715// )698 )
716// .await;699 .await;
717 700
718// // Hover with keyboard has no delay701 // Hover with keyboard has no delay
719// cx.set_state(indoc! {"702 cx.set_state(indoc! {"
720// fˇn test() { println!(); }703 fˇn test() { println!(); }
721// "});704 "});
722// cx.update_editor(|editor, cx| hover(editor, &Hover, cx));705 cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
723// let symbol_range = cx.lsp_range(indoc! {"706 let symbol_range = cx.lsp_range(indoc! {"
724// «fn» test() { println!(); }707 «fn» test() { println!(); }
725// "});708 "});
726// cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {709 cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
727// Ok(Some(lsp::Hover {710 Ok(Some(lsp::Hover {
728// contents: lsp::HoverContents::Array(vec![711 contents: lsp::HoverContents::Array(vec![
729// lsp::MarkedString::String("regular text for hover to show".to_string()),712 lsp::MarkedString::String("regular text for hover to show".to_string()),
730// lsp::MarkedString::String("".to_string()),713 lsp::MarkedString::String("".to_string()),
731// lsp::MarkedString::LanguageString(lsp::LanguageString {714 lsp::MarkedString::LanguageString(lsp::LanguageString {
732// language: "Rust".to_string(),715 language: "Rust".to_string(),
733// value: "".to_string(),716 value: "".to_string(),
734// }),717 }),
735// ]),718 ]),
736// range: Some(symbol_range),719 range: Some(symbol_range),
737// }))720 }))
738// })721 })
739// .next()722 .next()
740// .await;723 .await;
741 724
742// cx.condition(|editor, _| editor.hover_state.visible()).await;725 cx.condition(|editor, _| editor.hover_state.visible()).await;
743// cx.editor(|editor, _| {726 cx.editor(|editor, _| {
744// assert_eq!(727 assert_eq!(
745// editor.hover_state.info_popover.clone().unwrap().blocks,728 editor.hover_state.info_popover.clone().unwrap().blocks,
746// vec![HoverBlock {729 vec![HoverBlock {
747// text: "regular text for hover to show".to_string(),730 text: "regular text for hover to show".to_string(),
748// kind: HoverBlockKind::Markdown,731 kind: HoverBlockKind::Markdown,
749// }],732 }],
750// "No empty string hovers should be shown"733 "No empty string hovers should be shown"
751// );734 );
752// });735 });
753// }736 }
754 737
755// #[gpui::test]738 #[gpui::test]
756// async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) {739 async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) {
757// init_test(cx, |_| {});740 init_test(cx, |_| {});
758 741
759// let mut cx = EditorLspTestContext::new_rust(742 let mut cx = EditorLspTestContext::new_rust(
760// lsp::ServerCapabilities {743 lsp::ServerCapabilities {
761// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),744 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
762// ..Default::default()745 ..Default::default()
763// },746 },
764// cx,747 cx,
765// )748 )
766// .await;749 .await;
767 750
768// // Hover with keyboard has no delay751 // Hover with keyboard has no delay
769// cx.set_state(indoc! {"752 cx.set_state(indoc! {"
770// fˇn test() { println!(); }753 fˇn test() { println!(); }
771// "});754 "});
772// cx.update_editor(|editor, cx| hover(editor, &Hover, cx));755 cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
773// let symbol_range = cx.lsp_range(indoc! {"756 let symbol_range = cx.lsp_range(indoc! {"
774// «fn» test() { println!(); }757 «fn» test() { println!(); }
775// "});758 "});
776 759
777// let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n";760 let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n";
778// let markdown_string = format!("\n```rust\n{code_str}```");761 let markdown_string = format!("\n```rust\n{code_str}```");
779 762
780// let closure_markdown_string = markdown_string.clone();763 let closure_markdown_string = markdown_string.clone();
781// cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| {764 cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| {
782// let future_markdown_string = closure_markdown_string.clone();765 let future_markdown_string = closure_markdown_string.clone();
783// async move {766 async move {
784// Ok(Some(lsp::Hover {767 Ok(Some(lsp::Hover {
785// contents: lsp::HoverContents::Markup(lsp::MarkupContent {768 contents: lsp::HoverContents::Markup(lsp::MarkupContent {
786// kind: lsp::MarkupKind::Markdown,769 kind: lsp::MarkupKind::Markdown,
787// value: future_markdown_string,770 value: future_markdown_string,
788// }),771 }),
789// range: Some(symbol_range),772 range: Some(symbol_range),
790// }))773 }))
791// }774 }
792// })775 })
793// .next()776 .next()
794// .await;777 .await;
795 778
796// cx.condition(|editor, _| editor.hover_state.visible()).await;779 cx.condition(|editor, _| editor.hover_state.visible()).await;
797// cx.editor(|editor, _| {780 cx.editor(|editor, _| {
798// let blocks = editor.hover_state.info_popover.clone().unwrap().blocks;781 let blocks = editor.hover_state.info_popover.clone().unwrap().blocks;
799// assert_eq!(782 assert_eq!(
800// blocks,783 blocks,
801// vec![HoverBlock {784 vec![HoverBlock {
802// text: markdown_string,785 text: markdown_string,
803// kind: HoverBlockKind::Markdown,786 kind: HoverBlockKind::Markdown,
804// }],787 }],
805// );788 );
806 789
807// let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));790 let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
808// assert_eq!(791 assert_eq!(
809// rendered.text,792 rendered.text,
810// code_str.trim(),793 code_str.trim(),
811// "Should not have extra line breaks at end of rendered hover"794 "Should not have extra line breaks at end of rendered hover"
812// );795 );
813// });796 });
814// }797 }
815 798
816// #[gpui::test]799 #[gpui::test]
817// async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {800 async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
818// init_test(cx, |_| {});801 init_test(cx, |_| {});
819 802
820// let mut cx = EditorLspTestContext::new_rust(803 let mut cx = EditorLspTestContext::new_rust(
821// lsp::ServerCapabilities {804 lsp::ServerCapabilities {
822// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),805 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
823// ..Default::default()806 ..Default::default()
824// },807 },
825// cx,808 cx,
826// )809 )
827// .await;810 .await;
828 811
829// // Hover with just diagnostic, pops DiagnosticPopover immediately and then812 // Hover with just diagnostic, pops DiagnosticPopover immediately and then
830// // info popover once request completes813 // info popover once request completes
831// cx.set_state(indoc! {"814 cx.set_state(indoc! {"
832// fn teˇst() { println!(); }815 fn teˇst() { println!(); }
833// "});816 "});
834 817
835// // Send diagnostic to client818 // Send diagnostic to client
836// let range = cx.text_anchor_range(indoc! {"819 let range = cx.text_anchor_range(indoc! {"
837// fn «test»() { println!(); }820 fn «test»() { println!(); }
838// "});821 "});
839// cx.update_buffer(|buffer, cx| {822 cx.update_buffer(|buffer, cx| {
840// let snapshot = buffer.text_snapshot();823 let snapshot = buffer.text_snapshot();
841// let set = DiagnosticSet::from_sorted_entries(824 let set = DiagnosticSet::from_sorted_entries(
842// vec![DiagnosticEntry {825 vec![DiagnosticEntry {
843// range,826 range,
844// diagnostic: Diagnostic {827 diagnostic: Diagnostic {
845// message: "A test diagnostic message.".to_string(),828 message: "A test diagnostic message.".to_string(),
846// ..Default::default()829 ..Default::default()
847// },830 },
848// }],831 }],
849// &snapshot,832 &snapshot,
850// );833 );
851// buffer.update_diagnostics(LanguageServerId(0), set, cx);834 buffer.update_diagnostics(LanguageServerId(0), set, cx);
852// });835 });
853 836
854// // Hover pops diagnostic immediately837 // Hover pops diagnostic immediately
855// cx.update_editor(|editor, cx| hover(editor, &Hover, cx));838 cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
856// cx.foreground().run_until_parked();839 cx.background_executor.run_until_parked();
857 840
858// cx.editor(|Editor { hover_state, .. }, _| {841 cx.editor(|Editor { hover_state, .. }, _| {
859// assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none())842 assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none())
860// });843 });
861 844
862// // Info Popover shows after request responded to845 // Info Popover shows after request responded to
863// let range = cx.lsp_range(indoc! {"846 let range = cx.lsp_range(indoc! {"
864// fn «test»() { println!(); }847 fn «test»() { println!(); }
865// "});848 "});
866// cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {849 cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
867// Ok(Some(lsp::Hover {850 Ok(Some(lsp::Hover {
868// contents: lsp::HoverContents::Markup(lsp::MarkupContent {851 contents: lsp::HoverContents::Markup(lsp::MarkupContent {
869// kind: lsp::MarkupKind::Markdown,852 kind: lsp::MarkupKind::Markdown,
870// value: "some new docs".to_string(),853 value: "some new docs".to_string(),
871// }),854 }),
872// range: Some(range),855 range: Some(range),
873// }))856 }))
874// });857 });
875// cx.foreground()858 cx.background_executor
876// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));859 .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
877 860
878// cx.foreground().run_until_parked();861 cx.background_executor.run_until_parked();
879// cx.editor(|Editor { hover_state, .. }, _| {862 cx.editor(|Editor { hover_state, .. }, _| {
880// hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some()863 hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some()
881// });864 });
882// }865 }
883 866
884// #[gpui::test]867 #[gpui::test]
885// fn test_render_blocks(cx: &mut gpui::TestAppContext) {868 fn test_render_blocks(cx: &mut gpui::TestAppContext) {
886// init_test(cx, |_| {});869 init_test(cx, |_| {});
887 870
888// cx.add_window(|cx| {871 let editor = cx.add_window(|cx| Editor::single_line(cx));
889// let editor = Editor::single_line(None, cx);872 editor
890// let style = editor.style(cx);873 .update(cx, |editor, cx| {
891 874 let style = editor.style.clone().unwrap();
892// struct Row {875
893// blocks: Vec<HoverBlock>,876 struct Row {
894// expected_marked_text: String,877 blocks: Vec<HoverBlock>,
895// expected_styles: Vec<HighlightStyle>,878 expected_marked_text: String,
896// }879 expected_styles: Vec<HighlightStyle>,
897 880 }
898// let rows = &[881
899// // Strong emphasis882 let rows = &[
900// Row {883 // Strong emphasis
901// blocks: vec![HoverBlock {884 Row {
902// text: "one **two** three".to_string(),885 blocks: vec![HoverBlock {
903// kind: HoverBlockKind::Markdown,886 text: "one **two** three".to_string(),
904// }],887 kind: HoverBlockKind::Markdown,
905// expected_marked_text: "one «two» three".to_string(),888 }],
906// expected_styles: vec![HighlightStyle {889 expected_marked_text: "one «two» three".to_string(),
907// weight: Some(Weight::BOLD),890 expected_styles: vec![HighlightStyle {
908// ..Default::default()891 font_weight: Some(FontWeight::BOLD),
909// }],892 ..Default::default()
910// },893 }],
911// // Links894 },
912// Row {895 // Links
913// blocks: vec![HoverBlock {896 Row {
914// text: "one [two](https://the-url) three".to_string(),897 blocks: vec![HoverBlock {
915// kind: HoverBlockKind::Markdown,898 text: "one [two](https://the-url) three".to_string(),
916// }],899 kind: HoverBlockKind::Markdown,
917// expected_marked_text: "one «two» three".to_string(),900 }],
918// expected_styles: vec![HighlightStyle {901 expected_marked_text: "one «two» three".to_string(),
919// underline: Some(Underline {902 expected_styles: vec![HighlightStyle {
920// thickness: 1.0.into(),903 underline: Some(UnderlineStyle {
921// ..Default::default()904 thickness: 1.0.into(),
922// }),905 ..Default::default()
923// ..Default::default()906 }),
924// }],907 ..Default::default()
925// },908 }],
926// // Lists909 },
927// Row {910 // Lists
928// blocks: vec![HoverBlock {911 Row {
929// text: "912 blocks: vec![HoverBlock {
930// lists:913 text: "
931// * one914 lists:
932// - a915 * one
933// - b916 - a
934// * two917 - b
935// - [c](https://the-url)918 * two
936// - d"919 - [c](https://the-url)
937// .unindent(),920 - d"
938// kind: HoverBlockKind::Markdown,921 .unindent(),
939// }],922 kind: HoverBlockKind::Markdown,
940// expected_marked_text: "923 }],
941// lists:924 expected_marked_text: "
942// - one925 lists:
943// - a926 - one
944// - b927 - a
945// - two928 - b
946// - «c»929 - two
947// - d"930 - «c»
948// .unindent(),931 - d"
949// expected_styles: vec![HighlightStyle {932 .unindent(),
950// underline: Some(Underline {933 expected_styles: vec![HighlightStyle {
951// thickness: 1.0.into(),934 underline: Some(UnderlineStyle {
952// ..Default::default()935 thickness: 1.0.into(),
953// }),936 ..Default::default()
954// ..Default::default()937 }),
955// }],938 ..Default::default()
956// },939 }],
957// // Multi-paragraph list items940 },
958// Row {941 // Multi-paragraph list items
959// blocks: vec![HoverBlock {942 Row {
960// text: "943 blocks: vec![HoverBlock {
961// * one two944 text: "
962// three945 * one two
963 946 three
964// * four five947
965// * six seven948 * four five
966// eight949 * six seven
967 950 eight
968// nine951
969// * ten952 nine
970// * six"953 * ten
971// .unindent(),954 * six"
972// kind: HoverBlockKind::Markdown,955 .unindent(),
973// }],956 kind: HoverBlockKind::Markdown,
974// expected_marked_text: "957 }],
975// - one two three958 expected_marked_text: "
976// - four five959 - one two three
977// - six seven eight960 - four five
978 961 - six seven eight
979// nine962
980// - ten963 nine
981// - six"964 - ten
982// .unindent(),965 - six"
983// expected_styles: vec![HighlightStyle {966 .unindent(),
984// underline: Some(Underline {967 expected_styles: vec![HighlightStyle {
985// thickness: 1.0.into(),968 underline: Some(UnderlineStyle {
986// ..Default::default()969 thickness: 1.0.into(),
987// }),970 ..Default::default()
988// ..Default::default()971 }),
989// }],972 ..Default::default()
990// },973 }],
991// ];974 },
992 975 ];
993// for Row {976
994// blocks,977 for Row {
995// expected_marked_text,978 blocks,
996// expected_styles,979 expected_marked_text,
997// } in &rows[0..]980 expected_styles,
998// {981 } in &rows[0..]
999// let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));982 {
1000 983 let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
1001// let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);984
1002// let expected_highlights = ranges985 let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
1003// .into_iter()986 let expected_highlights = ranges
1004// .zip(expected_styles.iter().cloned())987 .into_iter()
1005// .collect::<Vec<_>>();988 .zip(expected_styles.iter().cloned())
1006// assert_eq!(989 .collect::<Vec<_>>();
1007// rendered.text, expected_text,990 assert_eq!(
1008// "wrong text for input {blocks:?}"991 rendered.text, expected_text,
1009// );992 "wrong text for input {blocks:?}"
1010 993 );
1011// let rendered_highlights: Vec<_> = rendered994
1012// .highlights995 let rendered_highlights: Vec<_> = rendered
1013// .iter()996 .highlights
1014// .filter_map(|(range, highlight)| {997 .iter()
1015// let highlight = highlight.to_highlight_style(&style.syntax)?;998 .filter_map(|(range, highlight)| {
1016// Some((range.clone(), highlight))999 let highlight = highlight.to_highlight_style(&style.syntax)?;
1017// })1000 Some((range.clone(), highlight))
1018// .collect();1001 })
1019 1002 .collect();
1020// assert_eq!(1003
1021// rendered_highlights, expected_highlights,1004 assert_eq!(
1022// "wrong highlights for input {blocks:?}"1005 rendered_highlights, expected_highlights,
1023// );1006 "wrong highlights for input {blocks:?}"
1024// }1007 );
1025 1008 }
1026// editor1009 })
1027// });1010 .unwrap();
1028// }1011 }
1029 1012
1030// #[gpui::test]1013 #[gpui::test]
1031// async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) {1014 async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) {
1032// init_test(cx, |settings| {1015 init_test(cx, |settings| {
1033// settings.defaults.inlay_hints = Some(InlayHintSettings {1016 settings.defaults.inlay_hints = Some(InlayHintSettings {
1034// enabled: true,1017 enabled: true,
1035// show_type_hints: true,1018 show_type_hints: true,
1036// show_parameter_hints: true,1019 show_parameter_hints: true,
1037// show_other_hints: true,1020 show_other_hints: true,
1038// })1021 })
1039// });1022 });
1040 1023
1041// let mut cx = EditorLspTestContext::new_rust(1024 let mut cx = EditorLspTestContext::new_rust(
1042// lsp::ServerCapabilities {1025 lsp::ServerCapabilities {
1043// inlay_hint_provider: Some(lsp::OneOf::Right(1026 inlay_hint_provider: Some(lsp::OneOf::Right(
1044// lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions {1027 lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions {
1045// resolve_provider: Some(true),1028 resolve_provider: Some(true),
1046// ..Default::default()1029 ..Default::default()
1047// }),1030 }),
1048// )),1031 )),
1049// ..Default::default()1032 ..Default::default()
1050// },1033 },
1051// cx,1034 cx,
1052// )1035 )
1053// .await;1036 .await;
1054 1037
1055// cx.set_state(indoc! {"1038 cx.set_state(indoc! {"
1056// struct TestStruct;1039 struct TestStruct;
1057 1040
1058// // ==================1041 // ==================
1059 1042
1060// struct TestNewType<T>(T);1043 struct TestNewType<T>(T);
1061 1044
1062// fn main() {1045 fn main() {
1063// let variableˇ = TestNewType(TestStruct);1046 let variableˇ = TestNewType(TestStruct);
1064// }1047 }
1065// "});1048 "});
1066 1049
1067// let hint_start_offset = cx.ranges(indoc! {"1050 let hint_start_offset = cx.ranges(indoc! {"
1068// struct TestStruct;1051 struct TestStruct;
1069 1052
1070// // ==================1053 // ==================
1071 1054
1072// struct TestNewType<T>(T);1055 struct TestNewType<T>(T);
1073 1056
1074// fn main() {1057 fn main() {
1075// let variableˇ = TestNewType(TestStruct);1058 let variableˇ = TestNewType(TestStruct);
1076// }1059 }
1077// "})[0]1060 "})[0]
1078// .start;1061 .start;
1079// let hint_position = cx.to_lsp(hint_start_offset);1062 let hint_position = cx.to_lsp(hint_start_offset);
1080// let new_type_target_range = cx.lsp_range(indoc! {"1063 let new_type_target_range = cx.lsp_range(indoc! {"
1081// struct TestStruct;1064 struct TestStruct;
1082 1065
1083// // ==================1066 // ==================
1084 1067
1085// struct «TestNewType»<T>(T);1068 struct «TestNewType»<T>(T);
1086 1069
1087// fn main() {1070 fn main() {
1088// let variable = TestNewType(TestStruct);1071 let variable = TestNewType(TestStruct);
1089// }1072 }
1090// "});1073 "});
1091// let struct_target_range = cx.lsp_range(indoc! {"1074 let struct_target_range = cx.lsp_range(indoc! {"
1092// struct «TestStruct»;1075 struct «TestStruct»;
1093 1076
1094// // ==================1077 // ==================
1095 1078
1096// struct TestNewType<T>(T);1079 struct TestNewType<T>(T);
1097 1080
1098// fn main() {1081 fn main() {
1099// let variable = TestNewType(TestStruct);1082 let variable = TestNewType(TestStruct);
1100// }1083 }
1101// "});1084 "});
1102 1085
1103// let uri = cx.buffer_lsp_url.clone();1086 let uri = cx.buffer_lsp_url.clone();
1104// let new_type_label = "TestNewType";1087 let new_type_label = "TestNewType";
1105// let struct_label = "TestStruct";1088 let struct_label = "TestStruct";
1106// let entire_hint_label = ": TestNewType<TestStruct>";1089 let entire_hint_label = ": TestNewType<TestStruct>";
1107// let closure_uri = uri.clone();1090 let closure_uri = uri.clone();
1108// cx.lsp1091 cx.lsp
1109// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {1092 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1110// let task_uri = closure_uri.clone();1093 let task_uri = closure_uri.clone();
1111// async move {1094 async move {
1112// assert_eq!(params.text_document.uri, task_uri);1095 assert_eq!(params.text_document.uri, task_uri);
1113// Ok(Some(vec![lsp::InlayHint {1096 Ok(Some(vec![lsp::InlayHint {
1114// position: hint_position,1097 position: hint_position,
1115// label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart {1098 label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart {
1116// value: entire_hint_label.to_string(),1099 value: entire_hint_label.to_string(),
1117// ..Default::default()1100 ..Default::default()
1118// }]),1101 }]),
1119// kind: Some(lsp::InlayHintKind::TYPE),1102 kind: Some(lsp::InlayHintKind::TYPE),
1120// text_edits: None,1103 text_edits: None,
1121// tooltip: None,1104 tooltip: None,
1122// padding_left: Some(false),1105 padding_left: Some(false),
1123// padding_right: Some(false),1106 padding_right: Some(false),
1124// data: None,1107 data: None,
1125// }]))1108 }]))
1126// }1109 }
1127// })1110 })
1128// .next()1111 .next()
1129// .await;1112 .await;
1130// cx.foreground().run_until_parked();1113 cx.background_executor.run_until_parked();
1131// cx.update_editor(|editor, cx| {1114 cx.update_editor(|editor, cx| {
1132// let expected_layers = vec![entire_hint_label.to_string()];1115 let expected_layers = vec![entire_hint_label.to_string()];
1133// assert_eq!(expected_layers, cached_hint_labels(editor));1116 assert_eq!(expected_layers, cached_hint_labels(editor));
1134// assert_eq!(expected_layers, visible_hint_labels(editor, cx));1117 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1135// });1118 });
1136 1119
1137// let inlay_range = cx1120 let inlay_range = cx
1138// .ranges(indoc! {"1121 .ranges(indoc! {"
1139// struct TestStruct;1122 struct TestStruct;
1140 1123
1141// // ==================1124 // ==================
1142 1125
1143// struct TestNewType<T>(T);1126 struct TestNewType<T>(T);
1144 1127
1145// fn main() {1128 fn main() {
1146// let variable« »= TestNewType(TestStruct);1129 let variable« »= TestNewType(TestStruct);
1147// }1130 }
1148// "})1131 "})
1149// .get(0)1132 .get(0)
1150// .cloned()1133 .cloned()
1151// .unwrap();1134 .unwrap();
1152// let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| {1135 let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| {
1153// let snapshot = editor.snapshot(cx);1136 let snapshot = editor.snapshot(cx);
1154// let previous_valid = inlay_range.start.to_display_point(&snapshot);1137 let previous_valid = inlay_range.start.to_display_point(&snapshot);
1155// let next_valid = inlay_range.end.to_display_point(&snapshot);1138 let next_valid = inlay_range.end.to_display_point(&snapshot);
1156// assert_eq!(previous_valid.row(), next_valid.row());1139 assert_eq!(previous_valid.row(), next_valid.row());
1157// assert!(previous_valid.column() < next_valid.column());1140 assert!(previous_valid.column() < next_valid.column());
1158// let exact_unclipped = DisplayPoint::new(1141 let exact_unclipped = DisplayPoint::new(
1159// previous_valid.row(),1142 previous_valid.row(),
1160// previous_valid.column()1143 previous_valid.column()
1161// + (entire_hint_label.find(new_type_label).unwrap() + new_type_label.len() / 2)1144 + (entire_hint_label.find(new_type_label).unwrap() + new_type_label.len() / 2)
1162// as u32,1145 as u32,
1163// );1146 );
1164// PointForPosition {1147 PointForPosition {
1165// previous_valid,1148 previous_valid,
1166// next_valid,1149 next_valid,
1167// exact_unclipped,1150 exact_unclipped,
1168// column_overshoot_after_line_end: 0,1151 column_overshoot_after_line_end: 0,
1169// }1152 }
1170// });1153 });
1171// cx.update_editor(|editor, cx| {1154 cx.update_editor(|editor, cx| {
1172// update_inlay_link_and_hover_points(1155 update_inlay_link_and_hover_points(
1173// &editor.snapshot(cx),1156 &editor.snapshot(cx),
1174// new_type_hint_part_hover_position,1157 new_type_hint_part_hover_position,
1175// editor,1158 editor,
1176// true,1159 true,
1177// false,1160 false,
1178// cx,1161 cx,
1179// );1162 );
1180// });1163 });
1181 1164
1182// let resolve_closure_uri = uri.clone();1165 let resolve_closure_uri = uri.clone();
1183// cx.lsp1166 cx.lsp
1184// .handle_request::<lsp::request::InlayHintResolveRequest, _, _>(1167 .handle_request::<lsp::request::InlayHintResolveRequest, _, _>(
1185// move |mut hint_to_resolve, _| {1168 move |mut hint_to_resolve, _| {
1186// let mut resolved_hint_positions = BTreeSet::new();1169 let mut resolved_hint_positions = BTreeSet::new();
1187// let task_uri = resolve_closure_uri.clone();1170 let task_uri = resolve_closure_uri.clone();
1188// async move {1171 async move {
1189// let inserted = resolved_hint_positions.insert(hint_to_resolve.position);1172 let inserted = resolved_hint_positions.insert(hint_to_resolve.position);
1190// assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice");1173 assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice");
1191 1174
1192// // `: TestNewType<TestStruct>`1175 // `: TestNewType<TestStruct>`
1193// hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![1176 hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![
1194// lsp::InlayHintLabelPart {1177 lsp::InlayHintLabelPart {
1195// value: ": ".to_string(),1178 value: ": ".to_string(),
1196// ..Default::default()1179 ..Default::default()
1197// },1180 },
1198// lsp::InlayHintLabelPart {1181 lsp::InlayHintLabelPart {
1199// value: new_type_label.to_string(),1182 value: new_type_label.to_string(),
1200// location: Some(lsp::Location {1183 location: Some(lsp::Location {
1201// uri: task_uri.clone(),1184 uri: task_uri.clone(),
1202// range: new_type_target_range,1185 range: new_type_target_range,
1203// }),1186 }),
1204// tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!(1187 tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!(
1205// "A tooltip for `{new_type_label}`"1188 "A tooltip for `{new_type_label}`"
1206// ))),1189 ))),
1207// ..Default::default()1190 ..Default::default()
1208// },1191 },
1209// lsp::InlayHintLabelPart {1192 lsp::InlayHintLabelPart {
1210// value: "<".to_string(),1193 value: "<".to_string(),
1211// ..Default::default()1194 ..Default::default()
1212// },1195 },
1213// lsp::InlayHintLabelPart {1196 lsp::InlayHintLabelPart {
1214// value: struct_label.to_string(),1197 value: struct_label.to_string(),
1215// location: Some(lsp::Location {1198 location: Some(lsp::Location {
1216// uri: task_uri,1199 uri: task_uri,
1217// range: struct_target_range,1200 range: struct_target_range,
1218// }),1201 }),
1219// tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent(1202 tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent(
1220// lsp::MarkupContent {1203 lsp::MarkupContent {
1221// kind: lsp::MarkupKind::Markdown,1204 kind: lsp::MarkupKind::Markdown,
1222// value: format!("A tooltip for `{struct_label}`"),1205 value: format!("A tooltip for `{struct_label}`"),
1223// },1206 },
1224// )),1207 )),
1225// ..Default::default()1208 ..Default::default()
1226// },1209 },
1227// lsp::InlayHintLabelPart {1210 lsp::InlayHintLabelPart {
1228// value: ">".to_string(),1211 value: ">".to_string(),
1229// ..Default::default()1212 ..Default::default()
1230// },1213 },
1231// ]);1214 ]);
1232 1215
1233// Ok(hint_to_resolve)1216 Ok(hint_to_resolve)
1234// }1217 }
1235// },1218 },
1236// )1219 )
1237// .next()1220 .next()
1238// .await;1221 .await;
1239// cx.foreground().run_until_parked();1222 cx.background_executor.run_until_parked();
1240 1223
1241// cx.update_editor(|editor, cx| {1224 cx.update_editor(|editor, cx| {
1242// update_inlay_link_and_hover_points(1225 update_inlay_link_and_hover_points(
1243// &editor.snapshot(cx),1226 &editor.snapshot(cx),
1244// new_type_hint_part_hover_position,1227 new_type_hint_part_hover_position,
1245// editor,1228 editor,
1246// true,1229 true,
1247// false,1230 false,
1248// cx,1231 cx,
1249// );1232 );
1250// });1233 });
1251// cx.foreground()1234 cx.background_executor
1252// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));1235 .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
1253// cx.foreground().run_until_parked();1236 cx.background_executor.run_until_parked();
1254// cx.update_editor(|editor, cx| {1237 cx.update_editor(|editor, cx| {
1255// let hover_state = &editor.hover_state;1238 let hover_state = &editor.hover_state;
1256// assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());1239 assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
1257// let popover = hover_state.info_popover.as_ref().unwrap();1240 let popover = hover_state.info_popover.as_ref().unwrap();
1258// let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));1241 let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
1259// assert_eq!(1242 assert_eq!(
1260// popover.symbol_range,1243 popover.symbol_range,
1261// RangeInEditor::Inlay(InlayHighlight {1244 RangeInEditor::Inlay(InlayHighlight {
1262// inlay: InlayId::Hint(0),1245 inlay: InlayId::Hint(0),
1263// inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),1246 inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
1264// range: ": ".len()..": ".len() + new_type_label.len(),1247 range: ": ".len()..": ".len() + new_type_label.len(),
1265// }),1248 }),
1266// "Popover range should match the new type label part"1249 "Popover range should match the new type label part"
1267// );1250 );
1268// assert_eq!(1251 assert_eq!(
1269// popover.parsed_content.text,1252 popover.parsed_content.text,
1270// format!("A tooltip for `{new_type_label}`"),1253 format!("A tooltip for `{new_type_label}`"),
1271// "Rendered text should not anyhow alter backticks"1254 "Rendered text should not anyhow alter backticks"
1272// );1255 );
1273// });1256 });
1274 1257
1275// let struct_hint_part_hover_position = cx.update_editor(|editor, cx| {1258 let struct_hint_part_hover_position = cx.update_editor(|editor, cx| {
1276// let snapshot = editor.snapshot(cx);1259 let snapshot = editor.snapshot(cx);
1277// let previous_valid = inlay_range.start.to_display_point(&snapshot);1260 let previous_valid = inlay_range.start.to_display_point(&snapshot);
1278// let next_valid = inlay_range.end.to_display_point(&snapshot);1261 let next_valid = inlay_range.end.to_display_point(&snapshot);
1279// assert_eq!(previous_valid.row(), next_valid.row());1262 assert_eq!(previous_valid.row(), next_valid.row());
1280// assert!(previous_valid.column() < next_valid.column());1263 assert!(previous_valid.column() < next_valid.column());
1281// let exact_unclipped = DisplayPoint::new(1264 let exact_unclipped = DisplayPoint::new(
1282// previous_valid.row(),1265 previous_valid.row(),
1283// previous_valid.column()1266 previous_valid.column()
1284// + (entire_hint_label.find(struct_label).unwrap() + struct_label.len() / 2)1267 + (entire_hint_label.find(struct_label).unwrap() + struct_label.len() / 2)
1285// as u32,1268 as u32,
1286// );1269 );
1287// PointForPosition {1270 PointForPosition {
1288// previous_valid,1271 previous_valid,
1289// next_valid,1272 next_valid,
1290// exact_unclipped,1273 exact_unclipped,
1291// column_overshoot_after_line_end: 0,1274 column_overshoot_after_line_end: 0,
1292// }1275 }
1293// });1276 });
1294// cx.update_editor(|editor, cx| {1277 cx.update_editor(|editor, cx| {
1295// update_inlay_link_and_hover_points(1278 update_inlay_link_and_hover_points(
1296// &editor.snapshot(cx),1279 &editor.snapshot(cx),
1297// struct_hint_part_hover_position,1280 struct_hint_part_hover_position,
1298// editor,1281 editor,
1299// true,1282 true,
1300// false,1283 false,
1301// cx,1284 cx,
1302// );1285 );
1303// });1286 });
1304// cx.foreground()1287 cx.background_executor
1305// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));1288 .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
1306// cx.foreground().run_until_parked();1289 cx.background_executor.run_until_parked();
1307// cx.update_editor(|editor, cx| {1290 cx.update_editor(|editor, cx| {
1308// let hover_state = &editor.hover_state;1291 let hover_state = &editor.hover_state;
1309// assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());1292 assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
1310// let popover = hover_state.info_popover.as_ref().unwrap();1293 let popover = hover_state.info_popover.as_ref().unwrap();
1311// let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));1294 let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
1312// assert_eq!(1295 assert_eq!(
1313// popover.symbol_range,1296 popover.symbol_range,
1314// RangeInEditor::Inlay(InlayHighlight {1297 RangeInEditor::Inlay(InlayHighlight {
1315// inlay: InlayId::Hint(0),1298 inlay: InlayId::Hint(0),
1316// inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),1299 inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
1317// range: ": ".len() + new_type_label.len() + "<".len()1300 range: ": ".len() + new_type_label.len() + "<".len()
1318// ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(),1301 ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(),
1319// }),1302 }),
1320// "Popover range should match the struct label part"1303 "Popover range should match the struct label part"
1321// );1304 );
1322// assert_eq!(1305 assert_eq!(
1323// popover.parsed_content.text,1306 popover.parsed_content.text,
1324// format!("A tooltip for {struct_label}"),1307 format!("A tooltip for {struct_label}"),
1325// "Rendered markdown element should remove backticks from text"1308 "Rendered markdown element should remove backticks from text"
1326// );1309 );
1327// });1310 });
1328// }1311 }
1329// }1312}
crates/editor2/src/inlay_hint_cache.rs+4−2
@@ -861,7 +861,7 @@ async fn fetch_and_update_hints( · 860 unmodified lines
861 let inlay_hints_fetch_task = editor861 let inlay_hints_fetch_task = editor
862 .update(&mut cx, |editor, cx| {862 .update(&mut cx, |editor, cx| {
863 if got_throttled {863 if got_throttled {
864 let query_not_around_visible_range = match editor.excerpt_visible_offsets(None, cx).remove(&query.excerpt_id) {864 let query_not_around_visible_range = match editor.excerpts_for_inlay_hints_query(None, cx).remove(&query.excerpt_id) {
865 Some((_, _, current_visible_range)) => {865 Some((_, _, current_visible_range)) => {
866 let visible_offset_length = current_visible_range.len();866 let visible_offset_length = current_visible_range.len();
867 let double_visible_range = current_visible_range867 let double_visible_range = current_visible_range
@@ -2201,7 +2201,9 @@ pub mod tests { · 1333 unmodified lines
2201 cx: &mut gpui::TestAppContext,2201 cx: &mut gpui::TestAppContext,
2202 ) -> Range<Point> {2202 ) -> Range<Point> {
2203 let ranges = editor2203 let ranges = editor
2204 .update(cx, |editor, cx| editor.excerpt_visible_offsets(None, cx))2204 .update(cx, |editor, cx| {
2205 editor.excerpts_for_inlay_hints_query(None, cx)
2206 })
2205 .unwrap();2207 .unwrap();
2206 assert_eq!(2208 assert_eq!(
2207 ranges.len(),2209 ranges.len(),
crates/editor2/src/items.rs+23−27
@@ -9,8 +9,8 @@ use collections::HashSet; · 8 unmodified lines
9use futures::future::try_join_all;9use futures::future::try_join_all;
10use gpui::{10use gpui::{
11 div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter,11 div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter,
12 FocusHandle, Model, ParentComponent, Pixels, SharedString, Styled, Subscription, Task, View,12 FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View,
13 ViewContext, VisualContext, WeakView,13 ViewContext, VisualContext, WeakView, WindowContext,
14};14};
15use language::{15use language::{
16 proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,16 proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
@@ -30,7 +30,7 @@ use std::{ · 13 unmodified lines
30};30};
31use text::Selection;31use text::Selection;
32use theme::{ActiveTheme, Theme};32use theme::{ActiveTheme, Theme};
33use ui::{Label, TextColor};33use ui::{Color, Label};
34use util::{paths::PathExt, ResultExt, TryFutureExt};34use util::{paths::PathExt, ResultExt, TryFutureExt};
35use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};35use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
36use workspace::{36use workspace::{
@@ -584,7 +584,7 @@ impl Item for Editor { · 547 unmodified lines
584 Some(path.to_string_lossy().to_string().into())584 Some(path.to_string_lossy().to_string().into())
585 }585 }
586 586
587 fn tab_content<T: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<T> {587 fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement {
588 let theme = cx.theme();588 let theme = cx.theme();
589 589
590 AnyElement::new(590 AnyElement::new(
@@ -604,7 +604,7 @@ impl Item for Editor { · 13 unmodified lines
604 &description,604 &description,
605 MAX_TAB_TITLE_LEN,605 MAX_TAB_TITLE_LEN,
606 ))606 ))
607 .color(TextColor::Muted),607 .color(Color::Muted),
608 ),608 ),
609 )609 )
610 })),610 })),
@@ -761,7 +761,7 @@ impl Item for Editor { · 150 unmodified lines
761 }761 }
762 762
763 fn breadcrumb_location(&self) -> ToolbarItemLocation {763 fn breadcrumb_location(&self) -> ToolbarItemLocation {
764 ToolbarItemLocation::PrimaryLeft { flex: None }764 ToolbarItemLocation::PrimaryLeft
765 }765 }
766 766
767 fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {767 fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
@@ -907,17 +907,15 @@ impl SearchableItem for Editor { · 139 unmodified lines
907 type Match = Range<Anchor>;907 type Match = Range<Anchor>;
908 908
909 fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {909 fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
910 todo!()910 self.clear_background_highlights::<BufferSearchHighlights>(cx);
911 // self.clear_background_highlights::<BufferSearchHighlights>(cx);
912 }911 }
913 912
914 fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {913 fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {
915 todo!()914 self.highlight_background::<BufferSearchHighlights>(
916 // self.highlight_background::<BufferSearchHighlights>(915 matches,
917 // matches,916 |theme| theme.title_bar_background, // todo: update theme
918 // |theme| theme.search.match_background,917 cx,
919 // cx,918 );
920 // );
921 }919 }
922 920
923 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {921 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
@@ -952,22 +950,20 @@ impl SearchableItem for Editor { · 28 unmodified lines
952 matches: Vec<Range<Anchor>>,950 matches: Vec<Range<Anchor>>,
953 cx: &mut ViewContext<Self>,951 cx: &mut ViewContext<Self>,
954 ) {952 ) {
955 todo!()953 self.unfold_ranges([matches[index].clone()], false, true, cx);
956 // self.unfold_ranges([matches[index].clone()], false, true, cx);954 let range = self.range_for_match(&matches[index]);
957 // let range = self.range_for_match(&matches[index]);955 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
958 // self.change_selections(Some(Autoscroll::fit()), cx, |s| {956 s.select_ranges([range]);
959 // s.select_ranges([range]);957 })
960 // })
961 }958 }
962 959
963 fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {960 fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
964 todo!()961 self.unfold_ranges(matches.clone(), false, false, cx);
965 // self.unfold_ranges(matches.clone(), false, false, cx);962 let mut ranges = Vec::new();
966 // let mut ranges = Vec::new();963 for m in &matches {
967 // for m in &matches {964 ranges.push(self.range_for_match(&m))
968 // ranges.push(self.range_for_match(&m))965 }
969 // }966 self.change_selections(None, cx, |s| s.select_ranges(ranges));
970 // self.change_selections(None, cx, |s| s.select_ranges(ranges));
971 }967 }
972 fn replace(968 fn replace(
973 &mut self,969 &mut self,
crates/file_finder2/src/file_finder.rs+13−23
@@ -2,9 +2,8 @@ use collections::HashMap; · 1 unmodified line
2use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};2use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
3use fuzzy::{CharBag, PathMatch, PathMatchCandidate};3use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
4use gpui::{4use gpui::{
5 actions, div, AppContext, Component, Div, EventEmitter, FocusHandle, FocusableView,5 actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Model,
6 InteractiveComponent, Manager, Model, ParentComponent, Render, Styled, Task, View, ViewContext,6 ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
7 VisualContext, WeakView,
8};7};
9use picker::{Picker, PickerDelegate};8use picker::{Picker, PickerDelegate};
10use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};9use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
@@ -16,8 +15,7 @@ use std::{ · 5 unmodified lines
16 },15 },
17};16};
18use text::Point;17use text::Point;
19use theme::ActiveTheme;18use ui::{v_stack, HighlightedLabel, ListItem};
20use ui::{v_stack, HighlightedLabel, StyledExt};
21use util::{paths::PathLikeWithPosition, post_inc, ResultExt};19use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
22use workspace::Workspace;20use workspace::Workspace;
23 21
@@ -111,14 +109,14 @@ impl FileFinder { · 87 unmodified lines
111 }109 }
112}110}
113 111
114impl EventEmitter<Manager> for FileFinder {}112impl EventEmitter<DismissEvent> for FileFinder {}
115impl FocusableView for FileFinder {113impl FocusableView for FileFinder {
116 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {114 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
117 self.picker.focus_handle(cx)115 self.picker.focus_handle(cx)
118 }116 }
119}117}
120impl Render for FileFinder {118impl Render for FileFinder {
121 type Element = Div<Self>;119 type Element = Div;
122 120
123 fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {121 fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
124 v_stack().w_96().child(self.picker.clone())122 v_stack().w_96().child(self.picker.clone())
@@ -530,7 +528,7 @@ impl FileFinderDelegate { · 405 unmodified lines
530}528}
531 529
532impl PickerDelegate for FileFinderDelegate {530impl PickerDelegate for FileFinderDelegate {
533 type ListItem = Div<Picker<Self>>;531 type ListItem = ListItem;
534 532
535 fn placeholder_text(&self) -> Arc<str> {533 fn placeholder_text(&self) -> Arc<str> {
536 "Search project files...".into()534 "Search project files...".into()
@@ -690,7 +688,7 @@ impl PickerDelegate for FileFinderDelegate { · 153 unmodified lines
690 }688 }
691 }689 }
692 finder690 finder
693 .update(&mut cx, |_, cx| cx.emit(Manager::Dismiss))691 .update(&mut cx, |_, cx| cx.emit(DismissEvent::Dismiss))
694 .ok()?;692 .ok()?;
695 693
696 Some(())694 Some(())
@@ -702,7 +700,7 @@ impl PickerDelegate for FileFinderDelegate { · 5 unmodified lines
702 700
703 fn dismissed(&mut self, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {701 fn dismissed(&mut self, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
704 self.file_finder702 self.file_finder
705 .update(cx, |_, cx| cx.emit(Manager::Dismiss))703 .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
706 .log_err();704 .log_err();
707 }705 }
708 706
@@ -711,30 +709,22 @@ impl PickerDelegate for FileFinderDelegate { · 2 unmodified lines
711 ix: usize,709 ix: usize,
712 selected: bool,710 selected: bool,
713 cx: &mut ViewContext<Picker<Self>>,711 cx: &mut ViewContext<Picker<Self>>,
714 ) -> Self::ListItem {712 ) -> Option<Self::ListItem> {
715 let path_match = self713 let path_match = self
716 .matches714 .matches
717 .get(ix)715 .get(ix)
718 .expect("Invalid matches state: no element for index {ix}");716 .expect("Invalid matches state: no element for index {ix}");
719 let theme = cx.theme();
720 let colors = theme.colors();
721 717
722 let (file_name, file_name_positions, full_path, full_path_positions) =718 let (file_name, file_name_positions, full_path, full_path_positions) =
723 self.labels_for_match(path_match, cx, ix);719 self.labels_for_match(path_match, cx, ix);
724 720
725 div()721 Some(
726 .px_1()722 ListItem::new(ix).selected(selected).child(
727 .text_color(colors.text)
728 .text_ui()
729 .bg(colors.ghost_element_background)
730 .rounded_md()
731 .when(selected, |this| this.bg(colors.ghost_element_selected))
732 .hover(|this| this.bg(colors.ghost_element_hover))
733 .child(
734 v_stack()723 v_stack()
735 .child(HighlightedLabel::new(file_name, file_name_positions))724 .child(HighlightedLabel::new(file_name, file_name_positions))
736 .child(HighlightedLabel::new(full_path, full_path_positions)),725 .child(HighlightedLabel::new(full_path, full_path_positions)),
737 )726 ),
727 )
738 }728 }
739}729}
740 730
crates/fuzzy2/src/strings.rs+28−0
@@ -6,6 +6,8 @@ use gpui::BackgroundExecutor; · 5 unmodified lines
6use std::{6use std::{
7 borrow::Cow,7 borrow::Cow,
8 cmp::{self, Ordering},8 cmp::{self, Ordering},
9 iter,
10 ops::Range,
9 sync::atomic::AtomicBool,11 sync::atomic::AtomicBool,
10};12};
11 13
@@ -54,6 +56,32 @@ pub struct StringMatch { · 42 unmodified lines
54 pub string: String,56 pub string: String,
55}57}
56 58
59impl 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
57impl PartialEq for StringMatch {85impl PartialEq for StringMatch {
58 fn eq(&self, other: &Self) -> bool {86 fn eq(&self, other: &Self) -> bool {
59 self.cmp(other).is_eq()87 self.cmp(other).is_eq()
crates/go_to_line2/src/go_to_line.rs+22−21
@@ -1,14 +1,13 @@
1use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};1use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
2use gpui::{2use gpui::{
3 actions, div, prelude::*, AppContext, Div, EventEmitter, FocusHandle, FocusableView, Manager,3 actions, div, prelude::*, AppContext, DismissEvent, Div, EventEmitter, FocusHandle,
4 ParentComponent, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,4 FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
5 WindowContext,5 WindowContext,
6};6};
7use text::{Bias, Point};7use text::{Bias, Point};
8use theme::ActiveTheme;8use theme::ActiveTheme;
9use ui::{h_stack, v_stack, Label, StyledExt, TextColor};9use ui::{h_stack, v_stack, Color, Label, StyledExt};
10use util::paths::FILE_ROW_COLUMN_DELIMITER;10use util::paths::FILE_ROW_COLUMN_DELIMITER;
11use workspace::Workspace;
12 11
13actions!(Toggle);12actions!(Toggle);
14 13
@@ -26,22 +25,24 @@ pub struct GoToLine { · 11 unmodified lines
26 25
27impl FocusableView for GoToLine {26impl FocusableView for GoToLine {
28 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {27 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
29 self.active_editor.focus_handle(cx)28 self.line_editor.focus_handle(cx)
30 }29 }
31}30}
32impl EventEmitter<Manager> for GoToLine {}31impl EventEmitter<DismissEvent> for GoToLine {}
33 32
34impl GoToLine {33impl GoToLine {
35 fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {34 fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
36 workspace.register_action(|workspace, _: &Toggle, cx| {35 let handle = cx.view().downgrade();
37 let Some(editor) = workspace36 editor.register_action(move |_: &Toggle, cx| {
38 .active_item(cx)37 let Some(editor) = handle.upgrade() else {
39 .and_then(|active_item| active_item.downcast::<Editor>())
40 else {
41 return;38 return;
42 };39 };
43 40 let Some(workspace) = editor.read(cx).workspace() else {
44 workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx));41 return;
42 };
43 workspace.update(cx, |workspace, cx| {
44 workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx));
45 })
45 });46 });
46 }47 }
47 48
@@ -89,7 +90,7 @@ impl GoToLine { · 41 unmodified lines
89 ) {90 ) {
90 match event {91 match event {
91 // todo!() this isn't working...92 // todo!() this isn't working...
92 editor::EditorEvent::Blurred => cx.emit(Manager::Dismiss),93 editor::EditorEvent::Blurred => cx.emit(DismissEvent::Dismiss),
93 editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx),94 editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx),
94 _ => {}95 _ => {}
95 }96 }
@@ -124,7 +125,7 @@ impl GoToLine { · 28 unmodified lines
124 }125 }
125 126
126 fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {127 fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
127 cx.emit(Manager::Dismiss);128 cx.emit(DismissEvent::Dismiss);
128 }129 }
129 130
130 fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {131 fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
@@ -141,19 +142,19 @@ impl GoToLine { · 10 unmodified lines
141 self.prev_scroll_position.take();142 self.prev_scroll_position.take();
142 }143 }
143 144
144 cx.emit(Manager::Dismiss);145 cx.emit(DismissEvent::Dismiss);
145 }146 }
146}147}
147 148
148impl Render for GoToLine {149impl Render for GoToLine {
149 type Element = Div<Self>;150 type Element = Div;
150 151
151 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {152 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
152 div()153 div()
153 .elevation_2(cx)154 .elevation_2(cx)
154 .key_context("GoToLine")155 .key_context("GoToLine")
155 .on_action(Self::cancel)156 .on_action(cx.listener(Self::cancel))
156 .on_action(Self::confirm)157 .on_action(cx.listener(Self::confirm))
157 .w_96()158 .w_96()
158 .child(159 .child(
159 v_stack()160 v_stack()
@@ -177,7 +178,7 @@ impl Render for GoToLine { · 17 unmodified lines
177 .justify_between()178 .justify_between()
178 .px_2()179 .px_2()
179 .py_1()180 .py_1()
180 .child(Label::new(self.current_text.clone()).color(TextColor::Muted)),181 .child(Label::new(self.current_text.clone()).color(Color::Muted)),
181 ),182 ),
182 )183 )
183 }184 }
crates/gpui2/src/app.rs+27−8
@@ -10,11 +10,12 @@ pub use entity_map::*; · 9 unmodified lines
10pub use model_context::*;10pub use model_context::*;
11use refineable::Refineable;11use refineable::Refineable;
12use smallvec::SmallVec;12use smallvec::SmallVec;
13use smol::future::FutureExt;
13#[cfg(any(test, feature = "test-support"))]14#[cfg(any(test, feature = "test-support"))]
14pub use test_context::*;15pub use test_context::*;
15 16
16use crate::{17use crate::{
17 current_platform, image_cache::ImageCache, Action, ActionRegistry, AnyBox, AnyView,18 current_platform, image_cache::ImageCache, Action, ActionRegistry, Any, AnyView,
18 AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,19 AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
19 DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId,20 DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId,
20 ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform,21 ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform,
@@ -28,7 +29,7 @@ use futures::{channel::oneshot, future::LocalBoxFuture, Future}; · 7 unmodified lines
28use parking_lot::Mutex;29use parking_lot::Mutex;
29use slotmap::SlotMap;30use slotmap::SlotMap;
30use std::{31use std::{
31 any::{type_name, Any, TypeId},32 any::{type_name, TypeId},
32 cell::{Ref, RefCell, RefMut},33 cell::{Ref, RefCell, RefMut},
33 marker::PhantomData,34 marker::PhantomData,
34 mem,35 mem,
@@ -194,7 +195,7 @@ pub struct AppContext { · 159 unmodified lines
194 asset_source: Arc<dyn AssetSource>,195 asset_source: Arc<dyn AssetSource>,
195 pub(crate) image_cache: ImageCache,196 pub(crate) image_cache: ImageCache,
196 pub(crate) text_style_stack: Vec<TextStyleRefinement>,197 pub(crate) text_style_stack: Vec<TextStyleRefinement>,
197 pub(crate) globals_by_type: HashMap<TypeId, AnyBox>,198 pub(crate) globals_by_type: HashMap<TypeId, Box<dyn Any>>,
198 pub(crate) entities: EntityMap,199 pub(crate) entities: EntityMap,
199 pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,200 pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
200 pub(crate) windows: SlotMap<WindowId, Option<Window>>,201 pub(crate) windows: SlotMap<WindowId, Option<Window>>,
@@ -424,7 +425,7 @@ impl AppContext { · 223 unmodified lines
424 /// Opens a new window with the given option and the root view returned by the given function.425 /// Opens a new window with the given option and the root view returned by the given function.
425 /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific426 /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
426 /// functionality.427 /// functionality.
427 pub fn open_window<V: Render>(428 pub fn open_window<V: 'static + Render>(
428 &mut self,429 &mut self,
429 options: crate::WindowOptions,430 options: crate::WindowOptions,
430 build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,431 build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
@@ -579,7 +580,7 @@ impl AppContext { · 148 unmodified lines
579 .windows580 .windows
580 .iter()581 .iter()
581 .filter_map(|(_, window)| {582 .filter_map(|(_, window)| {
582 let window = window.as_ref().unwrap();583 let window = window.as_ref()?;
583 if window.dirty {584 if window.dirty {
584 Some(window.handle.clone())585 Some(window.handle.clone())
585 } else {586 } else {
@@ -983,6 +984,22 @@ impl AppContext { · 397 unmodified lines
983 pub fn all_action_names(&self) -> &[SharedString] {984 pub fn all_action_names(&self) -> &[SharedString] {
984 self.actions.all_action_names()985 self.actions.all_action_names()
985 }986 }
987
988 pub fn on_app_quit<Fut>(
989 &mut self,
990 mut on_quit: impl FnMut(&mut AppContext) -> Fut + 'static,
991 ) -> Subscription
992 where
993 Fut: 'static + Future<Output = ()>,
994 {
995 self.quit_observers.insert(
996 (),
997 Box::new(move |cx| {
998 let future = on_quit(cx);
999 async move { future.await }.boxed_local()
1000 }),
1001 )
1002 }
986}1003}
987 1004
988impl Context for AppContext {1005impl Context for AppContext {
@@ -1032,7 +1049,9 @@ impl Context for AppContext { · 43 unmodified lines
1032 let root_view = window.root_view.clone().unwrap();1049 let root_view = window.root_view.clone().unwrap();
1033 let result = update(root_view, &mut WindowContext::new(cx, &mut window));1050 let result = update(root_view, &mut WindowContext::new(cx, &mut window));
1034 1051
1035 if !window.removed {1052 if window.removed {
1053 cx.windows.remove(handle.id);
1054 } else {
1036 cx.windows1055 cx.windows
1037 .get_mut(handle.id)1056 .get_mut(handle.id)
1038 .ok_or_else(|| anyhow!("window not found"))?1057 .ok_or_else(|| anyhow!("window not found"))?
@@ -1104,12 +1123,12 @@ pub(crate) enum Effect { · 65 unmodified lines
1104 1123
1105/// Wraps a global variable value during `update_global` while the value has been moved to the stack.1124/// Wraps a global variable value during `update_global` while the value has been moved to the stack.
1106pub(crate) struct GlobalLease<G: 'static> {1125pub(crate) struct GlobalLease<G: 'static> {
1107 global: AnyBox,1126 global: Box<dyn Any>,
1108 global_type: PhantomData<G>,1127 global_type: PhantomData<G>,
1109}1128}
1110 1129
1111impl<G: 'static> GlobalLease<G> {1130impl<G: 'static> GlobalLease<G> {
1112 fn new(global: AnyBox) -> Self {1131 fn new(global: Box<dyn Any>) -> Self {
1113 GlobalLease {1132 GlobalLease {
1114 global,1133 global,
1115 global_type: PhantomData,1134 global_type: PhantomData,
crates/gpui2/src/app/async_context.rs+6−6
@@ -1,7 +1,7 @@
1use crate::{1use crate::{
2 AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, FocusableView,2 AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, DismissEvent,
3 ForegroundExecutor, Manager, Model, ModelContext, Render, Result, Task, View, ViewContext,3 FocusableView, ForegroundExecutor, Model, ModelContext, Render, Result, Task, View,
4 VisualContext, WindowContext, WindowHandle,4 ViewContext, VisualContext, WindowContext, WindowHandle,
5};5};
6use anyhow::{anyhow, Context as _};6use anyhow::{anyhow, Context as _};
7use derive_more::{Deref, DerefMut};7use derive_more::{Deref, DerefMut};
@@ -115,7 +115,7 @@ impl AsyncAppContext { · 107 unmodified lines
115 build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,115 build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
116 ) -> Result<WindowHandle<V>>116 ) -> Result<WindowHandle<V>>
117 where117 where
118 V: Render,118 V: 'static + Render,
119 {119 {
120 let app = self120 let app = self
121 .app121 .app
@@ -306,7 +306,7 @@ impl VisualContext for AsyncWindowContext { · 184 unmodified lines
306 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,306 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
307 ) -> Self::Result<View<V>>307 ) -> Self::Result<View<V>>
308 where308 where
309 V: Render,309 V: 'static + Render,
310 {310 {
311 self.window311 self.window
312 .update(self, |_, cx| cx.replace_root_view(build_view))312 .update(self, |_, cx| cx.replace_root_view(build_view))
@@ -326,7 +326,7 @@ impl VisualContext for AsyncWindowContext { · 13 unmodified lines
326 V: crate::ManagedView,326 V: crate::ManagedView,
327 {327 {
328 self.window.update(self, |_, cx| {328 self.window.update(self, |_, cx| {
329 view.update(cx, |_, cx| cx.emit(Manager::Dismiss))329 view.update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
330 })330 })
331 }331 }
332}332}
crates/gpui2/src/app/entity_map.rs+5−5
@@ -1,10 +1,10 @@
1use crate::{private::Sealed, AnyBox, AppContext, Context, Entity, ModelContext};1use crate::{private::Sealed, AppContext, Context, Entity, ModelContext};
2use anyhow::{anyhow, Result};2use anyhow::{anyhow, Result};
3use derive_more::{Deref, DerefMut};3use derive_more::{Deref, DerefMut};
4use parking_lot::{RwLock, RwLockUpgradableReadGuard};4use parking_lot::{RwLock, RwLockUpgradableReadGuard};
5use slotmap::{SecondaryMap, SlotMap};5use slotmap::{SecondaryMap, SlotMap};
6use std::{6use std::{
7 any::{type_name, TypeId},7 any::{type_name, Any, TypeId},
8 fmt::{self, Display},8 fmt::{self, Display},
9 hash::{Hash, Hasher},9 hash::{Hash, Hasher},
10 marker::PhantomData,10 marker::PhantomData,
@@ -31,7 +31,7 @@ impl Display for EntityId { · 20 unmodified lines
31}31}
32 32
33pub(crate) struct EntityMap {33pub(crate) struct EntityMap {
34 entities: SecondaryMap<EntityId, AnyBox>,34 entities: SecondaryMap<EntityId, Box<dyn Any>>,
35 ref_counts: Arc<RwLock<EntityRefCounts>>,35 ref_counts: Arc<RwLock<EntityRefCounts>>,
36}36}
37 37
@@ -102,7 +102,7 @@ impl EntityMap { · 64 unmodified lines
102 );102 );
103 }103 }
104 104
105 pub fn take_dropped(&mut self) -> Vec<(EntityId, AnyBox)> {105 pub fn take_dropped(&mut self) -> Vec<(EntityId, Box<dyn Any>)> {
106 let mut ref_counts = self.ref_counts.write();106 let mut ref_counts = self.ref_counts.write();
107 let dropped_entity_ids = mem::take(&mut ref_counts.dropped_entity_ids);107 let dropped_entity_ids = mem::take(&mut ref_counts.dropped_entity_ids);
108 108
@@ -122,7 +122,7 @@ impl EntityMap { · 13 unmodified lines
122}122}
123 123
124pub struct Lease<'a, T> {124pub struct Lease<'a, T> {
125 entity: Option<AnyBox>,125 entity: Option<Box<dyn Any>>,
126 pub model: &'a Model<T>,126 pub model: &'a Model<T>,
127 entity_type: PhantomData<T>,127 entity_type: PhantomData<T>,
128}128}
crates/gpui2/src/app/test_context.rs+14−15
@@ -1,8 +1,9 @@
1use crate::{1use crate::{
2 div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,2 div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
3 BackgroundExecutor, Context, Div, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent,3 BackgroundExecutor, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
4 Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, TestWindow,4 KeyDownEvent, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher,
5 View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,5 TestPlatform, TestWindow, View, ViewContext, VisualContext, WindowContext, WindowHandle,
6 WindowOptions,
6};7};
7use anyhow::{anyhow, bail};8use anyhow::{anyhow, bail};
8use futures::{Stream, StreamExt};9use futures::{Stream, StreamExt};
@@ -126,7 +127,7 @@ impl TestAppContext { · 117 unmodified lines
126 pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>127 pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
127 where128 where
128 F: FnOnce(&mut ViewContext<V>) -> V,129 F: FnOnce(&mut ViewContext<V>) -> V,
129 V: Render,130 V: 'static + Render,
130 {131 {
131 let mut cx = self.app.borrow_mut();132 let mut cx = self.app.borrow_mut();
132 cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))133 cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
@@ -143,7 +144,7 @@ impl TestAppContext { · 10 unmodified lines
143 pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)144 pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
144 where145 where
145 F: FnOnce(&mut ViewContext<V>) -> V,146 F: FnOnce(&mut ViewContext<V>) -> V,
146 V: Render,147 V: 'static + Render,
147 {148 {
148 let mut cx = self.app.borrow_mut();149 let mut cx = self.app.borrow_mut();
149 let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));150 let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));
@@ -296,21 +297,19 @@ impl TestAppContext { · 146 unmodified lines
296 .unwrap()297 .unwrap()
297 }298 }
298 299
299 pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {300 pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
300 let (tx, rx) = futures::channel::mpsc::unbounded();301 let (tx, rx) = futures::channel::mpsc::unbounded();
301 302 self.update(|cx| {
302 entity.update(self, move |_, cx: &mut ModelContext<T>| {
303 cx.observe(entity, {303 cx.observe(entity, {
304 let tx = tx.clone();304 let tx = tx.clone();
305 move |_, _, _| {305 move |_, _| {
306 let _ = tx.unbounded_send(());306 let _ = tx.unbounded_send(());
307 }307 }
308 })308 })
309 .detach();309 .detach();
310 310 cx.observe_release(entity, move |_, _| tx.close_channel())
311 cx.on_release(move |_, _| tx.close_channel()).detach();311 .detach()
312 });312 });
313
314 rx313 rx
315 }314 }
316 315
@@ -591,7 +590,7 @@ impl<'a> VisualContext for VisualTestContext<'a> { · 274 unmodified lines
591 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,590 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
592 ) -> Self::Result<View<V>>591 ) -> Self::Result<View<V>>
593 where592 where
594 V: Render,593 V: 'static + Render,
595 {594 {
596 self.window595 self.window
597 .update(self.cx, |_, cx| cx.replace_root_view(build_view))596 .update(self.cx, |_, cx| cx.replace_root_view(build_view))
@@ -612,7 +611,7 @@ impl<'a> VisualContext for VisualTestContext<'a> { · 14 unmodified lines
612 {611 {
613 self.window612 self.window
614 .update(self.cx, |_, cx| {613 .update(self.cx, |_, cx| {
615 view.update(cx, |_, cx| cx.emit(crate::Manager::Dismiss))614 view.update(cx, |_, cx| cx.emit(crate::DismissEvent::Dismiss))
616 })615 })
617 .unwrap()616 .unwrap()
618 }617 }
@@ -631,7 +630,7 @@ impl AnyWindowHandle { · 12 unmodified lines
631pub struct EmptyView {}630pub struct EmptyView {}
632 631
633impl Render for EmptyView {632impl Render for EmptyView {
634 type Element = Div<Self>;633 type Element = Div;
635 634
636 fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {635 fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
637 div()636 div()
crates/gpui2/src/element.rs+356−215
@@ -1,236 +1,328 @@
1use crate::{1use crate::{
2 AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, ViewContext,2 AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, ViewContext,
3 WindowContext,
3};4};
4use derive_more::{Deref, DerefMut};5use derive_more::{Deref, DerefMut};
5pub(crate) use smallvec::SmallVec;6pub(crate) use smallvec::SmallVec;
6use std::{any::Any, fmt::Debug, mem};7use std::{any::Any, fmt::Debug};
7 8
8pub trait Element<V: 'static> {9pub trait Render: 'static + Sized {
9 type ElementState: 'static;10 type Element: Element + 'static;
11
12 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element;
13}
14
15pub trait IntoElement: Sized {
16 type Element: Element + 'static;
10 17
11 fn element_id(&self) -> Option<ElementId>;18 fn element_id(&self) -> Option<ElementId>;
12 19
13 fn layout(20 fn into_element(self) -> Self::Element;
14 &mut self,
15 view_state: &mut V,
16 element_state: Option<Self::ElementState>,
17 cx: &mut ViewContext<V>,
18 ) -> (LayoutId, Self::ElementState);
19 21
20 fn paint(22 fn into_any_element(self) -> AnyElement {
21 &mut self,23 self.into_element().into_any()
22 bounds: Bounds<Pixels>,24 }
23 view_state: &mut V,
24 element_state: &mut Self::ElementState,
25 cx: &mut ViewContext<V>,
26 );
27 25
28 fn draw<T, R>(26 fn draw<T, R>(
29 self,27 self,
30 origin: Point<Pixels>,28 origin: Point<Pixels>,
31 available_space: Size<T>,29 available_space: Size<T>,
32 view_state: &mut V,30 cx: &mut WindowContext,
33 cx: &mut ViewContext<V>,31 f: impl FnOnce(&mut <Self::Element as Element>::State, &mut WindowContext) -> R,
34 f: impl FnOnce(&Self::ElementState, &mut ViewContext<V>) -> R,
35 ) -> R32 ) -> R
36 where33 where
37 Self: Sized,
38 T: Clone + Default + Debug + Into<AvailableSpace>,34 T: Clone + Default + Debug + Into<AvailableSpace>,
39 {35 {
40 let mut element = RenderedElement {36 let element = self.into_element();
41 element: self,37 let element_id = element.element_id();
42 phase: ElementRenderPhase::Start,38 let element = DrawableElement {
39 element: Some(element),
40 phase: ElementDrawPhase::Start,
43 };41 };
44 element.draw(origin, available_space.map(Into::into), view_state, cx);42
45 if let ElementRenderPhase::Painted { frame_state } = &element.phase {43 let frame_state =
46 if let Some(frame_state) = frame_state.as_ref() {44 DrawableElement::draw(element, origin, available_space.map(Into::into), cx);
47 f(&frame_state, cx)45
46 if let Some(mut frame_state) = frame_state {
47 f(&mut frame_state, cx)
48 } else {
49 cx.with_element_state(element_id.unwrap(), |element_state, cx| {
50 let mut element_state = element_state.unwrap();
51 let result = f(&mut element_state, cx);
52 (result, element_state)
53 })
54 }
55 }
56
57 fn map<U>(self, f: impl FnOnce(Self) -> U) -> U
58 where
59 Self: Sized,
60 U: IntoElement,
61 {
62 f(self)
63 }
64
65 fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
66 where
67 Self: Sized,
68 {
69 self.map(|this| if condition { then(this) } else { this })
70 }
71
72 fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
73 where
74 Self: Sized,
75 {
76 self.map(|this| {
77 if let Some(value) = option {
78 then(this, value)
48 } else {79 } else {
49 let element_id = element80 this
50 .element
51 .element_id()
52 .expect("we either have some frame_state or some element_id");
53 cx.with_element_state(element_id, |element_state, cx| {
54 let element_state = element_state.unwrap();
55 let result = f(&element_state, cx);
56 (result, element_state)
57 })
58 }81 }
59 } else {82 })
60 unreachable!()83 }
84}
85
86pub 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
102pub trait RenderOnce: 'static {
103 type Rendered: IntoElement;
104
105 fn render(self, cx: &mut WindowContext) -> Self::Rendered;
106}
107
108pub struct Component<C> {
109 component: Option<C>,
110}
111
112pub 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
117impl<C> Component<C> {
118 pub fn new(component: C) -> Self {
119 Component {
120 component: Some(component),
61 }121 }
62 }122 }
63}123}
64 124
125impl<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
151impl<C: RenderOnce> IntoElement for Component<C> {
152 type Element = Self;
153
154 fn element_id(&self) -> Option<ElementId> {
155 None
156 }
157
158 fn into_element(self) -> Self::Element {
159 self
160 }
161}
162
65#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]163#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
66pub struct GlobalElementId(SmallVec<[ElementId; 32]>);164pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
67 165
68pub trait ParentComponent<V: 'static> {166pub trait ParentElement {
69 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;167 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]>;
70 168
71 fn child(mut self, child: impl Component<V>) -> Self169 fn child(mut self, child: impl IntoElement) -> Self
72 where170 where
73 Self: Sized,171 Self: Sized,
74 {172 {
75 self.children_mut().push(child.render());173 self.children_mut().push(child.into_element().into_any());
76 self174 self
77 }175 }
78 176
79 fn children(mut self, iter: impl IntoIterator<Item = impl Component<V>>) -> Self177 fn children(mut self, children: impl IntoIterator<Item = impl IntoElement>) -> Self
80 where178 where
81 Self: Sized,179 Self: Sized,
82 {180 {
83 self.children_mut()181 self.children_mut()
84 .extend(iter.into_iter().map(|item| item.render()));182 .extend(children.into_iter().map(|child| child.into_any_element()));
85 self183 self
86 }184 }
87}185}
88 186
89trait ElementObject<V> {187trait ElementObject {
90 fn element_id(&self) -> Option<ElementId>;188 fn element_id(&self) -> Option<ElementId>;
91 fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId;189
92 fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);190 fn layout(&mut self, cx: &mut WindowContext) -> LayoutId;
191
192 fn paint(&mut self, cx: &mut WindowContext);
193
93 fn measure(194 fn measure(
94 &mut self,195 &mut self,
95 available_space: Size<AvailableSpace>,196 available_space: Size<AvailableSpace>,
96 view_state: &mut V,197 cx: &mut WindowContext,
97 cx: &mut ViewContext<V>,
98 ) -> Size<Pixels>;198 ) -> Size<Pixels>;
199
99 fn draw(200 fn draw(
100 &mut self,201 &mut self,
101 origin: Point<Pixels>,202 origin: Point<Pixels>,
102 available_space: Size<AvailableSpace>,203 available_space: Size<AvailableSpace>,
103 view_state: &mut V,204 cx: &mut WindowContext,
104 cx: &mut ViewContext<V>,
105 );205 );
106}206}
107 207
108struct RenderedElement<V: 'static, E: Element<V>> {208pub struct DrawableElement<E: Element> {
109 element: E,209 element: Option<E>,
110 phase: ElementRenderPhase<E::ElementState>,210 phase: ElementDrawPhase<E::State>,
111}211}
112 212
113#[derive(Default)]213#[derive(Default)]
114enum ElementRenderPhase<V> {214enum ElementDrawPhase<S> {
115 #[default]215 #[default]
116 Start,216 Start,
117 LayoutRequested {217 LayoutRequested {
118 layout_id: LayoutId,218 layout_id: LayoutId,
119 frame_state: Option<V>,219 frame_state: Option<S>,
120 },220 },
121 LayoutComputed {221 LayoutComputed {
122 layout_id: LayoutId,222 layout_id: LayoutId,
123 available_space: Size<AvailableSpace>,223 available_space: Size<AvailableSpace>,
124 frame_state: Option<V>,224 frame_state: Option<S>,
125 },
126 Painted {
127 frame_state: Option<V>,
128 },225 },
129}226}
130 227
131/// Internal struct that wraps an element to store Layout and ElementState after the element is rendered.228/// A wrapper around an implementer of [Element] that allows it to be drawn in a window.
132/// It's allocated as a trait object to erase the element type and wrapped in AnyElement<E::State> for229impl<E: Element> DrawableElement<E> {
133/// improved usability.
134impl<V, E: Element<V>> RenderedElement<V, E> {
135 fn new(element: E) -> Self {230 fn new(element: E) -> Self {
136 RenderedElement {231 DrawableElement {
137 element,232 element: Some(element),
138 phase: ElementRenderPhase::Start,233 phase: ElementDrawPhase::Start,
139 }234 }
140 }235 }
141}
142 236
143impl<V, E> ElementObject<V> for RenderedElement<V, E>
144where
145 E: Element<V>,
146 E::ElementState: 'static,
147{
148 fn element_id(&self) -> Option<ElementId> {237 fn element_id(&self) -> Option<ElementId> {
149 self.element.element_id()238 self.element.as_ref()?.element_id()
150 }239 }
151 240
152 fn layout(&mut self, state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {241 fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
153 let (layout_id, frame_state) = match mem::take(&mut self.phase) {242 let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id()
154 ElementRenderPhase::Start => {243 {
155 if let Some(id) = self.element.element_id() {244 let layout_id = cx.with_element_state(id, |element_state, cx| {
156 let layout_id = cx.with_element_state(id, |element_state, cx| {245 self.element.as_mut().unwrap().layout(element_state, cx)
157 self.element.layout(state, element_state, cx)246 });
158 });247 (layout_id, None)
159 (layout_id, None)248 } else {
160 } else {249 let (layout_id, frame_state) = self.element.as_mut().unwrap().layout(None, cx);
161 let (layout_id, frame_state) = self.element.layout(state, None, cx);250 (layout_id, Some(frame_state))
162 (layout_id, Some(frame_state))
163 }
164 }
165 ElementRenderPhase::LayoutRequested { .. }
166 | ElementRenderPhase::LayoutComputed { .. }
167 | ElementRenderPhase::Painted { .. } => {
168 panic!("element rendered twice")
169 }
170 };251 };
171 252
172 self.phase = ElementRenderPhase::LayoutRequested {253 self.phase = ElementDrawPhase::LayoutRequested {
173 layout_id,254 layout_id,
174 frame_state,255 frame_state,
175 };256 };
176 layout_id257 layout_id
177 }258 }
178 259
179 fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {260 fn paint(mut self, cx: &mut WindowContext) -> Option<E::State> {
180 self.phase = match mem::take(&mut self.phase) {261 match self.phase {
181 ElementRenderPhase::LayoutRequested {262 ElementDrawPhase::LayoutRequested {
182 layout_id,263 layout_id,
183 mut frame_state,264 frame_state,
184 }265 }
185 | ElementRenderPhase::LayoutComputed {266 | ElementDrawPhase::LayoutComputed {
186 layout_id,267 layout_id,
187 mut frame_state,268 frame_state,
188 ..269 ..
189 } => {270 } => {
190 let bounds = cx.layout_bounds(layout_id);271 let bounds = cx.layout_bounds(layout_id);
191 if let Some(id) = self.element.element_id() {272
192 cx.with_element_state(id, |element_state, cx| {273 if let Some(mut frame_state) = frame_state {
274 self.element
275 .take()
276 .unwrap()
277 .paint(bounds, &mut frame_state, cx);
278 Some(frame_state)
279 } else {
280 let element_id = self
281 .element
282 .as_ref()
283 .unwrap()
284 .element_id()
285 .expect("if we don't have frame state, we should have element state");
286 cx.with_element_state(element_id, |element_state, cx| {
193 let mut element_state = element_state.unwrap();287 let mut element_state = element_state.unwrap();
194 self.element288 self.element
195 .paint(bounds, view_state, &mut element_state, cx);289 .take()
290 .unwrap()
291 .paint(bounds, &mut element_state, cx);
196 ((), element_state)292 ((), element_state)
197 });293 });
198 } else {294 None
199 self.element
200 .paint(bounds, view_state, frame_state.as_mut().unwrap(), cx);
201 }295 }
202 ElementRenderPhase::Painted { frame_state }
203 }296 }
204 297
205 _ => panic!("must call layout before paint"),298 _ => panic!("must call layout before paint"),
206 };299 }
207 }300 }
208 301
209 fn measure(302 fn measure(
210 &mut self,303 &mut self,
211 available_space: Size<AvailableSpace>,304 available_space: Size<AvailableSpace>,
212 view_state: &mut V,305 cx: &mut WindowContext,
213 cx: &mut ViewContext<V>,
214 ) -> Size<Pixels> {306 ) -> Size<Pixels> {
215 if matches!(&self.phase, ElementRenderPhase::Start) {307 if matches!(&self.phase, ElementDrawPhase::Start) {
216 self.layout(view_state, cx);308 self.layout(cx);
217 }309 }
218 310
219 let layout_id = match &mut self.phase {311 let layout_id = match &mut self.phase {
220 ElementRenderPhase::LayoutRequested {312 ElementDrawPhase::LayoutRequested {
221 layout_id,313 layout_id,
222 frame_state,314 frame_state,
223 } => {315 } => {
224 cx.compute_layout(*layout_id, available_space);316 cx.compute_layout(*layout_id, available_space);
225 let layout_id = *layout_id;317 let layout_id = *layout_id;
226 self.phase = ElementRenderPhase::LayoutComputed {318 self.phase = ElementDrawPhase::LayoutComputed {
227 layout_id,319 layout_id,
228 available_space,320 available_space,
229 frame_state: frame_state.take(),321 frame_state: frame_state.take(),
230 };322 };
231 layout_id323 layout_id
232 }324 }
233 ElementRenderPhase::LayoutComputed {325 ElementDrawPhase::LayoutComputed {
234 layout_id,326 layout_id,
235 available_space: prev_available_space,327 available_space: prev_available_space,
236 ..328 ..
@@ -248,154 +340,203 @@ where · 11 unmodified lines
248 }340 }
249 341
250 fn draw(342 fn draw(
251 &mut self,343 mut self,
252 origin: Point<Pixels>,344 origin: Point<Pixels>,
253 available_space: Size<AvailableSpace>,345 available_space: Size<AvailableSpace>,
254 view_state: &mut V,346 cx: &mut WindowContext,
255 cx: &mut ViewContext<V>,347 ) -> Option<E::State> {
256 ) {348 self.measure(available_space, cx);
257 self.measure(available_space, view_state, cx);349 cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
258 cx.with_absolute_element_offset(origin, |cx| self.paint(view_state, cx))
259 }350 }
260}351}
261 352
262pub struct AnyElement<V>(Box<dyn ElementObject<V>>);353// impl<V: 'static, E: Element> Element for DrawableElement<V, E> {
354// type State = <E::Element as Element>::State;
263 355
264impl<V> AnyElement<V> {356// fn layout(
265 pub fn new<E>(element: E) -> Self357// &mut self,
266 where358// element_state: Option<Self::State>,
267 V: 'static,359// cx: &mut WindowContext,
268 E: 'static + Element<V>,360// ) -> (LayoutId, Self::State) {
269 E::ElementState: Any,
270 {
271 AnyElement(Box::new(RenderedElement::new(element)))
272 }
273 361
274 pub fn element_id(&self) -> Option<ElementId> {362// }
275 self.0.element_id()363
364// fn paint(
365// self,
366// bounds: Bounds<Pixels>,
367// element_state: &mut Self::State,
368// cx: &mut WindowContext,
369// ) {
370// todo!()
371// }
372// }
373
374// impl<V: 'static, E: 'static + Element> RenderOnce for DrawableElement<V, E> {
375// type Element = Self;
376
377// fn element_id(&self) -> Option<ElementId> {
378// self.element.as_ref()?.element_id()
379// }
380
381// fn render_once(self) -> Self::Element {
382// self
383// }
384// }
385
386impl<E> ElementObject for Option<DrawableElement<E>>
387where
388 E: Element,
389 E::State: 'static,
390{
391 fn element_id(&self) -> Option<ElementId> {
392 self.as_ref().unwrap().element_id()
276 }393 }
277 394
278 pub fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {395 fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
279 self.0.layout(view_state, cx)396 DrawableElement::layout(self.as_mut().unwrap(), cx)
280 }397 }
281 398
282 pub fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {399 fn paint(&mut self, cx: &mut WindowContext) {
283 self.0.paint(view_state, cx)400 DrawableElement::paint(self.take().unwrap(), cx);
284 }401 }
285 402
286 /// Initializes this element and performs layout within the given available space to determine its size.403 fn measure(
287 pub fn measure(
288 &mut self,404 &mut self,
289 available_space: Size<AvailableSpace>,405 available_space: Size<AvailableSpace>,
290 view_state: &mut V,406 cx: &mut WindowContext,
291 cx: &mut ViewContext<V>,
292 ) -> Size<Pixels> {407 ) -> Size<Pixels> {
293 self.0.measure(available_space, view_state, cx)408 DrawableElement::measure(self.as_mut().unwrap(), available_space, cx)
294 }409 }
295 410
296 /// Initializes this element and performs layout in the available space, then paints it at the given origin.411 fn draw(
297 pub fn draw(
298 &mut self,412 &mut self,
299 origin: Point<Pixels>,413 origin: Point<Pixels>,
300 available_space: Size<AvailableSpace>,414 available_space: Size<AvailableSpace>,
301 view_state: &mut V,415 cx: &mut WindowContext,
302 cx: &mut ViewContext<V>,
303 ) {416 ) {
304 self.0.draw(origin, available_space, view_state, cx)417 DrawableElement::draw(self.take().unwrap(), origin, available_space, cx);
305 }418 }
306}419}
307 420
308pub trait Component<V> {421pub struct AnyElement(Box<dyn ElementObject>);
309 fn render(self) -> AnyElement<V>;
310 422
311 fn map<U>(self, f: impl FnOnce(Self) -> U) -> U423impl AnyElement {
424 pub fn new<E>(element: E) -> Self
312 where425 where
313 Self: Sized,426 E: 'static + Element,
314 U: Component<V>,427 E::State: Any,
315 {428 {
316 f(self)429 AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject>)
317 }430 }
318 431
319 fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self432 pub fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
320 where433 self.0.layout(cx)
321 Self: Sized,
322 {
323 self.map(|this| if condition { then(this) } else { this })
324 }434 }
325 435
326 fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self436 pub fn paint(mut self, cx: &mut WindowContext) {
327 where437 self.0.paint(cx)
328 Self: Sized,
329 {
330 self.map(|this| {
331 if let Some(value) = option {
332 then(this, value)
333 } else {
334 this
335 }
336 })
337 }438 }
338}
339 439
340impl<V> Component<V> for AnyElement<V> {440 /// Initializes this element and performs layout within the given available space to determine its size.
341 fn render(self) -> AnyElement<V> {441 pub fn measure(
342 self442 &mut self,
443 available_space: Size<AvailableSpace>,
444 cx: &mut WindowContext,
445 ) -> Size<Pixels> {
446 self.0.measure(available_space, cx)
343 }447 }
344}
345 448
346impl<V, E, F> Element<V> for Option<F>449 /// Initializes this element and performs layout in the available space, then paints it at the given origin.
347where450 pub fn draw(
348 V: 'static,451 mut self,
349 E: 'static + Component<V>,452 origin: Point<Pixels>,
350 F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,453 available_space: Size<AvailableSpace>,
351{454 cx: &mut WindowContext,
352 type ElementState = AnyElement<V>;455 ) {
456 self.0.draw(origin, available_space, cx)
457 }
353 458
354 fn element_id(&self) -> Option<ElementId> {459 /// Converts this `AnyElement` into a trait object that can be stored and manipulated.
355 None460 pub fn into_any(self) -> AnyElement {
461 AnyElement::new(self)
356 }462 }
357 463
464 pub fn inner_id(&self) -> Option<ElementId> {
465 self.0.element_id()
466 }
467}
468
469impl Element for AnyElement {
470 type State = ();
471
358 fn layout(472 fn layout(
359 &mut self,473 &mut self,
360 view_state: &mut V,474 _: Option<Self::State>,
361 _: Option<Self::ElementState>,475 cx: &mut WindowContext,
362 cx: &mut ViewContext<V>,476 ) -> (LayoutId, Self::State) {
363 ) -> (LayoutId, Self::ElementState) {477 let layout_id = self.layout(cx);
364 let render = self.take().unwrap();478 (layout_id, ())
365 let mut rendered_element = (render)(view_state, cx).render();
366 let layout_id = rendered_element.layout(view_state, cx);
367 (layout_id, rendered_element)
368 }479 }
369 480
370 fn paint(481 fn paint(self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut WindowContext) {
371 &mut self,482 self.paint(cx);
372 _bounds: Bounds<Pixels>,
373 view_state: &mut V,
374 rendered_element: &mut Self::ElementState,
375 cx: &mut ViewContext<V>,
376 ) {
377 rendered_element.paint(view_state, cx)
378 }483 }
379}484}
380 485
381impl<V, E, F> Component<V> for Option<F>486impl IntoElement for AnyElement {
382where487 type Element = Self;
383 V: 'static,488
384 E: 'static + Component<V>,489 fn element_id(&self) -> Option<ElementId> {
385 F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,490 None
386{
387 fn render(self) -> AnyElement<V> {
388 AnyElement::new(self)
389 }491 }
390}
391 492
392impl<V, E, F> Component<V> for F493 fn into_element(self) -> Self::Element {
393where494 self
394 V: 'static,
395 E: 'static + Component<V>,
396 F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
397{
398 fn render(self) -> AnyElement<V> {
399 AnyElement::new(Some(self))
400 }495 }
401}496}
497
498// impl<V, E, F> Element for Option<F>
499// where
500// V: 'static,
501// E: Element,
502// F: FnOnce(&mut V, &mut WindowContext<'_, V>) -> E + 'static,
503// {
504// type State = Option<AnyElement>;
505
506// fn element_id(&self) -> Option<ElementId> {
507// None
508// }
509
510// fn layout(
511// &mut self,
512// _: Option<Self::State>,
513// cx: &mut WindowContext,
514// ) -> (LayoutId, Self::State) {
515// let render = self.take().unwrap();
516// let mut element = (render)(view_state, cx).into_any();
517// let layout_id = element.layout(view_state, cx);
518// (layout_id, Some(element))
519// }
520
521// fn paint(
522// self,
523// _bounds: Bounds<Pixels>,
524// rendered_element: &mut Self::State,
525// cx: &mut WindowContext,
526// ) {
527// rendered_element.take().unwrap().paint(view_state, cx);
528// }
529// }
530
531// impl<V, E, F> RenderOnce for Option<F>
532// where
533// V: 'static,
534// E: Element,
535// F: FnOnce(&mut V, &mut WindowContext) -> E + 'static,
536// {
537// type Element = Self;
538
539// fn render(self) -> Self::Element {
540// self
541// }
542// }
crates/gpui2/src/elements/div.rs+290−342
@@ -1,9 +1,9 @@
1use crate::{1use crate::{
2 point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext,2 point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext,
3 BorrowWindow, Bounds, ClickEvent, Component, DispatchPhase, Element, ElementId, FocusEvent,3 BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle,
4 FocusHandle, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,4 IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
5 MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels, Point, Render, ScrollWheelEvent,5 MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent,
6 SharedString, Size, Style, StyleRefinement, Styled, Task, View, ViewContext, Visibility,6 SharedString, Size, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext,
7};7};
8use collections::HashMap;8use collections::HashMap;
9use refineable::Refineable;9use refineable::Refineable;
@@ -12,7 +12,6 @@ use std::{ · 2 unmodified lines
12 any::{Any, TypeId},12 any::{Any, TypeId},
13 cell::RefCell,13 cell::RefCell,
14 fmt::Debug,14 fmt::Debug,
15 marker::PhantomData,
16 mem,15 mem,
17 rc::Rc,16 rc::Rc,
18 time::Duration,17 time::Duration,
@@ -28,30 +27,24 @@ pub struct GroupStyle { · 9 unmodified lines
28 pub style: StyleRefinement,27 pub style: StyleRefinement,
29}28}
30 29
31pub trait InteractiveComponent<V: 'static>: Sized + Element<V> {30pub trait InteractiveElement: Sized + Element {
32 fn interactivity(&mut self) -> &mut Interactivity<V>;31 fn interactivity(&mut self) -> &mut Interactivity;
33 32
34 fn group(mut self, group: impl Into<SharedString>) -> Self {33 fn group(mut self, group: impl Into<SharedString>) -> Self {
35 self.interactivity().group = Some(group.into());34 self.interactivity().group = Some(group.into());
36 self35 self
37 }36 }
38 37
39 fn id(mut self, id: impl Into<ElementId>) -> Stateful<V, Self> {38 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
40 self.interactivity().element_id = Some(id.into());39 self.interactivity().element_id = Some(id.into());
41 40
42 Stateful {41 Stateful { element: self }
43 element: self,
44 view_type: PhantomData,
45 }
46 }42 }
47 43
48 fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<V, Self> {44 fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<Self> {
49 self.interactivity().focusable = true;45 self.interactivity().focusable = true;
50 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());46 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
51 Focusable {47 Focusable { element: self }
52 element: self,
53 view_type: PhantomData,
54 }
55 }48 }
56 49
57 fn key_context<C, E>(mut self, key_context: C) -> Self50 fn key_context<C, E>(mut self, key_context: C) -> Self
@@ -85,15 +78,15 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 27 unmodified lines
85 fn on_mouse_down(78 fn on_mouse_down(
86 mut self,79 mut self,
87 button: MouseButton,80 button: MouseButton,
88 handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,81 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
89 ) -> Self {82 ) -> Self {
90 self.interactivity().mouse_down_listeners.push(Box::new(83 self.interactivity().mouse_down_listeners.push(Box::new(
91 move |view, event, bounds, phase, cx| {84 move |event, bounds, phase, cx| {
92 if phase == DispatchPhase::Bubble85 if phase == DispatchPhase::Bubble
93 && event.button == button86 && event.button == button
94 && bounds.contains_point(&event.position)87 && bounds.contains_point(&event.position)
95 {88 {
96 handler(view, event, cx)89 (listener)(event, cx)
97 }90 }
98 },91 },
99 ));92 ));
@@ -102,12 +95,12 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 2 unmodified lines
102 95
103 fn on_any_mouse_down(96 fn on_any_mouse_down(
104 mut self,97 mut self,
105 handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,98 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
106 ) -> Self {99 ) -> Self {
107 self.interactivity().mouse_down_listeners.push(Box::new(100 self.interactivity().mouse_down_listeners.push(Box::new(
108 move |view, event, bounds, phase, cx| {101 move |event, bounds, phase, cx| {
109 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {102 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
110 handler(view, event, cx)103 (listener)(event, cx)
111 }104 }
112 },105 },
113 ));106 ));
@@ -117,43 +110,43 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 3 unmodified lines
117 fn on_mouse_up(110 fn on_mouse_up(
118 mut self,111 mut self,
119 button: MouseButton,112 button: MouseButton,
120 handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,113 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
121 ) -> Self {114 ) -> Self {
122 self.interactivity().mouse_up_listeners.push(Box::new(115 self.interactivity()
123 move |view, event, bounds, phase, cx| {116 .mouse_up_listeners
117 .push(Box::new(move |event, bounds, phase, cx| {
124 if phase == DispatchPhase::Bubble118 if phase == DispatchPhase::Bubble
125 && event.button == button119 && event.button == button
126 && bounds.contains_point(&event.position)120 && bounds.contains_point(&event.position)
127 {121 {
128 handler(view, event, cx)122 (listener)(event, cx)
129 }123 }
130 },124 }));
131 ));
132 self125 self
133 }126 }
134 127
135 fn on_any_mouse_up(128 fn on_any_mouse_up(
136 mut self,129 mut self,
137 handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,130 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
138 ) -> Self {131 ) -> Self {
139 self.interactivity().mouse_up_listeners.push(Box::new(132 self.interactivity()
140 move |view, event, bounds, phase, cx| {133 .mouse_up_listeners
134 .push(Box::new(move |event, bounds, phase, cx| {
141 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {135 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
142 handler(view, event, cx)136 (listener)(event, cx)
143 }137 }
144 },138 }));
145 ));
146 self139 self
147 }140 }
148 141
149 fn on_mouse_down_out(142 fn on_mouse_down_out(
150 mut self,143 mut self,
151 handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,144 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
152 ) -> Self {145 ) -> Self {
153 self.interactivity().mouse_down_listeners.push(Box::new(146 self.interactivity().mouse_down_listeners.push(Box::new(
154 move |view, event, bounds, phase, cx| {147 move |event, bounds, phase, cx| {
155 if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) {148 if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) {
156 handler(view, event, cx)149 (listener)(event, cx)
157 }150 }
158 },151 },
159 ));152 ));
@@ -163,29 +156,29 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 3 unmodified lines
163 fn on_mouse_up_out(156 fn on_mouse_up_out(
164 mut self,157 mut self,
165 button: MouseButton,158 button: MouseButton,
166 handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,159 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
167 ) -> Self {160 ) -> Self {
168 self.interactivity().mouse_up_listeners.push(Box::new(161 self.interactivity()
169 move |view, event, bounds, phase, cx| {162 .mouse_up_listeners
163 .push(Box::new(move |event, bounds, phase, cx| {
170 if phase == DispatchPhase::Capture164 if phase == DispatchPhase::Capture
171 && event.button == button165 && event.button == button
172 && !bounds.contains_point(&event.position)166 && !bounds.contains_point(&event.position)
173 {167 {
174 handler(view, event, cx);168 (listener)(event, cx);
175 }169 }
176 },170 }));
177 ));
178 self171 self
179 }172 }
180 173
181 fn on_mouse_move(174 fn on_mouse_move(
182 mut self,175 mut self,
183 handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + 'static,176 listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
184 ) -> Self {177 ) -> Self {
185 self.interactivity().mouse_move_listeners.push(Box::new(178 self.interactivity().mouse_move_listeners.push(Box::new(
186 move |view, event, bounds, phase, cx| {179 move |event, bounds, phase, cx| {
187 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {180 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
188 handler(view, event, cx);181 (listener)(event, cx);
189 }182 }
190 },183 },
191 ));184 ));
@@ -194,29 +187,29 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 2 unmodified lines
194 187
195 fn on_scroll_wheel(188 fn on_scroll_wheel(
196 mut self,189 mut self,
197 handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + 'static,190 listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
198 ) -> Self {191 ) -> Self {
199 self.interactivity().scroll_wheel_listeners.push(Box::new(192 self.interactivity().scroll_wheel_listeners.push(Box::new(
200 move |view, event, bounds, phase, cx| {193 move |event, bounds, phase, cx| {
201 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {194 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
202 handler(view, event, cx);195 (listener)(event, cx);
203 }196 }
204 },197 },
205 ));198 ));
206 self199 self
207 }200 }
208 201
209 /// Capture the given action, fires during the capture phase202 /// Capture the given action, before normal action dispatch can fire
210 fn capture_action<A: Action>(203 fn capture_action<A: Action>(
211 mut self,204 mut self,
212 listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,205 listener: impl Fn(&A, &mut WindowContext) + 'static,
213 ) -> Self {206 ) -> Self {
214 self.interactivity().action_listeners.push((207 self.interactivity().action_listeners.push((
215 TypeId::of::<A>(),208 TypeId::of::<A>(),
216 Box::new(move |view, action, phase, cx| {209 Box::new(move |action, phase, cx| {
217 let action = action.downcast_ref().unwrap();210 let action = action.downcast_ref().unwrap();
218 if phase == DispatchPhase::Capture {211 if phase == DispatchPhase::Capture {
219 listener(view, action, cx)212 (listener)(action, cx)
220 }213 }
221 }),214 }),
222 ));215 ));
@@ -224,10 +217,7 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 1 unmodified line
224 }217 }
225 218
226 /// Add a listener for the given action, fires during the bubble event phase219 /// Add a listener for the given action, fires during the bubble event phase
227 fn on_action<A: Action>(220 fn on_action<A: Action>(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self {
228 mut self,
229 listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
230 ) -> Self {
231 // NOTE: this debug assert has the side-effect of working around221 // NOTE: this debug assert has the side-effect of working around
232 // a bug where a crate consisting only of action definitions does222 // a bug where a crate consisting only of action definitions does
233 // not register the actions in debug builds:223 // not register the actions in debug builds:
@@ -244,10 +234,10 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 10 unmodified lines
244 // );234 // );
245 self.interactivity().action_listeners.push((235 self.interactivity().action_listeners.push((
246 TypeId::of::<A>(),236 TypeId::of::<A>(),
247 Box::new(move |view, action, phase, cx| {237 Box::new(move |action, phase, cx| {
248 let action = action.downcast_ref().unwrap();238 let action = action.downcast_ref().unwrap();
249 if phase == DispatchPhase::Bubble {239 if phase == DispatchPhase::Bubble {
250 listener(view, action, cx)240 (listener)(action, cx)
251 }241 }
252 }),242 }),
253 ));243 ));
@@ -256,24 +246,53 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 2 unmodified lines
256 246
257 fn on_key_down(247 fn on_key_down(
258 mut self,248 mut self,
259 listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static,249 listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
260 ) -> Self {250 ) -> Self {
261 self.interactivity()251 self.interactivity()
262 .key_down_listeners252 .key_down_listeners
263 .push(Box::new(move |view, event, phase, cx| {253 .push(Box::new(move |event, phase, cx| {
264 listener(view, event, phase, cx)254 if phase == DispatchPhase::Bubble {
255 (listener)(event, cx)
256 }
265 }));257 }));
266 self258 self
267 }259 }
268 260
269 fn on_key_up(261 fn capture_key_down(
270 mut self,262 mut self,
271 listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static,263 listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
272 ) -> Self {264 ) -> Self {
265 self.interactivity()
266 .key_down_listeners
267 .push(Box::new(move |event, phase, cx| {
268 if phase == DispatchPhase::Capture {
269 listener(event, cx)
270 }
271 }));
272 self
273 }
274
275 fn on_key_up(mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) -> Self {
273 self.interactivity()276 self.interactivity()
274 .key_up_listeners277 .key_up_listeners
275 .push(Box::new(move |view, event, phase, cx| {278 .push(Box::new(move |event, phase, cx| {
276 listener(view, event, phase, cx)279 if phase == DispatchPhase::Bubble {
280 listener(event, cx)
281 }
282 }));
283 self
284 }
285
286 fn capture_key_up(
287 mut self,
288 listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static,
289 ) -> Self {
290 self.interactivity()
291 .key_up_listeners
292 .push(Box::new(move |event, phase, cx| {
293 if phase == DispatchPhase::Capture {
294 listener(event, cx)
295 }
277 }));296 }));
278 self297 self
279 }298 }
@@ -302,25 +321,22 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 22 unmodified lines
302 321
303 fn on_drop<W: 'static>(322 fn on_drop<W: 'static>(
304 mut self,323 mut self,
305 listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + 'static,324 listener: impl Fn(&View<W>, &mut WindowContext) + 'static,
306 ) -> Self {325 ) -> Self {
307 self.interactivity().drop_listeners.push((326 self.interactivity().drop_listeners.push((
308 TypeId::of::<W>(),327 TypeId::of::<W>(),
309 Box::new(move |view, dragged_view, cx| {328 Box::new(move |dragged_view, cx| {
310 listener(view, dragged_view.downcast().unwrap(), cx);329 listener(&dragged_view.downcast().unwrap(), cx);
311 }),330 }),
312 ));331 ));
313 self332 self
314 }333 }
315}334}
316 335
317pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveComponent<V> {336pub trait StatefulInteractiveElement: InteractiveElement {
318 fn focusable(mut self) -> Focusable<V, Self> {337 fn focusable(mut self) -> Focusable<Self> {
319 self.interactivity().focusable = true;338 self.interactivity().focusable = true;
320 Focusable {339 Focusable { element: self }
321 element: self,
322 view_type: PhantomData,
323 }
324 }340 }
325 341
326 fn overflow_scroll(mut self) -> Self {342 fn overflow_scroll(mut self) -> Self {
@@ -362,23 +378,17 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo · 35 unmodified lines
362 self378 self
363 }379 }
364 380
365 fn on_click(381 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self
366 mut self,
367 listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static,
368 ) -> Self
369 where382 where
370 Self: Sized,383 Self: Sized,
371 {384 {
372 self.interactivity()385 self.interactivity()
373 .click_listeners386 .click_listeners
374 .push(Box::new(move |view, event, cx| listener(view, event, cx)));387 .push(Box::new(move |event, cx| listener(event, cx)));
375 self388 self
376 }389 }
377 390
378 fn on_drag<W>(391 fn on_drag<W>(mut self, listener: impl Fn(&mut WindowContext) -> View<W> + 'static) -> Self
379 mut self,
380 listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
381 ) -> Self
382 where392 where
383 Self: Sized,393 Self: Sized,
384 W: 'static + Render,394 W: 'static + Render,
@@ -387,15 +397,14 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo · 2 unmodified lines
387 self.interactivity().drag_listener.is_none(),397 self.interactivity().drag_listener.is_none(),
388 "calling on_drag more than once on the same element is not supported"398 "calling on_drag more than once on the same element is not supported"
389 );399 );
390 self.interactivity().drag_listener =400 self.interactivity().drag_listener = Some(Box::new(move |cursor_offset, cx| AnyDrag {
391 Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {401 view: listener(cx).into(),
392 view: listener(view_state, cx).into(),402 cursor_offset,
393 cursor_offset,403 }));
394 }));
395 self404 self
396 }405 }
397 406
398 fn on_hover(mut self, listener: impl 'static + Fn(&mut V, bool, &mut ViewContext<V>)) -> Self407 fn on_hover(mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static) -> Self
399 where408 where
400 Self: Sized,409 Self: Sized,
401 {410 {
@@ -407,10 +416,7 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo · 5 unmodified lines
407 self416 self
408 }417 }
409 418
410 fn tooltip(419 fn tooltip(mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self
411 mut self,
412 build_tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static,
413 ) -> Self
414 where420 where
415 Self: Sized,421 Self: Sized,
416 {422 {
@@ -418,14 +424,13 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo · 1 unmodified line
418 self.interactivity().tooltip_builder.is_none(),424 self.interactivity().tooltip_builder.is_none(),
419 "calling tooltip more than once on the same element is not supported"425 "calling tooltip more than once on the same element is not supported"
420 );426 );
421 self.interactivity().tooltip_builder =427 self.interactivity().tooltip_builder = Some(Rc::new(build_tooltip));
422 Some(Rc::new(move |view_state, cx| build_tooltip(view_state, cx)));
423 428
424 self429 self
425 }430 }
426}431}
427 432
428pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> {433pub trait FocusableElement: InteractiveElement {
429 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self434 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
430 where435 where
431 Self: Sized,436 Self: Sized,
@@ -442,49 +447,41 @@ pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> { · 10 unmodified lines
442 self447 self
443 }448 }
444 449
445 fn on_focus(450 fn on_focus(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
446 mut self,
447 listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
448 ) -> Self
449 where451 where
450 Self: Sized,452 Self: Sized,
451 {453 {
452 self.interactivity().focus_listeners.push(Box::new(454 self.interactivity()
453 move |view, focus_handle, event, cx| {455 .focus_listeners
456 .push(Box::new(move |focus_handle, event, cx| {
454 if event.focused.as_ref() == Some(focus_handle) {457 if event.focused.as_ref() == Some(focus_handle) {
455 listener(view, event, cx)458 listener(event, cx)
456 }459 }
457 },460 }));
458 ));
459 self461 self
460 }462 }
461 463
462 fn on_blur(464 fn on_blur(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
463 mut self,
464 listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
465 ) -> Self
466 where465 where
467 Self: Sized,466 Self: Sized,
468 {467 {
469 self.interactivity().focus_listeners.push(Box::new(468 self.interactivity()
470 move |view, focus_handle, event, cx| {469 .focus_listeners
470 .push(Box::new(move |focus_handle, event, cx| {
471 if event.blurred.as_ref() == Some(focus_handle) {471 if event.blurred.as_ref() == Some(focus_handle) {
472 listener(view, event, cx)472 listener(event, cx)
473 }473 }
474 },474 }));
475 ));
476 self475 self
477 }476 }
478 477
479 fn on_focus_in(478 fn on_focus_in(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
480 mut self,
481 listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
482 ) -> Self
483 where479 where
484 Self: Sized,480 Self: Sized,
485 {481 {
486 self.interactivity().focus_listeners.push(Box::new(482 self.interactivity()
487 move |view, focus_handle, event, cx| {483 .focus_listeners
484 .push(Box::new(move |focus_handle, event, cx| {
488 let descendant_blurred = event485 let descendant_blurred = event
489 .blurred486 .blurred
490 .as_ref()487 .as_ref()
@@ -495,22 +492,19 @@ pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> { · 4 unmodified lines
495 .map_or(false, |focused| focus_handle.contains(focused, cx));492 .map_or(false, |focused| focus_handle.contains(focused, cx));
496 493
497 if !descendant_blurred && descendant_focused {494 if !descendant_blurred && descendant_focused {
498 listener(view, event, cx)495 listener(event, cx)
499 }496 }
500 },497 }));
501 ));
502 self498 self
503 }499 }
504 500
505 fn on_focus_out(501 fn on_focus_out(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
506 mut self,
507 listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
508 ) -> Self
509 where502 where
510 Self: Sized,503 Self: Sized,
511 {504 {
512 self.interactivity().focus_listeners.push(Box::new(505 self.interactivity()
513 move |view, focus_handle, event, cx| {506 .focus_listeners
507 .push(Box::new(move |focus_handle, event, cx| {
514 let descendant_blurred = event508 let descendant_blurred = event
515 .blurred509 .blurred
516 .as_ref()510 .as_ref()
@@ -520,98 +514,80 @@ pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> { · 3 unmodified lines
520 .as_ref()514 .as_ref()
521 .map_or(false, |focused| focus_handle.contains(focused, cx));515 .map_or(false, |focused| focus_handle.contains(focused, cx));
522 if descendant_blurred && !descendant_focused {516 if descendant_blurred && !descendant_focused {
523 listener(view, event, cx)517 listener(event, cx)
524 }518 }
525 },519 }));
526 ));
527 self520 self
528 }521 }
529}522}
530 523
531pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;524pub type FocusListeners = SmallVec<[FocusListener; 2]>;
532
533pub type FocusListener<V> =
534 Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
535 525
536pub type MouseDownListener<V> = Box<526pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
537 dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
538>;
539pub type MouseUpListener<V> = Box<
540 dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
541>;
542 527
543pub type MouseMoveListener<V> = Box<528pub type MouseDownListener =
544 dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,529 Box<dyn Fn(&MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
545>;530pub type MouseUpListener =
531 Box<dyn Fn(&MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
546 532
547pub type ScrollWheelListener<V> = Box<533pub type MouseMoveListener =
548 dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)534 Box<dyn Fn(&MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
549 + 'static,
550>;
551 535
552pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static>;536pub type ScrollWheelListener =
537 Box<dyn Fn(&ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
553 538
554pub type DragListener<V> =539pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
555 Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
556 540
557type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;541pub type DragListener = Box<dyn Fn(Point<Pixels>, &mut WindowContext) -> AnyDrag + 'static>;
558 542
559pub type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>) + 'static>;543type DropListener = dyn Fn(AnyView, &mut WindowContext) + 'static;
560 544
561pub type TooltipBuilder<V> = Rc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;545pub type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>;
562 546
563pub type KeyDownListener<V> =547pub type KeyDownListener = Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>;
564 Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
565 548
566pub type KeyUpListener<V> =549pub type KeyUpListener = Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut WindowContext) + 'static>;
567 Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
568 550
569pub type ActionListener<V> =551pub type ActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
570 Box<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static>;
571 552
572pub fn div<V: 'static>() -> Div<V> {553pub fn div() -> Div {
573 Div {554 Div {
574 interactivity: Interactivity::default(),555 interactivity: Interactivity::default(),
575 children: SmallVec::default(),556 children: SmallVec::default(),
576 }557 }
577}558}
578 559
579pub struct Div<V> {560pub struct Div {
580 interactivity: Interactivity<V>,561 interactivity: Interactivity,
581 children: SmallVec<[AnyElement<V>; 2]>,562 children: SmallVec<[AnyElement; 2]>,
582}563}
583 564
584impl<V> Styled for Div<V> {565impl Styled for Div {
585 fn style(&mut self) -> &mut StyleRefinement {566 fn style(&mut self) -> &mut StyleRefinement {
586 &mut self.interactivity.base_style567 &mut self.interactivity.base_style
587 }568 }
588}569}
589 570
590impl<V: 'static> InteractiveComponent<V> for Div<V> {571impl InteractiveElement for Div {
591 fn interactivity(&mut self) -> &mut Interactivity<V> {572 fn interactivity(&mut self) -> &mut Interactivity {
592 &mut self.interactivity573 &mut self.interactivity
593 }574 }
594}575}
595 576
596impl<V: 'static> ParentComponent<V> for Div<V> {577impl ParentElement for Div {
597 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {578 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
598 &mut self.children579 &mut self.children
599 }580 }
600}581}
601 582
602impl<V: 'static> Element<V> for Div<V> {583impl Element for Div {
603 type ElementState = DivState;584 type State = DivState;
604
605 fn element_id(&self) -> Option<ElementId> {
606 self.interactivity.element_id.clone()
607 }
608 585
609 fn layout(586 fn layout(
610 &mut self,587 &mut self,
611 view_state: &mut V,588 element_state: Option<Self::State>,
612 element_state: Option<Self::ElementState>,589 cx: &mut WindowContext,
613 cx: &mut ViewContext<V>,590 ) -> (LayoutId, Self::State) {
614 ) -> (LayoutId, Self::ElementState) {
615 let mut child_layout_ids = SmallVec::new();591 let mut child_layout_ids = SmallVec::new();
616 let mut interactivity = mem::take(&mut self.interactivity);592 let mut interactivity = mem::take(&mut self.interactivity);
617 let (layout_id, interactive_state) = interactivity.layout(593 let (layout_id, interactive_state) = interactivity.layout(
@@ -622,7 +598,7 @@ impl<V: 'static> Element<V> for Div<V> { · 4 unmodified lines
622 child_layout_ids = self598 child_layout_ids = self
623 .children599 .children
624 .iter_mut()600 .iter_mut()
625 .map(|child| child.layout(view_state, cx))601 .map(|child| child.layout(cx))
626 .collect::<SmallVec<_>>();602 .collect::<SmallVec<_>>();
627 cx.request_layout(&style, child_layout_ids.iter().copied())603 cx.request_layout(&style, child_layout_ids.iter().copied())
628 })604 })
@@ -639,11 +615,10 @@ impl<V: 'static> Element<V> for Div<V> { · 10 unmodified lines
639 }615 }
640 616
641 fn paint(617 fn paint(
642 &mut self,618 self,
643 bounds: Bounds<Pixels>,619 bounds: Bounds<Pixels>,
644 view_state: &mut V,620 element_state: &mut Self::State,
645 element_state: &mut Self::ElementState,621 cx: &mut WindowContext,
646 cx: &mut ViewContext<V>,
647 ) {622 ) {
648 let mut child_min = point(Pixels::MAX, Pixels::MAX);623 let mut child_min = point(Pixels::MAX, Pixels::MAX);
649 let mut child_max = Point::default();624 let mut child_max = Point::default();
@@ -658,8 +633,7 @@ impl<V: 'static> Element<V> for Div<V> { · 8 unmodified lines
658 (child_max - child_min).into()633 (child_max - child_min).into()
659 };634 };
660 635
661 let mut interactivity = mem::take(&mut self.interactivity);636 self.interactivity.paint(
662 interactivity.paint(
663 bounds,637 bounds,
664 content_size,638 content_size,
665 &mut element_state.interactive_state,639 &mut element_state.interactive_state,
@@ -679,8 +653,8 @@ impl<V: 'static> Element<V> for Div<V> { · 13 unmodified lines
679 cx.with_text_style(style.text_style().cloned(), |cx| {653 cx.with_text_style(style.text_style().cloned(), |cx| {
680 cx.with_content_mask(style.overflow_mask(bounds), |cx| {654 cx.with_content_mask(style.overflow_mask(bounds), |cx| {
681 cx.with_element_offset(scroll_offset, |cx| {655 cx.with_element_offset(scroll_offset, |cx| {
682 for child in &mut self.children {656 for child in self.children {
683 child.paint(view_state, cx);657 child.paint(cx);
684 }658 }
685 })659 })
686 })660 })
@@ -689,13 +663,18 @@ impl<V: 'static> Element<V> for Div<V> { · 2 unmodified lines
689 })663 })
690 },664 },
691 );665 );
692 self.interactivity = interactivity;
693 }666 }
694}667}
695 668
696impl<V: 'static> Component<V> for Div<V> {669impl IntoElement for Div {
697 fn render(self) -> AnyElement<V> {670 type Element = Self;
698 AnyElement::new(self)671
672 fn element_id(&self) -> Option<ElementId> {
673 self.interactivity.element_id.clone()
674 }
675
676 fn into_element(self) -> Self::Element {
677 self
699 }678 }
700}679}
701 680
@@ -710,12 +689,12 @@ impl DivState { · 8 unmodified lines
710 }689 }
711}690}
712 691
713pub struct Interactivity<V> {692pub struct Interactivity {
714 pub element_id: Option<ElementId>,693 pub element_id: Option<ElementId>,
715 pub key_context: KeyContext,694 pub key_context: KeyContext,
716 pub focusable: bool,695 pub focusable: bool,
717 pub tracked_focus_handle: Option<FocusHandle>,696 pub tracked_focus_handle: Option<FocusHandle>,
718 pub focus_listeners: FocusListeners<V>,697 pub focus_listeners: FocusListeners,
719 pub group: Option<SharedString>,698 pub group: Option<SharedString>,
720 pub base_style: StyleRefinement,699 pub base_style: StyleRefinement,
721 pub focus_style: StyleRefinement,700 pub focus_style: StyleRefinement,
@@ -726,29 +705,26 @@ pub struct Interactivity<V> { · 4 unmodified lines
726 pub group_active_style: Option<GroupStyle>,705 pub group_active_style: Option<GroupStyle>,
727 pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,706 pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
728 pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,707 pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
729 pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,708 pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>,
730 pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,709 pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>,
731 pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,710 pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>,
732 pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,711 pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener; 2]>,
733 pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,712 pub key_down_listeners: SmallVec<[KeyDownListener; 2]>,
734 pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,713 pub key_up_listeners: SmallVec<[KeyUpListener; 2]>,
735 pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,714 pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>,
736 pub drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,715 pub drop_listeners: SmallVec<[(TypeId, Box<DropListener>); 2]>,
737 pub click_listeners: SmallVec<[ClickListener<V>; 2]>,716 pub click_listeners: SmallVec<[ClickListener; 2]>,
738 pub drag_listener: Option<DragListener<V>>,717 pub drag_listener: Option<DragListener>,
739 pub hover_listener: Option<HoverListener<V>>,718 pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
740 pub tooltip_builder: Option<TooltipBuilder<V>>,719 pub tooltip_builder: Option<TooltipBuilder>,
741}720}
742 721
743impl<V> Interactivity<V>722impl Interactivity {
744where
745 V: 'static,
746{
747 pub fn layout(723 pub fn layout(
748 &mut self,724 &mut self,
749 element_state: Option<InteractiveElementState>,725 element_state: Option<InteractiveElementState>,
750 cx: &mut ViewContext<V>,726 cx: &mut WindowContext,
751 f: impl FnOnce(Style, &mut ViewContext<V>) -> LayoutId,727 f: impl FnOnce(Style, &mut WindowContext) -> LayoutId,
752 ) -> (LayoutId, InteractiveElementState) {728 ) -> (LayoutId, InteractiveElementState) {
753 let mut element_state = element_state.unwrap_or_default();729 let mut element_state = element_state.unwrap_or_default();
754 730
@@ -770,12 +746,12 @@ where · 15 unmodified lines
770 }746 }
771 747
772 pub fn paint(748 pub fn paint(
773 &mut self,749 mut self,
774 bounds: Bounds<Pixels>,750 bounds: Bounds<Pixels>,
775 content_size: Size<Pixels>,751 content_size: Size<Pixels>,
776 element_state: &mut InteractiveElementState,752 element_state: &mut InteractiveElementState,
777 cx: &mut ViewContext<V>,753 cx: &mut WindowContext,
778 f: impl FnOnce(Style, Point<Pixels>, &mut ViewContext<V>),754 f: impl FnOnce(Style, Point<Pixels>, &mut WindowContext),
779 ) {755 ) {
780 let style = self.compute_style(Some(bounds), element_state, cx);756 let style = self.compute_style(Some(bounds), element_state, cx);
781 757
@@ -787,26 +763,26 @@ where · 5 unmodified lines
787 }763 }
788 764
789 for listener in self.mouse_down_listeners.drain(..) {765 for listener in self.mouse_down_listeners.drain(..) {
790 cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {766 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
791 listener(state, event, &bounds, phase, cx);767 listener(event, &bounds, phase, cx);
792 })768 })
793 }769 }
794 770
795 for listener in self.mouse_up_listeners.drain(..) {771 for listener in self.mouse_up_listeners.drain(..) {
796 cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {772 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
797 listener(state, event, &bounds, phase, cx);773 listener(event, &bounds, phase, cx);
798 })774 })
799 }775 }
800 776
801 for listener in self.mouse_move_listeners.drain(..) {777 for listener in self.mouse_move_listeners.drain(..) {
802 cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {778 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
803 listener(state, event, &bounds, phase, cx);779 listener(event, &bounds, phase, cx);
804 })780 })
805 }781 }
806 782
807 for listener in self.scroll_wheel_listeners.drain(..) {783 for listener in self.scroll_wheel_listeners.drain(..) {
808 cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {784 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
809 listener(state, event, &bounds, phase, cx);785 listener(event, &bounds, phase, cx);
810 })786 })
811 }787 }
812 788
@@ -817,7 +793,7 @@ where · 4 unmodified lines
817 793
818 if let Some(group_bounds) = hover_group_bounds {794 if let Some(group_bounds) = hover_group_bounds {
819 let hovered = group_bounds.contains_point(&cx.mouse_position());795 let hovered = group_bounds.contains_point(&cx.mouse_position());
820 cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {796 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
821 if phase == DispatchPhase::Capture {797 if phase == DispatchPhase::Capture {
822 if group_bounds.contains_point(&event.position) != hovered {798 if group_bounds.contains_point(&event.position) != hovered {
823 cx.notify();799 cx.notify();
@@ -830,7 +806,7 @@ where · 6 unmodified lines
830 || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())806 || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
831 {807 {
832 let hovered = bounds.contains_point(&cx.mouse_position());808 let hovered = bounds.contains_point(&cx.mouse_position());
833 cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {809 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
834 if phase == DispatchPhase::Capture {810 if phase == DispatchPhase::Capture {
835 if bounds.contains_point(&event.position) != hovered {811 if bounds.contains_point(&event.position) != hovered {
836 cx.notify();812 cx.notify();
@@ -841,7 +817,7 @@ where · 4 unmodified lines
841 817
842 if cx.active_drag.is_some() {818 if cx.active_drag.is_some() {
843 let drop_listeners = mem::take(&mut self.drop_listeners);819 let drop_listeners = mem::take(&mut self.drop_listeners);
844 cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {820 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
845 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {821 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
846 if let Some(drag_state_type) =822 if let Some(drag_state_type) =
847 cx.active_drag.as_ref().map(|drag| drag.view.entity_type())823 cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
@@ -852,7 +828,7 @@ where · 4 unmodified lines
852 .active_drag828 .active_drag
853 .take()829 .take()
854 .expect("checked for type drag state type above");830 .expect("checked for type drag state type above");
855 listener(view, drag.view.clone(), cx);831 listener(drag.view.clone(), cx);
856 cx.notify();832 cx.notify();
857 cx.stop_propagation();833 cx.stop_propagation();
858 }834 }
@@ -872,7 +848,7 @@ where · 13 unmodified lines
872 if let Some(drag_listener) = drag_listener {848 if let Some(drag_listener) = drag_listener {
873 let active_state = element_state.clicked_state.clone();849 let active_state = element_state.clicked_state.clone();
874 850
875 cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {851 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
876 if cx.active_drag.is_some() {852 if cx.active_drag.is_some() {
877 if phase == DispatchPhase::Capture {853 if phase == DispatchPhase::Capture {
878 cx.notify();854 cx.notify();
@@ -883,7 +859,7 @@ where · 4 unmodified lines
883 {859 {
884 *active_state.borrow_mut() = ElementClickedState::default();860 *active_state.borrow_mut() = ElementClickedState::default();
885 let cursor_offset = event.position - bounds.origin;861 let cursor_offset = event.position - bounds.origin;
886 let drag = drag_listener(view_state, cursor_offset, cx);862 let drag = drag_listener(cursor_offset, cx);
887 cx.active_drag = Some(drag);863 cx.active_drag = Some(drag);
888 cx.notify();864 cx.notify();
889 cx.stop_propagation();865 cx.stop_propagation();
@@ -891,21 +867,21 @@ where · 1 unmodified line
891 });867 });
892 }868 }
893 869
894 cx.on_mouse_event(move |view_state, event: &MouseUpEvent, phase, cx| {870 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
895 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {871 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
896 let mouse_click = ClickEvent {872 let mouse_click = ClickEvent {
897 down: mouse_down.clone(),873 down: mouse_down.clone(),
898 up: event.clone(),874 up: event.clone(),
899 };875 };
900 for listener in &click_listeners {876 for listener in &click_listeners {
901 listener(view_state, &mouse_click, cx);877 listener(&mouse_click, cx);
902 }878 }
903 }879 }
904 *pending_mouse_down.borrow_mut() = None;880 *pending_mouse_down.borrow_mut() = None;
905 cx.notify();881 cx.notify();
906 });882 });
907 } else {883 } else {
908 cx.on_mouse_event(move |_view_state, event: &MouseDownEvent, phase, cx| {884 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
909 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {885 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
910 *pending_mouse_down.borrow_mut() = Some(event.clone());886 *pending_mouse_down.borrow_mut() = Some(event.clone());
911 cx.notify();887 cx.notify();
@@ -918,7 +894,7 @@ where · 6 unmodified lines
918 let was_hovered = element_state.hover_state.clone();894 let was_hovered = element_state.hover_state.clone();
919 let has_mouse_down = element_state.pending_mouse_down.clone();895 let has_mouse_down = element_state.pending_mouse_down.clone();
920 896
921 cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {897 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
922 if phase != DispatchPhase::Bubble {898 if phase != DispatchPhase::Bubble {
923 return;899 return;
924 }900 }
@@ -930,7 +906,7 @@ where · 5 unmodified lines
930 *was_hovered = is_hovered;906 *was_hovered = is_hovered;
931 drop(was_hovered);907 drop(was_hovered);
932 908
933 hover_listener(view_state, is_hovered, cx);909 hover_listener(&is_hovered, cx);
934 }910 }
935 });911 });
936 }912 }
@@ -939,7 +915,7 @@ where · 2 unmodified lines
939 let active_tooltip = element_state.active_tooltip.clone();915 let active_tooltip = element_state.active_tooltip.clone();
940 let pending_mouse_down = element_state.pending_mouse_down.clone();916 let pending_mouse_down = element_state.pending_mouse_down.clone();
941 917
942 cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {918 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
943 if phase != DispatchPhase::Bubble {919 if phase != DispatchPhase::Bubble {
944 return;920 return;
945 }921 }
@@ -956,12 +932,12 @@ where · 10 unmodified lines
956 let active_tooltip = active_tooltip.clone();932 let active_tooltip = active_tooltip.clone();
957 let tooltip_builder = tooltip_builder.clone();933 let tooltip_builder = tooltip_builder.clone();
958 934
959 move |view, mut cx| async move {935 move |mut cx| async move {
960 cx.background_executor().timer(TOOLTIP_DELAY).await;936 cx.background_executor().timer(TOOLTIP_DELAY).await;
961 view.update(&mut cx, move |view_state, cx| {937 cx.update(|_, cx| {
962 active_tooltip.borrow_mut().replace(ActiveTooltip {938 active_tooltip.borrow_mut().replace(ActiveTooltip {
963 tooltip: Some(AnyTooltip {939 tooltip: Some(AnyTooltip {
964 view: tooltip_builder(view_state, cx),940 view: tooltip_builder(cx),
965 cursor_offset: cx.mouse_position(),941 cursor_offset: cx.mouse_position(),
966 }),942 }),
967 _task: None,943 _task: None,
@@ -979,7 +955,7 @@ where · 11 unmodified lines
979 });955 });
980 956
981 let active_tooltip = element_state.active_tooltip.clone();957 let active_tooltip = element_state.active_tooltip.clone();
982 cx.on_mouse_event(move |_, _: &MouseDownEvent, _, _| {958 cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
983 active_tooltip.borrow_mut().take();959 active_tooltip.borrow_mut().take();
984 });960 });
985 961
@@ -992,7 +968,7 @@ where · 6 unmodified lines
992 968
993 let active_state = element_state.clicked_state.clone();969 let active_state = element_state.clicked_state.clone();
994 if !active_state.borrow().is_clicked() {970 if !active_state.borrow().is_clicked() {
995 cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {971 cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| {
996 if phase == DispatchPhase::Capture {972 if phase == DispatchPhase::Capture {
997 *active_state.borrow_mut() = ElementClickedState::default();973 *active_state.borrow_mut() = ElementClickedState::default();
998 cx.notify();974 cx.notify();
@@ -1003,7 +979,7 @@ where · 4 unmodified lines
1003 .group_active_style979 .group_active_style
1004 .as_ref()980 .as_ref()
1005 .and_then(|group_active| GroupBounds::get(&group_active.group, cx));981 .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
1006 cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {982 cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
1007 if phase == DispatchPhase::Bubble {983 if phase == DispatchPhase::Bubble {
1008 let group = active_group_bounds984 let group = active_group_bounds
1009 .map_or(false, |bounds| bounds.contains_point(&down.position));985 .map_or(false, |bounds| bounds.contains_point(&down.position));
@@ -1025,7 +1001,7 @@ where · 15 unmodified lines
1025 let line_height = cx.line_height();1001 let line_height = cx.line_height();
1026 let scroll_max = (content_size - bounds.size).max(&Size::default());1002 let scroll_max = (content_size - bounds.size).max(&Size::default());
1027 1003
1028 cx.on_mouse_event(move |_, event: &ScrollWheelEvent, phase, cx| {1004 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
1029 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {1005 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
1030 let mut scroll_offset = scroll_offset.borrow_mut();1006 let mut scroll_offset = scroll_offset.borrow_mut();
1031 let old_scroll_offset = *scroll_offset;1007 let old_scroll_offset = *scroll_offset;
@@ -1063,27 +1039,25 @@ where · 31 unmodified lines
1063 element_state.focus_handle.clone(),1039 element_state.focus_handle.clone(),
1064 |_, cx| {1040 |_, cx| {
1065 for listener in self.key_down_listeners.drain(..) {1041 for listener in self.key_down_listeners.drain(..) {
1066 cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| {1042 cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
1067 listener(state, event, phase, cx);1043 listener(event, phase, cx);
1068 })1044 })
1069 }1045 }
1070 1046
1071 for listener in self.key_up_listeners.drain(..) {1047 for listener in self.key_up_listeners.drain(..) {
1072 cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| {1048 cx.on_key_event(move |event: &KeyUpEvent, phase, cx| {
1073 listener(state, event, phase, cx);1049 listener(event, phase, cx);
1074 })1050 })
1075 }1051 }
1076 1052
1077 for (action_type, listener) in self.action_listeners.drain(..) {1053 for (action_type, listener) in self.action_listeners {
1078 cx.on_action(action_type, listener)1054 cx.on_action(action_type, listener)
1079 }1055 }
1080 1056
1081 if let Some(focus_handle) = element_state.focus_handle.as_ref() {1057 if let Some(focus_handle) = element_state.focus_handle.as_ref() {
1082 for listener in self.focus_listeners.drain(..) {1058 for listener in self.focus_listeners {
1083 let focus_handle = focus_handle.clone();1059 let focus_handle = focus_handle.clone();
1084 cx.on_focus_changed(move |view, event, cx| {1060 cx.on_focus_changed(move |event, cx| listener(&focus_handle, event, cx));
1085 listener(view, &focus_handle, event, cx)
1086 });
1087 }1061 }
1088 }1062 }
1089 1063
@@ -1100,7 +1074,7 @@ where · 10 unmodified lines
1100 &self,1074 &self,
1101 bounds: Option<Bounds<Pixels>>,1075 bounds: Option<Bounds<Pixels>>,
1102 element_state: &mut InteractiveElementState,1076 element_state: &mut InteractiveElementState,
1103 cx: &mut ViewContext<V>,1077 cx: &mut WindowContext,
1104 ) -> Style {1078 ) -> Style {
1105 let mut style = Style::default();1079 let mut style = Style::default();
1106 style.refine(&self.base_style);1080 style.refine(&self.base_style);
@@ -1171,7 +1145,7 @@ where · 64 unmodified lines
1171 }1145 }
1172}1146}
1173 1147
1174impl<V: 'static> Default for Interactivity<V> {1148impl Default for Interactivity {
1175 fn default() -> Self {1149 fn default() -> Self {
1176 Self {1150 Self {
1177 element_id: None,1151 element_id: None,
@@ -1259,31 +1233,25 @@ impl GroupBounds { · 81 unmodified lines
1259 }1233 }
1260}1234}
1261 1235
1262pub struct Focusable<V, E> {1236pub struct Focusable<E> {
1263 element: E,1237 element: E,
1264 view_type: PhantomData<V>,
1265}1238}
1266 1239
1267impl<V: 'static, E: InteractiveComponent<V>> FocusableComponent<V> for Focusable<V, E> {}1240impl<E: InteractiveElement> FocusableElement for Focusable<E> {}
1268 1241
1269impl<V, E> InteractiveComponent<V> for Focusable<V, E>1242impl<E> InteractiveElement for Focusable<E>
1270where1243where
1271 V: 'static,1244 E: InteractiveElement,
1272 E: InteractiveComponent<V>,
1273{1245{
1274 fn interactivity(&mut self) -> &mut Interactivity<V> {1246 fn interactivity(&mut self) -> &mut Interactivity {
1275 self.element.interactivity()1247 self.element.interactivity()
1276 }1248 }
1277}1249}
1278 1250
1279impl<V: 'static, E: StatefulInteractiveComponent<V, E>> StatefulInteractiveComponent<V, E>1251impl<E: StatefulInteractiveElement> StatefulInteractiveElement for Focusable<E> {}
1280 for Focusable<V, E>
1281{
1282}
1283 1252
1284impl<V, E> Styled for Focusable<V, E>1253impl<E> Styled for Focusable<E>
1285where1254where
1286 V: 'static,
1287 E: Styled,1255 E: Styled,
1288{1256{
1289 fn style(&mut self) -> &mut StyleRefinement {1257 fn style(&mut self) -> &mut StyleRefinement {
@@ -1291,65 +1259,55 @@ where · 1 unmodified line
1291 }1259 }
1292}1260}
1293 1261
1294impl<V, E> Element<V> for Focusable<V, E>1262impl<E> Element for Focusable<E>
1295where1263where
1296 V: 'static,1264 E: Element,
1297 E: Element<V>,
1298{1265{
1299 type ElementState = E::ElementState;1266 type State = E::State;
1300
1301 fn element_id(&self) -> Option<ElementId> {
1302 self.element.element_id()
1303 }
1304 1267
1305 fn layout(1268 fn layout(
1306 &mut self,1269 &mut self,
1307 view_state: &mut V,1270 state: Option<Self::State>,
1308 element_state: Option<Self::ElementState>,1271 cx: &mut WindowContext,
1309 cx: &mut ViewContext<V>,1272 ) -> (LayoutId, Self::State) {
1310 ) -> (LayoutId, Self::ElementState) {1273 self.element.layout(state, cx)
1311 self.element.layout(view_state, element_state, cx)
1312 }1274 }
1313 1275
1314 fn paint(1276 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
1315 &mut self,1277 self.element.paint(bounds, state, cx)
1316 bounds: Bounds<Pixels>,
1317 view_state: &mut V,
1318 element_state: &mut Self::ElementState,
1319 cx: &mut ViewContext<V>,
1320 ) {
1321 self.element.paint(bounds, view_state, element_state, cx);
1322 }1278 }
1323}1279}
1324 1280
1325impl<V, E> Component<V> for Focusable<V, E>1281impl<E> IntoElement for Focusable<E>
1326where1282where
1327 V: 'static,1283 E: Element,
1328 E: 'static + Element<V>,
1329{1284{
1330 fn render(self) -> AnyElement<V> {1285 type Element = E;
1331 AnyElement::new(self)1286
1287 fn element_id(&self) -> Option<ElementId> {
1288 self.element.element_id()
1289 }
1290
1291 fn into_element(self) -> Self::Element {
1292 self.element
1332 }1293 }
1333}1294}
1334 1295
1335impl<V, E> ParentComponent<V> for Focusable<V, E>1296impl<E> ParentElement for Focusable<E>
1336where1297where
1337 V: 'static,1298 E: ParentElement,
1338 E: ParentComponent<V>,
1339{1299{
1340 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {1300 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
1341 self.element.children_mut()1301 self.element.children_mut()
1342 }1302 }
1343}1303}
1344 1304
1345pub struct Stateful<V, E> {1305pub struct Stateful<E> {
1346 element: E,1306 element: E,
1347 view_type: PhantomData<V>,
1348}1307}
1349 1308
1350impl<V, E> Styled for Stateful<V, E>1309impl<E> Styled for Stateful<E>
1351where1310where
1352 V: 'static,
1353 E: Styled,1311 E: Styled,
1354{1312{
1355 fn style(&mut self) -> &mut StyleRefinement {1313 fn style(&mut self) -> &mut StyleRefinement {
@@ -1357,73 +1315,63 @@ where · 1 unmodified line
1357 }1315 }
1358}1316}
1359 1317
1360impl<V, E> StatefulInteractiveComponent<V, E> for Stateful<V, E>1318impl<E> StatefulInteractiveElement for Stateful<E>
1361where1319where
1362 V: 'static,1320 E: Element,
1363 E: Element<V>,1321 Self: InteractiveElement,
1364 Self: InteractiveComponent<V>,
1365{1322{
1366}1323}
1367 1324
1368impl<V, E> InteractiveComponent<V> for Stateful<V, E>1325impl<E> InteractiveElement for Stateful<E>
1369where1326where
1370 V: 'static,1327 E: InteractiveElement,
1371 E: InteractiveComponent<V>,
1372{1328{
1373 fn interactivity(&mut self) -> &mut Interactivity<V> {1329 fn interactivity(&mut self) -> &mut Interactivity {
1374 self.element.interactivity()1330 self.element.interactivity()
1375 }1331 }
1376}1332}
1377 1333
1378impl<V: 'static, E: FocusableComponent<V>> FocusableComponent<V> for Stateful<V, E> {}1334impl<E: FocusableElement> FocusableElement for Stateful<E> {}
1379 1335
1380impl<V, E> Element<V> for Stateful<V, E>1336impl<E> Element for Stateful<E>
1381where1337where
1382 V: 'static,1338 E: Element,
1383 E: Element<V>,
1384{1339{
1385 type ElementState = E::ElementState;1340 type State = E::State;
1386
1387 fn element_id(&self) -> Option<ElementId> {
1388 self.element.element_id()
1389 }
1390 1341
1391 fn layout(1342 fn layout(
1392 &mut self,1343 &mut self,
1393 view_state: &mut V,1344 state: Option<Self::State>,
1394 element_state: Option<Self::ElementState>,1345 cx: &mut WindowContext,
1395 cx: &mut ViewContext<V>,1346 ) -> (LayoutId, Self::State) {
1396 ) -> (LayoutId, Self::ElementState) {1347 self.element.layout(state, cx)
1397 self.element.layout(view_state, element_state, cx)
1398 }1348 }
1399 1349
1400 fn paint(1350 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
1401 &mut self,1351 self.element.paint(bounds, state, cx)
1402 bounds: Bounds<Pixels>,
1403 view_state: &mut V,
1404 element_state: &mut Self::ElementState,
1405 cx: &mut ViewContext<V>,
1406 ) {
1407 self.element.paint(bounds, view_state, element_state, cx)
1408 }1352 }
1409}1353}
1410 1354
1411impl<V, E> Component<V> for Stateful<V, E>1355impl<E> IntoElement for Stateful<E>
1412where1356where
1413 V: 'static,1357 E: Element,
1414 E: 'static + Element<V>,
1415{1358{
1416 fn render(self) -> AnyElement<V> {1359 type Element = Self;
1417 AnyElement::new(self)1360
1361 fn element_id(&self) -> Option<ElementId> {
1362 self.element.element_id()
1363 }
1364
1365 fn into_element(self) -> Self::Element {
1366 self
1418 }1367 }
1419}1368}
1420 1369
1421impl<V, E> ParentComponent<V> for Stateful<V, E>1370impl<E> ParentElement for Stateful<E>
1422where1371where
1423 V: 'static,1372 E: ParentElement,
1424 E: ParentComponent<V>,
1425{1373{
1426 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {1374 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
1427 self.element.children_mut()1375 self.element.children_mut()
1428 }1376 }
1429}1377}
crates/gpui2/src/elements/img.rs+87−57
@@ -1,70 +1,83 @@
1use std::sync::Arc;
2
1use crate::{3use crate::{
2 AnyElement, BorrowWindow, Bounds, Component, Element, InteractiveComponent,4 Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity,
3 InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement,5 IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
4 Styled, ViewContext,
5};6};
6use futures::FutureExt;7use futures::FutureExt;
7use util::ResultExt;8use util::ResultExt;
8 9
9pub struct Img<V: 'static> {10#[derive(Clone, Debug)]
10 interactivity: Interactivity<V>,11pub enum ImageSource {
11 uri: Option<SharedString>,12 /// Image content will be loaded from provided URI at render time.
13 Uri(SharedString),
14 Data(Arc<ImageData>),
15}
16
17impl From<SharedString> for ImageSource {
18 fn from(value: SharedString) -> Self {
19 Self::Uri(value)
20 }
21}
22
23impl From<Arc<ImageData>> for ImageSource {
24 fn from(value: Arc<ImageData>) -> Self {
25 Self::Data(value)
26 }
27}
28
29pub struct Img {
30 interactivity: Interactivity,
31 source: Option<ImageSource>,
12 grayscale: bool,32 grayscale: bool,
13}33}
14 34
15pub fn img<V: 'static>() -> Img<V> {35pub fn img() -> Img {
16 Img {36 Img {
17 interactivity: Interactivity::default(),37 interactivity: Interactivity::default(),
18 uri: None,38 source: None,
19 grayscale: false,39 grayscale: false,
20 }40 }
21}41}
22 42
23impl<V> Img<V>43impl Img {
24where
25 V: 'static,
26{
27 pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {44 pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
28 self.uri = Some(uri.into());45 self.source = Some(ImageSource::from(uri.into()));
46 self
47 }
48 pub fn data(mut self, data: Arc<ImageData>) -> Self {
49 self.source = Some(ImageSource::from(data));
29 self50 self
30 }51 }
31 52
53 pub fn source(mut self, source: impl Into<ImageSource>) -> Self {
54 self.source = Some(source.into());
55 self
56 }
32 pub fn grayscale(mut self, grayscale: bool) -> Self {57 pub fn grayscale(mut self, grayscale: bool) -> Self {
33 self.grayscale = grayscale;58 self.grayscale = grayscale;
34 self59 self
35 }60 }
36}61}
37 62
38impl<V> Component<V> for Img<V> {63impl Element for Img {
39 fn render(self) -> AnyElement<V> {64 type State = InteractiveElementState;
40 AnyElement::new(self)
41 }
42}
43
44impl<V> Element<V> for Img<V> {
45 type ElementState = InteractiveElementState;
46
47 fn element_id(&self) -> Option<crate::ElementId> {
48 self.interactivity.element_id.clone()
49 }
50 65
51 fn layout(66 fn layout(
52 &mut self,67 &mut self,
53 _view_state: &mut V,68 element_state: Option<Self::State>,
54 element_state: Option<Self::ElementState>,69 cx: &mut WindowContext,
55 cx: &mut ViewContext<V>,70 ) -> (LayoutId, Self::State) {
56 ) -> (LayoutId, Self::ElementState) {
57 self.interactivity.layout(element_state, cx, |style, cx| {71 self.interactivity.layout(element_state, cx, |style, cx| {
58 cx.request_layout(&style, None)72 cx.request_layout(&style, None)
59 })73 })
60 }74 }
61 75
62 fn paint(76 fn paint(
63 &mut self,77 self,
64 bounds: Bounds<Pixels>,78 bounds: Bounds<Pixels>,
65 _view_state: &mut V,79 element_state: &mut Self::State,
66 element_state: &mut Self::ElementState,80 cx: &mut WindowContext,
67 cx: &mut ViewContext<V>,
68 ) {81 ) {
69 self.interactivity.paint(82 self.interactivity.paint(
70 bounds,83 bounds,
@@ -74,42 +87,59 @@ impl<V> Element<V> for Img<V> { · 3 unmodified lines
74 |style, _scroll_offset, cx| {87 |style, _scroll_offset, cx| {
75 let corner_radii = style.corner_radii;88 let corner_radii = style.corner_radii;
76 89
77 if let Some(uri) = self.uri.clone() {90 if let Some(source) = self.source {
78 // eprintln!(">>> image_cache.get({uri}");91 let image = match source {
79 let image_future = cx.image_cache.get(uri.clone());92 ImageSource::Uri(uri) => {
80 // eprintln!("<<< image_cache.get({uri}");93 let image_future = cx.image_cache.get(uri.clone());
81 if let Some(data) = image_future94 if let Some(data) = image_future
82 .clone()95 .clone()
83 .now_or_never()96 .now_or_never()
84 .and_then(|result| result.ok())97 .and_then(|result| result.ok())
85 {98 {
86 let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());99 data
87 cx.with_z_index(1, |cx| {100 } else {
88 cx.paint_image(bounds, corner_radii, data, self.grayscale)101 cx.spawn(|mut cx| async move {
89 .log_err()102 if image_future.await.ok().is_some() {
90 });103 cx.on_next_frame(|cx| cx.notify());
91 } else {104 }
92 cx.spawn(|_, mut cx| async move {105 })
93 if image_future.await.ok().is_some() {106 .detach();
94 cx.on_next_frame(|cx| cx.notify());107 return;
95 }108 }
96 })109 }
97 .detach()110 ImageSource::Data(image) => image,
98 }111 };
112 let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
113 cx.with_z_index(1, |cx| {
114 cx.paint_image(bounds, corner_radii, image, self.grayscale)
115 .log_err()
116 });
99 }117 }
100 },118 },
101 )119 )
102 }120 }
103}121}
104 122
105impl<V> Styled for Img<V> {123impl 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
135impl Styled for Img {
106 fn style(&mut self) -> &mut StyleRefinement {136 fn style(&mut self) -> &mut StyleRefinement {
107 &mut self.interactivity.base_style137 &mut self.interactivity.base_style
108 }138 }
109}139}
110 140
111impl<V> InteractiveComponent<V> for Img<V> {141impl InteractiveElement for Img {
112 fn interactivity(&mut self) -> &mut Interactivity<V> {142 fn interactivity(&mut self) -> &mut Interactivity {
113 &mut self.interactivity143 &mut self.interactivity
114 }144 }
115}145}
crates/gpui2/src/elements/overlay.rs+35−32
@@ -2,16 +2,16 @@ use smallvec::SmallVec; · 1 unmodified line
2use taffy::style::{Display, Position};2use taffy::style::{Display, Position};
3 3
4use crate::{4use crate::{
5 point, AnyElement, BorrowWindow, Bounds, Component, Element, LayoutId, ParentComponent, Pixels,5 point, AnyElement, BorrowWindow, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels,
6 Point, Size, Style,6 Point, Size, Style, WindowContext,
7};7};
8 8
9pub struct OverlayState {9pub struct OverlayState {
10 child_layout_ids: SmallVec<[LayoutId; 4]>,10 child_layout_ids: SmallVec<[LayoutId; 4]>,
11}11}
12 12
13pub struct Overlay<V> {13pub struct Overlay {
14 children: SmallVec<[AnyElement<V>; 2]>,14 children: SmallVec<[AnyElement; 2]>,
15 anchor_corner: AnchorCorner,15 anchor_corner: AnchorCorner,
16 fit_mode: OverlayFitMode,16 fit_mode: OverlayFitMode,
17 // todo!();17 // todo!();
@@ -21,7 +21,7 @@ pub struct Overlay<V> { · 3 unmodified lines
21 21
22/// overlay gives you a floating element that will avoid overflowing the window bounds.22/// overlay gives you a floating element that will avoid overflowing the window bounds.
23/// Its children should have no margin to avoid measurement issues.23/// Its children should have no margin to avoid measurement issues.
24pub fn overlay<V: 'static>() -> Overlay<V> {24pub fn overlay() -> Overlay {
25 Overlay {25 Overlay {
26 children: SmallVec::new(),26 children: SmallVec::new(),
27 anchor_corner: AnchorCorner::TopLeft,27 anchor_corner: AnchorCorner::TopLeft,
@@ -30,7 +30,7 @@ pub fn overlay<V: 'static>() -> Overlay<V> { · 2 unmodified lines
30 }30 }
31}31}
32 32
33impl<V> Overlay<V> {33impl Overlay {
34 /// Sets which corner of the overlay should be anchored to the current position.34 /// Sets which corner of the overlay should be anchored to the current position.
35 pub fn anchor(mut self, anchor: AnchorCorner) -> Self {35 pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
36 self.anchor_corner = anchor;36 self.anchor_corner = anchor;
@@ -51,35 +51,24 @@ impl<V> Overlay<V> { · 14 unmodified lines
51 }51 }
52}52}
53 53
54impl<V: 'static> ParentComponent<V> for Overlay<V> {54impl ParentElement for Overlay {
55 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {55 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
56 &mut self.children56 &mut self.children
57 }57 }
58}58}
59 59
60impl<V: 'static> Component<V> for Overlay<V> {60impl Element for Overlay {
61 fn render(self) -> AnyElement<V> {61 type State = OverlayState;
62 AnyElement::new(self)
63 }
64}
65
66impl<V: 'static> Element<V> for Overlay<V> {
67 type ElementState = OverlayState;
68
69 fn element_id(&self) -> Option<crate::ElementId> {
70 None
71 }
72 62
73 fn layout(63 fn layout(
74 &mut self,64 &mut self,
75 view_state: &mut V,65 _: Option<Self::State>,
76 _: Option<Self::ElementState>,66 cx: &mut WindowContext,
77 cx: &mut crate::ViewContext<V>,67 ) -> (crate::LayoutId, Self::State) {
78 ) -> (crate::LayoutId, Self::ElementState) {
79 let child_layout_ids = self68 let child_layout_ids = self
80 .children69 .children
81 .iter_mut()70 .iter_mut()
82 .map(|child| child.layout(view_state, cx))71 .map(|child| child.layout(cx))
83 .collect::<SmallVec<_>>();72 .collect::<SmallVec<_>>();
84 73
85 let mut overlay_style = Style::default();74 let mut overlay_style = Style::default();
@@ -92,11 +81,10 @@ impl<V: 'static> Element<V> for Overlay<V> { · 6 unmodified lines
92 }81 }
93 82
94 fn paint(83 fn paint(
95 &mut self,84 self,
96 bounds: crate::Bounds<crate::Pixels>,85 bounds: crate::Bounds<crate::Pixels>,
97 view_state: &mut V,86 element_state: &mut Self::State,
98 element_state: &mut Self::ElementState,87 cx: &mut WindowContext,
99 cx: &mut crate::ViewContext<V>,
100 ) {88 ) {
101 if element_state.child_layout_ids.is_empty() {89 if element_state.child_layout_ids.is_empty() {
102 return;90 return;
@@ -117,6 +105,7 @@ impl<V: 'static> Element<V> for Overlay<V> { · 14 unmodified lines
117 origin: Point::zero(),105 origin: Point::zero(),
118 size: cx.viewport_size(),106 size: cx.viewport_size(),
119 };107 };
108 dbg!(limits);
120 109
121 match self.fit_mode {110 match self.fit_mode {
122 OverlayFitMode::SnapToWindow => {111 OverlayFitMode::SnapToWindow => {
@@ -156,13 +145,27 @@ impl<V: 'static> Element<V> for Overlay<V> { · 33 unmodified lines
156 }145 }
157 146
158 cx.with_element_offset(desired.origin - bounds.origin, |cx| {147 cx.with_element_offset(desired.origin - bounds.origin, |cx| {
159 for child in &mut self.children {148 cx.break_content_mask(|cx| {
160 child.paint(view_state, cx);149 for child in self.children {
161 }150 child.paint(cx);
151 }
152 })
162 })153 })
163 }154 }
164}155}
165 156
157impl 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
166enum Axis {169enum Axis {
167 Horizontal,170 Horizontal,
168 Vertical,171 Vertical,
crates/gpui2/src/elements/svg.rs+28−33
@@ -1,60 +1,43 @@
1use crate::{1use crate::{
2 AnyElement, Bounds, Component, Element, ElementId, InteractiveComponent,2 Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
3 InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement,3 IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
4 Styled, ViewContext,
5};4};
6use util::ResultExt;5use util::ResultExt;
7 6
8pub struct Svg<V: 'static> {7pub struct Svg {
9 interactivity: Interactivity<V>,8 interactivity: Interactivity,
10 path: Option<SharedString>,9 path: Option<SharedString>,
11}10}
12 11
13pub fn svg<V: 'static>() -> Svg<V> {12pub fn svg() -> Svg {
14 Svg {13 Svg {
15 interactivity: Interactivity::default(),14 interactivity: Interactivity::default(),
16 path: None,15 path: None,
17 }16 }
18}17}
19 18
20impl<V> Svg<V> {19impl Svg {
21 pub fn path(mut self, path: impl Into<SharedString>) -> Self {20 pub fn path(mut self, path: impl Into<SharedString>) -> Self {
22 self.path = Some(path.into());21 self.path = Some(path.into());
23 self22 self
24 }23 }
25}24}
26 25
27impl<V> Component<V> for Svg<V> {26impl Element for Svg {
28 fn render(self) -> AnyElement<V> {27 type State = InteractiveElementState;
29 AnyElement::new(self)
30 }
31}
32
33impl<V> Element<V> for Svg<V> {
34 type ElementState = InteractiveElementState;
35
36 fn element_id(&self) -> Option<ElementId> {
37 self.interactivity.element_id.clone()
38 }
39 28
40 fn layout(29 fn layout(
41 &mut self,30 &mut self,
42 _view_state: &mut V,31 element_state: Option<Self::State>,
43 element_state: Option<Self::ElementState>,32 cx: &mut WindowContext,
44 cx: &mut ViewContext<V>,33 ) -> (LayoutId, Self::State) {
45 ) -> (LayoutId, Self::ElementState) {
46 self.interactivity.layout(element_state, cx, |style, cx| {34 self.interactivity.layout(element_state, cx, |style, cx| {
47 cx.request_layout(&style, None)35 cx.request_layout(&style, None)
48 })36 })
49 }37 }
50 38
51 fn paint(39 fn paint(self, bounds: Bounds<Pixels>, element_state: &mut Self::State, cx: &mut WindowContext)
52 &mut self,40 where
53 bounds: Bounds<Pixels>,
54 _view_state: &mut V,
55 element_state: &mut Self::ElementState,
56 cx: &mut ViewContext<V>,
57 ) where
58 Self: Sized,41 Self: Sized,
59 {42 {
60 self.interactivity43 self.interactivity
@@ -66,14 +49,26 @@ impl<V> Element<V> for Svg<V> { · 5 unmodified lines
66 }49 }
67}50}
68 51
69impl<V> Styled for Svg<V> {52impl 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
64impl Styled for Svg {
70 fn style(&mut self) -> &mut StyleRefinement {65 fn style(&mut self) -> &mut StyleRefinement {
71 &mut self.interactivity.base_style66 &mut self.interactivity.base_style
72 }67 }
73}68}
74 69
75impl<V> InteractiveComponent<V> for Svg<V> {70impl InteractiveElement for Svg {
76 fn interactivity(&mut self) -> &mut Interactivity<V> {71 fn interactivity(&mut self) -> &mut Interactivity {
77 &mut self.interactivity72 &mut self.interactivity
78 }73 }
79}74}
crates/gpui2/src/elements/text.rs+253−111
@@ -1,74 +1,177 @@
1use crate::{1use crate::{
2 AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, LayoutId, Pixels,2 Bounds, DispatchPhase, Element, ElementId, IntoElement, LayoutId, MouseDownEvent, MouseUpEvent,
3 SharedString, Size, TextRun, ViewContext, WrappedLine,3 Pixels, Point, SharedString, Size, TextRun, WhiteSpace, WindowContext, WrappedLine,
4};4};
5use anyhow::anyhow;
5use parking_lot::{Mutex, MutexGuard};6use parking_lot::{Mutex, MutexGuard};
6use smallvec::SmallVec;7use smallvec::SmallVec;
7use std::{cell::Cell, rc::Rc, sync::Arc};8use std::{cell::Cell, ops::Range, rc::Rc, sync::Arc};
8use util::ResultExt;9use util::ResultExt;
9 10
10pub struct Text {11impl 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
29impl 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
41impl 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
60impl 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.
77pub struct StyledText {
11 text: SharedString,78 text: SharedString,
12 runs: Option<Vec<TextRun>>,79 runs: Option<Vec<TextRun>>,
13}80}
14 81
15impl Text {82impl StyledText {
16 /// Renders text with runs of different styles.83 pub fn new(text: impl Into<SharedString>) -> Self {
17 ///84 StyledText {
18 /// Callers are responsible for setting the correct style for each run.85 text: text.into(),
19 /// For text with a uniform style, you can usually avoid calling this constructor86 runs: None,
20 /// and just pass text directly.
21 pub fn styled(text: SharedString, runs: Vec<TextRun>) -> Self {
22 Text {
23 text,
24 runs: Some(runs),
25 }87 }
26 }88 }
89
90 pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
91 self.runs = Some(runs);
92 self
93 }
27}94}
28 95
29impl<V: 'static> Component<V> for Text {96impl Element for StyledText {
30 fn render(self) -> AnyElement<V> {97 type State = TextState;
31 AnyElement::new(self)98
99 fn layout(
100 &mut self,
101 _: Option<Self::State>,
102 cx: &mut WindowContext,
103 ) -> (LayoutId, Self::State) {
104 let mut state = TextState::default();
105 let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
106 (layout_id, state)
107 }
108
109 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
110 state.paint(bounds, &self.text, cx)
32 }111 }
33}112}
34 113
35impl<V: 'static> Element<V> for Text {114impl IntoElement for StyledText {
36 type ElementState = TextState;115 type Element = Self;
37 116
38 fn element_id(&self) -> Option<crate::ElementId> {117 fn element_id(&self) -> Option<crate::ElementId> {
39 None118 None
40 }119 }
41 120
121 fn into_element(self) -> Self::Element {
122 self
123 }
124}
125
126#[derive(Default, Clone)]
127pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
128
129struct TextStateInner {
130 lines: SmallVec<[WrappedLine; 1]>,
131 line_height: Pixels,
132 wrap_width: Option<Pixels>,
133 size: Option<Size<Pixels>>,
134}
135
136impl TextState {
137 fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
138 self.0.lock()
139 }
140
42 fn layout(141 fn layout(
43 &mut self,142 &mut self,
44 _view: &mut V,143 text: SharedString,
45 element_state: Option<Self::ElementState>,144 runs: Option<Vec<TextRun>>,
46 cx: &mut ViewContext<V>,145 cx: &mut WindowContext,
47 ) -> (LayoutId, Self::ElementState) {146 ) -> LayoutId {
48 let element_state = element_state.unwrap_or_default();
49 let text_system = cx.text_system().clone();147 let text_system = cx.text_system().clone();
50 let text_style = cx.text_style();148 let text_style = cx.text_style();
51 let font_size = text_style.font_size.to_pixels(cx.rem_size());149 let font_size = text_style.font_size.to_pixels(cx.rem_size());
52 let line_height = text_style150 let line_height = text_style
53 .line_height151 .line_height
54 .to_pixels(font_size.into(), cx.rem_size());152 .to_pixels(font_size.into(), cx.rem_size());
55 let text = self.text.clone();153 let text = SharedString::from(text);
56 154
57 let rem_size = cx.rem_size();155 let rem_size = cx.rem_size();
58 156
59 let runs = if let Some(runs) = self.runs.take() {157 let runs = if let Some(runs) = runs {
60 runs158 runs
61 } else {159 } else {
62 vec![text_style.to_run(text.len())]160 vec![text_style.to_run(text.len())]
63 };161 };
64 162
65 let layout_id = cx.request_measured_layout(Default::default(), rem_size, {163 let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
66 let element_state = element_state.clone();164 let element_state = self.clone();
165
67 move |known_dimensions, available_space| {166 move |known_dimensions, available_space| {
68 let wrap_width = known_dimensions.width.or(match available_space.width {167 let wrap_width = if text_style.white_space == WhiteSpace::Normal {
69 crate::AvailableSpace::Definite(x) => Some(x),168 known_dimensions.width.or(match available_space.width {
70 _ => None,169 crate::AvailableSpace::Definite(x) => Some(x),
71 });170 _ => None,
171 })
172 } else {
173 None
174 };
72 175
73 if let Some(text_state) = element_state.0.lock().as_ref() {176 if let Some(text_state) = element_state.0.lock().as_ref() {
74 if text_state.size.is_some()177 if text_state.size.is_some()
@@ -80,10 +183,7 @@ impl<V: 'static> Element<V> for Text { · 5 unmodified lines
80 183
81 let Some(lines) = text_system184 let Some(lines) = text_system
82 .shape_text(185 .shape_text(
83 &text,186 &text, font_size, &runs, wrap_width, // Wrap if we know the width.
84 font_size,
85 &runs[..],
86 wrap_width, // Wrap if we know the width.
87 )187 )
88 .log_err()188 .log_err()
89 else {189 else {
@@ -100,7 +200,7 @@ impl<V: 'static> Element<V> for Text { · 10 unmodified lines
100 for line in &lines {200 for line in &lines {
101 let line_size = line.size(line_height);201 let line_size = line.size(line_height);
102 size.height += line_size.height;202 size.height += line_size.height;
103 size.width = size.width.max(line_size.width);203 size.width = size.width.max(line_size.width).ceil();
104 }204 }
105 205
106 element_state.lock().replace(TextStateInner {206 element_state.lock().replace(TextStateInner {
@@ -114,20 +214,14 @@ impl<V: 'static> Element<V> for Text { · 7 unmodified lines
114 }214 }
115 });215 });
116 216
117 (layout_id, element_state)217 layout_id
118 }218 }
119 219
120 fn paint(220 fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
121 &mut self,221 let element_state = self.lock();
122 bounds: Bounds<Pixels>,
123 _: &mut V,
124 element_state: &mut Self::ElementState,
125 cx: &mut ViewContext<V>,
126 ) {
127 let element_state = element_state.lock();
128 let element_state = element_state222 let element_state = element_state
129 .as_ref()223 .as_ref()
130 .ok_or_else(|| anyhow::anyhow!("measurement has not been performed on {}", &self.text))224 .ok_or_else(|| anyhow!("measurement has not been performed on {}", text))
131 .unwrap();225 .unwrap();
132 226
133 let line_height = element_state.line_height;227 let line_height = element_state.line_height;
@@ -137,108 +231,156 @@ impl<V: 'static> Element<V> for Text { · 3 unmodified lines
137 line_origin.y += line.size(line_height).height;231 line_origin.y += line.size(line_height).height;
138 }232 }
139 }233 }
140}
141 234
142#[derive(Default, Clone)]235 fn index_for_position(&self, bounds: Bounds<Pixels>, position: Point<Pixels>) -> Option<usize> {
143pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);236 if !bounds.contains_point(&position) {
237 return None;
238 }
144 239
145impl TextState {240 let element_state = self.lock();
146 fn lock(&self) -> MutexGuard<Option<TextStateInner>> {241 let element_state = element_state
147 self.0.lock()242 .as_ref()
243 .expect("measurement has not been performed");
244
245 let line_height = element_state.line_height;
246 let mut line_origin = bounds.origin;
247 let mut line_start_ix = 0;
248 for line in &element_state.lines {
249 let line_bottom = line_origin.y + line.size(line_height).height;
250 if position.y > line_bottom {
251 line_origin.y = line_bottom;
252 line_start_ix += line.len() + 1;
253 } else {
254 let position_within_line = position - line_origin;
255 let index_within_line =
256 line.index_for_position(position_within_line, line_height)?;
257 return Some(line_start_ix + index_within_line);
258 }
259 }
260
261 None
148 }262 }
149}263}
150 264
151struct TextStateInner {265pub struct InteractiveText {
152 lines: SmallVec<[WrappedLine; 1]>,266 element_id: ElementId,
153 line_height: Pixels,267 text: StyledText,
154 wrap_width: Option<Pixels>,268 click_listener: Option<Box<dyn Fn(InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
155 size: Option<Size<Pixels>>,
156}269}
157 270
158struct InteractiveText {271struct InteractiveTextClickEvent {
159 id: ElementId,272 mouse_down_index: usize,
160 text: Text,273 mouse_up_index: usize,
161}274}
162 275
163struct InteractiveTextState {276pub struct InteractiveTextState {
164 text_state: TextState,277 text_state: TextState,
165 clicked_range_ixs: Rc<Cell<SmallVec<[usize; 1]>>>,278 mouse_down_index: Rc<Cell<Option<usize>>>,
166}279}
167 280
168impl<V: 'static> Element<V> for InteractiveText {281impl InteractiveText {
169 type ElementState = InteractiveTextState;282 pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
283 Self {
284 element_id: id.into(),
285 text,
286 click_listener: None,
287 }
288 }
170 289
171 fn element_id(&self) -> Option<ElementId> {290 pub fn on_click(
172 Some(self.id.clone())291 mut self,
292 ranges: Vec<Range<usize>>,
293 listener: impl Fn(usize, &mut WindowContext<'_>) + 'static,
294 ) -> Self {
295 self.click_listener = Some(Box::new(move |event, cx| {
296 for (range_ix, range) in ranges.iter().enumerate() {
297 if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
298 {
299 listener(range_ix, cx);
300 }
301 }
302 }));
303 self
173 }304 }
305}
306
307impl Element for InteractiveText {
308 type State = InteractiveTextState;
174 309
175 fn layout(310 fn layout(
176 &mut self,311 &mut self,
177 view_state: &mut V,312 state: Option<Self::State>,
178 element_state: Option<Self::ElementState>,313 cx: &mut WindowContext,
179 cx: &mut ViewContext<V>,314 ) -> (LayoutId, Self::State) {
180 ) -> (LayoutId, Self::ElementState) {
181 if let Some(InteractiveTextState {315 if let Some(InteractiveTextState {
182 text_state,316 mouse_down_index, ..
183 clicked_range_ixs,317 }) = state
184 }) = element_state
185 {318 {
186 let (layout_id, text_state) = self.text.layout(view_state, Some(text_state), cx);319 let (layout_id, text_state) = self.text.layout(None, cx);
187 let element_state = InteractiveTextState {320 let element_state = InteractiveTextState {
188 text_state,321 text_state,
189 clicked_range_ixs,322 mouse_down_index,
190 };323 };
191 (layout_id, element_state)324 (layout_id, element_state)
192 } else {325 } else {
193 let (layout_id, text_state) = self.text.layout(view_state, None, cx);326 let (layout_id, text_state) = self.text.layout(None, cx);
194 let element_state = InteractiveTextState {327 let element_state = InteractiveTextState {
195 text_state,328 text_state,
196 clicked_range_ixs: Rc::default(),329 mouse_down_index: Rc::default(),
197 };330 };
198 (layout_id, element_state)331 (layout_id, element_state)
199 }332 }
200 }333 }
201 334
202 fn paint(335 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
203 &mut self,336 if let Some(click_listener) = self.click_listener {
204 bounds: Bounds<Pixels>,337 let text_state = state.text_state.clone();
205 view_state: &mut V,338 let mouse_down = state.mouse_down_index.clone();
206 element_state: &mut Self::ElementState,339 if let Some(mouse_down_index) = mouse_down.get() {
207 cx: &mut ViewContext<V>,340 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
208 ) {341 if phase == DispatchPhase::Bubble {
209 self.text342 if let Some(mouse_up_index) =
210 .paint(bounds, view_state, &mut element_state.text_state, cx)343 text_state.index_for_position(bounds, event.position)
211 }344 {
212}345 click_listener(
346 InteractiveTextClickEvent {
347 mouse_down_index,
348 mouse_up_index,
349 },
350 cx,
351 )
352 }
213 353
214impl<V: 'static> Component<V> for SharedString {354 mouse_down.take();
215 fn render(self) -> AnyElement<V> {355 cx.notify();
216 Text {356 }
217 text: self,357 });
218 runs: None,358 } else {
359 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
360 if phase == DispatchPhase::Bubble {
361 if let Some(mouse_down_index) =
362 text_state.index_for_position(bounds, event.position)
363 {
364 mouse_down.set(Some(mouse_down_index));
365 cx.notify();
366 }
367 }
368 });
369 }
219 }370 }
220 .render()371
372 self.text.paint(bounds, &mut state.text_state, cx)
221 }373 }
222}374}
223 375
224impl<V: 'static> Component<V> for &'static str {376impl IntoElement for InteractiveText {
225 fn render(self) -> AnyElement<V> {377 type Element = Self;
226 Text {378
227 text: self.into(),379 fn element_id(&self) -> Option<ElementId> {
228 runs: None,380 Some(self.element_id.clone())
229 }
230 .render()
231 }381 }
232}
233 382
234// TODO: Figure out how to pass `String` to `child` without this.383 fn into_element(self) -> Self::Element {
235// This impl doesn't exist in the `gpui2` crate.384 self
236impl<V: 'static> Component<V> for String {
237 fn render(self) -> AnyElement<V> {
238 Text {
239 text: self.into(),
240 runs: None,
241 }
242 .render()
243 }385 }
244}386}
crates/gpui2/src/elements/uniform_list.rs+81−90
@@ -1,61 +1,60 @@
1use crate::{1use crate::{
2 point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element,2 point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element,
3 ElementId, InteractiveComponent, InteractiveElementState, Interactivity, LayoutId, Pixels,3 ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
4 Point, Size, StyleRefinement, Styled, ViewContext,4 Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
5};5};
6use smallvec::SmallVec;6use smallvec::SmallVec;
7use std::{cell::RefCell, cmp, mem, ops::Range, rc::Rc};7use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
8use taffy::style::Overflow;8use taffy::style::Overflow;
9 9
10/// uniform_list provides lazy rendering for a set of items that are of uniform height.10/// uniform_list provides lazy rendering for a set of items that are of uniform height.
11/// When rendered into a container with overflow-y: hidden and a fixed (or max) height,11/// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
12/// uniform_list will only render the visibile subset of items.12/// uniform_list will only render the visible subset of items.
13pub fn uniform_list<I, V, C>(13pub fn uniform_list<I, R, V>(
14 view: View<V>,
14 id: I,15 id: I,
15 item_count: usize,16 item_count: usize,
16 f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<C>,17 f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<R>,
17) -> UniformList<V>18) -> UniformList
18where19where
19 I: Into<ElementId>,20 I: Into<ElementId>,
20 V: 'static,21 R: IntoElement,
21 C: Component<V>,22 V: Render,
22{23{
23 let id = id.into();24 let id = id.into();
24 let mut style = StyleRefinement::default();25 let mut base_style = StyleRefinement::default();
25 style.overflow.y = Some(Overflow::Hidden);26 base_style.overflow.y = Some(Overflow::Scroll);
27
28 let render_range = move |range, cx: &mut WindowContext| {
29 view.update(cx, |this, cx| {
30 f(this, range, cx)
31 .into_iter()
32 .map(|component| component.into_any_element())
33 .collect()
34 })
35 };
26 36
27 UniformList {37 UniformList {
28 id: id.clone(),38 id: id.clone(),
29 style,
30 item_count,39 item_count,
31 item_to_measure_index: 0,40 item_to_measure_index: 0,
32 render_items: Box::new(move |view, visible_range, cx| {41 render_items: Box::new(render_range),
33 f(view, visible_range, cx)
34 .into_iter()
35 .map(|component| component.render())
36 .collect()
37 }),
38 interactivity: Interactivity {42 interactivity: Interactivity {
39 element_id: Some(id.into()),43 element_id: Some(id.into()),
44 base_style,
40 ..Default::default()45 ..Default::default()
41 },46 },
42 scroll_handle: None,47 scroll_handle: None,
43 }48 }
44}49}
45 50
46pub struct UniformList<V: 'static> {51pub struct UniformList {
47 id: ElementId,52 id: ElementId,
48 style: StyleRefinement,
49 item_count: usize,53 item_count: usize,
50 item_to_measure_index: usize,54 item_to_measure_index: usize,
51 render_items: Box<55 render_items:
52 dyn for<'a> Fn(56 Box<dyn for<'a> Fn(Range<usize>, &'a mut WindowContext) -> SmallVec<[AnyElement; 64]>>,
53 &'a mut V,57 interactivity: Interactivity,
54 Range<usize>,
55 &'a mut ViewContext<V>,
56 ) -> SmallVec<[AnyElement<V>; 64]>,
57 >,
58 interactivity: Interactivity<V>,
59 scroll_handle: Option<UniformListScrollHandle>,58 scroll_handle: Option<UniformListScrollHandle>,
60}59}
61 60
@@ -89,9 +88,9 @@ impl UniformListScrollHandle { · 27 unmodified lines
89 }88 }
90}89}
91 90
92impl<V: 'static> Styled for UniformList<V> {91impl Styled for UniformList {
93 fn style(&mut self) -> &mut StyleRefinement {92 fn style(&mut self) -> &mut StyleRefinement {
94 &mut self.style93 &mut self.interactivity.base_style
95 }94 }
96}95}
97 96
@@ -101,29 +100,24 @@ pub struct UniformListState { · 3 unmodified lines
101 item_size: Size<Pixels>,100 item_size: Size<Pixels>,
102}101}
103 102
104impl<V: 'static> Element<V> for UniformList<V> {103impl Element for UniformList {
105 type ElementState = UniformListState;104 type State = UniformListState;
106
107 fn element_id(&self) -> Option<crate::ElementId> {
108 Some(self.id.clone())
109 }
110 105
111 fn layout(106 fn layout(
112 &mut self,107 &mut self,
113 view_state: &mut V,108 state: Option<Self::State>,
114 element_state: Option<Self::ElementState>,109 cx: &mut WindowContext,
115 cx: &mut ViewContext<V>,110 ) -> (LayoutId, Self::State) {
116 ) -> (LayoutId, Self::ElementState) {
117 let max_items = self.item_count;111 let max_items = self.item_count;
118 let rem_size = cx.rem_size();112 let rem_size = cx.rem_size();
119 let item_size = element_state113 let item_size = state
120 .as_ref()114 .as_ref()
121 .map(|s| s.item_size)115 .map(|s| s.item_size)
122 .unwrap_or_else(|| self.measure_item(view_state, None, cx));116 .unwrap_or_else(|| self.measure_item(None, cx));
123 117
124 let (layout_id, interactive) =118 let (layout_id, interactive) =
125 self.interactivity119 self.interactivity
126 .layout(element_state.map(|s| s.interactive), cx, |style, cx| {120 .layout(state.map(|s| s.interactive), cx, |style, cx| {
127 cx.request_measured_layout(121 cx.request_measured_layout(
128 style,122 style,
129 rem_size,123 rem_size,
@@ -159,11 +153,10 @@ impl<V: 'static> Element<V> for UniformList<V> { · 29 unmodified lines
159 }153 }
160 154
161 fn paint(155 fn paint(
162 &mut self,156 self,
163 bounds: Bounds<crate::Pixels>,157 bounds: Bounds<crate::Pixels>,
164 view_state: &mut V,158 element_state: &mut Self::State,
165 element_state: &mut Self::ElementState,159 cx: &mut WindowContext,
166 cx: &mut ViewContext<V>,
167 ) {160 ) {
168 let style =161 let style =
169 self.interactivity162 self.interactivity
@@ -183,14 +176,15 @@ impl<V: 'static> Element<V> for UniformList<V> { · 13 unmodified lines
183 height: item_size.height * self.item_count,176 height: item_size.height * self.item_count,
184 };177 };
185 178
186 let mut interactivity = mem::take(&mut self.interactivity);
187 let shared_scroll_offset = element_state179 let shared_scroll_offset = element_state
188 .interactive180 .interactive
189 .scroll_offset181 .scroll_offset
190 .get_or_insert_with(Rc::default)182 .get_or_insert_with(Rc::default)
191 .clone();183 .clone();
192 184
193 interactivity.paint(185 let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
186
187 self.interactivity.paint(
194 bounds,188 bounds,
195 content_size,189 content_size,
196 &mut element_state.interactive,190 &mut element_state.interactive,
@@ -209,9 +203,6 @@ impl<V: 'static> Element<V> for UniformList<V> { · 12 unmodified lines
209 style.paint(bounds, cx);203 style.paint(bounds, cx);
210 204
211 if self.item_count > 0 {205 if self.item_count > 0 {
212 let item_height = self
213 .measure_item(view_state, Some(padded_bounds.size.width), cx)
214 .height;
215 if let Some(scroll_handle) = self.scroll_handle.clone() {206 if let Some(scroll_handle) = self.scroll_handle.clone() {
216 scroll_handle.0.borrow_mut().replace(ScrollHandleState {207 scroll_handle.0.borrow_mut().replace(ScrollHandleState {
217 item_height,208 item_height,
@@ -219,58 +210,64 @@ impl<V: 'static> Element<V> for UniformList<V> { · 1 unmodified line
219 scroll_offset: shared_scroll_offset,210 scroll_offset: shared_scroll_offset,
220 });211 });
221 }212 }
222 let visible_item_count = if item_height > px(0.) {
223 (padded_bounds.size.height / item_height).ceil() as usize + 1
224 } else {
225 0
226 };
227 213
228 let first_visible_element_ix =214 let first_visible_element_ix =
229 (-scroll_offset.y / item_height).floor() as usize;215 (-scroll_offset.y / item_height).floor() as usize;
216 let last_visible_element_ix =
217 ((-scroll_offset.y + padded_bounds.size.height) / item_height).ceil()
218 as usize;
230 let visible_range = first_visible_element_ix219 let visible_range = first_visible_element_ix
231 ..cmp::min(220 ..cmp::min(last_visible_element_ix, self.item_count);
232 first_visible_element_ix + visible_item_count,
233 self.item_count,
234 );
235 221
236 let mut items = (self.render_items)(view_state, visible_range.clone(), cx);222 let items = (self.render_items)(visible_range.clone(), cx);
237 cx.with_z_index(1, |cx| {223 cx.with_z_index(1, |cx| {
238 for (item, ix) in items.iter_mut().zip(visible_range) {224 let content_mask = ContentMask {
239 let item_origin = padded_bounds.origin225 bounds: padded_bounds,
240 + point(px(0.), item_height * ix + scroll_offset.y);226 };
241 let available_space = size(227 cx.with_content_mask(Some(content_mask), |cx| {
242 AvailableSpace::Definite(padded_bounds.size.width),228 for (item, ix) in items.into_iter().zip(visible_range) {
243 AvailableSpace::Definite(item_height),229 let item_origin = padded_bounds.origin
244 );230 + point(px(0.), item_height * ix + scroll_offset.y);
245 item.draw(item_origin, available_space, view_state, cx);231 let available_space = size(
246 }232 AvailableSpace::Definite(padded_bounds.size.width),
233 AvailableSpace::Definite(item_height),
234 );
235 item.draw(item_origin, available_space, cx);
236 }
237 });
247 });238 });
248 }239 }
249 })240 })
250 },241 },
251 );242 );
252 self.interactivity = interactivity;
253 }243 }
254}244}
255 245
256impl<V> UniformList<V> {246impl 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
258impl UniformList {
257 pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {259 pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
258 self.item_to_measure_index = item_index.unwrap_or(0);260 self.item_to_measure_index = item_index.unwrap_or(0);
259 self261 self
260 }262 }
261 263
262 fn measure_item(264 fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
263 &self,
264 view_state: &mut V,
265 list_width: Option<Pixels>,
266 cx: &mut ViewContext<V>,
267 ) -> Size<Pixels> {
268 if self.item_count == 0 {265 if self.item_count == 0 {
269 return Size::default();266 return Size::default();
270 }267 }
271 268
272 let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1);269 let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1);
273 let mut items = (self.render_items)(view_state, item_ix..item_ix + 1, cx);270 let mut items = (self.render_items)(item_ix..item_ix + 1, cx);
274 let mut item_to_measure = items.pop().unwrap();271 let mut item_to_measure = items.pop().unwrap();
275 let available_space = size(272 let available_space = size(
276 list_width.map_or(AvailableSpace::MinContent, |width| {273 list_width.map_or(AvailableSpace::MinContent, |width| {
@@ -278,7 +275,7 @@ impl<V> UniformList<V> { · 1 unmodified line
278 }),275 }),
279 AvailableSpace::MinContent,276 AvailableSpace::MinContent,
280 );277 );
281 item_to_measure.measure(available_space, view_state, cx)278 item_to_measure.measure(available_space, cx)
282 }279 }
283 280
284 pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {281 pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
@@ -287,14 +284,8 @@ impl<V> UniformList<V> { · 2 unmodified lines
287 }284 }
288}285}
289 286
290impl<V> InteractiveComponent<V> for UniformList<V> {287impl InteractiveElement for UniformList {
291 fn interactivity(&mut self) -> &mut crate::Interactivity<V> {288 fn interactivity(&mut self) -> &mut crate::Interactivity {
292 &mut self.interactivity289 &mut self.interactivity
293 }290 }
294}291}
295
296impl<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
78};78};
79use taffy::TaffyLayoutEngine;79use taffy::TaffyLayoutEngine;
80 80
81type AnyBox = Box<dyn Any>;
82
83pub trait Context {81pub trait Context {
84 type Result<T>;82 type Result<T>;
85 83
@@ -136,7 +134,7 @@ pub trait VisualContext: Context { · 50 unmodified lines
136 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,134 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
137 ) -> Self::Result<View<V>>135 ) -> Self::Result<View<V>>
138 where136 where
139 V: Render;137 V: 'static + Render;
140 138
141 fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>139 fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
142 where140 where
crates/gpui2/src/input.rs+5−3
@@ -1,4 +1,6 @@
1use crate::{AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext};1use crate::{
2 AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext, WindowContext,
3};
2use std::ops::Range;4use std::ops::Range;
3 5
4/// Implement this trait to allow views to handle textual input when implementing an editor, field, etc.6/// Implement this trait to allow views to handle textual input when implementing an editor, field, etc.
@@ -43,9 +45,9 @@ pub struct ElementInputHandler<V> { · 38 unmodified lines
43impl<V: 'static> ElementInputHandler<V> {45impl<V: 'static> ElementInputHandler<V> {
44 /// Used in [Element::paint] with the element's bounds and a view context for its46 /// Used in [Element::paint] with the element's bounds and a view context for its
45 /// containing view.47 /// containing view.
46 pub fn new(element_bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) -> Self {48 pub fn new(element_bounds: Bounds<Pixels>, view: View<V>, cx: &mut WindowContext) -> Self {
47 ElementInputHandler {49 ElementInputHandler {
48 view: cx.view().clone(),50 view,
49 element_bounds,51 element_bounds,
50 cx: cx.to_async(),52 cx: cx.to_async(),
51 }53 }
crates/gpui2/src/interactive.rs+21−17
@@ -1,6 +1,6 @@
1use crate::{1use crate::{
2 div, point, Component, Div, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render,2 div, point, Div, Element, FocusHandle, IntoElement, Keystroke, Modifiers, Pixels, Point,
3 ViewContext,3 Render, ViewContext,
4};4};
5use smallvec::SmallVec;5use smallvec::SmallVec;
6use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf};6use 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
64where64where
65 R: Fn(&mut V, &mut ViewContext<V>) -> E,65 R: Fn(&mut V, &mut ViewContext<V>) -> E,
66 V: 'static,66 V: 'static,
67 E: Component<()>,67 E: IntoElement,
68{68{
69 pub state: S,69 pub state: S,
70 pub render_drag_handle: R,70 pub render_drag_handle: R,
71 view_type: PhantomData<V>,71 view_element_types: PhantomData<(V, E)>,
72}72}
73 73
74impl<S, R, V, E> Drag<S, R, V, E>74impl<S, R, V, E> Drag<S, R, V, E>
75where75where
76 R: Fn(&mut V, &mut ViewContext<V>) -> E,76 R: Fn(&mut V, &mut ViewContext<V>) -> E,
77 V: 'static,77 V: 'static,
78 E: Component<()>,78 E: Element,
79{79{
80 pub fn new(state: S, render_drag_handle: R) -> Self {80 pub fn new(state: S, render_drag_handle: R) -> Self {
81 Drag {81 Drag {
82 state,82 state,
83 render_drag_handle,83 render_drag_handle,
84 view_type: PhantomData,84 view_element_types: Default::default(),
85 }85 }
86 }86 }
87}87}
@@ -194,7 +194,7 @@ impl Deref for MouseExitEvent { · 106 unmodified lines
194pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);194pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
195 195
196impl Render for ExternalPaths {196impl Render for ExternalPaths {
197 type Element = Div<Self>;197 type Element = Div;
198 198
199 fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {199 fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
200 div() // Intentionally left empty because the platform will render icons for the dragged files200 div() // Intentionally left empty because the platform will render icons for the dragged files
@@ -286,8 +286,8 @@ pub struct FocusEvent { · 85 unmodified lines
286#[cfg(test)]286#[cfg(test)]
287mod test {287mod test {
288 use crate::{288 use crate::{
289 self as gpui, div, Component, Div, FocusHandle, InteractiveComponent, KeyBinding,289 self as gpui, div, Div, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
290 Keystroke, ParentComponent, Render, Stateful, TestAppContext, ViewContext, VisualContext,290 Keystroke, ParentElement, Render, Stateful, TestAppContext, VisualContext,
291 };291 };
292 292
293 struct TestView {293 struct TestView {
@@ -299,20 +299,24 @@ mod test { · 5 unmodified lines
299 actions!(TestAction);299 actions!(TestAction);
300 300
301 impl Render for TestView {301 impl Render for TestView {
302 type Element = Stateful<Self, Div<Self>>;302 type Element = Stateful<Div>;
303 303
304 fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {304 fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
305 div().id("testview").child(305 div().id("testview").child(
306 div()306 div()
307 .key_context("parent")307 .key_context("parent")
308 .on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)308 .on_key_down(cx.listener(|this, _, _| this.saw_key_down = true))
309 .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)309 .on_action(
310 .child(|this: &mut Self, _cx: &mut ViewContext<Self>| {310 cx.listener(|this: &mut TestView, _: &TestAction, _| {
311 this.saw_action = true
312 }),
313 )
314 .child(
311 div()315 div()
312 .key_context("nested")316 .key_context("nested")
313 .track_focus(&this.focus_handle)317 .track_focus(&self.focus_handle)
314 .render()318 .into_element(),
315 }),319 ),
316 )320 )
317 }321 }
318 }322 }
crates/gpui2/src/platform/mac/window.rs+3−0
@@ -683,6 +683,9 @@ impl Drop for MacWindow { · 682 unmodified lines
683 this.executor683 this.executor
684 .spawn(async move {684 .spawn(async move {
685 unsafe {685 unsafe {
686 // todo!() this panic()s when you click the red close button
687 // unless should_close returns false.
688 // (luckliy in zed it always returns false)
686 window.close();689 window.close();
687 }690 }
688 })691 })
crates/gpui2/src/prelude.rs+3−2
@@ -1,4 +1,5 @@
1pub use crate::{1pub use crate::{
2 BorrowAppContext, BorrowWindow, Component, Context, FocusableComponent, InteractiveComponent,2 BorrowAppContext, BorrowWindow, Context, Element, FocusableElement, InteractiveElement,
3 ParentComponent, Refineable, Render, StatefulInteractiveComponent, Styled, VisualContext,3 IntoElement, ParentElement, Refineable, Render, RenderOnce, StatefulInteractiveElement, Styled,
4 VisualContext,
4};5};
crates/gpui2/src/style.rs+184−4
@@ -1,9 +1,12 @@
1use std::{iter, mem, ops::Range};
2
1use crate::{3use crate::{
2 black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,4 black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
3 Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,5 Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
4 FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,6 FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
5 SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext,7 SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext,
6};8};
9use collections::HashSet;
7use refineable::{Cascade, Refineable};10use refineable::{Cascade, Refineable};
8use smallvec::SmallVec;11use smallvec::SmallVec;
9pub use taffy::style::{12pub use taffy::style::{
@@ -128,6 +131,13 @@ pub struct BoxShadow { · 118 unmodified lines
128 pub spread_radius: Pixels,131 pub spread_radius: Pixels,
129}132}
130 133
134#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
135pub enum WhiteSpace {
136 #[default]
137 Normal,
138 Nowrap,
139}
140
131#[derive(Refineable, Clone, Debug)]141#[derive(Refineable, Clone, Debug)]
132#[refineable(Debug)]142#[refineable(Debug)]
133pub struct TextStyle {143pub struct TextStyle {
@@ -138,7 +148,9 @@ pub struct TextStyle { · 4 unmodified lines
138 pub line_height: DefiniteLength,148 pub line_height: DefiniteLength,
139 pub font_weight: FontWeight,149 pub font_weight: FontWeight,
140 pub font_style: FontStyle,150 pub font_style: FontStyle,
151 pub background_color: Option<Hsla>,
141 pub underline: Option<UnderlineStyle>,152 pub underline: Option<UnderlineStyle>,
153 pub white_space: WhiteSpace,
142}154}
143 155
144impl Default for TextStyle {156impl Default for TextStyle {
@@ -151,13 +163,16 @@ impl Default for TextStyle { · 6 unmodified lines
151 line_height: phi(),163 line_height: phi(),
152 font_weight: FontWeight::default(),164 font_weight: FontWeight::default(),
153 font_style: FontStyle::default(),165 font_style: FontStyle::default(),
166 background_color: None,
154 underline: None,167 underline: None,
168 white_space: WhiteSpace::Normal,
155 }169 }
156 }170 }
157}171}
158 172
159impl TextStyle {173impl TextStyle {
160 pub fn highlight(mut self, style: HighlightStyle) -> Self {174 pub fn highlight(mut self, style: impl Into<HighlightStyle>) -> Self {
175 let style = style.into();
161 if let Some(weight) = style.font_weight {176 if let Some(weight) = style.font_weight {
162 self.font_weight = weight;177 self.font_weight = weight;
163 }178 }
@@ -173,6 +188,10 @@ impl TextStyle { · 9 unmodified lines
173 self.color.fade_out(factor);188 self.color.fade_out(factor);
174 }189 }
175 190
191 if let Some(background_color) = style.background_color {
192 self.background_color = Some(background_color);
193 }
194
176 if let Some(underline) = style.underline {195 if let Some(underline) = style.underline {
177 self.underline = Some(underline);196 self.underline = Some(underline);
178 }197 }
@@ -203,7 +222,7 @@ impl TextStyle { · 24 unmodified lines
203 style: self.font_style,222 style: self.font_style,
204 },223 },
205 color: self.color,224 color: self.color,
206 background_color: None,225 background_color: self.background_color,
207 underline: self.underline.clone(),226 underline: self.underline.clone(),
208 }227 }
209 }228 }
@@ -214,6 +233,7 @@ pub struct HighlightStyle { · 4 unmodified lines
214 pub color: Option<Hsla>,233 pub color: Option<Hsla>,
215 pub font_weight: Option<FontWeight>,234 pub font_weight: Option<FontWeight>,
216 pub font_style: Option<FontStyle>,235 pub font_style: Option<FontStyle>,
236 pub background_color: Option<Hsla>,
217 pub underline: Option<UnderlineStyle>,237 pub underline: Option<UnderlineStyle>,
218 pub fade_out: Option<f32>,238 pub fade_out: Option<f32>,
219}239}
@@ -313,7 +333,7 @@ impl Style { · 93 unmodified lines
313 }333 }
314 334
315 /// Paints the background of an element styled with this style.335 /// Paints the background of an element styled with this style.
316 pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {336 pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
317 let rem_size = cx.rem_size();337 let rem_size = cx.rem_size();
318 338
319 cx.with_z_index(0, |cx| {339 cx.with_z_index(0, |cx| {
@@ -432,6 +452,7 @@ impl From<&TextStyle> for HighlightStyle { · 112 unmodified lines
432 color: Some(other.color),452 color: Some(other.color),
433 font_weight: Some(other.font_weight),453 font_weight: Some(other.font_weight),
434 font_style: Some(other.font_style),454 font_style: Some(other.font_style),
455 background_color: other.background_color,
435 underline: other.underline.clone(),456 underline: other.underline.clone(),
436 fade_out: None,457 fade_out: None,
437 }458 }
@@ -458,6 +479,10 @@ impl HighlightStyle { · 20 unmodified lines
458 self.font_style = other.font_style;479 self.font_style = other.font_style;
459 }480 }
460 481
482 if other.background_color.is_some() {
483 self.background_color = other.background_color;
484 }
485
461 if other.underline.is_some() {486 if other.underline.is_some() {
462 self.underline = other.underline;487 self.underline = other.underline;
463 }488 }
@@ -481,6 +506,24 @@ impl From<Hsla> for HighlightStyle { · 17 unmodified lines
481 }506 }
482}507}
483 508
509impl 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
518impl 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
484impl From<Rgba> for HighlightStyle {527impl From<Rgba> for HighlightStyle {
485 fn from(color: Rgba) -> Self {528 fn from(color: Rgba) -> Self {
486 Self {529 Self {
@@ -489,3 +532,140 @@ impl From<Rgba> for HighlightStyle { · 2 unmodified lines
489 }532 }
490 }533 }
491}534}
535
536pub 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)]
578mod 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 @@
1use crate::{1use crate::{
2 self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,2 self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
3 DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,3 DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,
4 SharedString, StyleRefinement, Visibility,4 SharedString, StyleRefinement, Visibility, WhiteSpace,
5};5};
6use crate::{BoxShadow, TextStyleRefinement};6use crate::{BoxShadow, TextStyleRefinement};
7use smallvec::{smallvec, SmallVec};7use smallvec::{smallvec, SmallVec};
@@ -101,6 +101,24 @@ pub trait Styled: Sized { · 93 unmodified lines
101 self101 self
102 }102 }
103 103
104 /// Sets the whitespace of the element to `normal`.
105 /// [Docs](https://tailwindcss.com/docs/whitespace#normal)
106 fn whitespace_normal(mut self) -> Self {
107 self.text_style()
108 .get_or_insert_with(Default::default)
109 .white_space = Some(WhiteSpace::Normal);
110 self
111 }
112
113 /// Sets the whitespace of the element to `nowrap`.
114 /// [Docs](https://tailwindcss.com/docs/whitespace#nowrap)
115 fn whitespace_nowrap(mut self) -> Self {
116 self.text_style()
117 .get_or_insert_with(Default::default)
118 .white_space = Some(WhiteSpace::Nowrap);
119 self
120 }
121
104 /// Sets the flex direction of the element to `column`.122 /// Sets the flex direction of the element to `column`.
105 /// [Docs](https://tailwindcss.com/docs/flex-direction#column)123 /// [Docs](https://tailwindcss.com/docs/flex-direction#column)
106 fn flex_col(mut self) -> Self {124 fn flex_col(mut self) -> Self {
@@ -343,6 +361,13 @@ pub trait Styled: Sized { · 236 unmodified lines
343 self361 self
344 }362 }
345 363
364 fn text_bg(mut self, bg: impl Into<Hsla>) -> Self {
365 self.text_style()
366 .get_or_insert_with(Default::default)
367 .background_color = Some(bg.into());
368 self
369 }
370
346 fn text_size(mut self, size: impl Into<AbsoluteLength>) -> Self {371 fn text_size(mut self, size: impl Into<AbsoluteLength>) -> Self {
347 self.text_style()372 self.text_style()
348 .get_or_insert_with(Default::default)373 .get_or_insert_with(Default::default)
crates/gpui2/src/text_system.rs+18−3
@@ -196,7 +196,10 @@ impl TextSystem { · 195 unmodified lines
196 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();196 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
197 for run in runs {197 for run in runs {
198 if let Some(last_run) = decoration_runs.last_mut() {198 if let Some(last_run) = decoration_runs.last_mut() {
199 if last_run.color == run.color && last_run.underline == run.underline {199 if last_run.color == run.color
200 && last_run.underline == run.underline
201 && last_run.background_color == run.background_color
202 {
200 last_run.len += run.len as u32;203 last_run.len += run.len as u32;
201 continue;204 continue;
202 }205 }
@@ -204,6 +207,7 @@ impl TextSystem { · 1 unmodified line
204 decoration_runs.push(DecorationRun {207 decoration_runs.push(DecorationRun {
205 len: run.len as u32,208 len: run.len as u32,
206 color: run.color,209 color: run.color,
210 background_color: run.background_color,
207 underline: run.underline.clone(),211 underline: run.underline.clone(),
208 });212 });
209 }213 }
@@ -254,13 +258,16 @@ impl TextSystem { · 44 unmodified lines
254 }258 }
255 259
256 if decoration_runs.last().map_or(false, |last_run| {260 if decoration_runs.last().map_or(false, |last_run| {
257 last_run.color == run.color && last_run.underline == run.underline261 last_run.color == run.color
262 && last_run.underline == run.underline
263 && last_run.background_color == run.background_color
258 }) {264 }) {
259 decoration_runs.last_mut().unwrap().len += run_len_within_line as u32;265 decoration_runs.last_mut().unwrap().len += run_len_within_line as u32;
260 } else {266 } else {
261 decoration_runs.push(DecorationRun {267 decoration_runs.push(DecorationRun {
262 len: run_len_within_line as u32,268 len: run_len_within_line as u32,
263 color: run.color,269 color: run.color,
270 background_color: run.background_color,
264 underline: run.underline.clone(),271 underline: run.underline.clone(),
265 });272 });
266 }273 }
@@ -283,7 +290,15 @@ impl TextSystem { · 16 unmodified lines
283 text: SharedString::from(line_text),290 text: SharedString::from(line_text),
284 });291 });
285 292
286 line_start = line_end + 1; // Skip `\n` character.293 // Skip `\n` character.
294 line_start = line_end + 1;
295 if let Some(run) = runs.peek_mut() {
296 run.len = run.len.saturating_sub(1);
297 if run.len == 0 {
298 runs.next();
299 }
300 }
301
287 font_runs.clear();302 font_runs.clear();
288 }303 }
289 304
crates/gpui2/src/text_system/line.rs+74−14
@@ -1,6 +1,7 @@
1use crate::{1use crate::{
2 black, point, px, BorrowWindow, Bounds, Hsla, LineLayout, Pixels, Point, Result, SharedString,2 black, point, px, size, transparent_black, BorrowWindow, Bounds, Corners, Edges, Hsla,
3 UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,3 LineLayout, Pixels, Point, Result, SharedString, UnderlineStyle, WindowContext, WrapBoundary,
4 WrappedLineLayout,
4};5};
5use derive_more::{Deref, DerefMut};6use derive_more::{Deref, DerefMut};
6use smallvec::SmallVec;7use smallvec::SmallVec;
@@ -10,6 +11,7 @@ use std::sync::Arc; · 3 unmodified lines
10pub struct DecorationRun {11pub struct DecorationRun {
11 pub len: u32,12 pub len: u32,
12 pub color: Hsla,13 pub color: Hsla,
14 pub background_color: Option<Hsla>,
13 pub underline: Option<UnderlineStyle>,15 pub underline: Option<UnderlineStyle>,
14}16}
15 17
@@ -38,7 +40,6 @@ impl ShapedLine { · 22 unmodified lines
38 &self.layout,40 &self.layout,
39 line_height,41 line_height,
40 &self.decoration_runs,42 &self.decoration_runs,
41 None,
42 &[],43 &[],
43 cx,44 cx,
44 )?;45 )?;
@@ -72,7 +73,6 @@ impl WrappedLine { · 27 unmodified lines
72 &self.layout.unwrapped_layout,73 &self.layout.unwrapped_layout,
73 line_height,74 line_height,
74 &self.decoration_runs,75 &self.decoration_runs,
75 self.wrap_width,
76 &self.wrap_boundaries,76 &self.wrap_boundaries,
77 cx,77 cx,
78 )?;78 )?;
@@ -86,7 +86,6 @@ fn paint_line( · 7 unmodified lines
86 layout: &LineLayout,86 layout: &LineLayout,
87 line_height: Pixels,87 line_height: Pixels,
88 decoration_runs: &[DecorationRun],88 decoration_runs: &[DecorationRun],
89 wrap_width: Option<Pixels>,
90 wrap_boundaries: &[WrapBoundary],89 wrap_boundaries: &[WrapBoundary],
91 cx: &mut WindowContext<'_>,90 cx: &mut WindowContext<'_>,
92) -> Result<()> {91) -> Result<()> {
@@ -97,6 +96,7 @@ fn paint_line( · 4 unmodified lines
97 let mut run_end = 0;96 let mut run_end = 0;
98 let mut color = black();97 let mut color = black();
99 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;98 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
99 let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
100 let text_system = cx.text_system().clone();100 let text_system = cx.text_system().clone();
101 let mut glyph_origin = origin;101 let mut glyph_origin = origin;
102 let mut prev_glyph_position = Point::default();102 let mut prev_glyph_position = Point::default();
@@ -110,12 +110,28 @@ fn paint_line( · 7 unmodified lines
110 110
111 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {111 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
112 wraps.next();112 wraps.next();
113 if let Some((underline_origin, underline_style)) = current_underline.take() {113 if let Some((background_origin, background_color)) = current_background.as_mut() {
114 cx.paint_quad(
115 Bounds {
116 origin: *background_origin,
117 size: size(glyph_origin.x - background_origin.x, line_height),
118 },
119 Corners::default(),
120 *background_color,
121 Edges::default(),
122 transparent_black(),
123 );
124 background_origin.x = origin.x;
125 background_origin.y += line_height;
126 }
127 if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
114 cx.paint_underline(128 cx.paint_underline(
115 underline_origin,129 *underline_origin,
116 glyph_origin.x - underline_origin.x,130 glyph_origin.x - underline_origin.x,
117 &underline_style,131 underline_style,
118 )?;132 );
133 underline_origin.x = origin.x;
134 underline_origin.y += line_height;
119 }135 }
120 136
121 glyph_origin.x = origin.x;137 glyph_origin.x = origin.x;
@@ -123,9 +139,20 @@ fn paint_line( · 1 unmodified line
123 }139 }
124 prev_glyph_position = glyph.position;140 prev_glyph_position = glyph.position;
125 141
142 let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
126 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;143 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
127 if glyph.index >= run_end {144 if glyph.index >= run_end {
128 if let Some(style_run) = decoration_runs.next() {145 if let Some(style_run) = decoration_runs.next() {
146 if let Some((_, background_color)) = &mut current_background {
147 if style_run.background_color.as_ref() != Some(background_color) {
148 finished_background = current_background.take();
149 }
150 }
151 if let Some(run_background) = style_run.background_color {
152 current_background
153 .get_or_insert((point(glyph_origin.x, glyph_origin.y), run_background));
154 }
155
129 if let Some((_, underline_style)) = &mut current_underline {156 if let Some((_, underline_style)) = &mut current_underline {
130 if style_run.underline.as_ref() != Some(underline_style) {157 if style_run.underline.as_ref() != Some(underline_style) {
131 finished_underline = current_underline.take();158 finished_underline = current_underline.take();
@@ -135,7 +162,7 @@ fn paint_line( · 3 unmodified lines
135 current_underline.get_or_insert((162 current_underline.get_or_insert((
136 point(163 point(
137 glyph_origin.x,164 glyph_origin.x,
138 origin.y + baseline_offset.y + (layout.descent * 0.618),165 glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
139 ),166 ),
140 UnderlineStyle {167 UnderlineStyle {
141 color: Some(run_underline.color.unwrap_or(style_run.color)),168 color: Some(run_underline.color.unwrap_or(style_run.color)),
@@ -149,16 +176,30 @@ fn paint_line( · 7 unmodified lines
149 color = style_run.color;176 color = style_run.color;
150 } else {177 } else {
151 run_end = layout.len;178 run_end = layout.len;
179 finished_background = current_background.take();
152 finished_underline = current_underline.take();180 finished_underline = current_underline.take();
153 }181 }
154 }182 }
155 183
184 if let Some((background_origin, background_color)) = finished_background {
185 cx.paint_quad(
186 Bounds {
187 origin: background_origin,
188 size: size(glyph_origin.x - background_origin.x, line_height),
189 },
190 Corners::default(),
191 background_color,
192 Edges::default(),
193 transparent_black(),
194 );
195 }
196
156 if let Some((underline_origin, underline_style)) = finished_underline {197 if let Some((underline_origin, underline_style)) = finished_underline {
157 cx.paint_underline(198 cx.paint_underline(
158 underline_origin,199 underline_origin,
159 glyph_origin.x - underline_origin.x,200 glyph_origin.x - underline_origin.x,
160 &underline_style,201 &underline_style,
161 )?;202 );
162 }203 }
163 204
164 let max_glyph_bounds = Bounds {205 let max_glyph_bounds = Bounds {
@@ -188,13 +229,32 @@ fn paint_line( · 23 unmodified lines
188 }229 }
189 }230 }
190 231
232 let mut last_line_end_x = origin.x + layout.width;
233 if let Some(boundary) = wrap_boundaries.last() {
234 let run = &layout.runs[boundary.run_ix];
235 let glyph = &run.glyphs[boundary.glyph_ix];
236 last_line_end_x -= glyph.position.x;
237 }
238
239 if let Some((background_origin, background_color)) = current_background.take() {
240 cx.paint_quad(
241 Bounds {
242 origin: background_origin,
243 size: size(last_line_end_x - background_origin.x, line_height),
244 },
245 Corners::default(),
246 background_color,
247 Edges::default(),
248 transparent_black(),
249 );
250 }
251
191 if let Some((underline_start, underline_style)) = current_underline.take() {252 if let Some((underline_start, underline_style)) = current_underline.take() {
192 let line_end_x = origin.x + wrap_width.unwrap_or(Pixels::MAX).min(layout.width);
193 cx.paint_underline(253 cx.paint_underline(
194 underline_start,254 underline_start,
195 line_end_x - underline_start.x,255 last_line_end_x - underline_start.x,
196 &underline_style,256 &underline_style,
197 )?;257 );
198 }258 }
199 259
200 Ok(())260 Ok(())
crates/gpui2/src/text_system/line_layout.rs+35−0
@@ -198,6 +198,41 @@ impl WrappedLineLayout { · 197 unmodified lines
198 pub fn runs(&self) -> &[ShapedRun] {198 pub fn runs(&self) -> &[ShapedRun] {
199 &self.unwrapped_layout.runs199 &self.unwrapped_layout.runs
200 }200 }
201
202 pub fn index_for_position(
203 &self,
204 position: Point<Pixels>,
205 line_height: Pixels,
206 ) -> Option<usize> {
207 let wrapped_line_ix = (position.y / line_height) as usize;
208
209 let wrapped_line_start_x = if wrapped_line_ix > 0 {
210 let wrap_boundary_ix = wrapped_line_ix - 1;
211 let wrap_boundary = self.wrap_boundaries[wrap_boundary_ix];
212 let run = &self.unwrapped_layout.runs[wrap_boundary.run_ix];
213 run.glyphs[wrap_boundary.glyph_ix].position.x
214 } else {
215 Pixels::ZERO
216 };
217
218 let wrapped_line_end_x = if wrapped_line_ix < self.wrap_boundaries.len() {
219 let next_wrap_boundary_ix = wrapped_line_ix;
220 let next_wrap_boundary = self.wrap_boundaries[next_wrap_boundary_ix];
221 let run = &self.unwrapped_layout.runs[next_wrap_boundary.run_ix];
222 run.glyphs[next_wrap_boundary.glyph_ix].position.x
223 } else {
224 self.unwrapped_layout.width
225 };
226
227 let mut position_in_unwrapped_line = position;
228 position_in_unwrapped_line.x += wrapped_line_start_x;
229 if position_in_unwrapped_line.x > wrapped_line_end_x {
230 None
231 } else {
232 self.unwrapped_layout
233 .index_for_x(position_in_unwrapped_line.x)
234 }
235 }
201}236}
202 237
203pub(crate) struct LineLayoutCache {238pub(crate) struct LineLayoutCache {
crates/gpui2/src/view.rs+88−135
@@ -1,23 +1,17 @@
1use crate::{1use crate::{
2 private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace,2 private::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow,
3 BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, FocusHandle,3 Bounds, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement,
4 FocusableView, LayoutId, Model, Pixels, Point, Size, ViewContext, VisualContext, WeakModel,4 LayoutId, Model, Pixels, Point, Render, Size, ViewContext, VisualContext, WeakModel,
5 WindowContext,5 WindowContext,
6};6};
7use anyhow::{Context, Result};7use anyhow::{Context, Result};
8use std::{8use std::{
9 any::{Any, TypeId},9 any::TypeId,
10 hash::{Hash, Hasher},10 hash::{Hash, Hasher},
11};11};
12 12
13pub trait Render: 'static + Sized {
14 type Element: Element<Self> + 'static;
15
16 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element;
17}
18
19pub struct View<V> {13pub struct View<V> {
20 pub(crate) model: Model<V>,14 pub model: Model<V>,
21}15}
22 16
23impl<V> Sealed for View<V> {}17impl<V> Sealed for View<V> {}
@@ -65,15 +59,15 @@ impl<V: 'static> View<V> { · 41 unmodified lines
65 self.model.read(cx)59 self.model.read(cx)
66 }60 }
67 61
68 pub fn render_with<C>(&self, component: C) -> RenderViewWith<C, V>62 // pub fn render_with<E>(&self, component: E) -> RenderViewWith<E, V>
69 where63 // where
70 C: 'static + Component<V>,64 // E: 'static + Element,
71 {65 // {
72 RenderViewWith {66 // RenderViewWith {
73 view: self.clone(),67 // view: self.clone(),
74 component: Some(component),68 // element: Some(component),
75 }69 // }
76 }70 // }
77 71
78 pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle72 pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle
79 where73 where
@@ -83,6 +77,24 @@ impl<V: 'static> View<V> { · 3 unmodified lines
83 }77 }
84}78}
85 79
80impl<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
86impl<V> Clone for View<V> {98impl<V> Clone for View<V> {
87 fn clone(&self) -> Self {99 fn clone(&self) -> Self {
88 Self {100 Self {
@@ -105,12 +117,6 @@ impl<V> PartialEq for View<V> { · 16 unmodified lines
105 117
106impl<V> Eq for View<V> {}118impl<V> Eq for View<V> {}
107 119
108impl<V: Render, ParentViewState: 'static> Component<ParentViewState> for View<V> {
109 fn render(self) -> AnyElement<ParentViewState> {
110 AnyElement::new(AnyView::from(self))
111 }
112}
113
114pub struct WeakView<V> {120pub struct WeakView<V> {
115 pub(crate) model: WeakModel<V>,121 pub(crate) model: WeakModel<V>,
116}122}
@@ -163,8 +169,8 @@ impl<V> Eq for WeakView<V> {} · 46 unmodified lines
163#[derive(Clone, Debug)]169#[derive(Clone, Debug)]
164pub struct AnyView {170pub struct AnyView {
165 model: AnyModel,171 model: AnyModel,
166 layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>),172 layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
167 paint: fn(&AnyView, &mut AnyBox, &mut WindowContext),173 paint: fn(&AnyView, AnyElement, &mut WindowContext),
168}174}
169 175
170impl AnyView {176impl AnyView {
@@ -202,21 +208,15 @@ impl AnyView { · 31 unmodified lines
202 cx: &mut WindowContext,208 cx: &mut WindowContext,
203 ) {209 ) {
204 cx.with_absolute_element_offset(origin, |cx| {210 cx.with_absolute_element_offset(origin, |cx| {
205 let (layout_id, mut rendered_element) = (self.layout)(self, cx);211 let (layout_id, rendered_element) = (self.layout)(self, cx);
206 cx.window212 cx.window
207 .layout_engine213 .layout_engine
208 .compute_layout(layout_id, available_space);214 .compute_layout(layout_id, available_space);
209 (self.paint)(self, &mut rendered_element, cx);215 (self.paint)(self, rendered_element, cx);
210 })216 })
211 }217 }
212}218}
213 219
214impl<V: 'static> Component<V> for AnyView {
215 fn render(self) -> AnyElement<V> {
216 AnyElement::new(self)
217 }
218}
219
220impl<V: Render> From<View<V>> for AnyView {220impl<V: Render> From<View<V>> for AnyView {
221 fn from(value: View<V>) -> Self {221 fn from(value: View<V>) -> Self {
222 AnyView {222 AnyView {
@@ -227,37 +227,51 @@ impl<V: Render> From<View<V>> for AnyView { · 4 unmodified lines
227 }227 }
228}228}
229 229
230impl<ParentViewState: 'static> Element<ParentViewState> for AnyView {230impl Element for AnyView {
231 type ElementState = Box<dyn Any>;231 type State = Option<AnyElement>;
232
233 fn layout(
234 &mut self,
235 _state: Option<Self::State>,
236 cx: &mut WindowContext,
237 ) -> (LayoutId, Self::State) {
238 let (layout_id, state) = (self.layout)(self, cx);
239 (layout_id, Some(state))
240 }
241
242 fn paint(self, _: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
243 (self.paint)(&self, state.take().unwrap(), cx)
244 }
245}
246
247impl<V: 'static + Render> IntoElement for View<V> {
248 type Element = View<V>;
232 249
233 fn element_id(&self) -> Option<ElementId> {250 fn element_id(&self) -> Option<ElementId> {
234 Some(self.model.entity_id.into())251 Some(ElementId::from_entity_id(self.model.entity_id))
235 }252 }
236 253
237 fn layout(254 fn into_element(self) -> Self::Element {
238 &mut self,255 self
239 _view_state: &mut ParentViewState,
240 _element_state: Option<Self::ElementState>,
241 cx: &mut ViewContext<ParentViewState>,
242 ) -> (LayoutId, Self::ElementState) {
243 (self.layout)(self, cx)
244 }256 }
257}
245 258
246 fn paint(259impl IntoElement for AnyView {
247 &mut self,260 type Element = Self;
248 _bounds: Bounds<Pixels>,261
249 _view_state: &mut ParentViewState,262 fn element_id(&self) -> Option<ElementId> {
250 rendered_element: &mut Self::ElementState,263 Some(ElementId::from_entity_id(self.model.entity_id))
251 cx: &mut ViewContext<ParentViewState>,264 }
252 ) {265
253 (self.paint)(self, rendered_element, cx)266 fn into_element(self) -> Self::Element {
267 self
254 }268 }
255}269}
256 270
257pub struct AnyWeakView {271pub struct AnyWeakView {
258 model: AnyWeakModel,272 model: AnyWeakModel,
259 layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>),273 layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
260 paint: fn(&AnyView, &mut AnyBox, &mut WindowContext),274 paint: fn(&AnyView, AnyElement, &mut WindowContext),
261}275}
262 276
263impl AnyWeakView {277impl AnyWeakView {
@@ -271,7 +285,7 @@ impl AnyWeakView { · 7 unmodified lines
271 }285 }
272}286}
273 287
274impl<V: Render> From<WeakView<V>> for AnyWeakView {288impl<V: 'static + Render> From<WeakView<V>> for AnyWeakView {
275 fn from(view: WeakView<V>) -> Self {289 fn from(view: WeakView<V>) -> Self {
276 Self {290 Self {
277 model: view.model.into(),291 model: view.model.into(),
@@ -281,97 +295,36 @@ impl<V: Render> From<WeakView<V>> for AnyWeakView { · 3 unmodified lines
281 }295 }
282}296}
283 297
284// impl<T, E> Render for T298impl<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
296pub struct RenderViewWith<C, V> {
297 view: View<V>,
298 component: Option<C>,
299}
300
301impl<C, ParentViewState, ViewState> Component<ParentViewState> for RenderViewWith<C, ViewState>
302where
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
312impl<C, ParentViewState, ViewState> Element<ParentViewState> for RenderViewWith<C, ViewState>
313where299where
314 C: 'static + Component<ViewState>,300 T: 'static + FnMut(&mut WindowContext) -> E,
315 ParentViewState: 'static,301 E: 'static + Send + Element,
316 ViewState: 'static,
317{302{
318 type ElementState = AnyElement<ViewState>;303 type Element = E;
319
320 fn element_id(&self) -> Option<ElementId> {
321 Some(self.view.entity_id().into())
322 }
323 304
324 fn layout(305 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
325 &mut self,306 (self)(cx)
326 _: &mut ParentViewState,
327 _: Option<Self::ElementState>,
328 cx: &mut ViewContext<ParentViewState>,
329 ) -> (LayoutId, Self::ElementState) {
330 self.view.update(cx, |view, cx| {
331 let mut element = self.component.take().unwrap().render();
332 let layout_id = element.layout(view, cx);
333 (layout_id, element)
334 })
335 }
336
337 fn paint(
338 &mut self,
339 _: Bounds<Pixels>,
340 _: &mut ParentViewState,
341 element: &mut Self::ElementState,
342 cx: &mut ViewContext<ParentViewState>,
343 ) {
344 self.view.update(cx, |view, cx| element.paint(view, cx))
345 }307 }
346}308}
347 309
348mod any_view {310mod any_view {
349 use crate::{AnyElement, AnyView, BorrowWindow, LayoutId, Render, WindowContext};311 use crate::{AnyElement, AnyView, Element, LayoutId, Render, WindowContext};
350 use std::any::Any;
351 312
352 pub(crate) fn layout<V: Render>(313 pub(crate) fn layout<V: 'static + Render>(
353 view: &AnyView,314 view: &AnyView,
354 cx: &mut WindowContext,315 cx: &mut WindowContext,
355 ) -> (LayoutId, Box<dyn Any>) {316 ) -> (LayoutId, AnyElement) {
356 cx.with_element_id(Some(view.model.entity_id), |cx| {317 let view = view.clone().downcast::<V>().unwrap();
357 let view = view.clone().downcast::<V>().unwrap();318 let mut element = view.update(cx, |view, cx| view.render(cx).into_any());
358 view.update(cx, |view, cx| {319 let layout_id = element.layout(cx);
359 let mut element = AnyElement::new(view.render(cx));320 (layout_id, element)
360 let layout_id = element.layout(view, cx);
361 (layout_id, Box::new(element) as Box<dyn Any>)
362 })
363 })
364 }321 }
365 322
366 pub(crate) fn paint<V: Render>(323 pub(crate) fn paint<V: 'static + Render>(
367 view: &AnyView,324 _view: &AnyView,
368 element: &mut Box<dyn Any>,325 element: AnyElement,
369 cx: &mut WindowContext,326 cx: &mut WindowContext,
370 ) {327 ) {
371 cx.with_element_id(Some(view.model.entity_id), |cx| {328 element.paint(cx);
372 let view = view.clone().downcast::<V>().unwrap();
373 let element = element.downcast_mut::<AnyElement<V>>().unwrap();
374 view.update(cx, |view, cx| element.paint(view, cx))
375 })
376 }329 }
377}330}
crates/gpui2/src/window.rs+182−80
@@ -1,5 +1,5 @@
1use crate::{1use crate::{
2 key_dispatch::DispatchActionListener, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,2 key_dispatch::DispatchActionListener, px, size, Action, AnyDrag, AnyView, AppContext,
3 AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,3 AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
4 DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,4 DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
5 EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla,5 EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla,
@@ -187,17 +187,17 @@ impl Drop for FocusHandle { · 181 unmodified lines
187 187
188/// FocusableView allows users of your view to easily188/// FocusableView allows users of your view to easily
189/// focus it (using cx.focus_view(view))189/// focus it (using cx.focus_view(view))
190pub trait FocusableView: Render {190pub trait FocusableView: 'static + Render {
191 fn focus_handle(&self, cx: &AppContext) -> FocusHandle;191 fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
192}192}
193 193
194/// ManagedView is a view (like a Modal, Popover, Menu, etc.)194/// ManagedView is a view (like a Modal, Popover, Menu, etc.)
195/// where the lifecycle of the view is handled by another view.195/// where the lifecycle of the view is handled by another view.
196pub trait ManagedView: FocusableView + EventEmitter<Manager> {}196pub trait ManagedView: FocusableView + EventEmitter<DismissEvent> {}
197 197
198impl<M: FocusableView + EventEmitter<Manager>> ManagedView for M {}198impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {}
199 199
200pub enum Manager {200pub enum DismissEvent {
201 Dismiss,201 Dismiss,
202}202}
203 203
@@ -230,9 +230,15 @@ pub struct Window { · 26 unmodified lines
230 pub(crate) focus: Option<FocusId>,230 pub(crate) focus: Option<FocusId>,
231}231}
232 232
233pub(crate) struct ElementStateBox {
234 inner: Box<dyn Any>,
235 #[cfg(debug_assertions)]
236 type_name: &'static str,
237}
238
233// #[derive(Default)]239// #[derive(Default)]
234pub(crate) struct Frame {240pub(crate) struct Frame {
235 pub(crate) element_states: HashMap<GlobalElementId, AnyBox>,241 pub(crate) element_states: HashMap<GlobalElementId, ElementStateBox>,
236 mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,242 mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
237 pub(crate) dispatch_tree: DispatchTree,243 pub(crate) dispatch_tree: DispatchTree,
238 pub(crate) focus_listeners: Vec<AnyFocusListener>,244 pub(crate) focus_listeners: Vec<AnyFocusListener>,
@@ -875,7 +881,7 @@ impl<'a> WindowContext<'a> { · 636 unmodified lines
875 origin: Point<Pixels>,881 origin: Point<Pixels>,
876 width: Pixels,882 width: Pixels,
877 style: &UnderlineStyle,883 style: &UnderlineStyle,
878 ) -> Result<()> {884 ) {
879 let scale_factor = self.scale_factor();885 let scale_factor = self.scale_factor();
880 let height = if style.wavy {886 let height = if style.wavy {
881 style.thickness * 3.887 style.thickness * 3.
@@ -899,7 +905,6 @@ impl<'a> WindowContext<'a> { · 17 unmodified lines
899 wavy: style.wavy,905 wavy: style.wavy,
900 },906 },
901 );907 );
902 Ok(())
903 }908 }
904 909
905 /// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index.910 /// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index.
@@ -1436,6 +1441,89 @@ impl<'a> WindowContext<'a> { · 530 unmodified lines
1436 .dispatch_tree1441 .dispatch_tree
1437 .bindings_for_action(action)1442 .bindings_for_action(action)
1438 }1443 }
1444
1445 pub fn listener_for<V: Render, E>(
1446 &self,
1447 view: &View<V>,
1448 f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,
1449 ) -> impl Fn(&E, &mut WindowContext) + 'static {
1450 let view = view.downgrade();
1451 move |e: &E, cx: &mut WindowContext| {
1452 view.update(cx, |view, cx| f(view, e, cx)).ok();
1453 }
1454 }
1455
1456 pub fn constructor_for<V: Render, R>(
1457 &self,
1458 view: &View<V>,
1459 f: impl Fn(&mut V, &mut ViewContext<V>) -> R + 'static,
1460 ) -> impl Fn(&mut WindowContext) -> R + 'static {
1461 let view = view.clone();
1462 move |cx: &mut WindowContext| view.update(cx, |view, cx| f(view, cx))
1463 }
1464
1465 //========== ELEMENT RELATED FUNCTIONS ===========
1466 pub fn with_key_dispatch<R>(
1467 &mut self,
1468 context: KeyContext,
1469 focus_handle: Option<FocusHandle>,
1470 f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
1471 ) -> R {
1472 let window = &mut self.window;
1473 window
1474 .current_frame
1475 .dispatch_tree
1476 .push_node(context.clone());
1477 if let Some(focus_handle) = focus_handle.as_ref() {
1478 window
1479 .current_frame
1480 .dispatch_tree
1481 .make_focusable(focus_handle.id);
1482 }
1483 let result = f(focus_handle, self);
1484
1485 self.window.current_frame.dispatch_tree.pop_node();
1486
1487 result
1488 }
1489
1490 /// Register a focus listener for the current frame only. It will be cleared
1491 /// on the next frame render. You should use this method only from within elements,
1492 /// and we may want to enforce that better via a different context type.
1493 // todo!() Move this to `FrameContext` to emphasize its individuality?
1494 pub fn on_focus_changed(
1495 &mut self,
1496 listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static,
1497 ) {
1498 self.window
1499 .current_frame
1500 .focus_listeners
1501 .push(Box::new(move |event, cx| {
1502 listener(event, cx);
1503 }));
1504 }
1505
1506 /// Set an input handler, such as [ElementInputHandler], which interfaces with the
1507 /// platform to receive textual input with proper integration with concerns such
1508 /// as IME interactions.
1509 pub fn handle_input(
1510 &mut self,
1511 focus_handle: &FocusHandle,
1512 input_handler: impl PlatformInputHandler,
1513 ) {
1514 if focus_handle.is_focused(self) {
1515 self.window
1516 .platform_window
1517 .set_input_handler(Box::new(input_handler));
1518 }
1519 }
1520
1521 pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) {
1522 let mut this = self.to_async();
1523 self.window
1524 .platform_window
1525 .on_should_close(Box::new(move || this.update(|_, cx| f(cx)).unwrap_or(true)))
1526 }
1439}1527}
1440 1528
1441impl Context for WindowContext<'_> {1529impl Context for WindowContext<'_> {
@@ -1559,7 +1647,7 @@ impl VisualContext for WindowContext<'_> { · 117 unmodified lines
1559 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,1647 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
1560 ) -> Self::Result<View<V>>1648 ) -> Self::Result<View<V>>
1561 where1649 where
1562 V: Render,1650 V: 'static + Render,
1563 {1651 {
1564 let slot = self.app.entities.reserve();1652 let slot = self.app.entities.reserve();
1565 let view = View {1653 let view = View {
@@ -1582,7 +1670,7 @@ impl VisualContext for WindowContext<'_> { · 16 unmodified lines
1582 where1670 where
1583 V: ManagedView,1671 V: ManagedView,
1584 {1672 {
1585 self.update_view(view, |_, cx| cx.emit(Manager::Dismiss))1673 self.update_view(view, |_, cx| cx.emit(DismissEvent::Dismiss))
1586 }1674 }
1587}1675}
1588 1676
@@ -1617,6 +1705,10 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { · 28 unmodified lines
1617 self.borrow_mut()1705 self.borrow_mut()
1618 }1706 }
1619 1707
1708 fn app(&self) -> &AppContext {
1709 self.borrow()
1710 }
1711
1620 fn window(&self) -> &Window {1712 fn window(&self) -> &Window {
1621 self.borrow()1713 self.borrow()
1622 }1714 }
@@ -1667,6 +1759,24 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { · 44 unmodified lines
1667 }1759 }
1668 }1760 }
1669 1761
1762 /// Invoke the given function with the content mask reset to that
1763 /// of the window.
1764 fn break_content_mask<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
1765 let mask = ContentMask {
1766 bounds: Bounds {
1767 origin: Point::default(),
1768 size: self.window().viewport_size,
1769 },
1770 };
1771 self.window_mut()
1772 .current_frame
1773 .content_mask_stack
1774 .push(mask);
1775 let result = f(self);
1776 self.window_mut().current_frame.content_mask_stack.pop();
1777 result
1778 }
1779
1670 /// Update the global element offset relative to the current offset. This is used to implement1780 /// Update the global element offset relative to the current offset. This is used to implement
1671 /// scrolling.1781 /// scrolling.
1672 fn with_element_offset<R>(1782 fn with_element_offset<R>(
@@ -1735,10 +1845,37 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { · 62 unmodified lines
1735 .remove(&global_id)1845 .remove(&global_id)
1736 })1846 })
1737 {1847 {
1848 let ElementStateBox {
1849 inner,
1850
1851 #[cfg(debug_assertions)]
1852 type_name
1853 } = any;
1738 // Using the extra inner option to avoid needing to reallocate a new box.1854 // Using the extra inner option to avoid needing to reallocate a new box.
1739 let mut state_box = any1855 let mut state_box = inner
1740 .downcast::<Option<S>>()1856 .downcast::<Option<S>>()
1741 .expect("invalid element state type for id");1857 .map_err(|_| {
1858 #[cfg(debug_assertions)]
1859 {
1860 anyhow!(
1861 "invalid element state type for id, requested_type {:?}, actual type: {:?}",
1862 std::any::type_name::<S>(),
1863 type_name
1864 )
1865 }
1866
1867 #[cfg(not(debug_assertions))]
1868 {
1869 anyhow!(
1870 "invalid element state type for id, requested_type {:?}",
1871 std::any::type_name::<S>(),
1872 )
1873 }
1874 })
1875 .unwrap();
1876
1877 // Actual: Option<AnyElement> <- View
1878 // Requested: () <- AnyElemet
1742 let state = state_box1879 let state = state_box
1743 .take()1880 .take()
1744 .expect("element state is already on the stack");1881 .expect("element state is already on the stack");
@@ -1747,14 +1884,27 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { · 2 unmodified lines
1747 cx.window_mut()1884 cx.window_mut()
1748 .current_frame1885 .current_frame
1749 .element_states1886 .element_states
1750 .insert(global_id, state_box);1887 .insert(global_id, ElementStateBox {
1888 inner: state_box,
1889
1890 #[cfg(debug_assertions)]
1891 type_name
1892 });
1751 result1893 result
1752 } else {1894 } else {
1753 let (result, state) = f(None, cx);1895 let (result, state) = f(None, cx);
1754 cx.window_mut()1896 cx.window_mut()
1755 .current_frame1897 .current_frame
1756 .element_states1898 .element_states
1757 .insert(global_id, Box::new(Some(state)));1899 .insert(global_id,
1900 ElementStateBox {
1901 inner: Box::new(Some(state)),
1902
1903 #[cfg(debug_assertions)]
1904 type_name: std::any::type_name::<S>()
1905 }
1906
1907 );
1758 result1908 result
1759 }1909 }
1760 })1910 })
@@ -2124,49 +2274,6 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 363 unmodified lines
2124 )2274 )
2125 }2275 }
2126 2276
2127 /// Register a focus listener for the current frame only. It will be cleared
2128 /// on the next frame render. You should use this method only from within elements,
2129 /// and we may want to enforce that better via a different context type.
2130 // todo!() Move this to `FrameContext` to emphasize its individuality?
2131 pub fn on_focus_changed(
2132 &mut self,
2133 listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
2134 ) {
2135 let handle = self.view().downgrade();
2136 self.window
2137 .current_frame
2138 .focus_listeners
2139 .push(Box::new(move |event, cx| {
2140 handle
2141 .update(cx, |view, cx| listener(view, event, cx))
2142 .log_err();
2143 }));
2144 }
2145
2146 pub fn with_key_dispatch<R>(
2147 &mut self,
2148 context: KeyContext,
2149 focus_handle: Option<FocusHandle>,
2150 f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
2151 ) -> R {
2152 let window = &mut self.window;
2153 window
2154 .current_frame
2155 .dispatch_tree
2156 .push_node(context.clone());
2157 if let Some(focus_handle) = focus_handle.as_ref() {
2158 window
2159 .current_frame
2160 .dispatch_tree
2161 .make_focusable(focus_handle.id);
2162 }
2163 let result = f(focus_handle, self);
2164
2165 self.window.current_frame.dispatch_tree.pop_node();
2166
2167 result
2168 }
2169
2170 pub fn spawn<Fut, R>(2277 pub fn spawn<Fut, R>(
2171 &mut self,2278 &mut self,
2172 f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut,2279 f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut,
@@ -2243,21 +2350,6 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 70 unmodified lines
2243 });2350 });
2244 }2351 }
2245 2352
2246 /// Set an input handler, such as [ElementInputHandler], which interfaces with the
2247 /// platform to receive textual input with proper integration with concerns such
2248 /// as IME interactions.
2249 pub fn handle_input(
2250 &mut self,
2251 focus_handle: &FocusHandle,
2252 input_handler: impl PlatformInputHandler,
2253 ) {
2254 if focus_handle.is_focused(self) {
2255 self.window
2256 .platform_window
2257 .set_input_handler(Box::new(input_handler));
2258 }
2259 }
2260
2261 pub fn emit<Evt>(&mut self, event: Evt)2353 pub fn emit<Evt>(&mut self, event: Evt)
2262 where2354 where
2263 Evt: 'static,2355 Evt: 'static,
@@ -2282,7 +2374,17 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 18 unmodified lines
2282 where2374 where
2283 V: ManagedView,2375 V: ManagedView,
2284 {2376 {
2285 self.defer(|_, cx| cx.emit(Manager::Dismiss))2377 self.defer(|_, cx| cx.emit(DismissEvent::Dismiss))
2378 }
2379
2380 pub fn listener<E>(
2381 &self,
2382 f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,
2383 ) -> impl Fn(&E, &mut WindowContext) + 'static {
2384 let view = self.view().downgrade();
2385 move |e: &E, cx: &mut WindowContext| {
2386 view.update(cx, |view, cx| f(view, e, cx)).ok();
2387 }
2286 }2388 }
2287}2389}
2288 2390
@@ -2355,7 +2457,7 @@ impl<V: 'static> VisualContext for ViewContext<'_, V> { · 66 unmodified lines
2355 build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W,2457 build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W,
2356 ) -> Self::Result<View<W>>2458 ) -> Self::Result<View<W>>
2357 where2459 where
2358 W: Render,2460 W: 'static + Render,
2359 {2461 {
2360 self.window_cx.replace_root_view(build_view)2462 self.window_cx.replace_root_view(build_view)
2361 }2463 }
@@ -2567,6 +2669,12 @@ pub enum ElementId { · 205 unmodified lines
2567 FocusHandle(FocusId),2669 FocusHandle(FocusId),
2568}2670}
2569 2671
2672impl ElementId {
2673 pub(crate) fn from_entity_id(entity_id: EntityId) -> Self {
2674 ElementId::View(entity_id)
2675 }
2676}
2677
2570impl TryInto<SharedString> for ElementId {2678impl TryInto<SharedString> for ElementId {
2571 type Error = anyhow::Error;2679 type Error = anyhow::Error;
2572 2680
@@ -2579,12 +2687,6 @@ impl TryInto<SharedString> for ElementId { · 6 unmodified lines
2579 }2687 }
2580}2688}
2581 2689
2582impl From<EntityId> for ElementId {
2583 fn from(id: EntityId) -> Self {
2584 ElementId::View(id)
2585 }
2586}
2587
2588impl From<usize> for ElementId {2690impl From<usize> for ElementId {
2589 fn from(id: usize) -> Self {2691 fn from(id: usize) -> Self {
2590 ElementId::Integer(id)2692 ElementId::Integer(id)
crates/gpui2_macros/src/derive_component.rs+0−66
@@ -1,66 +0,0 @@
1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, parse_quote, DeriveInput};
4
5pub 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
42fn 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 @@
1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput};
4
5pub 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 @@
1mod action;1mod action;
2mod derive_component;2mod derive_into_element;
3mod register_action;3mod register_action;
4mod style_helpers;4mod style_helpers;
5mod test;5mod test;
6 6
7use proc_macro::TokenStream;7use proc_macro::TokenStream;
8 8
9#[proc_macro]
10pub fn style_helpers(args: TokenStream) -> TokenStream {
11 style_helpers::style_helpers(args)
12}
13
14#[proc_macro_derive(Action)]9#[proc_macro_derive(Action)]
15pub fn action(input: TokenStream) -> TokenStream {10pub fn action(input: TokenStream) -> TokenStream {
16 action::action(input)11 action::action(input)
@@ -21,9 +16,14 @@ pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream { · 4 unmodified lines
21 register_action::register_action_macro(attr, item)16 register_action::register_action_macro(attr, item)
22}17}
23 18
24#[proc_macro_derive(Component, attributes(component))]19#[proc_macro_derive(IntoElement)]
25pub fn derive_component(input: TokenStream) -> TokenStream {20pub fn derive_into_element(input: TokenStream) -> TokenStream {
26 derive_component::derive_component(input)21 derive_into_element::derive_into_element(input)
22}
23
24#[proc_macro]
25pub fn style_helpers(input: TokenStream) -> TokenStream {
26 style_helpers::style_helpers(input)
27}27}
28 28
29#[proc_macro_attribute]29#[proc_macro_attribute]
crates/language/src/highlight_map.rs+4−4
@@ -11,7 +11,7 @@ pub struct HighlightId(pub u32); · 10 unmodified lines
11const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);11const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
12 12
13impl HighlightMap {13impl HighlightMap {
14 pub fn new(capture_names: &[String], theme: &SyntaxTheme) -> Self {14 pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self {
15 // For each capture name in the highlight query, find the longest15 // For each capture name in the highlight query, find the longest
16 // key in the theme's syntax styles that matches all of the16 // key in the theme's syntax styles that matches all of the
17 // dot-separated components of the capture name.17 // dot-separated components of the capture name.
@@ -98,9 +98,9 @@ mod tests { · 80 unmodified lines
98 );98 );
99 99
100 let capture_names = &[100 let capture_names = &[
101 "function.special".to_string(),101 "function.special",
102 "function.async.rust".to_string(),102 "function.async.rust",
103 "variable.builtin.self".to_string(),103 "variable.builtin.self",
104 ];104 ];
105 105
106 let map = HighlightMap::new(capture_names, &theme);106 let map = HighlightMap::new(capture_names, &theme);
crates/language/src/language.rs+2−2
@@ -1383,7 +1383,7 @@ impl Language { · 1382 unmodified lines
1383 let query = Query::new(self.grammar_mut().ts_language, source)?;1383 let query = Query::new(self.grammar_mut().ts_language, source)?;
1384 1384
1385 let mut override_configs_by_id = HashMap::default();1385 let mut override_configs_by_id = HashMap::default();
1386 for (ix, name) in query.capture_names().iter().enumerate() {1386 for (ix, name) in query.capture_names().iter().copied().enumerate() {
1387 if !name.starts_with('_') {1387 if !name.starts_with('_') {
1388 let value = self.config.overrides.remove(name).unwrap_or_default();1388 let value = self.config.overrides.remove(name).unwrap_or_default();
1389 for server_name in &value.opt_into_language_servers {1389 for server_name in &value.opt_into_language_servers {
@@ -1396,7 +1396,7 @@ impl Language { · 6 unmodified lines
1396 }1396 }
1397 }1397 }
1398 1398
1399 override_configs_by_id.insert(ix as u32, (name.clone(), value));1399 override_configs_by_id.insert(ix as u32, (name.into(), value));
1400 }1400 }
1401 }1401 }
1402 1402
crates/language/src/syntax_map/syntax_map_tests.rs+1−1
@@ -1300,7 +1300,7 @@ fn assert_capture_ranges( · 1299 unmodified lines
1300 .collect::<Vec<_>>();1300 .collect::<Vec<_>>();
1301 for capture in captures {1301 for capture in captures {
1302 let name = &queries[capture.grammar_index].capture_names()[capture.index as usize];1302 let name = &queries[capture.grammar_index].capture_names()[capture.index as usize];
1303 if highlight_query_capture_names.contains(&name.as_str()) {1303 if highlight_query_capture_names.contains(&name) {
1304 actual_ranges.push(capture.node.byte_range());1304 actual_ranges.push(capture.node.byte_range());
1305 }1305 }
1306 }1306 }
crates/language2/src/buffer.rs+41−0
@@ -7,6 +7,7 @@ pub use crate::{ · 6 unmodified lines
7use crate::{7use crate::{
8 diagnostic_set::{DiagnosticEntry, DiagnosticGroup},8 diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
9 language_settings::{language_settings, LanguageSettings},9 language_settings::{language_settings, LanguageSettings},
10 markdown::parse_markdown,
10 outline::OutlineItem,11 outline::OutlineItem,
11 syntax_map::{12 syntax_map::{
12 SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,13 SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
@@ -155,12 +156,52 @@ pub struct Diagnostic { · 142 unmodified lines
155 pub is_unnecessary: bool,156 pub is_unnecessary: bool,
156}157}
157 158
159pub 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)]
191pub enum Documentation {
192 Undocumented,
193 SingleLine(String),
194 MultiLinePlainText(String),
195 MultiLineMarkdown(ParsedMarkdown),
196}
197
158#[derive(Clone, Debug)]198#[derive(Clone, Debug)]
159pub struct Completion {199pub struct Completion {
160 pub old_range: Range<Anchor>,200 pub old_range: Range<Anchor>,
161 pub new_text: String,201 pub new_text: String,
162 pub label: CodeLabel,202 pub label: CodeLabel,
163 pub server_id: LanguageServerId,203 pub server_id: LanguageServerId,
204 pub documentation: Option<Documentation>,
164 pub lsp_completion: lsp::CompletionItem,205 pub lsp_completion: lsp::CompletionItem,
165}206}
166 207
crates/language2/src/highlight_map.rs+4−4
@@ -11,7 +11,7 @@ pub struct HighlightId(pub u32); · 10 unmodified lines
11const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);11const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
12 12
13impl HighlightMap {13impl HighlightMap {
14 pub fn new(capture_names: &[String], theme: &SyntaxTheme) -> Self {14 pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self {
15 // For each capture name in the highlight query, find the longest15 // For each capture name in the highlight query, find the longest
16 // key in the theme's syntax styles that matches all of the16 // key in the theme's syntax styles that matches all of the
17 // dot-separated components of the capture name.17 // dot-separated components of the capture name.
@@ -100,9 +100,9 @@ mod tests { · 82 unmodified lines
100 };100 };
101 101
102 let capture_names = &[102 let capture_names = &[
103 "function.special".to_string(),103 "function.special",
104 "function.async.rust".to_string(),104 "function.async.rust",
105 "variable.builtin.self".to_string(),105 "variable.builtin.self",
106 ];106 ];
107 107
108 let map = HighlightMap::new(capture_names, &theme);108 let map = HighlightMap::new(capture_names, &theme);
crates/language2/src/language2.rs+2−2
@@ -1391,7 +1391,7 @@ impl Language { · 1390 unmodified lines
1391 let mut override_configs_by_id = HashMap::default();1391 let mut override_configs_by_id = HashMap::default();
1392 for (ix, name) in query.capture_names().iter().enumerate() {1392 for (ix, name) in query.capture_names().iter().enumerate() {
1393 if !name.starts_with('_') {1393 if !name.starts_with('_') {
1394 let value = self.config.overrides.remove(name).unwrap_or_default();1394 let value = self.config.overrides.remove(*name).unwrap_or_default();
1395 for server_name in &value.opt_into_language_servers {1395 for server_name in &value.opt_into_language_servers {
1396 if !self1396 if !self
1397 .config1397 .config
@@ -1402,7 +1402,7 @@ impl Language { · 4 unmodified lines
1402 }1402 }
1403 }1403 }
1404 1404
1405 override_configs_by_id.insert(ix as u32, (name.clone(), value));1405 override_configs_by_id.insert(ix as u32, (name.to_string(), value));
1406 }1406 }
1407 }1407 }
1408 1408
crates/language2/src/proto.rs+1−0
@@ -482,6 +482,7 @@ pub async fn deserialize_completion( · 481 unmodified lines
482 lsp_completion.filter_text.as_deref(),482 lsp_completion.filter_text.as_deref(),
483 )483 )
484 }),484 }),
485 documentation: None,
485 server_id: LanguageServerId(completion.server_id as usize),486 server_id: LanguageServerId(completion.server_id as usize),
486 lsp_completion,487 lsp_completion,
487 })488 })
crates/language2/src/syntax_map/syntax_map_tests.rs+1−1
@@ -1300,7 +1300,7 @@ fn assert_capture_ranges( · 1299 unmodified lines
1300 .collect::<Vec<_>>();1300 .collect::<Vec<_>>();
1301 for capture in captures {1301 for capture in captures {
1302 let name = &queries[capture.grammar_index].capture_names()[capture.index as usize];1302 let name = &queries[capture.grammar_index].capture_names()[capture.index as usize];
1303 if highlight_query_capture_names.contains(&name.as_str()) {1303 if highlight_query_capture_names.contains(&name) {
1304 actual_ranges.push(capture.node.byte_range());1304 actual_ranges.push(capture.node.byte_range());
1305 }1305 }
1306 }1306 }
crates/live_kit_client2/build.rs+11−1
@@ -61,12 +61,14 @@ fn build_bridge(swift_target: &SwiftTarget) { · 60 unmodified lines
61 61
62 let swift_package_root = swift_package_root();62 let swift_package_root = swift_package_root();
63 let swift_target_folder = swift_target_folder();63 let swift_target_folder = swift_target_folder();
64 let swift_cache_folder = swift_cache_folder();
64 if !Command::new("swift")65 if !Command::new("swift")
65 .arg("build")66 .arg("build")
66 .arg("--disable-automatic-resolution")67 .arg("--disable-automatic-resolution")
67 .args(["--configuration", &env::var("PROFILE").unwrap()])68 .args(["--configuration", &env::var("PROFILE").unwrap()])
68 .args(["--triple", &swift_target.target.triple])69 .args(["--triple", &swift_target.target.triple])
69 .args(["--build-path".into(), swift_target_folder])70 .args(["--build-path".into(), swift_target_folder])
71 .args(["--cache-path".into(), swift_cache_folder])
70 .current_dir(&swift_package_root)72 .current_dir(&swift_package_root)
71 .status()73 .status()
72 .unwrap()74 .unwrap()
@@ -133,9 +135,17 @@ fn swift_package_root() -> PathBuf { · 60 unmodified lines
133}135}
134 136
135fn swift_target_folder() -> PathBuf {137fn swift_target_folder() -> PathBuf {
138 let target = env::var("TARGET").unwrap();
136 env::current_dir()139 env::current_dir()
137 .unwrap()140 .unwrap()
138 .join(format!("../../target/{SWIFT_PACKAGE_NAME}"))141 .join(format!("../../target/{target}/{SWIFT_PACKAGE_NAME}_target"))
142}
143
144fn swift_cache_folder() -> PathBuf {
145 let target = env::var("TARGET").unwrap();
146 env::current_dir()
147 .unwrap()
148 .join(format!("../../target/{target}/{SWIFT_PACKAGE_NAME}_cache"))
139}149}
140 150
141fn copy_dir(source: &Path, destination: &Path) {151fn copy_dir(source: &Path, destination: &Path) {
crates/node_runtime/src/node_runtime.rs+8−6
@@ -73,6 +73,7 @@ impl RealNodeRuntime { · 72 unmodified lines
73 let npm_file = node_dir.join("bin/npm");73 let npm_file = node_dir.join("bin/npm");
74 74
75 let result = Command::new(&node_binary)75 let result = Command::new(&node_binary)
76 .env_clear()
76 .arg(npm_file)77 .arg(npm_file)
77 .arg("--version")78 .arg("--version")
78 .stdin(Stdio::null())79 .stdin(Stdio::null())
@@ -149,6 +150,7 @@ impl NodeRuntime for RealNodeRuntime { · 70 unmodified lines
149 }150 }
150 151
151 let mut command = Command::new(node_binary);152 let mut command = Command::new(node_binary);
153 command.env_clear();
152 command.env("PATH", env_path);154 command.env("PATH", env_path);
153 command.arg(npm_file).arg(subcommand);155 command.arg(npm_file).arg(subcommand);
154 command.args(["--cache".into(), installation_path.join("cache")]);156 command.args(["--cache".into(), installation_path.join("cache")]);
@@ -200,11 +202,11 @@ impl NodeRuntime for RealNodeRuntime { · 45 unmodified lines
200 &[202 &[
201 name,203 name,
202 "--json",204 "--json",
203 "-fetch-retry-mintimeout",205 "--fetch-retry-mintimeout",
204 "2000",206 "2000",
205 "-fetch-retry-maxtimeout",207 "--fetch-retry-maxtimeout",
206 "5000",208 "5000",
207 "-fetch-timeout",209 "--fetch-timeout",
208 "5000",210 "5000",
209 ],211 ],
210 )212 )
@@ -229,11 +231,11 @@ impl NodeRuntime for RealNodeRuntime { · 18 unmodified lines
229 231
230 let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect();232 let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect();
231 arguments.extend_from_slice(&[233 arguments.extend_from_slice(&[
232 "-fetch-retry-mintimeout",234 "--fetch-retry-mintimeout",
233 "2000",235 "2000",
234 "-fetch-retry-maxtimeout",236 "--fetch-retry-maxtimeout",
235 "5000",237 "5000",
236 "-fetch-timeout",238 "--fetch-timeout",
237 "5000",239 "5000",
238 ]);240 ]);
239 241
crates/picker2/src/picker2.rs+46−39
@@ -1,10 +1,10 @@
1use editor::Editor;1use editor::Editor;
2use gpui::{2use gpui::{
3 div, prelude::*, uniform_list, AppContext, Component, Div, FocusHandle, FocusableView,3 div, prelude::*, uniform_list, AppContext, Div, FocusHandle, FocusableView, MouseButton,
4 MouseButton, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext,4 MouseDownEvent, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext,
5};5};
6use std::{cmp, sync::Arc};6use std::{cmp, sync::Arc};
7use ui::{prelude::*, v_stack, Divider, Label, TextColor};7use ui::{prelude::*, v_stack, Color, Divider, Label};
8 8
9pub struct Picker<D: PickerDelegate> {9pub struct Picker<D: PickerDelegate> {
10 pub delegate: D,10 pub delegate: D,
@@ -15,7 +15,7 @@ pub struct Picker<D: PickerDelegate> { · 4 unmodified lines
15}15}
16 16
17pub trait PickerDelegate: Sized + 'static {17pub trait PickerDelegate: Sized + 'static {
18 type ListItem: Component<Picker<Self>>;18 type ListItem: IntoElement;
19 19
20 fn match_count(&self) -> usize;20 fn match_count(&self) -> usize;
21 fn selected_index(&self) -> usize;21 fn selected_index(&self) -> usize;
@@ -32,7 +32,7 @@ pub trait PickerDelegate: Sized + 'static { · 10 unmodified lines
32 ix: usize,32 ix: usize,
33 selected: bool,33 selected: bool,
34 cx: &mut ViewContext<Picker<Self>>,34 cx: &mut ViewContext<Picker<Self>>,
35 ) -> Self::ListItem;35 ) -> Option<Self::ListItem>;
36}36}
37 37
38impl<D: PickerDelegate> FocusableView for Picker<D> {38impl<D: PickerDelegate> FocusableView for Picker<D> {
@@ -114,6 +114,7 @@ impl<D: PickerDelegate> Picker<D> { · 75 unmodified lines
114 }114 }
115 115
116 fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {116 fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
117 dbg!("canceling!");
117 self.delegate.dismissed(cx);118 self.delegate.dismissed(cx);
118 }119 }
119 120
@@ -181,20 +182,20 @@ impl<D: PickerDelegate> Picker<D> { · 61 unmodified lines
181}182}
182 183
183impl<D: PickerDelegate> Render for Picker<D> {184impl<D: PickerDelegate> Render for Picker<D> {
184 type Element = Div<Self>;185 type Element = Div;
185 186
186 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {187 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
187 div()188 div()
188 .key_context("picker")189 .key_context("picker")
189 .size_full()190 .size_full()
190 .elevation_2(cx)191 .elevation_2(cx)
191 .on_action(Self::select_next)192 .on_action(cx.listener(Self::select_next))
192 .on_action(Self::select_prev)193 .on_action(cx.listener(Self::select_prev))
193 .on_action(Self::select_first)194 .on_action(cx.listener(Self::select_first))
194 .on_action(Self::select_last)195 .on_action(cx.listener(Self::select_last))
195 .on_action(Self::cancel)196 .on_action(cx.listener(Self::cancel))
196 .on_action(Self::confirm)197 .on_action(cx.listener(Self::confirm))
197 .on_action(Self::secondary_confirm)198 .on_action(cx.listener(Self::secondary_confirm))
198 .child(199 .child(
199 v_stack()200 v_stack()
200 .py_0p5()201 .py_0p5()
@@ -208,31 +209,37 @@ impl<D: PickerDelegate> Render for Picker<D> { · 7 unmodified lines
208 .p_1()209 .p_1()
209 .grow()210 .grow()
210 .child(211 .child(
211 uniform_list("candidates", self.delegate.match_count(), {212 uniform_list(
212 move |this: &mut Self, visible_range, cx| {213 cx.view().clone(),
213 let selected_ix = this.delegate.selected_index();214 "candidates",
214 visible_range215 self.delegate.match_count(),
215 .map(|ix| {216 {
216 div()217 let selected_index = self.delegate.selected_index();
217 .on_mouse_down(218
218 MouseButton::Left,219 move |picker, visible_range, cx| {
219 move |this: &mut Self, event, cx| {220 visible_range
220 this.handle_click(221 .map(|ix| {
221 ix,222 div()
222 event.modifiers.command,223 .on_mouse_down(
223 cx,224 MouseButton::Left,
224 )225 cx.listener(move |this, event: &MouseDownEvent, cx| {
225 },226 this.handle_click(
226 )227 ix,
227 .child(this.delegate.render_match(228 event.modifiers.command,
228 ix,229 cx,
229 ix == selected_ix,230 )
230 cx,231 }),
231 ))232 )
232 })233 .children(picker.delegate.render_match(
233 .collect()234 ix,
234 }235 ix == selected_index,
235 })236 cx,
237 ))
238 })
239 .collect()
240 }
241 },
242 )
236 .track_scroll(self.scroll_handle.clone()),243 .track_scroll(self.scroll_handle.clone()),
237 )244 )
238 .max_h_72()245 .max_h_72()
@@ -244,7 +251,7 @@ impl<D: PickerDelegate> Render for Picker<D> { · 5 unmodified lines
244 v_stack().p_1().grow().child(251 v_stack().p_1().grow().child(
245 div()252 div()
246 .px_1()253 .px_1()
247 .child(Label::new("No matches").color(TextColor::Muted)),254 .child(Label::new("No matches").color(Color::Muted)),
248 ),255 ),
249 )256 )
250 })257 })
crates/project/src/project.rs+118−5
@@ -13,7 +13,7 @@ mod worktree_tests; · 12 unmodified lines
13use anyhow::{anyhow, Context, Result};13use anyhow::{anyhow, Context, Result};
14use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};14use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
15use clock::ReplicaId;15use clock::ReplicaId;
16use collections::{hash_map, BTreeMap, HashMap, HashSet};16use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
17use copilot::Copilot;17use copilot::Copilot;
18use futures::{18use futures::{
19 channel::{19 channel::{
@@ -62,7 +62,10 @@ use serde::Serialize; · 42 unmodified lines
62use settings::SettingsStore;62use settings::SettingsStore;
63use sha2::{Digest, Sha256};63use sha2::{Digest, Sha256};
64use similar::{ChangeTag, TextDiff};64use similar::{ChangeTag, TextDiff};
65use smol::channel::{Receiver, Sender};65use smol::{
66 channel::{Receiver, Sender},
67 lock::Semaphore,
68};
66use std::{69use std::{
67 cmp::{self, Ordering},70 cmp::{self, Ordering},
68 convert::TryInto,71 convert::TryInto,
@@ -557,6 +560,7 @@ enum SearchMatchCandidate { · 488 unmodified lines
557 },560 },
558 Path {561 Path {
559 worktree_id: WorktreeId,562 worktree_id: WorktreeId,
563 is_ignored: bool,
560 path: Arc<Path>,564 path: Arc<Path>,
561 },565 },
562}566}
@@ -5742,13 +5746,18 @@ impl Project { · 5179 unmodified lines
5742 .await5746 .await
5743 .log_err();5747 .log_err();
5744 }5748 }
5749
5745 background5750 background
5746 .scoped(|scope| {5751 .scoped(|scope| {
5752 let max_concurrent_workers = Arc::new(Semaphore::new(workers));
5753
5747 for worker_ix in 0..workers {5754 for worker_ix in 0..workers {
5748 let worker_start_ix = worker_ix * paths_per_worker;5755 let worker_start_ix = worker_ix * paths_per_worker;
5749 let worker_end_ix = worker_start_ix + paths_per_worker;5756 let worker_end_ix = worker_start_ix + paths_per_worker;
5750 let unnamed_buffers = opened_buffers.clone();5757 let unnamed_buffers = opened_buffers.clone();
5758 let limiter = Arc::clone(&max_concurrent_workers);
5751 scope.spawn(async move {5759 scope.spawn(async move {
5760 let _guard = limiter.acquire().await;
5752 let mut snapshot_start_ix = 0;5761 let mut snapshot_start_ix = 0;
5753 let mut abs_path = PathBuf::new();5762 let mut abs_path = PathBuf::new();
5754 for snapshot in snapshots {5763 for snapshot in snapshots {
@@ -5797,6 +5806,7 @@ impl Project { · 42 unmodified lines
5797 let project_path = SearchMatchCandidate::Path {5806 let project_path = SearchMatchCandidate::Path {
5798 worktree_id: snapshot.id(),5807 worktree_id: snapshot.id(),
5799 path: entry.path.clone(),5808 path: entry.path.clone(),
5809 is_ignored: entry.is_ignored,
5800 };5810 };
5801 if matching_paths_tx.send(project_path).await.is_err() {5811 if matching_paths_tx.send(project_path).await.is_err() {
5802 break;5812 break;
@@ -5809,6 +5819,94 @@ impl Project { · 6 unmodified lines
5809 }5819 }
5810 });5820 });
5811 }5821 }
5822
5823 if query.include_ignored() {
5824 for snapshot in snapshots {
5825 for ignored_entry in snapshot
5826 .entries(query.include_ignored())
5827 .filter(|e| e.is_ignored)
5828 {
5829 let limiter = Arc::clone(&max_concurrent_workers);
5830 scope.spawn(async move {
5831 let _guard = limiter.acquire().await;
5832 let mut ignored_paths_to_process =
5833 VecDeque::from([snapshot.abs_path().join(&ignored_entry.path)]);
5834 while let Some(ignored_abs_path) =
5835 ignored_paths_to_process.pop_front()
5836 {
5837 if !query.file_matches(Some(&ignored_abs_path))
5838 || snapshot.is_path_excluded(&ignored_abs_path)
5839 {
5840 continue;
5841 }
5842 if let Some(fs_metadata) = fs
5843 .metadata(&ignored_abs_path)
5844 .await
5845 .with_context(|| {
5846 format!("fetching fs metadata for {ignored_abs_path:?}")
5847 })
5848 .log_err()
5849 .flatten()
5850 {
5851 if fs_metadata.is_dir {
5852 if let Some(mut subfiles) = fs
5853 .read_dir(&ignored_abs_path)
5854 .await
5855 .with_context(|| {
5856 format!(
5857 "listing ignored path {ignored_abs_path:?}"
5858 )
5859 })
5860 .log_err()
5861 {
5862 while let Some(subfile) = subfiles.next().await {
5863 if let Some(subfile) = subfile.log_err() {
5864 ignored_paths_to_process.push_back(subfile);
5865 }
5866 }
5867 }
5868 } else if !fs_metadata.is_symlink {
5869 let matches = if let Some(file) = fs
5870 .open_sync(&ignored_abs_path)
5871 .await
5872 .with_context(|| {
5873 format!(
5874 "Opening ignored path {ignored_abs_path:?}"
5875 )
5876 })
5877 .log_err()
5878 {
5879 query.detect(file).unwrap_or(false)
5880 } else {
5881 false
5882 };
5883 if matches {
5884 let project_path = SearchMatchCandidate::Path {
5885 worktree_id: snapshot.id(),
5886 path: Arc::from(
5887 ignored_abs_path
5888 .strip_prefix(snapshot.abs_path())
5889 .expect(
5890 "scanning worktree-related files",
5891 ),
5892 ),
5893 is_ignored: true,
5894 };
5895 if matching_paths_tx
5896 .send(project_path)
5897 .await
5898 .is_err()
5899 {
5900 return;
5901 }
5902 }
5903 }
5904 }
5905 }
5906 });
5907 }
5908 }
5909 }
5812 })5910 })
5813 .await;5911 .await;
5814 }5912 }
@@ -5917,11 +6015,24 @@ impl Project { · 102 unmodified lines
5917 let (buffers_tx, buffers_rx) = smol::channel::bounded(1024);6015 let (buffers_tx, buffers_rx) = smol::channel::bounded(1024);
5918 let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel();6016 let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel();
5919 cx.spawn(|this, cx| async move {6017 cx.spawn(|this, cx| async move {
5920 let mut buffers = vec![];6018 let mut buffers = Vec::new();
6019 let mut ignored_buffers = Vec::new();
5921 while let Some(entry) = matching_paths_rx.next().await {6020 while let Some(entry) = matching_paths_rx.next().await {
5922 buffers.push(entry);6021 if matches!(
6022 entry,
6023 SearchMatchCandidate::Path {
6024 is_ignored: true,
6025 ..
6026 }
6027 ) {
6028 ignored_buffers.push(entry);
6029 } else {
6030 buffers.push(entry);
6031 }
5923 }6032 }
5924 buffers.sort_by_key(|candidate| candidate.path());6033 buffers.sort_by_key(|candidate| candidate.path());
6034 ignored_buffers.sort_by_key(|candidate| candidate.path());
6035 buffers.extend(ignored_buffers);
5925 let matching_paths = buffers.clone();6036 let matching_paths = buffers.clone();
5926 let _ = sorted_buffers_tx.send(buffers);6037 let _ = sorted_buffers_tx.send(buffers);
5927 for (index, candidate) in matching_paths.into_iter().enumerate() {6038 for (index, candidate) in matching_paths.into_iter().enumerate() {
@@ -5933,7 +6044,9 @@ impl Project { · 5 unmodified lines
5933 cx.spawn(|mut cx| async move {6044 cx.spawn(|mut cx| async move {
5934 let buffer = match candidate {6045 let buffer = match candidate {
5935 SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer),6046 SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer),
5936 SearchMatchCandidate::Path { worktree_id, path } => this6047 SearchMatchCandidate::Path {
6048 worktree_id, path, ..
6049 } => this
5937 .update(&mut cx, |this, cx| {6050 .update(&mut cx, |this, cx| {
5938 this.open_buffer((worktree_id, path), cx)6051 this.open_buffer((worktree_id, path), cx)
5939 })6052 })
crates/project/src/worktree.rs+71−62
@@ -2226,7 +2226,7 @@ impl LocalSnapshot { · 2225 unmodified lines
2226 paths2226 paths
2227 }2227 }
2228 2228
2229 fn is_abs_path_excluded(&self, abs_path: &Path) -> bool {2229 pub fn is_path_excluded(&self, abs_path: &Path) -> bool {
2230 self.file_scan_exclusions2230 self.file_scan_exclusions
2231 .iter()2231 .iter()
2232 .any(|exclude_matcher| exclude_matcher.is_match(abs_path))2232 .any(|exclude_matcher| exclude_matcher.is_match(abs_path))
@@ -2399,26 +2399,9 @@ impl BackgroundScannerState { · 166 unmodified lines
2399 self.snapshot.check_invariants(false);2399 self.snapshot.check_invariants(false);
2400 }2400 }
2401 2401
2402 fn reload_repositories(&mut self, changed_paths: &[Arc<Path>], fs: &dyn Fs) {2402 fn reload_repositories(&mut self, dot_git_dirs_to_reload: &HashSet<PathBuf>, fs: &dyn Fs) {
2403 let scan_id = self.snapshot.scan_id;2403 let scan_id = self.snapshot.scan_id;
2404 2404 for dot_git_dir in dot_git_dirs_to_reload {
2405 // Find each of the .git directories that contain any of the given paths.
2406 let mut prev_dot_git_dir = None;
2407 for changed_path in changed_paths {
2408 let Some(dot_git_dir) = changed_path
2409 .ancestors()
2410 .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT))
2411 else {
2412 continue;
2413 };
2414
2415 // Avoid processing the same repository multiple times, if multiple paths
2416 // within it have changed.
2417 if prev_dot_git_dir == Some(dot_git_dir) {
2418 continue;
2419 }
2420 prev_dot_git_dir = Some(dot_git_dir);
2421
2422 // If there is already a repository for this .git directory, reload2405 // If there is already a repository for this .git directory, reload
2423 // the status for all of its files.2406 // the status for all of its files.
2424 let repository = self2407 let repository = self
@@ -2430,7 +2413,7 @@ impl BackgroundScannerState { · 5 unmodified lines
2430 });2413 });
2431 match repository {2414 match repository {
2432 None => {2415 None => {
2433 self.build_git_repository(dot_git_dir.into(), fs);2416 self.build_git_repository(Arc::from(dot_git_dir.as_path()), fs);
2434 }2417 }
2435 Some((entry_id, repository)) => {2418 Some((entry_id, repository)) => {
2436 if repository.git_dir_scan_id == scan_id {2419 if repository.git_dir_scan_id == scan_id {
@@ -2444,7 +2427,7 @@ impl BackgroundScannerState { · 7 unmodified lines
2444 continue;2427 continue;
2445 };2428 };
2446 2429
2447 log::info!("reload git repository {:?}", dot_git_dir);2430 log::info!("reload git repository {dot_git_dir:?}");
2448 let repository = repository.repo_ptr.lock();2431 let repository = repository.repo_ptr.lock();
2449 let branch = repository.branch_name();2432 let branch = repository.branch_name();
2450 repository.reload_index();2433 repository.reload_index();
@@ -2475,7 +2458,9 @@ impl BackgroundScannerState { · 24 unmodified lines
2475 ids_to_preserve.insert(work_directory_id);2458 ids_to_preserve.insert(work_directory_id);
2476 } else {2459 } else {
2477 let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path);2460 let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path);
2478 if snapshot.is_abs_path_excluded(&git_dir_abs_path)2461 let git_dir_excluded = snapshot.is_path_excluded(&entry.git_dir_path)
2462 || snapshot.is_path_excluded(&git_dir_abs_path);
2463 if git_dir_excluded
2479 && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))2464 && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))
2480 {2465 {
2481 ids_to_preserve.insert(work_directory_id);2466 ids_to_preserve.insert(work_directory_id);
@@ -3314,11 +3299,26 @@ impl BackgroundScanner { · 832 unmodified lines
3314 };3299 };
3315 3300
3316 let mut relative_paths = Vec::with_capacity(abs_paths.len());3301 let mut relative_paths = Vec::with_capacity(abs_paths.len());
3302 let mut dot_git_paths_to_reload = HashSet::default();
3317 abs_paths.sort_unstable();3303 abs_paths.sort_unstable();
3318 abs_paths.dedup_by(|a, b| a.starts_with(&b));3304 abs_paths.dedup_by(|a, b| a.starts_with(&b));
3319 abs_paths.retain(|abs_path| {3305 abs_paths.retain(|abs_path| {
3320 let snapshot = &self.state.lock().snapshot;3306 let snapshot = &self.state.lock().snapshot;
3321 {3307 {
3308 let mut is_git_related = false;
3309 if let Some(dot_git_dir) = abs_path
3310 .ancestors()
3311 .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT))
3312 {
3313 let dot_git_path = dot_git_dir
3314 .strip_prefix(&root_canonical_path)
3315 .ok()
3316 .map(|path| path.to_path_buf())
3317 .unwrap_or_else(|| dot_git_dir.to_path_buf());
3318 dot_git_paths_to_reload.insert(dot_git_path.to_path_buf());
3319 is_git_related = true;
3320 }
3321
3322 let relative_path: Arc<Path> =3322 let relative_path: Arc<Path> =
3323 if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {3323 if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
3324 path.into()3324 path.into()
@@ -3328,23 +3328,30 @@ impl BackgroundScanner { · 3 unmodified lines
3328 );3328 );
3329 return false;3329 return false;
3330 };3330 };
3331 let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {
3332 snapshot
3333 .entry_for_path(parent)
3334 .map_or(false, |entry| entry.kind == EntryKind::Dir)
3335 });
3336 if !parent_dir_is_loaded {
3337 log::debug!("ignoring event {relative_path:?} within unloaded directory");
3338 return false;
3339 }
3331 3340
3332 if !is_git_related(&abs_path) {3341 // FS events may come for files which parent directory is excluded, need to check ignore those.
3333 let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {3342 let mut path_to_test = abs_path.clone();
3334 snapshot3343 let mut excluded_file_event = snapshot.is_path_excluded(abs_path)
3335 .entry_for_path(parent)3344 || snapshot.is_path_excluded(&relative_path);
3336 .map_or(false, |entry| entry.kind == EntryKind::Dir)3345 while !excluded_file_event && path_to_test.pop() {
3337 });3346 if snapshot.is_path_excluded(&path_to_test) {
3338 if !parent_dir_is_loaded {3347 excluded_file_event = true;
3339 log::debug!("ignoring event {relative_path:?} within unloaded directory");
3340 return false;
3341 }3348 }
3342 if snapshot.is_abs_path_excluded(abs_path) {3349 }
3343 log::debug!(3350 if excluded_file_event {
3344 "ignoring FS event for path {relative_path:?} within excluded directory"3351 if !is_git_related {
3345 );3352 log::debug!("ignoring FS event for excluded path {relative_path:?}");
3346 return false;
3347 }3353 }
3354 return false;
3348 }3355 }
3349 3356
3350 relative_paths.push(relative_path);3357 relative_paths.push(relative_path);
@@ -3352,31 +3359,39 @@ impl BackgroundScanner { · 1 unmodified line
3352 }3359 }
3353 });3360 });
3354 3361
3355 if relative_paths.is_empty() {3362 if dot_git_paths_to_reload.is_empty() && relative_paths.is_empty() {
3356 return;3363 return;
3357 }3364 }
3358 3365
3359 log::debug!("received fs events {:?}", relative_paths);3366 if !relative_paths.is_empty() {
3367 log::debug!("received fs events {:?}", relative_paths);
3360 3368
3361 let (scan_job_tx, scan_job_rx) = channel::unbounded();3369 let (scan_job_tx, scan_job_rx) = channel::unbounded();
3362 self.reload_entries_for_paths(3370 self.reload_entries_for_paths(
3363 root_path,3371 root_path,
3364 root_canonical_path,3372 root_canonical_path,
3365 &relative_paths,3373 &relative_paths,
3366 abs_paths,3374 abs_paths,
3367 Some(scan_job_tx.clone()),3375 Some(scan_job_tx.clone()),
3368 )3376 )
3369 .await;3377 .await;
3370 drop(scan_job_tx);3378 drop(scan_job_tx);
3371 self.scan_dirs(false, scan_job_rx).await;3379 self.scan_dirs(false, scan_job_rx).await;
3372 3380
3373 let (scan_job_tx, scan_job_rx) = channel::unbounded();3381 let (scan_job_tx, scan_job_rx) = channel::unbounded();
3374 self.update_ignore_statuses(scan_job_tx).await;3382 self.update_ignore_statuses(scan_job_tx).await;
3375 self.scan_dirs(false, scan_job_rx).await;3383 self.scan_dirs(false, scan_job_rx).await;
3384 }
3376 3385
3377 {3386 {
3378 let mut state = self.state.lock();3387 let mut state = self.state.lock();
3379 state.reload_repositories(&relative_paths, self.fs.as_ref());3388 if !dot_git_paths_to_reload.is_empty() {
3389 if relative_paths.is_empty() {
3390 state.snapshot.scan_id += 1;
3391 }
3392 log::debug!("reloading repositories: {dot_git_paths_to_reload:?}");
3393 state.reload_repositories(&dot_git_paths_to_reload, self.fs.as_ref());
3394 }
3380 state.snapshot.completed_scan_id = state.snapshot.scan_id;3395 state.snapshot.completed_scan_id = state.snapshot.scan_id;
3381 for (_, entry_id) in mem::take(&mut state.removed_entry_ids) {3396 for (_, entry_id) in mem::take(&mut state.removed_entry_ids) {
3382 state.scanned_dirs.remove(&entry_id);3397 state.scanned_dirs.remove(&entry_id);
@@ -3516,7 +3531,7 @@ impl BackgroundScanner { · 133 unmodified lines
3516 let state = self.state.lock();3531 let state = self.state.lock();
3517 let snapshot = &state.snapshot;3532 let snapshot = &state.snapshot;
3518 root_abs_path = snapshot.abs_path().clone();3533 root_abs_path = snapshot.abs_path().clone();
3519 if snapshot.is_abs_path_excluded(&job.abs_path) {3534 if snapshot.is_path_excluded(&job.abs_path) {
3520 log::error!("skipping excluded directory {:?}", job.path);3535 log::error!("skipping excluded directory {:?}", job.path);
3521 return Ok(());3536 return Ok(());
3522 }3537 }
@@ -3588,7 +3603,7 @@ impl BackgroundScanner { · 65 unmodified lines
3588 3603
3589 {3604 {
3590 let mut state = self.state.lock();3605 let mut state = self.state.lock();
3591 if state.snapshot.is_abs_path_excluded(&child_abs_path) {3606 if state.snapshot.is_path_excluded(&child_abs_path) {
3592 let relative_path = job.path.join(child_name);3607 let relative_path = job.path.join(child_name);
3593 log::debug!("skipping excluded child entry {relative_path:?}");3608 log::debug!("skipping excluded child entry {relative_path:?}");
3594 state.remove_path(&relative_path);3609 state.remove_path(&relative_path);
@@ -4130,12 +4145,6 @@ impl BackgroundScanner { · 535 unmodified lines
4130 }4145 }
4131}4146}
4132 4147
4133fn 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
4139fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {4148fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
4140 let mut result = root_char_bag;4149 let mut result = root_char_bag;
4141 result.extend(4150 result.extend(
crates/project/src/worktree_tests.rs+139−0
@@ -990,6 +990,145 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { · 989 unmodified lines
990 });990 });
991}991}
992 992
993#[gpui::test]
994async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
995 init_test(cx);
996 let dir = temp_tree(json!({
997 ".git": {
998 "HEAD": "ref: refs/heads/main\n",
999 "foo": "bar",
1000 },
1001 ".gitignore": "**/target\n/node_modules\ntest_output\n",
1002 "target": {
1003 "index": "blah2"
1004 },
1005 "node_modules": {
1006 ".DS_Store": "",
1007 "prettier": {
1008 "package.json": "{}",
1009 },
1010 },
1011 "src": {
1012 ".DS_Store": "",
1013 "foo": {
1014 "foo.rs": "mod another;\n",
1015 "another.rs": "// another",
1016 },
1017 "bar": {
1018 "bar.rs": "// bar",
1019 },
1020 "lib.rs": "mod foo;\nmod bar;\n",
1021 },
1022 ".DS_Store": "",
1023 }));
1024 cx.update(|cx| {
1025 cx.update_global::<SettingsStore, _, _>(|store, cx| {
1026 store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
1027 project_settings.file_scan_exclusions = Some(vec![
1028 "**/.git".to_string(),
1029 "node_modules/".to_string(),
1030 "build_output".to_string(),
1031 ]);
1032 });
1033 });
1034 });
1035
1036 let tree = Worktree::local(
1037 build_client(cx),
1038 dir.path(),
1039 true,
1040 Arc::new(RealFs),
1041 Default::default(),
1042 &mut cx.to_async(),
1043 )
1044 .await
1045 .unwrap();
1046 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1047 .await;
1048 tree.flush_fs_events(cx).await;
1049 tree.read_with(cx, |tree, _| {
1050 check_worktree_entries(
1051 tree,
1052 &[
1053 ".git/HEAD",
1054 ".git/foo",
1055 "node_modules/.DS_Store",
1056 "node_modules/prettier",
1057 "node_modules/prettier/package.json",
1058 ],
1059 &["target", "node_modules"],
1060 &[
1061 ".DS_Store",
1062 "src/.DS_Store",
1063 "src/lib.rs",
1064 "src/foo/foo.rs",
1065 "src/foo/another.rs",
1066 "src/bar/bar.rs",
1067 ".gitignore",
1068 ],
1069 )
1070 });
1071
1072 let new_excluded_dir = dir.path().join("build_output");
1073 let new_ignored_dir = dir.path().join("test_output");
1074 std::fs::create_dir_all(&new_excluded_dir)
1075 .unwrap_or_else(|e| panic!("Failed to create a {new_excluded_dir:?} directory: {e}"));
1076 std::fs::create_dir_all(&new_ignored_dir)
1077 .unwrap_or_else(|e| panic!("Failed to create a {new_ignored_dir:?} directory: {e}"));
1078 let node_modules_dir = dir.path().join("node_modules");
1079 let dot_git_dir = dir.path().join(".git");
1080 let src_dir = dir.path().join("src");
1081 for existing_dir in [&node_modules_dir, &dot_git_dir, &src_dir] {
1082 assert!(
1083 existing_dir.is_dir(),
1084 "Expect {existing_dir:?} to be present in the FS already"
1085 );
1086 }
1087
1088 for directory_for_new_file in [
1089 new_excluded_dir,
1090 new_ignored_dir,
1091 node_modules_dir,
1092 dot_git_dir,
1093 src_dir,
1094 ] {
1095 std::fs::write(directory_for_new_file.join("new_file"), "new file contents")
1096 .unwrap_or_else(|e| {
1097 panic!("Failed to create in {directory_for_new_file:?} a new file: {e}")
1098 });
1099 }
1100 tree.flush_fs_events(cx).await;
1101
1102 tree.read_with(cx, |tree, _| {
1103 check_worktree_entries(
1104 tree,
1105 &[
1106 ".git/HEAD",
1107 ".git/foo",
1108 ".git/new_file",
1109 "node_modules/.DS_Store",
1110 "node_modules/prettier",
1111 "node_modules/prettier/package.json",
1112 "node_modules/new_file",
1113 "build_output",
1114 "build_output/new_file",
1115 "test_output/new_file",
1116 ],
1117 &["target", "node_modules", "test_output"],
1118 &[
1119 ".DS_Store",
1120 "src/.DS_Store",
1121 "src/lib.rs",
1122 "src/foo/foo.rs",
1123 "src/foo/another.rs",
1124 "src/bar/bar.rs",
1125 "src/new_file",
1126 ".gitignore",
1127 ],
1128 )
1129 });
1130}
1131
993#[gpui::test(iterations = 30)]1132#[gpui::test(iterations = 30)]
994async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {1133async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
995 init_test(cx);1134 init_test(cx);
crates/project2/src/lsp_command.rs+21−4
@@ -10,7 +10,7 @@ use futures::future; · 9 unmodified lines
10use gpui::{AppContext, AsyncAppContext, Model};10use gpui::{AppContext, AsyncAppContext, Model};
11use language::{11use language::{
12 language_settings::{language_settings, InlayHintKind},12 language_settings::{language_settings, InlayHintKind},
13 point_from_lsp, point_to_lsp,13 point_from_lsp, point_to_lsp, prepare_completion_documentation,
14 proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},14 proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
15 range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,15 range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
16 CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,16 CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
@@ -1339,7 +1339,7 @@ impl LspCommand for GetCompletions { · 1322 unmodified lines
1339 async fn response_from_lsp(1339 async fn response_from_lsp(
1340 self,1340 self,
1341 completions: Option<lsp::CompletionResponse>,1341 completions: Option<lsp::CompletionResponse>,
1342 _: Model<Project>,1342 project: Model<Project>,
1343 buffer: Model<Buffer>,1343 buffer: Model<Buffer>,
1344 server_id: LanguageServerId,1344 server_id: LanguageServerId,
1345 mut cx: AsyncAppContext,1345 mut cx: AsyncAppContext,
@@ -1359,7 +1359,8 @@ impl LspCommand for GetCompletions { · 13 unmodified lines
1359 Default::default()1359 Default::default()
1360 };1360 };
1361 1361
1362 let completions = buffer.update(&mut cx, |buffer, _| {1362 let completions = buffer.update(&mut cx, |buffer, cx| {
1363 let language_registry = project.read(cx).languages().clone();
1363 let language = buffer.language().cloned();1364 let language = buffer.language().cloned();
1364 let snapshot = buffer.snapshot();1365 let snapshot = buffer.snapshot();
1365 let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);1366 let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
@@ -1443,14 +1444,29 @@ impl LspCommand for GetCompletions { · 77 unmodified lines
1443 }1444 }
1444 };1445 };
1445 1446
1447 let language_registry = language_registry.clone();
1446 let language = language.clone();1448 let language = language.clone();
1447 LineEnding::normalize(&mut new_text);1449 LineEnding::normalize(&mut new_text);
1448 Some(async move {1450 Some(async move {
1449 let mut label = None;1451 let mut label = None;
1450 if let Some(language) = language {1452 if let Some(language) = language.as_ref() {
1451 language.process_completion(&mut lsp_completion).await;1453 language.process_completion(&mut lsp_completion).await;
1452 label = language.label_for_completion(&lsp_completion).await;1454 label = language.label_for_completion(&lsp_completion).await;
1453 }1455 }
1456
1457 let documentation = if let Some(lsp_docs) = &lsp_completion.documentation {
1458 Some(
1459 prepare_completion_documentation(
1460 lsp_docs,
1461 &language_registry,
1462 language.clone(),
1463 )
1464 .await,
1465 )
1466 } else {
1467 None
1468 };
1469
1454 Completion {1470 Completion {
1455 old_range,1471 old_range,
1456 new_text,1472 new_text,
@@ -1460,6 +1476,7 @@ impl LspCommand for GetCompletions { · 3 unmodified lines
1460 lsp_completion.filter_text.as_deref(),1476 lsp_completion.filter_text.as_deref(),
1461 )1477 )
1462 }),1478 }),
1479 documentation,
1463 server_id,1480 server_id,
1464 lsp_completion,1481 lsp_completion,
1465 }1482 }
crates/project2/src/project2.rs+114−4
@@ -13,7 +13,7 @@ mod worktree_tests; · 12 unmodified lines
13use anyhow::{anyhow, Context as _, Result};13use anyhow::{anyhow, Context as _, Result};
14use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};14use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
15use clock::ReplicaId;15use clock::ReplicaId;
16use collections::{hash_map, BTreeMap, HashMap, HashSet};16use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
17use copilot::Copilot;17use copilot::Copilot;
18use futures::{18use futures::{
19 channel::{19 channel::{
@@ -63,6 +63,7 @@ use settings::{Settings, SettingsStore}; · 43 unmodified lines
63use sha2::{Digest, Sha256};63use sha2::{Digest, Sha256};
64use similar::{ChangeTag, TextDiff};64use similar::{ChangeTag, TextDiff};
65use smol::channel::{Receiver, Sender};65use smol::channel::{Receiver, Sender};
66use smol::lock::Semaphore;
66use std::{67use std::{
67 cmp::{self, Ordering},68 cmp::{self, Ordering},
68 convert::TryInto,69 convert::TryInto,
@@ -557,6 +558,7 @@ enum SearchMatchCandidate { · 488 unmodified lines
557 },558 },
558 Path {559 Path {
559 worktree_id: WorktreeId,560 worktree_id: WorktreeId,
561 is_ignored: bool,
560 path: Arc<Path>,562 path: Arc<Path>,
561 },563 },
562}564}
@@ -5815,11 +5817,15 @@ impl Project { · 5252 unmodified lines
5815 }5817 }
5816 executor5818 executor
5817 .scoped(|scope| {5819 .scoped(|scope| {
5820 let max_concurrent_workers = Arc::new(Semaphore::new(workers));
5821
5818 for worker_ix in 0..workers {5822 for worker_ix in 0..workers {
5819 let worker_start_ix = worker_ix * paths_per_worker;5823 let worker_start_ix = worker_ix * paths_per_worker;
5820 let worker_end_ix = worker_start_ix + paths_per_worker;5824 let worker_end_ix = worker_start_ix + paths_per_worker;
5821 let unnamed_buffers = opened_buffers.clone();5825 let unnamed_buffers = opened_buffers.clone();
5826 let limiter = Arc::clone(&max_concurrent_workers);
5822 scope.spawn(async move {5827 scope.spawn(async move {
5828 let _guard = limiter.acquire().await;
5823 let mut snapshot_start_ix = 0;5829 let mut snapshot_start_ix = 0;
5824 let mut abs_path = PathBuf::new();5830 let mut abs_path = PathBuf::new();
5825 for snapshot in snapshots {5831 for snapshot in snapshots {
@@ -5868,6 +5874,7 @@ impl Project { · 42 unmodified lines
5868 let project_path = SearchMatchCandidate::Path {5874 let project_path = SearchMatchCandidate::Path {
5869 worktree_id: snapshot.id(),5875 worktree_id: snapshot.id(),
5870 path: entry.path.clone(),5876 path: entry.path.clone(),
5877 is_ignored: entry.is_ignored,
5871 };5878 };
5872 if matching_paths_tx.send(project_path).await.is_err() {5879 if matching_paths_tx.send(project_path).await.is_err() {
5873 break;5880 break;
@@ -5880,6 +5887,94 @@ impl Project { · 6 unmodified lines
5880 }5887 }
5881 });5888 });
5882 }5889 }
5890
5891 if query.include_ignored() {
5892 for snapshot in snapshots {
5893 for ignored_entry in snapshot
5894 .entries(query.include_ignored())
5895 .filter(|e| e.is_ignored)
5896 {
5897 let limiter = Arc::clone(&max_concurrent_workers);
5898 scope.spawn(async move {
5899 let _guard = limiter.acquire().await;
5900 let mut ignored_paths_to_process =
5901 VecDeque::from([snapshot.abs_path().join(&ignored_entry.path)]);
5902 while let Some(ignored_abs_path) =
5903 ignored_paths_to_process.pop_front()
5904 {
5905 if !query.file_matches(Some(&ignored_abs_path))
5906 || snapshot.is_path_excluded(&ignored_abs_path)
5907 {
5908 continue;
5909 }
5910 if let Some(fs_metadata) = fs
5911 .metadata(&ignored_abs_path)
5912 .await
5913 .with_context(|| {
5914 format!("fetching fs metadata for {ignored_abs_path:?}")
5915 })
5916 .log_err()
5917 .flatten()
5918 {
5919 if fs_metadata.is_dir {
5920 if let Some(mut subfiles) = fs
5921 .read_dir(&ignored_abs_path)
5922 .await
5923 .with_context(|| {
5924 format!(
5925 "listing ignored path {ignored_abs_path:?}"
5926 )
5927 })
5928 .log_err()
5929 {
5930 while let Some(subfile) = subfiles.next().await {
5931 if let Some(subfile) = subfile.log_err() {
5932 ignored_paths_to_process.push_back(subfile);
5933 }
5934 }
5935 }
5936 } else if !fs_metadata.is_symlink {
5937 let matches = if let Some(file) = fs
5938 .open_sync(&ignored_abs_path)
5939 .await
5940 .with_context(|| {
5941 format!(
5942 "Opening ignored path {ignored_abs_path:?}"
5943 )
5944 })
5945 .log_err()
5946 {
5947 query.detect(file).unwrap_or(false)
5948 } else {
5949 false
5950 };
5951 if matches {
5952 let project_path = SearchMatchCandidate::Path {
5953 worktree_id: snapshot.id(),
5954 path: Arc::from(
5955 ignored_abs_path
5956 .strip_prefix(snapshot.abs_path())
5957 .expect(
5958 "scanning worktree-related files",
5959 ),
5960 ),
5961 is_ignored: true,
5962 };
5963 if matching_paths_tx
5964 .send(project_path)
5965 .await
5966 .is_err()
5967 {
5968 return;
5969 }
5970 }
5971 }
5972 }
5973 }
5974 });
5975 }
5976 }
5977 }
5883 })5978 })
5884 .await;5979 .await;
5885 }5980 }
@@ -5986,11 +6081,24 @@ impl Project { · 100 unmodified lines
5986 let (buffers_tx, buffers_rx) = smol::channel::bounded(1024);6081 let (buffers_tx, buffers_rx) = smol::channel::bounded(1024);
5987 let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel();6082 let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel();
5988 cx.spawn(move |this, cx| async move {6083 cx.spawn(move |this, cx| async move {
5989 let mut buffers = vec![];6084 let mut buffers = Vec::new();
6085 let mut ignored_buffers = Vec::new();
5990 while let Some(entry) = matching_paths_rx.next().await {6086 while let Some(entry) = matching_paths_rx.next().await {
5991 buffers.push(entry);6087 if matches!(
6088 entry,
6089 SearchMatchCandidate::Path {
6090 is_ignored: true,
6091 ..
6092 }
6093 ) {
6094 ignored_buffers.push(entry);
6095 } else {
6096 buffers.push(entry);
6097 }
5992 }6098 }
5993 buffers.sort_by_key(|candidate| candidate.path());6099 buffers.sort_by_key(|candidate| candidate.path());
6100 ignored_buffers.sort_by_key(|candidate| candidate.path());
6101 buffers.extend(ignored_buffers);
5994 let matching_paths = buffers.clone();6102 let matching_paths = buffers.clone();
5995 let _ = sorted_buffers_tx.send(buffers);6103 let _ = sorted_buffers_tx.send(buffers);
5996 for (index, candidate) in matching_paths.into_iter().enumerate() {6104 for (index, candidate) in matching_paths.into_iter().enumerate() {
@@ -6002,7 +6110,9 @@ impl Project { · 5 unmodified lines
6002 cx.spawn(move |mut cx| async move {6110 cx.spawn(move |mut cx| async move {
6003 let buffer = match candidate {6111 let buffer = match candidate {
6004 SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer),6112 SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer),
6005 SearchMatchCandidate::Path { worktree_id, path } => this6113 SearchMatchCandidate::Path {
6114 worktree_id, path, ..
6115 } => this
6006 .update(&mut cx, |this, cx| {6116 .update(&mut cx, |this, cx| {
6007 this.open_buffer((worktree_id, path), cx)6117 this.open_buffer((worktree_id, path), cx)
6008 })?6118 })?
crates/project2/src/worktree.rs+72−61
@@ -2222,7 +2222,7 @@ impl LocalSnapshot { · 2221 unmodified lines
2222 paths2222 paths
2223 }2223 }
2224 2224
2225 fn is_abs_path_excluded(&self, abs_path: &Path) -> bool {2225 pub fn is_path_excluded(&self, abs_path: &Path) -> bool {
2226 self.file_scan_exclusions2226 self.file_scan_exclusions
2227 .iter()2227 .iter()
2228 .any(|exclude_matcher| exclude_matcher.is_match(abs_path))2228 .any(|exclude_matcher| exclude_matcher.is_match(abs_path))
@@ -2395,26 +2395,10 @@ impl BackgroundScannerState { · 166 unmodified lines
2395 self.snapshot.check_invariants(false);2395 self.snapshot.check_invariants(false);
2396 }2396 }
2397 2397
2398 fn reload_repositories(&mut self, changed_paths: &[Arc<Path>], fs: &dyn Fs) {2398 fn reload_repositories(&mut self, dot_git_dirs_to_reload: &HashSet<PathBuf>, fs: &dyn Fs) {
2399 let scan_id = self.snapshot.scan_id;2399 let scan_id = self.snapshot.scan_id;
2400 2400
2401 // Find each of the .git directories that contain any of the given paths.2401 for dot_git_dir in dot_git_dirs_to_reload {
2402 let mut prev_dot_git_dir = None;
2403 for changed_path in changed_paths {
2404 let Some(dot_git_dir) = changed_path
2405 .ancestors()
2406 .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT))
2407 else {
2408 continue;
2409 };
2410
2411 // Avoid processing the same repository multiple times, if multiple paths
2412 // within it have changed.
2413 if prev_dot_git_dir == Some(dot_git_dir) {
2414 continue;
2415 }
2416 prev_dot_git_dir = Some(dot_git_dir);
2417
2418 // If there is already a repository for this .git directory, reload2402 // If there is already a repository for this .git directory, reload
2419 // the status for all of its files.2403 // the status for all of its files.
2420 let repository = self2404 let repository = self
@@ -2426,7 +2410,7 @@ impl BackgroundScannerState { · 5 unmodified lines
2426 });2410 });
2427 match repository {2411 match repository {
2428 None => {2412 None => {
2429 self.build_git_repository(dot_git_dir.into(), fs);2413 self.build_git_repository(Arc::from(dot_git_dir.as_path()), fs);
2430 }2414 }
2431 Some((entry_id, repository)) => {2415 Some((entry_id, repository)) => {
2432 if repository.git_dir_scan_id == scan_id {2416 if repository.git_dir_scan_id == scan_id {
@@ -2440,7 +2424,7 @@ impl BackgroundScannerState { · 7 unmodified lines
2440 continue;2424 continue;
2441 };2425 };
2442 2426
2443 log::info!("reload git repository {:?}", dot_git_dir);2427 log::info!("reload git repository {dot_git_dir:?}");
2444 let repository = repository.repo_ptr.lock();2428 let repository = repository.repo_ptr.lock();
2445 let branch = repository.branch_name();2429 let branch = repository.branch_name();
2446 repository.reload_index();2430 repository.reload_index();
@@ -2471,7 +2455,9 @@ impl BackgroundScannerState { · 24 unmodified lines
2471 ids_to_preserve.insert(work_directory_id);2455 ids_to_preserve.insert(work_directory_id);
2472 } else {2456 } else {
2473 let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path);2457 let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path);
2474 if snapshot.is_abs_path_excluded(&git_dir_abs_path)2458 let git_dir_excluded = snapshot.is_path_excluded(&entry.git_dir_path)
2459 || snapshot.is_path_excluded(&git_dir_abs_path);
2460 if git_dir_excluded
2475 && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))2461 && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))
2476 {2462 {
2477 ids_to_preserve.insert(work_directory_id);2463 ids_to_preserve.insert(work_directory_id);
@@ -3303,11 +3289,26 @@ impl BackgroundScanner { · 825 unmodified lines
3303 };3289 };
3304 3290
3305 let mut relative_paths = Vec::with_capacity(abs_paths.len());3291 let mut relative_paths = Vec::with_capacity(abs_paths.len());
3292 let mut dot_git_paths_to_reload = HashSet::default();
3306 abs_paths.sort_unstable();3293 abs_paths.sort_unstable();
3307 abs_paths.dedup_by(|a, b| a.starts_with(&b));3294 abs_paths.dedup_by(|a, b| a.starts_with(&b));
3308 abs_paths.retain(|abs_path| {3295 abs_paths.retain(|abs_path| {
3309 let snapshot = &self.state.lock().snapshot;3296 let snapshot = &self.state.lock().snapshot;
3310 {3297 {
3298 let mut is_git_related = false;
3299 if let Some(dot_git_dir) = abs_path
3300 .ancestors()
3301 .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT))
3302 {
3303 let dot_git_path = dot_git_dir
3304 .strip_prefix(&root_canonical_path)
3305 .ok()
3306 .map(|path| path.to_path_buf())
3307 .unwrap_or_else(|| dot_git_dir.to_path_buf());
3308 dot_git_paths_to_reload.insert(dot_git_path.to_path_buf());
3309 is_git_related = true;
3310 }
3311
3311 let relative_path: Arc<Path> =3312 let relative_path: Arc<Path> =
3312 if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {3313 if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
3313 path.into()3314 path.into()
@@ -3318,22 +3319,30 @@ impl BackgroundScanner { · 4 unmodified lines
3318 return false;3319 return false;
3319 };3320 };
3320 3321
3321 if !is_git_related(&abs_path) {3322 let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {
3322 let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {3323 snapshot
3323 snapshot3324 .entry_for_path(parent)
3324 .entry_for_path(parent)3325 .map_or(false, |entry| entry.kind == EntryKind::Dir)
3325 .map_or(false, |entry| entry.kind == EntryKind::Dir)3326 });
3326 });3327 if !parent_dir_is_loaded {
3327 if !parent_dir_is_loaded {3328 log::debug!("ignoring event {relative_path:?} within unloaded directory");
3328 log::debug!("ignoring event {relative_path:?} within unloaded directory");3329 return false;
3329 return false;3330 }
3331
3332 // FS events may come for files which parent directory is excluded, need to check ignore those.
3333 let mut path_to_test = abs_path.clone();
3334 let mut excluded_file_event = snapshot.is_path_excluded(abs_path)
3335 || snapshot.is_path_excluded(&relative_path);
3336 while !excluded_file_event && path_to_test.pop() {
3337 if snapshot.is_path_excluded(&path_to_test) {
3338 excluded_file_event = true;
3330 }3339 }
3331 if snapshot.is_abs_path_excluded(abs_path) {3340 }
3332 log::debug!(3341 if excluded_file_event {
3333 "ignoring FS event for path {relative_path:?} within excluded directory"3342 if !is_git_related {
3334 );3343 log::debug!("ignoring FS event for excluded path {relative_path:?}");
3335 return false;
3336 }3344 }
3345 return false;
3337 }3346 }
3338 3347
3339 relative_paths.push(relative_path);3348 relative_paths.push(relative_path);
@@ -3341,31 +3350,39 @@ impl BackgroundScanner { · 1 unmodified line
3341 }3350 }
3342 });3351 });
3343 3352
3344 if relative_paths.is_empty() {3353 if dot_git_paths_to_reload.is_empty() && relative_paths.is_empty() {
3345 return;3354 return;
3346 }3355 }
3347 3356
3348 log::debug!("received fs events {:?}", relative_paths);3357 if !relative_paths.is_empty() {
3358 log::debug!("received fs events {:?}", relative_paths);
3349 3359
3350 let (scan_job_tx, scan_job_rx) = channel::unbounded();3360 let (scan_job_tx, scan_job_rx) = channel::unbounded();
3351 self.reload_entries_for_paths(3361 self.reload_entries_for_paths(
3352 root_path,3362 root_path,
3353 root_canonical_path,3363 root_canonical_path,
3354 &relative_paths,3364 &relative_paths,
3355 abs_paths,3365 abs_paths,
3356 Some(scan_job_tx.clone()),3366 Some(scan_job_tx.clone()),
3357 )3367 )
3358 .await;3368 .await;
3359 drop(scan_job_tx);3369 drop(scan_job_tx);
3360 self.scan_dirs(false, scan_job_rx).await;3370 self.scan_dirs(false, scan_job_rx).await;
3361 3371
3362 let (scan_job_tx, scan_job_rx) = channel::unbounded();3372 let (scan_job_tx, scan_job_rx) = channel::unbounded();
3363 self.update_ignore_statuses(scan_job_tx).await;3373 self.update_ignore_statuses(scan_job_tx).await;
3364 self.scan_dirs(false, scan_job_rx).await;3374 self.scan_dirs(false, scan_job_rx).await;
3375 }
3365 3376
3366 {3377 {
3367 let mut state = self.state.lock();3378 let mut state = self.state.lock();
3368 state.reload_repositories(&relative_paths, self.fs.as_ref());3379 if !dot_git_paths_to_reload.is_empty() {
3380 if relative_paths.is_empty() {
3381 state.snapshot.scan_id += 1;
3382 }
3383 log::debug!("reloading repositories: {dot_git_paths_to_reload:?}");
3384 state.reload_repositories(&dot_git_paths_to_reload, self.fs.as_ref());
3385 }
3369 state.snapshot.completed_scan_id = state.snapshot.scan_id;3386 state.snapshot.completed_scan_id = state.snapshot.scan_id;
3370 for (_, entry_id) in mem::take(&mut state.removed_entry_ids) {3387 for (_, entry_id) in mem::take(&mut state.removed_entry_ids) {
3371 state.scanned_dirs.remove(&entry_id);3388 state.scanned_dirs.remove(&entry_id);
@@ -3505,7 +3522,7 @@ impl BackgroundScanner { · 133 unmodified lines
3505 let state = self.state.lock();3522 let state = self.state.lock();
3506 let snapshot = &state.snapshot;3523 let snapshot = &state.snapshot;
3507 root_abs_path = snapshot.abs_path().clone();3524 root_abs_path = snapshot.abs_path().clone();
3508 if snapshot.is_abs_path_excluded(&job.abs_path) {3525 if snapshot.is_path_excluded(&job.abs_path) {
3509 log::error!("skipping excluded directory {:?}", job.path);3526 log::error!("skipping excluded directory {:?}", job.path);
3510 return Ok(());3527 return Ok(());
3511 }3528 }
@@ -3577,7 +3594,7 @@ impl BackgroundScanner { · 65 unmodified lines
3577 3594
3578 {3595 {
3579 let mut state = self.state.lock();3596 let mut state = self.state.lock();
3580 if state.snapshot.is_abs_path_excluded(&child_abs_path) {3597 if state.snapshot.is_path_excluded(&child_abs_path) {
3581 let relative_path = job.path.join(child_name);3598 let relative_path = job.path.join(child_name);
3582 log::debug!("skipping excluded child entry {relative_path:?}");3599 log::debug!("skipping excluded child entry {relative_path:?}");
3583 state.remove_path(&relative_path);3600 state.remove_path(&relative_path);
@@ -4119,12 +4136,6 @@ impl BackgroundScanner { · 535 unmodified lines
4119 }4136 }
4120}4137}
4121 4138
4122fn 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
4128fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {4139fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
4129 let mut result = root_char_bag;4140 let mut result = root_char_bag;
4130 result.extend(4141 result.extend(
crates/project2/src/worktree_tests.rs+143−3
@@ -992,6 +992,146 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { · 991 unmodified lines
992 });992 });
993}993}
994 994
995#[gpui::test]
996async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
997 init_test(cx);
998 cx.executor().allow_parking();
999 let dir = temp_tree(json!({
1000 ".git": {
1001 "HEAD": "ref: refs/heads/main\n",
1002 "foo": "bar",
1003 },
1004 ".gitignore": "**/target\n/node_modules\ntest_output\n",
1005 "target": {
1006 "index": "blah2"
1007 },
1008 "node_modules": {
1009 ".DS_Store": "",
1010 "prettier": {
1011 "package.json": "{}",
1012 },
1013 },
1014 "src": {
1015 ".DS_Store": "",
1016 "foo": {
1017 "foo.rs": "mod another;\n",
1018 "another.rs": "// another",
1019 },
1020 "bar": {
1021 "bar.rs": "// bar",
1022 },
1023 "lib.rs": "mod foo;\nmod bar;\n",
1024 },
1025 ".DS_Store": "",
1026 }));
1027 cx.update(|cx| {
1028 cx.update_global::<SettingsStore, _>(|store, cx| {
1029 store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
1030 project_settings.file_scan_exclusions = Some(vec![
1031 "**/.git".to_string(),
1032 "node_modules/".to_string(),
1033 "build_output".to_string(),
1034 ]);
1035 });
1036 });
1037 });
1038
1039 let tree = Worktree::local(
1040 build_client(cx),
1041 dir.path(),
1042 true,
1043 Arc::new(RealFs),
1044 Default::default(),
1045 &mut cx.to_async(),
1046 )
1047 .await
1048 .unwrap();
1049 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1050 .await;
1051 tree.flush_fs_events(cx).await;
1052 tree.read_with(cx, |tree, _| {
1053 check_worktree_entries(
1054 tree,
1055 &[
1056 ".git/HEAD",
1057 ".git/foo",
1058 "node_modules/.DS_Store",
1059 "node_modules/prettier",
1060 "node_modules/prettier/package.json",
1061 ],
1062 &["target", "node_modules"],
1063 &[
1064 ".DS_Store",
1065 "src/.DS_Store",
1066 "src/lib.rs",
1067 "src/foo/foo.rs",
1068 "src/foo/another.rs",
1069 "src/bar/bar.rs",
1070 ".gitignore",
1071 ],
1072 )
1073 });
1074
1075 let new_excluded_dir = dir.path().join("build_output");
1076 let new_ignored_dir = dir.path().join("test_output");
1077 std::fs::create_dir_all(&new_excluded_dir)
1078 .unwrap_or_else(|e| panic!("Failed to create a {new_excluded_dir:?} directory: {e}"));
1079 std::fs::create_dir_all(&new_ignored_dir)
1080 .unwrap_or_else(|e| panic!("Failed to create a {new_ignored_dir:?} directory: {e}"));
1081 let node_modules_dir = dir.path().join("node_modules");
1082 let dot_git_dir = dir.path().join(".git");
1083 let src_dir = dir.path().join("src");
1084 for existing_dir in [&node_modules_dir, &dot_git_dir, &src_dir] {
1085 assert!(
1086 existing_dir.is_dir(),
1087 "Expect {existing_dir:?} to be present in the FS already"
1088 );
1089 }
1090
1091 for directory_for_new_file in [
1092 new_excluded_dir,
1093 new_ignored_dir,
1094 node_modules_dir,
1095 dot_git_dir,
1096 src_dir,
1097 ] {
1098 std::fs::write(directory_for_new_file.join("new_file"), "new file contents")
1099 .unwrap_or_else(|e| {
1100 panic!("Failed to create in {directory_for_new_file:?} a new file: {e}")
1101 });
1102 }
1103 tree.flush_fs_events(cx).await;
1104
1105 tree.read_with(cx, |tree, _| {
1106 check_worktree_entries(
1107 tree,
1108 &[
1109 ".git/HEAD",
1110 ".git/foo",
1111 ".git/new_file",
1112 "node_modules/.DS_Store",
1113 "node_modules/prettier",
1114 "node_modules/prettier/package.json",
1115 "node_modules/new_file",
1116 "build_output",
1117 "build_output/new_file",
1118 "test_output/new_file",
1119 ],
1120 &["target", "node_modules", "test_output"],
1121 &[
1122 ".DS_Store",
1123 "src/.DS_Store",
1124 "src/lib.rs",
1125 "src/foo/foo.rs",
1126 "src/foo/another.rs",
1127 "src/bar/bar.rs",
1128 "src/new_file",
1129 ".gitignore",
1130 ],
1131 )
1132 });
1133}
1134
995#[gpui::test(iterations = 30)]1135#[gpui::test(iterations = 30)]
996async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {1136async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
997 init_test(cx);1137 init_test(cx);
@@ -1056,7 +1196,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) { · 58 unmodified lines
1056async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {1196async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
1057 init_test(cx);1197 init_test(cx);
1058 cx.executor().allow_parking();1198 cx.executor().allow_parking();
1059 let client_fake = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));1199 let client_fake = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
1060 1200
1061 let fs_fake = FakeFs::new(cx.background_executor.clone());1201 let fs_fake = FakeFs::new(cx.background_executor.clone());
1062 fs_fake1202 fs_fake
@@ -1096,7 +1236,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) { · 33 unmodified lines
1096 assert!(tree.entry_for_path("a/b/").unwrap().is_dir());1236 assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
1097 });1237 });
1098 1238
1099 let client_real = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));1239 let client_real = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
1100 1240
1101 let fs_real = Arc::new(RealFs);1241 let fs_real = Arc::new(RealFs);
1102 let temp_root = temp_tree(json!({1242 let temp_root = temp_tree(json!({
@@ -2181,7 +2321,7 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) { · 1078 unmodified lines
2181 2321
2182fn build_client(cx: &mut TestAppContext) -> Arc<Client> {2322fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
2183 let http_client = FakeHttpClient::with_404_response();2323 let http_client = FakeHttpClient::with_404_response();
2184 cx.read(|cx| Client::new(http_client, cx))2324 cx.update(|cx| Client::new(http_client, cx))
2185}2325}
2186 2326
2187#[track_caller]2327#[track_caller]
crates/project_panel2/Cargo.toml+0−1
@@ -9,7 +9,6 @@ path = "src/project_panel.rs" · 8 unmodified lines
9doctest = false9doctest = false
10 10
11[dependencies]11[dependencies]
12context_menu = { path = "../context_menu" }
13collections = { path = "../collections" }12collections = { path = "../collections" }
14db = { path = "../db2", package = "db2" }13db = { path = "../db2", package = "db2" }
15editor = { path = "../editor2", package = "editor2" }14editor = { path = "../editor2", package = "editor2" }
crates/project_panel2/src/project_panel.rs+57−73
@@ -9,10 +9,9 @@ use file_associations::FileAssociations; · 8 unmodified lines
9use anyhow::{anyhow, Result};9use anyhow::{anyhow, Result};
10use gpui::{10use gpui::{
11 actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,11 actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
12 ClipboardItem, Component, Div, EventEmitter, FocusHandle, Focusable, FocusableView,12 ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement,
13 InteractiveComponent, Model, MouseButton, ParentComponent, Pixels, Point, PromptLevel, Render,13 Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful, Styled,
14 Stateful, StatefulInteractiveComponent, Styled, Task, UniformListScrollHandle, View,14 Task, UniformListScrollHandle, View, ViewContext, VisualContext as _, WeakView, WindowContext,
15 ViewContext, VisualContext as _, WeakView, WindowContext,
16};15};
17use menu::{Confirm, SelectNext, SelectPrev};16use menu::{Confirm, SelectNext, SelectPrev};
18use project::{17use project::{
@@ -30,7 +29,7 @@ use std::{ · 11 unmodified lines
30 sync::Arc,29 sync::Arc,
31};30};
32use theme::ActiveTheme as _;31use theme::ActiveTheme as _;
33use ui::{h_stack, v_stack, IconElement, Label};32use ui::{v_stack, IconElement, Label, ListItem};
34use unicase::UniCase;33use unicase::UniCase;
35use util::{maybe, ResultExt, TryFutureExt};34use util::{maybe, ResultExt, TryFutureExt};
36use workspace::{35use workspace::{
@@ -247,7 +246,6 @@ impl ProjectPanel { · 210 unmodified lines
247 let mut old_dock_position = this.position(cx);246 let mut old_dock_position = this.position(cx);
248 ProjectPanelSettings::register(cx);247 ProjectPanelSettings::register(cx);
249 cx.observe_global::<SettingsStore>(move |this, cx| {248 cx.observe_global::<SettingsStore>(move |this, cx| {
250 dbg!("OLA!");
251 let new_dock_position = this.position(cx);249 let new_dock_position = this.position(cx);
252 if new_dock_position != old_dock_position {250 if new_dock_position != old_dock_position {
253 old_dock_position = new_dock_position;251 old_dock_position = new_dock_position;
@@ -372,7 +370,7 @@ impl ProjectPanel { · 118 unmodified lines
372 _entry_id: ProjectEntryId,370 _entry_id: ProjectEntryId,
373 _cx: &mut ViewContext<Self>,371 _cx: &mut ViewContext<Self>,
374 ) {372 ) {
375 todo!()373 // todo!()
376 // let project = self.project.read(cx);374 // let project = self.project.read(cx);
377 375
378 // let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) {376 // let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) {
@@ -645,6 +643,7 @@ impl ProjectPanel { · 266 unmodified lines
645 }643 }
646 644
647 fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {645 fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
646 dbg!("odd");
648 self.edit_state = None;647 self.edit_state = None;
649 self.update_visible_entries(None, cx);648 self.update_visible_entries(None, cx);
650 cx.focus(&self.focus_handle);649 cx.focus(&self.focus_handle);
@@ -1335,13 +1334,19 @@ impl ProjectPanel { · 684 unmodified lines
1335 }1334 }
1336 }1335 }
1337 1336
1338 fn render_entry_visual_element(1337 fn render_entry(
1339 details: &EntryDetails,1338 &self,
1340 editor: Option<&View<Editor>>,1339 entry_id: ProjectEntryId,
1341 padding: Pixels,1340 details: EntryDetails,
1341 // dragged_entry_destination: &mut Option<Arc<Path>>,
1342 cx: &mut ViewContext<Self>,1342 cx: &mut ViewContext<Self>,
1343 ) -> Div<Self> {1343 ) -> ListItem {
1344 let kind = details.kind;
1345 let settings = ProjectPanelSettings::get_global(cx);
1344 let show_editor = details.is_editing && !details.is_processing;1346 let show_editor = details.is_editing && !details.is_processing;
1347 let is_selected = self
1348 .selection
1349 .map_or(false, |selection| selection.entry_id == entry_id);
1345 1350
1346 let theme = cx.theme();1351 let theme = cx.theme();
1347 let filename_text_color = details1352 let filename_text_color = details
@@ -1354,14 +1359,17 @@ impl ProjectPanel { · 6 unmodified lines
1354 })1359 })
1355 .unwrap_or(theme.status().info);1360 .unwrap_or(theme.status().info);
1356 1361
1357 h_stack()1362 ListItem::new(entry_id.to_proto() as usize)
1363 .indent_level(details.depth)
1364 .indent_step_size(px(settings.indent_size))
1365 .selected(is_selected)
1358 .child(if let Some(icon) = &details.icon {1366 .child(if let Some(icon) = &details.icon {
1359 div().child(IconElement::from_path(icon.to_string()))1367 div().child(IconElement::from_path(icon.to_string()))
1360 } else {1368 } else {
1361 div()1369 div()
1362 })1370 })
1363 .child(1371 .child(
1364 if let (Some(editor), true) = (editor, show_editor) {1372 if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {
1365 div().w_full().child(editor.clone())1373 div().w_full().child(editor.clone())
1366 } else {1374 } else {
1367 div()1375 div()
@@ -1370,34 +1378,7 @@ impl ProjectPanel { · 2 unmodified lines
1370 }1378 }
1371 .ml_1(),1379 .ml_1(),
1372 )1380 )
1373 .pl(padding)1381 .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
1374 }
1375
1376 fn render_entry(
1377 &self,
1378 entry_id: ProjectEntryId,
1379 details: EntryDetails,
1380 // dragged_entry_destination: &mut Option<Arc<Path>>,
1381 cx: &mut ViewContext<Self>,
1382 ) -> Stateful<Self, Div<Self>> {
1383 let kind = details.kind;
1384 let settings = ProjectPanelSettings::get_global(cx);
1385 const INDENT_SIZE: Pixels = px(16.0);
1386 let padding = INDENT_SIZE + details.depth as f32 * px(settings.indent_size);
1387 let show_editor = details.is_editing && !details.is_processing;
1388 let is_selected = self
1389 .selection
1390 .map_or(false, |selection| selection.entry_id == entry_id);
1391
1392 Self::render_entry_visual_element(&details, Some(&self.filename_editor), padding, cx)
1393 .id(entry_id.to_proto() as usize)
1394 .w_full()
1395 .cursor_pointer()
1396 .when(is_selected, |this| {
1397 this.bg(cx.theme().colors().element_selected)
1398 })
1399 .hover(|style| style.bg(cx.theme().colors().element_hover))
1400 .on_click(move |this, event, cx| {
1401 if !show_editor {1382 if !show_editor {
1402 if kind.is_dir() {1383 if kind.is_dir() {
1403 this.toggle_expanded(entry_id, cx);1384 this.toggle_expanded(entry_id, cx);
@@ -1409,10 +1390,10 @@ impl ProjectPanel { · 5 unmodified lines
1409 }1390 }
1410 }1391 }
1411 }1392 }
1412 })1393 }))
1413 .on_mouse_down(MouseButton::Right, move |this, event, cx| {1394 .on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| {
1414 this.deploy_context_menu(event.position, entry_id, cx);1395 this.deploy_context_menu(event.position, entry_id, cx);
1415 })1396 }))
1416 // .on_drop::<ProjectEntryId>(|this, event, cx| {1397 // .on_drop::<ProjectEntryId>(|this, event, cx| {
1417 // this.move_entry(1398 // this.move_entry(
1418 // *dragged_entry,1399 // *dragged_entry,
@@ -1425,9 +1406,9 @@ impl ProjectPanel { · 6 unmodified lines
1425}1406}
1426 1407
1427impl Render for ProjectPanel {1408impl Render for ProjectPanel {
1428 type Element = Focusable<Self, Stateful<Self, Div<Self>>>;1409 type Element = Focusable<Stateful<Div>>;
1429 1410
1430 fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {1411 fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
1431 let has_worktree = self.visible_entries.len() != 0;1412 let has_worktree = self.visible_entries.len() != 0;
1432 1413
1433 if has_worktree {1414 if has_worktree {
@@ -1435,40 +1416,43 @@ impl Render for ProjectPanel { · 1 unmodified line
1435 .id("project-panel")1416 .id("project-panel")
1436 .size_full()1417 .size_full()
1437 .key_context("ProjectPanel")1418 .key_context("ProjectPanel")
1438 .on_action(Self::select_next)1419 .on_action(cx.listener(Self::select_next))
1439 .on_action(Self::select_prev)1420 .on_action(cx.listener(Self::select_prev))
1440 .on_action(Self::expand_selected_entry)1421 .on_action(cx.listener(Self::expand_selected_entry))
1441 .on_action(Self::collapse_selected_entry)1422 .on_action(cx.listener(Self::collapse_selected_entry))
1442 .on_action(Self::collapse_all_entries)1423 .on_action(cx.listener(Self::collapse_all_entries))
1443 .on_action(Self::new_file)1424 .on_action(cx.listener(Self::new_file))
1444 .on_action(Self::new_directory)1425 .on_action(cx.listener(Self::new_directory))
1445 .on_action(Self::rename)1426 .on_action(cx.listener(Self::rename))
1446 .on_action(Self::delete)1427 .on_action(cx.listener(Self::delete))
1447 .on_action(Self::confirm)1428 .on_action(cx.listener(Self::confirm))
1448 .on_action(Self::open_file)1429 .on_action(cx.listener(Self::open_file))
1449 .on_action(Self::cancel)1430 .on_action(cx.listener(Self::cancel))
1450 .on_action(Self::cut)1431 .on_action(cx.listener(Self::cut))
1451 .on_action(Self::copy)1432 .on_action(cx.listener(Self::copy))
1452 .on_action(Self::copy_path)1433 .on_action(cx.listener(Self::copy_path))
1453 .on_action(Self::copy_relative_path)1434 .on_action(cx.listener(Self::copy_relative_path))
1454 .on_action(Self::paste)1435 .on_action(cx.listener(Self::paste))
1455 .on_action(Self::reveal_in_finder)1436 .on_action(cx.listener(Self::reveal_in_finder))
1456 .on_action(Self::open_in_terminal)1437 .on_action(cx.listener(Self::open_in_terminal))
1457 .on_action(Self::new_search_in_directory)1438 .on_action(cx.listener(Self::new_search_in_directory))
1458 .track_focus(&self.focus_handle)1439 .track_focus(&self.focus_handle)
1459 .child(1440 .child(
1460 uniform_list(1441 uniform_list(
1442 cx.view().clone(),
1461 "entries",1443 "entries",
1462 self.visible_entries1444 self.visible_entries
1463 .iter()1445 .iter()
1464 .map(|(_, worktree_entries)| worktree_entries.len())1446 .map(|(_, worktree_entries)| worktree_entries.len())
1465 .sum(),1447 .sum(),
1466 |this: &mut Self, range, cx| {1448 {
1467 let mut items = Vec::new();1449 |this, range, cx| {
1468 this.for_each_visible_entry(range, cx, |id, details, cx| {1450 let mut items = Vec::new();
1469 items.push(this.render_entry(id, details, cx));1451 this.for_each_visible_entry(range, cx, |id, details, cx| {
1470 });1452 items.push(this.render_entry(id, details, cx));
1471 items1453 });
1454 items
1455 }
1472 },1456 },
1473 )1457 )
1474 .size_full()1458 .size_full()
crates/rich_text2/src/rich_text.rs+2−2
@@ -56,12 +56,12 @@ pub struct Mention { · 55 unmodified lines
56}56}
57 57
58impl RichText {58impl RichText {
59 pub fn element<V: 'static>(59 pub fn element(
60 &self,60 &self,
61 // syntax: Arc<SyntaxTheme>,61 // syntax: Arc<SyntaxTheme>,
62 // style: RichTextStyle,62 // style: RichTextStyle,
63 // cx: &mut ViewContext<V>,63 // cx: &mut ViewContext<V>,
64 ) -> AnyElement<V> {64 ) -> AnyElement {
65 todo!();65 todo!();
66 66
67 // let mut region_id = 0;67 // let mut region_id = 0;
crates/search/src/project_search.rs+2−5
@@ -1767,16 +1767,13 @@ impl View for ProjectSearchBar { · 1766 unmodified lines
1767 render_option_button_icon("icons/word_search.svg", SearchOptions::WHOLE_WORD, cx)1767 render_option_button_icon("icons/word_search.svg", SearchOptions::WHOLE_WORD, cx)
1768 });1768 });
1769 1769
1770 let mut include_ignored = is_semantic_disabled.then(|| {1770 let include_ignored = is_semantic_disabled.then(|| {
1771 render_option_button_icon(1771 render_option_button_icon(
1772 // TODO proper icon1772 "icons/file_icons/git.svg",
1773 "icons/case_insensitive.svg",
1774 SearchOptions::INCLUDE_IGNORED,1773 SearchOptions::INCLUDE_IGNORED,
1775 cx,1774 cx,
1776 )1775 )
1777 });1776 });
1778 // TODO not implemented yet
1779 let _ = include_ignored.take();
1780 1777
1781 let search_button_for_mode = |mode, side, cx: &mut ViewContext<ProjectSearchBar>| {1778 let search_button_for_mode = |mode, side, cx: &mut ViewContext<ProjectSearchBar>| {
1782 let is_active = if let Some(search) = self.active_project_search.as_ref() {1779 let is_active = if let Some(search) = self.active_project_search.as_ref() {
crates/search2/Cargo.toml+40−0
@@ -0,0 +1,40 @@
1[package]
2name = "search2"
3version = "0.1.0"
4edition = "2021"
5publish = false
6
7[lib]
8path = "src/search.rs"
9doctest = false
10
11[dependencies]
12bitflags = "1"
13collections = { path = "../collections" }
14editor = { package = "editor2", path = "../editor2" }
15gpui = { package = "gpui2", path = "../gpui2" }
16language = { package = "language2", path = "../language2" }
17menu = { package = "menu2", path = "../menu2" }
18project = { package = "project2", path = "../project2" }
19settings = { package = "settings2", path = "../settings2" }
20theme = { package = "theme2", path = "../theme2" }
21util = { path = "../util" }
22ui = {package = "ui2", path = "../ui2"}
23workspace = { package = "workspace2", path = "../workspace2" }
24#semantic_index = { path = "../semantic_index" }
25anyhow.workspace = true
26futures.workspace = true
27log.workspace = true
28postage.workspace = true
29serde.workspace = true
30serde_derive.workspace = true
31smallvec.workspace = true
32smol.workspace = true
33serde_json.workspace = true
34[dev-dependencies]
35client = { package = "client2", path = "../client2", features = ["test-support"] }
36editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
37gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
38
39workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
40unindent.workspace = true
crates/search2/src/buffer_search.rs+1747−0
@@ -0,0 +1,1747 @@
1use 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};
9use collections::HashMap;
10use editor::{Editor, EditorMode};
11use futures::channel::oneshot;
12use 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};
17use project::search::SearchQuery;
18use serde::Deserialize;
19use std::{any::Any, sync::Arc};
20
21use ui::{h_stack, ButtonGroup, Icon, IconButton, IconElement};
22use util::ResultExt;
23use workspace::{
24 item::ItemHandle,
25 searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle},
26 ToolbarItemLocation, ToolbarItemView,
27};
28
29#[derive(PartialEq, Clone, Deserialize, Default, Action)]
30pub struct Deploy {
31 pub focus: bool,
32}
33
34actions!(Dismiss, FocusEditor);
35
36pub enum Event {
37 UpdateLocation,
38}
39
40pub fn init(cx: &mut AppContext) {
41 cx.observe_new_views(|editor: &mut Editor, cx| BufferSearchBar::register(editor, cx))
42 .detach();
43}
44
45pub 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
64impl EventEmitter<Event> for BufferSearchBar {}
65impl EventEmitter<workspace::ToolbarItemEvent> for BufferSearchBar {}
66impl 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
253impl 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
297impl 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)]
918mod 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