дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
fix(keymap): match remaining letter shortcuts by typed character (#292)
#291 named letter keys by the typed character (e.key) when Alt is not held, so AZERTY Ctrl+Z is no longer Ctrl+W. Hard-coded handlers still compared the physical KeyboardEvent.code. A shared letterOf(e) in platform/keymap.ts now backs strokeOf and those handlers: - terminal Ctrl+Shift+C / V / T (copy, paste, new tab) and Ctrl+letter control codes - Run/Debug Configurations Ctrl/Cmd+N - browser shortcut guards (blocked reload/print etc.) Alt combos keep the physical key (Option / AltGr type symbols).
Цей коміт міститься в:
1 джерело
d1a597898d
коміт
aeec0e6759
8 files changed
+64
-18
No files matched your search
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState, type DragEvent, type KeyboardEven
|
||||
import clsx from "clsx";
|
||||
import { executeAction } from "@/platform/actions";
|
||||
import { call, commands, isTauri, type RunConfig } from "@/platform/ipc";
|
||||
import { letterOf } from "@/platform/keymap";
|
||||
import { useWorkflowSettings } from "@/platform/workflowSettings";
|
||||
import { Button, Dialog, Icon, IconButton, Input, T } from "@/ui";
|
||||
import {
|
||||
@@ -312,7 +313,8 @@ function EditConfigsDialog() {
|
||||
// IDEA: ⌘N / Alt+Insert adds a configuration, ⌥M opens "Modify options".
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
const add =
|
||||
(e.code === "KeyN" && (isMac() ? e.metaKey || e.ctrlKey : e.ctrlKey)) || (e.altKey && e.key === "Insert");
|
||||
(letterOf(e) === "N" && !e.altKey && (isMac() ? e.metaKey || e.ctrlKey : e.ctrlKey)) ||
|
||||
(e.altKey && e.key === "Insert");
|
||||
if (add && mode === "configs") {
|
||||
e.preventDefault();
|
||||
openAdd();
|
||||
|
||||
@@ -318,6 +318,17 @@ describe("Run/Debug Configurations tree", () => {
|
||||
fireEvent.click(await screen.findByText("Redirect input"));
|
||||
expect(screen.getByLabelText("Redirect input from")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("opens Add New Configuration on Ctrl+N by typed letter", () => {
|
||||
useRun.setState({ configs: [], loaded: true, editorOpen: true });
|
||||
render(<EditConfigsOverlay />);
|
||||
const tree = screen.getByRole("tree", { name: "Run configurations" });
|
||||
// Dvorak: physical KeyN types "b", physical KeyL types "n".
|
||||
fireEvent.keyDown(tree, { key: "b", code: "KeyN", ctrlKey: true });
|
||||
expect(screen.queryByLabelText("Search configuration types")).toBeNull();
|
||||
fireEvent.keyDown(tree, { key: "n", code: "KeyL", ctrlKey: true });
|
||||
expect(screen.getByLabelText("Search configuration types")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("debug-only types (Remote JVM Debug)", () => {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* there is no terminal, so there is nothing to handle.
|
||||
*/
|
||||
import { executeAction } from "@/platform/actions";
|
||||
import { effectiveBindings, strokeOf } from "@/platform/keymap";
|
||||
import { effectiveBindings, letterOf, strokeOf } from "@/platform/keymap";
|
||||
import { workflowSettings } from "@/platform/workflowSettings";
|
||||
import { terminalRuntime } from "./loader";
|
||||
import { isImeKey } from "@/platform/ime";
|
||||
@@ -53,16 +53,16 @@ export function terminalKeyAction(e: KeyLike, hasSelection: boolean): TerminalKe
|
||||
if (e.key === "Backspace") return { kind: "send", data: "\x17" };
|
||||
return { kind: "pass" };
|
||||
}
|
||||
const letter = letterOf(e);
|
||||
if (ctrl && shift && !meta && !alt) {
|
||||
if (e.code === "KeyC") return hasSelection ? { kind: "copy" } : { kind: "pass" };
|
||||
if (e.code === "KeyV") return { kind: "paste" };
|
||||
if (letter === "C") return hasSelection ? { kind: "copy" } : { kind: "pass" };
|
||||
if (letter === "V") return { kind: "paste" };
|
||||
// New Tab on Windows / Linux (IDEA keymap); plain Ctrl+T still reaches the shell.
|
||||
if (e.code === "KeyT") return { kind: "pass" };
|
||||
if (letter === "T") return { kind: "pass" };
|
||||
}
|
||||
if (ctrl && !meta && !alt) {
|
||||
// The letter the key types, so Shift doesn't change the control code and AZERTY's Ctrl+Z is ^Z, not ^W.
|
||||
const letter = /^[a-zA-Z]$/.test(e.key) ? e.key : /^Key([A-Z])$/.exec(e.code)?.[1];
|
||||
if (letter) return { kind: "send", data: String.fromCharCode(letter.toUpperCase().charCodeAt(0) - 64) };
|
||||
if (letter) return { kind: "send", data: String.fromCharCode(letter.charCodeAt(0) - 64) };
|
||||
if (e.code === "BracketLeft") return { kind: "send", data: "\x1b" };
|
||||
if (e.code === "Backslash") return { kind: "send", data: "\x1c" };
|
||||
if (e.code === "BracketRight") return { kind: "send", data: "\x1d" };
|
||||
|
||||
@@ -133,6 +133,20 @@ describe("terminal keys", () => {
|
||||
kind: "send",
|
||||
data: "\x1a",
|
||||
});
|
||||
// Non-QWERTY Ctrl+Shift+C / V / T match the typed letter, not the physical key.
|
||||
expect(terminalKeyAction(key({ key: "C", code: "KeyI", ctrlKey: true, shiftKey: true }), true)).toEqual({
|
||||
kind: "copy",
|
||||
});
|
||||
expect(terminalKeyAction(key({ key: "V", code: "KeyPeriod", ctrlKey: true, shiftKey: true }), false)).toEqual({
|
||||
kind: "paste",
|
||||
});
|
||||
expect(terminalKeyAction(key({ key: "T", code: "KeyK", ctrlKey: true, shiftKey: true }), false)).toEqual({
|
||||
kind: "pass",
|
||||
});
|
||||
expect(terminalKeyAction(key({ key: "J", code: "KeyC", ctrlKey: true, shiftKey: true }), true)).toEqual({
|
||||
kind: "send",
|
||||
data: "\x0a",
|
||||
});
|
||||
});
|
||||
|
||||
it("implements macOS conveniences", () => {
|
||||
|
||||
Reference in new issue
Заблокувати користувача