diff --git a/Cargo.lock b/Cargo.lock index ed06172fd8..4a6a42b1c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1880,6 +1880,30 @@ dependencies = [ "zed-actions", ] +[[package]] +name = "command_palette2" +version = "0.1.0" +dependencies = [ + "anyhow", + "collections", + "ctor", + "editor2", + "env_logger 0.9.3", + "fuzzy2", + "gpui2", + "language2", + "picker2", + "project2", + "serde", + "serde_json", + "settings2", + "theme2", + "ui2", + "util", + "workspace2", + "zed_actions2", +] + [[package]] name = "component_test" version = "0.1.0" @@ -4434,6 +4458,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json_comments" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbbfed4e59ba9750e15ba154fdfd9329cee16ff3df539c2666b70f58cc32105" + [[package]] name = "jwt" version = "0.16.0" @@ -6130,6 +6160,7 @@ dependencies = [ "serde_json", "settings2", "theme2", + "ui2", "util", ] @@ -9156,10 +9187,13 @@ dependencies = [ "anyhow", "convert_case 0.6.0", "gpui2", + "indexmap 1.9.3", + "json_comments", "log", "rust-embed", "serde", "simplelog", + "strum", "theme2", "uuid 1.4.1", ] @@ -11363,6 +11397,7 @@ dependencies = [ "cli", "client2", "collections", + "command_palette2", "copilot2", "ctor", "db2", @@ -11449,6 +11484,15 @@ dependencies = [ "util", "uuid 1.4.1", "workspace2", + "zed_actions2", +] + +[[package]] +name = "zed_actions2" +version = "0.1.0" +dependencies = [ + "gpui2", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1b8081d066..905750f835 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "crates/collab_ui", "crates/collections", "crates/command_palette", + "crates/command_palette2", "crates/component_test", "crates/context_menu", "crates/copilot", @@ -110,7 +111,8 @@ members = [ "crates/xtask", "crates/zed", "crates/zed2", - "crates/zed-actions" + "crates/zed-actions", + "crates/zed_actions2" ] default-members = ["crates/zed"] resolver = "2" diff --git a/assets/settings/default.json b/assets/settings/default.json index 19c73ca021..9a6c7587d6 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -102,6 +102,16 @@ "selections": true }, "relative_line_numbers": false, + // When to populate a new search's query based on the text under the cursor. + // This setting can take the following three values: + // + // 1. Always populate the search query with the word under the cursor (default). + // "always" + // 2. Only populate the search query when there is text selected + // "selection" + // 3. Never populate the search query + // "never" + "seed_search_query_from_cursor": "always", // Inlay hint related settings "inlay_hints": { // Global switch to toggle hints on and off, switched off by default. diff --git a/assets/themes/src/vscode/gruvbox/gruvbox-dark-hard.json b/assets/themes/src/vscode/gruvbox/gruvbox-dark-hard.json index 4c1a7c9e8a..1b7eb8eef6 100644 --- a/assets/themes/src/vscode/gruvbox/gruvbox-dark-hard.json +++ b/assets/themes/src/vscode/gruvbox/gruvbox-dark-hard.json @@ -423,7 +423,10 @@ } }, { - "scope": ["string.interpolated.dollar.shell", "string.interpolated.backtick.shell"], + "scope": [ + "string.interpolated.dollar.shell", + "string.interpolated.backtick.shell" + ], "settings": { "foreground": "#8ec07c" } @@ -489,13 +492,19 @@ { "name": "coloring of the Java import and package identifiers", - "scope": ["storage.modifier.import.java", "storage.modifier.package.java"], + "scope": [ + "storage.modifier.import.java", + "storage.modifier.package.java" + ], "settings": { "foreground": "#bdae93" } }, { - "scope": ["keyword.other.import.java", "keyword.other.package.java"], + "scope": [ + "keyword.other.import.java", + "keyword.other.package.java" + ], "settings": { "foreground": "#8ec07c" } @@ -628,7 +637,9 @@ }, { "name": "JSON Level 0", - "scope": ["source.json meta.structure.dictionary.json support.type.property-name.json"], + "scope": [ + "source.json meta.structure.dictionary.json support.type.property-name.json" + ], "settings": { "foreground": "#b8bb26" } @@ -761,7 +772,10 @@ } }, { - "scope": ["source.go keyword.interface", "source.go keyword.struct"], + "scope": [ + "source.go keyword.interface", + "source.go keyword.struct" + ], "settings": { "foreground": "#83a598" } @@ -788,7 +802,10 @@ { "name": "ReasonML String", - "scope": ["source.reason string.double", "source.reason string.regexp"], + "scope": [ + "source.reason string.double", + "source.reason string.regexp" + ], "settings": { "foreground": "#b8bb26" } @@ -809,7 +826,10 @@ }, { "name": "ReasonML property", - "scope": ["source.reason support.property-value", "source.reason entity.name.filename"], + "scope": [ + "source.reason support.property-value", + "source.reason entity.name.filename" + ], "settings": { "foreground": "#fe8019" } @@ -831,7 +851,9 @@ }, { "name": "Powershell function attribute", - "scope": ["source.powershell support.function.attribute.powershell"], + "scope": [ + "source.powershell support.function.attribute.powershell" + ], "settings": { "foreground": "#bdae93" } diff --git a/assets/themes/src/vscode/gruvbox/gruvbox-dark-medium.json b/assets/themes/src/vscode/gruvbox/gruvbox-dark-medium.json index e9ef916193..0f22d6fc21 100644 --- a/assets/themes/src/vscode/gruvbox/gruvbox-dark-medium.json +++ b/assets/themes/src/vscode/gruvbox/gruvbox-dark-medium.json @@ -423,7 +423,10 @@ } }, { - "scope": ["string.interpolated.dollar.shell", "string.interpolated.backtick.shell"], + "scope": [ + "string.interpolated.dollar.shell", + "string.interpolated.backtick.shell" + ], "settings": { "foreground": "#8ec07c" } @@ -489,13 +492,19 @@ { "name": "coloring of the Java import and package identifiers", - "scope": ["storage.modifier.import.java", "storage.modifier.package.java"], + "scope": [ + "storage.modifier.import.java", + "storage.modifier.package.java" + ], "settings": { "foreground": "#bdae93" } }, { - "scope": ["keyword.other.import.java", "keyword.other.package.java"], + "scope": [ + "keyword.other.import.java", + "keyword.other.package.java" + ], "settings": { "foreground": "#8ec07c" } @@ -628,7 +637,9 @@ }, { "name": "JSON Level 0", - "scope": ["source.json meta.structure.dictionary.json support.type.property-name.json"], + "scope": [ + "source.json meta.structure.dictionary.json support.type.property-name.json" + ], "settings": { "foreground": "#b8bb26" } @@ -761,7 +772,10 @@ } }, { - "scope": ["source.go keyword.interface", "source.go keyword.struct"], + "scope": [ + "source.go keyword.interface", + "source.go keyword.struct" + ], "settings": { "foreground": "#83a598" } @@ -788,7 +802,10 @@ { "name": "ReasonML String", - "scope": ["source.reason string.double", "source.reason string.regexp"], + "scope": [ + "source.reason string.double", + "source.reason string.regexp" + ], "settings": { "foreground": "#b8bb26" } @@ -809,7 +826,10 @@ }, { "name": "ReasonML property", - "scope": ["source.reason support.property-value", "source.reason entity.name.filename"], + "scope": [ + "source.reason support.property-value", + "source.reason entity.name.filename" + ], "settings": { "foreground": "#fe8019" } @@ -831,7 +851,9 @@ }, { "name": "Powershell function attribute", - "scope": ["source.powershell support.function.attribute.powershell"], + "scope": [ + "source.powershell support.function.attribute.powershell" + ], "settings": { "foreground": "#bdae93" } diff --git a/assets/themes/src/vscode/gruvbox/gruvbox-dark-soft.json b/assets/themes/src/vscode/gruvbox/gruvbox-dark-soft.json index 8581447f49..e03a9f0f5c 100644 --- a/assets/themes/src/vscode/gruvbox/gruvbox-dark-soft.json +++ b/assets/themes/src/vscode/gruvbox/gruvbox-dark-soft.json @@ -423,7 +423,10 @@ } }, { - "scope": ["string.interpolated.dollar.shell", "string.interpolated.backtick.shell"], + "scope": [ + "string.interpolated.dollar.shell", + "string.interpolated.backtick.shell" + ], "settings": { "foreground": "#8ec07c" } @@ -489,13 +492,19 @@ { "name": "coloring of the Java import and package identifiers", - "scope": ["storage.modifier.import.java", "storage.modifier.package.java"], + "scope": [ + "storage.modifier.import.java", + "storage.modifier.package.java" + ], "settings": { "foreground": "#bdae93" } }, { - "scope": ["keyword.other.import.java", "keyword.other.package.java"], + "scope": [ + "keyword.other.import.java", + "keyword.other.package.java" + ], "settings": { "foreground": "#8ec07c" } @@ -628,7 +637,9 @@ }, { "name": "JSON Level 0", - "scope": ["source.json meta.structure.dictionary.json support.type.property-name.json"], + "scope": [ + "source.json meta.structure.dictionary.json support.type.property-name.json" + ], "settings": { "foreground": "#b8bb26" } @@ -761,7 +772,10 @@ } }, { - "scope": ["source.go keyword.interface", "source.go keyword.struct"], + "scope": [ + "source.go keyword.interface", + "source.go keyword.struct" + ], "settings": { "foreground": "#83a598" } @@ -788,7 +802,10 @@ { "name": "ReasonML String", - "scope": ["source.reason string.double", "source.reason string.regexp"], + "scope": [ + "source.reason string.double", + "source.reason string.regexp" + ], "settings": { "foreground": "#b8bb26" } @@ -809,7 +826,10 @@ }, { "name": "ReasonML property", - "scope": ["source.reason support.property-value", "source.reason entity.name.filename"], + "scope": [ + "source.reason support.property-value", + "source.reason entity.name.filename" + ], "settings": { "foreground": "#fe8019" } @@ -831,7 +851,9 @@ }, { "name": "Powershell function attribute", - "scope": ["source.powershell support.function.attribute.powershell"], + "scope": [ + "source.powershell support.function.attribute.powershell" + ], "settings": { "foreground": "#bdae93" } diff --git a/assets/themes/src/vscode/gruvbox/gruvbox-light-hard.json b/assets/themes/src/vscode/gruvbox/gruvbox-light-hard.json index bd0f60eac9..71af6c807e 100644 --- a/assets/themes/src/vscode/gruvbox/gruvbox-light-hard.json +++ b/assets/themes/src/vscode/gruvbox/gruvbox-light-hard.json @@ -422,7 +422,10 @@ } }, { - "scope": ["string.interpolated.dollar.shell", "string.interpolated.backtick.shell"], + "scope": [ + "string.interpolated.dollar.shell", + "string.interpolated.backtick.shell" + ], "settings": { "foreground": "#427b58" } @@ -488,13 +491,19 @@ { "name": "coloring of the Java import and package identifiers", - "scope": ["storage.modifier.import.java", "storage.modifier.package.java"], + "scope": [ + "storage.modifier.import.java", + "storage.modifier.package.java" + ], "settings": { "foreground": "#665c54" } }, { - "scope": ["keyword.other.import.java", "keyword.other.package.java"], + "scope": [ + "keyword.other.import.java", + "keyword.other.package.java" + ], "settings": { "foreground": "#427b58" } @@ -627,7 +636,9 @@ }, { "name": "JSON Level 0", - "scope": ["source.json meta.structure.dictionary.json support.type.property-name.json"], + "scope": [ + "source.json meta.structure.dictionary.json support.type.property-name.json" + ], "settings": { "foreground": "#79740e" } @@ -760,7 +771,10 @@ } }, { - "scope": ["source.go keyword.interface", "source.go keyword.struct"], + "scope": [ + "source.go keyword.interface", + "source.go keyword.struct" + ], "settings": { "foreground": "#076678" } @@ -787,7 +801,10 @@ { "name": "ReasonML String", - "scope": ["source.reason string.double", "source.reason string.regexp"], + "scope": [ + "source.reason string.double", + "source.reason string.regexp" + ], "settings": { "foreground": "#79740e" } @@ -808,7 +825,10 @@ }, { "name": "ReasonML property", - "scope": ["source.reason support.property-value", "source.reason entity.name.filename"], + "scope": [ + "source.reason support.property-value", + "source.reason entity.name.filename" + ], "settings": { "foreground": "#af3a03" } @@ -830,7 +850,9 @@ }, { "name": "Powershell function attribute", - "scope": ["source.powershell support.function.attribute.powershell"], + "scope": [ + "source.powershell support.function.attribute.powershell" + ], "settings": { "foreground": "#665c54" } diff --git a/assets/themes/src/vscode/gruvbox/gruvbox-light-medium.json b/assets/themes/src/vscode/gruvbox/gruvbox-light-medium.json index 6cd742090e..39de91c9ec 100644 --- a/assets/themes/src/vscode/gruvbox/gruvbox-light-medium.json +++ b/assets/themes/src/vscode/gruvbox/gruvbox-light-medium.json @@ -422,7 +422,10 @@ } }, { - "scope": ["string.interpolated.dollar.shell", "string.interpolated.backtick.shell"], + "scope": [ + "string.interpolated.dollar.shell", + "string.interpolated.backtick.shell" + ], "settings": { "foreground": "#427b58" } @@ -488,13 +491,19 @@ { "name": "coloring of the Java import and package identifiers", - "scope": ["storage.modifier.import.java", "storage.modifier.package.java"], + "scope": [ + "storage.modifier.import.java", + "storage.modifier.package.java" + ], "settings": { "foreground": "#665c54" } }, { - "scope": ["keyword.other.import.java", "keyword.other.package.java"], + "scope": [ + "keyword.other.import.java", + "keyword.other.package.java" + ], "settings": { "foreground": "#427b58" } @@ -627,7 +636,9 @@ }, { "name": "JSON Level 0", - "scope": ["source.json meta.structure.dictionary.json support.type.property-name.json"], + "scope": [ + "source.json meta.structure.dictionary.json support.type.property-name.json" + ], "settings": { "foreground": "#79740e" } @@ -760,7 +771,10 @@ } }, { - "scope": ["source.go keyword.interface", "source.go keyword.struct"], + "scope": [ + "source.go keyword.interface", + "source.go keyword.struct" + ], "settings": { "foreground": "#076678" } @@ -787,7 +801,10 @@ { "name": "ReasonML String", - "scope": ["source.reason string.double", "source.reason string.regexp"], + "scope": [ + "source.reason string.double", + "source.reason string.regexp" + ], "settings": { "foreground": "#79740e" } @@ -808,7 +825,10 @@ }, { "name": "ReasonML property", - "scope": ["source.reason support.property-value", "source.reason entity.name.filename"], + "scope": [ + "source.reason support.property-value", + "source.reason entity.name.filename" + ], "settings": { "foreground": "#af3a03" } @@ -830,7 +850,9 @@ }, { "name": "Powershell function attribute", - "scope": ["source.powershell support.function.attribute.powershell"], + "scope": [ + "source.powershell support.function.attribute.powershell" + ], "settings": { "foreground": "#665c54" } diff --git a/assets/themes/src/vscode/gruvbox/gruvbox-light-soft.json b/assets/themes/src/vscode/gruvbox/gruvbox-light-soft.json index a101704eed..997dcfe154 100644 --- a/assets/themes/src/vscode/gruvbox/gruvbox-light-soft.json +++ b/assets/themes/src/vscode/gruvbox/gruvbox-light-soft.json @@ -422,7 +422,10 @@ } }, { - "scope": ["string.interpolated.dollar.shell", "string.interpolated.backtick.shell"], + "scope": [ + "string.interpolated.dollar.shell", + "string.interpolated.backtick.shell" + ], "settings": { "foreground": "#427b58" } @@ -488,13 +491,19 @@ { "name": "coloring of the Java import and package identifiers", - "scope": ["storage.modifier.import.java", "storage.modifier.package.java"], + "scope": [ + "storage.modifier.import.java", + "storage.modifier.package.java" + ], "settings": { "foreground": "#665c54" } }, { - "scope": ["keyword.other.import.java", "keyword.other.package.java"], + "scope": [ + "keyword.other.import.java", + "keyword.other.package.java" + ], "settings": { "foreground": "#427b58" } @@ -627,7 +636,9 @@ }, { "name": "JSON Level 0", - "scope": ["source.json meta.structure.dictionary.json support.type.property-name.json"], + "scope": [ + "source.json meta.structure.dictionary.json support.type.property-name.json" + ], "settings": { "foreground": "#79740e" } @@ -760,7 +771,10 @@ } }, { - "scope": ["source.go keyword.interface", "source.go keyword.struct"], + "scope": [ + "source.go keyword.interface", + "source.go keyword.struct" + ], "settings": { "foreground": "#076678" } @@ -787,7 +801,10 @@ { "name": "ReasonML String", - "scope": ["source.reason string.double", "source.reason string.regexp"], + "scope": [ + "source.reason string.double", + "source.reason string.regexp" + ], "settings": { "foreground": "#79740e" } @@ -808,7 +825,10 @@ }, { "name": "ReasonML property", - "scope": ["source.reason support.property-value", "source.reason entity.name.filename"], + "scope": [ + "source.reason support.property-value", + "source.reason entity.name.filename" + ], "settings": { "foreground": "#af3a03" } @@ -830,7 +850,9 @@ }, { "name": "Powershell function attribute", - "scope": ["source.powershell support.function.attribute.powershell"], + "scope": [ + "source.powershell support.function.attribute.powershell" + ], "settings": { "foreground": "#665c54" } diff --git a/assets/themes/src/vscode/noctis/family.json b/assets/themes/src/vscode/noctis/family.json index 26aba7e213..525cb160f5 100644 --- a/assets/themes/src/vscode/noctis/family.json +++ b/assets/themes/src/vscode/noctis/family.json @@ -1,61 +1,61 @@ { - "name": "Notctis", - "author": "Liviu Schera (liviuschera)", - "themes": [ - { - "name": "Noctis Azureus", - "file_name": "azureus.json", - "appearance": "dark" - }, - { - "name": "Noctis Bordo", - "file_name": "bordo.json", - "appearance": "dark" - }, - { - "name": "Noctus Hibernus", - "file_name": "hibernus.json", - "appearance": "light" - }, - { - "name": "Noctis Lilac", - "file_name": "lilac.json", - "appearance": "dark" - }, - { - "name": "Noctis Lux", - "file_name": "lux.json", - "appearance": "light" - }, - { - "name": "Noctis Minimus", - "file_name": "minimus.json", - "appearance": "dark" - }, - { - "name": "Noctis", - "file_name": "noctis.json", - "appearance": "dark" - }, - { - "name": "Noctis Obscuro", - "file_name": "obscuro.json", - "appearance": "dark" - }, - { - "name": "Noctis Sereno", - "file_name": "obscuro.json", - "appearance": "dark" - }, - { - "name": "Noctis Uva", - "file_name": "uva.json", - "appearance": "dark" - }, - { - "name": "Noctis Viola", - "file_name": "viola.json", - "appearance": "dark" - } - ] + "name": "Noctis", + "author": "Liviu Schera (liviuschera)", + "themes": [ + { + "name": "Noctis Azureus", + "file_name": "azureus.json", + "appearance": "dark" + }, + { + "name": "Noctis Bordo", + "file_name": "bordo.json", + "appearance": "dark" + }, + { + "name": "Noctus Hibernus", + "file_name": "hibernus.json", + "appearance": "light" + }, + { + "name": "Noctis Lilac", + "file_name": "lilac.json", + "appearance": "dark" + }, + { + "name": "Noctis Lux", + "file_name": "lux.json", + "appearance": "light" + }, + { + "name": "Noctis Minimus", + "file_name": "minimus.json", + "appearance": "dark" + }, + { + "name": "Noctis", + "file_name": "noctis.json", + "appearance": "dark" + }, + { + "name": "Noctis Obscuro", + "file_name": "obscuro.json", + "appearance": "dark" + }, + { + "name": "Noctis Sereno", + "file_name": "obscuro.json", + "appearance": "dark" + }, + { + "name": "Noctis Uva", + "file_name": "uva.json", + "appearance": "dark" + }, + { + "name": "Noctis Viola", + "file_name": "viola.json", + "appearance": "dark" + } + ] } diff --git a/assets/themes/src/vscode/rose-pine/family.json b/assets/themes/src/vscode/rose-pine/family.json index 5b72edd300..1cdcab7842 100644 --- a/assets/themes/src/vscode/rose-pine/family.json +++ b/assets/themes/src/vscode/rose-pine/family.json @@ -1,21 +1,21 @@ { - "name": "Rose Pine", - "author": "Rosé Pine", - "themes": [ - { - "name": "Rose Pine", - "file_name": "rose-pine.json", - "appearance": "dark" - }, - { - "name": "Rose Moon", - "file_name": "rose-pine-moon.json", - "appearance": "dark" - }, - { - "name": "Rose Pine Dawn", - "file_name": "rose-pine-dawn.json", - "appearance": "light" - } - ] + "name": "Rose Pine", + "author": "Rosé Pine", + "themes": [ + { + "name": "Rose Pine", + "file_name": "rose-pine.json", + "appearance": "dark" + }, + { + "name": "Rose Pine Moon", + "file_name": "rose-pine-moon.json", + "appearance": "dark" + }, + { + "name": "Rose Pine Dawn", + "file_name": "rose-pine-dawn.json", + "appearance": "light" + } + ] } diff --git a/assets/themes/src/vscode/synthwave-84/synthwave.json b/assets/themes/src/vscode/synthwave-84/synthwave.json index 5b38ef46fa..9b23270d8c 100644 --- a/assets/themes/src/vscode/synthwave-84/synthwave.json +++ b/assets/themes/src/vscode/synthwave-84/synthwave.json @@ -1,822 +1,841 @@ { - "name": "SynthWave 84", - "type": "dark", - "semanticHighlighting": true, - "colors": { - "focusBorder": "#1f212b", - "foreground": "#ffffff", - "widget.shadow": "#2a2139", - "selection.background": "#ffffff20", - "errorForeground": "#fe4450", - "textLink.activeForeground": "#ff7edb", - "textLink.foreground": "#f97e72", - "button.background": "#614D85", - "dropdown.background": "#232530", - "dropdown.listBackground": "#2a2139", - "input.background": "#2a2139", - "inputOption.activeBorder": "#ff7edb99", - "inputValidation.errorBackground": "#fe445080", - "inputValidation.errorBorder": "#fe445000", - "scrollbar.shadow": "#2a2139", - "scrollbarSlider.activeBackground": "#9d8bca20", - "scrollbarSlider.background": "#9d8bca30", - "scrollbarSlider.hoverBackground": "#9d8bca50", - "badge.foreground": "#ffffff", - "badge.background": "#2a2139", - "progressBar.background": "#f97e72", - "list.activeSelectionBackground": "#ffffff20", - "list.activeSelectionForeground": "#ffffff", - "list.dropBackground": "#34294f66", - "list.focusBackground": "#ffffff20", - "list.focusForeground": "#ffffff", - "list.highlightForeground": "#f97e72", - "list.hoverBackground": "#37294d99", - "list.hoverForeground": "#ffffff", - "list.inactiveSelectionBackground": "#ffffff20", - "list.inactiveSelectionForeground": "#ffffff", - "list.inactiveFocusBackground": "#2a213999", - "list.errorForeground": "#fe4450E6", - "list.warningForeground": "#72f1b8bb", - "activityBar.background": "#171520", - "activityBar.dropBackground": "#34294f66", - "activityBar.foreground": "#ffffffCC", - "activityBarBadge.background": "#f97e72", - "activityBarBadge.foreground": "#2a2139", - "sideBar.background": "#241b2f", - "sideBar.foreground": "#ffffff99", - "sideBar.dropBackground": "#34294f4c", - "sideBarSectionHeader.background": "#241b2f", - "sideBarSectionHeader.foreground": "#ffffffca", - "menu.background": "#463465", - "editorGroup.border": "#495495", - "editorGroup.dropBackground": "#4954954a", - "editorGroupHeader.tabsBackground": "#241b2f", - "tab.border": "#241b2f00", - "tab.activeBorder": "#880088", - "tab.inactiveBackground": "#262335", - "editor.background": "#262335", - "editorLineNumber.foreground": "#ffffff73", - "editorLineNumber.activeForeground": "#ffffffcc", - "editorCursor.background": "#241b2f", - "editorCursor.foreground": "#f97e72", - "editor.selectionBackground": "#ffffff20", - "editor.selectionHighlightBackground": "#ffffff20", - "editor.wordHighlightBackground": "#34294f88", - "editor.wordHighlightStrongBackground": "#34294f88", - "editor.findMatchBackground": "#D18616bb", - "editor.findMatchHighlightBackground": "#D1861655", - "editor.findRangeHighlightBackground": "#34294f1a", - "editor.hoverHighlightBackground": "#463564", - "editor.lineHighlightBorder": "#7059AB66", - "editor.rangeHighlightBackground": "#49549539", - "editorIndentGuide.background": "#444251", - "editorIndentGuide.activeBackground": "#A148AB80", - "editorRuler.foreground": "#A148AB80", - "editorCodeLens.foreground": "#ffffff7c", - "editorBracketMatch.background": "#34294f66", - "editorBracketMatch.border": "#495495", - "editorOverviewRuler.border": "#34294fb3", - "editorOverviewRuler.findMatchForeground": "#D1861699", - "editorOverviewRuler.modifiedForeground": "#b893ce99", - "editorOverviewRuler.addedForeground": "#09f7a099", - "editorOverviewRuler.deletedForeground": "#fe445099", - "editorOverviewRuler.errorForeground": "#fe4450dd", - "editorOverviewRuler.warningForeground": "#72f1b8cc", - "editorError.foreground": "#fe4450", - "editorWarning.foreground": "#72f1b8cc", - "editorGutter.modifiedBackground": "#b893ce8f", - "editorGutter.addedBackground": "#206d4bd6", - "editorGutter.deletedBackground": "#fa2e46a4", - "diffEditor.insertedTextBackground": "#0beb9935", - "diffEditor.removedTextBackground": "#fe445035", - "editorWidget.background": "#171520DC", - "editorWidget.border": "#ffffff22", - "editorWidget.resizeBorder": "#ffffff44", - "editorSuggestWidget.highlightForeground": "#f97e72", - "editorSuggestWidget.selectedBackground": "#ffffff36", - "peekView.border": "#495495", - "peekViewEditor.background": "#232530", - "peekViewEditor.matchHighlightBackground": "#D18616bb", - "peekViewResult.background": "#232530", - "peekViewResult.matchHighlightBackground": "#D1861655", - "peekViewResult.selectionBackground": "#2a213980", - "peekViewTitle.background": "#232530", - "panelTitle.activeBorder": "#f97e72", - "statusBar.background": "#241b2f", - "statusBar.foreground": "#ffffff80", - "statusBar.debuggingBackground": "#f97e72", - "statusBar.debuggingForeground": "#08080f", - "statusBar.noFolderBackground": "#241b2f", - "statusBarItem.prominentBackground": "#2a2139", - "statusBarItem.prominentHoverBackground": "#34294f", - "titleBar.activeBackground": "#241b2f", - "titleBar.inactiveBackground": "#241b2f", - "extensionButton.prominentBackground": "#f97e72", - "extensionButton.prominentHoverBackground": "#ff7edb", - "pickerGroup.foreground": "#f97e72ea", - "terminal.foreground": "#ffffff", - "terminal.ansiBlue": "#03edf9", - "terminal.ansiBrightBlue": "#03edf9", - "terminal.ansiBrightCyan": "#03edf9", - "terminal.ansiBrightGreen": "#72f1b8", - "terminal.ansiBrightMagenta": "#ff7edb", - "terminal.ansiBrightRed": "#fe4450", - "terminal.ansiBrightYellow": "#fede5d", - "terminal.ansiCyan": "#03edf9", - "terminal.ansiGreen": "#72f1b8", - "terminal.ansiMagenta": "#ff7edb", - "terminal.ansiRed": "#fe4450", - "terminal.ansiYellow": "#f3e70f", - "terminal.selectionBackground": "#ffffff20", - "terminalCursor.background": "#ffffff", - "terminalCursor.foreground": "#03edf9", - "debugToolBar.background": "#463465", - "walkThrough.embeddedEditorBackground": "#232530", - "gitDecoration.modifiedResourceForeground": "#b893ceee", - "gitDecoration.deletedResourceForeground": "#fe4450", - "gitDecoration.addedResourceForeground": "#72f1b8cc", - "gitDecoration.untrackedResourceForeground": "#72f1b8", - "gitDecoration.ignoredResourceForeground": "#ffffff59", - "minimapGutter.addedBackground": "#09f7a099", - "minimapGutter.modifiedBackground": "#b893ce", - "minimapGutter.deletedBackground": "#fe4450", - "breadcrumbPicker.background": "#232530" + "name": "SynthWave 84", + "type": "dark", + "semanticHighlighting": true, + "colors": { + "focusBorder": "#1f212b", + "foreground": "#ffffff", + "widget.shadow": "#2a2139", + "selection.background": "#ffffff20", + "errorForeground": "#fe4450", + "textLink.activeForeground": "#ff7edb", + "textLink.foreground": "#f97e72", + "button.background": "#614D85", + "dropdown.background": "#232530", + "dropdown.listBackground": "#2a2139", + "input.background": "#2a2139", + "inputOption.activeBorder": "#ff7edb99", + "inputValidation.errorBackground": "#fe445080", + "inputValidation.errorBorder": "#fe445000", + "scrollbar.shadow": "#2a2139", + "scrollbarSlider.activeBackground": "#9d8bca20", + "scrollbarSlider.background": "#9d8bca30", + "scrollbarSlider.hoverBackground": "#9d8bca50", + "badge.foreground": "#ffffff", + "badge.background": "#2a2139", + "progressBar.background": "#f97e72", + "list.activeSelectionBackground": "#ffffff20", + "list.activeSelectionForeground": "#ffffff", + "list.dropBackground": "#34294f66", + "list.focusBackground": "#ffffff20", + "list.focusForeground": "#ffffff", + "list.highlightForeground": "#f97e72", + "list.hoverBackground": "#37294d99", + "list.hoverForeground": "#ffffff", + "list.inactiveSelectionBackground": "#ffffff20", + "list.inactiveSelectionForeground": "#ffffff", + "list.inactiveFocusBackground": "#2a213999", + "list.errorForeground": "#fe4450E6", + "list.warningForeground": "#72f1b8bb", + "activityBar.background": "#171520", + "activityBar.dropBackground": "#34294f66", + "activityBar.foreground": "#ffffffCC", + "activityBarBadge.background": "#f97e72", + "activityBarBadge.foreground": "#2a2139", + "sideBar.background": "#241b2f", + "sideBar.foreground": "#ffffff99", + "sideBar.dropBackground": "#34294f4c", + "sideBarSectionHeader.background": "#241b2f", + "sideBarSectionHeader.foreground": "#ffffffca", + "menu.background": "#463465", + "editorGroup.border": "#495495", + "editorGroup.dropBackground": "#4954954a", + "editorGroupHeader.tabsBackground": "#241b2f", + "tab.border": "#241b2f00", + "tab.activeBorder": "#880088", + "tab.inactiveBackground": "#262335", + "editor.background": "#262335", + "editorLineNumber.foreground": "#ffffff73", + "editorLineNumber.activeForeground": "#ffffffcc", + "editorCursor.background": "#241b2f", + "editorCursor.foreground": "#f97e72", + "editor.selectionBackground": "#ffffff20", + "editor.selectionHighlightBackground": "#ffffff20", + "editor.wordHighlightBackground": "#34294f88", + "editor.wordHighlightStrongBackground": "#34294f88", + "editor.findMatchBackground": "#D18616bb", + "editor.findMatchHighlightBackground": "#D1861655", + "editor.findRangeHighlightBackground": "#34294f1a", + "editor.hoverHighlightBackground": "#463564", + "editor.lineHighlightBorder": "#7059AB66", + "editor.rangeHighlightBackground": "#49549539", + "editorIndentGuide.background": "#444251", + "editorIndentGuide.activeBackground": "#A148AB80", + "editorRuler.foreground": "#A148AB80", + "editorCodeLens.foreground": "#ffffff7c", + "editorBracketMatch.background": "#34294f66", + "editorBracketMatch.border": "#495495", + "editorOverviewRuler.border": "#34294fb3", + "editorOverviewRuler.findMatchForeground": "#D1861699", + "editorOverviewRuler.modifiedForeground": "#b893ce99", + "editorOverviewRuler.addedForeground": "#09f7a099", + "editorOverviewRuler.deletedForeground": "#fe445099", + "editorOverviewRuler.errorForeground": "#fe4450dd", + "editorOverviewRuler.warningForeground": "#72f1b8cc", + "editorError.foreground": "#fe4450", + "editorWarning.foreground": "#72f1b8cc", + "editorGutter.modifiedBackground": "#b893ce8f", + "editorGutter.addedBackground": "#206d4bd6", + "editorGutter.deletedBackground": "#fa2e46a4", + "diffEditor.insertedTextBackground": "#0beb9935", + "diffEditor.removedTextBackground": "#fe445035", + "editorWidget.background": "#171520DC", + "editorWidget.border": "#ffffff22", + "editorWidget.resizeBorder": "#ffffff44", + "editorSuggestWidget.highlightForeground": "#f97e72", + "editorSuggestWidget.selectedBackground": "#ffffff36", + "peekView.border": "#495495", + "peekViewEditor.background": "#232530", + "peekViewEditor.matchHighlightBackground": "#D18616bb", + "peekViewResult.background": "#232530", + "peekViewResult.matchHighlightBackground": "#D1861655", + "peekViewResult.selectionBackground": "#2a213980", + "peekViewTitle.background": "#232530", + "panelTitle.activeBorder": "#f97e72", + "statusBar.background": "#241b2f", + "statusBar.foreground": "#ffffff80", + "statusBar.debuggingBackground": "#f97e72", + "statusBar.debuggingForeground": "#08080f", + "statusBar.noFolderBackground": "#241b2f", + "statusBarItem.prominentBackground": "#2a2139", + "statusBarItem.prominentHoverBackground": "#34294f", + "titleBar.activeBackground": "#241b2f", + "titleBar.inactiveBackground": "#241b2f", + "extensionButton.prominentBackground": "#f97e72", + "extensionButton.prominentHoverBackground": "#ff7edb", + "pickerGroup.foreground": "#f97e72ea", + "terminal.foreground": "#ffffff", + "terminal.ansiBlue": "#03edf9", + "terminal.ansiBrightBlue": "#03edf9", + "terminal.ansiBrightCyan": "#03edf9", + "terminal.ansiBrightGreen": "#72f1b8", + "terminal.ansiBrightMagenta": "#ff7edb", + "terminal.ansiBrightRed": "#fe4450", + "terminal.ansiBrightYellow": "#fede5d", + "terminal.ansiCyan": "#03edf9", + "terminal.ansiGreen": "#72f1b8", + "terminal.ansiMagenta": "#ff7edb", + "terminal.ansiRed": "#fe4450", + "terminal.ansiYellow": "#f3e70f", + "terminal.selectionBackground": "#ffffff20", + "terminalCursor.background": "#ffffff", + "terminalCursor.foreground": "#03edf9", + "debugToolBar.background": "#463465", + "walkThrough.embeddedEditorBackground": "#232530", + "gitDecoration.modifiedResourceForeground": "#b893ceee", + "gitDecoration.deletedResourceForeground": "#fe4450", + "gitDecoration.addedResourceForeground": "#72f1b8cc", + "gitDecoration.untrackedResourceForeground": "#72f1b8", + "gitDecoration.ignoredResourceForeground": "#ffffff59", + "minimapGutter.addedBackground": "#09f7a099", + "minimapGutter.modifiedBackground": "#b893ce", + "minimapGutter.deletedBackground": "#fe4450", + "breadcrumbPicker.background": "#232530" + }, + "tokenColors": [ + { + "name": "Comment", + "scope": [ + "comment", + "string.quoted.docstring.multi.python", + "string.quoted.docstring.multi.python punctuation.definition.string.begin.python", + "string.quoted.docstring.multi.python punctuation.definition.string.end.python" + ], + "settings": { + "foreground": "#848bbd", + "fontStyle": "italic" + } }, - "tokenColors": [ - { - "name": "Comment", - "scope": [ - "comment", - "string.quoted.docstring.multi.python", - "string.quoted.docstring.multi.python punctuation.definition.string.begin.python", - "string.quoted.docstring.multi.python punctuation.definition.string.end.python" - ], - "settings": { - "foreground": "#848bbd", - "fontStyle": "italic" - } - }, - { - "name": "String", - "scope": ["string.quoted", "string.template", "punctuation.definition.string"], - "settings": { - "foreground": "#ff8b39" - } - }, - { - "name": "Punctuation within templates", - "scope": "string.template meta.embedded.line", - "settings": { - "foreground": "#b6b1b1" - } - }, - { - "name": "Variable", - "scope": ["variable", "entity.name.variable"], - "settings": { - "foreground": "#ff7edb" - } - }, - { - "name": "Language variable", - "scope": "variable.language", - "settings": { - "foreground": "#fe4450", - "fontStyle": "bold" - } - }, - { - "name": "Parameter", - "scope": "variable.parameter", - "settings": { - "fontStyle": "italic" - } - }, - { - "name": "Storage (declaration or modifier keyword)", - "scope": ["storage.type", "storage.modifier"], - "settings": { - "foreground": "#fede5d" - } - }, - { - "name": "Constant", - "scope": "constant", - "settings": { - "foreground": "#f97e72" - } - }, - { - "name": "Regex", - "scope": "string.regexp", - "settings": { - "foreground": "#f97e72" - } - }, - { - "name": "Number", - "scope": "constant.numeric", - "settings": { - "foreground": "#f97e72" - } - }, - { - "name": "Language constant (boolean, null)", - "scope": "constant.language", - "settings": { - "foreground": "#f97e72" - } - }, - { - "name": "Character escape", - "scope": "constant.character.escape", - "settings": { - "foreground": "#36f9f6" - } - }, - { - "name": "Entity", - "scope": "entity.name", - "settings": { - "foreground": "#fe4450" - } - }, - { - "name": "HTML or XML tag", - "scope": "entity.name.tag", - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "HTML or XML tag brackets", - "scope": ["punctuation.definition.tag"], - "settings": { - "foreground": "#36f9f6" - } - }, - { - "name": "Tag attribute", - "scope": "entity.other.attribute-name", - "settings": { - "foreground": "#fede5d" - } - }, - { - "name": "Tag attribute HTML", - "scope": "entity.other.attribute-name.html", - "settings": { - "foreground": "#fede5d", - "fontStyle": "italic" - } - }, - { - "name": "Class", - "scope": ["entity.name.type", "meta.attribute.class.html"], - "settings": { - "foreground": "#fe4450" - } - }, - { - "name": "Inherited class", - "scope": "entity.other.inherited-class", - "settings": { - "foreground": "#D50" - } - }, - { - "name": "Function", - "scope": ["entity.name.function", "variable.function"], - "settings": { - "foreground": "#36f9f6" - } - }, - { - "name": "JS Export", - "scope": ["keyword.control.export.js", "keyword.control.import.js"], - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "JS Numerics", - "scope": ["constant.numeric.decimal.js"], - "settings": { - "foreground": "#2EE2FA" - } - }, - { - "name": "Keyword", - "scope": "keyword", - "settings": { - "foreground": "#fede5d" - } - }, - { - "name": "Control keyword", - "scope": "keyword.control", - "settings": { - "foreground": "#fede5d" - } - }, - { - "name": "Operator", - "scope": "keyword.operator", - "settings": { - "foreground": "#fede5d" - } - }, - { - "name": "Special operator", - "scope": [ - "keyword.operator.new", - "keyword.operator.expression", - "keyword.operator.logical" - ], - "settings": { - "foreground": "#fede5d" - } - }, - { - "name": "Unit", - "scope": "keyword.other.unit", - "settings": { - "foreground": "#f97e72" - } - }, - { - "name": "Support", - "scope": "support", - "settings": { - "foreground": "#fe4450" - } - }, - { - "name": "Support function", - "scope": "support.function", - "settings": { - "foreground": "#36f9f6" - } - }, - { - "name": "Support variable", - "scope": "support.variable", - "settings": { - "foreground": "#ff7edb" - } - }, - { - "name": "Object literal key / property", - "scope": ["meta.object-literal.key", "support.type.property-name"], - "settings": { - "foreground": "#ff7edb" - } - }, - { - "name": "Key-value separator", - "scope": "punctuation.separator.key-value", - "settings": { - "foreground": "#b6b1b1" - } - }, - { - "name": "Embedded punctuation", - "scope": "punctuation.section.embedded", - "settings": { - "foreground": "#fede5d" - } - }, - { - "name": "Template expression", - "scope": [ - "punctuation.definition.template-expression.begin", - "punctuation.definition.template-expression.end" - ], - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "CSS property", - "scope": ["support.type.property-name.css", "support.type.property-name.json"], - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "JS Switch control", - "scope": "switch-block.expr.js", - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "JS object path", - "scope": "variable.other.constant.property.js, variable.other.property.js", - "settings": { - "foreground": "#2ee2fa" - } - }, - { - "name": "Color", - "scope": "constant.other.color", - "settings": { - "foreground": "#f97e72" - } - }, - { - "name": "Font names", - "scope": "support.constant.font-name", - "settings": { - "foreground": "#f97e72" - } - }, - { - "name": "CSS #id", - "scope": "entity.other.attribute-name.id", - "settings": { - "foreground": "#36f9f6" - } - }, - { - "name": "Pseudo CSS", - "scope": [ - "entity.other.attribute-name.pseudo-element", - "entity.other.attribute-name.pseudo-class" - ], - "settings": { - "foreground": "#D50" - } - }, - { - "name": "CSS support functions (rgb)", - "scope": "support.function.misc.css", - "settings": { - "foreground": "#fe4450" - } - }, - { - "name": "Markup heading", - "scope": ["markup.heading", "entity.name.section"], - "settings": { - "foreground": "#ff7edb" - } - }, - { - "name": "Markup text", - "scope": ["text.html", "keyword.operator.assignment"], - "settings": { - "foreground": "#ffffffee" - } - }, - { - "name": "Markup quote", - "scope": "markup.quote", - "settings": { - "foreground": "#b6b1b1cc", - "fontStyle": "italic" - } - }, - { - "name": "Markup list", - "scope": "beginning.punctuation.definition.list", - "settings": { - "foreground": "#ff7edb" - } - }, - { - "name": "Markup link", - "scope": "markup.underline.link", - "settings": { - "foreground": "#D50" - } - }, - { - "name": "Markup link description", - "scope": "string.other.link.description", - "settings": { - "foreground": "#f97e72" - } - }, - { - "name": "Python function call", - "scope": "meta.function-call.generic.python", - "settings": { - "foreground": "#36f9f6" - } - }, - { - "name": "Python variable params", - "scope": "variable.parameter.function-call.python", - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "C# storage type", - "scope": "storage.type.cs", - "settings": { - "foreground": "#fe4450" - } - }, - { - "name": "C# local variable", - "scope": "entity.name.variable.local.cs", - "settings": { - "foreground": "#ff7edb" - } - }, - { - "name": "C# properties and fields", - "scope": ["entity.name.variable.field.cs", "entity.name.variable.property.cs"], - "settings": { - "foreground": "#ff7edb" - } - }, - { - "name": "C placeholder", - "scope": "constant.other.placeholder.c", - "settings": { - "foreground": "#72f1b8", - "fontStyle": "italic" - } - }, - { - "name": "C preprocessors", - "scope": ["keyword.control.directive.include.c", "keyword.control.directive.define.c"], - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "C storage modifier", - "scope": "storage.modifier.c", - "settings": { - "foreground": "#fe4450" - } - }, - { - "name": "C++ operators", - "scope": "source.cpp keyword.operator", - "settings": { - "foreground": "#fede5d" - } - }, - { - "name": "C++ placeholder", - "scope": "constant.other.placeholder.cpp", - "settings": { - "foreground": "#72f1b8", - "fontStyle": "italic" - } - }, - { - "name": "C++ include", - "scope": [ - "keyword.control.directive.include.cpp", - "keyword.control.directive.define.cpp" - ], - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "C++ constant modifier", - "scope": "storage.modifier.specifier.const.cpp", - "settings": { - "foreground": "#fe4450" - } - }, - { - "name": "Elixir Classes", - "scope": [ - "source.elixir support.type.elixir", - "source.elixir meta.module.elixir entity.name.class.elixir" - ], - "settings": { - "foreground": "#36f9f6" - } - }, - { - "name": "Elixir Functions", - "scope": "source.elixir entity.name.function", - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "Elixir Constants", - "scope": [ - "source.elixir constant.other.symbol.elixir", - "source.elixir constant.other.keywords.elixir" - ], - "settings": { - "foreground": "#36f9f6" - } - }, - { - "name": "Elixir String Punctuation", - "scope": "source.elixir punctuation.definition.string", - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "Elixir", - "scope": [ - "source.elixir variable.other.readwrite.module.elixir", - "source.elixir variable.other.readwrite.module.elixir punctuation.definition.variable.elixir" - ], - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "Elixir Binary Punctuation", - "scope": "source.elixir .punctuation.binary.elixir", - "settings": { - "foreground": "#ff7edb", - "fontStyle": "italic" - } - }, - { - "name": "Clojure Globals", - "scope": ["entity.global.clojure"], - "settings": { - "foreground": "#36f9f6", - "fontStyle": "bold" - } - }, - { - "name": "Clojure Storage", - "scope": ["storage.control.clojure"], - "settings": { - "foreground": "#36f9f6", - "fontStyle": "italic" - } - }, - { - "name": "Clojure Metadata", - "scope": ["meta.metadata.simple.clojure", "meta.metadata.map.clojure"], - "settings": { - "foreground": "#fe4450", - "fontStyle": "italic" - } - }, - { - "name": "Clojure Macros, Quoted", - "scope": ["meta.quoted-expression.clojure"], - "settings": { - "fontStyle": "italic" - } - }, - { - "name": "Clojure Symbols", - "scope": ["meta.symbol.clojure"], - "settings": { - "foreground": "#ff7edbff" - } - }, - { - "name": "Go basic", - "scope": "source.go", - "settings": { - "foreground": "#ff7edbff" - } - }, - { - "name": "Go Function Calls", - "scope": "source.go meta.function-call.go", - "settings": { - "foreground": "#36f9f6" - } - }, - { - "name": "Go Keywords", - "scope": [ - "source.go keyword.package.go", - "source.go keyword.import.go", - "source.go keyword.function.go", - "source.go keyword.type.go", - "source.go keyword.const.go", - "source.go keyword.var.go", - "source.go keyword.map.go", - "source.go keyword.channel.go", - "source.go keyword.control.go" - ], - "settings": { - "foreground": "#fede5d" - } - }, - { - "name": "Go interfaces", - "scope": [ - "source.go storage.type", - "source.go keyword.struct.go", - "source.go keyword.interface.go" - ], - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "Go Constants e.g. nil, string format (%s, %d, etc.)", - "scope": [ - "source.go constant.language.go", - "source.go constant.other.placeholder.go", - "source.go variable" - ], - "settings": { - "foreground": "#2EE2FA" - } - }, - { - "name": "Markdown links and image paths", - "scope": ["markup.underline.link.markdown", "markup.inline.raw.string.markdown"], - "settings": { - "foreground": "#72f1b8", - "fontStyle": "italic" - } - }, - { - "name": "Markdown links and image paths", - "scope": ["string.other.link.title.markdown"], - "settings": { - "foreground": "#fede5d" - } - }, - { - "name": "Markdown headings", - "scope": ["markup.heading.markdown", "entity.name.section.markdown"], - "settings": { - "foreground": "#ff7edb", - "fontStyle": "bold" - } - }, - { - "name": "Markdown italic", - "scope": ["markup.italic.markdown"], - "settings": { - "foreground": "#2EE2FA", - "fontStyle": "italic" - } - }, - { - "name": "Markdown bold", - "scope": ["markup.bold.markdown"], - "settings": { - "foreground": "#2EE2FA", - "fontStyle": "bold" - } - }, - { - "name": "Markdown quotes", - "scope": ["punctuation.definition.quote.begin.markdown", "markup.quote.markdown"], - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "Basic source colours", - "scope": ["source.dart", "source.python", "source.scala"], - "settings": { - "foreground": "#ff7edbff" - } - }, - { - "name": "Dart strings", - "scope": ["string.interpolated.single.dart"], - "settings": { - "foreground": "#f97e72" - } - }, - { - "name": "Dart variable params", - "scope": ["variable.parameter.dart"], - "settings": { - "foreground": "#72f1b8" - } - }, - { - "name": "Dart numerics", - "scope": ["constant.numeric.dart"], - "settings": { - "foreground": "#2EE2FA" - } - }, - { - "name": "Scala variable params", - "scope": ["variable.parameter.scala"], - "settings": { - "foreground": "#2EE2FA" - } - }, - { - "name": "Scala", - "scope": ["meta.template.expression.scala"], - "settings": { - "foreground": "#72f1b8" - } - } - ] + { + "name": "String", + "scope": [ + "string.quoted", + "string.template", + "punctuation.definition.string" + ], + "settings": { + "foreground": "#ff8b39" + } + }, + { + "name": "Punctuation within templates", + "scope": "string.template meta.embedded.line", + "settings": { + "foreground": "#b6b1b1" + } + }, + { + "name": "Variable", + "scope": ["variable", "entity.name.variable"], + "settings": { + "foreground": "#ff7edb" + } + }, + { + "name": "Language variable", + "scope": "variable.language", + "settings": { + "foreground": "#fe4450", + "fontStyle": "bold" + } + }, + { + "name": "Parameter", + "scope": "variable.parameter", + "settings": { + "fontStyle": "italic" + } + }, + { + "name": "Storage (declaration or modifier keyword)", + "scope": ["storage.type", "storage.modifier"], + "settings": { + "foreground": "#fede5d" + } + }, + { + "name": "Constant", + "scope": "constant", + "settings": { + "foreground": "#f97e72" + } + }, + { + "name": "Regex", + "scope": "string.regexp", + "settings": { + "foreground": "#f97e72" + } + }, + { + "name": "Number", + "scope": "constant.numeric", + "settings": { + "foreground": "#f97e72" + } + }, + { + "name": "Language constant (boolean, null)", + "scope": "constant.language", + "settings": { + "foreground": "#f97e72" + } + }, + { + "name": "Character escape", + "scope": "constant.character.escape", + "settings": { + "foreground": "#36f9f6" + } + }, + { + "name": "Entity", + "scope": "entity.name", + "settings": { + "foreground": "#fe4450" + } + }, + { + "name": "HTML or XML tag", + "scope": "entity.name.tag", + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "HTML or XML tag brackets", + "scope": ["punctuation.definition.tag"], + "settings": { + "foreground": "#36f9f6" + } + }, + { + "name": "Tag attribute", + "scope": "entity.other.attribute-name", + "settings": { + "foreground": "#fede5d" + } + }, + { + "name": "Tag attribute HTML", + "scope": "entity.other.attribute-name.html", + "settings": { + "foreground": "#fede5d", + "fontStyle": "italic" + } + }, + { + "name": "Class", + "scope": ["entity.name.type", "meta.attribute.class.html"], + "settings": { + "foreground": "#fe4450" + } + }, + { + "name": "Inherited class", + "scope": "entity.other.inherited-class", + "settings": { + "foreground": "#D50" + } + }, + { + "name": "Function", + "scope": ["entity.name.function", "variable.function"], + "settings": { + "foreground": "#36f9f6" + } + }, + { + "name": "JS Export", + "scope": ["keyword.control.export.js", "keyword.control.import.js"], + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "JS Numerics", + "scope": ["constant.numeric.decimal.js"], + "settings": { + "foreground": "#2EE2FA" + } + }, + { + "name": "Keyword", + "scope": "keyword", + "settings": { + "foreground": "#fede5d" + } + }, + { + "name": "Control keyword", + "scope": "keyword.control", + "settings": { + "foreground": "#fede5d" + } + }, + { + "name": "Operator", + "scope": "keyword.operator", + "settings": { + "foreground": "#fede5d" + } + }, + { + "name": "Special operator", + "scope": [ + "keyword.operator.new", + "keyword.operator.expression", + "keyword.operator.logical" + ], + "settings": { + "foreground": "#fede5d" + } + }, + { + "name": "Unit", + "scope": "keyword.other.unit", + "settings": { + "foreground": "#f97e72" + } + }, + { + "name": "Support", + "scope": "support", + "settings": { + "foreground": "#fe4450" + } + }, + { + "name": "Support function", + "scope": "support.function", + "settings": { + "foreground": "#36f9f6" + } + }, + { + "name": "Support variable", + "scope": "support.variable", + "settings": { + "foreground": "#ff7edb" + } + }, + { + "name": "Object literal key / property", + "scope": ["meta.object-literal.key", "support.type.property-name"], + "settings": { + "foreground": "#ff7edb" + } + }, + { + "name": "Key-value separator", + "scope": "punctuation.separator.key-value", + "settings": { + "foreground": "#b6b1b1" + } + }, + { + "name": "Embedded punctuation", + "scope": "punctuation.section.embedded", + "settings": { + "foreground": "#fede5d" + } + }, + { + "name": "Template expression", + "scope": [ + "punctuation.definition.template-expression.begin", + "punctuation.definition.template-expression.end" + ], + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "CSS property", + "scope": [ + "support.type.property-name.css", + "support.type.property-name.json" + ], + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "JS Switch control", + "scope": "switch-block.expr.js", + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "JS object path", + "scope": "variable.other.constant.property.js, variable.other.property.js", + "settings": { + "foreground": "#2ee2fa" + } + }, + { + "name": "Color", + "scope": "constant.other.color", + "settings": { + "foreground": "#f97e72" + } + }, + { + "name": "Font names", + "scope": "support.constant.font-name", + "settings": { + "foreground": "#f97e72" + } + }, + { + "name": "CSS #id", + "scope": "entity.other.attribute-name.id", + "settings": { + "foreground": "#36f9f6" + } + }, + { + "name": "Pseudo CSS", + "scope": [ + "entity.other.attribute-name.pseudo-element", + "entity.other.attribute-name.pseudo-class" + ], + "settings": { + "foreground": "#D50" + } + }, + { + "name": "CSS support functions (rgb)", + "scope": "support.function.misc.css", + "settings": { + "foreground": "#fe4450" + } + }, + { + "name": "Markup heading", + "scope": ["markup.heading", "entity.name.section"], + "settings": { + "foreground": "#ff7edb" + } + }, + { + "name": "Markup text", + "scope": ["text.html", "keyword.operator.assignment"], + "settings": { + "foreground": "#ffffffee" + } + }, + { + "name": "Markup quote", + "scope": "markup.quote", + "settings": { + "foreground": "#b6b1b1cc", + "fontStyle": "italic" + } + }, + { + "name": "Markup list", + "scope": "beginning.punctuation.definition.list", + "settings": { + "foreground": "#ff7edb" + } + }, + { + "name": "Markup link", + "scope": "markup.underline.link", + "settings": { + "foreground": "#D50" + } + }, + { + "name": "Markup link description", + "scope": "string.other.link.description", + "settings": { + "foreground": "#f97e72" + } + }, + { + "name": "Python function call", + "scope": "meta.function-call.generic.python", + "settings": { + "foreground": "#36f9f6" + } + }, + { + "name": "Python variable params", + "scope": "variable.parameter.function-call.python", + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "C# storage type", + "scope": "storage.type.cs", + "settings": { + "foreground": "#fe4450" + } + }, + { + "name": "C# local variable", + "scope": "entity.name.variable.local.cs", + "settings": { + "foreground": "#ff7edb" + } + }, + { + "name": "C# properties and fields", + "scope": [ + "entity.name.variable.field.cs", + "entity.name.variable.property.cs" + ], + "settings": { + "foreground": "#ff7edb" + } + }, + { + "name": "C placeholder", + "scope": "constant.other.placeholder.c", + "settings": { + "foreground": "#72f1b8", + "fontStyle": "italic" + } + }, + { + "name": "C preprocessors", + "scope": [ + "keyword.control.directive.include.c", + "keyword.control.directive.define.c" + ], + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "C storage modifier", + "scope": "storage.modifier.c", + "settings": { + "foreground": "#fe4450" + } + }, + { + "name": "C++ operators", + "scope": "source.cpp keyword.operator", + "settings": { + "foreground": "#fede5d" + } + }, + { + "name": "C++ placeholder", + "scope": "constant.other.placeholder.cpp", + "settings": { + "foreground": "#72f1b8", + "fontStyle": "italic" + } + }, + { + "name": "C++ include", + "scope": [ + "keyword.control.directive.include.cpp", + "keyword.control.directive.define.cpp" + ], + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "C++ constant modifier", + "scope": "storage.modifier.specifier.const.cpp", + "settings": { + "foreground": "#fe4450" + } + }, + { + "name": "Elixir Classes", + "scope": [ + "source.elixir support.type.elixir", + "source.elixir meta.module.elixir entity.name.class.elixir" + ], + "settings": { + "foreground": "#36f9f6" + } + }, + { + "name": "Elixir Functions", + "scope": "source.elixir entity.name.function", + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "Elixir Constants", + "scope": [ + "source.elixir constant.other.symbol.elixir", + "source.elixir constant.other.keywords.elixir" + ], + "settings": { + "foreground": "#36f9f6" + } + }, + { + "name": "Elixir String Punctuation", + "scope": "source.elixir punctuation.definition.string", + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "Elixir", + "scope": [ + "source.elixir variable.other.readwrite.module.elixir", + "source.elixir variable.other.readwrite.module.elixir punctuation.definition.variable.elixir" + ], + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "Elixir Binary Punctuation", + "scope": "source.elixir .punctuation.binary.elixir", + "settings": { + "foreground": "#ff7edb", + "fontStyle": "italic" + } + }, + { + "name": "Clojure Globals", + "scope": ["entity.global.clojure"], + "settings": { + "foreground": "#36f9f6", + "fontStyle": "bold" + } + }, + { + "name": "Clojure Storage", + "scope": ["storage.control.clojure"], + "settings": { + "foreground": "#36f9f6", + "fontStyle": "italic" + } + }, + { + "name": "Clojure Metadata", + "scope": ["meta.metadata.simple.clojure", "meta.metadata.map.clojure"], + "settings": { + "foreground": "#fe4450", + "fontStyle": "italic" + } + }, + { + "name": "Clojure Macros, Quoted", + "scope": ["meta.quoted-expression.clojure"], + "settings": { + "fontStyle": "italic" + } + }, + { + "name": "Clojure Symbols", + "scope": ["meta.symbol.clojure"], + "settings": { + "foreground": "#ff7edbff" + } + }, + { + "name": "Go basic", + "scope": "source.go", + "settings": { + "foreground": "#ff7edbff" + } + }, + { + "name": "Go Function Calls", + "scope": "source.go meta.function-call.go", + "settings": { + "foreground": "#36f9f6" + } + }, + { + "name": "Go Keywords", + "scope": [ + "source.go keyword.package.go", + "source.go keyword.import.go", + "source.go keyword.function.go", + "source.go keyword.type.go", + "source.go keyword.const.go", + "source.go keyword.var.go", + "source.go keyword.map.go", + "source.go keyword.channel.go", + "source.go keyword.control.go" + ], + "settings": { + "foreground": "#fede5d" + } + }, + { + "name": "Go interfaces", + "scope": [ + "source.go storage.type", + "source.go keyword.struct.go", + "source.go keyword.interface.go" + ], + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "Go Constants e.g. nil, string format (%s, %d, etc.)", + "scope": [ + "source.go constant.language.go", + "source.go constant.other.placeholder.go", + "source.go variable" + ], + "settings": { + "foreground": "#2EE2FA" + } + }, + { + "name": "Markdown links and image paths", + "scope": [ + "markup.underline.link.markdown", + "markup.inline.raw.string.markdown" + ], + "settings": { + "foreground": "#72f1b8", + "fontStyle": "italic" + } + }, + { + "name": "Markdown links and image paths", + "scope": ["string.other.link.title.markdown"], + "settings": { + "foreground": "#fede5d" + } + }, + { + "name": "Markdown headings", + "scope": ["markup.heading.markdown", "entity.name.section.markdown"], + "settings": { + "foreground": "#ff7edb", + "fontStyle": "bold" + } + }, + { + "name": "Markdown italic", + "scope": ["markup.italic.markdown"], + "settings": { + "foreground": "#2EE2FA", + "fontStyle": "italic" + } + }, + { + "name": "Markdown bold", + "scope": ["markup.bold.markdown"], + "settings": { + "foreground": "#2EE2FA", + "fontStyle": "bold" + } + }, + { + "name": "Markdown quotes", + "scope": [ + "punctuation.definition.quote.begin.markdown", + "markup.quote.markdown" + ], + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "Basic source colours", + "scope": ["source.dart", "source.python", "source.scala"], + "settings": { + "foreground": "#ff7edbff" + } + }, + { + "name": "Dart strings", + "scope": ["string.interpolated.single.dart"], + "settings": { + "foreground": "#f97e72" + } + }, + { + "name": "Dart variable params", + "scope": ["variable.parameter.dart"], + "settings": { + "foreground": "#72f1b8" + } + }, + { + "name": "Dart numerics", + "scope": ["constant.numeric.dart"], + "settings": { + "foreground": "#2EE2FA" + } + }, + { + "name": "Scala variable params", + "scope": ["variable.parameter.scala"], + "settings": { + "foreground": "#2EE2FA" + } + }, + { + "name": "Scala", + "scope": ["meta.template.expression.scala"], + "settings": { + "foreground": "#72f1b8" + } + } + ] } diff --git a/crates/command_palette2/Cargo.toml b/crates/command_palette2/Cargo.toml new file mode 100644 index 0000000000..bcc0099c20 --- /dev/null +++ b/crates/command_palette2/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "command_palette2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/command_palette.rs" +doctest = false + +[dependencies] +collections = { path = "../collections" } +editor = { package = "editor2", path = "../editor2" } +fuzzy = { package = "fuzzy2", path = "../fuzzy2" } +gpui = { package = "gpui2", path = "../gpui2" } +picker = { package = "picker2", path = "../picker2" } +project = { package = "project2", path = "../project2" } +settings = { package = "settings2", path = "../settings2" } +ui = { package = "ui2", path = "../ui2" } +util = { path = "../util" } +theme = { package = "theme2", path = "../theme2" } +workspace = { package="workspace2", path = "../workspace2" } +zed_actions = { package = "zed_actions2", path = "../zed_actions2" } +anyhow.workspace = true +serde.workspace = true +[dev-dependencies] +gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } +editor = { package = "editor2", path = "../editor2", features = ["test-support"] } +language = { package="language2", path = "../language2", features = ["test-support"] } +project = { package="project2", path = "../project2", features = ["test-support"] } +serde_json.workspace = true +workspace = { package="workspace2", path = "../workspace2", features = ["test-support"] } +ctor.workspace = true +env_logger.workspace = true diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs new file mode 100644 index 0000000000..abba09519b --- /dev/null +++ b/crates/command_palette2/src/command_palette.rs @@ -0,0 +1,534 @@ +use collections::{CommandPaletteFilter, HashMap}; +use fuzzy::{StringMatch, StringMatchCandidate}; +use gpui::{ + actions, div, Action, AppContext, Component, Div, EventEmitter, FocusHandle, Keystroke, + ParentElement, Render, StatelessInteractive, Styled, View, ViewContext, VisualContext, + WeakView, WindowContext, +}; +use picker::{Picker, PickerDelegate}; +use std::cmp::{self, Reverse}; +use theme::ActiveTheme; +use ui::{modal, Label}; +use util::{ + channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, + ResultExt, +}; +use workspace::{Modal, ModalEvent, Workspace}; +use zed_actions::OpenZedURL; + +actions!(Toggle); + +pub fn init(cx: &mut AppContext) { + cx.set_global(HitCounts::default()); + cx.observe_new_views(CommandPalette::register).detach(); +} + +pub struct CommandPalette { + picker: View>, +} + +impl CommandPalette { + fn register(workspace: &mut Workspace, _: &mut ViewContext) { + workspace.register_action(|workspace, _: &Toggle, cx| { + let Some(previous_focus_handle) = cx.focused() else { + return; + }; + workspace.toggle_modal(cx, move |cx| CommandPalette::new(previous_focus_handle, cx)); + }); + } + + fn new(previous_focus_handle: FocusHandle, cx: &mut ViewContext) -> Self { + let filter = cx.try_global::(); + + let commands = cx + .available_actions() + .into_iter() + .filter_map(|action| { + let name = action.name(); + let namespace = name.split("::").next().unwrap_or("malformed action name"); + if filter.is_some_and(|f| f.filtered_namespaces.contains(namespace)) { + return None; + } + + Some(Command { + name: humanize_action_name(&name), + action, + keystrokes: vec![], // todo!() + }) + }) + .collect(); + + let delegate = + CommandPaletteDelegate::new(cx.view().downgrade(), commands, previous_focus_handle); + + let picker = cx.build_view(|cx| Picker::new(delegate, cx)); + Self { picker } + } +} + +impl EventEmitter for CommandPalette {} +impl Modal for CommandPalette { + fn focus(&self, cx: &mut WindowContext) { + self.picker.update(cx, |picker, cx| picker.focus(cx)); + } +} + +impl Render for CommandPalette { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + modal(cx).w_96().child(self.picker.clone()) + } +} + +pub type CommandPaletteInterceptor = + Box Option>; + +pub struct CommandInterceptResult { + pub action: Box, + pub string: String, + pub positions: Vec, +} + +pub struct CommandPaletteDelegate { + command_palette: WeakView, + commands: Vec, + matches: Vec, + selected_ix: usize, + previous_focus_handle: FocusHandle, +} + +struct Command { + name: String, + action: Box, + keystrokes: Vec, +} + +impl Clone for Command { + fn clone(&self) -> Self { + Self { + name: self.name.clone(), + action: self.action.boxed_clone(), + keystrokes: self.keystrokes.clone(), + } + } +} +/// Hit count for each command in the palette. +/// We only account for commands triggered directly via command palette and not by e.g. keystrokes because +/// if an user already knows a keystroke for a command, they are unlikely to use a command palette to look for it. +#[derive(Default)] +struct HitCounts(HashMap); + +impl CommandPaletteDelegate { + fn new( + command_palette: WeakView, + commands: Vec, + previous_focus_handle: FocusHandle, + ) -> Self { + Self { + command_palette, + matches: commands + .iter() + .enumerate() + .map(|(i, command)| StringMatch { + candidate_id: i, + string: command.name.clone(), + positions: Vec::new(), + score: 0.0, + }) + .collect(), + commands, + selected_ix: 0, + previous_focus_handle, + } + } +} + +impl PickerDelegate for CommandPaletteDelegate { + type ListItem = Div>; + + fn match_count(&self) -> usize { + self.matches.len() + } + + fn selected_index(&self) -> usize { + self.selected_ix + } + + fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext>) { + self.selected_ix = ix; + } + + fn update_matches( + &mut self, + query: String, + cx: &mut ViewContext>, + ) -> gpui::Task<()> { + let mut commands = self.commands.clone(); + + cx.spawn(move |picker, mut cx| async move { + cx.read_global::(|hit_counts, _| { + commands.sort_by_key(|action| { + ( + Reverse(hit_counts.0.get(&action.name).cloned()), + action.name.clone(), + ) + }); + }) + .ok(); + + let candidates = commands + .iter() + .enumerate() + .map(|(ix, command)| StringMatchCandidate { + id: ix, + string: command.name.to_string(), + char_bag: command.name.chars().collect(), + }) + .collect::>(); + let mut matches = if query.is_empty() { + candidates + .into_iter() + .enumerate() + .map(|(index, candidate)| StringMatch { + candidate_id: index, + string: candidate.string, + positions: Vec::new(), + score: 0.0, + }) + .collect() + } else { + fuzzy::match_strings( + &candidates, + &query, + true, + 10000, + &Default::default(), + cx.background_executor().clone(), + ) + .await + }; + + let mut intercept_result = cx + .try_read_global(|interceptor: &CommandPaletteInterceptor, cx| { + (interceptor)(&query, cx) + }) + .flatten(); + + if *RELEASE_CHANNEL == ReleaseChannel::Dev { + if parse_zed_link(&query).is_some() { + intercept_result = Some(CommandInterceptResult { + action: OpenZedURL { url: query.clone() }.boxed_clone(), + string: query.clone(), + positions: vec![], + }) + } + } + if let Some(CommandInterceptResult { + action, + string, + positions, + }) = intercept_result + { + if let Some(idx) = matches + .iter() + .position(|m| commands[m.candidate_id].action.type_id() == action.type_id()) + { + matches.remove(idx); + } + commands.push(Command { + name: string.clone(), + action, + keystrokes: vec![], + }); + matches.insert( + 0, + StringMatch { + candidate_id: commands.len() - 1, + string, + positions, + score: 0.0, + }, + ) + } + picker + .update(&mut cx, |picker, _| { + let delegate = &mut picker.delegate; + delegate.commands = commands; + delegate.matches = matches; + if delegate.matches.is_empty() { + delegate.selected_ix = 0; + } else { + delegate.selected_ix = + cmp::min(delegate.selected_ix, delegate.matches.len() - 1); + } + }) + .log_err(); + }) + } + + fn dismissed(&mut self, cx: &mut ViewContext>) { + self.command_palette + .update(cx, |_, cx| cx.emit(ModalEvent::Dismissed)) + .log_err(); + } + + fn confirm(&mut self, _: bool, cx: &mut ViewContext>) { + if self.matches.is_empty() { + self.dismissed(cx); + return; + } + let action_ix = self.matches[self.selected_ix].candidate_id; + let command = self.commands.swap_remove(action_ix); + cx.update_global(|hit_counts: &mut HitCounts, _| { + *hit_counts.0.entry(command.name).or_default() += 1; + }); + let action = command.action; + cx.focus(&self.previous_focus_handle); + cx.dispatch_action(action); + self.dismissed(cx); + } + + fn render_match( + &self, + ix: usize, + selected: bool, + cx: &mut ViewContext>, + ) -> Self::ListItem { + let colors = cx.theme().colors(); + let Some(command) = self + .matches + .get(ix) + .and_then(|m| self.commands.get(m.candidate_id)) + else { + return div(); + }; + + div() + .text_color(colors.text) + .when(selected, |s| { + s.border_l_10().border_color(colors.terminal_ansi_yellow) + }) + .hover(|style| { + style + .bg(colors.element_active) + .text_color(colors.text_accent) + }) + .child(Label::new(command.name.clone())) + } + + // fn render_match( + // &self, + // ix: usize, + // mouse_state: &mut MouseState, + // selected: bool, + // cx: &gpui::AppContext, + // ) -> AnyElement> { + // let mat = &self.matches[ix]; + // let command = &self.actions[mat.candidate_id]; + // let theme = theme::current(cx); + // let style = theme.picker.item.in_state(selected).style_for(mouse_state); + // let key_style = &theme.command_palette.key.in_state(selected); + // let keystroke_spacing = theme.command_palette.keystroke_spacing; + + // Flex::row() + // .with_child( + // Label::new(mat.string.clone(), style.label.clone()) + // .with_highlights(mat.positions.clone()), + // ) + // .with_children(command.keystrokes.iter().map(|keystroke| { + // Flex::row() + // .with_children( + // [ + // (keystroke.ctrl, "^"), + // (keystroke.alt, "⌥"), + // (keystroke.cmd, "⌘"), + // (keystroke.shift, "⇧"), + // ] + // .into_iter() + // .filter_map(|(modifier, label)| { + // if modifier { + // Some( + // Label::new(label, key_style.label.clone()) + // .contained() + // .with_style(key_style.container), + // ) + // } else { + // None + // } + // }), + // ) + // .with_child( + // Label::new(keystroke.key.clone(), key_style.label.clone()) + // .contained() + // .with_style(key_style.container), + // ) + // .contained() + // .with_margin_left(keystroke_spacing) + // .flex_float() + // })) + // .contained() + // .with_style(style.container) + // .into_any() + // } +} + +fn humanize_action_name(name: &str) -> String { + let capacity = name.len() + name.chars().filter(|c| c.is_uppercase()).count(); + let mut result = String::with_capacity(capacity); + for char in name.chars() { + if char == ':' { + if result.ends_with(':') { + result.push(' '); + } else { + result.push(':'); + } + } else if char == '_' { + result.push(' '); + } else if char.is_uppercase() { + if !result.ends_with(' ') { + result.push(' '); + } + result.extend(char.to_lowercase()); + } else { + result.push(char); + } + } + result +} + +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() + } +} + +// #[cfg(test)] +// mod tests { +// use std::sync::Arc; + +// use super::*; +// use editor::Editor; +// use gpui::{executor::Deterministic, TestAppContext}; +// use project::Project; +// use workspace::{AppState, Workspace}; + +// #[test] +// fn test_humanize_action_name() { +// assert_eq!( +// humanize_action_name("editor::GoToDefinition"), +// "editor: go to definition" +// ); +// assert_eq!( +// humanize_action_name("editor::Backspace"), +// "editor: backspace" +// ); +// assert_eq!( +// humanize_action_name("go_to_line::Deploy"), +// "go to line: deploy" +// ); +// } + +// #[gpui::test] +// async fn test_command_palette(deterministic: Arc, cx: &mut TestAppContext) { +// let app_state = init_test(cx); + +// let project = Project::test(app_state.fs.clone(), [], cx).await; +// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); +// let workspace = window.root(cx); +// let editor = window.add_view(cx, |cx| { +// let mut editor = Editor::single_line(None, cx); +// editor.set_text("abc", cx); +// editor +// }); + +// workspace.update(cx, |workspace, cx| { +// cx.focus(&editor); +// workspace.add_item(Box::new(editor.clone()), cx) +// }); + +// workspace.update(cx, |workspace, cx| { +// toggle_command_palette(workspace, &Toggle, cx); +// }); + +// let palette = workspace.read_with(cx, |workspace, _| { +// workspace.modal::().unwrap() +// }); + +// palette +// .update(cx, |palette, cx| { +// // Fill up palette's command list by running an empty query; +// // we only need it to subsequently assert that the palette is initially +// // sorted by command's name. +// palette.delegate_mut().update_matches("".to_string(), cx) +// }) +// .await; + +// palette.update(cx, |palette, _| { +// let is_sorted = +// |actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name); +// assert!(is_sorted(&palette.delegate().actions)); +// }); + +// palette +// .update(cx, |palette, cx| { +// palette +// .delegate_mut() +// .update_matches("bcksp".to_string(), cx) +// }) +// .await; + +// palette.update(cx, |palette, cx| { +// assert_eq!(palette.delegate().matches[0].string, "editor: backspace"); +// palette.confirm(&Default::default(), cx); +// }); +// deterministic.run_until_parked(); +// editor.read_with(cx, |editor, cx| { +// assert_eq!(editor.text(cx), "ab"); +// }); + +// // Add namespace filter, and redeploy the palette +// cx.update(|cx| { +// cx.update_default_global::(|filter, _| { +// filter.filtered_namespaces.insert("editor"); +// }) +// }); + +// workspace.update(cx, |workspace, cx| { +// toggle_command_palette(workspace, &Toggle, cx); +// }); + +// // Assert editor command not present +// let palette = workspace.read_with(cx, |workspace, _| { +// workspace.modal::().unwrap() +// }); + +// palette +// .update(cx, |palette, cx| { +// palette +// .delegate_mut() +// .update_matches("bcksp".to_string(), cx) +// }) +// .await; + +// palette.update(cx, |palette, _| { +// assert!(palette.delegate().matches.is_empty()) +// }); +// } + +// fn init_test(cx: &mut TestAppContext) -> Arc { +// cx.update(|cx| { +// let app_state = AppState::test(cx); +// theme::init(cx); +// language::init(cx); +// editor::init(cx); +// workspace::init(app_state.clone(), cx); +// init(cx); +// Project::init_settings(cx); +// app_state +// }) +// } +// } diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 75f8b800f9..b885e065a1 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -2,7 +2,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::Setting; -#[derive(Deserialize)] +#[derive(Clone, Deserialize)] pub struct EditorSettings { pub cursor_blink: bool, pub hover_popover_enabled: bool, @@ -11,6 +11,15 @@ pub struct EditorSettings { pub use_on_type_format: bool, pub scrollbar: Scrollbar, pub relative_line_numbers: bool, + pub seed_search_query_from_cursor: SeedQuerySetting, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SeedQuerySetting { + Always, + Selection, + Never, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -38,6 +47,7 @@ pub struct EditorSettingsContent { pub use_on_type_format: Option, pub scrollbar: Option, pub relative_line_numbers: Option, + pub seed_search_query_from_cursor: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 1b922848e0..4c45904c50 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1,7 +1,7 @@ use crate::{ - display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition, - movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, - Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, + editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition, + persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, EditorSettings, Event, + ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, }; use anyhow::{Context, Result}; use collections::HashSet; @@ -13,8 +13,8 @@ use gpui::{ ViewHandle, WeakViewHandle, }; use language::{ - proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, - SelectionGoal, + proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt, + Point, SelectionGoal, }; use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view, PeerId}; @@ -937,24 +937,28 @@ impl SearchableItem for Editor { } fn query_suggestion(&mut self, cx: &mut ViewContext) -> String { - let display_map = self.snapshot(cx).display_snapshot; + let setting = settings::get::(cx).seed_search_query_from_cursor; + let snapshot = &self.snapshot(cx).buffer_snapshot; let selection = self.selections.newest::(cx); - if selection.start == selection.end { - let point = selection.start.to_display_point(&display_map); - let range = surrounding_word(&display_map, point); - let range = range.start.to_offset(&display_map, Bias::Left) - ..range.end.to_offset(&display_map, Bias::Right); - let text: String = display_map.buffer_snapshot.text_for_range(range).collect(); - if text.trim().is_empty() { + + match setting { + SeedQuerySetting::Never => String::new(), + SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => { + snapshot + .text_for_range(selection.start..selection.end) + .collect() + } + SeedQuerySetting::Selection => String::new(), + SeedQuerySetting::Always => { + let (range, kind) = snapshot.surrounding_word(selection.start); + if kind == Some(CharKind::Word) { + let text: String = snapshot.text_for_range(range).collect(); + if !text.trim().is_empty() { + return text; + } + } String::new() - } else { - text } - } else { - display_map - .buffer_snapshot - .text_for_range(selection.start..selection.end) - .collect() } } diff --git a/crates/editor2/src/editor_settings.rs b/crates/editor2/src/editor_settings.rs index 45c797598f..fd7e2feea3 100644 --- a/crates/editor2/src/editor_settings.rs +++ b/crates/editor2/src/editor_settings.rs @@ -11,6 +11,15 @@ pub struct EditorSettings { pub use_on_type_format: bool, pub scrollbar: Scrollbar, pub relative_line_numbers: bool, + pub seed_search_query_from_cursor: SeedQuerySetting, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SeedQuerySetting { + Always, + Selection, + Never, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -38,6 +47,7 @@ pub struct EditorSettingsContent { pub use_on_type_format: Option, pub scrollbar: Option, pub relative_line_numbers: Option, + pub seed_search_query_from_cursor: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] diff --git a/crates/editor2/src/items.rs b/crates/editor2/src/items.rs index 1081a329c6..25e9f91608 100644 --- a/crates/editor2/src/items.rs +++ b/crates/editor2/src/items.rs @@ -1,7 +1,8 @@ use crate::{ - display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition, + editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition, movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, - Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, + EditorSettings, Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, + NavigationData, ToPoint as _, }; use anyhow::{anyhow, Context, Result}; use collections::HashSet; @@ -12,11 +13,12 @@ use gpui::{ ViewContext, VisualContext, WeakView, }; use language::{ - proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, - SelectionGoal, + proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt, + Point, SelectionGoal, }; use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view, PeerId}; +use settings::Settings; use smallvec::SmallVec; use std::{ borrow::Cow, @@ -918,24 +920,28 @@ impl SearchableItem for Editor { } fn query_suggestion(&mut self, cx: &mut ViewContext) -> String { - let display_map = self.snapshot(cx).display_snapshot; + let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor; + let snapshot = &self.snapshot(cx).buffer_snapshot; let selection = self.selections.newest::(cx); - if selection.start == selection.end { - let point = selection.start.to_display_point(&display_map); - let range = surrounding_word(&display_map, point); - let range = range.start.to_offset(&display_map, Bias::Left) - ..range.end.to_offset(&display_map, Bias::Right); - let text: String = display_map.buffer_snapshot.text_for_range(range).collect(); - if text.trim().is_empty() { + + match setting { + SeedQuerySetting::Never => String::new(), + SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => { + snapshot + .text_for_range(selection.start..selection.end) + .collect() + } + SeedQuerySetting::Selection => String::new(), + SeedQuerySetting::Always => { + let (range, kind) = snapshot.surrounding_word(selection.start); + if kind == Some(CharKind::Word) { + let text: String = snapshot.text_for_range(range).collect(); + if !text.trim().is_empty() { + return text; + } + } String::new() - } else { - text } - } else { - display_map - .buffer_snapshot - .text_for_range(selection.start..selection.end) - .collect() } } diff --git a/crates/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index c65373e6ac..9ec770e05c 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -8,25 +8,12 @@ use text::{Bias, Point}; use theme::ActiveTheme; use ui::{h_stack, modal, v_stack, Label, LabelColor}; use util::paths::FILE_ROW_COLUMN_DELIMITER; -use workspace::{ModalEvent, Workspace}; +use workspace::{Modal, ModalEvent, Workspace}; actions!(Toggle); pub fn init(cx: &mut AppContext) { - cx.observe_new_views( - |workspace: &mut Workspace, _: &mut ViewContext| { - workspace - .modal_layer() - .register_modal(Toggle, |workspace, cx| { - let editor = workspace - .active_item(cx) - .and_then(|active_item| active_item.downcast::())?; - - Some(cx.build_view(|cx| GoToLine::new(editor, cx))) - }); - }, - ) - .detach(); + cx.observe_new_views(GoToLine::register).detach(); } pub struct GoToLine { @@ -37,21 +24,29 @@ pub struct GoToLine { _subscriptions: Vec, } -pub enum Event { - Dismissed, +impl EventEmitter for GoToLine {} +impl Modal for GoToLine { + fn focus(&self, cx: &mut WindowContext) { + self.line_editor.update(cx, |editor, cx| editor.focus(cx)) + } } -impl EventEmitter for GoToLine {} - -impl EventEmitter for GoToLine {} - impl GoToLine { - pub fn new(active_editor: View, cx: &mut ViewContext) -> Self { - let line_editor = cx.build_view(|cx| { - let editor = Editor::single_line(cx); - editor.focus(cx); - editor + fn register(workspace: &mut Workspace, _: &mut ViewContext) { + workspace.register_action(|workspace, _: &Toggle, cx| { + let Some(editor) = workspace + .active_item(cx) + .and_then(|active_item| active_item.downcast::()) + else { + return; + }; + + workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx)); }); + } + + pub fn new(active_editor: View, cx: &mut ViewContext) -> Self { + let line_editor = cx.build_view(|cx| Editor::single_line(cx)); let line_editor_change = cx.subscribe(&line_editor, Self::on_line_editor_event); let editor = active_editor.read(cx); @@ -78,7 +73,6 @@ impl GoToLine { fn release(&mut self, cx: &mut WindowContext) { let scroll_position = self.prev_scroll_position.take(); self.active_editor.update(cx, |editor, cx| { - editor.focus(cx); editor.highlight_rows(None); if let Some(scroll_position) = scroll_position { editor.set_scroll_position(scroll_position, cx); @@ -95,7 +89,7 @@ impl GoToLine { ) { match event { // todo!() this isn't working... - editor::Event::Blurred => cx.emit(Event::Dismissed), + editor::Event::Blurred => cx.emit(ModalEvent::Dismissed), editor::Event::BufferEdited { .. } => self.highlight_current_line(cx), _ => {} } @@ -130,22 +124,24 @@ impl GoToLine { } fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { - cx.emit(Event::Dismissed); + cx.emit(ModalEvent::Dismissed); } fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { if let Some(point) = self.point_from_query(cx) { - self.active_editor.update(cx, |active_editor, cx| { - let snapshot = active_editor.snapshot(cx).display_snapshot; + self.active_editor.update(cx, |editor, cx| { + let snapshot = editor.snapshot(cx).display_snapshot; let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left); - active_editor.change_selections(Some(Autoscroll::center()), cx, |s| { + editor.change_selections(Some(Autoscroll::center()), cx, |s| { s.select_ranges([point..point]) }); + editor.focus(cx); + cx.notify(); }); self.prev_scroll_position.take(); } - cx.emit(Event::Dismissed); + cx.emit(ModalEvent::Dismissed); } } diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index 40b0682787..fe197af5d2 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -136,7 +136,7 @@ impl ToJson for RectF { } #[derive(Refineable, Debug)] -#[refineable(debug)] +#[refineable(Debug)] pub struct Point { pub x: T, pub y: T, @@ -161,7 +161,7 @@ impl Into> for Point { } #[derive(Refineable, Clone, Debug)] -#[refineable(debug)] +#[refineable(Debug)] pub struct Size { pub width: T, pub height: T, @@ -227,7 +227,7 @@ impl Size { } #[derive(Clone, Default, Refineable, Debug)] -#[refineable(debug)] +#[refineable(Debug)] pub struct Edges { pub top: T, pub right: T, diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index 85149f5d55..170ddf942f 100644 --- a/crates/gpui2/src/action.rs +++ b/crates/gpui2/src/action.rs @@ -4,7 +4,7 @@ use collections::{HashMap, HashSet}; use lazy_static::lazy_static; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; use serde::Deserialize; -use std::any::{type_name, Any}; +use std::any::{type_name, 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 @@ -100,6 +100,21 @@ where } } +impl dyn Action { + pub fn type_id(&self) -> TypeId { + self.as_any().type_id() + } + + pub fn name(&self) -> SharedString { + ACTION_REGISTRY + .read() + .names_by_type_id + .get(&self.type_id()) + .expect("type is not a registered action") + .clone() + } +} + type ActionBuilder = fn(json: Option) -> anyhow::Result>; lazy_static! { @@ -109,6 +124,7 @@ lazy_static! { #[derive(Default)] struct ActionRegistry { builders_by_name: HashMap, + names_by_type_id: HashMap, all_names: Vec, // So we can return a static slice. } @@ -117,9 +133,24 @@ pub fn register_action() { let name = A::qualified_name(); let mut lock = ACTION_REGISTRY.write(); lock.builders_by_name.insert(name.clone(), A::build); + lock.names_by_type_id + .insert(TypeId::of::(), name.clone()); lock.all_names.push(name); } +/// Construct an action based on its name and optional JSON parameters sourced from the keymap. +pub fn build_action_from_type(type_id: &TypeId) -> Result> { + let lock = ACTION_REGISTRY.read(); + let name = lock + .names_by_type_id + .get(type_id) + .ok_or_else(|| anyhow!("no action type registered for {:?}", type_id))? + .clone(); + drop(lock); + + build_action(&name, None) +} + /// Construct an action based on its name and optional JSON parameters sourced from the keymap. pub fn build_action(name: &str, params: Option) -> Result> { let lock = ACTION_REGISTRY.read(); diff --git a/crates/gpui2/src/color.rs b/crates/gpui2/src/color.rs index 3ae247962b..6fcb12e178 100644 --- a/crates/gpui2/src/color.rs +++ b/crates/gpui2/src/color.rs @@ -1,8 +1,8 @@ #![allow(dead_code)] +use anyhow::bail; use serde::de::{self, Deserialize, Deserializer, Visitor}; use std::fmt; -use std::num::ParseIntError; pub fn rgb>(hex: u32) -> C { let r = ((hex >> 16) & 0xFF) as f32 / 255.0; @@ -19,7 +19,7 @@ pub fn rgba(hex: u32) -> Rgba { Rgba { r, g, b, a } } -#[derive(Clone, Copy, Default)] +#[derive(PartialEq, Clone, Copy, Default)] pub struct Rgba { pub r: f32, pub g: f32, @@ -70,21 +70,7 @@ impl<'de> Visitor<'de> for RgbaVisitor { } fn visit_str(self, value: &str) -> Result { - if value.len() == 7 || value.len() == 9 { - let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.0; - let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.0; - let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.0; - let a = if value.len() == 9 { - u8::from_str_radix(&value[7..9], 16).unwrap() as f32 / 255.0 - } else { - 1.0 - }; - Ok(Rgba { r, g, b, a }) - } else { - Err(E::custom( - "Bad format for RGBA. Expected #rrggbb or #rrggbbaa.", - )) - } + Rgba::try_from(value).map_err(E::custom) } } @@ -125,19 +111,59 @@ impl From for Rgba { } impl TryFrom<&'_ str> for Rgba { - type Error = ParseIntError; + type Error = anyhow::Error; fn try_from(value: &'_ str) -> Result { - let r = u8::from_str_radix(&value[1..3], 16)? as f32 / 255.0; - let g = u8::from_str_radix(&value[3..5], 16)? as f32 / 255.0; - let b = u8::from_str_radix(&value[5..7], 16)? as f32 / 255.0; - let a = if value.len() > 7 { - u8::from_str_radix(&value[7..9], 16)? as f32 / 255.0 - } else { - 1.0 + const RGB: usize = "rgb".len(); + const RGBA: usize = "rgba".len(); + const RRGGBB: usize = "rrggbb".len(); + const RRGGBBAA: usize = "rrggbbaa".len(); + + const EXPECTED_FORMATS: &'static str = "Expected #rgb, #rgba, #rrggbb, or #rrggbbaa"; + + let Some(("", hex)) = value.trim().split_once('#') else { + bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}"); }; - Ok(Rgba { r, g, b, a }) + let (r, g, b, a) = match hex.len() { + RGB | RGBA => { + let r = u8::from_str_radix(&hex[0..1], 16)?; + let g = u8::from_str_radix(&hex[1..2], 16)?; + let b = u8::from_str_radix(&hex[2..3], 16)?; + let a = if hex.len() == RGBA { + u8::from_str_radix(&hex[3..4], 16)? + } else { + 0xf + }; + + /// Duplicates a given hex digit. + /// E.g., `0xf` -> `0xff`. + const fn duplicate(value: u8) -> u8 { + value << 4 | value + } + + (duplicate(r), duplicate(g), duplicate(b), duplicate(a)) + } + RRGGBB | RRGGBBAA => { + let r = u8::from_str_radix(&hex[0..2], 16)?; + let g = u8::from_str_radix(&hex[2..4], 16)?; + let b = u8::from_str_radix(&hex[4..6], 16)?; + let a = if hex.len() == RRGGBBAA { + u8::from_str_radix(&hex[6..8], 16)? + } else { + 0xff + }; + (r, g, b, a) + } + _ => bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}"), + }; + + Ok(Rgba { + r: r as f32 / 255., + g: g as f32 / 255., + b: b as f32 / 255., + a: a as f32 / 255., + }) } } @@ -311,3 +337,52 @@ impl<'de> Deserialize<'de> for Hsla { Ok(Hsla::from(rgba)) } } + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + + #[test] + fn test_deserialize_three_value_hex_to_rgba() { + let actual: Rgba = serde_json::from_value(json!("#f09")).unwrap(); + + assert_eq!(actual, rgba(0xff0099ff)) + } + + #[test] + fn test_deserialize_four_value_hex_to_rgba() { + let actual: Rgba = serde_json::from_value(json!("#f09f")).unwrap(); + + assert_eq!(actual, rgba(0xff0099ff)) + } + + #[test] + fn test_deserialize_six_value_hex_to_rgba() { + let actual: Rgba = serde_json::from_value(json!("#ff0099")).unwrap(); + + assert_eq!(actual, rgba(0xff0099ff)) + } + + #[test] + fn test_deserialize_eight_value_hex_to_rgba() { + let actual: Rgba = serde_json::from_value(json!("#ff0099ff")).unwrap(); + + assert_eq!(actual, rgba(0xff0099ff)) + } + + #[test] + fn test_deserialize_eight_value_hex_with_padding_to_rgba() { + let actual: Rgba = serde_json::from_value(json!(" #f5f5f5ff ")).unwrap(); + + assert_eq!(actual, rgba(0xf5f5f5ff)) + } + + #[test] + fn test_deserialize_eight_value_hex_with_mixed_case_to_rgba() { + let actual: Rgba = serde_json::from_value(json!("#DeAdbEeF")).unwrap(); + + assert_eq!(actual, rgba(0xdeadbeef)) + } +} diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index 679c420cc3..6436d73d68 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -9,6 +9,9 @@ use smallvec::SmallVec; use std::{cmp, ops::Range, sync::Arc}; use taffy::style::Overflow; +/// uniform_list provides lazy rendering for a set of items that are of uniform height. +/// When rendered into a container with overflow-y: hidden and a fixed (or max) height, +/// uniform_list will only render the visibile subset of items. pub fn uniform_list( id: Id, item_count: usize, @@ -20,9 +23,12 @@ where C: Component, { let id = id.into(); + let mut style = StyleRefinement::default(); + style.overflow.y = Some(Overflow::Hidden); + UniformList { id: id.clone(), - style: Default::default(), + style, item_count, render_items: Box::new(move |view, visible_range, cx| { f(view, visible_range, cx) @@ -86,8 +92,14 @@ impl Styled for UniformList { } } +#[derive(Default)] +pub struct UniformListState { + interactive: InteractiveElementState, + item_size: Size, +} + impl Element for UniformList { - type ElementState = InteractiveElementState; + type ElementState = UniformListState; fn id(&self) -> Option { Some(self.id.clone()) @@ -95,20 +107,47 @@ impl Element for UniformList { fn initialize( &mut self, - _: &mut V, + view_state: &mut V, element_state: Option, - _: &mut ViewContext, + cx: &mut ViewContext, ) -> Self::ElementState { - element_state.unwrap_or_default() + element_state.unwrap_or_else(|| { + let item_size = self.measure_first_item(view_state, None, cx); + UniformListState { + interactive: InteractiveElementState::default(), + item_size, + } + }) } fn layout( &mut self, _view_state: &mut V, - _element_state: &mut Self::ElementState, + element_state: &mut Self::ElementState, cx: &mut ViewContext, ) -> LayoutId { - cx.request_layout(&self.computed_style(), None) + let max_items = self.item_count; + let item_size = element_state.item_size; + let rem_size = cx.rem_size(); + + cx.request_measured_layout( + self.computed_style(), + rem_size, + move |known_dimensions: Size>, available_space: Size| { + let desired_height = item_size.height * max_items; + let width = known_dimensions + .width + .unwrap_or(match available_space.width { + AvailableSpace::Definite(x) => x, + AvailableSpace::MinContent | AvailableSpace::MaxContent => item_size.width, + }); + let height = match available_space.height { + AvailableSpace::Definite(x) => desired_height.min(x), + AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height, + }; + size(width, height) + }, + ) } fn paint( @@ -133,12 +172,14 @@ impl Element for UniformList { cx.with_z_index(style.z_index.unwrap_or(0), |cx| { let content_size; if self.item_count > 0 { - let item_height = self.measure_item_height(view_state, padded_bounds, cx); + let item_height = self + .measure_first_item(view_state, Some(padded_bounds.size.width), cx) + .height; if let Some(scroll_handle) = self.scroll_handle.clone() { scroll_handle.0.lock().replace(ScrollHandleState { item_height, list_height: padded_bounds.size.height, - scroll_offset: element_state.track_scroll_offset(), + scroll_offset: element_state.interactive.track_scroll_offset(), }); } let visible_item_count = if item_height > px(0.) { @@ -147,6 +188,7 @@ impl Element for UniformList { 0 }; let scroll_offset = element_state + .interactive .scroll_offset() .map_or((0.0).into(), |offset| offset.y); let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize; @@ -184,29 +226,35 @@ impl Element for UniformList { let overflow = point(style.overflow.x, Overflow::Scroll); cx.with_z_index(0, |cx| { - self.interactivity - .paint(bounds, content_size, overflow, element_state, cx); + self.interactivity.paint( + bounds, + content_size, + overflow, + &mut element_state.interactive, + cx, + ); }); }) } } impl UniformList { - fn measure_item_height( + fn measure_first_item( &self, view_state: &mut V, - list_bounds: Bounds, + list_width: Option, cx: &mut ViewContext, - ) -> Pixels { + ) -> Size { let mut items = (self.render_items)(view_state, 0..1, cx); debug_assert_eq!(items.len(), 1); let mut item_to_measure = items.pop().unwrap(); let available_space = size( - AvailableSpace::Definite(list_bounds.size.width), + list_width.map_or(AvailableSpace::MinContent, |width| { + AvailableSpace::Definite(width) + }), AvailableSpace::MinContent, ); - let size = item_to_measure.measure(available_space, view_state, cx); - size.height + item_to_measure.measure(available_space, view_state, cx) } pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self { diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index c5209239ae..f290c6a81c 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -9,7 +9,7 @@ use std::{ }; #[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)] -#[refineable(debug)] +#[refineable(Debug)] #[repr(C)] pub struct Point { pub x: T, @@ -140,7 +140,7 @@ impl Clone for Point { } #[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash, Serialize, Deserialize)] -#[refineable(debug)] +#[refineable(Debug)] #[repr(C)] pub struct Size { pub width: T, @@ -313,7 +313,7 @@ impl Size { } #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)] -#[refineable(debug)] +#[refineable(Debug)] #[repr(C)] pub struct Bounds { pub origin: Point, @@ -477,7 +477,7 @@ impl Bounds { impl Copy for Bounds {} #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)] -#[refineable(debug)] +#[refineable(Debug)] #[repr(C)] pub struct Edges { pub top: T, @@ -619,7 +619,7 @@ impl Edges { } #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)] -#[refineable(debug)] +#[refineable(Debug)] #[repr(C)] pub struct Corners { pub top_left: T, diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index a546c1b40b..243eb3cb07 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -94,7 +94,6 @@ pub trait StatelessInteractive: Element { fn on_mouse_down_out( mut self, - button: MouseButton, handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext) + 'static, ) -> Self where @@ -103,10 +102,7 @@ pub trait StatelessInteractive: Element { self.stateless_interactivity() .mouse_down_listeners .push(Box::new(move |view, event, bounds, phase, cx| { - if phase == DispatchPhase::Capture - && event.button == button - && !bounds.contains_point(&event.position) - { + if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) { handler(view, event, cx) } })); diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index 6d3b31c1f7..664cc61f8a 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -14,7 +14,7 @@ pub use taffy::style::{ pub type StyleCascade = Cascade