mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
feat(search): Jump between result groups in Search Everywhere
Page Down / Page Up and ⌘↓ / ⌘↑ (Ctrl+Down / Ctrl+Up on Windows) move the selection to the first result of the next or previous group (Classes, Files, Symbols, Actions…), including in the All tab, where groups have no header rows. ⌘↑ inside a popup no longer jumps to the navigation bar.
This commit is contained in:
@@ -782,6 +782,8 @@ export function registerEditorActions(): () => void {
|
||||
category: "Navigate",
|
||||
keywords: ["breadcrumbs"],
|
||||
when: "projectOpen",
|
||||
// ⌘↑ / ⌘↓ inside a popup (Search Everywhere's previous / next group) stay with it.
|
||||
keyWhen: "!popupOpen",
|
||||
run: () => {
|
||||
if (activeSlot()) return openNavBarPopup();
|
||||
// No editor: the status bar breadcrumbs (StatusItems › Breadcrumbs) take focus.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { groupJump, type SeRow } from "../everywhere/model";
|
||||
|
||||
const section = (id: string) => ({ kind: "section", key: `#${id}`, section: id, title: id }) as unknown as SeRow;
|
||||
const more = (id: string) => ({ kind: "more", key: `more:${id}`, section: id }) as unknown as SeRow;
|
||||
const item = (key: string, id: string) => ({ kind: "file", key, section: id }) as unknown as SeRow;
|
||||
|
||||
// 0 Classes, 1 a, 2 b, 3 Files, 4 c, 5 more, 6 Actions, 7 d
|
||||
const rows = [
|
||||
section("classes"),
|
||||
item("a", "classes"),
|
||||
item("b", "classes"),
|
||||
section("files"),
|
||||
item("c", "files"),
|
||||
more("files"),
|
||||
section("actions"),
|
||||
item("d", "actions"),
|
||||
];
|
||||
|
||||
describe("groupJump", () => {
|
||||
it("moves to the first item of the next group", () => {
|
||||
expect(groupJump(rows, 1, 1)).toBe(4);
|
||||
expect(groupJump(rows, 5, 1)).toBe(7);
|
||||
expect(groupJump(rows, 7, 1)).toBe(7);
|
||||
});
|
||||
|
||||
it("moves to the start of the current group, then the previous one", () => {
|
||||
expect(groupJump(rows, 5, -1)).toBe(4);
|
||||
expect(groupJump(rows, 4, -1)).toBe(1);
|
||||
expect(groupJump(rows, 2, -1)).toBe(1);
|
||||
});
|
||||
|
||||
it("finds groups without header rows", () => {
|
||||
const flat = [item("a", "classes"), item("b", "classes"), item("c", "files")];
|
||||
expect(groupJump(flat, 0, 1)).toBe(2);
|
||||
expect(groupJump(flat, 2, -1)).toBe(0);
|
||||
});
|
||||
|
||||
it("is null with a single group", () => {
|
||||
expect(groupJump([item("a", "files"), item("b", "files")], 0, 1)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
SECTION_TITLES,
|
||||
TABS,
|
||||
buildRows,
|
||||
groupJump,
|
||||
isSectionRow,
|
||||
parseCommand,
|
||||
stableIndex,
|
||||
@@ -338,6 +339,18 @@ function SearchEverywhereDialog() {
|
||||
browseHistory(e.key === "ArrowUp" ? 1 : -1);
|
||||
return;
|
||||
}
|
||||
const groupKey =
|
||||
e.key === "PageDown" ||
|
||||
e.key === "PageUp" ||
|
||||
(hasPrimaryModifier(e) && (e.key === "ArrowDown" || e.key === "ArrowUp"));
|
||||
if (groupKey) {
|
||||
const jump = groupJump(rows, active, e.key === "PageDown" || e.key === "ArrowDown" ? 1 : -1);
|
||||
if (jump !== null) {
|
||||
e.preventDefault();
|
||||
select(jump);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!["ArrowDown", "ArrowUp", "PageDown", "PageUp"].includes(e.key)) return;
|
||||
const next = listNavigate(e.key, { items: rows, active, isSection: isSectionRow, wrap: true, pageSize: 10 });
|
||||
if (next === null) return;
|
||||
|
||||
@@ -453,6 +453,25 @@ export function buildRows(
|
||||
|
||||
export const isSectionRow = (r: SeRow) => r.kind === "section";
|
||||
|
||||
/**
|
||||
* Next / Previous Group (PageDown / PageUp, ⌘↓ / ⌘↑): the first item of the
|
||||
* next section, or of the current (then previous) one; null without sections.
|
||||
*/
|
||||
export function groupJump(rows: readonly SeRow[], active: number, dir: 1 | -1): number | null {
|
||||
// Groups start where the section changes (the All tab may show no header rows).
|
||||
const starts: number[] = [];
|
||||
let last: string | null = null;
|
||||
rows.forEach((r, i) => {
|
||||
if (isSectionRow(r) || r.kind === "command" || !("section" in r)) return;
|
||||
if (r.section !== last) starts.push(i);
|
||||
last = r.section;
|
||||
});
|
||||
if (starts.length < 2) return null;
|
||||
if (dir === 1) return starts.find((s) => s > active) ?? starts[starts.length - 1]!;
|
||||
const current = [...starts].reverse().find((s) => s <= active) ?? starts[0]!;
|
||||
return current < active ? current : ([...starts].reverse().find((s) => s < active) ?? starts[0]!);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// "/" commands
|
||||
|
||||
|
||||
Reference in New Issue
Block a user