mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-07 10:16:03 -10:00
fix(keymap): clear shortcuts when a focused tree is removed (#1940)
This commit is contained in:
5 files changed
+62
-17
No files matched your search
@@ -13,6 +13,35 @@ test("opens a file from the project tree and types into it", async ({ workbench:
|
|||||||
await expect(editor.locator(".cm-content")).not.toContainText("// typed by e2e");
|
await expect(editor.locator(".cm-content")).not.toContainText("// typed by e2e");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Delete edits text after the focused Project tree is remounted", async ({ workbench: page }) => {
|
||||||
|
const editor = await openFromTree(page, "README.md");
|
||||||
|
const content = editor.locator(".cm-content");
|
||||||
|
await content.press("Meta+End");
|
||||||
|
await page.keyboard.press("Enter");
|
||||||
|
await page.keyboard.insertText("delete probe abc");
|
||||||
|
const line = editor.locator(".cm-line", { hasText: "delete probe" });
|
||||||
|
await expect(line).toHaveText("delete probe abc");
|
||||||
|
|
||||||
|
const tree = page.getByRole("tree", { name: "Project" });
|
||||||
|
await tree.focus();
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const keymapModule = "/src/platform/keymap.ts";
|
||||||
|
const layoutModule = "/src/workbench/toolwindows/store.ts";
|
||||||
|
const { applyKeymapSettings } = await import(/* @vite-ignore */ keymapModule);
|
||||||
|
const { moveToolWindow } = await import(/* @vite-ignore */ layoutModule);
|
||||||
|
applyKeymapSettings("intellij-windows", {});
|
||||||
|
moveToolWindow("project", "right");
|
||||||
|
});
|
||||||
|
await expect(page.locator('[data-toolwindow="project"]')).toBeVisible();
|
||||||
|
await expect(tree).not.toBeFocused();
|
||||||
|
|
||||||
|
await content.press("ArrowLeft");
|
||||||
|
await page.keyboard.press("Delete");
|
||||||
|
await expect(line).toHaveText("delete probe ab");
|
||||||
|
await page.keyboard.press("Backspace");
|
||||||
|
await expect(line).toHaveText("delete probe a");
|
||||||
|
});
|
||||||
|
|
||||||
test("⌘F opens the find bar and highlights matches; Escape returns to the editor", async ({ workbench: page }) => {
|
test("⌘F opens the find bar and highlights matches; Escape returns to the editor", async ({ workbench: page }) => {
|
||||||
const editor = await openFromTree(page, "build.gradle.kts");
|
const editor = await openFromTree(page, "build.gradle.kts");
|
||||||
await editor.locator(".cm-content").click();
|
await editor.locator(".cm-content").click();
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
import { useEffect, useMemo, useRef, type CSSProperties } from "react";
|
import { useEffect, useMemo, useRef, type CSSProperties } from "react";
|
||||||
import type { DbDatabases, DbTableDetails } from "@/platform/ipc";
|
import type { DbDatabases, DbTableDetails } from "@/platform/ipc";
|
||||||
import { setContext } from "@/platform/context";
|
|
||||||
import { executeAction } from "@/platform/actions";
|
import { executeAction } from "@/platform/actions";
|
||||||
import { shortcutLabel } from "@/platform/keymap";
|
import { shortcutLabel } from "@/platform/keymap";
|
||||||
import {
|
import {
|
||||||
@@ -378,13 +377,10 @@ export function DatabaseView() {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.dbView}>
|
<div className={styles.dbView}>
|
||||||
<DatabaseToolbar />
|
<DatabaseToolbar />
|
||||||
<div
|
<div className={styles.treeHost}>
|
||||||
className={styles.treeHost}
|
|
||||||
onFocus={(e) => setContext(DB_VIEW_FOCUS, (e.target as HTMLElement).getAttribute("role") === "tree")}
|
|
||||||
onBlur={() => setContext(DB_VIEW_FOCUS, false)}
|
|
||||||
>
|
|
||||||
<Tree<Node>
|
<Tree<Node>
|
||||||
aria-label="Data sources"
|
aria-label="Data sources"
|
||||||
|
focusContext={DB_VIEW_FOCUS}
|
||||||
roots={roots}
|
roots={roots}
|
||||||
handleRef={tree}
|
handleRef={tree}
|
||||||
expanded={st.treeExpanded}
|
expanded={st.treeExpanded}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import { createPortal } from "react-dom";
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { revealRange, useEditorRegistry } from "@/editor/api";
|
import { revealRange, useEditorRegistry } from "@/editor/api";
|
||||||
import { executeAction } from "@/platform/actions";
|
import { executeAction } from "@/platform/actions";
|
||||||
import { setContext } from "@/platform/context";
|
|
||||||
import { tx, useTxTemplate } from "@/platform/i18n";
|
import { tx, useTxTemplate } from "@/platform/i18n";
|
||||||
import { isTauri } from "@/platform/ipc";
|
import { isTauri } from "@/platform/ipc";
|
||||||
import { nativeDropPoint } from "@/platform/nativeDrop";
|
import { nativeDropPoint } from "@/platform/nativeDrop";
|
||||||
@@ -779,8 +778,6 @@ export function ProjectView() {
|
|||||||
onCopy={(e) => onClipboard(e, "project.copy")}
|
onCopy={(e) => onClipboard(e, "project.copy")}
|
||||||
onCut={(e) => onClipboard(e, "project.cut")}
|
onCut={(e) => onClipboard(e, "project.cut")}
|
||||||
onPaste={(e) => onClipboard(e, "project.paste")}
|
onPaste={(e) => onClipboard(e, "project.paste")}
|
||||||
onFocus={(e) => setContext("projectViewFocus", (e.target as HTMLElement).getAttribute("role") === "tree")}
|
|
||||||
onBlur={() => setContext("projectViewFocus", false)}
|
|
||||||
>
|
>
|
||||||
{mode === "unity" && unity && !unity.projects.length && (
|
{mode === "unity" && unity && !unity.projects.length && (
|
||||||
<div className={styles.unityNotice} role="status">
|
<div className={styles.unityNotice} role="status">
|
||||||
@@ -795,6 +792,7 @@ export function ProjectView() {
|
|||||||
<Tree<ProjectNode>
|
<Tree<ProjectNode>
|
||||||
horizontalScroll
|
horizontalScroll
|
||||||
aria-label="Project"
|
aria-label="Project"
|
||||||
|
focusContext="projectViewFocus"
|
||||||
handleRef={setTreeHandle}
|
handleRef={setTreeHandle}
|
||||||
roots={model.roots}
|
roots={model.roots}
|
||||||
getKey={getKey}
|
getKey={getKey}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
*/
|
*/
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { setContext } from "@/platform/context";
|
|
||||||
import { useTxTemplate, tx } from "@/platform/i18n";
|
import { useTxTemplate, tx } from "@/platform/i18n";
|
||||||
import { shortcutLabel } from "@/platform/keymap";
|
import { shortcutLabel } from "@/platform/keymap";
|
||||||
import { useProject } from "@/platform/project";
|
import { useProject } from "@/platform/project";
|
||||||
@@ -208,15 +207,10 @@ export function ServicesTreeView() {
|
|||||||
) : (
|
) : (
|
||||||
<SplitGroup orientation="horizontal" className={styles.split}>
|
<SplitGroup orientation="horizontal" className={styles.split}>
|
||||||
<SplitPanel defaultSize="40%" minSize="160px">
|
<SplitPanel defaultSize="40%" minSize="160px">
|
||||||
<div
|
<div className={styles.tree}>
|
||||||
className={styles.tree}
|
|
||||||
onFocus={(e) =>
|
|
||||||
setContext(SERVICES_VIEW_FOCUS, (e.target as HTMLElement).getAttribute("role") === "tree")
|
|
||||||
}
|
|
||||||
onBlur={() => setContext(SERVICES_VIEW_FOCUS, false)}
|
|
||||||
>
|
|
||||||
<Tree<ServiceTreeItem>
|
<Tree<ServiceTreeItem>
|
||||||
key={location}
|
key={location}
|
||||||
|
focusContext={SERVICES_VIEW_FOCUS}
|
||||||
reorder={reorder}
|
reorder={reorder}
|
||||||
roots={roots}
|
roots={roots}
|
||||||
getKey={(n) => n.key}
|
getKey={(n) => n.key}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { fuzzyMatch } from "./match";
|
|||||||
import { acquirePopup } from "./popupState";
|
import { acquirePopup } from "./popupState";
|
||||||
import styles from "./Tree.module.css";
|
import styles from "./Tree.module.css";
|
||||||
import { T } from "./T";
|
import { T } from "./T";
|
||||||
|
import { setContext } from "@/platform/context";
|
||||||
import { tx } from "@/platform/i18n";
|
import { tx } from "@/platform/i18n";
|
||||||
import { useTreeReorder, type TreeReorder } from "./treeReorder";
|
import { useTreeReorder, type TreeReorder } from "./treeReorder";
|
||||||
import { useTreeDirectoryContext } from "./treeDirectoryContext";
|
import { useTreeDirectoryContext } from "./treeDirectoryContext";
|
||||||
@@ -116,6 +117,8 @@ export interface TreeProps<T> {
|
|||||||
rowClassName?: (node: T) => string | undefined;
|
rowClassName?: (node: T) => string | undefined;
|
||||||
"aria-label"?: string;
|
"aria-label"?: string;
|
||||||
handleRef?: Ref<TreeHandle>;
|
handleRef?: Ref<TreeHandle>;
|
||||||
|
/** Context key set while the tree itself has keyboard focus. */
|
||||||
|
focusContext?: string;
|
||||||
empty?: ReactNode;
|
empty?: ReactNode;
|
||||||
/** Rows wider than the pane scroll horizontally instead of clipping. */
|
/** Rows wider than the pane scroll horizontally instead of clipping. */
|
||||||
horizontalScroll?: boolean;
|
horizontalScroll?: boolean;
|
||||||
@@ -592,6 +595,31 @@ export function Tree<T>(props: TreeProps<T>) {
|
|||||||
);
|
);
|
||||||
const noMatch = !!query && matchMap.size === 0;
|
const noMatch = !!query && matchMap.size === 0;
|
||||||
|
|
||||||
|
const { focusContext } = props;
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const element = scrollRef.current;
|
||||||
|
if (!focusContext || !element) return;
|
||||||
|
let focused = false;
|
||||||
|
const focus = () => {
|
||||||
|
focused = true;
|
||||||
|
setContext(focusContext, true);
|
||||||
|
};
|
||||||
|
const blur = () => {
|
||||||
|
if (!focused) return;
|
||||||
|
focused = false;
|
||||||
|
setContext(focusContext, false);
|
||||||
|
};
|
||||||
|
element.addEventListener("focus", focus);
|
||||||
|
element.addEventListener("blur", blur);
|
||||||
|
if (element.ownerDocument.activeElement === element) focus();
|
||||||
|
return () => {
|
||||||
|
element.removeEventListener("focus", focus);
|
||||||
|
element.removeEventListener("blur", blur);
|
||||||
|
// Removing a focused tree can skip blur, leaving its shortcuts active.
|
||||||
|
blur();
|
||||||
|
};
|
||||||
|
}, [emptyState, focusContext]);
|
||||||
|
|
||||||
if (emptyState) return <div className={clsx(styles.tree, styles.emptyWrap, className)}>{empty}</div>;
|
if (emptyState) return <div className={clsx(styles.tree, styles.emptyWrap, className)}>{empty}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in new issue
Block a user