Merge branch 'main' into project_search2
This commit is contained in:
commit
ddbd5cf2cb
145 changed files with 16798 additions and 12900 deletions
3
.github/workflows/release_nightly.yml
vendored
3
.github/workflows/release_nightly.yml
vendored
|
@ -81,12 +81,11 @@ jobs:
|
|||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Set release channel to nightly, add nightly prefix to the final version
|
||||
- name: Set release channel to nightly
|
||||
run: |
|
||||
set -eu
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
sed -i '' "s/version = \"\(.*\)\"/version = \"\1-nightly\"/" crates/zed2/Cargo.toml
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
|
||||
- name: Generate license file
|
||||
|
|
43
Cargo.lock
generated
43
Cargo.lock
generated
|
@ -312,6 +312,15 @@ version = "1.0.75"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.7"
|
||||
|
@ -1145,6 +1154,7 @@ dependencies = [
|
|||
"gpui2",
|
||||
"itertools 0.10.5",
|
||||
"language2",
|
||||
"outline2",
|
||||
"project2",
|
||||
"search2",
|
||||
"settings2",
|
||||
|
@ -1743,7 +1753,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.30.0"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
@ -3162,6 +3172,12 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast-srgb8"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
|
@ -6263,6 +6279,28 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "palette"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2e2f34147767aa758aa649415b50a69eeb46a67f9dc7db8011eeb3d84b351dc"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"fast-srgb8",
|
||||
"palette_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "palette_derive"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7db010ec5ff3d4385e4f133916faacd9dad0f6a09394c92d825b3aed310fa0a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-tokio-ipc"
|
||||
version = "0.9.0"
|
||||
|
@ -9696,6 +9734,7 @@ dependencies = [
|
|||
"indexmap 1.9.3",
|
||||
"json_comments",
|
||||
"log",
|
||||
"palette",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"simplelog",
|
||||
|
@ -10464,7 +10503,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "tree-sitter-vue"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58#9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"
|
||||
source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=6608d9d60c386f19d80af7d8132322fa11199c42#6608d9d60c386f19d80af7d8132322fa11199c42"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
|
|
|
@ -205,7 +205,7 @@ tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml",
|
|||
tree-sitter-lua = "0.0.14"
|
||||
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
|
||||
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"}
|
||||
tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"}
|
||||
tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42"}
|
||||
tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
|
||||
|
||||
[patch.crates-io]
|
||||
|
|
|
@ -17,5 +17,8 @@
|
|||
"file_name": "rose-pine-dawn.json",
|
||||
"appearance": "light"
|
||||
}
|
||||
]
|
||||
],
|
||||
"syntax": {
|
||||
"function": ["entity.name"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,16 +72,16 @@
|
|||
"editor.findMatchBackground": "#6e6a8626",
|
||||
"editor.findMatchHighlightBackground": "#6e6a8626",
|
||||
"editor.findRangeHighlightBackground": "#6e6a8626",
|
||||
"editor.findRangeHighlightBorder": "#000000",
|
||||
"editor.findRangeHighlightBorder": "#0000",
|
||||
"editor.focusedStackFrameHighlightBackground": "#6e6a8614",
|
||||
"editor.foldBackground": "#fffaf3",
|
||||
"editor.foreground": "#575279",
|
||||
"editor.hoverHighlightBackground": "#000000",
|
||||
"editor.hoverHighlightBackground": "#0000",
|
||||
"editor.inactiveSelectionBackground": "#6e6a860d",
|
||||
"editor.inlineValuesBackground": "#000000",
|
||||
"editor.inlineValuesBackground": "#0000",
|
||||
"editor.inlineValuesForeground": "#797593",
|
||||
"editor.lineHighlightBackground": "#6e6a860d",
|
||||
"editor.lineHighlightBorder": "#000000",
|
||||
"editor.lineHighlightBorder": "#0000",
|
||||
"editor.linkedEditingBackground": "#fffaf3",
|
||||
"editor.rangeHighlightBackground": "#6e6a860d",
|
||||
"editor.selectionBackground": "#6e6a8614",
|
||||
|
@ -94,9 +94,9 @@
|
|||
"editor.snippetTabstopHighlightBorder": "#fffaf3",
|
||||
"editor.stackFrameHighlightBackground": "#6e6a8614",
|
||||
"editor.symbolHighlightBackground": "#6e6a8614",
|
||||
"editor.symbolHighlightBorder": "#000000",
|
||||
"editor.symbolHighlightBorder": "#0000",
|
||||
"editor.wordHighlightBackground": "#6e6a8614",
|
||||
"editor.wordHighlightBorder": "#000000",
|
||||
"editor.wordHighlightBorder": "#0000",
|
||||
"editor.wordHighlightStrongBackground": "#6e6a8614",
|
||||
"editor.wordHighlightStrongBorder": "#6e6a8614",
|
||||
"editorBracketHighlight.foreground1": "#b4637a80",
|
||||
|
@ -105,7 +105,7 @@
|
|||
"editorBracketHighlight.foreground4": "#56949f80",
|
||||
"editorBracketHighlight.foreground5": "#d7827e80",
|
||||
"editorBracketHighlight.foreground6": "#907aa980",
|
||||
"editorBracketMatch.background": "#000000",
|
||||
"editorBracketMatch.background": "#0000",
|
||||
"editorBracketMatch.border": "#797593",
|
||||
"editorBracketPairGuide.activeBackground1": "#286983",
|
||||
"editorBracketPairGuide.activeBackground2": "#d7827e",
|
||||
|
@ -122,29 +122,29 @@
|
|||
"editorCodeLens.foreground": "#d7827e",
|
||||
"editorCursor.background": "#575279",
|
||||
"editorCursor.foreground": "#9893a5",
|
||||
"editorError.border": "#000000",
|
||||
"editorError.border": "#0000",
|
||||
"editorError.foreground": "#b4637a",
|
||||
"editorGhostText.foreground": "#797593",
|
||||
"editorGroup.border": "#000000",
|
||||
"editorGroup.border": "#0000",
|
||||
"editorGroup.dropBackground": "#fffaf3",
|
||||
"editorGroup.emptyBackground": "#000000",
|
||||
"editorGroup.focusedEmptyBorder": "#000000",
|
||||
"editorGroupHeader.noTabsBackground": "#000000",
|
||||
"editorGroupHeader.tabsBackground": "#000000",
|
||||
"editorGroupHeader.tabsBorder": "#000000",
|
||||
"editorGroup.emptyBackground": "#0000",
|
||||
"editorGroup.focusedEmptyBorder": "#0000",
|
||||
"editorGroupHeader.noTabsBackground": "#0000",
|
||||
"editorGroupHeader.tabsBackground": "#0000",
|
||||
"editorGroupHeader.tabsBorder": "#0000",
|
||||
"editorGutter.addedBackground": "#56949f",
|
||||
"editorGutter.background": "#faf4ed",
|
||||
"editorGutter.commentRangeForeground": "#797593",
|
||||
"editorGutter.deletedBackground": "#b4637a",
|
||||
"editorGutter.foldingControlForeground": "#907aa9",
|
||||
"editorGutter.modifiedBackground": "#d7827e",
|
||||
"editorHint.border": "#000000",
|
||||
"editorHint.border": "#0000",
|
||||
"editorHint.foreground": "#797593",
|
||||
"editorHoverWidget.background": "#fffaf3",
|
||||
"editorHoverWidget.border": "#9893a580",
|
||||
"editorHoverWidget.foreground": "#797593",
|
||||
"editorHoverWidget.highlightForeground": "#575279",
|
||||
"editorHoverWidget.statusBarBackground": "#000000",
|
||||
"editorHoverWidget.statusBarBackground": "#0000",
|
||||
"editorIndentGuide.activeBackground": "#9893a5",
|
||||
"editorIndentGuide.background": "#6e6a8626",
|
||||
"editorInfo.border": "#f2e9e1",
|
||||
|
@ -181,19 +181,19 @@
|
|||
"editorOverviewRuler.warningForeground": "#ea9d3480",
|
||||
"editorOverviewRuler.wordHighlightForeground": "#6e6a8614",
|
||||
"editorOverviewRuler.wordHighlightStrongForeground": "#6e6a8626",
|
||||
"editorPane.background": "#000000",
|
||||
"editorPane.background": "#0000",
|
||||
"editorRuler.foreground": "#6e6a8626",
|
||||
"editorSuggestWidget.background": "#fffaf3",
|
||||
"editorSuggestWidget.border": "#000000",
|
||||
"editorSuggestWidget.border": "#0000",
|
||||
"editorSuggestWidget.focusHighlightForeground": "#d7827e",
|
||||
"editorSuggestWidget.foreground": "#797593",
|
||||
"editorSuggestWidget.highlightForeground": "#d7827e",
|
||||
"editorSuggestWidget.selectedBackground": "#6e6a8614",
|
||||
"editorSuggestWidget.selectedForeground": "#575279",
|
||||
"editorSuggestWidget.selectedIconForeground": "#575279",
|
||||
"editorUnnecessaryCode.border": "#000000",
|
||||
"editorUnnecessaryCode.border": "#0000",
|
||||
"editorUnnecessaryCode.opacity": "#57527980",
|
||||
"editorWarning.border": "#000000",
|
||||
"editorWarning.border": "#0000",
|
||||
"editorWarning.foreground": "#ea9d34",
|
||||
"editorWhitespace.foreground": "#9893a5",
|
||||
"editorWidget.background": "#fffaf3",
|
||||
|
@ -310,7 +310,7 @@
|
|||
"notificationsWarningIcon.foreground": "#ea9d34",
|
||||
"notificationToast.border": "#6e6a8614",
|
||||
"panel.background": "#fffaf3",
|
||||
"panel.border": "#000000",
|
||||
"panel.border": "#0000",
|
||||
"panel.dropBorder": "#f2e9e1",
|
||||
"panelInput.border": "#fffaf3",
|
||||
"panelSection.dropBackground": "#6e6a8614",
|
||||
|
@ -356,7 +356,7 @@
|
|||
"sideBar.background": "#faf4ed",
|
||||
"sideBar.dropBackground": "#fffaf3",
|
||||
"sideBar.foreground": "#797593",
|
||||
"sideBarSectionHeader.background": "#000000",
|
||||
"sideBarSectionHeader.background": "#0000",
|
||||
"sideBarSectionHeader.border": "#6e6a8614",
|
||||
"statusBar.background": "#faf4ed",
|
||||
"statusBar.debuggingBackground": "#907aa9",
|
||||
|
@ -408,15 +408,15 @@
|
|||
"tab.activeBackground": "#6e6a860d",
|
||||
"tab.activeForeground": "#575279",
|
||||
"tab.activeModifiedBorder": "#56949f",
|
||||
"tab.border": "#000000",
|
||||
"tab.border": "#0000",
|
||||
"tab.hoverBackground": "#6e6a8614",
|
||||
"tab.inactiveBackground": "#000000",
|
||||
"tab.inactiveBackground": "#0000",
|
||||
"tab.inactiveForeground": "#797593",
|
||||
"tab.inactiveModifiedBorder": "#56949f80",
|
||||
"tab.lastPinnedBorder": "#9893a5",
|
||||
"tab.unfocusedActiveBackground": "#000000",
|
||||
"tab.unfocusedHoverBackground": "#000000",
|
||||
"tab.unfocusedInactiveBackground": "#000000",
|
||||
"tab.unfocusedActiveBackground": "#0000",
|
||||
"tab.unfocusedHoverBackground": "#0000",
|
||||
"tab.unfocusedInactiveBackground": "#0000",
|
||||
"tab.unfocusedInactiveModifiedBorder": "#56949f80",
|
||||
"terminal.ansiBlack": "#f2e9e1",
|
||||
"terminal.ansiBlue": "#56949f",
|
||||
|
|
|
@ -72,16 +72,16 @@
|
|||
"editor.findMatchBackground": "#817c9c4d",
|
||||
"editor.findMatchHighlightBackground": "#817c9c4d",
|
||||
"editor.findRangeHighlightBackground": "#817c9c4d",
|
||||
"editor.findRangeHighlightBorder": "#000000",
|
||||
"editor.findRangeHighlightBorder": "#0000",
|
||||
"editor.focusedStackFrameHighlightBackground": "#817c9c26",
|
||||
"editor.foldBackground": "#2a273f",
|
||||
"editor.foreground": "#e0def4",
|
||||
"editor.hoverHighlightBackground": "#000000",
|
||||
"editor.hoverHighlightBackground": "#0000",
|
||||
"editor.inactiveSelectionBackground": "#817c9c14",
|
||||
"editor.inlineValuesBackground": "#000000",
|
||||
"editor.inlineValuesBackground": "#0000",
|
||||
"editor.inlineValuesForeground": "#908caa",
|
||||
"editor.lineHighlightBackground": "#817c9c14",
|
||||
"editor.lineHighlightBorder": "#000000",
|
||||
"editor.lineHighlightBorder": "#0000",
|
||||
"editor.linkedEditingBackground": "#2a273f",
|
||||
"editor.rangeHighlightBackground": "#817c9c14",
|
||||
"editor.selectionBackground": "#817c9c26",
|
||||
|
@ -94,9 +94,9 @@
|
|||
"editor.snippetTabstopHighlightBorder": "#2a273f",
|
||||
"editor.stackFrameHighlightBackground": "#817c9c26",
|
||||
"editor.symbolHighlightBackground": "#817c9c26",
|
||||
"editor.symbolHighlightBorder": "#000000",
|
||||
"editor.symbolHighlightBorder": "#0000",
|
||||
"editor.wordHighlightBackground": "#817c9c26",
|
||||
"editor.wordHighlightBorder": "#000000",
|
||||
"editor.wordHighlightBorder": "#0000",
|
||||
"editor.wordHighlightStrongBackground": "#817c9c26",
|
||||
"editor.wordHighlightStrongBorder": "#817c9c26",
|
||||
"editorBracketHighlight.foreground1": "#eb6f9280",
|
||||
|
@ -105,7 +105,7 @@
|
|||
"editorBracketHighlight.foreground4": "#9ccfd880",
|
||||
"editorBracketHighlight.foreground5": "#ea9a9780",
|
||||
"editorBracketHighlight.foreground6": "#c4a7e780",
|
||||
"editorBracketMatch.background": "#000000",
|
||||
"editorBracketMatch.background": "#0000",
|
||||
"editorBracketMatch.border": "#908caa",
|
||||
"editorBracketPairGuide.activeBackground1": "#3e8fb0",
|
||||
"editorBracketPairGuide.activeBackground2": "#ea9a97",
|
||||
|
@ -122,29 +122,29 @@
|
|||
"editorCodeLens.foreground": "#ea9a97",
|
||||
"editorCursor.background": "#e0def4",
|
||||
"editorCursor.foreground": "#6e6a86",
|
||||
"editorError.border": "#000000",
|
||||
"editorError.border": "#0000",
|
||||
"editorError.foreground": "#eb6f92",
|
||||
"editorGhostText.foreground": "#908caa",
|
||||
"editorGroup.border": "#000000",
|
||||
"editorGroup.border": "#0000",
|
||||
"editorGroup.dropBackground": "#2a273f",
|
||||
"editorGroup.emptyBackground": "#000000",
|
||||
"editorGroup.focusedEmptyBorder": "#000000",
|
||||
"editorGroupHeader.noTabsBackground": "#000000",
|
||||
"editorGroupHeader.tabsBackground": "#000000",
|
||||
"editorGroupHeader.tabsBorder": "#000000",
|
||||
"editorGroup.emptyBackground": "#0000",
|
||||
"editorGroup.focusedEmptyBorder": "#0000",
|
||||
"editorGroupHeader.noTabsBackground": "#0000",
|
||||
"editorGroupHeader.tabsBackground": "#0000",
|
||||
"editorGroupHeader.tabsBorder": "#0000",
|
||||
"editorGutter.addedBackground": "#9ccfd8",
|
||||
"editorGutter.background": "#232136",
|
||||
"editorGutter.commentRangeForeground": "#908caa",
|
||||
"editorGutter.deletedBackground": "#eb6f92",
|
||||
"editorGutter.foldingControlForeground": "#c4a7e7",
|
||||
"editorGutter.modifiedBackground": "#ea9a97",
|
||||
"editorHint.border": "#000000",
|
||||
"editorHint.border": "#0000",
|
||||
"editorHint.foreground": "#908caa",
|
||||
"editorHoverWidget.background": "#2a273f",
|
||||
"editorHoverWidget.border": "#6e6a8680",
|
||||
"editorHoverWidget.foreground": "#908caa",
|
||||
"editorHoverWidget.highlightForeground": "#e0def4",
|
||||
"editorHoverWidget.statusBarBackground": "#000000",
|
||||
"editorHoverWidget.statusBarBackground": "#0000",
|
||||
"editorIndentGuide.activeBackground": "#6e6a86",
|
||||
"editorIndentGuide.background": "#817c9c4d",
|
||||
"editorInfo.border": "#393552",
|
||||
|
@ -181,19 +181,19 @@
|
|||
"editorOverviewRuler.warningForeground": "#f6c17780",
|
||||
"editorOverviewRuler.wordHighlightForeground": "#817c9c26",
|
||||
"editorOverviewRuler.wordHighlightStrongForeground": "#817c9c4d",
|
||||
"editorPane.background": "#000000",
|
||||
"editorPane.background": "#0000",
|
||||
"editorRuler.foreground": "#817c9c4d",
|
||||
"editorSuggestWidget.background": "#2a273f",
|
||||
"editorSuggestWidget.border": "#000000",
|
||||
"editorSuggestWidget.border": "#0000",
|
||||
"editorSuggestWidget.focusHighlightForeground": "#ea9a97",
|
||||
"editorSuggestWidget.foreground": "#908caa",
|
||||
"editorSuggestWidget.highlightForeground": "#ea9a97",
|
||||
"editorSuggestWidget.selectedBackground": "#817c9c26",
|
||||
"editorSuggestWidget.selectedForeground": "#e0def4",
|
||||
"editorSuggestWidget.selectedIconForeground": "#e0def4",
|
||||
"editorUnnecessaryCode.border": "#000000",
|
||||
"editorUnnecessaryCode.border": "#0000",
|
||||
"editorUnnecessaryCode.opacity": "#e0def480",
|
||||
"editorWarning.border": "#000000",
|
||||
"editorWarning.border": "#0000",
|
||||
"editorWarning.foreground": "#f6c177",
|
||||
"editorWhitespace.foreground": "#6e6a86",
|
||||
"editorWidget.background": "#2a273f",
|
||||
|
@ -310,7 +310,7 @@
|
|||
"notificationsWarningIcon.foreground": "#f6c177",
|
||||
"notificationToast.border": "#817c9c26",
|
||||
"panel.background": "#2a273f",
|
||||
"panel.border": "#000000",
|
||||
"panel.border": "#0000",
|
||||
"panel.dropBorder": "#393552",
|
||||
"panelInput.border": "#2a273f",
|
||||
"panelSection.dropBackground": "#817c9c26",
|
||||
|
@ -356,7 +356,7 @@
|
|||
"sideBar.background": "#232136",
|
||||
"sideBar.dropBackground": "#2a273f",
|
||||
"sideBar.foreground": "#908caa",
|
||||
"sideBarSectionHeader.background": "#000000",
|
||||
"sideBarSectionHeader.background": "#0000",
|
||||
"sideBarSectionHeader.border": "#817c9c26",
|
||||
"statusBar.background": "#232136",
|
||||
"statusBar.debuggingBackground": "#c4a7e7",
|
||||
|
@ -408,15 +408,15 @@
|
|||
"tab.activeBackground": "#817c9c14",
|
||||
"tab.activeForeground": "#e0def4",
|
||||
"tab.activeModifiedBorder": "#9ccfd8",
|
||||
"tab.border": "#000000",
|
||||
"tab.border": "#0000",
|
||||
"tab.hoverBackground": "#817c9c26",
|
||||
"tab.inactiveBackground": "#000000",
|
||||
"tab.inactiveBackground": "#0000",
|
||||
"tab.inactiveForeground": "#908caa",
|
||||
"tab.inactiveModifiedBorder": "#9ccfd880",
|
||||
"tab.lastPinnedBorder": "#6e6a86",
|
||||
"tab.unfocusedActiveBackground": "#000000",
|
||||
"tab.unfocusedHoverBackground": "#000000",
|
||||
"tab.unfocusedInactiveBackground": "#000000",
|
||||
"tab.unfocusedActiveBackground": "#0000",
|
||||
"tab.unfocusedHoverBackground": "#0000",
|
||||
"tab.unfocusedInactiveBackground": "#0000",
|
||||
"tab.unfocusedInactiveModifiedBorder": "#9ccfd880",
|
||||
"terminal.ansiBlack": "#393552",
|
||||
"terminal.ansiBlue": "#9ccfd8",
|
||||
|
|
|
@ -72,16 +72,16 @@
|
|||
"editor.findMatchBackground": "#6e6a8666",
|
||||
"editor.findMatchHighlightBackground": "#6e6a8666",
|
||||
"editor.findRangeHighlightBackground": "#6e6a8666",
|
||||
"editor.findRangeHighlightBorder": "#000000",
|
||||
"editor.findRangeHighlightBorder": "#0000",
|
||||
"editor.focusedStackFrameHighlightBackground": "#6e6a8633",
|
||||
"editor.foldBackground": "#1f1d2e",
|
||||
"editor.foreground": "#e0def4",
|
||||
"editor.hoverHighlightBackground": "#000000",
|
||||
"editor.hoverHighlightBackground": "#0000",
|
||||
"editor.inactiveSelectionBackground": "#6e6a861a",
|
||||
"editor.inlineValuesBackground": "#000000",
|
||||
"editor.inlineValuesBackground": "#0000",
|
||||
"editor.inlineValuesForeground": "#908caa",
|
||||
"editor.lineHighlightBackground": "#6e6a861a",
|
||||
"editor.lineHighlightBorder": "#000000",
|
||||
"editor.lineHighlightBorder": "#0000",
|
||||
"editor.linkedEditingBackground": "#1f1d2e",
|
||||
"editor.rangeHighlightBackground": "#6e6a861a",
|
||||
"editor.selectionBackground": "#6e6a8633",
|
||||
|
@ -94,9 +94,9 @@
|
|||
"editor.snippetTabstopHighlightBorder": "#1f1d2e",
|
||||
"editor.stackFrameHighlightBackground": "#6e6a8633",
|
||||
"editor.symbolHighlightBackground": "#6e6a8633",
|
||||
"editor.symbolHighlightBorder": "#000000",
|
||||
"editor.symbolHighlightBorder": "#0000",
|
||||
"editor.wordHighlightBackground": "#6e6a8633",
|
||||
"editor.wordHighlightBorder": "#000000",
|
||||
"editor.wordHighlightBorder": "#0000",
|
||||
"editor.wordHighlightStrongBackground": "#6e6a8633",
|
||||
"editor.wordHighlightStrongBorder": "#6e6a8633",
|
||||
"editorBracketHighlight.foreground1": "#eb6f9280",
|
||||
|
@ -105,7 +105,7 @@
|
|||
"editorBracketHighlight.foreground4": "#9ccfd880",
|
||||
"editorBracketHighlight.foreground5": "#ebbcba80",
|
||||
"editorBracketHighlight.foreground6": "#c4a7e780",
|
||||
"editorBracketMatch.background": "#000000",
|
||||
"editorBracketMatch.background": "#0000",
|
||||
"editorBracketMatch.border": "#908caa",
|
||||
"editorBracketPairGuide.activeBackground1": "#31748f",
|
||||
"editorBracketPairGuide.activeBackground2": "#ebbcba",
|
||||
|
@ -122,29 +122,29 @@
|
|||
"editorCodeLens.foreground": "#ebbcba",
|
||||
"editorCursor.background": "#e0def4",
|
||||
"editorCursor.foreground": "#6e6a86",
|
||||
"editorError.border": "#000000",
|
||||
"editorError.border": "#0000",
|
||||
"editorError.foreground": "#eb6f92",
|
||||
"editorGhostText.foreground": "#908caa",
|
||||
"editorGroup.border": "#000000",
|
||||
"editorGroup.border": "#0000",
|
||||
"editorGroup.dropBackground": "#1f1d2e",
|
||||
"editorGroup.emptyBackground": "#000000",
|
||||
"editorGroup.focusedEmptyBorder": "#000000",
|
||||
"editorGroupHeader.noTabsBackground": "#000000",
|
||||
"editorGroupHeader.tabsBackground": "#000000",
|
||||
"editorGroupHeader.tabsBorder": "#000000",
|
||||
"editorGroup.emptyBackground": "#0000",
|
||||
"editorGroup.focusedEmptyBorder": "#0000",
|
||||
"editorGroupHeader.noTabsBackground": "#0000",
|
||||
"editorGroupHeader.tabsBackground": "#0000",
|
||||
"editorGroupHeader.tabsBorder": "#0000",
|
||||
"editorGutter.addedBackground": "#9ccfd8",
|
||||
"editorGutter.background": "#191724",
|
||||
"editorGutter.commentRangeForeground": "#908caa",
|
||||
"editorGutter.deletedBackground": "#eb6f92",
|
||||
"editorGutter.foldingControlForeground": "#c4a7e7",
|
||||
"editorGutter.modifiedBackground": "#ebbcba",
|
||||
"editorHint.border": "#000000",
|
||||
"editorHint.border": "#0000",
|
||||
"editorHint.foreground": "#908caa",
|
||||
"editorHoverWidget.background": "#1f1d2e",
|
||||
"editorHoverWidget.border": "#6e6a8680",
|
||||
"editorHoverWidget.foreground": "#908caa",
|
||||
"editorHoverWidget.highlightForeground": "#e0def4",
|
||||
"editorHoverWidget.statusBarBackground": "#000000",
|
||||
"editorHoverWidget.statusBarBackground": "#0000",
|
||||
"editorIndentGuide.activeBackground": "#6e6a86",
|
||||
"editorIndentGuide.background": "#6e6a8666",
|
||||
"editorInfo.border": "#26233a",
|
||||
|
@ -181,19 +181,19 @@
|
|||
"editorOverviewRuler.warningForeground": "#f6c17780",
|
||||
"editorOverviewRuler.wordHighlightForeground": "#6e6a8633",
|
||||
"editorOverviewRuler.wordHighlightStrongForeground": "#6e6a8666",
|
||||
"editorPane.background": "#000000",
|
||||
"editorPane.background": "#0000",
|
||||
"editorRuler.foreground": "#6e6a8666",
|
||||
"editorSuggestWidget.background": "#1f1d2e",
|
||||
"editorSuggestWidget.border": "#000000",
|
||||
"editorSuggestWidget.border": "#0000",
|
||||
"editorSuggestWidget.focusHighlightForeground": "#ebbcba",
|
||||
"editorSuggestWidget.foreground": "#908caa",
|
||||
"editorSuggestWidget.highlightForeground": "#ebbcba",
|
||||
"editorSuggestWidget.selectedBackground": "#6e6a8633",
|
||||
"editorSuggestWidget.selectedForeground": "#e0def4",
|
||||
"editorSuggestWidget.selectedIconForeground": "#e0def4",
|
||||
"editorUnnecessaryCode.border": "#000000",
|
||||
"editorUnnecessaryCode.border": "#0000",
|
||||
"editorUnnecessaryCode.opacity": "#e0def480",
|
||||
"editorWarning.border": "#000000",
|
||||
"editorWarning.border": "#0000",
|
||||
"editorWarning.foreground": "#f6c177",
|
||||
"editorWhitespace.foreground": "#6e6a86",
|
||||
"editorWidget.background": "#1f1d2e",
|
||||
|
@ -310,7 +310,7 @@
|
|||
"notificationsWarningIcon.foreground": "#f6c177",
|
||||
"notificationToast.border": "#6e6a8633",
|
||||
"panel.background": "#1f1d2e",
|
||||
"panel.border": "#000000",
|
||||
"panel.border": "#0000",
|
||||
"panel.dropBorder": "#26233a",
|
||||
"panelInput.border": "#1f1d2e",
|
||||
"panelSection.dropBackground": "#6e6a8633",
|
||||
|
@ -356,7 +356,7 @@
|
|||
"sideBar.background": "#191724",
|
||||
"sideBar.dropBackground": "#1f1d2e",
|
||||
"sideBar.foreground": "#908caa",
|
||||
"sideBarSectionHeader.background": "#000000",
|
||||
"sideBarSectionHeader.background": "#0000",
|
||||
"sideBarSectionHeader.border": "#6e6a8633",
|
||||
"statusBar.background": "#191724",
|
||||
"statusBar.debuggingBackground": "#c4a7e7",
|
||||
|
@ -408,15 +408,15 @@
|
|||
"tab.activeBackground": "#6e6a861a",
|
||||
"tab.activeForeground": "#e0def4",
|
||||
"tab.activeModifiedBorder": "#9ccfd8",
|
||||
"tab.border": "#000000",
|
||||
"tab.border": "#0000",
|
||||
"tab.hoverBackground": "#6e6a8633",
|
||||
"tab.inactiveBackground": "#000000",
|
||||
"tab.inactiveBackground": "#0000",
|
||||
"tab.inactiveForeground": "#908caa",
|
||||
"tab.inactiveModifiedBorder": "#9ccfd880",
|
||||
"tab.lastPinnedBorder": "#6e6a86",
|
||||
"tab.unfocusedActiveBackground": "#000000",
|
||||
"tab.unfocusedHoverBackground": "#000000",
|
||||
"tab.unfocusedInactiveBackground": "#000000",
|
||||
"tab.unfocusedActiveBackground": "#0000",
|
||||
"tab.unfocusedHoverBackground": "#0000",
|
||||
"tab.unfocusedInactiveBackground": "#0000",
|
||||
"tab.unfocusedInactiveModifiedBorder": "#9ccfd880",
|
||||
"terminal.ansiBlack": "#26233a",
|
||||
"terminal.ansiBlue": "#9ccfd8",
|
||||
|
|
|
@ -14,7 +14,7 @@ use ui::h_stack;
|
|||
use util::ResultExt;
|
||||
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||
|
||||
actions!(ShowErrorMessage);
|
||||
actions!(activity_indicator, [ShowErrorMessage]);
|
||||
|
||||
const DOWNLOAD_ICON: &str = "icons/download.svg";
|
||||
const WARNING_ICON: &str = "icons/warning.svg";
|
||||
|
|
|
@ -12,13 +12,15 @@ use chrono::{DateTime, Local};
|
|||
use collections::HashMap;
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use gpui::{actions, AppContext};
|
||||
use gpui::{actions, AppContext, SharedString};
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc};
|
||||
use util::paths::CONVERSATIONS_DIR;
|
||||
|
||||
actions!(
|
||||
assistant,
|
||||
[
|
||||
NewConversation,
|
||||
Assist,
|
||||
Split,
|
||||
|
@ -29,6 +31,7 @@ actions!(
|
|||
InlineAssist,
|
||||
ToggleIncludeConversation,
|
||||
ToggleRetrieveContext,
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(
|
||||
|
@ -47,7 +50,7 @@ struct MessageMetadata {
|
|||
enum MessageStatus {
|
||||
Pending,
|
||||
Done,
|
||||
Error(Arc<str>),
|
||||
Error(SharedString),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
|
|
@ -1628,8 +1628,9 @@ impl Conversation {
|
|||
metadata.status = MessageStatus::Done;
|
||||
}
|
||||
Err(error) => {
|
||||
metadata.status =
|
||||
MessageStatus::Error(error.to_string().trim().into());
|
||||
metadata.status = MessageStatus::Error(SharedString::from(
|
||||
error.to_string().trim().to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
|
@ -2273,7 +2274,7 @@ impl ConversationEditor {
|
|||
Some(
|
||||
div()
|
||||
.id("error")
|
||||
.tooltip(move |cx| Tooltip::text(&error, cx))
|
||||
.tooltip(move |cx| Tooltip::text(error.clone(), cx))
|
||||
.child(IconElement::new(Icon::XCircle)),
|
||||
)
|
||||
} else {
|
||||
|
|
|
@ -26,10 +26,13 @@ const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60);
|
|||
|
||||
//todo!(remove CheckThatAutoUpdaterWorks)
|
||||
actions!(
|
||||
auto_update,
|
||||
[
|
||||
Check,
|
||||
DismissErrorMessage,
|
||||
ViewReleaseNotes,
|
||||
CheckThatAutoUpdaterWorks
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
@ -85,15 +88,7 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
|
|||
AutoUpdateSetting::register(cx);
|
||||
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
workspace
|
||||
.register_action(|_, action: &Check, cx| check(action, cx))
|
||||
.register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| {
|
||||
let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]);
|
||||
cx.spawn(|_, _cx| async move {
|
||||
prompt.await.ok();
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
workspace.register_action(|_, action: &Check, cx| check(action, cx));
|
||||
|
||||
// @nate - code to trigger update notification on launch
|
||||
// workspace.show_notification(0, _cx, |cx| {
|
||||
|
@ -130,9 +125,15 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
|
|||
}
|
||||
}
|
||||
|
||||
pub fn check(_: &Check, cx: &mut AppContext) {
|
||||
pub fn check(_: &Check, cx: &mut ViewContext<Workspace>) {
|
||||
if let Some(updater) = AutoUpdater::get(cx) {
|
||||
updater.update(cx, |updater, cx| updater.poll(cx));
|
||||
} else {
|
||||
drop(cx.prompt(
|
||||
gpui::PromptLevel::Info,
|
||||
"Auto-updates disabled for non-bundled app.",
|
||||
&["Ok"],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ search = { package = "search2", path = "../search2" }
|
|||
settings = { package = "settings2", path = "../settings2" }
|
||||
theme = { package = "theme2", path = "../theme2" }
|
||||
workspace = { package = "workspace2", path = "../workspace2" }
|
||||
# outline = { path = "../outline" }
|
||||
outline = { package = "outline2", path = "../outline2" }
|
||||
itertools = "0.10"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use editor::Editor;
|
||||
use gpui::{
|
||||
Div, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
|
||||
ViewContext, WeakView,
|
||||
ViewContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use theme::ActiveTheme;
|
||||
use ui::{prelude::*, ButtonLike, ButtonStyle, Label};
|
||||
use ui::{prelude::*, ButtonLike, ButtonStyle, Label, Tooltip};
|
||||
use workspace::{
|
||||
item::{ItemEvent, ItemHandle},
|
||||
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
|
||||
};
|
||||
|
||||
pub enum Event {
|
||||
|
@ -18,16 +19,14 @@ pub struct Breadcrumbs {
|
|||
pane_focused: bool,
|
||||
active_item: Option<Box<dyn ItemHandle>>,
|
||||
subscription: Option<Subscription>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
}
|
||||
|
||||
impl Breadcrumbs {
|
||||
pub fn new(workspace: &Workspace) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pane_focused: false,
|
||||
active_item: Default::default(),
|
||||
subscription: Default::default(),
|
||||
_workspace: workspace.weak_handle(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,97 +61,24 @@ impl Render for Breadcrumbs {
|
|||
Label::new("›").into_any_element()
|
||||
});
|
||||
|
||||
let editor = active_item
|
||||
.downcast::<Editor>()
|
||||
.map(|editor| editor.downgrade());
|
||||
|
||||
element.child(
|
||||
ButtonLike::new("toggle outline view")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(h_stack().gap_1().children(breadcrumbs))
|
||||
// We disable the button when it is not focused
|
||||
// due to ... @julia what was the reason again?
|
||||
.disabled(!self.pane_focused)
|
||||
.on_click(move |_, _cx| {
|
||||
todo!("outline::toggle");
|
||||
// this.update(cx, |this, cx| {
|
||||
// if let Some(workspace) = this.workspace.upgrade() {
|
||||
// workspace.update(cx, |_workspace, _cx| {
|
||||
// outline::toggle(workspace, &Default::default(), cx)
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// .ok();
|
||||
}),
|
||||
.on_click(move |_, cx| {
|
||||
if let Some(editor) = editor.as_ref().and_then(|editor| editor.upgrade()) {
|
||||
outline::toggle(editor, &outline::Toggle, cx)
|
||||
}
|
||||
})
|
||||
.tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// impl View for Breadcrumbs {
|
||||
// fn ui_name() -> &'static str {
|
||||
// "Breadcrumbs"
|
||||
// }
|
||||
|
||||
// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
// let active_item = match &self.active_item {
|
||||
// Some(active_item) => active_item,
|
||||
// None => return Empty::new().into_any(),
|
||||
// };
|
||||
// let not_editor = active_item.downcast::<editor::Editor>().is_none();
|
||||
|
||||
// let theme = theme::current(cx).clone();
|
||||
// let style = &theme.workspace.toolbar.breadcrumbs;
|
||||
|
||||
// let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
|
||||
// Some(breadcrumbs) => breadcrumbs,
|
||||
// None => return Empty::new().into_any(),
|
||||
// }
|
||||
// .into_iter()
|
||||
// .map(|breadcrumb| {
|
||||
// Text::new(
|
||||
// breadcrumb.text,
|
||||
// theme.workspace.toolbar.breadcrumbs.default.text.clone(),
|
||||
// )
|
||||
// .with_highlights(breadcrumb.highlights.unwrap_or_default())
|
||||
// .into_any()
|
||||
// });
|
||||
|
||||
// let crumbs = Flex::row()
|
||||
// .with_children(Itertools::intersperse_with(breadcrumbs, || {
|
||||
// Label::new(" › ", style.default.text.clone()).into_any()
|
||||
// }))
|
||||
// .constrained()
|
||||
// .with_height(theme.workspace.toolbar.breadcrumb_height)
|
||||
// .contained();
|
||||
|
||||
// if not_editor || !self.pane_focused {
|
||||
// return crumbs
|
||||
// .with_style(style.default.container)
|
||||
// .aligned()
|
||||
// .left()
|
||||
// .into_any();
|
||||
// }
|
||||
|
||||
// MouseEventHandler::new::<Breadcrumbs, _>(0, cx, |state, _| {
|
||||
// let style = style.style_for(state);
|
||||
// crumbs.with_style(style.container)
|
||||
// })
|
||||
// .on_click(MouseButton::Left, |_, this, cx| {
|
||||
// if let Some(workspace) = this.workspace.upgrade(cx) {
|
||||
// workspace.update(cx, |workspace, cx| {
|
||||
// outline::toggle(workspace, &Default::default(), cx)
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// .with_tooltip::<Breadcrumbs>(
|
||||
// 0,
|
||||
// "Show symbol outline".to_owned(),
|
||||
// Some(Box::new(outline::Toggle)),
|
||||
// theme.tooltip.clone(),
|
||||
// cx,
|
||||
// )
|
||||
// .aligned()
|
||||
// .left()
|
||||
// .into_any()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl ToolbarItemView for Breadcrumbs {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
|
|
|
@ -8,7 +8,8 @@ use collections::{hash_map, HashMap, HashSet};
|
|||
use db::RELEASE_CHANNEL;
|
||||
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedString, Task,
|
||||
WeakModel,
|
||||
};
|
||||
use rpc::{
|
||||
proto::{self, ChannelVisibility},
|
||||
|
@ -46,7 +47,7 @@ pub struct ChannelStore {
|
|||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Channel {
|
||||
pub id: ChannelId,
|
||||
pub name: String,
|
||||
pub name: SharedString,
|
||||
pub visibility: proto::ChannelVisibility,
|
||||
pub role: proto::ChannelRole,
|
||||
pub unseen_note_version: Option<(u64, clock::Global)>,
|
||||
|
@ -895,14 +896,16 @@ impl ChannelStore {
|
|||
.channel_invitations
|
||||
.binary_search_by_key(&channel.id, |c| c.id)
|
||||
{
|
||||
Ok(ix) => Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name,
|
||||
Ok(ix) => {
|
||||
Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name.into()
|
||||
}
|
||||
Err(ix) => self.channel_invitations.insert(
|
||||
ix,
|
||||
Arc::new(Channel {
|
||||
id: channel.id,
|
||||
visibility: channel.visibility(),
|
||||
role: channel.role(),
|
||||
name: channel.name,
|
||||
name: channel.name.into(),
|
||||
unseen_note_version: None,
|
||||
unseen_message_id: None,
|
||||
parent_path: channel.parent_path,
|
||||
|
|
|
@ -104,7 +104,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
|
|||
|
||||
existing_channel.visibility = channel_proto.visibility();
|
||||
existing_channel.role = channel_proto.role();
|
||||
existing_channel.name = channel_proto.name;
|
||||
existing_channel.name = channel_proto.name.into();
|
||||
} else {
|
||||
self.channels_by_id.insert(
|
||||
channel_proto.id,
|
||||
|
@ -112,7 +112,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
|
|||
id: channel_proto.id,
|
||||
visibility: channel_proto.visibility(),
|
||||
role: channel_proto.role(),
|
||||
name: channel_proto.name,
|
||||
name: channel_proto.name.into(),
|
||||
unseen_note_version: None,
|
||||
unseen_message_id: None,
|
||||
parent_path: channel_proto.parent_path,
|
||||
|
@ -146,11 +146,11 @@ fn channel_path_sorting_key<'a>(
|
|||
let (parent_path, name) = channels_by_id
|
||||
.get(&id)
|
||||
.map_or((&[] as &[_], None), |channel| {
|
||||
(channel.parent_path.as_slice(), Some(channel.name.as_str()))
|
||||
(channel.parent_path.as_slice(), Some(channel.name.as_ref()))
|
||||
});
|
||||
parent_path
|
||||
.iter()
|
||||
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_str()))
|
||||
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_ref()))
|
||||
.chain(name)
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
|
|||
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
|
||||
pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
actions!(SignIn, SignOut, Reconnect);
|
||||
actions!(client, [SignIn, SignOut, Reconnect]);
|
||||
|
||||
pub fn init_settings(cx: &mut AppContext) {
|
||||
TelemetrySettings::register(cx);
|
||||
|
|
|
@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
|||
default-run = "collab"
|
||||
edition = "2021"
|
||||
name = "collab"
|
||||
version = "0.30.0"
|
||||
version = "0.30.1"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
|
|
|
@ -116,12 +116,13 @@ struct CreateUserResponse {
|
|||
#[derive(Debug, Deserialize)]
|
||||
struct Panic {
|
||||
version: String,
|
||||
release_channel: String,
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[instrument(skip(panic))]
|
||||
async fn trace_panic(panic: Json<Panic>) -> Result<()> {
|
||||
tracing::error!(version = %panic.version, text = %panic.text, "panic report");
|
||||
tracing::error!(version = %panic.version, release_channel = %panic.release_channel, text = %panic.text, "panic report");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use call::ActiveCall;
|
|||
use channel::{ChannelId, ChannelMembership, ChannelStore};
|
||||
use client::User;
|
||||
use futures::future::try_join_all;
|
||||
use gpui::{BackgroundExecutor, Model, TestAppContext};
|
||||
use gpui::{BackgroundExecutor, Model, SharedString, TestAppContext};
|
||||
use rpc::{
|
||||
proto::{self, ChannelRole},
|
||||
RECEIVE_TIMEOUT,
|
||||
|
@ -46,13 +46,13 @@ async fn test_core_channels(
|
|||
&[
|
||||
ExpectedChannel {
|
||||
id: channel_a_id,
|
||||
name: "channel-a".to_string(),
|
||||
name: "channel-a".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
ExpectedChannel {
|
||||
id: channel_b_id,
|
||||
name: "channel-b".to_string(),
|
||||
name: "channel-b".into(),
|
||||
depth: 1,
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
|
@ -92,7 +92,7 @@ async fn test_core_channels(
|
|||
cx_b,
|
||||
&[ExpectedChannel {
|
||||
id: channel_a_id,
|
||||
name: "channel-a".to_string(),
|
||||
name: "channel-a".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Member,
|
||||
}],
|
||||
|
@ -140,13 +140,13 @@ async fn test_core_channels(
|
|||
&[
|
||||
ExpectedChannel {
|
||||
id: channel_a_id,
|
||||
name: "channel-a".to_string(),
|
||||
name: "channel-a".into(),
|
||||
role: ChannelRole::Member,
|
||||
depth: 0,
|
||||
},
|
||||
ExpectedChannel {
|
||||
id: channel_b_id,
|
||||
name: "channel-b".to_string(),
|
||||
name: "channel-b".into(),
|
||||
role: ChannelRole::Member,
|
||||
depth: 1,
|
||||
},
|
||||
|
@ -168,19 +168,19 @@ async fn test_core_channels(
|
|||
&[
|
||||
ExpectedChannel {
|
||||
id: channel_a_id,
|
||||
name: "channel-a".to_string(),
|
||||
name: "channel-a".into(),
|
||||
role: ChannelRole::Member,
|
||||
depth: 0,
|
||||
},
|
||||
ExpectedChannel {
|
||||
id: channel_b_id,
|
||||
name: "channel-b".to_string(),
|
||||
name: "channel-b".into(),
|
||||
role: ChannelRole::Member,
|
||||
depth: 1,
|
||||
},
|
||||
ExpectedChannel {
|
||||
id: channel_c_id,
|
||||
name: "channel-c".to_string(),
|
||||
name: "channel-c".into(),
|
||||
role: ChannelRole::Member,
|
||||
depth: 2,
|
||||
},
|
||||
|
@ -211,19 +211,19 @@ async fn test_core_channels(
|
|||
&[
|
||||
ExpectedChannel {
|
||||
id: channel_a_id,
|
||||
name: "channel-a".to_string(),
|
||||
name: "channel-a".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
ExpectedChannel {
|
||||
id: channel_b_id,
|
||||
name: "channel-b".to_string(),
|
||||
name: "channel-b".into(),
|
||||
depth: 1,
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
ExpectedChannel {
|
||||
id: channel_c_id,
|
||||
name: "channel-c".to_string(),
|
||||
name: "channel-c".into(),
|
||||
depth: 2,
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
|
@ -245,7 +245,7 @@ async fn test_core_channels(
|
|||
cx_a,
|
||||
&[ExpectedChannel {
|
||||
id: channel_a_id,
|
||||
name: "channel-a".to_string(),
|
||||
name: "channel-a".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Admin,
|
||||
}],
|
||||
|
@ -255,7 +255,7 @@ async fn test_core_channels(
|
|||
cx_b,
|
||||
&[ExpectedChannel {
|
||||
id: channel_a_id,
|
||||
name: "channel-a".to_string(),
|
||||
name: "channel-a".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Admin,
|
||||
}],
|
||||
|
@ -278,7 +278,7 @@ async fn test_core_channels(
|
|||
cx_a,
|
||||
&[ExpectedChannel {
|
||||
id: channel_a_id,
|
||||
name: "channel-a".to_string(),
|
||||
name: "channel-a".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Admin,
|
||||
}],
|
||||
|
@ -309,7 +309,7 @@ async fn test_core_channels(
|
|||
cx_a,
|
||||
&[ExpectedChannel {
|
||||
id: channel_a_id,
|
||||
name: "channel-a-renamed".to_string(),
|
||||
name: "channel-a-renamed".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Admin,
|
||||
}],
|
||||
|
@ -418,7 +418,7 @@ async fn test_channel_room(
|
|||
cx_b,
|
||||
&[ExpectedChannel {
|
||||
id: zed_id,
|
||||
name: "zed".to_string(),
|
||||
name: "zed".into(),
|
||||
depth: 0,
|
||||
role: ChannelRole::Member,
|
||||
}],
|
||||
|
@ -680,7 +680,7 @@ async fn test_permissions_update_while_invited(
|
|||
&[ExpectedChannel {
|
||||
depth: 0,
|
||||
id: rust_id,
|
||||
name: "rust".to_string(),
|
||||
name: "rust".into(),
|
||||
role: ChannelRole::Member,
|
||||
}],
|
||||
);
|
||||
|
@ -708,7 +708,7 @@ async fn test_permissions_update_while_invited(
|
|||
&[ExpectedChannel {
|
||||
depth: 0,
|
||||
id: rust_id,
|
||||
name: "rust".to_string(),
|
||||
name: "rust".into(),
|
||||
role: ChannelRole::Member,
|
||||
}],
|
||||
);
|
||||
|
@ -747,7 +747,7 @@ async fn test_channel_rename(
|
|||
&[ExpectedChannel {
|
||||
depth: 0,
|
||||
id: rust_id,
|
||||
name: "rust-archive".to_string(),
|
||||
name: "rust-archive".into(),
|
||||
role: ChannelRole::Admin,
|
||||
}],
|
||||
);
|
||||
|
@ -759,7 +759,7 @@ async fn test_channel_rename(
|
|||
&[ExpectedChannel {
|
||||
depth: 0,
|
||||
id: rust_id,
|
||||
name: "rust-archive".to_string(),
|
||||
name: "rust-archive".into(),
|
||||
role: ChannelRole::Member,
|
||||
}],
|
||||
);
|
||||
|
@ -888,7 +888,7 @@ async fn test_lost_channel_creation(
|
|||
&[ExpectedChannel {
|
||||
depth: 0,
|
||||
id: channel_id,
|
||||
name: "x".to_string(),
|
||||
name: "x".into(),
|
||||
role: ChannelRole::Member,
|
||||
}],
|
||||
);
|
||||
|
@ -912,13 +912,13 @@ async fn test_lost_channel_creation(
|
|||
ExpectedChannel {
|
||||
depth: 0,
|
||||
id: channel_id,
|
||||
name: "x".to_string(),
|
||||
name: "x".into(),
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
ExpectedChannel {
|
||||
depth: 1,
|
||||
id: subchannel_id,
|
||||
name: "subchannel".to_string(),
|
||||
name: "subchannel".into(),
|
||||
role: ChannelRole::Admin,
|
||||
},
|
||||
],
|
||||
|
@ -943,13 +943,13 @@ async fn test_lost_channel_creation(
|
|||
ExpectedChannel {
|
||||
depth: 0,
|
||||
id: channel_id,
|
||||
name: "x".to_string(),
|
||||
name: "x".into(),
|
||||
role: ChannelRole::Member,
|
||||
},
|
||||
ExpectedChannel {
|
||||
depth: 1,
|
||||
id: subchannel_id,
|
||||
name: "subchannel".to_string(),
|
||||
name: "subchannel".into(),
|
||||
role: ChannelRole::Member,
|
||||
},
|
||||
],
|
||||
|
@ -1221,13 +1221,13 @@ async fn test_channel_membership_notifications(
|
|||
ExpectedChannel {
|
||||
depth: 0,
|
||||
id: zed_channel,
|
||||
name: "zed".to_string(),
|
||||
name: "zed".into(),
|
||||
role: ChannelRole::Guest,
|
||||
},
|
||||
ExpectedChannel {
|
||||
depth: 1,
|
||||
id: vim_channel,
|
||||
name: "vim".to_string(),
|
||||
name: "vim".into(),
|
||||
role: ChannelRole::Member,
|
||||
},
|
||||
],
|
||||
|
@ -1250,13 +1250,13 @@ async fn test_channel_membership_notifications(
|
|||
ExpectedChannel {
|
||||
depth: 0,
|
||||
id: zed_channel,
|
||||
name: "zed".to_string(),
|
||||
name: "zed".into(),
|
||||
role: ChannelRole::Guest,
|
||||
},
|
||||
ExpectedChannel {
|
||||
depth: 1,
|
||||
id: vim_channel,
|
||||
name: "vim".to_string(),
|
||||
name: "vim".into(),
|
||||
role: ChannelRole::Guest,
|
||||
},
|
||||
],
|
||||
|
@ -1476,7 +1476,7 @@ async fn test_channel_moving(
|
|||
struct ExpectedChannel {
|
||||
depth: usize,
|
||||
id: ChannelId,
|
||||
name: String,
|
||||
name: SharedString,
|
||||
role: ChannelRole,
|
||||
}
|
||||
|
||||
|
@ -1515,7 +1515,7 @@ fn assert_channels(
|
|||
.ordered_channels()
|
||||
.map(|(depth, channel)| ExpectedChannel {
|
||||
depth,
|
||||
name: channel.name.clone(),
|
||||
name: channel.name.clone().into(),
|
||||
id: channel.id,
|
||||
role: channel.role,
|
||||
})
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::db::ChannelRole;
|
|||
use super::{run_randomized_test, RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use gpui::{BackgroundExecutor, TestAppContext};
|
||||
use gpui::{BackgroundExecutor, SharedString, TestAppContext};
|
||||
use rand::prelude::*;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
@ -30,13 +30,13 @@ struct RandomChannelBufferTest;
|
|||
#[derive(Clone, Serialize, Deserialize)]
|
||||
enum ChannelBufferOperation {
|
||||
JoinChannelNotes {
|
||||
channel_name: String,
|
||||
channel_name: SharedString,
|
||||
},
|
||||
LeaveChannelNotes {
|
||||
channel_name: String,
|
||||
channel_name: SharedString,
|
||||
},
|
||||
EditChannelNotes {
|
||||
channel_name: String,
|
||||
channel_name: SharedString,
|
||||
edits: Vec<(Range<usize>, Arc<str>)>,
|
||||
},
|
||||
Noop,
|
||||
|
|
|
@ -26,7 +26,7 @@ use workspace::{
|
|||
ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId,
|
||||
};
|
||||
|
||||
actions!(Deploy);
|
||||
actions!(collab, [Deploy]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
register_followable_item::<ChannelView>(cx)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,13 +3,14 @@ use client::UserId;
|
|||
use collections::HashMap;
|
||||
use editor::{AnchorRangeExt, Editor};
|
||||
use gpui::{
|
||||
elements::ChildView, AnyElement, AsyncAppContext, Element, Entity, ModelHandle, Task, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
AnyView, AsyncWindowContext, FocusableView, Model, Render, SharedString, Task, View,
|
||||
ViewContext, WeakView,
|
||||
};
|
||||
use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry};
|
||||
use lazy_static::lazy_static;
|
||||
use project::search::SearchQuery;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use workspace::item::ItemHandle;
|
||||
|
||||
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
||||
|
||||
|
@ -19,8 +20,8 @@ lazy_static! {
|
|||
}
|
||||
|
||||
pub struct MessageEditor {
|
||||
pub editor: ViewHandle<Editor>,
|
||||
channel_store: ModelHandle<ChannelStore>,
|
||||
pub editor: View<Editor>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
users: HashMap<String, UserId>,
|
||||
mentions: Vec<UserId>,
|
||||
mentions_task: Option<Task<()>>,
|
||||
|
@ -30,8 +31,8 @@ pub struct MessageEditor {
|
|||
impl MessageEditor {
|
||||
pub fn new(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
channel_store: ModelHandle<ChannelStore>,
|
||||
editor: ViewHandle<Editor>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
editor: View<Editor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
editor.update(cx, |editor, cx| {
|
||||
|
@ -48,13 +49,11 @@ impl MessageEditor {
|
|||
cx.subscribe(&buffer, Self::on_buffer_event).detach();
|
||||
|
||||
let markdown = language_registry.language_for_name("Markdown");
|
||||
cx.app_context()
|
||||
.spawn(|mut cx| async move {
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let markdown = markdown.await?;
|
||||
buffer.update(&mut cx, |buffer, cx| {
|
||||
buffer.set_language(Some(markdown), cx)
|
||||
});
|
||||
anyhow::Ok(())
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
|
@ -71,7 +70,7 @@ impl MessageEditor {
|
|||
pub fn set_channel(
|
||||
&mut self,
|
||||
channel_id: u64,
|
||||
channel_name: Option<String>,
|
||||
channel_name: Option<SharedString>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
|
@ -132,26 +131,28 @@ impl MessageEditor {
|
|||
|
||||
fn on_buffer_event(
|
||||
&mut self,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
buffer: Model<Buffer>,
|
||||
event: &language::Event,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let language::Event::Reparsed | language::Event::Edited = event {
|
||||
let buffer = buffer.read(cx).snapshot();
|
||||
self.mentions_task = Some(cx.spawn(|this, cx| async move {
|
||||
cx.background().timer(MENTIONS_DEBOUNCE_INTERVAL).await;
|
||||
cx.background_executor()
|
||||
.timer(MENTIONS_DEBOUNCE_INTERVAL)
|
||||
.await;
|
||||
Self::find_mentions(this, buffer, cx).await;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async fn find_mentions(
|
||||
this: WeakViewHandle<MessageEditor>,
|
||||
this: WeakView<MessageEditor>,
|
||||
buffer: BufferSnapshot,
|
||||
mut cx: AsyncAppContext,
|
||||
mut cx: AsyncWindowContext,
|
||||
) {
|
||||
let (buffer, ranges) = cx
|
||||
.background()
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
let ranges = MENTIONS_SEARCH.search(&buffer, None).await;
|
||||
(buffer, ranges)
|
||||
|
@ -180,11 +181,7 @@ impl MessageEditor {
|
|||
}
|
||||
|
||||
editor.clear_highlights::<Self>(cx);
|
||||
editor.highlight_text::<Self>(
|
||||
anchor_ranges,
|
||||
theme::current(cx).chat_panel.rich_text.mention_highlight,
|
||||
cx,
|
||||
)
|
||||
editor.highlight_text::<Self>(anchor_ranges, gpui::red().into(), cx)
|
||||
});
|
||||
|
||||
this.mentions = mentioned_user_ids;
|
||||
|
@ -192,21 +189,17 @@ impl MessageEditor {
|
|||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub(crate) fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
|
||||
self.editor.read(cx).focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for MessageEditor {
|
||||
type Event = ();
|
||||
}
|
||||
impl Render for MessageEditor {
|
||||
type Element = AnyView;
|
||||
|
||||
impl View for MessageEditor {
|
||||
fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
|
||||
ChildView::new(&self.editor, cx).into_any()
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_self_focused() {
|
||||
cx.focus(&self.editor);
|
||||
}
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
self.editor.to_any()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,7 +207,7 @@ impl View for MessageEditor {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use client::{Client, User, UserStore};
|
||||
use gpui::{TestAppContext, WindowHandle};
|
||||
use gpui::{Context as _, TestAppContext, VisualContext as _};
|
||||
use language::{Language, LanguageConfig};
|
||||
use rpc::proto;
|
||||
use settings::SettingsStore;
|
||||
|
@ -222,8 +215,17 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_message_editor(cx: &mut TestAppContext) {
|
||||
let editor = init_test(cx);
|
||||
let editor = editor.root(cx);
|
||||
let language_registry = init_test(cx);
|
||||
|
||||
let (editor, cx) = cx.add_window_view(|cx| {
|
||||
MessageEditor::new(
|
||||
language_registry,
|
||||
ChannelStore::global(cx),
|
||||
cx.build_view(|cx| Editor::auto_height(4, cx)),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.set_members(
|
||||
|
@ -255,7 +257,7 @@ mod tests {
|
|||
});
|
||||
});
|
||||
|
||||
cx.foreground().advance_clock(MENTIONS_DEBOUNCE_INTERVAL);
|
||||
cx.executor().advance_clock(MENTIONS_DEBOUNCE_INTERVAL);
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
let (text, ranges) = marked_text_ranges("Hello, «@a-b»! Have you met «@C_D»?", false);
|
||||
|
@ -269,15 +271,14 @@ mod tests {
|
|||
});
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) -> WindowHandle<MessageEditor> {
|
||||
cx.foreground().forbid_parking();
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) -> Arc<LanguageRegistry> {
|
||||
cx.update(|cx| {
|
||||
let http = FakeHttpClient::with_404_response();
|
||||
let client = Client::new(http.clone(), cx);
|
||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
theme::init((), cx);
|
||||
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http, cx));
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
language::init(cx);
|
||||
editor::init(cx);
|
||||
client::init(&client, cx);
|
||||
|
@ -292,16 +293,6 @@ mod tests {
|
|||
},
|
||||
Some(tree_sitter_markdown::language()),
|
||||
)));
|
||||
|
||||
let editor = cx.add_window(|cx| {
|
||||
MessageEditor::new(
|
||||
language_registry,
|
||||
ChannelStore::global(cx),
|
||||
cx.add_view(|cx| Editor::auto_height(4, None, cx)),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
editor
|
||||
language_registry
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ use theme::{ActiveTheme, ThemeSettings};
|
|||
// channel_id: ChannelId,
|
||||
// }
|
||||
|
||||
#[derive(Action, PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct OpenChannelNotes {
|
||||
pub channel_id: ChannelId,
|
||||
}
|
||||
|
@ -122,7 +122,11 @@ pub struct OpenChannelNotes {
|
|||
// to: ChannelId,
|
||||
// }
|
||||
|
||||
impl_actions!(collab_panel, [OpenChannelNotes]);
|
||||
|
||||
actions!(
|
||||
collab_panel,
|
||||
[
|
||||
ToggleFocus,
|
||||
Remove,
|
||||
Secondary,
|
||||
|
@ -131,6 +135,7 @@ actions!(
|
|||
StartMoveChannel,
|
||||
MoveSelected,
|
||||
InsertSpace,
|
||||
]
|
||||
);
|
||||
|
||||
// impl_actions!(
|
||||
|
@ -169,12 +174,12 @@ use editor::Editor;
|
|||
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
|
||||
use fuzzy::{match_strings, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, canvas, div, img, overlay, point, prelude::*, px, rems, serde_json, size, Action,
|
||||
AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter,
|
||||
FocusHandle, Focusable, FocusableView, Hsla, InteractiveElement, IntoElement, Length, Model,
|
||||
MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Quad, Render, RenderOnce,
|
||||
ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, View, ViewContext,
|
||||
VisualContext, WeakView,
|
||||
actions, canvas, div, img, impl_actions, overlay, point, prelude::*, px, rems, serde_json,
|
||||
size, Action, AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div,
|
||||
EventEmitter, FocusHandle, Focusable, FocusableView, Hsla, InteractiveElement, IntoElement,
|
||||
Length, Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Quad, Render,
|
||||
RenderOnce, ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, View,
|
||||
ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use project::{Fs, Project};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
@ -192,6 +197,7 @@ use workspace::{
|
|||
};
|
||||
|
||||
use crate::channel_view::ChannelView;
|
||||
use crate::chat_panel::ChatPanel;
|
||||
use crate::{face_pile::FacePile, CollaborationPanelSettings};
|
||||
|
||||
use self::channel_modal::ChannelModal;
|
||||
|
@ -852,7 +858,7 @@ impl CollabPanel {
|
|||
.extend(channel_store.ordered_channels().enumerate().map(
|
||||
|(ix, (_, channel))| StringMatchCandidate {
|
||||
id: ix,
|
||||
string: channel.name.clone(),
|
||||
string: channel.name.clone().into(),
|
||||
char_bag: channel.name.chars().collect(),
|
||||
},
|
||||
));
|
||||
|
@ -2102,14 +2108,13 @@ impl CollabPanel {
|
|||
};
|
||||
cx.window_context().defer(move |cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
todo!();
|
||||
// if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
|
||||
// panel.update(cx, |panel, cx| {
|
||||
// panel
|
||||
// .select_channel(channel_id, None, cx)
|
||||
// .detach_and_log_err(cx);
|
||||
// });
|
||||
// }
|
||||
if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel
|
||||
.select_channel(channel_id, None, cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -2262,7 +2267,7 @@ impl CollabPanel {
|
|||
}
|
||||
};
|
||||
|
||||
Some(channel.name.as_str())
|
||||
Some(channel.name.as_ref())
|
||||
});
|
||||
|
||||
if let Some(name) = channel_name {
|
||||
|
@ -2603,9 +2608,14 @@ impl CollabPanel {
|
|||
Color::Default
|
||||
} else {
|
||||
Color::Muted
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.join_channel_chat(channel_id, cx)
|
||||
}))
|
||||
.tooltip(|cx| {
|
||||
Tooltip::text("Open channel chat", cx)
|
||||
}),
|
||||
)
|
||||
.tooltip(|cx| Tooltip::text("Open channel chat", cx)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
|
|
|
@ -13,12 +13,16 @@ use picker::{Picker, PickerDelegate};
|
|||
use std::sync::Arc;
|
||||
use ui::prelude::*;
|
||||
use util::TryFutureExt;
|
||||
use workspace::ModalView;
|
||||
|
||||
actions!(
|
||||
channel_modal,
|
||||
[
|
||||
SelectNextControl,
|
||||
ToggleMode,
|
||||
ToggleMemberAdmin,
|
||||
RemoveMember
|
||||
]
|
||||
);
|
||||
|
||||
// pub fn init(cx: &mut AppContext) {
|
||||
|
@ -140,6 +144,7 @@ impl ChannelModal {
|
|||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for ChannelModal {}
|
||||
impl ModalView for ChannelModal {}
|
||||
|
||||
impl FocusableView for ChannelModal {
|
||||
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||
|
|
|
@ -9,6 +9,7 @@ use std::sync::Arc;
|
|||
use theme::ActiveTheme as _;
|
||||
use ui::prelude::*;
|
||||
use util::{ResultExt as _, TryFutureExt};
|
||||
use workspace::ModalView;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
//Picker::<ContactFinderDelegate>::init(cx);
|
||||
|
@ -95,6 +96,7 @@ pub struct ContactFinderDelegate {
|
|||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for ContactFinder {}
|
||||
impl ModalView for ContactFinder {}
|
||||
|
||||
impl FocusableView for ContactFinder {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,6 +12,7 @@ use std::{rc::Rc, sync::Arc};
|
|||
use call::{report_call_event_for_room, ActiveCall, Room};
|
||||
pub use collab_panel::CollabPanel;
|
||||
pub use collab_titlebar_item::CollabTitlebarItem;
|
||||
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt};
|
||||
use gpui::{
|
||||
actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds,
|
||||
WindowKind, WindowOptions,
|
||||
|
@ -23,7 +24,10 @@ use settings::Settings;
|
|||
use util::ResultExt;
|
||||
use workspace::AppState;
|
||||
|
||||
actions!(ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall);
|
||||
actions!(
|
||||
collab,
|
||||
[ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
|
||||
);
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
CollaborationPanelSettings::register(cx);
|
||||
|
@ -34,7 +38,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||
collab_titlebar_item::init(cx);
|
||||
collab_panel::init(cx);
|
||||
channel_view::init(cx);
|
||||
// chat_panel::init(cx);
|
||||
chat_panel::init(cx);
|
||||
notifications::init(&app_state, cx);
|
||||
|
||||
// cx.add_global_action(toggle_screen_sharing);
|
||||
|
@ -157,6 +161,6 @@ fn notification_window_options(
|
|||
// .into_any()
|
||||
// }
|
||||
|
||||
// fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool {
|
||||
// cx.is_staff() || cx.has_flag::<ChannelsAlpha>()
|
||||
// }
|
||||
fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool {
|
||||
cx.is_staff() || cx.has_flag::<ChannelsAlpha>()
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use gpui::{
|
||||
div, AnyElement, Div, IntoElement as _, ParentElement as _, RenderOnce, Styled, WindowContext,
|
||||
div, AnyElement, Div, ElementId, IntoElement, ParentElement as _, RenderOnce, Styled,
|
||||
WindowContext,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, IntoElement)]
|
||||
pub struct FacePile {
|
||||
pub faces: Vec<AnyElement>,
|
||||
}
|
||||
|
@ -15,64 +16,15 @@ impl RenderOnce for FacePile {
|
|||
let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
|
||||
let isnt_last = ix < player_count - 1;
|
||||
|
||||
div().when(isnt_last, |div| div.neg_mr_1()).child(player)
|
||||
div()
|
||||
.z_index((player_count - ix) as u32)
|
||||
.when(isnt_last, |div| div.neg_mr_1())
|
||||
.child(player)
|
||||
});
|
||||
div().p_1().flex().items_center().children(player_list)
|
||||
}
|
||||
}
|
||||
|
||||
// impl Element for FacePile {
|
||||
// type State = ();
|
||||
// fn layout(
|
||||
// &mut self,
|
||||
// state: Option<Self::State>,
|
||||
// cx: &mut WindowContext,
|
||||
// ) -> (LayoutId, Self::State) {
|
||||
// let mut width = 0.;
|
||||
// let mut max_height = 0.;
|
||||
// let mut faces = Vec::with_capacity(self.faces.len());
|
||||
// for face in &mut self.faces {
|
||||
// let layout = face.layout(cx);
|
||||
// width += layout.x();
|
||||
// max_height = f32::max(max_height, layout.y());
|
||||
// faces.push(layout);
|
||||
// }
|
||||
// width -= self.overlap * self.faces.len().saturating_sub(1) as f32;
|
||||
// (cx.request_layout(&Style::default(), faces), ())
|
||||
// // (
|
||||
// // Vector2F::new(width, max_height.clamp(1., constraint.max.y())),
|
||||
// // (),
|
||||
// // ))
|
||||
// }
|
||||
|
||||
// fn paint(
|
||||
// &mut self,
|
||||
// bounds: RectF,
|
||||
// visible_bounds: RectF,
|
||||
// _layout: &mut Self::LayoutState,
|
||||
// view: &mut V,
|
||||
// cx: &mut ViewContext<V>,
|
||||
// ) -> Self::PaintState {
|
||||
// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
||||
|
||||
// let origin_y = bounds.upper_right().y();
|
||||
// let mut origin_x = bounds.upper_right().x();
|
||||
|
||||
// for face in self.faces.iter_mut().rev() {
|
||||
// let size = face.size();
|
||||
// origin_x -= size.x();
|
||||
// let origin_y = origin_y + (bounds.height() - size.y()) / 2.0;
|
||||
|
||||
// cx.scene().push_layer(None);
|
||||
// face.paint(vec2f(origin_x, origin_y), visible_bounds, view, cx);
|
||||
// cx.scene().pop_layer();
|
||||
// origin_x += self.overlap;
|
||||
// }
|
||||
|
||||
// ()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Extend<AnyElement> for FacePile {
|
||||
fn extend<T: IntoIterator<Item = AnyElement>>(&mut self, children: T) {
|
||||
self.faces.extend(children);
|
||||
|
|
|
@ -3,9 +3,9 @@ use std::sync::Arc;
|
|||
use workspace::AppState;
|
||||
|
||||
pub mod incoming_call_notification;
|
||||
// pub mod project_shared_notification;
|
||||
pub mod project_shared_notification;
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
incoming_call_notification::init(app_state, cx);
|
||||
//project_shared_notification::init(app_state, cx);
|
||||
project_shared_notification::init(app_state, cx);
|
||||
}
|
||||
|
|
|
@ -33,8 +33,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||
height: px(64.),
|
||||
};
|
||||
|
||||
for window in unique_screens {
|
||||
let options = notification_window_options(window, window_size);
|
||||
for screen in unique_screens {
|
||||
let options = notification_window_options(screen, window_size);
|
||||
let window = cx
|
||||
.open_window(options, |cx| {
|
||||
cx.build_view(|_| {
|
||||
|
@ -47,15 +47,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||
.unwrap();
|
||||
notification_windows.push(window);
|
||||
}
|
||||
|
||||
// for screen in cx.platform().screens() {
|
||||
// let window = cx
|
||||
// .add_window(notification_window_options(screen, window_size), |_| {
|
||||
// IncomingCallNotification::new(incoming_call.clone(), app_state.clone())
|
||||
// });
|
||||
|
||||
// notification_windows.push(window);
|
||||
// }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -84,21 +75,22 @@ impl IncomingCallNotificationState {
|
|||
let active_call = ActiveCall::global(cx);
|
||||
if accept {
|
||||
let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
|
||||
let caller_user_id = self.call.calling_user.id;
|
||||
let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
|
||||
let app_state = self.app_state.clone();
|
||||
let cx: &mut AppContext = cx;
|
||||
cx.spawn(|cx| async move {
|
||||
join.await?;
|
||||
if let Some(_project_id) = initial_project_id {
|
||||
cx.update(|_cx| {
|
||||
if let Some(_app_state) = app_state.upgrade() {
|
||||
// workspace::join_remote_project(
|
||||
// project_id,
|
||||
// caller_user_id,
|
||||
// app_state,
|
||||
// cx,
|
||||
// )
|
||||
// .detach_and_log_err(cx);
|
||||
if let Some(project_id) = initial_project_id {
|
||||
cx.update(|cx| {
|
||||
if let Some(app_state) = app_state.upgrade() {
|
||||
workspace::join_remote_project(
|
||||
project_id,
|
||||
caller_user_id,
|
||||
app_state,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
|
@ -138,135 +130,25 @@ impl IncomingCallNotification {
|
|||
)))
|
||||
.child(self.render_buttons(cx)),
|
||||
)
|
||||
// let theme = &theme::current(cx).incoming_call_notification;
|
||||
// let default_project = proto::ParticipantProject::default();
|
||||
// let initial_project = self
|
||||
// .call
|
||||
// .initial_project
|
||||
// .as_ref()
|
||||
// .unwrap_or(&default_project);
|
||||
// Flex::row()
|
||||
// .with_children(self.call.calling_user.avatar.clone().map(|avatar| {
|
||||
// Image::from_data(avatar)
|
||||
// .with_style(theme.caller_avatar)
|
||||
// .aligned()
|
||||
// }))
|
||||
// .with_child(
|
||||
// Flex::column()
|
||||
// .with_child(
|
||||
// Label::new(
|
||||
// self.call.calling_user.github_login.clone(),
|
||||
// theme.caller_username.text.clone(),
|
||||
// )
|
||||
// .contained()
|
||||
// .with_style(theme.caller_username.container),
|
||||
// )
|
||||
// .with_child(
|
||||
// Label::new(
|
||||
// format!(
|
||||
// "is sharing a project in Zed{}",
|
||||
// if initial_project.worktree_root_names.is_empty() {
|
||||
// ""
|
||||
// } else {
|
||||
// ":"
|
||||
// }
|
||||
// ),
|
||||
// theme.caller_message.text.clone(),
|
||||
// )
|
||||
// .contained()
|
||||
// .with_style(theme.caller_message.container),
|
||||
// )
|
||||
// .with_children(if initial_project.worktree_root_names.is_empty() {
|
||||
// None
|
||||
// } else {
|
||||
// Some(
|
||||
// Label::new(
|
||||
// initial_project.worktree_root_names.join(", "),
|
||||
// theme.worktree_roots.text.clone(),
|
||||
// )
|
||||
// .contained()
|
||||
// .with_style(theme.worktree_roots.container),
|
||||
// )
|
||||
// })
|
||||
// .contained()
|
||||
// .with_style(theme.caller_metadata)
|
||||
// .aligned(),
|
||||
// )
|
||||
// .contained()
|
||||
// .with_style(theme.caller_container)
|
||||
// .flex(1., true)
|
||||
// .into_any()
|
||||
}
|
||||
|
||||
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
|
||||
h_stack()
|
||||
.child(
|
||||
Button::new("accept", "Accept")
|
||||
.render(cx)
|
||||
// .bg(green())
|
||||
.on_click({
|
||||
.child(Button::new("accept", "Accept").render(cx).on_click({
|
||||
let state = self.state.clone();
|
||||
move |_, cx| state.respond(true, cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Button::new("decline", "Decline")
|
||||
.render(cx)
|
||||
// .bg(red())
|
||||
.on_click({
|
||||
}))
|
||||
.child(Button::new("decline", "Decline").render(cx).on_click({
|
||||
let state = self.state.clone();
|
||||
move |_, cx| state.respond(false, cx)
|
||||
}),
|
||||
)
|
||||
|
||||
// enum Accept {}
|
||||
// enum Decline {}
|
||||
|
||||
// let theme = theme::current(cx);
|
||||
// Flex::column()
|
||||
// .with_child(
|
||||
// MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
|
||||
// let theme = &theme.incoming_call_notification;
|
||||
// Label::new("Accept", theme.accept_button.text.clone())
|
||||
// .aligned()
|
||||
// .contained()
|
||||
// .with_style(theme.accept_button.container)
|
||||
// })
|
||||
// .with_cursor_style(CursorStyle::PointingHand)
|
||||
// .on_click(MouseButton::Left, |_, this, cx| {
|
||||
// this.respond(true, cx);
|
||||
// })
|
||||
// .flex(1., true),
|
||||
// )
|
||||
// .with_child(
|
||||
// MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {
|
||||
// let theme = &theme.incoming_call_notification;
|
||||
// Label::new("Decline", theme.decline_button.text.clone())
|
||||
// .aligned()
|
||||
// .contained()
|
||||
// .with_style(theme.decline_button.container)
|
||||
// })
|
||||
// .with_cursor_style(CursorStyle::PointingHand)
|
||||
// .on_click(MouseButton::Left, |_, this, cx| {
|
||||
// this.respond(false, cx);
|
||||
// })
|
||||
// .flex(1., true),
|
||||
// )
|
||||
// .constrained()
|
||||
// .with_width(theme.incoming_call_notification.button_width)
|
||||
// .into_any()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for IncomingCallNotification {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
div().bg(red()).flex_none().child(self.render_caller(cx))
|
||||
// Flex::row()
|
||||
// .with_child()
|
||||
// .with_child(self.render_buttons(cx))
|
||||
// .contained()
|
||||
// .with_background_color(background)
|
||||
// .expanded()
|
||||
// .into_any()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,11 @@ use call::{room, ActiveCall};
|
|||
use client::User;
|
||||
use collections::HashMap;
|
||||
use gpui::{
|
||||
elements::*,
|
||||
geometry::vector::vec2f,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AppContext, Entity, View, ViewContext,
|
||||
px, AppContext, Div, Element, ParentElement, Render, RenderOnce, Size, Styled, ViewContext,
|
||||
VisualContext,
|
||||
};
|
||||
use std::sync::{Arc, Weak};
|
||||
use ui::{h_stack, v_stack, Avatar, Button, Clickable, Label};
|
||||
use workspace::AppState;
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
|
@ -21,18 +20,22 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||
project_id,
|
||||
worktree_root_names,
|
||||
} => {
|
||||
let theme = &theme::current(cx).project_shared_notification;
|
||||
let window_size = vec2f(theme.window_width, theme.window_height);
|
||||
let window_size = Size {
|
||||
width: px(380.),
|
||||
height: px(64.),
|
||||
};
|
||||
|
||||
for screen in cx.platform().screens() {
|
||||
let window =
|
||||
cx.add_window(notification_window_options(screen, window_size), |_| {
|
||||
for screen in cx.displays() {
|
||||
let options = notification_window_options(screen, window_size);
|
||||
let window = cx.open_window(options, |cx| {
|
||||
cx.build_view(|_| {
|
||||
ProjectSharedNotification::new(
|
||||
owner.clone(),
|
||||
*project_id,
|
||||
worktree_root_names.clone(),
|
||||
app_state.clone(),
|
||||
)
|
||||
})
|
||||
});
|
||||
notification_windows
|
||||
.entry(*project_id)
|
||||
|
@ -40,19 +43,31 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||
.push(window);
|
||||
}
|
||||
}
|
||||
|
||||
room::Event::RemoteProjectUnshared { project_id }
|
||||
| room::Event::RemoteProjectJoined { project_id }
|
||||
| room::Event::RemoteProjectInvitationDiscarded { project_id } => {
|
||||
if let Some(windows) = notification_windows.remove(&project_id) {
|
||||
for window in windows {
|
||||
window.remove(cx);
|
||||
window
|
||||
.update(cx, |_, cx| {
|
||||
// todo!()
|
||||
cx.remove_window();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
room::Event::Left => {
|
||||
for (_, windows) in notification_windows.drain() {
|
||||
for window in windows {
|
||||
window.remove(cx);
|
||||
window
|
||||
.update(cx, |_, cx| {
|
||||
// todo!()
|
||||
cx.remove_window();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,116 +117,60 @@ impl ProjectSharedNotification {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = &theme::current(cx).project_shared_notification;
|
||||
Flex::row()
|
||||
.with_children(self.owner.avatar.clone().map(|avatar| {
|
||||
Image::from_data(avatar)
|
||||
.with_style(theme.owner_avatar)
|
||||
.aligned()
|
||||
}))
|
||||
.with_child(
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Label::new(
|
||||
self.owner.github_login.clone(),
|
||||
theme.owner_username.text.clone(),
|
||||
fn render_owner(&self) -> impl Element {
|
||||
h_stack()
|
||||
.children(
|
||||
self.owner
|
||||
.avatar
|
||||
.clone()
|
||||
.map(|avatar| Avatar::data(avatar.clone())),
|
||||
)
|
||||
.contained()
|
||||
.with_style(theme.owner_username.container),
|
||||
)
|
||||
.with_child(
|
||||
Label::new(
|
||||
format!(
|
||||
.child(
|
||||
v_stack()
|
||||
.child(Label::new(self.owner.github_login.clone()))
|
||||
.child(Label::new(format!(
|
||||
"is sharing a project in Zed{}",
|
||||
if self.worktree_root_names.is_empty() {
|
||||
""
|
||||
} else {
|
||||
":"
|
||||
}
|
||||
),
|
||||
theme.message.text.clone(),
|
||||
)
|
||||
.contained()
|
||||
.with_style(theme.message.container),
|
||||
)
|
||||
.with_children(if self.worktree_root_names.is_empty() {
|
||||
)))
|
||||
.children(if self.worktree_root_names.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
Label::new(
|
||||
self.worktree_root_names.join(", "),
|
||||
theme.worktree_roots.text.clone(),
|
||||
Some(Label::new(self.worktree_root_names.join(", ")))
|
||||
}),
|
||||
)
|
||||
.contained()
|
||||
.with_style(theme.worktree_roots.container),
|
||||
)
|
||||
})
|
||||
.contained()
|
||||
.with_style(theme.owner_metadata)
|
||||
.aligned(),
|
||||
)
|
||||
.contained()
|
||||
.with_style(theme.owner_container)
|
||||
.flex(1., true)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
enum Open {}
|
||||
enum Dismiss {}
|
||||
|
||||
let theme = theme::current(cx);
|
||||
Flex::column()
|
||||
.with_child(
|
||||
MouseEventHandler::new::<Open, _>(0, cx, |_, _| {
|
||||
let theme = &theme.project_shared_notification;
|
||||
Label::new("Open", theme.open_button.text.clone())
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(theme.open_button.container)
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, this, cx| this.join(cx))
|
||||
.flex(1., true),
|
||||
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
|
||||
let this = cx.view().clone();
|
||||
v_stack()
|
||||
.child(Button::new("open", "Open").render(cx).on_click({
|
||||
let this = this.clone();
|
||||
move |_, cx| {
|
||||
this.update(cx, |this, cx| this.join(cx));
|
||||
}
|
||||
}))
|
||||
.child(
|
||||
Button::new("dismiss", "Dismiss")
|
||||
.render(cx)
|
||||
.on_click(move |_, cx| {
|
||||
this.update(cx, |this, cx| this.dismiss(cx));
|
||||
}),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::new::<Dismiss, _>(0, cx, |_, _| {
|
||||
let theme = &theme.project_shared_notification;
|
||||
Label::new("Dismiss", theme.dismiss_button.text.clone())
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(theme.dismiss_button.container)
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, |_, this, cx| {
|
||||
this.dismiss(cx);
|
||||
})
|
||||
.flex(1., true),
|
||||
)
|
||||
.constrained()
|
||||
.with_width(theme.project_shared_notification.button_width)
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for ProjectSharedNotification {
|
||||
type Event = ();
|
||||
}
|
||||
impl Render for ProjectSharedNotification {
|
||||
type Element = Div;
|
||||
|
||||
impl View for ProjectSharedNotification {
|
||||
fn ui_name() -> &'static str {
|
||||
"ProjectSharedNotification"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
|
||||
let background = theme::current(cx).project_shared_notification.background;
|
||||
Flex::row()
|
||||
.with_child(self.render_owner(cx))
|
||||
.with_child(self.render_buttons(cx))
|
||||
.contained()
|
||||
.with_background_color(background)
|
||||
.expanded()
|
||||
.into_any()
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
h_stack()
|
||||
.size_full()
|
||||
.bg(gpui::red())
|
||||
.child(self.render_owner())
|
||||
.child(self.render_buttons(cx))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use collections::{CommandPaletteFilter, HashMap};
|
|||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
|
||||
Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
|
||||
ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
|
||||
|
@ -16,16 +16,18 @@ use util::{
|
|||
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
|
||||
ResultExt,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
use workspace::{ModalView, Workspace};
|
||||
use zed_actions::OpenZedURL;
|
||||
|
||||
actions!(Toggle);
|
||||
actions!(command_palette, [Toggle]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.set_global(HitCounts::default());
|
||||
cx.observe_new_views(CommandPalette::register).detach();
|
||||
}
|
||||
|
||||
impl ModalView for CommandPalette {}
|
||||
|
||||
pub struct CommandPalette {
|
||||
picker: View<Picker<CommandPaletteDelegate>>,
|
||||
}
|
||||
|
@ -47,7 +49,7 @@ impl CommandPalette {
|
|||
.available_actions()
|
||||
.into_iter()
|
||||
.filter_map(|action| {
|
||||
let name = gpui::remove_the_2(action.name());
|
||||
let name = action.name();
|
||||
let namespace = name.split("::").next().unwrap_or("malformed action name");
|
||||
if filter.is_some_and(|f| {
|
||||
f.hidden_namespaces.contains(namespace)
|
||||
|
@ -59,7 +61,6 @@ impl CommandPalette {
|
|||
Some(Command {
|
||||
name: humanize_action_name(&name),
|
||||
action,
|
||||
keystrokes: vec![], // todo!()
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
@ -108,7 +109,6 @@ pub struct CommandPaletteDelegate {
|
|||
struct Command {
|
||||
name: String,
|
||||
action: Box<dyn Action>,
|
||||
keystrokes: Vec<Keystroke>,
|
||||
}
|
||||
|
||||
impl Clone for Command {
|
||||
|
@ -116,7 +116,6 @@ impl Clone for Command {
|
|||
Self {
|
||||
name: self.name.clone(),
|
||||
action: self.action.boxed_clone(),
|
||||
keystrokes: self.keystrokes.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -227,6 +226,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(CommandInterceptResult {
|
||||
action,
|
||||
string,
|
||||
|
@ -242,7 +242,6 @@ impl PickerDelegate for CommandPaletteDelegate {
|
|||
commands.push(Command {
|
||||
name: string.clone(),
|
||||
action,
|
||||
keystrokes: vec![],
|
||||
});
|
||||
matches.insert(
|
||||
0,
|
||||
|
@ -254,6 +253,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
picker
|
||||
.update(&mut cx, |picker, _| {
|
||||
let delegate = &mut picker.delegate;
|
||||
|
@ -283,6 +283,8 @@ impl PickerDelegate for CommandPaletteDelegate {
|
|||
}
|
||||
let action_ix = self.matches[self.selected_ix].candidate_id;
|
||||
let command = self.commands.swap_remove(action_ix);
|
||||
self.matches.clear();
|
||||
self.commands.clear();
|
||||
cx.update_global(|hit_counts: &mut HitCounts, _| {
|
||||
*hit_counts.0.entry(command.name).or_default() += 1;
|
||||
});
|
||||
|
@ -298,13 +300,8 @@ impl PickerDelegate for CommandPaletteDelegate {
|
|||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let Some(r#match) = self.matches.get(ix) else {
|
||||
return None;
|
||||
};
|
||||
let Some(command) = self.commands.get(r#match.candidate_id) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let r#match = self.matches.get(ix)?;
|
||||
let command = self.commands.get(r#match.candidate_id)?;
|
||||
Some(
|
||||
ListItem::new(ix).inset(true).selected(selected).child(
|
||||
h_stack()
|
||||
|
@ -352,8 +349,7 @@ impl std::fmt::Debug for Command {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Command")
|
||||
.field("name", &self.name)
|
||||
.field("keystrokes", &self.keystrokes)
|
||||
.finish()
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,12 +34,15 @@ use util::{
|
|||
};
|
||||
|
||||
actions!(
|
||||
copilot,
|
||||
[
|
||||
Suggest,
|
||||
NextSuggestion,
|
||||
PreviousSuggestion,
|
||||
Reinstall,
|
||||
SignIn,
|
||||
SignOut
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(
|
||||
|
|
|
@ -43,7 +43,7 @@ use workspace::{
|
|||
ItemNavHistory, Pane, ToolbarItemLocation, Workspace,
|
||||
};
|
||||
|
||||
actions!(Deploy, ToggleWarnings);
|
||||
actions!(diagnostics, [Deploy, ToggleWarnings]);
|
||||
|
||||
const CONTEXT_LINE_COUNT: u32 = 1;
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ mod link_go_to_definition;
|
|||
mod mouse_context_menu;
|
||||
pub mod movement;
|
||||
mod persistence;
|
||||
mod rust_analyzer_ext;
|
||||
pub mod scroll;
|
||||
pub mod selections_collection;
|
||||
|
||||
|
@ -300,6 +301,7 @@ actions!(
|
|||
DeleteToEndOfLine,
|
||||
CutToEndOfLine,
|
||||
DuplicateLine,
|
||||
ExpandMacroRecursively,
|
||||
MoveLineUp,
|
||||
MoveLineDown,
|
||||
JoinLines,
|
||||
|
@ -425,6 +427,8 @@ pub fn init_settings(cx: &mut AppContext) {
|
|||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
init_settings(cx);
|
||||
|
||||
rust_analyzer_ext::apply_related_actions(cx);
|
||||
cx.add_action(Editor::new_file);
|
||||
cx.add_action(Editor::new_file_in_direction);
|
||||
cx.add_action(Editor::cancel);
|
||||
|
|
98
crates/editor/src/rust_analyzer_ext.rs
Normal file
98
crates/editor/src/rust_analyzer_ext.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use gpui::{AppContext, Task, ViewContext};
|
||||
use language::Language;
|
||||
use multi_buffer::MultiBuffer;
|
||||
use project::lsp_ext_command::ExpandMacro;
|
||||
use text::ToPointUtf16;
|
||||
|
||||
use crate::{Editor, ExpandMacroRecursively};
|
||||
|
||||
pub fn apply_related_actions(cx: &mut AppContext) {
|
||||
cx.add_async_action(expand_macro_recursively);
|
||||
}
|
||||
|
||||
pub fn expand_macro_recursively(
|
||||
editor: &mut Editor,
|
||||
_: &ExpandMacroRecursively,
|
||||
cx: &mut ViewContext<'_, '_, Editor>,
|
||||
) -> Option<Task<anyhow::Result<()>>> {
|
||||
if editor.selections.count() == 0 {
|
||||
return None;
|
||||
}
|
||||
let project = editor.project.as_ref()?;
|
||||
let workspace = editor.workspace(cx)?;
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
|
||||
let (trigger_anchor, rust_language, server_to_query, buffer) = editor
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.filter(|selection| selection.start == selection.end)
|
||||
.filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
|
||||
.filter_map(|(buffer_id, trigger_anchor)| {
|
||||
let buffer = multibuffer.buffer(buffer_id)?;
|
||||
let rust_language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
|
||||
if !is_rust_language(&rust_language) {
|
||||
return None;
|
||||
}
|
||||
Some((trigger_anchor, rust_language, buffer))
|
||||
})
|
||||
.find_map(|(trigger_anchor, rust_language, buffer)| {
|
||||
project
|
||||
.read(cx)
|
||||
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||
.into_iter()
|
||||
.find_map(|(adapter, server)| {
|
||||
if adapter.name.0.as_ref() == "rust-analyzer" {
|
||||
Some((
|
||||
trigger_anchor,
|
||||
Arc::clone(&rust_language),
|
||||
server.server_id(),
|
||||
buffer.clone(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})?;
|
||||
|
||||
let project = project.clone();
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
|
||||
let expand_macro_task = project.update(cx, |project, cx| {
|
||||
project.request_lsp(
|
||||
buffer,
|
||||
project::LanguageServerToQuery::Other(server_to_query),
|
||||
ExpandMacro { position },
|
||||
cx,
|
||||
)
|
||||
});
|
||||
Some(cx.spawn(|_, mut cx| async move {
|
||||
let macro_expansion = expand_macro_task.await.context("expand macro")?;
|
||||
if macro_expansion.is_empty() {
|
||||
log::info!("Empty macro expansion for position {position:?}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let buffer = project.update(&mut cx, |project, cx| {
|
||||
project.create_buffer(¯o_expansion.expansion, Some(rust_language), cx)
|
||||
})?;
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let buffer = cx.add_model(|cx| {
|
||||
MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
|
||||
});
|
||||
workspace.add_item(
|
||||
Box::new(cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
anyhow::Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
fn is_rust_language(language: &Language) -> bool {
|
||||
language.name().as_ref() == "Rust"
|
||||
}
|
|
@ -13,6 +13,7 @@ mod link_go_to_definition;
|
|||
mod mouse_context_menu;
|
||||
pub mod movement;
|
||||
mod persistence;
|
||||
mod rust_analyzer_ext;
|
||||
pub mod scroll;
|
||||
pub mod selections_collection;
|
||||
|
||||
|
@ -39,8 +40,8 @@ use futures::FutureExt;
|
|||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use git::diff_hunk_to_display;
|
||||
use gpui::{
|
||||
actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement,
|
||||
AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
|
||||
actions, div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
|
||||
AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
|
||||
DispatchPhase, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontFeatures,
|
||||
FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model,
|
||||
MouseButton, ParentElement, Pixels, Render, RenderOnce, SharedString, Styled, StyledText,
|
||||
|
@ -107,7 +108,7 @@ use ui::{
|
|||
use ui::{prelude::*, IconSize};
|
||||
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
item::{ItemEvent, ItemHandle},
|
||||
item::{Item, ItemEvent, ItemHandle},
|
||||
searchable::SearchEvent,
|
||||
ItemNavHistory, Pane, SplitDirection, ViewId, Workspace,
|
||||
};
|
||||
|
@ -185,82 +186,101 @@ pub fn render_parsed_markdown(
|
|||
})
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct SelectNext {
|
||||
#[serde(default)]
|
||||
pub replace_newest: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct SelectPrevious {
|
||||
#[serde(default)]
|
||||
pub replace_newest: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct SelectAllMatches {
|
||||
#[serde(default)]
|
||||
pub replace_newest: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct SelectToBeginningOfLine {
|
||||
#[serde(default)]
|
||||
stop_at_soft_wraps: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct MovePageUp {
|
||||
#[serde(default)]
|
||||
center_cursor: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct MovePageDown {
|
||||
#[serde(default)]
|
||||
center_cursor: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct SelectToEndOfLine {
|
||||
#[serde(default)]
|
||||
stop_at_soft_wraps: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct ToggleCodeActions {
|
||||
#[serde(default)]
|
||||
pub deployed_from_indicator: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct ConfirmCompletion {
|
||||
#[serde(default)]
|
||||
pub item_ix: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct ConfirmCodeAction {
|
||||
#[serde(default)]
|
||||
pub item_ix: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct ToggleComments {
|
||||
#[serde(default)]
|
||||
pub advance_downwards: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct FoldAt {
|
||||
pub buffer_row: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct UnfoldAt {
|
||||
pub buffer_row: u32,
|
||||
}
|
||||
|
||||
impl_actions!(
|
||||
editor,
|
||||
[
|
||||
SelectNext,
|
||||
SelectPrevious,
|
||||
SelectAllMatches,
|
||||
SelectToBeginningOfLine,
|
||||
MovePageUp,
|
||||
MovePageDown,
|
||||
SelectToEndOfLine,
|
||||
ToggleCodeActions,
|
||||
ConfirmCompletion,
|
||||
ConfirmCodeAction,
|
||||
ToggleComments,
|
||||
FoldAt,
|
||||
UnfoldAt
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum InlayId {
|
||||
Suggestion(usize),
|
||||
|
@ -277,6 +297,8 @@ impl InlayId {
|
|||
}
|
||||
|
||||
actions!(
|
||||
editor,
|
||||
[
|
||||
AddSelectionAbove,
|
||||
AddSelectionBelow,
|
||||
Backspace,
|
||||
|
@ -308,6 +330,7 @@ actions!(
|
|||
DeleteToPreviousSubwordStart,
|
||||
DeleteToPreviousWordStart,
|
||||
DuplicateLine,
|
||||
ExpandMacroRecursively,
|
||||
FindAllReferences,
|
||||
Fold,
|
||||
FoldSelectedRanges,
|
||||
|
@ -392,6 +415,7 @@ actions!(
|
|||
Undo,
|
||||
UndoSelection,
|
||||
UnfoldLines,
|
||||
]
|
||||
);
|
||||
|
||||
enum DocumentHighlightRead {}
|
||||
|
@ -9319,7 +9343,6 @@ impl Render for Editor {
|
|||
scrollbar_width: px(12.),
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
diagnostic_style: cx.theme().diagnostic_style(),
|
||||
// TODO kb find `HighlightStyle` usages
|
||||
// todo!("what about the rest of the highlight style parts?")
|
||||
inlays_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().hint),
|
||||
|
|
|
@ -32,7 +32,7 @@ use gpui::{
|
|||
Style, Styled, TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::language_settings::ShowWhitespaceSetting;
|
||||
use language::{language_settings::ShowWhitespaceSetting, Language};
|
||||
use multi_buffer::Anchor;
|
||||
use project::{
|
||||
project_settings::{GitGutterSetting, ProjectSettings},
|
||||
|
@ -135,11 +135,13 @@ impl EditorElement {
|
|||
|
||||
fn register_actions(&self, cx: &mut WindowContext) {
|
||||
let view = &self.editor;
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
view.update(cx, |editor, cx| {
|
||||
for action in editor.editor_actions.iter() {
|
||||
(action)(cx)
|
||||
}
|
||||
});
|
||||
|
||||
crate::rust_analyzer_ext::apply_related_actions(view, cx);
|
||||
register_action(view, cx, Editor::move_left);
|
||||
register_action(view, cx, Editor::move_right);
|
||||
register_action(view, cx, Editor::move_down);
|
||||
|
@ -385,17 +387,17 @@ impl EditorElement {
|
|||
gutter_bounds: Bounds<Pixels>,
|
||||
stacking_order: &StackingOrder,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
) {
|
||||
let mut click_count = event.click_count;
|
||||
let modifiers = event.modifiers;
|
||||
|
||||
if gutter_bounds.contains_point(&event.position) {
|
||||
click_count = 3; // Simulate triple-click when clicking the gutter to select lines
|
||||
} else if !text_bounds.contains_point(&event.position) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
if !cx.was_top_layer(&event.position, stacking_order) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
||||
|
@ -427,7 +429,7 @@ impl EditorElement {
|
|||
);
|
||||
}
|
||||
|
||||
true
|
||||
cx.stop_propagation();
|
||||
}
|
||||
|
||||
fn mouse_right_down(
|
||||
|
@ -436,9 +438,9 @@ impl EditorElement {
|
|||
position_map: &PositionMap,
|
||||
text_bounds: Bounds<Pixels>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
) {
|
||||
if !text_bounds.contains_point(&event.position) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
||||
mouse_context_menu::deploy_context_menu(
|
||||
|
@ -447,7 +449,7 @@ impl EditorElement {
|
|||
point_for_position.previous_valid,
|
||||
cx,
|
||||
);
|
||||
true
|
||||
cx.stop_propagation();
|
||||
}
|
||||
|
||||
fn mouse_up(
|
||||
|
@ -457,7 +459,7 @@ impl EditorElement {
|
|||
text_bounds: Bounds<Pixels>,
|
||||
stacking_order: &StackingOrder,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
) {
|
||||
let end_selection = editor.has_pending_selection();
|
||||
let pending_nonempty_selections = editor.has_pending_nonempty_selection();
|
||||
|
||||
|
@ -479,10 +481,10 @@ impl EditorElement {
|
|||
go_to_fetched_definition(editor, point, split, cx);
|
||||
}
|
||||
|
||||
return true;
|
||||
cx.stop_propagation();
|
||||
} else if end_selection {
|
||||
cx.stop_propagation();
|
||||
}
|
||||
|
||||
end_selection
|
||||
}
|
||||
|
||||
fn mouse_moved(
|
||||
|
@ -493,7 +495,7 @@ impl EditorElement {
|
|||
gutter_bounds: Bounds<Pixels>,
|
||||
stacking_order: &StackingOrder,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
) {
|
||||
let modifiers = event.modifiers;
|
||||
if editor.has_pending_selection() && event.pressed_button == Some(MouseButton::Left) {
|
||||
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
||||
|
@ -562,11 +564,13 @@ impl EditorElement {
|
|||
}
|
||||
}
|
||||
|
||||
true
|
||||
cx.stop_propagation();
|
||||
} else {
|
||||
update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
|
||||
hover_at(editor, None, cx);
|
||||
gutter_hovered && was_top
|
||||
if gutter_hovered && was_top {
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -576,9 +580,9 @@ impl EditorElement {
|
|||
position_map: &PositionMap,
|
||||
bounds: &InteractiveBounds,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
) {
|
||||
if !bounds.visibly_contains(&event.position, cx) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
let line_height = position_map.line_height;
|
||||
|
@ -602,8 +606,7 @@ impl EditorElement {
|
|||
let y = f32::from((scroll_position.y * line_height - delta.y) / line_height);
|
||||
let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
|
||||
editor.scroll(scroll_position, axis, cx);
|
||||
|
||||
true
|
||||
cx.stop_propagation();
|
||||
}
|
||||
|
||||
fn paint_background(
|
||||
|
@ -749,6 +752,7 @@ impl EditorElement {
|
|||
}
|
||||
}
|
||||
|
||||
cx.with_z_index(1, |cx| {
|
||||
for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
|
||||
if let Some(mut fold_indicator) = fold_indicator {
|
||||
let mut fold_indicator = fold_indicator.into_any_element();
|
||||
|
@ -763,7 +767,8 @@ impl EditorElement {
|
|||
ix as f32 * line_height - (scroll_top % line_height),
|
||||
);
|
||||
let centering_offset = point(
|
||||
(layout.gutter_padding + layout.gutter_margin - fold_indicator_size.width) / 2.,
|
||||
(layout.gutter_padding + layout.gutter_margin - fold_indicator_size.width)
|
||||
/ 2.,
|
||||
(line_height - fold_indicator_size.height) / 2.,
|
||||
);
|
||||
let origin = bounds.origin + position + centering_offset;
|
||||
|
@ -787,6 +792,7 @@ impl EditorElement {
|
|||
|
||||
button.draw(bounds.origin + point(x, y), available_space, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
|
||||
|
@ -824,8 +830,8 @@ impl EditorElement {
|
|||
};
|
||||
|
||||
let color = match status {
|
||||
DiffHunkStatus::Added => gpui::green(), // todo!("use the appropriate color")
|
||||
DiffHunkStatus::Modified => gpui::yellow(), // todo!("use the appropriate color")
|
||||
DiffHunkStatus::Added => cx.theme().status().created,
|
||||
DiffHunkStatus::Modified => cx.theme().status().modified,
|
||||
|
||||
//TODO: This rendering is entirely a horrible hack
|
||||
DiffHunkStatus::Removed => {
|
||||
|
@ -842,7 +848,7 @@ impl EditorElement {
|
|||
cx.paint_quad(
|
||||
highlight_bounds,
|
||||
Corners::all(1. * line_height),
|
||||
gpui::red(), // todo!("use the right color")
|
||||
cx.theme().status().deleted,
|
||||
Edges::default(),
|
||||
transparent_black(),
|
||||
);
|
||||
|
@ -1230,203 +1236,216 @@ impl EditorElement {
|
|||
bounds.upper_right().x - self.style.scrollbar_width
|
||||
}
|
||||
|
||||
// fn paint_scrollbar(
|
||||
// &mut self,
|
||||
// bounds: Bounds<Pixels>,
|
||||
// layout: &mut LayoutState,
|
||||
// editor: &Editor,
|
||||
// cx: &mut ViewContext<Editor>,
|
||||
// ) {
|
||||
// enum ScrollbarMouseHandlers {}
|
||||
// if layout.mode != EditorMode::Full {
|
||||
// return;
|
||||
// }
|
||||
fn paint_scrollbar(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
layout: &mut LayoutState,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
if layout.mode != EditorMode::Full {
|
||||
return;
|
||||
}
|
||||
|
||||
// let style = &self.style.theme.scrollbar;
|
||||
let top = bounds.origin.y;
|
||||
let bottom = bounds.lower_left().y;
|
||||
let right = bounds.lower_right().x;
|
||||
let left = self.scrollbar_left(&bounds);
|
||||
let row_range = layout.scrollbar_row_range.clone();
|
||||
let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
|
||||
|
||||
// let top = bounds.min_y;
|
||||
// let bottom = bounds.max_y;
|
||||
// let right = bounds.max_x;
|
||||
// let left = self.scrollbar_left(&bounds);
|
||||
// let row_range = &layout.scrollbar_row_range;
|
||||
// let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
|
||||
let mut height = bounds.size.height;
|
||||
let mut first_row_y_offset = px(0.0);
|
||||
|
||||
// let mut height = bounds.height();
|
||||
// let mut first_row_y_offset = 0.0;
|
||||
// Impose a minimum height on the scrollbar thumb
|
||||
let row_height = height / max_row;
|
||||
let min_thumb_height = layout.position_map.line_height;
|
||||
let thumb_height = (row_range.end - row_range.start) * row_height;
|
||||
if thumb_height < min_thumb_height {
|
||||
first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
|
||||
height -= min_thumb_height - thumb_height;
|
||||
}
|
||||
|
||||
// // Impose a minimum height on the scrollbar thumb
|
||||
// let row_height = height / max_row;
|
||||
// let min_thumb_height =
|
||||
// style.min_height_factor * cx.font_cache.line_height(self.style.text.font_size);
|
||||
// let thumb_height = (row_range.end - row_range.start) * row_height;
|
||||
// if thumb_height < min_thumb_height {
|
||||
// first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
|
||||
// height -= min_thumb_height - thumb_height;
|
||||
// }
|
||||
let y_for_row = |row: f32| -> Pixels { top + first_row_y_offset + row * row_height };
|
||||
|
||||
// let y_for_row = |row: f32| -> f32 { top + first_row_y_offset + row * row_height };
|
||||
let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
|
||||
let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
|
||||
let track_bounds = Bounds::from_corners(point(left, top), point(right, bottom));
|
||||
let thumb_bounds = Bounds::from_corners(point(left, thumb_top), point(right, thumb_bottom));
|
||||
|
||||
// let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
|
||||
// let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
|
||||
// let track_bounds = Bounds::<Pixels>::from_points(point(left, top), point(right, bottom));
|
||||
// let thumb_bounds = Bounds::<Pixels>::from_points(point(left, thumb_top), point(right, thumb_bottom));
|
||||
if layout.show_scrollbars {
|
||||
cx.paint_quad(
|
||||
track_bounds,
|
||||
Corners::default(),
|
||||
gpui::blue(), // todo!("style.track.background_color")
|
||||
Edges::default(), // todo!("style.track.border")
|
||||
transparent_black(), // todo!("style.track.border")
|
||||
);
|
||||
let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
|
||||
if layout.is_singleton && scrollbar_settings.selections {
|
||||
let start_anchor = Anchor::min();
|
||||
let end_anchor = Anchor::max();
|
||||
let background_ranges = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
|
||||
start_anchor..end_anchor,
|
||||
&layout.position_map.snapshot,
|
||||
50000,
|
||||
);
|
||||
for range in background_ranges {
|
||||
let start_y = y_for_row(range.start().row() as f32);
|
||||
let mut end_y = y_for_row(range.end().row() as f32);
|
||||
if end_y - start_y < px(1.) {
|
||||
end_y = start_y + px(1.);
|
||||
}
|
||||
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
|
||||
cx.paint_quad(
|
||||
bounds,
|
||||
Corners::default(),
|
||||
gpui::yellow(), // todo!("theme.editor.scrollbar")
|
||||
Edges {
|
||||
top: Pixels::ZERO,
|
||||
right: px(1.),
|
||||
bottom: Pixels::ZERO,
|
||||
left: px(1.),
|
||||
},
|
||||
gpui::green(), // todo!("style.thumb.border.color")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// if layout.show_scrollbars {
|
||||
// cx.paint_quad(Quad {
|
||||
// bounds: track_bounds,
|
||||
// border: style.track.border.into(),
|
||||
// background: style.track.background_color,
|
||||
// ..Default::default()
|
||||
// });
|
||||
// let scrollbar_settings = settings::get::<EditorSettings>(cx).scrollbar;
|
||||
// let theme = theme::current(cx);
|
||||
// let scrollbar_theme = &theme.editor.scrollbar;
|
||||
// if layout.is_singleton && scrollbar_settings.selections {
|
||||
// let start_anchor = Anchor::min();
|
||||
// let end_anchor = Anchor::max;
|
||||
// let color = scrollbar_theme.selections;
|
||||
// let border = Border {
|
||||
// width: 1.,
|
||||
// color: style.thumb.border.color,
|
||||
// overlay: false,
|
||||
// top: false,
|
||||
// right: true,
|
||||
// bottom: false,
|
||||
// left: true,
|
||||
// };
|
||||
// let mut push_region = |start: DisplayPoint, end: DisplayPoint| {
|
||||
// let start_y = y_for_row(start.row() as f32);
|
||||
// let mut end_y = y_for_row(end.row() as f32);
|
||||
// if end_y - start_y < 1. {
|
||||
// end_y = start_y + 1.;
|
||||
// }
|
||||
// let bounds = Bounds::<Pixels>::from_points(point(left, start_y), point(right, end_y));
|
||||
if layout.is_singleton && scrollbar_settings.git_diff {
|
||||
for hunk in layout
|
||||
.position_map
|
||||
.snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range(0..(max_row.floor() as u32))
|
||||
{
|
||||
let start_display = Point::new(hunk.buffer_range.start, 0)
|
||||
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||
let end_display = Point::new(hunk.buffer_range.end, 0)
|
||||
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||
let start_y = y_for_row(start_display.row() as f32);
|
||||
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
|
||||
y_for_row((end_display.row() + 1) as f32)
|
||||
} else {
|
||||
y_for_row((end_display.row()) as f32)
|
||||
};
|
||||
|
||||
// cx.paint_quad(Quad {
|
||||
// bounds,
|
||||
// background: Some(color),
|
||||
// border: border.into(),
|
||||
// corner_radii: style.thumb.corner_radii.into(),
|
||||
// })
|
||||
// };
|
||||
// let background_ranges = editor
|
||||
// .background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
|
||||
// start_anchor..end_anchor,
|
||||
// &layout.position_map.snapshot,
|
||||
// 50000,
|
||||
// );
|
||||
// for row in background_ranges {
|
||||
// let start = row.start();
|
||||
// let end = row.end();
|
||||
// push_region(*start, *end);
|
||||
// }
|
||||
// }
|
||||
if end_y - start_y < px(1.) {
|
||||
end_y = start_y + px(1.);
|
||||
}
|
||||
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
|
||||
|
||||
// if layout.is_singleton && scrollbar_settings.git_diff {
|
||||
// let diff_style = scrollbar_theme.git.clone();
|
||||
// for hunk in layout
|
||||
// .position_map
|
||||
// .snapshot
|
||||
// .buffer_snapshot
|
||||
// .git_diff_hunks_in_range(0..(max_row.floor() as u32))
|
||||
// {
|
||||
// let start_display = Point::new(hunk.buffer_range.start, 0)
|
||||
// .to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||
// let end_display = Point::new(hunk.buffer_range.end, 0)
|
||||
// .to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||
// let start_y = y_for_row(start_display.row() as f32);
|
||||
// let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
|
||||
// y_for_row((end_display.row() + 1) as f32)
|
||||
// } else {
|
||||
// y_for_row((end_display.row()) as f32)
|
||||
// };
|
||||
let color = match hunk.status() {
|
||||
DiffHunkStatus::Added => gpui::green(), // todo!("use the right color")
|
||||
DiffHunkStatus::Modified => gpui::yellow(), // todo!("use the right color")
|
||||
DiffHunkStatus::Removed => gpui::red(), // todo!("use the right color")
|
||||
};
|
||||
cx.paint_quad(
|
||||
bounds,
|
||||
Corners::default(),
|
||||
color,
|
||||
Edges {
|
||||
top: Pixels::ZERO,
|
||||
right: px(1.),
|
||||
bottom: Pixels::ZERO,
|
||||
left: px(1.),
|
||||
},
|
||||
gpui::green(), // todo!("style.thumb.border.color")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// if end_y - start_y < 1. {
|
||||
// end_y = start_y + 1.;
|
||||
// }
|
||||
// let bounds = Bounds::<Pixels>::from_points(point(left, start_y), point(right, end_y));
|
||||
cx.paint_quad(
|
||||
thumb_bounds,
|
||||
Corners::default(),
|
||||
gpui::black(), // todo!("style.thumb.background_color")
|
||||
Edges {
|
||||
top: Pixels::ZERO,
|
||||
right: px(1.),
|
||||
bottom: Pixels::ZERO,
|
||||
left: px(1.),
|
||||
},
|
||||
gpui::green(), // todo!("style.thumb.border.color")
|
||||
);
|
||||
}
|
||||
|
||||
// let color = match hunk.status() {
|
||||
// DiffHunkStatus::Added => diff_style.inserted,
|
||||
// DiffHunkStatus::Modified => diff_style.modified,
|
||||
// DiffHunkStatus::Removed => diff_style.deleted,
|
||||
// };
|
||||
let mouse_position = cx.mouse_position();
|
||||
if track_bounds.contains_point(&mouse_position) {
|
||||
cx.set_cursor_style(CursorStyle::Arrow);
|
||||
}
|
||||
|
||||
// let border = Border {
|
||||
// width: 1.,
|
||||
// color: style.thumb.border.color,
|
||||
// overlay: false,
|
||||
// top: false,
|
||||
// right: true,
|
||||
// bottom: false,
|
||||
// left: true,
|
||||
// };
|
||||
cx.on_mouse_event({
|
||||
let editor = self.editor.clone();
|
||||
move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
return;
|
||||
}
|
||||
|
||||
// cx.paint_quad(Quad {
|
||||
// bounds,
|
||||
// background: Some(color),
|
||||
// border: border.into(),
|
||||
// corner_radii: style.thumb.corner_radii.into(),
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
editor.update(cx, |editor, cx| {
|
||||
if event.pressed_button == Some(MouseButton::Left)
|
||||
&& editor.scroll_manager.is_dragging_scrollbar()
|
||||
{
|
||||
let y = mouse_position.y;
|
||||
let new_y = event.position.y;
|
||||
if thumb_top < y && y < thumb_bottom {
|
||||
let mut position = editor.scroll_position(cx);
|
||||
position.y += (new_y - y) * (max_row as f32) / height;
|
||||
if position.y < 0.0 {
|
||||
position.y = 0.0;
|
||||
}
|
||||
editor.set_scroll_position(position, cx);
|
||||
}
|
||||
cx.stop_propagation();
|
||||
} else {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
|
||||
if track_bounds.contains_point(&event.position) {
|
||||
editor.scroll_manager.show_scrollbar(cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// cx.paint_quad(Quad {
|
||||
// bounds: thumb_bounds,
|
||||
// border: style.thumb.border.into(),
|
||||
// background: style.thumb.background_color,
|
||||
// corner_radii: style.thumb.corner_radii.into(),
|
||||
// });
|
||||
// }
|
||||
if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() {
|
||||
cx.on_mouse_event({
|
||||
let editor = self.editor.clone();
|
||||
move |event: &MouseUpEvent, phase, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
|
||||
cx.stop_propagation();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cx.on_mouse_event({
|
||||
let editor = self.editor.clone();
|
||||
move |event: &MouseDownEvent, phase, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
if track_bounds.contains_point(&event.position) {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(true, cx);
|
||||
|
||||
// cx.scene().push_cursor_region(CursorRegion {
|
||||
// bounds: track_bounds,
|
||||
// style: CursorStyle::Arrow,
|
||||
// });
|
||||
// let region_id = cx.view_id();
|
||||
// cx.scene().push_mouse_region(
|
||||
// MouseRegion::new::<ScrollbarMouseHandlers>(region_id, region_id, track_bounds)
|
||||
// .on_move(move |event, editor: &mut Editor, cx| {
|
||||
// if event.pressed_button.is_none() {
|
||||
// editor.scroll_manager.show_scrollbar(cx);
|
||||
// }
|
||||
// })
|
||||
// .on_down(MouseButton::Left, {
|
||||
// let row_range = row_range.clone();
|
||||
// move |event, editor: &mut Editor, cx| {
|
||||
// let y = event.position.y;
|
||||
// if y < thumb_top || thumb_bottom < y {
|
||||
// let center_row = ((y - top) * max_row as f32 / height).round() as u32;
|
||||
// let top_row = center_row
|
||||
// .saturating_sub((row_range.end - row_range.start) as u32 / 2);
|
||||
// let mut position = editor.scroll_position(cx);
|
||||
// position.set_y(top_row as f32);
|
||||
// editor.set_scroll_position(position, cx);
|
||||
// } else {
|
||||
// editor.scroll_manager.show_scrollbar(cx);
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// .on_drag(MouseButton::Left, {
|
||||
// move |event, editor: &mut Editor, cx| {
|
||||
// if event.end {
|
||||
// return;
|
||||
// }
|
||||
let y = event.position.y;
|
||||
if y < thumb_top || thumb_bottom < y {
|
||||
let center_row =
|
||||
((y - top) * max_row as f32 / height).round() as u32;
|
||||
let top_row = center_row
|
||||
.saturating_sub((row_range.end - row_range.start) as u32 / 2);
|
||||
let mut position = editor.scroll_position(cx);
|
||||
position.y = top_row as f32;
|
||||
editor.set_scroll_position(position, cx);
|
||||
} else {
|
||||
editor.scroll_manager.show_scrollbar(cx);
|
||||
}
|
||||
|
||||
// let y = event.prev_mouse_position.y;
|
||||
// let new_y = event.position.y;
|
||||
// if thumb_top < y && y < thumb_bottom {
|
||||
// let mut position = editor.scroll_position(cx);
|
||||
// position.set_y(position.y + (new_y - y) * (max_row as f32) / height);
|
||||
// if position.y < 0.0 {
|
||||
// position.set_y(0.);
|
||||
// }
|
||||
// editor.set_scroll_position(position, cx);
|
||||
// }
|
||||
// }
|
||||
// }),
|
||||
// );
|
||||
// }
|
||||
cx.stop_propagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn paint_highlighted_range(
|
||||
|
@ -2452,12 +2471,9 @@ impl EditorElement {
|
|||
return;
|
||||
}
|
||||
|
||||
let handled = editor.update(cx, |editor, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
|
||||
});
|
||||
if handled {
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2471,7 +2487,7 @@ impl EditorElement {
|
|||
return;
|
||||
}
|
||||
|
||||
let handled = match event.button {
|
||||
match event.button {
|
||||
MouseButton::Left => editor.update(cx, |editor, cx| {
|
||||
Self::mouse_left_down(
|
||||
editor,
|
||||
|
@ -2486,12 +2502,8 @@ impl EditorElement {
|
|||
MouseButton::Right => editor.update(cx, |editor, cx| {
|
||||
Self::mouse_right_down(editor, event, &position_map, text_bounds, cx)
|
||||
}),
|
||||
_ => false,
|
||||
_ => {}
|
||||
};
|
||||
|
||||
if handled {
|
||||
cx.stop_propagation()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2501,7 +2513,7 @@ impl EditorElement {
|
|||
let stacking_order = cx.stacking_order().clone();
|
||||
|
||||
move |event: &MouseUpEvent, phase, cx| {
|
||||
let handled = editor.update(cx, |editor, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
Self::mouse_up(
|
||||
editor,
|
||||
event,
|
||||
|
@ -2511,10 +2523,6 @@ impl EditorElement {
|
|||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
if handled {
|
||||
cx.stop_propagation()
|
||||
}
|
||||
}
|
||||
});
|
||||
cx.on_mouse_event({
|
||||
|
@ -2527,7 +2535,7 @@ impl EditorElement {
|
|||
return;
|
||||
}
|
||||
|
||||
let stop_propogating = editor.update(cx, |editor, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
Self::mouse_moved(
|
||||
editor,
|
||||
event,
|
||||
|
@ -2538,10 +2546,6 @@ impl EditorElement {
|
|||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
if stop_propogating {
|
||||
cx.stop_propagation()
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2840,9 +2844,11 @@ impl Element for EditorElement {
|
|||
cx.with_z_index(1, |cx| {
|
||||
cx.with_element_id(Some("editor_blocks"), |cx| {
|
||||
self.paint_blocks(bounds, &mut layout, cx);
|
||||
})
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
cx.with_z_index(2, |cx| self.paint_scrollbar(bounds, &mut layout, cx));
|
||||
});
|
||||
});
|
||||
})
|
||||
|
@ -2944,7 +2950,7 @@ impl PositionMap {
|
|||
) -> PointForPosition {
|
||||
let scroll_position = self.snapshot.scroll_position();
|
||||
let position = position - text_bounds.origin;
|
||||
let y = position.y.max(px(0.)).min(self.size.width);
|
||||
let y = position.y.max(px(0.)).min(self.size.height);
|
||||
let x = position.x + (scroll_position.x * self.em_width);
|
||||
let row = (f32::from(y / self.line_height) + scroll_position.y) as u32;
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
|
|||
pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.);
|
||||
pub const HOVER_POPOVER_GAP: Pixels = px(10.);
|
||||
|
||||
actions!(Hover);
|
||||
actions!(editor, [Hover]);
|
||||
|
||||
/// Bindable action which uses the most recent selection head to trigger a hover
|
||||
pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
|
||||
|
|
119
crates/editor2/src/rust_analyzer_ext.rs
Normal file
119
crates/editor2/src/rust_analyzer_ext.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use gpui::{Context, Model, View, ViewContext, VisualContext, WindowContext};
|
||||
use language::Language;
|
||||
use multi_buffer::MultiBuffer;
|
||||
use project::lsp_ext_command::ExpandMacro;
|
||||
use text::ToPointUtf16;
|
||||
|
||||
use crate::{element::register_action, Editor, ExpandMacroRecursively};
|
||||
|
||||
pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
|
||||
let is_rust_related = editor.update(cx, |editor, cx| {
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.all_buffers()
|
||||
.iter()
|
||||
.any(|b| match b.read(cx).language() {
|
||||
Some(l) => is_rust_language(l),
|
||||
None => false,
|
||||
})
|
||||
});
|
||||
|
||||
if is_rust_related {
|
||||
register_action(editor, cx, expand_macro_recursively);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand_macro_recursively(
|
||||
editor: &mut Editor,
|
||||
_: &ExpandMacroRecursively,
|
||||
cx: &mut ViewContext<'_, Editor>,
|
||||
) {
|
||||
if editor.selections.count() == 0 {
|
||||
return;
|
||||
}
|
||||
let Some(project) = &editor.project else {
|
||||
return;
|
||||
};
|
||||
let Some(workspace) = editor.workspace() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
|
||||
let Some((trigger_anchor, rust_language, server_to_query, buffer)) = editor
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.filter(|selection| selection.start == selection.end)
|
||||
.filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
|
||||
.filter_map(|(buffer_id, trigger_anchor)| {
|
||||
let buffer = multibuffer.buffer(buffer_id)?;
|
||||
let rust_language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
|
||||
if !is_rust_language(&rust_language) {
|
||||
return None;
|
||||
}
|
||||
Some((trigger_anchor, rust_language, buffer))
|
||||
})
|
||||
.find_map(|(trigger_anchor, rust_language, buffer)| {
|
||||
project
|
||||
.read(cx)
|
||||
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||
.into_iter()
|
||||
.find_map(|(adapter, server)| {
|
||||
if adapter.name.0.as_ref() == "rust-analyzer" {
|
||||
Some((
|
||||
trigger_anchor,
|
||||
Arc::clone(&rust_language),
|
||||
server.server_id(),
|
||||
buffer.clone(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let project = project.clone();
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
|
||||
let expand_macro_task = project.update(cx, |project, cx| {
|
||||
project.request_lsp(
|
||||
buffer,
|
||||
project::LanguageServerToQuery::Other(server_to_query),
|
||||
ExpandMacro { position },
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.spawn(|editor, mut cx| async move {
|
||||
let macro_expansion = expand_macro_task.await.context("expand macro")?;
|
||||
if macro_expansion.is_empty() {
|
||||
log::info!("Empty macro expansion for position {position:?}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let buffer = project.update(&mut cx, |project, cx| {
|
||||
project.create_buffer(¯o_expansion.expansion, Some(rust_language), cx)
|
||||
})??;
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let buffer = cx.build_model(|cx| {
|
||||
MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
|
||||
});
|
||||
workspace.add_item(
|
||||
Box::new(cx.build_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn is_rust_language(language: &Language) -> bool {
|
||||
language.name().as_ref() == "Rust"
|
||||
}
|
|
@ -136,6 +136,7 @@ pub struct ScrollManager {
|
|||
last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
|
||||
show_scrollbars: bool,
|
||||
hide_scrollbar_task: Option<Task<()>>,
|
||||
dragging_scrollbar: bool,
|
||||
visible_line_count: Option<f32>,
|
||||
}
|
||||
|
||||
|
@ -148,6 +149,7 @@ impl ScrollManager {
|
|||
autoscroll_request: None,
|
||||
show_scrollbars: true,
|
||||
hide_scrollbar_task: None,
|
||||
dragging_scrollbar: false,
|
||||
last_autoscroll: None,
|
||||
visible_line_count: None,
|
||||
}
|
||||
|
@ -278,6 +280,17 @@ impl ScrollManager {
|
|||
self.autoscroll_request.is_some()
|
||||
}
|
||||
|
||||
pub fn is_dragging_scrollbar(&self) -> bool {
|
||||
self.dragging_scrollbar
|
||||
}
|
||||
|
||||
pub fn set_is_dragging_scrollbar(&mut self, dragging: bool, cx: &mut ViewContext<Editor>) {
|
||||
if dragging != self.dragging_scrollbar {
|
||||
self.dragging_scrollbar = dragging;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
|
||||
if max < self.anchor.offset.x {
|
||||
self.anchor.offset.x = max;
|
||||
|
|
|
@ -32,7 +32,7 @@ impl Render for DeployFeedbackButton {
|
|||
IconButton::new("give-feedback", Icon::Envelope)
|
||||
.style(ui::ButtonStyle::Subtle)
|
||||
.selected(is_open)
|
||||
.tooltip(|cx| Tooltip::text("Give Feedback", cx))
|
||||
.tooltip(|cx| Tooltip::text("Share Feedback", cx))
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(GiveFeedback));
|
||||
})
|
||||
|
|
|
@ -5,15 +5,18 @@ use workspace::Workspace;
|
|||
pub mod deploy_feedback_button;
|
||||
pub mod feedback_modal;
|
||||
|
||||
actions!(GiveFeedback, SubmitFeedback);
|
||||
actions!(feedback, [GiveFeedback, SubmitFeedback]);
|
||||
|
||||
mod system_specs;
|
||||
|
||||
actions!(
|
||||
zed,
|
||||
[
|
||||
CopySystemSpecsIntoClipboard,
|
||||
FileBugReport,
|
||||
RequestFeature,
|
||||
OpenZedCommunityRepo
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
|
|
|
@ -1,28 +1,38 @@
|
|||
use std::{ops::RangeInclusive, sync::Arc};
|
||||
|
||||
use anyhow::bail;
|
||||
use anyhow::{anyhow, bail};
|
||||
use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::{Editor, EditorEvent};
|
||||
use futures::AsyncReadExt;
|
||||
use gpui::{
|
||||
div, red, rems, serde_json, AppContext, DismissEvent, Div, EventEmitter, FocusHandle,
|
||||
FocusableView, Model, PromptLevel, Render, Task, View, ViewContext,
|
||||
div, rems, serde_json, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
|
||||
Model, PromptLevel, Render, Task, View, ViewContext,
|
||||
};
|
||||
use isahc::Request;
|
||||
use language::Buffer;
|
||||
use project::Project;
|
||||
use regex::Regex;
|
||||
use serde_derive::Serialize;
|
||||
use ui::{prelude::*, Button, ButtonStyle, Label, Tooltip};
|
||||
use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedCommunityRepo};
|
||||
|
||||
// For UI testing purposes
|
||||
const SEND_SUCCESS_IN_DEV_MODE: bool = true;
|
||||
|
||||
// Temporary, until tests are in place
|
||||
#[cfg(debug_assertions)]
|
||||
const DEV_MODE: bool = true;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
const DEV_MODE: bool = false;
|
||||
|
||||
const DATABASE_KEY_NAME: &str = "email_address";
|
||||
const EMAIL_REGEX: &str = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b";
|
||||
const FEEDBACK_CHAR_LIMIT: RangeInclusive<usize> = 10..=5000;
|
||||
const FEEDBACK_CHAR_LIMIT: RangeInclusive<i32> = 10..=5000;
|
||||
const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
|
||||
"Feedback failed to submit, see error log for details.";
|
||||
|
||||
|
@ -41,8 +51,9 @@ pub struct FeedbackModal {
|
|||
system_specs: SystemSpecs,
|
||||
feedback_editor: View<Editor>,
|
||||
email_address_editor: View<Editor>,
|
||||
character_count: usize,
|
||||
pending_submission: bool,
|
||||
awaiting_submission: bool,
|
||||
user_submitted: bool,
|
||||
character_count: i32,
|
||||
}
|
||||
|
||||
impl FocusableView for FeedbackModal {
|
||||
|
@ -52,6 +63,25 @@ impl FocusableView for FeedbackModal {
|
|||
}
|
||||
impl EventEmitter<DismissEvent> for FeedbackModal {}
|
||||
|
||||
impl ModalView for FeedbackModal {
|
||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>) -> Task<bool> {
|
||||
if self.user_submitted {
|
||||
self.set_user_submitted(false, cx);
|
||||
return cx.spawn(|_, _| async { true });
|
||||
}
|
||||
|
||||
let has_feedback = self.feedback_editor.read(cx).text_option(cx).is_some();
|
||||
|
||||
if !has_feedback {
|
||||
return cx.spawn(|_, _| async { true });
|
||||
}
|
||||
|
||||
let answer = cx.prompt(PromptLevel::Info, "Discard feedback?", &["Yes", "No"]);
|
||||
|
||||
cx.spawn(|_, _| async { answer.await.ok() == Some(0) })
|
||||
}
|
||||
}
|
||||
|
||||
impl FeedbackModal {
|
||||
pub fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||
let _handle = cx.view().downgrade();
|
||||
|
@ -104,6 +134,11 @@ impl FeedbackModal {
|
|||
|
||||
let feedback_editor = cx.build_view(|cx| {
|
||||
let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx);
|
||||
editor.set_placeholder_text(
|
||||
"You can use markdown to organize your feedback wiht add code and links, or organize feedback.",
|
||||
cx,
|
||||
);
|
||||
// editor.set_show_gutter(false, cx);
|
||||
editor.set_vertical_scroll_margin(5, cx);
|
||||
editor
|
||||
});
|
||||
|
@ -119,7 +154,7 @@ impl FeedbackModal {
|
|||
.as_singleton()
|
||||
.expect("Feedback editor is never a multi-buffer")
|
||||
.read(cx)
|
||||
.len();
|
||||
.len() as i32;
|
||||
cx.notify();
|
||||
}
|
||||
_ => {}
|
||||
|
@ -131,7 +166,8 @@ impl FeedbackModal {
|
|||
system_specs: system_specs.clone(),
|
||||
feedback_editor,
|
||||
email_address_editor,
|
||||
pending_submission: false,
|
||||
awaiting_submission: false,
|
||||
user_submitted: false,
|
||||
character_count: 0,
|
||||
}
|
||||
}
|
||||
|
@ -163,16 +199,25 @@ impl FeedbackModal {
|
|||
}
|
||||
};
|
||||
|
||||
this.update(&mut cx, |feedback_editor, cx| {
|
||||
feedback_editor.set_pending_submission(true, cx);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.set_awaiting_submission(true, cx);
|
||||
})
|
||||
.log_err();
|
||||
|
||||
if let Err(error) =
|
||||
FeedbackModal::submit_feedback(&feedback_text, email, client, specs).await
|
||||
{
|
||||
let res =
|
||||
FeedbackModal::submit_feedback(&feedback_text, email, client, specs).await;
|
||||
|
||||
match res {
|
||||
Ok(_) => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.set_user_submitted(true, cx);
|
||||
cx.emit(DismissEvent)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!("{}", error);
|
||||
this.update(&mut cx, |feedback_editor, cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let prompt = cx.prompt(
|
||||
PromptLevel::Critical,
|
||||
FEEDBACK_SUBMISSION_ERROR_TEXT,
|
||||
|
@ -182,18 +227,25 @@ impl FeedbackModal {
|
|||
prompt.await.ok();
|
||||
})
|
||||
.detach();
|
||||
feedback_editor.set_pending_submission(false, cx);
|
||||
this.set_awaiting_submission(false, cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn set_pending_submission(&mut self, pending_submission: bool, cx: &mut ViewContext<Self>) {
|
||||
self.pending_submission = pending_submission;
|
||||
fn set_awaiting_submission(&mut self, awaiting_submission: bool, cx: &mut ViewContext<Self>) {
|
||||
self.awaiting_submission = awaiting_submission;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_user_submitted(&mut self, user_submitted: bool, cx: &mut ViewContext<Self>) {
|
||||
self.user_submitted = user_submitted;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
@ -203,6 +255,14 @@ impl FeedbackModal {
|
|||
zed_client: Arc<Client>,
|
||||
system_specs: SystemSpecs,
|
||||
) -> anyhow::Result<()> {
|
||||
if DEV_MODE {
|
||||
if SEND_SUCCESS_IN_DEV_MODE {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(anyhow!("Error submitting feedback"));
|
||||
}
|
||||
}
|
||||
|
||||
let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL);
|
||||
let telemetry = zed_client.telemetry();
|
||||
let metrics_id = telemetry.metrics_id();
|
||||
|
@ -233,11 +293,8 @@ impl FeedbackModal {
|
|||
}
|
||||
|
||||
// TODO: Escape button calls dismiss
|
||||
// TODO: Should do same as hitting cancel / clicking outside of modal
|
||||
// Close immediately if no text in field
|
||||
// Ask to close if text in the field
|
||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
cx.emit(DismissEvent)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,132 +308,128 @@ impl Render for FeedbackModal {
|
|||
};
|
||||
|
||||
let valid_character_count = FEEDBACK_CHAR_LIMIT.contains(&self.character_count);
|
||||
let characters_remaining =
|
||||
if valid_character_count || self.character_count > *FEEDBACK_CHAR_LIMIT.end() {
|
||||
*FEEDBACK_CHAR_LIMIT.end() as i32 - self.character_count as i32
|
||||
} else {
|
||||
self.character_count as i32 - *FEEDBACK_CHAR_LIMIT.start() as i32
|
||||
};
|
||||
|
||||
let allow_submission =
|
||||
valid_character_count && valid_email_address && !self.pending_submission;
|
||||
valid_character_count && valid_email_address && !self.awaiting_submission;
|
||||
|
||||
let has_feedback = self.feedback_editor.read(cx).text_option(cx).is_some();
|
||||
|
||||
let submit_button_text = if self.pending_submission {
|
||||
"Sending..."
|
||||
let submit_button_text = if self.awaiting_submission {
|
||||
"Submitting..."
|
||||
} else {
|
||||
"Send Feedback"
|
||||
"Submit"
|
||||
};
|
||||
let dismiss = cx.listener(|_, _, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
});
|
||||
// TODO: get the "are you sure you want to dismiss?" prompt here working
|
||||
let dismiss_prompt = cx.listener(|_, _, _| {
|
||||
// let answer = cx.prompt(PromptLevel::Info, "Exit feedback?", &["Yes", "No"]);
|
||||
// cx.spawn(|_, _| async move {
|
||||
// let answer = answer.await.ok();
|
||||
// if answer == Some(0) {
|
||||
// cx.emit(DismissEvent);
|
||||
// }
|
||||
// })
|
||||
// .detach();
|
||||
});
|
||||
|
||||
let open_community_repo =
|
||||
cx.listener(|_, _, cx| cx.dispatch_action(Box::new(OpenZedCommunityRepo)));
|
||||
|
||||
// TODO: Nate UI pass
|
||||
// Moved this here because providing it inline breaks rustfmt
|
||||
let provide_an_email_address =
|
||||
"Provide an email address if you want us to be able to reply.";
|
||||
|
||||
v_stack()
|
||||
.elevation_3(cx)
|
||||
.key_context("GiveFeedback")
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.min_w(rems(40.))
|
||||
.max_w(rems(96.))
|
||||
.border()
|
||||
.border_color(red())
|
||||
.h(rems(40.))
|
||||
.p_2()
|
||||
.gap_2()
|
||||
.h(rems(32.))
|
||||
.p_4()
|
||||
.gap_4()
|
||||
.child(v_stack().child(
|
||||
// TODO: Add Headline component to `ui2`
|
||||
div().text_xl().child("Share Feedback"),
|
||||
))
|
||||
.child(
|
||||
v_stack().child(
|
||||
div()
|
||||
.size_full()
|
||||
.child(Label::new("Give Feedback").color(Color::Default))
|
||||
.child(Label::new("This editor supports markdown").color(Color::Muted)),
|
||||
),
|
||||
Label::new(if self.character_count < *FEEDBACK_CHAR_LIMIT.start() {
|
||||
format!(
|
||||
"Feedback must be at least {} characters.",
|
||||
FEEDBACK_CHAR_LIMIT.start()
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Characters: {}",
|
||||
*FEEDBACK_CHAR_LIMIT.end() - self.character_count
|
||||
)
|
||||
})
|
||||
.color(if valid_character_count {
|
||||
Color::Success
|
||||
} else {
|
||||
Color::Error
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.p_2()
|
||||
.border()
|
||||
.rounded_md()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(self.feedback_editor.clone()),
|
||||
)
|
||||
.child(
|
||||
div().child(
|
||||
Label::new(format!(
|
||||
"Characters: {}",
|
||||
characters_remaining
|
||||
))
|
||||
.when_else(
|
||||
valid_character_count,
|
||||
|this| this.color(Color::Success),
|
||||
|this| this.color(Color::Error)
|
||||
)
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child(
|
||||
h_stack()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.p_2()
|
||||
.border()
|
||||
.rounded_md()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(self.email_address_editor.clone())
|
||||
.child(self.email_address_editor.clone()),
|
||||
)
|
||||
.child(
|
||||
h_stack()
|
||||
.justify_between()
|
||||
.gap_1()
|
||||
.child(Button::new("community_repo", "Community Repo")
|
||||
.style(ButtonStyle::Filled)
|
||||
.color(Color::Muted)
|
||||
.on_click(open_community_repo)
|
||||
.child(
|
||||
Button::new("community_repo", "Community Repo")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.icon(Icon::ExternalLink)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(open_community_repo),
|
||||
)
|
||||
.child(h_stack().justify_between().gap_1()
|
||||
.child(
|
||||
h_stack()
|
||||
.gap_1()
|
||||
.child(
|
||||
Button::new("cancel_feedback", "Cancel")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.color(Color::Muted)
|
||||
// TODO: replicate this logic when clicking outside the modal
|
||||
// TODO: Will require somehow overriding the modal dismal default behavior
|
||||
.when_else(
|
||||
has_feedback,
|
||||
|this| this.on_click(dismiss_prompt),
|
||||
|this| this.on_click(dismiss)
|
||||
)
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
this.update(&mut cx, |_, cx| {
|
||||
cx.emit(DismissEvent)
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("send_feedback", submit_button_text)
|
||||
.color(Color::Accent)
|
||||
.style(ButtonStyle::Filled)
|
||||
// TODO: Ensure that while submitting, "Sending..." is shown and disable the button
|
||||
// TODO: If submit errors: show popup with error, don't close modal, set text back to "Send Feedback", and re-enable button
|
||||
// TODO: If submit is successful, close the modal
|
||||
// TODO: If submit errors: show popup with error, don't close modal, set text back to "Submit", and re-enable button
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
let _ = this.submit(cx);
|
||||
this.submit(cx).detach();
|
||||
}))
|
||||
.tooltip(|cx| {
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
"Submit feedback to the Zed team.",
|
||||
None,
|
||||
"Provide an email address if you want us to be able to reply.",
|
||||
provide_an_email_address,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.when(!allow_submission, |this| this.disabled(true))
|
||||
.when(!allow_submission, |this| this.disabled(true)),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Maybe store email address whenever the modal is closed, versus just on submit, so users can remove it if they want without submitting
|
||||
// TODO: Testing of various button states, dismissal prompts, etc.
|
||||
|
|
|
@ -17,9 +17,11 @@ use std::{
|
|||
use text::Point;
|
||||
use ui::{prelude::*, HighlightedLabel, ListItem};
|
||||
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
|
||||
use workspace::Workspace;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
actions!(Toggle);
|
||||
actions!(file_finder, [Toggle]);
|
||||
|
||||
impl ModalView for FileFinder {}
|
||||
|
||||
pub struct FileFinder {
|
||||
picker: View<Picker<FileFinderDelegate>>,
|
||||
|
|
|
@ -8,8 +8,9 @@ use text::{Bias, Point};
|
|||
use theme::ActiveTheme;
|
||||
use ui::{h_stack, prelude::*, v_stack, Label};
|
||||
use util::paths::FILE_ROW_COLUMN_DELIMITER;
|
||||
use workspace::ModalView;
|
||||
|
||||
actions!(Toggle);
|
||||
actions!(go_to_line, [Toggle]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(GoToLine::register).detach();
|
||||
|
@ -23,6 +24,8 @@ pub struct GoToLine {
|
|||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl ModalView for GoToLine {}
|
||||
|
||||
impl FocusableView for GoToLine {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
self.line_editor.focus_handle(cx)
|
||||
|
|
|
@ -22,7 +22,7 @@ Actions are frequently unit structs, for which we have a macro. The above could
|
|||
|
||||
```rust
|
||||
mod menu {
|
||||
actions!(MoveUp, MoveDown);
|
||||
actions!(gpui, [MoveUp, MoveDown]);
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -3,34 +3,33 @@ use anyhow::{anyhow, Context, Result};
|
|||
use collections::HashMap;
|
||||
pub use no_action::NoAction;
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
ops::Deref,
|
||||
};
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
/// Actions are used to implement keyboard-driven UI.
|
||||
/// When you declare an action, you can bind keys to the action in the keymap and
|
||||
/// listeners for that action in the element tree.
|
||||
///
|
||||
/// To declare a list of simple actions, you can use the actions! macro, which defines a simple unit struct
|
||||
/// action for each listed action name.
|
||||
/// action for each listed action name in the given namespace.
|
||||
/// ```rust
|
||||
/// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline);
|
||||
/// actions!(editor, [MoveUp, MoveDown, MoveLeft, MoveRight, Newline]);
|
||||
/// ```
|
||||
/// More complex data types can also be actions. If you annotate your type with the action derive macro
|
||||
/// it will be implemented and registered automatically.
|
||||
/// More complex data types can also be actions, providing they implement Clone, PartialEq,
|
||||
/// and serde_derive::Deserialize.
|
||||
/// Use `impl_actions!` to automatically implement the action in the given namespace.
|
||||
/// ```
|
||||
/// #[derive(Clone, PartialEq, serde_derive::Deserialize, Action)]
|
||||
/// #[derive(Clone, PartialEq, serde_derive::Deserialize)]
|
||||
/// pub struct SelectNext {
|
||||
/// pub replace_newest: bool,
|
||||
/// }
|
||||
/// impl_actions!(editor, [SelectNext]);
|
||||
/// ```
|
||||
///
|
||||
/// If you want to control the behavior of the action trait manually, you can use the lower-level `#[register_action]`
|
||||
/// macro, which only generates the code needed to register your action before `main`.
|
||||
///
|
||||
/// ```
|
||||
/// #[gpui::register_action]
|
||||
/// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::fmt::Debug)]
|
||||
/// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone)]
|
||||
/// pub struct Paste {
|
||||
/// pub content: SharedString,
|
||||
/// }
|
||||
|
@ -38,6 +37,7 @@ use std::{
|
|||
/// impl gpui::Action for Paste {
|
||||
/// ///...
|
||||
/// }
|
||||
/// register_action!(Paste);
|
||||
/// ```
|
||||
pub trait Action: 'static {
|
||||
fn boxed_clone(&self) -> Box<dyn Action>;
|
||||
|
@ -56,7 +56,7 @@ pub trait Action: 'static {
|
|||
impl std::fmt::Debug for dyn Action {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("dyn Action")
|
||||
.field("type_name", &self.name())
|
||||
.field("name", &self.name())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ impl ActionRegistry {
|
|||
for builder in __GPUI_ACTIONS {
|
||||
let action = builder();
|
||||
//todo(remove)
|
||||
let name: SharedString = remove_the_2(action.name).into();
|
||||
let name: SharedString = action.name.into();
|
||||
self.builders_by_name.insert(name.clone(), action.build);
|
||||
self.names_by_type_id.insert(action.type_id, name.clone());
|
||||
self.all_names.push(name);
|
||||
|
@ -139,11 +139,9 @@ impl ActionRegistry {
|
|||
name: &str,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<Box<dyn Action>> {
|
||||
//todo(remove)
|
||||
let name = remove_the_2(name);
|
||||
let build_action = self
|
||||
.builders_by_name
|
||||
.get(name.deref())
|
||||
.get(name)
|
||||
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
|
||||
(build_action)(params.unwrap_or_else(|| json!({})))
|
||||
.with_context(|| format!("Attempting to build action {}", name))
|
||||
|
@ -155,36 +153,88 @@ impl ActionRegistry {
|
|||
}
|
||||
|
||||
/// Defines unit structs that can be used as actions.
|
||||
/// To use more complex data types as actions, annotate your type with the #[action] macro.
|
||||
/// To use more complex data types as actions, use `impl_actions!`
|
||||
#[macro_export]
|
||||
macro_rules! actions {
|
||||
() => {};
|
||||
|
||||
( $name:ident ) => {
|
||||
#[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::serde_derive::Deserialize, gpui::Action)]
|
||||
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
||||
$(
|
||||
#[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::serde_derive::Deserialize)]
|
||||
#[serde(crate = "gpui::serde")]
|
||||
pub struct $name;
|
||||
};
|
||||
|
||||
( $name:ident, $($rest:tt)* ) => {
|
||||
actions!($name);
|
||||
actions!($($rest)*);
|
||||
gpui::__impl_action!($namespace, $name,
|
||||
fn build(_: gpui::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
||||
Ok(Box::new(Self))
|
||||
}
|
||||
);
|
||||
|
||||
gpui::register_action!($name);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
//todo!(remove)
|
||||
pub fn remove_the_2(action_name: &str) -> String {
|
||||
let mut separator_matches = action_name.rmatch_indices("::");
|
||||
separator_matches.next().unwrap();
|
||||
let name_start_ix = separator_matches.next().map_or(0, |(ix, _)| ix + 2);
|
||||
// todo!() remove the 2 replacement when migration is done
|
||||
action_name[name_start_ix..]
|
||||
.replace("2::", "::")
|
||||
.to_string()
|
||||
/// Implements the Action trait for any struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize
|
||||
#[macro_export]
|
||||
macro_rules! impl_actions {
|
||||
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
||||
$(
|
||||
gpui::__impl_action!($namespace, $name,
|
||||
fn build(value: gpui::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
||||
Ok(std::boxed::Box::new(gpui::serde_json::from_value::<Self>(value)?))
|
||||
}
|
||||
);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __impl_action {
|
||||
($namespace:path, $name:ident, $build:item) => {
|
||||
impl gpui::Action for $name {
|
||||
fn name(&self) -> &'static str
|
||||
{
|
||||
concat!(
|
||||
stringify!($namespace),
|
||||
"::",
|
||||
stringify!($name),
|
||||
)
|
||||
}
|
||||
|
||||
// todo!() why is this needed in addition to name?
|
||||
fn debug_name() -> &'static str
|
||||
where
|
||||
Self: ::std::marker::Sized
|
||||
{
|
||||
concat!(
|
||||
stringify!($namespace),
|
||||
"::",
|
||||
stringify!($name),
|
||||
)
|
||||
}
|
||||
|
||||
$build
|
||||
|
||||
fn partial_eq(&self, action: &dyn gpui::Action) -> bool {
|
||||
action
|
||||
.as_any()
|
||||
.downcast_ref::<Self>()
|
||||
.map_or(false, |a| self == a)
|
||||
}
|
||||
|
||||
fn boxed_clone(&self) -> std::boxed::Box<dyn gpui::Action> {
|
||||
::std::boxed::Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn ::std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod no_action {
|
||||
use crate as gpui;
|
||||
|
||||
actions!(NoAction);
|
||||
actions!(zed, [NoAction]);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ use smallvec::SmallVec;
|
|||
use smol::future::FutureExt;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use test_context::*;
|
||||
use time::UtcOffset;
|
||||
|
||||
use crate::{
|
||||
current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
|
||||
|
@ -536,6 +537,10 @@ impl AppContext {
|
|||
self.platform.restart()
|
||||
}
|
||||
|
||||
pub fn local_timezone(&self) -> UtcOffset {
|
||||
self.platform.local_timezone()
|
||||
}
|
||||
|
||||
pub(crate) fn push_effect(&mut self, effect: Effect) {
|
||||
match &effect {
|
||||
Effect::Notify { emitter } => {
|
||||
|
@ -1110,6 +1115,10 @@ impl AppContext {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_active_drag(&self) -> bool {
|
||||
self.active_drag.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AppContext {
|
||||
|
|
|
@ -69,24 +69,6 @@ pub trait IntoElement: Sized {
|
|||
self.map(|this| if condition { then(this) } else { this })
|
||||
}
|
||||
|
||||
fn when_else(
|
||||
self,
|
||||
condition: bool,
|
||||
then: impl FnOnce(Self) -> Self,
|
||||
otherwise: impl FnOnce(Self) -> Self,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.map(|this| {
|
||||
if condition {
|
||||
then(this)
|
||||
} else {
|
||||
otherwise(this)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
|
|
496
crates/gpui2/src/elements/list.rs
Normal file
496
crates/gpui2/src/elements/list.rs
Normal file
|
@ -0,0 +1,496 @@
|
|||
use crate::{
|
||||
px, AnyElement, AvailableSpace, BorrowAppContext, DispatchPhase, Element, IntoElement, Pixels,
|
||||
Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, WindowContext,
|
||||
};
|
||||
use collections::VecDeque;
|
||||
use refineable::Refineable as _;
|
||||
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
|
||||
pub fn list(state: ListState) -> List {
|
||||
List {
|
||||
state,
|
||||
style: StyleRefinement::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct List {
|
||||
state: ListState,
|
||||
style: StyleRefinement,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ListState(Rc<RefCell<StateInner>>);
|
||||
|
||||
struct StateInner {
|
||||
last_layout_width: Option<Pixels>,
|
||||
render_item: Box<dyn FnMut(usize, &mut WindowContext) -> AnyElement>,
|
||||
items: SumTree<ListItem>,
|
||||
logical_scroll_top: Option<ListOffset>,
|
||||
alignment: ListAlignment,
|
||||
overdraw: Pixels,
|
||||
#[allow(clippy::type_complexity)]
|
||||
scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ListAlignment {
|
||||
Top,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
pub struct ListScrollEvent {
|
||||
pub visible_range: Range<usize>,
|
||||
pub count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ListItem {
|
||||
Unrendered,
|
||||
Rendered { height: Pixels },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
struct ListItemSummary {
|
||||
count: usize,
|
||||
rendered_count: usize,
|
||||
unrendered_count: usize,
|
||||
height: Pixels,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct Count(usize);
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct RenderedCount(usize);
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct UnrenderedCount(usize);
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct Height(Pixels);
|
||||
|
||||
impl ListState {
|
||||
pub fn new<F>(
|
||||
element_count: usize,
|
||||
orientation: ListAlignment,
|
||||
overdraw: Pixels,
|
||||
render_item: F,
|
||||
) -> Self
|
||||
where
|
||||
F: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
|
||||
{
|
||||
let mut items = SumTree::new();
|
||||
items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||
Self(Rc::new(RefCell::new(StateInner {
|
||||
last_layout_width: None,
|
||||
render_item: Box::new(render_item),
|
||||
items,
|
||||
logical_scroll_top: None,
|
||||
alignment: orientation,
|
||||
overdraw,
|
||||
scroll_handler: None,
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn reset(&self, element_count: usize) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
state.logical_scroll_top = None;
|
||||
state.items = SumTree::new();
|
||||
state
|
||||
.items
|
||||
.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||
}
|
||||
|
||||
pub fn item_count(&self) -> usize {
|
||||
self.0.borrow().items.summary().count
|
||||
}
|
||||
|
||||
pub fn splice(&self, old_range: Range<usize>, count: usize) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
|
||||
if let Some(ListOffset {
|
||||
item_ix,
|
||||
offset_in_item,
|
||||
}) = state.logical_scroll_top.as_mut()
|
||||
{
|
||||
if old_range.contains(item_ix) {
|
||||
*item_ix = old_range.start;
|
||||
*offset_in_item = px(0.);
|
||||
} else if old_range.end <= *item_ix {
|
||||
*item_ix = *item_ix - (old_range.end - old_range.start) + count;
|
||||
}
|
||||
}
|
||||
|
||||
let mut old_heights = state.items.cursor::<Count>();
|
||||
let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
|
||||
old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
|
||||
|
||||
new_heights.extend((0..count).map(|_| ListItem::Unrendered), &());
|
||||
new_heights.append(old_heights.suffix(&()), &());
|
||||
drop(old_heights);
|
||||
state.items = new_heights;
|
||||
}
|
||||
|
||||
pub fn set_scroll_handler(
|
||||
&self,
|
||||
handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
|
||||
) {
|
||||
self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
|
||||
}
|
||||
|
||||
pub fn logical_scroll_top(&self) -> ListOffset {
|
||||
self.0.borrow().logical_scroll_top()
|
||||
}
|
||||
|
||||
pub fn scroll_to(&self, mut scroll_top: ListOffset) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
let item_count = state.items.summary().count;
|
||||
if scroll_top.item_ix >= item_count {
|
||||
scroll_top.item_ix = item_count;
|
||||
scroll_top.offset_in_item = px(0.);
|
||||
}
|
||||
state.logical_scroll_top = Some(scroll_top);
|
||||
}
|
||||
}
|
||||
|
||||
impl StateInner {
|
||||
fn visible_range(&self, height: Pixels, scroll_top: &ListOffset) -> Range<usize> {
|
||||
let mut cursor = self.items.cursor::<ListItemSummary>();
|
||||
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||
let start_y = cursor.start().height + scroll_top.offset_in_item;
|
||||
cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
|
||||
scroll_top.item_ix..cursor.start().count + 1
|
||||
}
|
||||
|
||||
fn scroll(
|
||||
&mut self,
|
||||
scroll_top: &ListOffset,
|
||||
height: Pixels,
|
||||
delta: Point<Pixels>,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let scroll_max = (self.items.summary().height - height).max(px(0.));
|
||||
let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
|
||||
.max(px(0.))
|
||||
.min(scroll_max);
|
||||
|
||||
if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max {
|
||||
self.logical_scroll_top = None;
|
||||
} else {
|
||||
let mut cursor = self.items.cursor::<ListItemSummary>();
|
||||
cursor.seek(&Height(new_scroll_top), Bias::Right, &());
|
||||
let item_ix = cursor.start().count;
|
||||
let offset_in_item = new_scroll_top - cursor.start().height;
|
||||
self.logical_scroll_top = Some(ListOffset {
|
||||
item_ix,
|
||||
offset_in_item,
|
||||
});
|
||||
}
|
||||
|
||||
if self.scroll_handler.is_some() {
|
||||
let visible_range = self.visible_range(height, scroll_top);
|
||||
self.scroll_handler.as_mut().unwrap()(
|
||||
&ListScrollEvent {
|
||||
visible_range,
|
||||
count: self.items.summary().count,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn logical_scroll_top(&self) -> ListOffset {
|
||||
self.logical_scroll_top
|
||||
.unwrap_or_else(|| match self.alignment {
|
||||
ListAlignment::Top => ListOffset {
|
||||
item_ix: 0,
|
||||
offset_in_item: px(0.),
|
||||
},
|
||||
ListAlignment::Bottom => ListOffset {
|
||||
item_ix: self.items.summary().count,
|
||||
offset_in_item: px(0.),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels {
|
||||
let mut cursor = self.items.cursor::<ListItemSummary>();
|
||||
cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
|
||||
cursor.start().height + logical_scroll_top.offset_in_item
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ListItem {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Unrendered => write!(f, "Unrendered"),
|
||||
Self::Rendered { height, .. } => {
|
||||
f.debug_struct("Rendered").field("height", height).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ListOffset {
|
||||
pub item_ix: usize,
|
||||
pub offset_in_item: Pixels,
|
||||
}
|
||||
|
||||
impl Element for List {
|
||||
type State = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_state: Option<Self::State>,
|
||||
cx: &mut crate::WindowContext,
|
||||
) -> (crate::LayoutId, Self::State) {
|
||||
let mut style = Style::default();
|
||||
style.refine(&self.style);
|
||||
let layout_id = cx.with_text_style(style.text_style().cloned(), |cx| {
|
||||
cx.request_layout(&style, None)
|
||||
});
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
self,
|
||||
bounds: crate::Bounds<crate::Pixels>,
|
||||
_state: &mut Self::State,
|
||||
cx: &mut crate::WindowContext,
|
||||
) {
|
||||
let state = &mut *self.state.0.borrow_mut();
|
||||
|
||||
// If the width of the list has changed, invalidate all cached item heights
|
||||
if state.last_layout_width != Some(bounds.size.width) {
|
||||
state.items = SumTree::from_iter(
|
||||
(0..state.items.summary().count).map(|_| ListItem::Unrendered),
|
||||
&(),
|
||||
)
|
||||
}
|
||||
|
||||
let old_items = state.items.clone();
|
||||
let mut measured_items = VecDeque::new();
|
||||
let mut item_elements = VecDeque::new();
|
||||
let mut rendered_height = px(0.);
|
||||
let mut scroll_top = state.logical_scroll_top();
|
||||
|
||||
let available_item_space = Size {
|
||||
width: AvailableSpace::Definite(bounds.size.width),
|
||||
height: AvailableSpace::MinContent,
|
||||
};
|
||||
|
||||
// Render items after the scroll top, including those in the trailing overdraw
|
||||
let mut cursor = old_items.cursor::<Count>();
|
||||
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||
for (ix, item) in cursor.by_ref().enumerate() {
|
||||
let visible_height = rendered_height - scroll_top.offset_in_item;
|
||||
if visible_height >= bounds.size.height + state.overdraw {
|
||||
break;
|
||||
}
|
||||
|
||||
// Use the previously cached height if available
|
||||
let mut height = if let ListItem::Rendered { height } = item {
|
||||
Some(*height)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// If we're within the visible area or the height wasn't cached, render and measure the item's element
|
||||
if visible_height < bounds.size.height || height.is_none() {
|
||||
let mut element = (state.render_item)(scroll_top.item_ix + ix, cx);
|
||||
let element_size = element.measure(available_item_space, cx);
|
||||
height = Some(element_size.height);
|
||||
if visible_height < bounds.size.height {
|
||||
item_elements.push_back(element);
|
||||
}
|
||||
}
|
||||
|
||||
let height = height.unwrap();
|
||||
rendered_height += height;
|
||||
measured_items.push_back(ListItem::Rendered { height });
|
||||
}
|
||||
|
||||
// Prepare to start walking upward from the item at the scroll top.
|
||||
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||
|
||||
// If the rendered items do not fill the visible region, then adjust
|
||||
// the scroll top upward.
|
||||
if rendered_height - scroll_top.offset_in_item < bounds.size.height {
|
||||
while rendered_height < bounds.size.height {
|
||||
cursor.prev(&());
|
||||
if cursor.item().is_some() {
|
||||
let mut element = (state.render_item)(cursor.start().0, cx);
|
||||
let element_size = element.measure(available_item_space, cx);
|
||||
|
||||
rendered_height += element_size.height;
|
||||
measured_items.push_front(ListItem::Rendered {
|
||||
height: element_size.height,
|
||||
});
|
||||
item_elements.push_front(element)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
scroll_top = ListOffset {
|
||||
item_ix: cursor.start().0,
|
||||
offset_in_item: rendered_height - bounds.size.height,
|
||||
};
|
||||
|
||||
match state.alignment {
|
||||
ListAlignment::Top => {
|
||||
scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.));
|
||||
state.logical_scroll_top = Some(scroll_top);
|
||||
}
|
||||
ListAlignment::Bottom => {
|
||||
scroll_top = ListOffset {
|
||||
item_ix: cursor.start().0,
|
||||
offset_in_item: rendered_height - bounds.size.height,
|
||||
};
|
||||
state.logical_scroll_top = None;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Measure items in the leading overdraw
|
||||
let mut leading_overdraw = scroll_top.offset_in_item;
|
||||
while leading_overdraw < state.overdraw {
|
||||
cursor.prev(&());
|
||||
if let Some(item) = cursor.item() {
|
||||
let height = if let ListItem::Rendered { height } = item {
|
||||
*height
|
||||
} else {
|
||||
let mut element = (state.render_item)(cursor.start().0, cx);
|
||||
element.measure(available_item_space, cx).height
|
||||
};
|
||||
|
||||
leading_overdraw += height;
|
||||
measured_items.push_front(ListItem::Rendered { height });
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len());
|
||||
let mut cursor = old_items.cursor::<Count>();
|
||||
let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &());
|
||||
new_items.extend(measured_items, &());
|
||||
cursor.seek(&Count(measured_range.end), Bias::Right, &());
|
||||
new_items.append(cursor.suffix(&()), &());
|
||||
|
||||
// Paint the visible items
|
||||
let mut item_origin = bounds.origin;
|
||||
item_origin.y -= scroll_top.offset_in_item;
|
||||
for mut item_element in item_elements {
|
||||
let item_height = item_element.measure(available_item_space, cx).height;
|
||||
item_element.draw(item_origin, available_item_space, cx);
|
||||
item_origin.y += item_height;
|
||||
}
|
||||
|
||||
state.items = new_items;
|
||||
state.last_layout_width = Some(bounds.size.width);
|
||||
|
||||
let list_state = self.state.clone();
|
||||
let height = bounds.size.height;
|
||||
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
list_state.0.borrow_mut().scroll(
|
||||
&scroll_top,
|
||||
height,
|
||||
event.delta.pixel_delta(px(20.)),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for List {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for List {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Item for ListItem {
|
||||
type Summary = ListItemSummary;
|
||||
|
||||
fn summary(&self) -> Self::Summary {
|
||||
match self {
|
||||
ListItem::Unrendered => ListItemSummary {
|
||||
count: 1,
|
||||
rendered_count: 0,
|
||||
unrendered_count: 1,
|
||||
height: px(0.),
|
||||
},
|
||||
ListItem::Rendered { height } => ListItemSummary {
|
||||
count: 1,
|
||||
rendered_count: 1,
|
||||
unrendered_count: 0,
|
||||
height: *height,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Summary for ListItemSummary {
|
||||
type Context = ();
|
||||
|
||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||
self.count += summary.count;
|
||||
self.rendered_count += summary.rendered_count;
|
||||
self.unrendered_count += summary.unrendered_count;
|
||||
self.height += summary.height;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Count {
|
||||
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||
self.0 += summary.count;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for RenderedCount {
|
||||
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||
self.0 += summary.rendered_count;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for UnrenderedCount {
|
||||
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||
self.0 += summary.unrendered_count;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height {
|
||||
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||
self.0 += summary.height;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Count {
|
||||
fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
|
||||
self.0.partial_cmp(&other.count).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
|
||||
fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
|
||||
self.0.partial_cmp(&other.height).unwrap()
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
mod canvas;
|
||||
mod div;
|
||||
mod img;
|
||||
mod list;
|
||||
mod overlay;
|
||||
mod svg;
|
||||
mod text;
|
||||
|
@ -9,6 +10,7 @@ mod uniform_list;
|
|||
pub use canvas::*;
|
||||
pub use div::*;
|
||||
pub use img::*;
|
||||
pub use list::*;
|
||||
pub use overlay::*;
|
||||
pub use svg::*;
|
||||
pub use text::*;
|
||||
|
|
|
@ -131,7 +131,7 @@ impl Element for UniformList {
|
|||
}
|
||||
});
|
||||
let height = match available_space.height {
|
||||
AvailableSpace::Definite(x) => desired_height.min(x),
|
||||
AvailableSpace::Definite(height) => desired_height.min(height),
|
||||
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
|
||||
desired_height
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#[macro_use]
|
||||
mod action;
|
||||
mod app;
|
||||
|
||||
mod assets;
|
||||
mod color;
|
||||
mod element;
|
||||
|
@ -15,6 +16,7 @@ mod keymap;
|
|||
mod platform;
|
||||
pub mod prelude;
|
||||
mod scene;
|
||||
mod shared_string;
|
||||
mod style;
|
||||
mod styled;
|
||||
mod subscription;
|
||||
|
@ -57,6 +59,7 @@ pub use scene::*;
|
|||
pub use serde;
|
||||
pub use serde_derive;
|
||||
pub use serde_json;
|
||||
pub use shared_string::*;
|
||||
pub use smallvec;
|
||||
pub use smol::Timer;
|
||||
pub use style::*;
|
||||
|
@ -71,10 +74,9 @@ pub use util::arc_cow::ArcCow;
|
|||
pub use view::*;
|
||||
pub use window::*;
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::{Borrow, BorrowMut},
|
||||
borrow::BorrowMut,
|
||||
};
|
||||
use taffy::TaffyLayoutEngine;
|
||||
|
||||
|
@ -209,42 +211,3 @@ impl<T> Flatten<T> for Result<T> {
|
|||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
|
||||
pub struct SharedString(ArcCow<'static, str>);
|
||||
|
||||
impl Default for SharedString {
|
||||
fn default() -> Self {
|
||||
Self(ArcCow::Owned("".into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for SharedString {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for SharedString {
|
||||
fn borrow(&self) -> &str {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SharedString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SharedString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
|
||||
fn from(value: T) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -193,6 +193,12 @@ impl Deref for MouseExitEvent {
|
|||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
|
||||
|
||||
impl ExternalPaths {
|
||||
pub fn paths(&self) -> &[PathBuf] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ExternalPaths {
|
||||
type Element = Div;
|
||||
|
||||
|
@ -296,7 +302,7 @@ mod test {
|
|||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
actions!(TestAction);
|
||||
actions!(test, [TestAction]);
|
||||
|
||||
impl Render for TestView {
|
||||
type Element = Stateful<Div>;
|
||||
|
|
|
@ -149,13 +149,19 @@ impl DispatchTree {
|
|||
}
|
||||
|
||||
pub fn available_actions(&self, target: DispatchNodeId) -> Vec<Box<dyn Action>> {
|
||||
let mut actions = Vec::new();
|
||||
let mut actions = Vec::<Box<dyn Action>>::new();
|
||||
for node_id in self.dispatch_path(target) {
|
||||
let node = &self.nodes[node_id.0];
|
||||
for DispatchActionListener { action_type, .. } in &node.action_listeners {
|
||||
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.as_any().type_id())
|
||||
{
|
||||
// Intentionally silence these errors without logging.
|
||||
// If an action cannot be built by default, it's not available.
|
||||
actions.extend(self.action_registry.build_action_type(action_type).ok());
|
||||
let action = self.action_registry.build_action_type(action_type).ok();
|
||||
if let Some(action) = action {
|
||||
actions.insert(ix, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
actions
|
||||
|
|
|
@ -293,11 +293,13 @@ mod tests {
|
|||
#[test]
|
||||
fn test_actions_definition() {
|
||||
{
|
||||
actions!(A, B, C, D, E, F, G);
|
||||
actions!(test, [A, B, C, D, E, F, G]);
|
||||
}
|
||||
|
||||
{
|
||||
actions!(
|
||||
test,
|
||||
[
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
|
@ -305,6 +307,7 @@ mod tests {
|
|||
E,
|
||||
F,
|
||||
G, // Don't wrap, test the trailing comma
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -325,7 +325,7 @@ impl MetalRenderer {
|
|||
.entry(tile.texture_id)
|
||||
.or_insert(Vec::new())
|
||||
.extend(path.vertices.iter().map(|vertex| PathVertex {
|
||||
xy_position: vertex.xy_position - path.bounds.origin
|
||||
xy_position: vertex.xy_position - clipped_bounds.origin
|
||||
+ tile.bounds.origin.map(Into::into),
|
||||
st_position: vertex.st_position,
|
||||
content_mask: ContentMask {
|
||||
|
@ -544,9 +544,10 @@ impl MetalRenderer {
|
|||
if let Some((path, tile)) = paths_and_tiles.peek() {
|
||||
if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) {
|
||||
prev_texture_id = Some(tile.texture_id);
|
||||
let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
|
||||
sprites.push(PathSprite {
|
||||
bounds: Bounds {
|
||||
origin: path.bounds.origin.map(|p| p.floor()),
|
||||
origin: origin.map(|p| p.floor()),
|
||||
size: tile.bounds.size.map(Into::into),
|
||||
},
|
||||
color: path.color,
|
||||
|
|
101
crates/gpui2/src/shared_string.rs
Normal file
101
crates/gpui2/src/shared_string.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
use derive_more::{Deref, DerefMut};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{borrow::Borrow, sync::Arc};
|
||||
use util::arc_cow::ArcCow;
|
||||
|
||||
#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
|
||||
pub struct SharedString(ArcCow<'static, str>);
|
||||
|
||||
impl Default for SharedString {
|
||||
fn default() -> Self {
|
||||
Self(ArcCow::Owned("".into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for SharedString {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for SharedString {
|
||||
fn borrow(&self) -> &str {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SharedString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SharedString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for SharedString {
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
self.as_ref() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<SharedString> for String {
|
||||
fn eq(&self, other: &SharedString) -> bool {
|
||||
self == other.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for SharedString {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.as_ref() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<&'a str> for SharedString {
|
||||
fn eq(&self, other: &&'a str) -> bool {
|
||||
self.as_ref() == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Arc<str>> for SharedString {
|
||||
fn into(self) -> Arc<str> {
|
||||
match self.0 {
|
||||
ArcCow::Borrowed(borrowed) => Arc::from(borrowed),
|
||||
ArcCow::Owned(owned) => owned.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
|
||||
fn from(value: T) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for SharedString {
|
||||
fn into(self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SharedString {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SharedString {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Ok(SharedString::from(s))
|
||||
}
|
||||
}
|
|
@ -245,6 +245,13 @@ pub trait Styled: Sized {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the flex direction of the element to `column-reverse`.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex-direction#column-reverse)
|
||||
fn flex_col_reverse(mut self) -> Self {
|
||||
self.style().flex_direction = Some(FlexDirection::ColumnReverse);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the flex direction of the element to `row`.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex-direction#row)
|
||||
fn flex_row(mut self) -> Self {
|
||||
|
@ -252,6 +259,13 @@ pub trait Styled: Sized {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the flex direction of the element to `row-reverse`.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex-direction#row-reverse)
|
||||
fn flex_row_reverse(mut self) -> Self {
|
||||
self.style().flex_direction = Some(FlexDirection::RowReverse);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the element to allow a flex item to grow and shrink as needed, ignoring its initial size.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex#flex-1)
|
||||
fn flex_1(mut self) -> Self {
|
||||
|
|
|
@ -4,12 +4,12 @@ use crate::{
|
|||
DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
|
||||
EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla,
|
||||
ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model,
|
||||
ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler,
|
||||
PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
|
||||
RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
|
||||
Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline,
|
||||
UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
|
||||
ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, Path,
|
||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
|
||||
PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
|
||||
RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
|
||||
Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
|
||||
WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::HashMap;
|
||||
|
@ -1269,10 +1269,9 @@ impl<'a> WindowContext<'a> {
|
|||
cursor_offset: position,
|
||||
});
|
||||
}
|
||||
InputEvent::MouseDown(MouseDownEvent {
|
||||
InputEvent::MouseMove(MouseMoveEvent {
|
||||
position,
|
||||
button: MouseButton::Left,
|
||||
click_count: 1,
|
||||
pressed_button: Some(MouseButton::Left),
|
||||
modifiers: Modifiers::default(),
|
||||
})
|
||||
}
|
||||
|
@ -1285,6 +1284,7 @@ impl<'a> WindowContext<'a> {
|
|||
})
|
||||
}
|
||||
FileDropEvent::Submit { position } => {
|
||||
self.activate(true);
|
||||
self.window.mouse_position = position;
|
||||
InputEvent::MouseUp(MouseUpEvent {
|
||||
button: MouseButton::Left,
|
||||
|
@ -2839,3 +2839,9 @@ impl From<(&'static str, usize)> for ElementId {
|
|||
ElementId::NamedInteger(name.into(), id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&'static str, u64)> for ElementId {
|
||||
fn from((name, id): (&'static str, u64)) -> Self {
|
||||
ElementId::NamedInteger(name.into(), id as usize)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
use gpui2::{actions, impl_actions};
|
||||
use gpui2_macros::register_action;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[test]
|
||||
fn test_derive() {
|
||||
fn test_action_macros() {
|
||||
use gpui2 as gpui;
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, gpui2_macros::Action)]
|
||||
actions!(test, [TestAction]);
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize)]
|
||||
struct AnotherTestAction;
|
||||
|
||||
#[gpui2_macros::register_action]
|
||||
impl_actions!(test, [AnotherTestAction]);
|
||||
|
||||
#[derive(PartialEq, Clone, gpui::serde_derive::Deserialize)]
|
||||
struct RegisterableAction {}
|
||||
|
||||
register_action!(RegisterableAction);
|
||||
|
||||
impl gpui::Action for RegisterableAction {
|
||||
fn boxed_clone(&self) -> Box<dyn gpui::Action> {
|
||||
todo!()
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
// Input:
|
||||
//
|
||||
// #[action]
|
||||
// struct Foo {
|
||||
// bar: String,
|
||||
// }
|
||||
|
||||
// Output:
|
||||
//
|
||||
// #[gpui::register_action]
|
||||
// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)]
|
||||
// struct Foo {
|
||||
// bar: String,
|
||||
// }
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput, Error};
|
||||
|
||||
use crate::register_action::register_action;
|
||||
|
||||
pub fn action(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let name = &input.ident;
|
||||
|
||||
if input.generics.lt_token.is_some() {
|
||||
return Error::new(name.span(), "Actions must be a concrete type")
|
||||
.into_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let is_unit_struct = match input.data {
|
||||
syn::Data::Struct(struct_data) => struct_data.fields.is_empty(),
|
||||
syn::Data::Enum(_) => false,
|
||||
syn::Data::Union(_) => false,
|
||||
};
|
||||
|
||||
let build_impl = if is_unit_struct {
|
||||
quote! {
|
||||
Ok(std::boxed::Box::new(Self {}))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
Ok(std::boxed::Box::new(gpui::serde_json::from_value::<Self>(value)?))
|
||||
}
|
||||
};
|
||||
|
||||
let register_action = register_action(&name);
|
||||
|
||||
let output = quote! {
|
||||
const _: fn() = || {
|
||||
fn assert_impl<T: ?Sized + for<'a> gpui::serde::Deserialize<'a> + ::std::cmp::PartialEq + ::std::clone::Clone>() {}
|
||||
assert_impl::<#name>();
|
||||
};
|
||||
|
||||
impl gpui::Action for #name {
|
||||
fn name(&self) -> &'static str
|
||||
{
|
||||
::std::any::type_name::<#name>()
|
||||
}
|
||||
|
||||
fn debug_name() -> &'static str
|
||||
where
|
||||
Self: ::std::marker::Sized
|
||||
{
|
||||
::std::any::type_name::<#name>()
|
||||
}
|
||||
|
||||
fn build(value: gpui::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>>
|
||||
where
|
||||
Self: ::std::marker::Sized {
|
||||
#build_impl
|
||||
}
|
||||
|
||||
fn partial_eq(&self, action: &dyn gpui::Action) -> bool {
|
||||
action
|
||||
.as_any()
|
||||
.downcast_ref::<Self>()
|
||||
.map_or(false, |a| self == a)
|
||||
}
|
||||
|
||||
fn boxed_clone(&self) -> std::boxed::Box<dyn gpui::Action> {
|
||||
::std::boxed::Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn ::std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#register_action
|
||||
};
|
||||
|
||||
TokenStream::from(output)
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
mod action;
|
||||
mod derive_into_element;
|
||||
mod register_action;
|
||||
mod style_helpers;
|
||||
|
@ -6,14 +5,9 @@ mod test;
|
|||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
#[proc_macro_derive(Action)]
|
||||
pub fn action(input: TokenStream) -> TokenStream {
|
||||
action::action(input)
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
register_action::register_action_macro(attr, item)
|
||||
#[proc_macro]
|
||||
pub fn register_action(ident: TokenStream) -> TokenStream {
|
||||
register_action::register_action_macro(ident)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(IntoElement)]
|
||||
|
|
|
@ -14,47 +14,13 @@
|
|||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Ident;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse_macro_input, DeriveInput, Error};
|
||||
use syn::parse_macro_input;
|
||||
|
||||
pub fn register_action_macro(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(item as DeriveInput);
|
||||
let registration = register_action(&input.ident);
|
||||
|
||||
let has_action_derive = input
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|attr| {
|
||||
(|| {
|
||||
let meta = attr.parse_meta().ok()?;
|
||||
meta.path().is_ident("derive").then(|| match meta {
|
||||
syn::Meta::Path(_) => None,
|
||||
syn::Meta::NameValue(_) => None,
|
||||
syn::Meta::List(list) => list
|
||||
.nested
|
||||
.iter()
|
||||
.find(|list| match list {
|
||||
syn::NestedMeta::Meta(meta) => meta.path().is_ident("Action"),
|
||||
syn::NestedMeta::Lit(_) => false,
|
||||
})
|
||||
.map(|_| true),
|
||||
})?
|
||||
})()
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.is_some();
|
||||
|
||||
if has_action_derive {
|
||||
return Error::new(
|
||||
input.ident.span(),
|
||||
"The Action derive macro has already registered this action",
|
||||
)
|
||||
.into_compile_error()
|
||||
.into();
|
||||
}
|
||||
pub fn register_action_macro(ident: TokenStream) -> TokenStream {
|
||||
let name = parse_macro_input!(ident as Ident);
|
||||
let registration = register_action(&name);
|
||||
|
||||
TokenStream::from(quote! {
|
||||
#input
|
||||
|
||||
#registration
|
||||
})
|
||||
}
|
||||
|
@ -78,7 +44,7 @@ pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream {
|
|||
#[doc(hidden)]
|
||||
fn #action_builder_fn_name() -> gpui::ActionData {
|
||||
gpui::ActionData {
|
||||
name: ::std::any::type_name::<#type_name>(),
|
||||
name: <#type_name as gpui::Action>::debug_name(),
|
||||
type_id: ::std::any::TypeId::of::<#type_name>(),
|
||||
build: <#type_name as gpui::Action>::build,
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use gpui::{actions, AsyncAppContext};
|
|||
use std::path::Path;
|
||||
use util::ResultExt;
|
||||
|
||||
actions!(Install);
|
||||
actions!(cli, [Install]);
|
||||
|
||||
pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
|
||||
let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??;
|
||||
|
|
|
@ -14,9 +14,9 @@ use project::Project;
|
|||
use std::sync::Arc;
|
||||
use ui::{prelude::*, HighlightedLabel, ListItem};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
actions!(Toggle);
|
||||
actions!(language_selector, [Toggle]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(LanguageSelector::register).detach();
|
||||
|
@ -81,6 +81,7 @@ impl FocusableView for LanguageSelector {
|
|||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for LanguageSelector {}
|
||||
impl ModalView for LanguageSelector {}
|
||||
|
||||
pub struct LanguageSelectorDelegate {
|
||||
language_selector: WeakView<LanguageSelector>,
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use futures::StreamExt;
|
||||
use gpui::{Action, KeyBinding};
|
||||
use gpui::{actions, KeyBinding};
|
||||
use live_kit_client2::{
|
||||
LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room,
|
||||
};
|
||||
use live_kit_server::token::{self, VideoGrant};
|
||||
use log::LevelFilter;
|
||||
use serde_derive::Deserialize;
|
||||
use simplelog::SimpleLogger;
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Action)]
|
||||
struct Quit;
|
||||
actions!(live_kit_client, [Quit]);
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||
|
|
|
@ -10,6 +10,8 @@ use gpui::actions;
|
|||
pub fn init() {}
|
||||
|
||||
actions!(
|
||||
menu,
|
||||
[
|
||||
Cancel,
|
||||
Confirm,
|
||||
SecondaryConfirm,
|
||||
|
@ -18,4 +20,5 @@ actions!(
|
|||
SelectFirst,
|
||||
SelectLast,
|
||||
ShowContextMenu
|
||||
]
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use editor::{
|
||||
display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Anchor, AnchorRangeExt,
|
||||
DisplayPoint, Editor, ToPoint,
|
||||
DisplayPoint, Editor, EditorMode, ToPoint,
|
||||
};
|
||||
use fuzzy::StringMatch;
|
||||
use gpui::{
|
||||
|
@ -20,19 +20,15 @@ use std::{
|
|||
use theme::{color_alpha, ActiveTheme, ThemeSettings};
|
||||
use ui::{prelude::*, ListItem};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
use workspace::ModalView;
|
||||
|
||||
actions!(Toggle);
|
||||
actions!(outline, [Toggle]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(OutlineView::register).detach();
|
||||
}
|
||||
|
||||
pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
if let Some(editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.downcast::<Editor>())
|
||||
{
|
||||
pub fn toggle(editor: View<Editor>, _: &Toggle, cx: &mut WindowContext) {
|
||||
let outline = editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
|
@ -40,9 +36,10 @@ pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Worksp
|
|||
.snapshot(cx)
|
||||
.outline(Some(&cx.theme().syntax()));
|
||||
|
||||
if let Some(outline) = outline {
|
||||
if let Some((workspace, outline)) = editor.read(cx).workspace().zip(outline) {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.toggle_modal(cx, |cx| OutlineView::new(outline, editor, cx));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,6 +54,7 @@ impl FocusableView for OutlineView {
|
|||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for OutlineView {}
|
||||
impl ModalView for OutlineView {}
|
||||
|
||||
impl Render for OutlineView {
|
||||
type Element = Div;
|
||||
|
@ -67,8 +65,15 @@ impl Render for OutlineView {
|
|||
}
|
||||
|
||||
impl OutlineView {
|
||||
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
||||
workspace.register_action(toggle);
|
||||
fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||
if editor.mode() == EditorMode::Full {
|
||||
let handle = cx.view().downgrade();
|
||||
editor.register_action(move |action, cx| {
|
||||
if let Some(editor) = handle.upgrade() {
|
||||
toggle(editor, action, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn new(
|
||||
|
@ -238,6 +243,7 @@ impl PickerDelegate for OutlineViewDelegate {
|
|||
s.select_ranges([position..position])
|
||||
});
|
||||
active_editor.highlight_rows(None);
|
||||
active_editor.focus(cx);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
|
|||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub(crate) trait LspCommand: 'static + Sized {
|
||||
pub trait LspCommand: 'static + Sized {
|
||||
type Response: 'static + Default + Send;
|
||||
type LspRequest: 'static + Send + lsp::request::Request;
|
||||
type ProtoRequest: 'static + Send + proto::RequestMessage;
|
||||
|
|
137
crates/project/src/lsp_ext_command.rs
Normal file
137
crates/project/src/lsp_ext_command.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use gpui::{AppContext, AsyncAppContext, ModelHandle};
|
||||
use language::{point_to_lsp, proto::deserialize_anchor, Buffer};
|
||||
use lsp::{LanguageServer, LanguageServerId};
|
||||
use rpc::proto::{self, PeerId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use text::{PointUtf16, ToPointUtf16};
|
||||
|
||||
use crate::{lsp_command::LspCommand, Project};
|
||||
|
||||
pub enum LspExpandMacro {}
|
||||
|
||||
impl lsp::request::Request for LspExpandMacro {
|
||||
type Params = ExpandMacroParams;
|
||||
type Result = Option<ExpandedMacro>;
|
||||
const METHOD: &'static str = "rust-analyzer/expandMacro";
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExpandMacroParams {
|
||||
pub text_document: lsp::TextDocumentIdentifier,
|
||||
pub position: lsp::Position,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExpandedMacro {
|
||||
pub name: String,
|
||||
pub expansion: String,
|
||||
}
|
||||
|
||||
impl ExpandedMacro {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.name.is_empty() && self.expansion.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExpandMacro {
|
||||
pub position: PointUtf16,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for ExpandMacro {
|
||||
type Response = ExpandedMacro;
|
||||
type LspRequest = LspExpandMacro;
|
||||
type ProtoRequest = proto::LspExtExpandMacro;
|
||||
|
||||
fn to_lsp(
|
||||
&self,
|
||||
path: &Path,
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> ExpandMacroParams {
|
||||
ExpandMacroParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
self,
|
||||
message: Option<ExpandedMacro>,
|
||||
_: ModelHandle<Project>,
|
||||
_: ModelHandle<Buffer>,
|
||||
_: LanguageServerId,
|
||||
_: AsyncAppContext,
|
||||
) -> anyhow::Result<ExpandedMacro> {
|
||||
Ok(message
|
||||
.map(|message| ExpandedMacro {
|
||||
name: message.name,
|
||||
expansion: message.expansion,
|
||||
})
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtExpandMacro {
|
||||
proto::LspExtExpandMacro {
|
||||
project_id,
|
||||
buffer_id: buffer.remote_id(),
|
||||
position: Some(language::proto::serialize_anchor(
|
||||
&buffer.anchor_before(self.position),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_proto(
|
||||
message: Self::ProtoRequest,
|
||||
_: ModelHandle<Project>,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> anyhow::Result<Self> {
|
||||
let position = message
|
||||
.position
|
||||
.and_then(deserialize_anchor)
|
||||
.context("invalid position")?;
|
||||
Ok(Self {
|
||||
position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer)),
|
||||
})
|
||||
}
|
||||
|
||||
fn response_to_proto(
|
||||
response: ExpandedMacro,
|
||||
_: &mut Project,
|
||||
_: PeerId,
|
||||
_: &clock::Global,
|
||||
_: &mut AppContext,
|
||||
) -> proto::LspExtExpandMacroResponse {
|
||||
proto::LspExtExpandMacroResponse {
|
||||
name: response.name,
|
||||
expansion: response.expansion,
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_proto(
|
||||
self,
|
||||
message: proto::LspExtExpandMacroResponse,
|
||||
_: ModelHandle<Project>,
|
||||
_: ModelHandle<Buffer>,
|
||||
_: AsyncAppContext,
|
||||
) -> anyhow::Result<ExpandedMacro> {
|
||||
Ok(ExpandedMacro {
|
||||
name: message.name,
|
||||
expansion: message.expansion,
|
||||
})
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> u64 {
|
||||
message.buffer_id
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
mod ignore;
|
||||
mod lsp_command;
|
||||
pub mod lsp_command;
|
||||
pub mod lsp_ext_command;
|
||||
mod prettier_support;
|
||||
pub mod project_settings;
|
||||
pub mod search;
|
||||
|
@ -174,7 +175,7 @@ struct DelayedDebounced {
|
|||
cancel_channel: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
enum LanguageServerToQuery {
|
||||
pub enum LanguageServerToQuery {
|
||||
Primary,
|
||||
Other(LanguageServerId),
|
||||
}
|
||||
|
@ -626,6 +627,7 @@ impl Project {
|
|||
client.add_model_request_handler(Self::handle_open_buffer_by_path);
|
||||
client.add_model_request_handler(Self::handle_save_buffer);
|
||||
client.add_model_message_handler(Self::handle_update_diff_base);
|
||||
client.add_model_request_handler(Self::handle_lsp_command::<lsp_ext_command::ExpandMacro>);
|
||||
}
|
||||
|
||||
pub fn local(
|
||||
|
@ -5863,7 +5865,7 @@ impl Project {
|
|||
.await;
|
||||
}
|
||||
|
||||
fn request_lsp<R: LspCommand>(
|
||||
pub fn request_lsp<R: LspCommand>(
|
||||
&self,
|
||||
buffer_handle: ModelHandle<Buffer>,
|
||||
server: LanguageServerToQuery,
|
||||
|
|
|
@ -33,7 +33,7 @@ pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
|
|||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub(crate) trait LspCommand: 'static + Sized + Send {
|
||||
pub trait LspCommand: 'static + Sized + Send {
|
||||
type Response: 'static + Default + Send;
|
||||
type LspRequest: 'static + Send + lsp::request::Request;
|
||||
type ProtoRequest: 'static + Send + proto::RequestMessage;
|
||||
|
|
137
crates/project2/src/lsp_ext_command.rs
Normal file
137
crates/project2/src/lsp_ext_command.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use gpui::{AppContext, AsyncAppContext, Model};
|
||||
use language::{point_to_lsp, proto::deserialize_anchor, Buffer};
|
||||
use lsp::{LanguageServer, LanguageServerId};
|
||||
use rpc::proto::{self, PeerId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use text::{PointUtf16, ToPointUtf16};
|
||||
|
||||
use crate::{lsp_command::LspCommand, Project};
|
||||
|
||||
pub enum LspExpandMacro {}
|
||||
|
||||
impl lsp::request::Request for LspExpandMacro {
|
||||
type Params = ExpandMacroParams;
|
||||
type Result = Option<ExpandedMacro>;
|
||||
const METHOD: &'static str = "rust-analyzer/expandMacro";
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExpandMacroParams {
|
||||
pub text_document: lsp::TextDocumentIdentifier,
|
||||
pub position: lsp::Position,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExpandedMacro {
|
||||
pub name: String,
|
||||
pub expansion: String,
|
||||
}
|
||||
|
||||
impl ExpandedMacro {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.name.is_empty() && self.expansion.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExpandMacro {
|
||||
pub position: PointUtf16,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for ExpandMacro {
|
||||
type Response = ExpandedMacro;
|
||||
type LspRequest = LspExpandMacro;
|
||||
type ProtoRequest = proto::LspExtExpandMacro;
|
||||
|
||||
fn to_lsp(
|
||||
&self,
|
||||
path: &Path,
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> ExpandMacroParams {
|
||||
ExpandMacroParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
self,
|
||||
message: Option<ExpandedMacro>,
|
||||
_: Model<Project>,
|
||||
_: Model<Buffer>,
|
||||
_: LanguageServerId,
|
||||
_: AsyncAppContext,
|
||||
) -> anyhow::Result<ExpandedMacro> {
|
||||
Ok(message
|
||||
.map(|message| ExpandedMacro {
|
||||
name: message.name,
|
||||
expansion: message.expansion,
|
||||
})
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtExpandMacro {
|
||||
proto::LspExtExpandMacro {
|
||||
project_id,
|
||||
buffer_id: buffer.remote_id(),
|
||||
position: Some(language::proto::serialize_anchor(
|
||||
&buffer.anchor_before(self.position),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_proto(
|
||||
message: Self::ProtoRequest,
|
||||
_: Model<Project>,
|
||||
buffer: Model<Buffer>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> anyhow::Result<Self> {
|
||||
let position = message
|
||||
.position
|
||||
.and_then(deserialize_anchor)
|
||||
.context("invalid position")?;
|
||||
Ok(Self {
|
||||
position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
|
||||
})
|
||||
}
|
||||
|
||||
fn response_to_proto(
|
||||
response: ExpandedMacro,
|
||||
_: &mut Project,
|
||||
_: PeerId,
|
||||
_: &clock::Global,
|
||||
_: &mut AppContext,
|
||||
) -> proto::LspExtExpandMacroResponse {
|
||||
proto::LspExtExpandMacroResponse {
|
||||
name: response.name,
|
||||
expansion: response.expansion,
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_proto(
|
||||
self,
|
||||
message: proto::LspExtExpandMacroResponse,
|
||||
_: Model<Project>,
|
||||
_: Model<Buffer>,
|
||||
_: AsyncAppContext,
|
||||
) -> anyhow::Result<ExpandedMacro> {
|
||||
Ok(ExpandedMacro {
|
||||
name: message.name,
|
||||
expansion: message.expansion,
|
||||
})
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> u64 {
|
||||
message.buffer_id
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
mod ignore;
|
||||
mod lsp_command;
|
||||
pub mod lsp_command;
|
||||
pub mod lsp_ext_command;
|
||||
mod prettier_support;
|
||||
pub mod project_settings;
|
||||
pub mod search;
|
||||
|
@ -172,7 +173,7 @@ struct DelayedDebounced {
|
|||
cancel_channel: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
enum LanguageServerToQuery {
|
||||
pub enum LanguageServerToQuery {
|
||||
Primary,
|
||||
Other(LanguageServerId),
|
||||
}
|
||||
|
@ -623,6 +624,7 @@ impl Project {
|
|||
client.add_model_request_handler(Self::handle_open_buffer_by_path);
|
||||
client.add_model_request_handler(Self::handle_save_buffer);
|
||||
client.add_model_message_handler(Self::handle_update_diff_base);
|
||||
client.add_model_request_handler(Self::handle_lsp_command::<lsp_ext_command::ExpandMacro>);
|
||||
}
|
||||
|
||||
pub fn local(
|
||||
|
@ -5933,7 +5935,7 @@ impl Project {
|
|||
.await;
|
||||
}
|
||||
|
||||
fn request_lsp<R: LspCommand>(
|
||||
pub fn request_lsp<R: LspCommand>(
|
||||
&self,
|
||||
buffer_handle: Model<Buffer>,
|
||||
server: LanguageServerToQuery,
|
||||
|
|
|
@ -103,6 +103,8 @@ pub struct EntryDetails {
|
|||
}
|
||||
|
||||
actions!(
|
||||
project_panel,
|
||||
[
|
||||
ExpandSelectedEntry,
|
||||
CollapseSelectedEntry,
|
||||
CollapseAllEntries,
|
||||
|
@ -120,6 +122,7 @@ actions!(
|
|||
Open,
|
||||
ToggleFocus,
|
||||
NewSearchInDirectory,
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init_settings(cx: &mut AppContext) {
|
||||
|
|
1
crates/recent_projects2/src/projects.rs
Normal file
1
crates/recent_projects2/src/projects.rs
Normal file
|
@ -0,0 +1 @@
|
|||
gpui::actions!(projects, [OpenRecent]);
|
|
@ -1,9 +1,10 @@
|
|||
mod highlighted_workspace_location;
|
||||
mod projects;
|
||||
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Task,
|
||||
View, ViewContext, WeakView,
|
||||
AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Task, View,
|
||||
ViewContext, WeakView,
|
||||
};
|
||||
use highlighted_workspace_location::HighlightedWorkspaceLocation;
|
||||
use ordered_float::OrderedFloat;
|
||||
|
@ -12,11 +13,11 @@ use std::sync::Arc;
|
|||
use ui::{prelude::*, ListItem};
|
||||
use util::paths::PathExt;
|
||||
use workspace::{
|
||||
notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation,
|
||||
WORKSPACE_DB,
|
||||
notifications::simple_message_notification::MessageNotification, ModalView, Workspace,
|
||||
WorkspaceLocation, WORKSPACE_DB,
|
||||
};
|
||||
|
||||
actions!(OpenRecent);
|
||||
pub use projects::OpenRecent;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(RecentProjects::register).detach();
|
||||
|
@ -26,6 +27,8 @@ pub struct RecentProjects {
|
|||
picker: View<Picker<RecentProjectsDelegate>>,
|
||||
}
|
||||
|
||||
impl ModalView for RecentProjects {}
|
||||
|
||||
impl RecentProjects {
|
||||
fn new(delegate: RecentProjectsDelegate, cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
use anyhow::bail;
|
||||
use futures::FutureExt;
|
||||
use gpui::{AnyElement, FontStyle, FontWeight, HighlightStyle, UnderlineStyle};
|
||||
use gpui::{
|
||||
AnyElement, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement,
|
||||
SharedString, StyledText, UnderlineStyle, WindowContext,
|
||||
};
|
||||
use language::{HighlightId, Language, LanguageRegistry};
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use theme::ActiveTheme;
|
||||
use util::RangeExt;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Highlight {
|
||||
Code,
|
||||
Id(HighlightId),
|
||||
Highlight(HighlightStyle),
|
||||
Mention,
|
||||
|
@ -28,24 +31,10 @@ impl From<HighlightId> for Highlight {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RichText {
|
||||
pub text: String,
|
||||
pub text: SharedString,
|
||||
pub highlights: Vec<(Range<usize>, Highlight)>,
|
||||
pub region_ranges: Vec<Range<usize>>,
|
||||
pub regions: Vec<RenderedRegion>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum BackgroundKind {
|
||||
Code,
|
||||
/// A mention background for non-self user.
|
||||
Mention,
|
||||
SelfMention,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RenderedRegion {
|
||||
pub background_kind: Option<BackgroundKind>,
|
||||
pub link_url: Option<String>,
|
||||
pub link_ranges: Vec<Range<usize>>,
|
||||
pub link_urls: Arc<[String]>,
|
||||
}
|
||||
|
||||
/// Allows one to specify extra links to the rendered markdown, which can be used
|
||||
|
@ -56,94 +45,71 @@ pub struct Mention {
|
|||
}
|
||||
|
||||
impl RichText {
|
||||
pub fn element(
|
||||
&self,
|
||||
// syntax: Arc<SyntaxTheme>,
|
||||
// style: RichTextStyle,
|
||||
// cx: &mut ViewContext<V>,
|
||||
) -> AnyElement {
|
||||
todo!();
|
||||
pub fn element(&self, id: ElementId, cx: &mut WindowContext) -> AnyElement {
|
||||
let theme = cx.theme();
|
||||
let code_background = theme.colors().surface_background;
|
||||
|
||||
// let mut region_id = 0;
|
||||
// let view_id = cx.view_id();
|
||||
InteractiveText::new(
|
||||
id,
|
||||
StyledText::new(self.text.clone()).with_highlights(
|
||||
&cx.text_style(),
|
||||
self.highlights.iter().map(|(range, highlight)| {
|
||||
(
|
||||
range.clone(),
|
||||
match highlight {
|
||||
Highlight::Code => HighlightStyle {
|
||||
background_color: Some(code_background),
|
||||
..Default::default()
|
||||
},
|
||||
Highlight::Id(id) => HighlightStyle {
|
||||
background_color: Some(code_background),
|
||||
..id.style(&theme.syntax()).unwrap_or_default()
|
||||
},
|
||||
Highlight::Highlight(highlight) => *highlight,
|
||||
Highlight::Mention => HighlightStyle {
|
||||
font_weight: Some(FontWeight::BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
Highlight::SelfMention => HighlightStyle {
|
||||
font_weight: Some(FontWeight::BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.on_click(self.link_ranges.clone(), {
|
||||
let link_urls = self.link_urls.clone();
|
||||
move |ix, cx| cx.open_url(&link_urls[ix])
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
// let regions = self.regions.clone();
|
||||
|
||||
// enum Markdown {}
|
||||
// Text::new(self.text.clone(), style.text.clone())
|
||||
// .with_highlights(
|
||||
// self.highlights
|
||||
// .iter()
|
||||
// .filter_map(|(range, highlight)| {
|
||||
// let style = match highlight {
|
||||
// Highlight::Id(id) => id.style(&syntax)?,
|
||||
// Highlight::Highlight(style) => style.clone(),
|
||||
// Highlight::Mention => style.mention_highlight,
|
||||
// Highlight::SelfMention => style.self_mention_highlight,
|
||||
// };
|
||||
// Some((range.clone(), style))
|
||||
// })
|
||||
// .collect::<Vec<_>>(),
|
||||
// )
|
||||
// .with_custom_runs(self.region_ranges.clone(), move |ix, bounds, cx| {
|
||||
// region_id += 1;
|
||||
// let region = regions[ix].clone();
|
||||
// if let Some(url) = region.link_url {
|
||||
// cx.scene().push_cursor_region(CursorRegion {
|
||||
// bounds,
|
||||
// style: CursorStyle::PointingHand,
|
||||
// });
|
||||
// cx.scene().push_mouse_region(
|
||||
// MouseRegion::new::<Markdown>(view_id, region_id, bounds)
|
||||
// .on_click::<V, _>(MouseButton::Left, move |_, _, cx| {
|
||||
// cx.platform().open_url(&url)
|
||||
// }),
|
||||
// pub fn add_mention(
|
||||
// &mut self,
|
||||
// range: Range<usize>,
|
||||
// is_current_user: bool,
|
||||
// mention_style: HighlightStyle,
|
||||
// ) -> anyhow::Result<()> {
|
||||
// if range.end > self.text.len() {
|
||||
// bail!(
|
||||
// "Mention in range {range:?} is outside of bounds for a message of length {}",
|
||||
// self.text.len()
|
||||
// );
|
||||
// }
|
||||
// if let Some(region_kind) = ®ion.background_kind {
|
||||
// let background = match region_kind {
|
||||
// BackgroundKind::Code => style.code_background,
|
||||
// BackgroundKind::Mention => style.mention_background,
|
||||
// BackgroundKind::SelfMention => style.self_mention_background,
|
||||
// };
|
||||
// if background.is_some() {
|
||||
// cx.scene().push_quad(gpui::Quad {
|
||||
// bounds,
|
||||
// background,
|
||||
// border: Default::default(),
|
||||
// corner_radii: (2.0).into(),
|
||||
|
||||
// if is_current_user {
|
||||
// self.region_ranges.push(range.clone());
|
||||
// self.regions.push(RenderedRegion {
|
||||
// background_kind: Some(BackgroundKind::Mention),
|
||||
// link_url: None,
|
||||
// });
|
||||
// }
|
||||
// self.highlights
|
||||
// .push((range, Highlight::Highlight(mention_style)));
|
||||
// Ok(())
|
||||
// }
|
||||
// })
|
||||
// .with_soft_wrap(true)
|
||||
// .into_any()
|
||||
}
|
||||
|
||||
pub fn add_mention(
|
||||
&mut self,
|
||||
range: Range<usize>,
|
||||
is_current_user: bool,
|
||||
mention_style: HighlightStyle,
|
||||
) -> anyhow::Result<()> {
|
||||
if range.end > self.text.len() {
|
||||
bail!(
|
||||
"Mention in range {range:?} is outside of bounds for a message of length {}",
|
||||
self.text.len()
|
||||
);
|
||||
}
|
||||
|
||||
if is_current_user {
|
||||
self.region_ranges.push(range.clone());
|
||||
self.regions.push(RenderedRegion {
|
||||
background_kind: Some(BackgroundKind::Mention),
|
||||
link_url: None,
|
||||
});
|
||||
}
|
||||
self.highlights
|
||||
.push((range, Highlight::Highlight(mention_style)));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_markdown_mut(
|
||||
|
@ -151,7 +117,10 @@ pub fn render_markdown_mut(
|
|||
mut mentions: &[Mention],
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
language: Option<&Arc<Language>>,
|
||||
data: &mut RichText,
|
||||
text: &mut String,
|
||||
highlights: &mut Vec<(Range<usize>, Highlight)>,
|
||||
link_ranges: &mut Vec<Range<usize>>,
|
||||
link_urls: &mut Vec<String>,
|
||||
) {
|
||||
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
||||
|
||||
|
@ -163,18 +132,18 @@ pub fn render_markdown_mut(
|
|||
|
||||
let options = Options::all();
|
||||
for (event, source_range) in Parser::new_ext(&block, options).into_offset_iter() {
|
||||
let prev_len = data.text.len();
|
||||
let prev_len = text.len();
|
||||
match event {
|
||||
Event::Text(t) => {
|
||||
if let Some(language) = ¤t_language {
|
||||
render_code(&mut data.text, &mut data.highlights, t.as_ref(), language);
|
||||
render_code(text, highlights, t.as_ref(), language);
|
||||
} else {
|
||||
if let Some(mention) = mentions.first() {
|
||||
if source_range.contains_inclusive(&mention.range) {
|
||||
mentions = &mentions[1..];
|
||||
let range = (prev_len + mention.range.start - source_range.start)
|
||||
..(prev_len + mention.range.end - source_range.start);
|
||||
data.highlights.push((
|
||||
highlights.push((
|
||||
range.clone(),
|
||||
if mention.is_self_mention {
|
||||
Highlight::SelfMention
|
||||
|
@ -182,19 +151,10 @@ pub fn render_markdown_mut(
|
|||
Highlight::Mention
|
||||
},
|
||||
));
|
||||
data.region_ranges.push(range);
|
||||
data.regions.push(RenderedRegion {
|
||||
background_kind: Some(if mention.is_self_mention {
|
||||
BackgroundKind::SelfMention
|
||||
} else {
|
||||
BackgroundKind::Mention
|
||||
}),
|
||||
link_url: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
data.text.push_str(t.as_ref());
|
||||
text.push_str(t.as_ref());
|
||||
let mut style = HighlightStyle::default();
|
||||
if bold_depth > 0 {
|
||||
style.font_weight = Some(FontWeight::BOLD);
|
||||
|
@ -203,11 +163,8 @@ pub fn render_markdown_mut(
|
|||
style.font_style = Some(FontStyle::Italic);
|
||||
}
|
||||
if let Some(link_url) = link_url.clone() {
|
||||
data.region_ranges.push(prev_len..data.text.len());
|
||||
data.regions.push(RenderedRegion {
|
||||
link_url: Some(link_url),
|
||||
background_kind: None,
|
||||
});
|
||||
link_ranges.push(prev_len..text.len());
|
||||
link_urls.push(link_url);
|
||||
style.underline = Some(UnderlineStyle {
|
||||
thickness: 1.0.into(),
|
||||
..Default::default()
|
||||
|
@ -216,27 +173,25 @@ pub fn render_markdown_mut(
|
|||
|
||||
if style != HighlightStyle::default() {
|
||||
let mut new_highlight = true;
|
||||
if let Some((last_range, last_style)) = data.highlights.last_mut() {
|
||||
if let Some((last_range, last_style)) = highlights.last_mut() {
|
||||
if last_range.end == prev_len
|
||||
&& last_style == &Highlight::Highlight(style)
|
||||
{
|
||||
last_range.end = data.text.len();
|
||||
last_range.end = text.len();
|
||||
new_highlight = false;
|
||||
}
|
||||
}
|
||||
if new_highlight {
|
||||
data.highlights
|
||||
.push((prev_len..data.text.len(), Highlight::Highlight(style)));
|
||||
highlights.push((prev_len..text.len(), Highlight::Highlight(style)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Code(t) => {
|
||||
data.text.push_str(t.as_ref());
|
||||
data.region_ranges.push(prev_len..data.text.len());
|
||||
text.push_str(t.as_ref());
|
||||
if link_url.is_some() {
|
||||
data.highlights.push((
|
||||
prev_len..data.text.len(),
|
||||
highlights.push((
|
||||
prev_len..text.len(),
|
||||
Highlight::Highlight(HighlightStyle {
|
||||
underline: Some(UnderlineStyle {
|
||||
thickness: 1.0.into(),
|
||||
|
@ -246,19 +201,19 @@ pub fn render_markdown_mut(
|
|||
}),
|
||||
));
|
||||
}
|
||||
data.regions.push(RenderedRegion {
|
||||
background_kind: Some(BackgroundKind::Code),
|
||||
link_url: link_url.clone(),
|
||||
});
|
||||
if let Some(link_url) = link_url.clone() {
|
||||
link_ranges.push(prev_len..text.len());
|
||||
link_urls.push(link_url);
|
||||
}
|
||||
}
|
||||
Event::Start(tag) => match tag {
|
||||
Tag::Paragraph => new_paragraph(&mut data.text, &mut list_stack),
|
||||
Tag::Paragraph => new_paragraph(text, &mut list_stack),
|
||||
Tag::Heading(_, _, _) => {
|
||||
new_paragraph(&mut data.text, &mut list_stack);
|
||||
new_paragraph(text, &mut list_stack);
|
||||
bold_depth += 1;
|
||||
}
|
||||
Tag::CodeBlock(kind) => {
|
||||
new_paragraph(&mut data.text, &mut list_stack);
|
||||
new_paragraph(text, &mut list_stack);
|
||||
current_language = if let CodeBlockKind::Fenced(language) = kind {
|
||||
language_registry
|
||||
.language_for_name(language.as_ref())
|
||||
|
@ -278,18 +233,18 @@ pub fn render_markdown_mut(
|
|||
let len = list_stack.len();
|
||||
if let Some((list_number, has_content)) = list_stack.last_mut() {
|
||||
*has_content = false;
|
||||
if !data.text.is_empty() && !data.text.ends_with('\n') {
|
||||
data.text.push('\n');
|
||||
if !text.is_empty() && !text.ends_with('\n') {
|
||||
text.push('\n');
|
||||
}
|
||||
for _ in 0..len - 1 {
|
||||
data.text.push_str(" ");
|
||||
text.push_str(" ");
|
||||
}
|
||||
if let Some(number) = list_number {
|
||||
data.text.push_str(&format!("{}. ", number));
|
||||
text.push_str(&format!("{}. ", number));
|
||||
*number += 1;
|
||||
*has_content = false;
|
||||
} else {
|
||||
data.text.push_str("- ");
|
||||
text.push_str("- ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -304,8 +259,8 @@ pub fn render_markdown_mut(
|
|||
Tag::List(_) => drop(list_stack.pop()),
|
||||
_ => {}
|
||||
},
|
||||
Event::HardBreak => data.text.push('\n'),
|
||||
Event::SoftBreak => data.text.push(' '),
|
||||
Event::HardBreak => text.push('\n'),
|
||||
Event::SoftBreak => text.push(' '),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -317,18 +272,35 @@ pub fn render_markdown(
|
|||
language_registry: &Arc<LanguageRegistry>,
|
||||
language: Option<&Arc<Language>>,
|
||||
) -> RichText {
|
||||
let mut data = RichText {
|
||||
text: Default::default(),
|
||||
highlights: Default::default(),
|
||||
region_ranges: Default::default(),
|
||||
regions: Default::default(),
|
||||
};
|
||||
// let mut data = RichText {
|
||||
// text: Default::default(),
|
||||
// highlights: Default::default(),
|
||||
// region_ranges: Default::default(),
|
||||
// regions: Default::default(),
|
||||
// };
|
||||
|
||||
render_markdown_mut(&block, mentions, language_registry, language, &mut data);
|
||||
let mut text = String::new();
|
||||
let mut highlights = Vec::new();
|
||||
let mut link_ranges = Vec::new();
|
||||
let mut link_urls = Vec::new();
|
||||
render_markdown_mut(
|
||||
&block,
|
||||
mentions,
|
||||
language_registry,
|
||||
language,
|
||||
&mut text,
|
||||
&mut highlights,
|
||||
&mut link_ranges,
|
||||
&mut link_urls,
|
||||
);
|
||||
text.truncate(text.trim_end().len());
|
||||
|
||||
data.text = data.text.trim().to_string();
|
||||
|
||||
data
|
||||
RichText {
|
||||
text: SharedString::from(text),
|
||||
link_urls: link_urls.into(),
|
||||
link_ranges,
|
||||
highlights,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_code(
|
||||
|
@ -339,11 +311,19 @@ pub fn render_code(
|
|||
) {
|
||||
let prev_len = text.len();
|
||||
text.push_str(content);
|
||||
let mut offset = 0;
|
||||
for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) {
|
||||
if range.start > offset {
|
||||
highlights.push((prev_len + offset..prev_len + range.start, Highlight::Code));
|
||||
}
|
||||
highlights.push((
|
||||
prev_len + range.start..prev_len + range.end,
|
||||
Highlight::Id(highlight_id),
|
||||
));
|
||||
offset = range.end;
|
||||
}
|
||||
if offset < content.len() {
|
||||
highlights.push((prev_len + offset..prev_len + content.len(), Highlight::Code));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -178,7 +178,9 @@ message Envelope {
|
|||
GetNotifications get_notifications = 150;
|
||||
GetNotificationsResponse get_notifications_response = 151;
|
||||
DeleteNotification delete_notification = 152;
|
||||
MarkNotificationRead mark_notification_read = 153; // Current max
|
||||
MarkNotificationRead mark_notification_read = 153;
|
||||
LspExtExpandMacro lsp_ext_expand_macro = 154;
|
||||
LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; // Current max
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1619,3 +1621,14 @@ message Notification {
|
|||
bool is_read = 6;
|
||||
optional bool response = 7;
|
||||
}
|
||||
|
||||
message LspExtExpandMacro {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
Anchor position = 3;
|
||||
}
|
||||
|
||||
message LspExtExpandMacroResponse {
|
||||
string name = 1;
|
||||
string expansion = 2;
|
||||
}
|
||||
|
|
|
@ -280,6 +280,8 @@ messages!(
|
|||
(UpdateWorktree, Foreground),
|
||||
(UpdateWorktreeSettings, Foreground),
|
||||
(UsersResponse, Foreground),
|
||||
(LspExtExpandMacro, Background),
|
||||
(LspExtExpandMacroResponse, Background),
|
||||
);
|
||||
|
||||
request_messages!(
|
||||
|
@ -363,6 +365,7 @@ request_messages!(
|
|||
(UpdateParticipantLocation, Ack),
|
||||
(UpdateProject, Ack),
|
||||
(UpdateWorktree, Ack),
|
||||
(LspExtExpandMacro, LspExtExpandMacroResponse),
|
||||
);
|
||||
|
||||
entity_messages!(
|
||||
|
@ -415,6 +418,7 @@ entity_messages!(
|
|||
UpdateProjectCollaborator,
|
||||
UpdateWorktree,
|
||||
UpdateWorktreeSettings,
|
||||
LspExtExpandMacro,
|
||||
);
|
||||
|
||||
entity_messages!(
|
||||
|
|
|
@ -178,7 +178,9 @@ message Envelope {
|
|||
GetNotifications get_notifications = 150;
|
||||
GetNotificationsResponse get_notifications_response = 151;
|
||||
DeleteNotification delete_notification = 152;
|
||||
MarkNotificationRead mark_notification_read = 153; // Current max
|
||||
MarkNotificationRead mark_notification_read = 153;
|
||||
LspExtExpandMacro lsp_ext_expand_macro = 154;
|
||||
LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; // Current max
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1619,3 +1621,14 @@ message Notification {
|
|||
bool is_read = 6;
|
||||
optional bool response = 7;
|
||||
}
|
||||
|
||||
message LspExtExpandMacro {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
Anchor position = 3;
|
||||
}
|
||||
|
||||
message LspExtExpandMacroResponse {
|
||||
string name = 1;
|
||||
string expansion = 2;
|
||||
}
|
||||
|
|
|
@ -280,6 +280,8 @@ messages!(
|
|||
(UpdateWorktree, Foreground),
|
||||
(UpdateWorktreeSettings, Foreground),
|
||||
(UsersResponse, Foreground),
|
||||
(LspExtExpandMacro, Background),
|
||||
(LspExtExpandMacroResponse, Background),
|
||||
);
|
||||
|
||||
request_messages!(
|
||||
|
@ -363,6 +365,7 @@ request_messages!(
|
|||
(UpdateParticipantLocation, Ack),
|
||||
(UpdateProject, Ack),
|
||||
(UpdateWorktree, Ack),
|
||||
(LspExtExpandMacro, LspExtExpandMacroResponse),
|
||||
);
|
||||
|
||||
entity_messages!(
|
||||
|
@ -415,6 +418,7 @@ entity_messages!(
|
|||
UpdateProjectCollaborator,
|
||||
UpdateWorktree,
|
||||
UpdateWorktreeSettings,
|
||||
LspExtExpandMacro,
|
||||
);
|
||||
|
||||
entity_messages!(
|
||||
|
|
|
@ -1075,8 +1075,7 @@ impl ProjectSearchView {
|
|||
});
|
||||
}
|
||||
|
||||
// Re-activate the most recently activated search or the most recent if it has been closed.
|
||||
// If no search exists in the workspace, create a new one.
|
||||
// Add another search tab to the workspace.
|
||||
fn deploy(
|
||||
workspace: &mut Workspace,
|
||||
_: &workspace::NewSearch,
|
||||
|
@ -1087,19 +1086,6 @@ impl ProjectSearchView {
|
|||
state.0.retain(|project, _| project.is_upgradable(cx))
|
||||
});
|
||||
|
||||
let active_search = cx
|
||||
.global::<ActiveSearches>()
|
||||
.0
|
||||
.get(&workspace.project().downgrade());
|
||||
|
||||
let existing = active_search
|
||||
.and_then(|active_search| {
|
||||
workspace
|
||||
.items_of_type::<ProjectSearchView>(cx)
|
||||
.find(|search| search == active_search)
|
||||
})
|
||||
.or_else(|| workspace.item_of_type::<ProjectSearchView>(cx));
|
||||
|
||||
let query = workspace.active_item(cx).and_then(|item| {
|
||||
let editor = item.act_as::<Editor>(cx)?;
|
||||
let query = editor.query_suggestion(cx);
|
||||
|
@ -1110,10 +1096,6 @@ impl ProjectSearchView {
|
|||
}
|
||||
});
|
||||
|
||||
let search = if let Some(existing) = existing {
|
||||
workspace.activate_item(&existing, cx);
|
||||
existing
|
||||
} else {
|
||||
let settings = cx
|
||||
.global::<ActiveSettings>()
|
||||
.0
|
||||
|
@ -1126,11 +1108,9 @@ impl ProjectSearchView {
|
|||
};
|
||||
|
||||
let model = cx.add_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
|
||||
let view = cx.add_view(|cx| ProjectSearchView::new(model, cx, settings));
|
||||
let search = cx.add_view(|cx| ProjectSearchView::new(model, cx, settings));
|
||||
|
||||
workspace.add_item(Box::new(view.clone()), cx);
|
||||
view
|
||||
};
|
||||
workspace.add_item(Box::new(search.clone()), cx);
|
||||
|
||||
search.update(cx, |search, cx| {
|
||||
if let Some(query) = query {
|
||||
|
@ -2306,29 +2286,86 @@ pub mod tests {
|
|||
workspace.update(cx, |workspace, cx| {
|
||||
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
let Some(search_view_2) = cx.read(|cx| {
|
||||
workspace
|
||||
.read(cx)
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.active_item()
|
||||
.and_then(|item| item.downcast::<ProjectSearchView>())
|
||||
}) else {
|
||||
panic!("Search view expected to appear after new search event trigger")
|
||||
};
|
||||
let search_view_id_2 = search_view_2.id();
|
||||
assert_ne!(
|
||||
search_view_2, search_view,
|
||||
"New search view should be open after `workspace::NewSearch` event"
|
||||
);
|
||||
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
assert_eq!(search_view.query_editor.read(cx).text(cx), "two", "Query should be updated to first search result after search view 2nd open in a row");
|
||||
assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO", "First search view should not have an updated query");
|
||||
assert_eq!(
|
||||
search_view
|
||||
.results_editor
|
||||
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
|
||||
"Results should be unchanged after search view 2nd open in a row"
|
||||
"Results of the first search view should not update too"
|
||||
);
|
||||
assert!(
|
||||
search_view.query_editor.is_focused(cx),
|
||||
"Focus should be moved into query editor again after search view 2nd open in a row"
|
||||
!search_view.query_editor.is_focused(cx),
|
||||
"Focus should be moved away from the first search view"
|
||||
);
|
||||
});
|
||||
|
||||
search_view_2.update(cx, |search_view_2, cx| {
|
||||
assert_eq!(
|
||||
search_view_2.query_editor.read(cx).text(cx),
|
||||
"two",
|
||||
"New search view should get the query from the text cursor was at during the event spawn (first search view's first result)"
|
||||
);
|
||||
assert_eq!(
|
||||
search_view_2
|
||||
.results_editor
|
||||
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"",
|
||||
"No search results should be in the 2nd view yet, as we did not spawn a search for it"
|
||||
);
|
||||
assert!(
|
||||
search_view_2.query_editor.is_focused(cx),
|
||||
"Focus should be moved into query editor fo the new window"
|
||||
);
|
||||
});
|
||||
|
||||
search_view_2.update(cx, |search_view_2, cx| {
|
||||
search_view_2
|
||||
.query_editor
|
||||
.update(cx, |query_editor, cx| query_editor.set_text("FOUR", cx));
|
||||
search_view_2.search(cx);
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
search_view_2.update(cx, |search_view_2, cx| {
|
||||
assert_eq!(
|
||||
search_view_2
|
||||
.results_editor
|
||||
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"\n\nconst FOUR: usize = one::ONE + three::THREE;",
|
||||
"New search view with the updated query should have new search results"
|
||||
);
|
||||
assert!(
|
||||
search_view_2.results_editor.is_focused(cx),
|
||||
"Search view with mismatching query should be focused after search results are available",
|
||||
);
|
||||
});
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
|
||||
window.dispatch_action(search_view_id_2, &ToggleFocus, &mut cx);
|
||||
})
|
||||
.detach();
|
||||
deterministic.run_until_parked();
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
search_view_2.update(cx, |search_view_2, cx| {
|
||||
assert!(
|
||||
search_view.results_editor.is_focused(cx),
|
||||
search_view_2.results_editor.is_focused(cx),
|
||||
"Search view with matching query should switch focus to the results editor after the toggle focus event",
|
||||
);
|
||||
});
|
||||
|
|
|
@ -10,15 +10,15 @@ use collections::HashMap;
|
|||
use editor::{Editor, EditorMode};
|
||||
use futures::channel::oneshot;
|
||||
use gpui::{
|
||||
actions, div, red, Action, AppContext, Div, EventEmitter, FocusableView,
|
||||
InteractiveElement as _, IntoElement, ParentElement as _, Render, Styled, Subscription, Task,
|
||||
View, ViewContext, VisualContext as _, WeakView, WindowContext,
|
||||
actions, div, impl_actions, red, Action, AppContext, Div, EventEmitter, FocusableView,
|
||||
InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, Styled,
|
||||
Subscription, Task, View, ViewContext, VisualContext as _, WeakView, WindowContext,
|
||||
};
|
||||
use project::search::SearchQuery;
|
||||
use serde::Deserialize;
|
||||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use ui::{h_stack, Clickable, Icon, IconButton, IconElement};
|
||||
use ui::{h_stack, ButtonCommon, Clickable, Icon, IconButton, IconElement, Tooltip};
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
item::ItemHandle,
|
||||
|
@ -26,12 +26,14 @@ use workspace::{
|
|||
ToolbarItemLocation, ToolbarItemView,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
||||
#[derive(PartialEq, Clone, Deserialize)]
|
||||
pub struct Deploy {
|
||||
pub focus: bool,
|
||||
}
|
||||
|
||||
actions!(Dismiss, FocusEditor);
|
||||
impl_actions!(buffer_search, [Deploy]);
|
||||
|
||||
actions!(buffer_search, [Dismiss, FocusEditor]);
|
||||
|
||||
pub enum Event {
|
||||
UpdateLocation,
|
||||
|
@ -131,13 +133,7 @@ impl Render for BufferSearchBar {
|
|||
let search_button_for_mode = |mode| {
|
||||
let is_active = self.current_mode == mode;
|
||||
|
||||
render_search_mode_button(
|
||||
mode,
|
||||
is_active,
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.activate_search_mode(mode, cx);
|
||||
}),
|
||||
)
|
||||
render_search_mode_button(mode, is_active)
|
||||
};
|
||||
let search_option_button = |option| {
|
||||
let is_active = self.search_options.contains(option);
|
||||
|
@ -163,23 +159,35 @@ impl Render for BufferSearchBar {
|
|||
});
|
||||
let should_show_replace_input = self.replace_enabled && supported_options.replacement;
|
||||
let replace_all = should_show_replace_input
|
||||
.then(|| super::render_replace_button(ReplaceAll, ui::Icon::ReplaceAll));
|
||||
let replace_next = should_show_replace_input
|
||||
.then(|| super::render_replace_button(ReplaceNext, ui::Icon::ReplaceNext));
|
||||
.then(|| super::render_replace_button(ReplaceAll, ui::Icon::ReplaceAll, "Replace all"));
|
||||
let replace_next = should_show_replace_input.then(|| {
|
||||
super::render_replace_button(ReplaceNext, ui::Icon::ReplaceNext, "Replace next")
|
||||
});
|
||||
let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx);
|
||||
|
||||
let mut key_context = KeyContext::default();
|
||||
key_context.add("BufferSearchBar");
|
||||
if in_replace {
|
||||
key_context.add("in_replace");
|
||||
}
|
||||
|
||||
h_stack()
|
||||
.key_context("BufferSearchBar")
|
||||
.key_context(key_context)
|
||||
.on_action(cx.listener(Self::previous_history_query))
|
||||
.on_action(cx.listener(Self::next_history_query))
|
||||
.on_action(cx.listener(Self::dismiss))
|
||||
.on_action(cx.listener(Self::select_next_match))
|
||||
.on_action(cx.listener(Self::select_prev_match))
|
||||
.on_action(cx.listener(|this, _: &ActivateRegexMode, cx| {
|
||||
this.activate_search_mode(SearchMode::Regex, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &ActivateTextMode, cx| {
|
||||
this.activate_search_mode(SearchMode::Text, cx);
|
||||
}))
|
||||
.when(self.supported_options().replacement, |this| {
|
||||
this.on_action(cx.listener(Self::toggle_replace))
|
||||
.when(in_replace, |this| {
|
||||
this.key_context("in_replace")
|
||||
.on_action(cx.listener(Self::replace_next))
|
||||
this.on_action(cx.listener(Self::replace_next))
|
||||
.on_action(cx.listener(Self::replace_all))
|
||||
})
|
||||
})
|
||||
|
@ -238,21 +246,19 @@ impl Render for BufferSearchBar {
|
|||
h_stack()
|
||||
.gap_0p5()
|
||||
.flex_none()
|
||||
.child(self.render_action_button(cx))
|
||||
.child(self.render_action_button())
|
||||
.children(match_count)
|
||||
.child(render_nav_button(
|
||||
ui::Icon::ChevronLeft,
|
||||
self.active_match_index.is_some(),
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.select_prev_match(&Default::default(), cx);
|
||||
}),
|
||||
"Select previous match",
|
||||
&SelectPrevMatch,
|
||||
))
|
||||
.child(render_nav_button(
|
||||
ui::Icon::ChevronRight,
|
||||
self.active_match_index.is_some(),
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.select_next_match(&Default::default(), cx);
|
||||
}),
|
||||
"Select next match",
|
||||
&SelectNextMatch,
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
@ -597,14 +603,10 @@ impl BufferSearchBar {
|
|||
self.update_matches(cx)
|
||||
}
|
||||
|
||||
fn render_action_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
// let tooltip_style = theme.tooltip.clone();
|
||||
|
||||
// let style = theme.search.action_button.clone();
|
||||
|
||||
IconButton::new("select-all", ui::Icon::SelectAll).on_click(cx.listener(|this, _, cx| {
|
||||
this.select_all_matches(&SelectAllMatches, cx);
|
||||
}))
|
||||
fn render_action_button(&self) -> impl IntoElement {
|
||||
IconButton::new("select-all", ui::Icon::SelectAll)
|
||||
.on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))
|
||||
.tooltip(|cx| Tooltip::for_action("Select all matches", &SelectAllMatches, cx))
|
||||
}
|
||||
|
||||
pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
use gpui::{Action, SharedString};
|
||||
|
||||
use crate::{ActivateRegexMode, ActivateSemanticMode, ActivateTextMode};
|
||||
|
||||
// TODO: Update the default search mode to get from config
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
pub enum SearchMode {
|
||||
|
@ -15,6 +19,16 @@ impl SearchMode {
|
|||
SearchMode::Regex => "Regex",
|
||||
}
|
||||
}
|
||||
pub(crate) fn tooltip(&self) -> SharedString {
|
||||
format!("Activate {} Mode", self.label()).into()
|
||||
}
|
||||
pub(crate) fn action(&self) -> Box<dyn Action> {
|
||||
match self {
|
||||
SearchMode::Text => ActivateTextMode.boxed_clone(),
|
||||
SearchMode::Semantic => ActivateSemanticMode.boxed_clone(),
|
||||
SearchMode::Regex => ActivateRegexMode.boxed_clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn next_mode(mode: &SearchMode, semantic_enabled: bool) -> SearchMode {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::{
|
||||
history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateSemanticMode,
|
||||
ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
|
||||
SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace,
|
||||
ToggleWholeWord,
|
||||
history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateTextMode, CycleMode,
|
||||
NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
|
||||
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::HashMap;
|
||||
|
@ -45,7 +44,10 @@ use workspace::{
|
|||
WorkspaceId,
|
||||
};
|
||||
|
||||
actions!(SearchInNew, ToggleFocus, NextField, ToggleFilters,);
|
||||
actions!(
|
||||
project_search,
|
||||
[SearchInNew, ToggleFocus, NextField, ToggleFilters]
|
||||
);
|
||||
|
||||
#[derive(Default)]
|
||||
struct ActiveSearches(HashMap<WeakModel<Project>, WeakView<ProjectSearchView>>);
|
||||
|
@ -58,7 +60,9 @@ pub fn init(cx: &mut AppContext) {
|
|||
cx.set_global(ActiveSearches::default());
|
||||
cx.set_global(ActiveSettings::default());
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
workspace.register_action(ProjectSearchView::deploy);
|
||||
workspace
|
||||
.register_action(ProjectSearchView::deploy)
|
||||
.register_action(ProjectSearchBar::search_in_new);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
@ -1007,8 +1011,7 @@ impl ProjectSearchView {
|
|||
});
|
||||
}
|
||||
|
||||
// Re-activate the most recently activated search or the most recent if it has been closed.
|
||||
// If no search exists in the workspace, create a new one.
|
||||
// Add another search tab to the workspace.
|
||||
fn deploy(
|
||||
workspace: &mut Workspace,
|
||||
_: &workspace::NewSearch,
|
||||
|
@ -1019,20 +1022,6 @@ impl ProjectSearchView {
|
|||
state.0.retain(|project, _| project.is_upgradable())
|
||||
});
|
||||
|
||||
let active_search = cx
|
||||
.global::<ActiveSearches>()
|
||||
.0
|
||||
.get(&workspace.project().downgrade())
|
||||
.and_then(WeakView::upgrade);
|
||||
|
||||
let existing = active_search
|
||||
.and_then(|active_search| {
|
||||
workspace
|
||||
.items_of_type::<ProjectSearchView>(cx)
|
||||
.find(|search| search == &active_search)
|
||||
})
|
||||
.or_else(|| workspace.item_of_type::<ProjectSearchView>(cx));
|
||||
|
||||
let query = workspace.active_item(cx).and_then(|item| {
|
||||
let editor = item.act_as::<Editor>(cx)?;
|
||||
let query = editor.query_suggestion(cx);
|
||||
|
@ -1043,10 +1032,6 @@ impl ProjectSearchView {
|
|||
}
|
||||
});
|
||||
|
||||
let search = if let Some(existing) = existing {
|
||||
workspace.activate_item(&existing, cx);
|
||||
existing
|
||||
} else {
|
||||
let settings = cx
|
||||
.global::<ActiveSettings>()
|
||||
.0
|
||||
|
@ -1059,11 +1044,9 @@ impl ProjectSearchView {
|
|||
};
|
||||
|
||||
let model = cx.build_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
|
||||
let view = cx.build_view(|cx| ProjectSearchView::new(model, cx, settings));
|
||||
let search = cx.build_view(|cx| ProjectSearchView::new(model, cx, settings));
|
||||
|
||||
workspace.add_item(Box::new(view.clone()), cx);
|
||||
view
|
||||
};
|
||||
workspace.add_item(Box::new(search.clone()), cx);
|
||||
|
||||
search.update(cx, |search, cx| {
|
||||
if let Some(query) = query {
|
||||
|
@ -2436,29 +2419,86 @@ pub mod tests {
|
|||
workspace.update(cx, |workspace, cx| {
|
||||
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
let Some(search_view_2) = cx.read(|cx| {
|
||||
workspace
|
||||
.read(cx)
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.active_item()
|
||||
.and_then(|item| item.downcast::<ProjectSearchView>())
|
||||
}) else {
|
||||
panic!("Search view expected to appear after new search event trigger")
|
||||
};
|
||||
let search_view_id_2 = search_view_2.id();
|
||||
assert_ne!(
|
||||
search_view_2, search_view,
|
||||
"New search view should be open after `workspace::NewSearch` event"
|
||||
);
|
||||
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
assert_eq!(search_view.query_editor.read(cx).text(cx), "two", "Query should be updated to first search result after search view 2nd open in a row");
|
||||
assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO", "First search view should not have an updated query");
|
||||
assert_eq!(
|
||||
search_view
|
||||
.results_editor
|
||||
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
|
||||
"Results should be unchanged after search view 2nd open in a row"
|
||||
"Results of the first search view should not update too"
|
||||
);
|
||||
assert!(
|
||||
search_view.query_editor.is_focused(cx),
|
||||
"Focus should be moved into query editor again after search view 2nd open in a row"
|
||||
!search_view.query_editor.is_focused(cx),
|
||||
"Focus should be moved away from the first search view"
|
||||
);
|
||||
});
|
||||
|
||||
search_view_2.update(cx, |search_view_2, cx| {
|
||||
assert_eq!(
|
||||
search_view_2.query_editor.read(cx).text(cx),
|
||||
"two",
|
||||
"New search view should get the query from the text cursor was at during the event spawn (first search view's first result)"
|
||||
);
|
||||
assert_eq!(
|
||||
search_view_2
|
||||
.results_editor
|
||||
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"",
|
||||
"No search results should be in the 2nd view yet, as we did not spawn a search for it"
|
||||
);
|
||||
assert!(
|
||||
search_view_2.query_editor.is_focused(cx),
|
||||
"Focus should be moved into query editor fo the new window"
|
||||
);
|
||||
});
|
||||
|
||||
search_view_2.update(cx, |search_view_2, cx| {
|
||||
search_view_2
|
||||
.query_editor
|
||||
.update(cx, |query_editor, cx| query_editor.set_text("FOUR", cx));
|
||||
search_view_2.search(cx);
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
search_view_2.update(cx, |search_view_2, cx| {
|
||||
assert_eq!(
|
||||
search_view_2
|
||||
.results_editor
|
||||
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"\n\nconst FOUR: usize = one::ONE + three::THREE;",
|
||||
"New search view with the updated query should have new search results"
|
||||
);
|
||||
assert!(
|
||||
search_view_2.results_editor.is_focused(cx),
|
||||
"Search view with mismatching query should be focused after search results are available",
|
||||
);
|
||||
});
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
|
||||
window.dispatch_action(search_view_id_2, &ToggleFocus, &mut cx);
|
||||
})
|
||||
.detach();
|
||||
deterministic.run_until_parked();
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
search_view_id_2.update(cx, |search_view_2, cx| {
|
||||
assert!(
|
||||
search_view.results_editor.is_focused(cx),
|
||||
search_view_2.results_editor.is_focused(cx),
|
||||
"Search view with matching query should switch focus to the results editor after the toggle focus event",
|
||||
);
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@ pub use buffer_search::BufferSearchBar;
|
|||
use gpui::{actions, Action, AppContext, IntoElement};
|
||||
pub use mode::SearchMode;
|
||||
use project::search::SearchQuery;
|
||||
use ui::prelude::*;
|
||||
use ui::{prelude::*, Tooltip};
|
||||
use ui::{ButtonStyle, Icon, IconButton};
|
||||
//pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
||||
// use theme::components::{
|
||||
|
@ -22,6 +22,8 @@ pub fn init(cx: &mut AppContext) {
|
|||
}
|
||||
|
||||
actions!(
|
||||
search,
|
||||
[
|
||||
CycleMode,
|
||||
ToggleWholeWord,
|
||||
ToggleCaseSensitive,
|
||||
|
@ -36,6 +38,7 @@ actions!(
|
|||
ActivateRegexMode,
|
||||
ReplaceAll,
|
||||
ReplaceNext,
|
||||
]
|
||||
);
|
||||
|
||||
bitflags! {
|
||||
|
@ -85,7 +88,7 @@ impl SearchOptions {
|
|||
}
|
||||
|
||||
pub fn as_button(&self, active: bool) -> impl IntoElement {
|
||||
IconButton::new(0, self.icon())
|
||||
IconButton::new(self.label(), self.icon())
|
||||
.on_click({
|
||||
let action = self.to_toggle_action();
|
||||
move |_, cx| {
|
||||
|
@ -94,26 +97,38 @@ impl SearchOptions {
|
|||
})
|
||||
.style(ButtonStyle::Subtle)
|
||||
.when(active, |button| button.style(ButtonStyle::Filled))
|
||||
.tooltip({
|
||||
let action = self.to_toggle_action();
|
||||
let label: SharedString = format!("Toggle {}", self.label()).into();
|
||||
move |cx| Tooltip::for_action(label.clone(), &*action, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_replace_button(active: bool) -> impl IntoElement {
|
||||
// todo: add toggle_replace button
|
||||
IconButton::new(0, Icon::Replace)
|
||||
IconButton::new("buffer-search-bar-toggle-replace-button", Icon::Replace)
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(ToggleReplace));
|
||||
cx.notify();
|
||||
})
|
||||
.style(ButtonStyle::Subtle)
|
||||
.when(active, |button| button.style(ButtonStyle::Filled))
|
||||
.tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx))
|
||||
}
|
||||
|
||||
fn render_replace_button(
|
||||
action: impl Action + 'static + Send + Sync,
|
||||
icon: Icon,
|
||||
tooltip: &'static str,
|
||||
) -> impl IntoElement {
|
||||
// todo: add tooltip
|
||||
IconButton::new(0, icon).on_click(move |_, cx| {
|
||||
let id: SharedString = format!("search-replace-{}", action.name()).into();
|
||||
IconButton::new(id, icon)
|
||||
.tooltip({
|
||||
let action = action.boxed_clone();
|
||||
move |cx| Tooltip::for_action(tooltip, &*action, cx)
|
||||
})
|
||||
.on_click(move |_, cx| {
|
||||
cx.dispatch_action(action.boxed_clone());
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,30 +1,36 @@
|
|||
use gpui::{ClickEvent, IntoElement, WindowContext};
|
||||
use ui::prelude::*;
|
||||
use gpui::{Action, IntoElement};
|
||||
use ui::{prelude::*, Tooltip};
|
||||
use ui::{Button, IconButton};
|
||||
|
||||
use crate::mode::SearchMode;
|
||||
|
||||
pub(super) fn render_nav_button(
|
||||
icon: ui::Icon,
|
||||
_active: bool,
|
||||
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
active: bool,
|
||||
tooltip: &'static str,
|
||||
action: &'static dyn Action,
|
||||
) -> impl IntoElement {
|
||||
// let tooltip_style = cx.theme().tooltip.clone();
|
||||
// let cursor_style = if active {
|
||||
// CursorStyle::PointingHand
|
||||
// } else {
|
||||
// CursorStyle::default()
|
||||
// };
|
||||
// enum NavButton {}
|
||||
IconButton::new("search-nav-button", icon).on_click(on_click)
|
||||
IconButton::new(
|
||||
SharedString::from(format!("search-nav-button-{}", action.name())),
|
||||
icon,
|
||||
)
|
||||
.on_click(|_, cx| cx.dispatch_action(action.boxed_clone()))
|
||||
.tooltip(move |cx| Tooltip::for_action(tooltip, action, cx))
|
||||
.disabled(!active)
|
||||
}
|
||||
|
||||
pub(crate) fn render_search_mode_button(
|
||||
mode: SearchMode,
|
||||
is_active: bool,
|
||||
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> Button {
|
||||
pub(crate) fn render_search_mode_button(mode: SearchMode, is_active: bool) -> Button {
|
||||
Button::new(mode.label(), mode.label())
|
||||
.selected(is_active)
|
||||
.on_click(on_click)
|
||||
.on_click({
|
||||
let action = mode.action();
|
||||
move |_, cx| {
|
||||
cx.dispatch_action(action.boxed_clone());
|
||||
}
|
||||
})
|
||||
.tooltip({
|
||||
let action = mode.action();
|
||||
let tooltip_text = mode.tooltip();
|
||||
move |cx| Tooltip::for_action(tooltip_text.clone(), &*action, cx)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{settings_store::parse_json_with_comments, SettingsAssets};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::BTreeMap;
|
||||
use gpui::{actions, Action, AppContext, KeyBinding, SharedString};
|
||||
use gpui::{Action, AppContext, KeyBinding, SharedString};
|
||||
use schemars::{
|
||||
gen::{SchemaGenerator, SchemaSettings},
|
||||
schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
|
||||
|
@ -137,10 +137,8 @@ impl KeymapFile {
|
|||
}
|
||||
}
|
||||
|
||||
actions!(NoAction);
|
||||
|
||||
fn no_action() -> Box<dyn gpui::Action> {
|
||||
NoAction.boxed_clone()
|
||||
gpui::NoAction.boxed_clone()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -4,7 +4,7 @@ use gpui::{
|
|||
};
|
||||
use ui::prelude::*;
|
||||
|
||||
actions!(ActionA, ActionB, ActionC);
|
||||
actions!(focus, [ActionA, ActionB, ActionC]);
|
||||
|
||||
pub struct FocusStory {
|
||||
child_1_focus: FocusHandle,
|
||||
|
|
|
@ -28,6 +28,7 @@ pub enum ComponentStory {
|
|||
ListHeader,
|
||||
ListItem,
|
||||
Scroll,
|
||||
Tab,
|
||||
Text,
|
||||
ZIndex,
|
||||
Picker,
|
||||
|
@ -53,6 +54,7 @@ impl ComponentStory {
|
|||
Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(),
|
||||
Self::Scroll => ScrollStory::view(cx).into(),
|
||||
Self::Text => TextStory::view(cx).into(),
|
||||
Self::Tab => cx.build_view(|_| ui::TabStory).into(),
|
||||
Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
|
||||
Self::Picker => PickerStory::new(cx).into(),
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ use std::{
|
|||
use thiserror::Error;
|
||||
|
||||
use gpui::{
|
||||
px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke,
|
||||
actions, px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke,
|
||||
ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||
Point, ScrollWheelEvent, Size, Task, TouchPhase,
|
||||
};
|
||||
|
@ -58,6 +58,19 @@ use gpui::{
|
|||
use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
actions!(
|
||||
terminal,
|
||||
[
|
||||
Clear,
|
||||
Copy,
|
||||
Paste,
|
||||
ShowCharacterPalette,
|
||||
SearchTest,
|
||||
SendText,
|
||||
SendKeystroke,
|
||||
]
|
||||
);
|
||||
|
||||
///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
|
||||
///Scroll multiplier that is set to 3 by default. This will be removed when I
|
||||
///Implement scroll bars.
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
|
||||
use gpui::{
|
||||
black, div, point, px, red, relative, transparent_black, AnyElement, AsyncWindowContext,
|
||||
AvailableSpace, Bounds, DispatchPhase, Element, ElementId, FocusHandle, Font, FontStyle,
|
||||
FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, IntoElement,
|
||||
LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels,
|
||||
AvailableSpace, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font,
|
||||
FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState,
|
||||
IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels,
|
||||
PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled,
|
||||
TextRun, TextStyle, TextSystem, UnderlineStyle, View, WhiteSpace, WindowContext,
|
||||
TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::CursorShape;
|
||||
|
@ -27,8 +27,6 @@ use ui::Tooltip;
|
|||
use std::mem;
|
||||
use std::{fmt::Debug, ops::RangeInclusive};
|
||||
|
||||
use crate::TerminalView;
|
||||
|
||||
///The information generated during layout that is necessary for painting
|
||||
pub struct LayoutState {
|
||||
cells: Vec<LayoutCell>,
|
||||
|
@ -149,7 +147,6 @@ impl LayoutRect {
|
|||
///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
|
||||
pub struct TerminalElement {
|
||||
terminal: Model<Terminal>,
|
||||
terminal_view: View<TerminalView>,
|
||||
focus: FocusHandle,
|
||||
focused: bool,
|
||||
cursor_visible: bool,
|
||||
|
@ -168,7 +165,6 @@ impl StatefulInteractiveElement for TerminalElement {}
|
|||
impl TerminalElement {
|
||||
pub fn new(
|
||||
terminal: Model<Terminal>,
|
||||
terminal_view: View<TerminalView>,
|
||||
focus: FocusHandle,
|
||||
focused: bool,
|
||||
cursor_visible: bool,
|
||||
|
@ -176,7 +172,6 @@ impl TerminalElement {
|
|||
) -> TerminalElement {
|
||||
TerminalElement {
|
||||
terminal,
|
||||
terminal_view,
|
||||
focused,
|
||||
focus: focus.clone(),
|
||||
cursor_visible,
|
||||
|
@ -474,6 +469,7 @@ impl TerminalElement {
|
|||
.size_full()
|
||||
.id("terminal-element")
|
||||
.tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx))
|
||||
.into_any_element()
|
||||
});
|
||||
|
||||
let TerminalContent {
|
||||
|
@ -575,7 +571,7 @@ impl TerminalElement {
|
|||
relative_highlighted_ranges,
|
||||
mode: *mode,
|
||||
display_offset: *display_offset,
|
||||
hyperlink_tooltip: None, // todo!(tooltips)
|
||||
hyperlink_tooltip,
|
||||
gutter,
|
||||
}
|
||||
}
|
||||
|
@ -643,15 +639,13 @@ impl TerminalElement {
|
|||
let connection = connection.clone();
|
||||
let focus = focus.clone();
|
||||
move |e, cx| {
|
||||
if e.pressed_button.is_some() {
|
||||
if focus.is_focused(cx) {
|
||||
if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() {
|
||||
connection.update(cx, |terminal, cx| {
|
||||
terminal.mouse_drag(e, origin, bounds);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_mouse_up(
|
||||
MouseButton::Left,
|
||||
|
@ -776,18 +770,11 @@ impl Element for TerminalElement {
|
|||
(layout_id, interactive_state)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::State,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) {
|
||||
fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext<'_>) {
|
||||
let mut layout = self.compute_layout(bounds, cx);
|
||||
|
||||
let theme = cx.theme();
|
||||
|
||||
let dispatch_context = self.terminal_view.read(cx).dispatch_context(cx);
|
||||
self.interactivity().key_context = Some(dispatch_context);
|
||||
cx.paint_quad(
|
||||
bounds,
|
||||
Default::default(),
|
||||
|
@ -806,7 +793,28 @@ impl Element for TerminalElement {
|
|||
.map(|cursor| cursor.bounding_rect(origin)),
|
||||
};
|
||||
|
||||
let mut this = self.register_mouse_listeners(origin, layout.mode, bounds, cx);
|
||||
let terminal_focus_handle = self.focus.clone();
|
||||
let terminal_handle = self.terminal.clone();
|
||||
let mut this: TerminalElement = self
|
||||
.register_mouse_listeners(origin, layout.mode, bounds, cx)
|
||||
.drag_over::<ExternalPaths>(|style| {
|
||||
// todo!() why does not it work? z-index of elements?
|
||||
style.bg(cx.theme().colors().ghost_element_hover)
|
||||
})
|
||||
.on_drop::<ExternalPaths>(move |external_paths, cx| {
|
||||
cx.focus(&terminal_focus_handle);
|
||||
let mut new_text = external_paths
|
||||
.read(cx)
|
||||
.paths()
|
||||
.iter()
|
||||
.map(|path| format!(" {path:?}"))
|
||||
.join("");
|
||||
new_text.push(' ');
|
||||
terminal_handle.update(cx, |terminal, _| {
|
||||
// todo!() long paths are not displayed properly albeit the text is there
|
||||
terminal.paste(&new_text);
|
||||
});
|
||||
});
|
||||
|
||||
let interactivity = mem::take(&mut this.interactivity);
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ use anyhow::Result;
|
|||
|
||||
const TERMINAL_PANEL_KEY: &'static str = "TerminalPanel";
|
||||
|
||||
actions!(ToggleFocus);
|
||||
actions!(terminal_view, [ToggleFocus]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(
|
||||
|
|
|
@ -9,9 +9,10 @@ pub mod terminal_panel;
|
|||
// use crate::terminal_element::TerminalElement;
|
||||
use editor::{scroll::autoscroll::Autoscroll, Editor};
|
||||
use gpui::{
|
||||
actions, div, Action, AnyElement, AppContext, Div, EventEmitter, FocusEvent, FocusHandle,
|
||||
Focusable, FocusableElement, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model,
|
||||
MouseButton, MouseDownEvent, Pixels, Render, Subscription, Task, View, VisualContext, WeakView,
|
||||
div, impl_actions, overlay, AnyElement, AppContext, DismissEvent, Div, EventEmitter,
|
||||
FocusEvent, FocusHandle, Focusable, FocusableElement, FocusableView, KeyContext, KeyDownEvent,
|
||||
Keystroke, Model, MouseButton, MouseDownEvent, Pixels, Render, Styled, Subscription, Task,
|
||||
View, VisualContext, WeakView,
|
||||
};
|
||||
use language::Bias;
|
||||
use persistence::TERMINAL_DB;
|
||||
|
@ -22,7 +23,7 @@ use terminal::{
|
|||
term::{search::RegexSearch, TermMode},
|
||||
},
|
||||
terminal_settings::{TerminalBlink, TerminalSettings, WorkingDirectory},
|
||||
Event, MaybeNavigationTarget, Terminal,
|
||||
Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal,
|
||||
};
|
||||
use terminal_element::TerminalElement;
|
||||
use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label};
|
||||
|
@ -54,13 +55,13 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
|||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ScrollTerminal(pub i32);
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Action)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
|
||||
pub struct SendText(String);
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Action)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
|
||||
pub struct SendKeystroke(String);
|
||||
|
||||
actions!(Clear, Copy, Paste, ShowCharacterPalette, SearchTest);
|
||||
impl_actions!(terminal_view, [SendText, SendKeystroke]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
terminal_panel::init(cx);
|
||||
|
@ -83,7 +84,7 @@ pub struct TerminalView {
|
|||
has_new_content: bool,
|
||||
//Currently using iTerm bell, show bell emoji in tab until input is received
|
||||
has_bell: bool,
|
||||
context_menu: Option<View<ContextMenu>>,
|
||||
context_menu: Option<(View<ContextMenu>, gpui::Point<Pixels>, Subscription)>,
|
||||
blink_state: bool,
|
||||
blinking_on: bool,
|
||||
blinking_paused: bool,
|
||||
|
@ -314,14 +315,24 @@ impl TerminalView {
|
|||
position: gpui::Point<Pixels>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.context_menu = Some(ContextMenu::build(cx, |menu, cx| {
|
||||
let context_menu = ContextMenu::build(cx, |menu, cx| {
|
||||
menu.action("Clear", Box::new(Clear))
|
||||
.action("Close", Box::new(CloseActiveItem { save_intent: None }))
|
||||
}));
|
||||
// todo!(context menus)
|
||||
// self.context_menu
|
||||
// .show(position, AnchorCorner::TopLeft, menu_entries, cx);
|
||||
// cx.notify();
|
||||
});
|
||||
|
||||
cx.focus_view(&context_menu);
|
||||
let subscription =
|
||||
cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
|
||||
if this.context_menu.as_ref().is_some_and(|context_menu| {
|
||||
context_menu.0.focus_handle(cx).contains_focused(cx)
|
||||
}) {
|
||||
cx.focus_self();
|
||||
}
|
||||
this.context_menu.take();
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
self.context_menu = Some((context_menu, position, subscription));
|
||||
}
|
||||
|
||||
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
|
||||
|
@ -623,7 +634,6 @@ impl Render for TerminalView {
|
|||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
let terminal_handle = self.terminal.clone();
|
||||
let this_view = cx.view().clone();
|
||||
|
||||
let self_id = cx.entity_id();
|
||||
let focused = self.focus_handle.is_focused(cx);
|
||||
|
@ -631,12 +641,8 @@ impl Render for TerminalView {
|
|||
div()
|
||||
.size_full()
|
||||
.relative()
|
||||
.child(
|
||||
div()
|
||||
.z_index(0)
|
||||
.absolute()
|
||||
.size_full()
|
||||
.on_key_down(cx.listener(Self::key_down))
|
||||
.track_focus(&self.focus_handle)
|
||||
.key_context(self.dispatch_context(cx))
|
||||
.on_action(cx.listener(TerminalView::send_text))
|
||||
.on_action(cx.listener(TerminalView::send_keystroke))
|
||||
.on_action(cx.listener(TerminalView::copy))
|
||||
|
@ -644,30 +650,32 @@ impl Render for TerminalView {
|
|||
.on_action(cx.listener(TerminalView::clear))
|
||||
.on_action(cx.listener(TerminalView::show_character_palette))
|
||||
.on_action(cx.listener(TerminalView::select_all))
|
||||
.child(TerminalElement::new(
|
||||
terminal_handle,
|
||||
this_view,
|
||||
self.focus_handle.clone(),
|
||||
focused,
|
||||
self.should_show_cursor(focused, cx),
|
||||
self.can_navigate_to_selected_word,
|
||||
))
|
||||
.on_focus_in(cx.listener(Self::focus_in))
|
||||
.on_focus_out(cx.listener(Self::focus_out))
|
||||
.on_key_down(cx.listener(Self::key_down))
|
||||
.on_mouse_down(
|
||||
MouseButton::Right,
|
||||
cx.listener(|this, event: &MouseDownEvent, cx| {
|
||||
this.deploy_context_menu(event.position, cx);
|
||||
cx.notify();
|
||||
}),
|
||||
),
|
||||
)
|
||||
.children(
|
||||
self.context_menu
|
||||
.clone()
|
||||
.map(|context_menu| div().z_index(1).absolute().child(context_menu)),
|
||||
.child(
|
||||
// TODO: Oddly this wrapper div is needed for TerminalElement to not steal events from the context menu
|
||||
div().size_full().child(TerminalElement::new(
|
||||
terminal_handle,
|
||||
self.focus_handle.clone(),
|
||||
focused,
|
||||
self.should_show_cursor(focused, cx),
|
||||
self.can_navigate_to_selected_word,
|
||||
)),
|
||||
)
|
||||
.track_focus(&self.focus_handle)
|
||||
.on_focus_in(cx.listener(Self::focus_in))
|
||||
.on_focus_out(cx.listener(Self::focus_out))
|
||||
.children(self.context_menu.as_ref().map(|(menu, positon, _)| {
|
||||
overlay()
|
||||
.position(*positon)
|
||||
.anchor(gpui::AnchorCorner::TopLeft)
|
||||
.child(menu.clone())
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,12 @@ use crate::{
|
|||
SystemColors, Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ThemeMeta {
|
||||
pub name: SharedString,
|
||||
pub appearance: Appearance,
|
||||
}
|
||||
|
||||
pub struct ThemeRegistry {
|
||||
themes: HashMap<SharedString, Arc<Theme>>,
|
||||
}
|
||||
|
@ -94,8 +100,11 @@ impl ThemeRegistry {
|
|||
self.themes.keys().cloned()
|
||||
}
|
||||
|
||||
pub fn list(&self, _staff: bool) -> impl Iterator<Item = SharedString> + '_ {
|
||||
self.themes.values().map(|theme| theme.name.clone())
|
||||
pub fn list(&self, _staff: bool) -> impl Iterator<Item = ThemeMeta> + '_ {
|
||||
self.themes.values().map(|theme| ThemeMeta {
|
||||
name: theme.name.clone(),
|
||||
appearance: theme.appearance(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Result<Arc<Theme>> {
|
||||
|
|
|
@ -31,6 +31,15 @@ pub enum Appearance {
|
|||
Dark,
|
||||
}
|
||||
|
||||
impl Appearance {
|
||||
pub fn is_light(&self) -> bool {
|
||||
match self {
|
||||
Self::Light => true,
|
||||
Self::Dark => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum LoadThemes {
|
||||
/// Only load the base theme.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue