mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-14 16:07:52 +00:00
fix(keymap): let the keymap own the project view's shortcuts (#2811)
The project view handled Shift+F6, F6, F4, Delete, copy/cut/paste, Copy Path and New in its own onKeyDown, so unbinding or rebinding those actions in the keymap had no effect there (a cleared Rename File still renamed on Shift+F6, a rebound one was ignored, and Eclipse's Shift+Alt+R opened the file rename). Those keys now go only through the global dispatcher and the preset bindings; the view keeps just the strokes no preset binds (Cmd+Z undoing the last file refactoring, Ctrl+Enter's New popup on macOS). The Eclipse presets bind Delete Project Item explicitly, since the hardcoded Delete used to cover for them. Fixes #2801 Co-authored-by: Inf1mum <8135225+Inf1mum@users.noreply.github.com>
This commit is contained in:
@@ -2,7 +2,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
||||
import { emitLocalIdeEvent } from "@/platform/ipc";
|
||||
import { installRepeatedDoubleClick } from "@/platform/doubleClick";
|
||||
import { getContext } from "@/platform/context";
|
||||
import { getContext, setContext } from "@/platform/context";
|
||||
import { applyKeymapSettings, installKeyboardDispatcher, useKeymap } from "@/platform/keymap";
|
||||
import { usePlatform } from "@/platform/platformInfo";
|
||||
import {
|
||||
provideEditorService,
|
||||
registerProjectViewRoot,
|
||||
@@ -54,6 +56,19 @@ let fs: MemoryFs;
|
||||
let openFile: ReturnType<typeof vi.fn>;
|
||||
let dispose: () => void;
|
||||
|
||||
const initialPlatform = usePlatform.getState();
|
||||
const initialKeymap = useKeymap.getState();
|
||||
let uninstallDispatcher: (() => void) | null = null;
|
||||
|
||||
/** Presses the presets' shortcuts: the global dispatcher runs the keymap's actions. */
|
||||
function dispatchViaKeymap() {
|
||||
usePlatform.setState({ os: "macos" });
|
||||
useKeymap.setState({ preset: "intellij-macos", overrides: {} });
|
||||
// The workbench activation that publishes this in the app does not run here.
|
||||
setContext("projectOpen", true);
|
||||
uninstallDispatcher = installKeyboardDispatcher(window);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
fs = new MemoryFs(
|
||||
@@ -78,6 +93,10 @@ beforeEach(() => {
|
||||
afterEach(() => {
|
||||
dispose();
|
||||
setProjectFsForTests(null);
|
||||
uninstallDispatcher?.();
|
||||
uninstallDispatcher = null;
|
||||
usePlatform.setState(initialPlatform);
|
||||
useKeymap.setState(initialKeymap);
|
||||
});
|
||||
|
||||
function setup() {
|
||||
@@ -153,6 +172,7 @@ describe("ProjectView", () => {
|
||||
});
|
||||
|
||||
it("pastes the files Explorer copied into the selected folder, as copies", async () => {
|
||||
dispatchViaKeymap();
|
||||
// What Explorer leaves on the clipboard: a path outside the project.
|
||||
fs.nodes.set("/elsewhere/Photo.png", { kind: "file", size: 3 });
|
||||
setOsClipboardForTests({
|
||||
@@ -176,6 +196,7 @@ describe("ProjectView", () => {
|
||||
});
|
||||
|
||||
it("clears cut dimming and disables Paste on focus after the OS clipboard becomes text", async () => {
|
||||
dispatchViaKeymap();
|
||||
let files: string[] = [];
|
||||
setOsClipboardForTests({
|
||||
files: async () => files,
|
||||
@@ -412,6 +433,7 @@ describe("ProjectView", () => {
|
||||
});
|
||||
|
||||
it("renames in IDEA's Rename dialog with Shift+F6, validating conflicts, and undoes with ⌘Z", async () => {
|
||||
dispatchViaKeymap();
|
||||
const { tree, row, rows } = setup();
|
||||
await waitFor(() => expect(rows()).toContain("README.md"));
|
||||
fireEvent.mouseDown(row("README.md"));
|
||||
@@ -439,7 +461,34 @@ describe("ProjectView", () => {
|
||||
expect(fs.nodes.has("/w/acme/GUIDE.md")).toBe(false);
|
||||
});
|
||||
|
||||
it("stops renaming with Shift+F6 once Rename File is unbound, following the keymap", async () => {
|
||||
dispatchViaKeymap();
|
||||
// Unbinding used to leave the component's hardcoded Shift+F6 in place (issue #2801).
|
||||
applyKeymapSettings("intellij-macos", { "project.rename": [] });
|
||||
const { tree, row, rows } = setup();
|
||||
await waitFor(() => expect(rows()).toContain("README.md"));
|
||||
fireEvent.mouseDown(row("README.md"));
|
||||
tree.focus();
|
||||
fireEvent.keyDown(tree, { key: "F6", code: "F6", shiftKey: true });
|
||||
expect(screen.queryByRole("dialog", { name: "Rename" })).toBeNull();
|
||||
});
|
||||
|
||||
it("renames with the shortcut the keymap rebinds Rename File to", async () => {
|
||||
dispatchViaKeymap();
|
||||
applyKeymapSettings("intellij-macos", { "project.rename": ["F2"] });
|
||||
const { tree, row, rows } = setup();
|
||||
await waitFor(() => expect(rows()).toContain("README.md"));
|
||||
fireEvent.mouseDown(row("README.md"));
|
||||
tree.focus();
|
||||
fireEvent.keyDown(tree, { key: "F6", code: "F6", shiftKey: true });
|
||||
expect(screen.queryByRole("dialog", { name: "Rename" })).toBeNull();
|
||||
fireEvent.keyDown(tree, { key: "F2", code: "F2" });
|
||||
const dialog = await screen.findByRole("dialog", { name: "Rename" });
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "Cancel" }));
|
||||
});
|
||||
|
||||
it("moves with F6 through the Move dialog", async () => {
|
||||
dispatchViaKeymap();
|
||||
const { tree, row, rows } = setup();
|
||||
await waitFor(() => expect(rows()).toContain("README.md"));
|
||||
fireEvent.mouseDown(row("README.md"));
|
||||
@@ -458,6 +507,7 @@ describe("ProjectView", () => {
|
||||
});
|
||||
|
||||
it("deletes the selection after confirmation", async () => {
|
||||
dispatchViaKeymap();
|
||||
const { tree, row, rows } = setup();
|
||||
await waitFor(() => expect(rows()).toContain("README.md"));
|
||||
fireEvent.mouseDown(row("README.md"));
|
||||
|
||||
@@ -71,16 +71,9 @@ import {
|
||||
cancelEditing,
|
||||
checkEditingName,
|
||||
commitEditing,
|
||||
copyPaths,
|
||||
copySelection,
|
||||
deleteSelection,
|
||||
dropPaths,
|
||||
importExternal,
|
||||
openFile,
|
||||
openMoveDialog,
|
||||
openRenameDialog,
|
||||
openSelection,
|
||||
paste,
|
||||
refreshOsClipboard,
|
||||
targetFolder,
|
||||
undoLastFileOperation,
|
||||
@@ -757,43 +750,20 @@ export function ProjectView() {
|
||||
return () => window.removeEventListener("focus", refresh);
|
||||
}, []);
|
||||
|
||||
// The keymap owns the action keys (Delete, Shift+F6, F6, F4, copy/paste, New …):
|
||||
// the global dispatcher runs them from the presets in the capture phase, so a
|
||||
// cleared binding stays cleared and a rebound one follows the user (issue #2801).
|
||||
// Only the strokes no preset binds stay local: ⌘Z undoing the last file
|
||||
// refactoring (`project.undoMove` is unbound) and ⌃Enter's New popup on macOS.
|
||||
const onKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>) => {
|
||||
if ((e.target as HTMLElement).tagName === "INPUT") return;
|
||||
const stroke = strokeOf(e.nativeEvent);
|
||||
let handled = true;
|
||||
switch (stroke && macStroke(stroke)) {
|
||||
case "Delete":
|
||||
case "Meta+Backspace":
|
||||
void deleteSelection();
|
||||
break;
|
||||
case "Shift+F6":
|
||||
void openRenameDialog();
|
||||
break;
|
||||
case "F6":
|
||||
openMoveDialog();
|
||||
break;
|
||||
case "Meta+Z":
|
||||
handled = undoLastFileOperation();
|
||||
break;
|
||||
case "Meta+C":
|
||||
copySelection(false);
|
||||
break;
|
||||
case "Meta+X":
|
||||
copySelection(true);
|
||||
break;
|
||||
case "Meta+V":
|
||||
void paste();
|
||||
break;
|
||||
case "Shift+Meta+C":
|
||||
void copyPaths("absolute");
|
||||
break;
|
||||
case "F4":
|
||||
case "Meta+Down":
|
||||
openSelection();
|
||||
break;
|
||||
case "Meta+N":
|
||||
case "Ctrl+Enter":
|
||||
case "Alt+Insert":
|
||||
showNewPopup();
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -23,6 +23,7 @@ export const eclipseMacos: Record<string, string[]> = {
|
||||
"code.commentBlock": ["Shift+Meta+/"],
|
||||
"build.buildProject": ["Meta+B"],
|
||||
"project.copyElement": [],
|
||||
"project.delete": ["Meta+Backspace"],
|
||||
"run.debug": ["Meta+F11"],
|
||||
"project.open": ["F3"],
|
||||
"debug.jumpToSource": ["F3"],
|
||||
|
||||
@@ -23,6 +23,7 @@ export const eclipseWindows: Record<string, string[]> = {
|
||||
"code.commentBlock": ["Shift+Ctrl+/"],
|
||||
"build.buildProject": ["Ctrl+F9", "Ctrl+B"],
|
||||
"project.copyElement": [],
|
||||
"project.delete": ["Delete"],
|
||||
"run.debug": ["Shift+Alt+D", "Shift+F9"],
|
||||
"project.open": ["F12", "Shift+Ctrl+E"],
|
||||
"debug.jumpToSource": ["F12", "Shift+Ctrl+E"],
|
||||
|
||||
Reference in New Issue
Block a user