editor: Highlight search results

Piotr Osiewicz created

Z-1292

Change summary

assets/settings/default.json         |   4 
crates/editor/src/editor_settings.rs |   2 
crates/editor/src/element.rs         |  47 ++
crates/theme/src/theme.rs            |   1 
styles/src/styleTree/editor.ts       | 507 +++++++++++++++--------------
5 files changed, 305 insertions(+), 256 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -71,7 +71,9 @@
       //    "never"
       "show": "auto",
       // Whether to show git diff indicators in the scrollbar.
-      "git_diff": true
+      "git_diff": true,
+      // Whether to show selections in the scrollbar.
+      "selections": true
   },
   "project_panel": {
       // Whether to show the git status in the project panel.

crates/editor/src/editor_settings.rs 🔗

@@ -15,6 +15,7 @@ pub struct EditorSettings {
 pub struct Scrollbar {
     pub show: ShowScrollbar,
     pub git_diff: bool,
+    pub selections: bool,
 }
 
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@@ -39,6 +40,7 @@ pub struct EditorSettingsContent {
 pub struct ScrollbarContent {
     pub show: Option<ShowScrollbar>,
     pub git_diff: Option<bool>,
+    pub selections: Option<bool>,
 }
 
 impl Setting for EditorSettings {

crates/editor/src/element.rs 🔗

@@ -1008,6 +1008,7 @@ impl EditorElement {
         bounds: RectF,
         layout: &mut LayoutState,
         cx: &mut ViewContext<Editor>,
+        editor: &Editor,
     ) {
         enum ScrollbarMouseHandlers {}
         if layout.mode != EditorMode::Full {
@@ -1050,9 +1051,49 @@ impl EditorElement {
                 background: style.track.background_color,
                 ..Default::default()
             });
+            let scrollbar_settings = settings::get::<EditorSettings>(cx).scrollbar;
+            let theme = theme::current(cx);
+            let scrollbar_theme = &theme.editor.scrollbar;
+            if layout.is_singleton && scrollbar_settings.selections {
+                let start_anchor = Anchor::min();
+                let end_anchor = Anchor::max();
+                for (row, _) in &editor.background_highlights_in_range(
+                    start_anchor..end_anchor,
+                    &layout.position_map.snapshot,
+                    &theme,
+                ) {
+                    let start_display = row.start;
+                    let end_display = row.end;
+                    let start_y = y_for_row(start_display.row() as f32);
+                    let mut end_y = y_for_row((end_display.row()) as f32);
+                    if end_y - start_y < 1. {
+                        end_y = start_y + 1.;
+                    }
+                    let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
+
+                    let color = scrollbar_theme.selections;
+
+                    let border = Border {
+                        width: 1.,
+                        color: style.thumb.border.color,
+                        overlay: false,
+                        top: false,
+                        right: true,
+                        bottom: false,
+                        left: true,
+                    };
+
+                    scene.push_quad(Quad {
+                        bounds,
+                        background: Some(color),
+                        border,
+                        corner_radius: style.thumb.corner_radius,
+                    })
+                }
+            }
 
-            if layout.is_singleton && settings::get::<EditorSettings>(cx).scrollbar.git_diff {
-                let diff_style = theme::current(cx).editor.scrollbar.git.clone();
+            if layout.is_singleton && scrollbar_settings.git_diff {
+                let diff_style = scrollbar_theme.git.clone();
                 for hunk in layout
                     .position_map
                     .snapshot
@@ -2359,7 +2400,7 @@ impl Element<Editor> for EditorElement {
         if !layout.blocks.is_empty() {
             self.paint_blocks(scene, bounds, visible_bounds, layout, editor, cx);
         }
-        self.paint_scrollbar(scene, bounds, layout, cx);
+        self.paint_scrollbar(scene, bounds, layout, cx, &editor);
         scene.pop_layer();
 
         scene.pop_layer();

crates/theme/src/theme.rs 🔗

@@ -700,6 +700,7 @@ pub struct Scrollbar {
     pub width: f32,
     pub min_height_factor: f32,
     pub git: GitDiffColors,
+    pub selections: Color,
 }
 
 #[derive(Clone, Deserialize, Default)]

styles/src/styleTree/editor.ts 🔗

@@ -6,273 +6,276 @@ import hoverPopover from "./hoverPopover"
 import { buildSyntax } from "../theme/syntax"
 
 export default function editor(colorScheme: ColorScheme) {
-    const { isLight } = colorScheme
+  const { isLight } = colorScheme
 
-    let layer = colorScheme.highest
+  let layer = colorScheme.highest
 
-    const autocompleteItem = {
-        cornerRadius: 6,
-        padding: {
-            bottom: 2,
-            left: 6,
-            right: 6,
-            top: 2,
-        },
-    }
+  const autocompleteItem = {
+    cornerRadius: 6,
+    padding: {
+      bottom: 2,
+      left: 6,
+      right: 6,
+      top: 2,
+    },
+  }
 
-    function diagnostic(layer: Layer, styleSet: StyleSets) {
-        return {
-            textScaleFactor: 0.857,
-            header: {
-                border: border(layer, {
-                    top: true,
-                }),
-            },
-            message: {
-                text: text(layer, "sans", styleSet, "default", { size: "sm" }),
-                highlightText: text(layer, "sans", styleSet, "default", {
-                    size: "sm",
-                    weight: "bold",
-                }),
-            },
-        }
+  function diagnostic(layer: Layer, styleSet: StyleSets) {
+    return {
+      textScaleFactor: 0.857,
+      header: {
+        border: border(layer, {
+          top: true,
+        }),
+      },
+      message: {
+        text: text(layer, "sans", styleSet, "default", { size: "sm" }),
+        highlightText: text(layer, "sans", styleSet, "default", {
+          size: "sm",
+          weight: "bold",
+        }),
+      },
     }
+  }
 
-    const syntax = buildSyntax(colorScheme)
+  const syntax = buildSyntax(colorScheme)
 
-    return {
-        textColor: syntax.primary.color,
-        background: background(layer),
-        activeLineBackground: withOpacity(background(layer, "on"), 0.75),
-        highlightedLineBackground: background(layer, "on"),
-        // Inline autocomplete suggestions, Co-pilot suggestions, etc.
-        suggestion: syntax.predictive,
-        codeActions: {
-            indicator: {
-                color: foreground(layer, "variant"),
+  return {
+    textColor: syntax.primary.color,
+    background: background(layer),
+    activeLineBackground: withOpacity(background(layer, "on"), 0.75),
+    highlightedLineBackground: background(layer, "on"),
+    // Inline autocomplete suggestions, Co-pilot suggestions, etc.
+    suggestion: syntax.predictive,
+    codeActions: {
+      indicator: {
+        color: foreground(layer, "variant"),
 
-                clicked: {
-                    color: foreground(layer, "base"),
-                },
-                hover: {
-                    color: foreground(layer, "on"),
-                },
-                active: {
-                    color: foreground(layer, "on"),
-                },
-            },
-            verticalScale: 0.55,
+        clicked: {
+          color: foreground(layer, "base"),
         },
-        folds: {
-            iconMarginScale: 2.5,
-            foldedIcon: "icons/chevron_right_8.svg",
-            foldableIcon: "icons/chevron_down_8.svg",
-            indicator: {
-                color: foreground(layer, "variant"),
-
-                clicked: {
-                    color: foreground(layer, "base"),
-                },
-                hover: {
-                    color: foreground(layer, "on"),
-                },
-                active: {
-                    color: foreground(layer, "on"),
-                },
-            },
-            ellipses: {
-                textColor: colorScheme.ramps.neutral(0.71).hex(),
-                cornerRadiusFactor: 0.15,
-                background: {
-                    // Copied from hover_popover highlight
-                    color: colorScheme.ramps.neutral(0.5).alpha(0.0).hex(),
-
-                    hover: {
-                        color: colorScheme.ramps.neutral(0.5).alpha(0.5).hex(),
-                    },
-
-                    clicked: {
-                        color: colorScheme.ramps.neutral(0.5).alpha(0.7).hex(),
-                    },
-                },
-            },
-            foldBackground: foreground(layer, "variant"),
+        hover: {
+          color: foreground(layer, "on"),
         },
-        diff: {
-            deleted: isLight
-                ? colorScheme.ramps.red(0.5).hex()
-                : colorScheme.ramps.red(0.4).hex(),
-            modified: isLight
-                ? colorScheme.ramps.yellow(0.5).hex()
-                : colorScheme.ramps.yellow(0.5).hex(),
-            inserted: isLight
-                ? colorScheme.ramps.green(0.4).hex()
-                : colorScheme.ramps.green(0.5).hex(),
-            removedWidthEm: 0.275,
-            widthEm: 0.15,
-            cornerRadius: 0.05,
+        active: {
+          color: foreground(layer, "on"),
         },
-        /** Highlights matching occurrences of what is under the cursor
-         * as well as matched brackets
-         */
-        documentHighlightReadBackground: withOpacity(
-            foreground(layer, "accent"),
-            0.1
-        ),
-        documentHighlightWriteBackground: colorScheme.ramps
-            .neutral(0.5)
-            .alpha(0.4)
-            .hex(), // TODO: This was blend * 2
-        errorColor: background(layer, "negative"),
-        gutterBackground: background(layer),
-        gutterPaddingFactor: 3.5,
-        lineNumber: withOpacity(foreground(layer), 0.35),
-        lineNumberActive: foreground(layer),
-        renameFade: 0.6,
-        unnecessaryCodeFade: 0.5,
-        selection: colorScheme.players[0],
-        whitespace: colorScheme.ramps.neutral(0.5).hex(),
-        guestSelections: [
-            colorScheme.players[1],
-            colorScheme.players[2],
-            colorScheme.players[3],
-            colorScheme.players[4],
-            colorScheme.players[5],
-            colorScheme.players[6],
-            colorScheme.players[7],
-        ],
-        autocomplete: {
-            background: background(colorScheme.middle),
-            cornerRadius: 8,
-            padding: 4,
-            margin: {
-                left: -14,
-            },
-            border: border(colorScheme.middle),
-            shadow: colorScheme.popoverShadow,
-            matchHighlight: foreground(colorScheme.middle, "accent"),
-            item: autocompleteItem,
-            hoveredItem: {
-                ...autocompleteItem,
-                matchHighlight: foreground(
-                    colorScheme.middle,
-                    "accent",
-                    "hovered"
-                ),
-                background: background(colorScheme.middle, "hovered"),
-            },
-            selectedItem: {
-                ...autocompleteItem,
-                matchHighlight: foreground(
-                    colorScheme.middle,
-                    "accent",
-                    "active"
-                ),
-                background: background(colorScheme.middle, "active"),
-            },
+      },
+      verticalScale: 0.55,
+    },
+    folds: {
+      iconMarginScale: 2.5,
+      foldedIcon: "icons/chevron_right_8.svg",
+      foldableIcon: "icons/chevron_down_8.svg",
+      indicator: {
+        color: foreground(layer, "variant"),
+
+        clicked: {
+          color: foreground(layer, "base"),
         },
-        diagnosticHeader: {
-            background: background(colorScheme.middle),
-            iconWidthFactor: 1.5,
-            textScaleFactor: 0.857,
-            border: border(colorScheme.middle, {
-                bottom: true,
-                top: true,
-            }),
-            code: {
-                ...text(colorScheme.middle, "mono", { size: "sm" }),
-                margin: {
-                    left: 10,
-                },
-            },
-            source: {
-                text: text(colorScheme.middle, "sans", {
-                    size: "sm",
-                    weight: "bold",
-                }),
-            },
-            message: {
-                highlightText: text(colorScheme.middle, "sans", {
-                    size: "sm",
-                    weight: "bold",
-                }),
-                text: text(colorScheme.middle, "sans", { size: "sm" }),
-            },
+        hover: {
+          color: foreground(layer, "on"),
         },
-        diagnosticPathHeader: {
-            background: background(colorScheme.middle),
-            textScaleFactor: 0.857,
-            filename: text(colorScheme.middle, "mono", { size: "sm" }),
-            path: {
-                ...text(colorScheme.middle, "mono", { size: "sm" }),
-                margin: {
-                    left: 12,
-                },
-            },
+        active: {
+          color: foreground(layer, "on"),
         },
-        errorDiagnostic: diagnostic(colorScheme.middle, "negative"),
-        warningDiagnostic: diagnostic(colorScheme.middle, "warning"),
-        informationDiagnostic: diagnostic(colorScheme.middle, "accent"),
-        hintDiagnostic: diagnostic(colorScheme.middle, "warning"),
-        invalidErrorDiagnostic: diagnostic(colorScheme.middle, "base"),
-        invalidHintDiagnostic: diagnostic(colorScheme.middle, "base"),
-        invalidInformationDiagnostic: diagnostic(colorScheme.middle, "base"),
-        invalidWarningDiagnostic: diagnostic(colorScheme.middle, "base"),
-        hoverPopover: hoverPopover(colorScheme),
-        linkDefinition: {
-            color: syntax.linkUri.color,
-            underline: syntax.linkUri.underline,
+      },
+      ellipses: {
+        textColor: colorScheme.ramps.neutral(0.71).hex(),
+        cornerRadiusFactor: 0.15,
+        background: {
+          // Copied from hover_popover highlight
+          color: colorScheme.ramps.neutral(0.5).alpha(0.0).hex(),
+
+          hover: {
+            color: colorScheme.ramps.neutral(0.5).alpha(0.5).hex(),
+          },
+
+          clicked: {
+            color: colorScheme.ramps.neutral(0.5).alpha(0.7).hex(),
+          },
         },
-        jumpIcon: {
-            color: foreground(layer, "on"),
-            iconWidth: 20,
-            buttonWidth: 20,
-            cornerRadius: 6,
-            padding: {
-                top: 6,
-                bottom: 6,
-                left: 6,
-                right: 6,
-            },
-            hover: {
-                background: background(layer, "on", "hovered"),
-            },
+      },
+      foldBackground: foreground(layer, "variant"),
+    },
+    diff: {
+      deleted: isLight
+        ? colorScheme.ramps.red(0.5).hex()
+        : colorScheme.ramps.red(0.4).hex(),
+      modified: isLight
+        ? colorScheme.ramps.yellow(0.5).hex()
+        : colorScheme.ramps.yellow(0.5).hex(),
+      inserted: isLight
+        ? colorScheme.ramps.green(0.4).hex()
+        : colorScheme.ramps.green(0.5).hex(),
+      removedWidthEm: 0.275,
+      widthEm: 0.15,
+      cornerRadius: 0.05,
+    },
+    /** Highlights matching occurrences of what is under the cursor
+     * as well as matched brackets
+     */
+    documentHighlightReadBackground: withOpacity(
+      foreground(layer, "accent"),
+      0.1
+    ),
+    documentHighlightWriteBackground: colorScheme.ramps
+      .neutral(0.5)
+      .alpha(0.4)
+      .hex(), // TODO: This was blend * 2
+    errorColor: background(layer, "negative"),
+    gutterBackground: background(layer),
+    gutterPaddingFactor: 3.5,
+    lineNumber: withOpacity(foreground(layer), 0.35),
+    lineNumberActive: foreground(layer),
+    renameFade: 0.6,
+    unnecessaryCodeFade: 0.5,
+    selection: colorScheme.players[0],
+    whitespace: colorScheme.ramps.neutral(0.5).hex(),
+    guestSelections: [
+      colorScheme.players[1],
+      colorScheme.players[2],
+      colorScheme.players[3],
+      colorScheme.players[4],
+      colorScheme.players[5],
+      colorScheme.players[6],
+      colorScheme.players[7],
+    ],
+    autocomplete: {
+      background: background(colorScheme.middle),
+      cornerRadius: 8,
+      padding: 4,
+      margin: {
+        left: -14,
+      },
+      border: border(colorScheme.middle),
+      shadow: colorScheme.popoverShadow,
+      matchHighlight: foreground(colorScheme.middle, "accent"),
+      item: autocompleteItem,
+      hoveredItem: {
+        ...autocompleteItem,
+        matchHighlight: foreground(
+          colorScheme.middle,
+          "accent",
+          "hovered"
+        ),
+        background: background(colorScheme.middle, "hovered"),
+      },
+      selectedItem: {
+        ...autocompleteItem,
+        matchHighlight: foreground(
+          colorScheme.middle,
+          "accent",
+          "active"
+        ),
+        background: background(colorScheme.middle, "active"),
+      },
+    },
+    diagnosticHeader: {
+      background: background(colorScheme.middle),
+      iconWidthFactor: 1.5,
+      textScaleFactor: 0.857,
+      border: border(colorScheme.middle, {
+        bottom: true,
+        top: true,
+      }),
+      code: {
+        ...text(colorScheme.middle, "mono", { size: "sm" }),
+        margin: {
+          left: 10,
         },
-        scrollbar: {
-            width: 12,
-            minHeightFactor: 1.0,
-            track: {
-                border: border(layer, "variant", { left: true }),
-            },
-            thumb: {
-                background: withOpacity(background(layer, "inverted"), 0.3),
-                border: {
-                    width: 1,
-                    color: borderColor(layer, "variant"),
-                    top: false,
-                    right: true,
-                    left: true,
-                    bottom: false,
-                },
-            },
-            git: {
-                deleted: isLight
-                    ? withOpacity(colorScheme.ramps.red(0.5).hex(), 0.8)
-                    : withOpacity(colorScheme.ramps.red(0.4).hex(), 0.8),
-                modified: isLight
-                    ? withOpacity(colorScheme.ramps.yellow(0.5).hex(), 0.8)
-                    : withOpacity(colorScheme.ramps.yellow(0.4).hex(), 0.8),
-                inserted: isLight
-                    ? withOpacity(colorScheme.ramps.green(0.5).hex(), 0.8)
-                    : withOpacity(colorScheme.ramps.green(0.4).hex(), 0.8),
-            },
+      },
+      source: {
+        text: text(colorScheme.middle, "sans", {
+          size: "sm",
+          weight: "bold",
+        }),
+      },
+      message: {
+        highlightText: text(colorScheme.middle, "sans", {
+          size: "sm",
+          weight: "bold",
+        }),
+        text: text(colorScheme.middle, "sans", { size: "sm" }),
+      },
+    },
+    diagnosticPathHeader: {
+      background: background(colorScheme.middle),
+      textScaleFactor: 0.857,
+      filename: text(colorScheme.middle, "mono", { size: "sm" }),
+      path: {
+        ...text(colorScheme.middle, "mono", { size: "sm" }),
+        margin: {
+          left: 12,
         },
-        compositionMark: {
-            underline: {
-                thickness: 1.0,
-                color: borderColor(layer),
-            },
+      },
+    },
+    errorDiagnostic: diagnostic(colorScheme.middle, "negative"),
+    warningDiagnostic: diagnostic(colorScheme.middle, "warning"),
+    informationDiagnostic: diagnostic(colorScheme.middle, "accent"),
+    hintDiagnostic: diagnostic(colorScheme.middle, "warning"),
+    invalidErrorDiagnostic: diagnostic(colorScheme.middle, "base"),
+    invalidHintDiagnostic: diagnostic(colorScheme.middle, "base"),
+    invalidInformationDiagnostic: diagnostic(colorScheme.middle, "base"),
+    invalidWarningDiagnostic: diagnostic(colorScheme.middle, "base"),
+    hoverPopover: hoverPopover(colorScheme),
+    linkDefinition: {
+      color: syntax.linkUri.color,
+      underline: syntax.linkUri.underline,
+    },
+    jumpIcon: {
+      color: foreground(layer, "on"),
+      iconWidth: 20,
+      buttonWidth: 20,
+      cornerRadius: 6,
+      padding: {
+        top: 6,
+        bottom: 6,
+        left: 6,
+        right: 6,
+      },
+      hover: {
+        background: background(layer, "on", "hovered"),
+      },
+    },
+    scrollbar: {
+      width: 12,
+      minHeightFactor: 1.0,
+      track: {
+        border: border(layer, "variant", { left: true }),
+      },
+      thumb: {
+        background: withOpacity(background(layer, "inverted"), 0.3),
+        border: {
+          width: 1,
+          color: borderColor(layer, "variant"),
+          top: false,
+          right: true,
+          left: true,
+          bottom: false,
         },
-        syntax,
-    }
+      },
+      git: {
+        deleted: isLight
+          ? withOpacity(colorScheme.ramps.red(0.5).hex(), 0.8)
+          : withOpacity(colorScheme.ramps.red(0.4).hex(), 0.8),
+        modified: isLight
+          ? withOpacity(colorScheme.ramps.yellow(0.5).hex(), 0.8)
+          : withOpacity(colorScheme.ramps.yellow(0.4).hex(), 0.8),
+        inserted: isLight
+          ? withOpacity(colorScheme.ramps.green(0.5).hex(), 0.8)
+          : withOpacity(colorScheme.ramps.green(0.4).hex(), 0.8),
+      },
+      selections: isLight
+        ? withOpacity(colorScheme.ramps.blue(0.5).hex(), 0.8)
+        : withOpacity(colorScheme.ramps.blue(0.4).hex(), 0.8)
+    },
+    compositionMark: {
+      underline: {
+        thickness: 1.0,
+        color: borderColor(layer),
+      },
+    },
+    syntax,
+  }
 }