feat: Second pass aligning interactions with IntelliJ IDEA (#23)

* feat: Second pass aligning interactions with IntelliJ IDEA

Result tool windows
- Next/Previous Occurrence (⌥⌘↓/↑) across Find, Usages, Problems, TODO,
  Inspection and Hierarchy, with an "N of M" status hint
- Find: reuse the unpinned tab, Pin Tab, source preview, group by file or
  directory, exclude moves to the next result, Rerun
- Usages: tabs with pinning, exclude and preview
- Problems, TODO, Inspection: no autoscroll by default, severity-first sorting,
  context menu with quick fixes and copy description
- Trees: F4/⌘↓ jump to source; speed search Home/End and arrow handling

Find in Files, bookmarks, navigation
- Replace mode: Enter replaces the selection, ⇧⌥Enter replaces all, multi-select
- Pin Window toggle, highlighted read-only preview, search/replace history (⌥↓),
  Alt mnemonics, search context filter (comments / string literals)
- Mnemonic bookmarks (⌥F3, ⌃⇧digit, ⌃digit), ⌘F3 bookmarks popup, descriptions,
  ⌘-click gutter toggles a bookmark
- Select In popup (⌥F1); Recent Locations double-click and Delete;
  Search Everywhere remembers the query per tab

Code insight and refactoring
- Extract/Introduce continue with in-place rename and name suggestions;
  expression and occurrence choosers with live highlighting
- Safe Delete confirms first; numbered Refactor This
- Quick Documentation history, in-popup links and a Documentation tool window;
  Quick Definition switches between targets and shows the whole declaration
- Goto chooser lists symbol names and can pin to the Find window
- Parameter Info Tab/⇧Tab, intention preview, Reformat dialog and summary,
  inlay hint ⌘-click and context menu
- Live templates: Enter moves to the next field, two-step Esc, ⌘J as a lookup,
  language surrounders with mnemonics
- File Structure popup as a filterable tree; Show Usages filter and preview;
  call hierarchy jumps to call sites

Editor
- Paste re-indents each line by default; Move Statement moves whole statements
  in and out of blocks and re-indents
- Type over closing quotes of existing strings; ⌘D/⌘⌫ cover folded regions;
  ⌥⌫ deletes the preceding word
- ⌃G / ⌃⌘G whole-word rules and wrap hint; double-modifier caret cloning
- Visual-column block selection, nested Collapse All, recursive folding,
  fold selection toggle, Go to Matching Brace, gutter drag selects lines,
  sticky line and error stripe clicks, Insert toggles overwrite mode

Run, VCS, workbench
- Gutter icons show the last test result; Modify Run Configuration; test tree
  hides passed tests, context menu and assertion diff
- Stack frames resolve by fully qualified class name; context run chooser
- Inline Smart Step Into
- Git Log multi-select filters, "me" and date range; favourite branches,
  Fetch and tags in the branch popup; stash Keep index and Unstash options
- Terminal tab titles follow the shell; Distraction Free persists; gear menu;
  sticky balloons move to the notifications tool window after a while

* fix(vcs): Label restored local changes as yours after Smart Checkout; name the branch checked out

Conflicts from restoring stashed changes show the user's uncommitted
changes on the left like other operations, and the checkout notification
names the new branch instead of the previous one.

* fix: Close the merge editor on Apply; confirm before terminating a running Run tab

Like IDEA, applying a merge result closes its tab, and closing a Run tab
whose process still runs asks before terminating it (with a Settings
toggle and "Do not ask again").

* fix(terminal): Don't start a new session in the background after closing the last tab

The hidden Terminal window stays mounted, so it reopened a session as soon
as the last tab closed. It now opens the Local tab only once the window is
shown again, as IDEA does.

* fix(vcs): Start the merge result from a three-way merge; close the editor on Apply

The result pane now begins with non-conflicting changes merged and each
conflict showing the base text, resolved from the side panes, instead of
raw conflict markers. Apply now reports success, so the tab closes.

fix(search): Match File Structure filter on word starts

* fix: Limit Smart Step Into to the paused line; drop usages hints on callbacks; show full intention titles

Adapters that list every remaining call in the function (debugpy) made Smart
Step Into fall back to a chooser that offered calls from later lines. Targets
are now narrowed to the calls on the paused line, so they are picked in place.

Anonymous functions and callbacks no longer get a 'no usages' hint, and
intention titles are no longer cut short when there is room for them.

* fix: Undo / Redo from the Edit menu and Find Action use the editor's history

They ran the webview's native undo, which misses programmatic edits such as
templates, completion and refactorings, so nothing happened in the editor.
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-10-25 09:44:46 +08:00
1 джерело 81e0e8e2d0
коміт 82b6980a59
262 files changed
+13085 -1596

No files matched your search

+2 -2
Переглянути файл
@@ -60,7 +60,7 @@ export const EDITOR_EXTRA_DEFAULTS = {
caretScroll: "minimize",
softWrapFiles: "*.md; *.txt; *.rst; *.adoc",
softWrapIndent: true,
scrollPastEnd: true,
scrollPastEnd: false,
stripTrailingScope: "modified",
keepTrailingOnCaretLine: true,
removeTrailingBlankLines: false,
@@ -112,7 +112,7 @@ export const EDITOR_EXTRA_DEFAULTS = {
pairQuotes: true,
surroundSelection: true,
tabOutOfPair: true,
reformatOnPaste: "indentBlock",
reformatOnPaste: "indentEachLine",
smartIndent: true,
backspaceUnindent: "autoindent",
},
+72 -1
Переглянути файл
@@ -1,5 +1,14 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { formatBinding, installKeyboardDispatcher, isAltGrTyping, normalizeBinding, strokeOf, useKeymap } from "./keymap";
import {
formatBinding,
installKeyboardDispatcher,
isAltGrTyping,
isValidBinding,
normalizeBinding,
parseDoubleModifier,
strokeOf,
useKeymap,
} from "./keymap";
import { executeAction, registerAction } from "./actions";
import { usePlatform } from "./platformInfo";
@@ -167,6 +176,68 @@ describe("keymap", () => {
});
});
describe("double modifier with a key", () => {
let uninstall = () => {};
afterEach(() => uninstall());
it("parses, validates and formats the gesture", () => {
expect(parseDoubleModifier("alt alt+up")).toEqual({ modifier: "Alt", key: "up" });
expect(parseDoubleModifier("Ctrl Ctrl")).toEqual({ modifier: "Ctrl", key: null });
expect(parseDoubleModifier("Alt Ctrl+Up")).toBeNull();
expect(isValidBinding("Alt Alt+Up")).toBe(true);
expect(normalizeBinding("alt ALT+Down")).toBe("Alt Alt+Down");
expect(formatBinding("Alt Alt+Up", true)).toBe("⌥⌥↑");
expect(formatBinding("Ctrl Ctrl+Up", false)).toBe("Ctrl, Ctrl+Up");
});
function setup() {
const above = vi.fn();
const anything = vi.fn();
registerAction({ id: "t.cloneAbove", title: "clone above", run: above });
registerAction({ id: "t.ctrlCtrl", title: "ctrl ctrl", run: anything });
useKeymap.setState({ overrides: { "t.cloneAbove": ["Alt Alt+Up"], "t.ctrlCtrl": ["Ctrl Ctrl"] } });
let t = 1000;
uninstall = installKeyboardDispatcher(window, () => t);
const mod = (type: "keydown" | "keyup", key: "Alt" | "Control", ms: number) => {
t = ms;
const held = type === "keydown";
window.dispatchEvent(new KeyboardEvent(type, { key, code: `${key}Left`, altKey: key === "Alt" && held, ctrlKey: key === "Control" && held }));
};
const up = (ms: number) => {
t = ms;
const e = new KeyboardEvent("keydown", { key: "ArrowUp", code: "ArrowUp", altKey: true, cancelable: true });
window.dispatchEvent(e);
return e;
};
return { above, anything, mod, up };
}
it("runs while the second press is held, once per key press", async () => {
const { above, mod, up } = setup();
mod("keydown", "Alt", 1000);
mod("keyup", "Alt", 1100);
mod("keydown", "Alt", 1200);
expect(up(1500).defaultPrevented).toBe(true);
up(1700);
mod("keyup", "Alt", 1800);
await Promise.resolve();
expect(above).toHaveBeenCalledTimes(2);
// A single press is plain Alt+Up again.
mod("keydown", "Alt", 3000);
expect(up(3100).defaultPrevented).toBe(false);
});
it("fires a bare double tap of another modifier on its second release", async () => {
const { anything, mod } = setup();
mod("keydown", "Control", 1000);
mod("keyup", "Control", 1050);
mod("keydown", "Control", 1150);
mod("keyup", "Control", 1200);
await Promise.resolve();
expect(anything).toHaveBeenCalledOnce();
});
});
describe("AltGr", () => {
let uninstall = () => {};
afterEach(() => {
+85 -37
Переглянути файл
@@ -3,7 +3,8 @@
*
* Binding syntax: modifiers `Meta Ctrl Alt Shift` joined with `+` to a key, e.g.
* `Meta+Shift+O`, `Alt+F7`, `F8`. Space separates chord strokes
* (`Meta+K Meta+C`). The special binding `Shift Shift` is IDEA's double-Shift.
* (`Meta+K Meta+C`). A modifier pressed twice is `Shift Shift` (IDEA's double
* Shift, `Ctrl Ctrl`…); pressed twice and held, then a key, `Alt Alt+Up`.
* Keys are matched by physical position (`KeyboardEvent.code`), so ⌥-letter
* combos work on macOS where `event.key` would be a symbol.
*/
@@ -66,11 +67,24 @@ export const useKeymap = create<KeymapState>(() => ({ preset: defaultPreset(), o
const STROKE_RE = /^(?:(?:Ctrl|Alt|Shift|Meta)\+)*[^\s+]+$|^(?:(?:Ctrl|Alt|Shift|Meta)\+)*\+$/i;
/** Syntactic check: one or two strokes of `Mod+…+Key`, or `Shift Shift`. */
const MODIFIERS = ["Ctrl", "Alt", "Shift", "Meta"] as const;
export type Modifier = (typeof MODIFIERS)[number];
const DOUBLE_RE = /^(Ctrl|Alt|Shift|Meta) (Ctrl|Alt|Shift|Meta)(?:\+([^\s+]+|\+))?$/i;
const modifierNamed = (name: string) => MODIFIERS.find((m) => m.toLowerCase() === name.toLowerCase())!;
/** A double-modifier gesture: `Shift Shift` (key null) or `Alt Alt+Up`. */
export function parseDoubleModifier(binding: string): { modifier: Modifier; key: string | null } | null {
const m = DOUBLE_RE.exec(binding.trim());
if (!m || m[1]!.toLowerCase() !== m[2]!.toLowerCase()) return null;
const key = m[3] ?? null;
return { modifier: modifierNamed(m[1]!), key: key && key.length === 1 ? key.toUpperCase() : key };
}
/** Syntactic check: one or two strokes of `Mod+…+Key`, or a double-modifier gesture. */
export function isValidBinding(binding: unknown): binding is string {
if (typeof binding !== "string") return false;
const b = binding.trim();
if (b === "Shift Shift") return true;
if (parseDoubleModifier(b)) return true;
const strokes = b.split(/\s+/);
return strokes.length >= 1 && strokes.length <= 2 && strokes.every((s) => STROKE_RE.test(s));
}
@@ -131,15 +145,16 @@ export function effectiveBindings(): Array<[string, string]> {
return Object.entries(merged).flatMap(([id, keys]) => keys.map((k) => [normalizeBinding(k), id] as [string, string]));
}
const MOD_ORDER = ["Ctrl", "Alt", "Shift", "Meta"] as const;
const MOD_ORDER = MODIFIERS;
/** Canonical form: modifiers in fixed order, so `Shift+Meta+o` == `Meta+Shift+O`. */
export function normalizeBinding(binding: string): string {
const double = parseDoubleModifier(binding);
if (double) return `${double.modifier} ${double.modifier}${double.key ? `+${double.key}` : ""}`;
return binding
.trim()
.split(/\s+/)
.map((stroke) => {
if (stroke === "Shift" || stroke === "Shift Shift") return stroke;
const parts = stroke.split("+");
const key = parts.pop()!;
const mods = MOD_ORDER.filter((m) => parts.some((p) => p.toLowerCase() === m.toLowerCase()));
@@ -220,12 +235,13 @@ const SYMBOLS: Record<string, string> = {
};
/**
* Human display. macOS: `Meta+Shift+O` → `⇧⌘O`, `Shift Shift` → `⇧⇧`.
* Windows/Linux: `Ctrl+Shift+N`, chords as `Ctrl+K, Ctrl+I`, `Shift, Shift`.
* Human display. macOS: `Meta+Shift+O` → `⇧⌘O`, `Shift Shift` → `⇧⇧`, `Alt Alt+Up` → `⌥⌥↑`.
* Windows/Linux: `Ctrl+Shift+N`, chords as `Ctrl+K, Ctrl+I`, `Shift, Shift`, `Ctrl, Ctrl+Up`.
*/
export function formatBinding(binding: string, mac = isMacHost()): string {
if (!mac) return binding === "Shift Shift" ? "Shift, Shift" : normalizeBinding(binding).split(" ").join(", ");
if (binding === "Shift Shift") return "⇧⇧";
if (!mac) return normalizeBinding(binding).split(" ").join(", ");
const double = parseDoubleModifier(binding);
if (double) return SYMBOLS[double.modifier]!.repeat(2) + (double.key ? (SYMBOLS[double.key] ?? double.key) : "");
return normalizeBinding(binding)
.split(" ")
.map((stroke) => {
@@ -249,9 +265,18 @@ export function shortcutLabel(actionId: string): string | undefined {
const CHORD_TIMEOUT = 2000;
/** IDEA's double-modifier timing: each press/release step. */
const DOUBLE_TAP_STEP_MS = 300;
/** A Shift tap this soon after typing another key is part of typing, not a gesture. */
/** A modifier tap this soon after typing another key is part of typing, not a gesture. */
const DOUBLE_TAP_TYPING_GUARD_MS = 100;
const KEY_MODIFIER: Record<string, Modifier> = { Shift: "Shift", Control: "Ctrl", Alt: "Alt", Meta: "Meta" };
function modifierHeld(e: Pick<KeyboardEvent, "ctrlKey" | "altKey" | "shiftKey" | "metaKey">, m: Modifier): boolean {
return m === "Ctrl" ? e.ctrlKey : m === "Alt" ? e.altKey : m === "Shift" ? e.shiftKey : e.metaKey;
}
const otherModifiers = (e: Pick<KeyboardEvent, "ctrlKey" | "altKey" | "shiftKey" | "metaKey">, m: Modifier) =>
MODIFIERS.some((x) => x !== m && modifierHeld(e, x));
function isPlainTextField(target: EventTarget | null): boolean {
const el = target as HTMLElement | null;
if (!el || !el.tagName) return false;
@@ -293,44 +318,67 @@ function runIfEnabled(id: string): boolean {
/**
* Installs the global keydown dispatcher (capture phase, so IDE shortcuts win
* over CodeMirror defaults). Returns an uninstall function. `now` is the clock
* for the double-Shift gesture.
* for the double-modifier gestures.
*/
export function installKeyboardDispatcher(target: Window = window, now: () => number = () => performance.now()): () => void {
let pending: string[] = [];
let pendingTimer: ReturnType<typeof setTimeout> | undefined;
// Double Shift: 0 idle, 1 pressed, 2 released, 3 pressed again; fires on the second release.
let shiftStep = 0;
let shiftAt = 0;
/**
* Double modifier: step 1 pressed, 2 released, 3 pressed again. A bare
* gesture (`Shift Shift`) fires on the second release; while the second
* press is held, keys fire their `Alt Alt+Up` gestures.
*/
let tap: { modifier: Modifier; step: 1 | 2 | 3; at: number; typed: boolean; keyUsed: boolean } | null = null;
let otherKeyAt = -Infinity;
const reset = () => {
pending = [];
clearTimeout(pendingTimer);
};
const resetShift = () => {
shiftStep = 0;
const resetTap = () => {
tap = null;
};
const onShiftDown = (e: KeyboardEvent) => {
const onModifierDown = (e: KeyboardEvent, modifier: Modifier) => {
const t = now();
if (e.repeat || e.metaKey || e.ctrlKey || e.altKey || t - otherKeyAt < DOUBLE_TAP_TYPING_GUARD_MS) return resetShift();
if (shiftStep !== 0 && t - shiftAt > DOUBLE_TAP_STEP_MS) resetShift();
if (shiftStep === 0 || shiftStep === 2) {
shiftStep++;
shiftAt = t;
} else {
resetShift();
if (e.repeat && tap?.step === 3 && tap.modifier === modifier) return;
if (e.repeat || otherModifiers(e, modifier)) return resetTap();
const typed = t - otherKeyAt < DOUBLE_TAP_TYPING_GUARD_MS;
if (tap && (tap.modifier !== modifier || t - tap.at > DOUBLE_TAP_STEP_MS)) resetTap();
if (!tap) tap = { modifier, step: 1, at: t, typed, keyUsed: false };
else if (tap.step === 2) tap = { ...tap, step: 3, at: t, typed: tap.typed || typed };
else resetTap();
};
/** A key pressed while the second press of a modifier is held: its `Alt Alt+Up` gesture. */
const runHeldGesture = (e: KeyboardEvent): boolean => {
if (tap?.step !== 3 || !modifierHeld(e, tap.modifier) || otherModifiers(e, tap.modifier)) return false;
const key = strokeOf(e)?.split("+").pop();
if (!key) return false;
const binding = normalizeBinding(`${tap.modifier} ${tap.modifier}+${key}`);
const ids = effectiveBindings().filter(([b]) => b === binding).map(([, id]) => id);
for (const id of ids.reverse()) {
const action = getAction(id);
if (!action || !isActionKeyEnabled(action)) continue;
e.preventDefault();
e.stopPropagation();
tap.keyUsed = true;
void executeAction(id);
return true;
}
return false;
};
const onKeyDown = (e: KeyboardEvent) => {
if (suspended > 0) {
reset();
resetShift();
resetTap();
return;
}
if (e.key === "Shift") return onShiftDown(e);
resetShift();
const modifier = KEY_MODIFIER[e.key];
if (modifier) return onModifierDown(e, modifier);
if (runHeldGesture(e)) return reset();
resetTap();
otherKeyAt = e.key === "Escape" || e.key === "Tab" ? -Infinity : now();
if (e.isComposing) return;
const stroke = strokeOf(e);
@@ -374,27 +422,27 @@ export function installKeyboardDispatcher(target: Window = window, now: () => nu
};
const onKeyUp = (e: KeyboardEvent) => {
if (suspended > 0 || e.key !== "Shift" || shiftStep === 0) return;
const modifier = KEY_MODIFIER[e.key];
if (suspended > 0 || !modifier || !tap || tap.modifier !== modifier) return;
const t = now();
if (e.metaKey || e.ctrlKey || e.altKey || t - shiftAt > DOUBLE_TAP_STEP_MS) return resetShift();
if (shiftStep === 1) {
shiftStep = 2;
shiftAt = t;
if (otherModifiers(e, modifier) || t - tap.at > DOUBLE_TAP_STEP_MS) return resetTap();
if (tap.step === 1) {
tap = { ...tap, step: 2, at: t };
return;
}
const fire = shiftStep === 3;
resetShift();
const fire = tap.step === 3 && !tap.keyUsed && !tap.typed;
resetTap();
if (!fire) return;
const ids = effectiveBindings().filter(([b]) => b === "Shift Shift").map(([, id]) => id);
const ids = effectiveBindings().filter(([b]) => b === `${modifier} ${modifier}`).map(([, id]) => id);
for (const id of ids.reverse()) if (runIfEnabled(id)) return;
};
// Shift+click (extend a selection) is not half of a double Shift.
const onMouseDown = () => resetShift();
const onMouseDown = () => resetTap();
const onBlur = () => {
reset();
resetShift();
resetTap();
};
target.addEventListener("keydown", onKeyDown, true);
+12
Переглянути файл
@@ -36,6 +36,17 @@ describe("IDEA keymap parity", () => {
["project.copy", ["Meta+C"], ["Ctrl+C", "Ctrl+Insert"]],
["project.cut", ["Meta+X"], ["Ctrl+X", "Shift+Delete"]],
["project.paste", ["Meta+V"], ["Ctrl+V", "Shift+Insert"]],
["editor.matchBrace", ["Ctrl+M"], ["Ctrl+Shift+M"]],
["code.collapseRecursively", ["Meta+Alt+-"], ["Ctrl+Alt+-"]],
["code.expandRecursively", ["Meta+Alt+="], ["Ctrl+Alt+="]],
["editor.cloneCaretAbove", ["Alt Alt+Up"], ["Ctrl Ctrl+Up"]],
["editor.cloneCaretBelow", ["Alt Alt+Down"], ["Ctrl Ctrl+Down"]],
["editor.toggleInsertState", ["Insert"], ["Insert"]],
["navigate.nextOccurrence", ["Meta+Alt+Down"], ["Ctrl+Alt+Down"]],
["navigate.prevOccurrence", ["Meta+Alt+Up"], ["Ctrl+Alt+Up"]],
// Rerun in the Find / Find Usages tool windows uses the Rerun shortcut.
["find.rerun", ["Meta+R"], ["Ctrl+F5"]],
["usages.rerun", ["Meta+R"], ["Ctrl+F5"]],
])("%s", (id, mac, win) => {
expect(intellijMacos[id]).toEqual(mac);
expect(intellijWindows[id]).toEqual(win);
@@ -56,5 +67,6 @@ describe("IDEA keymap parity", () => {
order(km, "code.fold", "project.collapseAll");
order(km, "navigate.nextError", "vcs.changelist.edit");
order(km, "debug.setValue", "vcs.changelist.edit");
order(km, "find.replaceInFile", "find.rerun");
});
});
+40 -3
Переглянути файл
@@ -28,6 +28,27 @@ export const intellijMacos: Record<string, string[]> = {
"navigate.typeHierarchy": ["Ctrl+H"],
"navigate.toggleBookmark": ["F3"],
"navigate.showBookmarks": ["Meta+F3"],
"navigate.toggleBookmarkWithMnemonic": ["Alt+F3"],
"navigate.toggleBookmark0": ["Ctrl+Shift+0"],
"navigate.toggleBookmark1": ["Ctrl+Shift+1"],
"navigate.toggleBookmark2": ["Ctrl+Shift+2"],
"navigate.toggleBookmark3": ["Ctrl+Shift+3"],
"navigate.toggleBookmark4": ["Ctrl+Shift+4"],
"navigate.toggleBookmark5": ["Ctrl+Shift+5"],
"navigate.toggleBookmark6": ["Ctrl+Shift+6"],
"navigate.toggleBookmark7": ["Ctrl+Shift+7"],
"navigate.toggleBookmark8": ["Ctrl+Shift+8"],
"navigate.toggleBookmark9": ["Ctrl+Shift+9"],
"navigate.gotoBookmark0": ["Ctrl+0"],
"navigate.gotoBookmark1": ["Ctrl+1"],
"navigate.gotoBookmark2": ["Ctrl+2"],
"navigate.gotoBookmark3": ["Ctrl+3"],
"navigate.gotoBookmark4": ["Ctrl+4"],
"navigate.gotoBookmark5": ["Ctrl+5"],
"navigate.gotoBookmark6": ["Ctrl+6"],
"navigate.gotoBookmark7": ["Ctrl+7"],
"navigate.gotoBookmark8": ["Ctrl+8"],
"navigate.gotoBookmark9": ["Ctrl+9"],
"navigate.selectIn": ["Alt+F1"],
"navigate.lastEditLocation": ["Meta+Shift+Backspace"],
"navigate.nextEditLocation": [],
@@ -35,6 +56,8 @@ export const intellijMacos: Record<string, string[]> = {
"navigate.superMethod": ["Meta+U"],
"navigate.nextMethod": ["Ctrl+Shift+Down"],
"navigate.previousMethod": ["Ctrl+Shift+Up"],
"navigate.nextOccurrence": ["Meta+Alt+Down"],
"navigate.prevOccurrence": ["Meta+Alt+Up"],
// Find
"find.inFile": ["Meta+F"],
@@ -54,6 +77,7 @@ export const intellijMacos: Record<string, string[]> = {
"code.quickDefinition": ["Alt+Space", "Meta+Y"],
"code.showIntentions": ["Alt+Enter"],
"code.reformat": ["Meta+Alt+L"],
"code.reformatDialog": ["Meta+Alt+Shift+L"],
"code.optimizeImports": ["Ctrl+Alt+O"],
"code.commentLine": ["Meta+/"],
"code.commentBlock": ["Meta+Alt+/", "Ctrl+Shift+/"],
@@ -72,6 +96,8 @@ export const intellijMacos: Record<string, string[]> = {
"code.unfold": ["Meta+="],
"code.foldAll": ["Meta+Shift+-"],
"code.unfoldAll": ["Meta+Shift+="],
"code.collapseRecursively": ["Meta+Alt+-"],
"code.expandRecursively": ["Meta+Alt+="],
"code.addCaretNextOccurrence": ["Ctrl+G"],
"code.selectAllOccurrences": ["Ctrl+Meta+G"],
"code.unselectOccurrence": ["Ctrl+Shift+G"],
@@ -83,9 +109,11 @@ export const intellijMacos: Record<string, string[]> = {
"editor.scrollToCenter": ["Ctrl+L"],
"editor.codeBlockStart": ["Meta+Alt+["],
"editor.codeBlockEnd": ["Meta+Alt+]"],
// IDEA clones carets with ⌥⌥↑ / ⌥⌥↓ (double modifier); no single-stroke default.
"editor.cloneCaretAbove": [],
"editor.cloneCaretBelow": [],
// ⌥ pressed twice and held, then ↑ / ↓.
"editor.cloneCaretAbove": ["Alt Alt+Up"],
"editor.cloneCaretBelow": ["Alt Alt+Down"],
"editor.matchBrace": ["Ctrl+M"],
"editor.toggleInsertState": ["Insert"],
"code.joinLines": ["Ctrl+Shift+J"],
"code.toggleCase": ["Meta+Shift+U"],
"code.pasteFromHistory": ["Meta+Shift+V"],
@@ -184,6 +212,9 @@ export const intellijMacos: Record<string, string[]> = {
// Rerun in the Run / Debug tool window only (when / keyWhen); Meta+R stays with the editor elsewhere.
"run.rerun": ["Meta+R"],
"debug.rerun": ["Meta+R"],
// Rerun in the Find / Find Usages tool windows only (when).
"find.rerun": ["Meta+R"],
"usages.rerun": ["Meta+R"],
"run.chooseConfig": ["Ctrl+Alt+R"],
"run.editConfigs": ["Ctrl+Alt+Shift+R"],
"debug.toggleBreakpoint": ["Meta+F8"],
@@ -283,4 +314,10 @@ export const intellijMacos: Record<string, string[]> = {
"terminal.new": ["Meta+T"],
// Last: later bindings win, so ⌘N is Generate in the editor and New in tool windows.
"code.generate": ["Meta+N"],
// Find in Files popup (findPopupOpen): after the editor's Find Next, Jump to Source and
// selection keys, so these win while the popup is open.
"find.popup.nextResult": ["Meta+G"],
"find.popup.previousResult": ["Meta+Shift+G"],
"find.popup.openResult": ["F4", "Meta+Down"],
"find.popup.recentSearches": ["Alt+Down"],
};
+40 -3
Переглянути файл
@@ -29,6 +29,27 @@ export const intellijWindows: Record<string, string[]> = {
"navigate.typeHierarchy": ["Ctrl+H"],
"navigate.toggleBookmark": ["F11"],
"navigate.showBookmarks": ["Shift+F11"],
"navigate.toggleBookmarkWithMnemonic": ["Ctrl+F11"],
"navigate.toggleBookmark0": ["Ctrl+Shift+0"],
"navigate.toggleBookmark1": ["Ctrl+Shift+1"],
"navigate.toggleBookmark2": ["Ctrl+Shift+2"],
"navigate.toggleBookmark3": ["Ctrl+Shift+3"],
"navigate.toggleBookmark4": ["Ctrl+Shift+4"],
"navigate.toggleBookmark5": ["Ctrl+Shift+5"],
"navigate.toggleBookmark6": ["Ctrl+Shift+6"],
"navigate.toggleBookmark7": ["Ctrl+Shift+7"],
"navigate.toggleBookmark8": ["Ctrl+Shift+8"],
"navigate.toggleBookmark9": ["Ctrl+Shift+9"],
"navigate.gotoBookmark0": ["Ctrl+0"],
"navigate.gotoBookmark1": ["Ctrl+1"],
"navigate.gotoBookmark2": ["Ctrl+2"],
"navigate.gotoBookmark3": ["Ctrl+3"],
"navigate.gotoBookmark4": ["Ctrl+4"],
"navigate.gotoBookmark5": ["Ctrl+5"],
"navigate.gotoBookmark6": ["Ctrl+6"],
"navigate.gotoBookmark7": ["Ctrl+7"],
"navigate.gotoBookmark8": ["Ctrl+8"],
"navigate.gotoBookmark9": ["Ctrl+9"],
"navigate.selectIn": ["Alt+F1"],
"navigate.lastEditLocation": ["Ctrl+Shift+Backspace"],
"navigate.nextEditLocation": [],
@@ -36,6 +57,8 @@ export const intellijWindows: Record<string, string[]> = {
"navigate.superMethod": ["Ctrl+U"],
"navigate.nextMethod": ["Alt+Down"],
"navigate.previousMethod": ["Alt+Up"],
"navigate.nextOccurrence": ["Ctrl+Alt+Down"],
"navigate.prevOccurrence": ["Ctrl+Alt+Up"],
// Find
"find.inFile": ["Ctrl+F"],
@@ -55,6 +78,7 @@ export const intellijWindows: Record<string, string[]> = {
"code.quickDefinition": ["Ctrl+Shift+I"],
"code.showIntentions": ["Alt+Enter"],
"code.reformat": ["Ctrl+Alt+L"],
"code.reformatDialog": ["Ctrl+Alt+Shift+L"],
"code.optimizeImports": ["Ctrl+Alt+O"],
"code.commentLine": ["Ctrl+/"],
"code.commentBlock": ["Ctrl+Shift+/"],
@@ -73,6 +97,8 @@ export const intellijWindows: Record<string, string[]> = {
"code.unfold": ["Ctrl+="],
"code.foldAll": ["Ctrl+Shift+-"],
"code.unfoldAll": ["Ctrl+Shift+="],
"code.collapseRecursively": ["Ctrl+Alt+-"],
"code.expandRecursively": ["Ctrl+Alt+="],
"code.addCaretNextOccurrence": ["Alt+J"],
"code.selectAllOccurrences": ["Ctrl+Alt+Shift+J"],
"code.unselectOccurrence": ["Alt+Shift+J"],
@@ -84,9 +110,11 @@ export const intellijWindows: Record<string, string[]> = {
"editor.scrollToCenter": ["Ctrl+M"],
"editor.codeBlockStart": ["Ctrl+["],
"editor.codeBlockEnd": ["Ctrl+]"],
// IDEA clones carets with Ctrl, Ctrl+Up / Down (double modifier); no single-stroke default.
"editor.cloneCaretAbove": [],
"editor.cloneCaretBelow": [],
// Ctrl pressed twice and held, then Up / Down.
"editor.cloneCaretAbove": ["Ctrl Ctrl+Up"],
"editor.cloneCaretBelow": ["Ctrl Ctrl+Down"],
"editor.matchBrace": ["Ctrl+Shift+M"],
"editor.toggleInsertState": ["Insert"],
"code.joinLines": ["Ctrl+Shift+J"],
"code.toggleCase": ["Ctrl+Shift+U"],
"code.pasteFromHistory": ["Ctrl+Shift+V"],
@@ -182,6 +210,9 @@ export const intellijWindows: Record<string, string[]> = {
// Rerun in the Run / Debug tool window only (when / keyWhen); Ctrl+F5 stays with the editor elsewhere.
"run.rerun": ["Ctrl+F5"],
"debug.rerun": ["Ctrl+F5"],
// Rerun in the Find / Find Usages tool windows only (when).
"find.rerun": ["Ctrl+F5"],
"usages.rerun": ["Ctrl+F5"],
"run.chooseConfig": ["Alt+Shift+F10"],
"run.editConfigs": [],
"debug.toggleBreakpoint": ["Ctrl+F8"],
@@ -280,4 +311,10 @@ export const intellijWindows: Record<string, string[]> = {
"terminal.new": ["Ctrl+Shift+T"],
// Last: later bindings win, so Alt+Insert is Generate in the editor and New in tool windows.
"code.generate": ["Alt+Insert"],
// Find in Files popup (findPopupOpen): after the editor's Find Next, Jump to Source and
// selection keys, so these win while the popup is open.
"find.popup.nextResult": ["F3", "Ctrl+L"],
"find.popup.previousResult": ["Shift+F3", "Ctrl+Shift+L"],
"find.popup.openResult": ["F4"],
"find.popup.recentSearches": ["Alt+Down"],
};
+35
Переглянути файл
@@ -29,6 +29,27 @@ export const vscodeMacos: Record<string, string[]> = {
"navigate.typeHierarchy": [],
"navigate.toggleBookmark": [],
"navigate.showBookmarks": [],
"navigate.toggleBookmarkWithMnemonic": [],
"navigate.toggleBookmark0": [],
"navigate.toggleBookmark1": [],
"navigate.toggleBookmark2": [],
"navigate.toggleBookmark3": [],
"navigate.toggleBookmark4": [],
"navigate.toggleBookmark5": [],
"navigate.toggleBookmark6": [],
"navigate.toggleBookmark7": [],
"navigate.toggleBookmark8": [],
"navigate.toggleBookmark9": [],
"navigate.gotoBookmark0": [],
"navigate.gotoBookmark1": [],
"navigate.gotoBookmark2": [],
"navigate.gotoBookmark3": [],
"navigate.gotoBookmark4": [],
"navigate.gotoBookmark5": [],
"navigate.gotoBookmark6": [],
"navigate.gotoBookmark7": [],
"navigate.gotoBookmark8": [],
"navigate.gotoBookmark9": [],
"navigate.selectIn": [],
"navigate.lastEditLocation": [],
"navigate.nextEditLocation": [],
@@ -36,6 +57,8 @@ export const vscodeMacos: Record<string, string[]> = {
"navigate.superMethod": [],
"navigate.nextMethod": [],
"navigate.previousMethod": [],
"navigate.nextOccurrence": [],
"navigate.prevOccurrence": [],
// Find
"find.inFile": ["Meta+F"],
@@ -56,6 +79,7 @@ export const vscodeMacos: Record<string, string[]> = {
"code.showIntentions": ["Meta+."],
"code.generate": [],
"code.reformat": ["Alt+Shift+F"],
"code.reformatDialog": [],
"code.optimizeImports": ["Alt+Shift+O"],
"code.commentLine": ["Meta+/"],
"code.commentBlock": ["Alt+Shift+A"],
@@ -73,6 +97,8 @@ export const vscodeMacos: Record<string, string[]> = {
"code.fold": ["Meta+Alt+["],
"code.unfold": ["Meta+Alt+]"],
"code.foldAll": ["Meta+K Meta+0"],
"code.collapseRecursively": ["Meta+K Meta+["],
"code.expandRecursively": ["Meta+K Meta+]"],
"code.unfoldAll": ["Meta+K Meta+J"],
"code.addCaretNextOccurrence": ["Meta+D"],
"code.selectAllOccurrences": ["Meta+Shift+L"],
@@ -128,6 +154,8 @@ export const vscodeMacos: Record<string, string[]> = {
"editor.codeBlockEnd": [],
"editor.cloneCaretAbove": ["Meta+Alt+Up"],
"editor.cloneCaretBelow": ["Meta+Alt+Down"],
"editor.matchBrace": ["Meta+Shift+\\"],
"editor.toggleInsertState": [],
// Explorer (when: projectViewFocus)
"project.newFile": [],
@@ -200,6 +228,8 @@ export const vscodeMacos: Record<string, string[]> = {
"debug.jumpToSource": [],
"debug.evaluate": [],
"debug.rerun": [],
"find.rerun": [],
"usages.rerun": [],
// VCS
"vcs.operations": [],
@@ -274,4 +304,9 @@ export const vscodeMacos: Record<string, string[]> = {
"db.sqlScript": [],
"db.goToReferencedTable": [],
"db.collapseAll": [],
// Find in Files popup (findPopupOpen): after the editor's keys, so these win while it is open.
"find.popup.nextResult": ["Meta+G"],
"find.popup.previousResult": ["Meta+Shift+G"],
"find.popup.openResult": [],
"find.popup.recentSearches": ["Alt+Down"],
};
+2
Переглянути файл
@@ -25,6 +25,8 @@ export const vscodeWindows: Record<string, string[]> = {
"find.replaceInFile": ["Ctrl+H"],
"find.next": ["F3"],
"find.previous": ["Shift+F3"],
"find.popup.nextResult": ["F3"],
"find.popup.previousResult": ["Shift+F3"],
"code.extendSelection": ["Alt+Shift+Right"],
"code.shrinkSelection": ["Alt+Shift+Left"],
"code.fold": ["Ctrl+Shift+["],
+68 -1
Переглянути файл
@@ -109,6 +109,7 @@
"Approve…": "批准…",
"Arguments": "实参",
"Arrow function": "箭头函数",
"As new branch:": "作为新分支:",
"As typed": "按输入",
"Ask": "询问",
"Asks for a value when the tool runs": "工具运行时询问一个值",
@@ -130,6 +131,7 @@
"Auto-update if push of the current branch was rejected": "当前分支推送被拒绝时自动更新",
"Autosave": "自动保存",
"Autoscroll from Source": "从源代码自动滚动",
"Autoscroll to Source": "自动滚动到源代码",
"Available": "可用",
"Available plugins": "可用插件",
"Back": "后退",
@@ -260,7 +262,9 @@
"Children": "子项",
"Choose Content to Paste": "选择要粘贴的内容",
"Choose a data source for this console": "为此控制台选择数据源",
"Choose a start or end date": "请选择开始或结束日期",
"Choose configuration to debug": "选择要调试的配置",
"Choose configuration to run": "选择要运行的配置",
"Choose the Statement to Unwrap/Remove": "选择要解包/移除的语句",
"Choose which call on the current line to step into": "选择要步入当前行中的哪个调用",
"Class": "类",
@@ -354,6 +358,7 @@
"Compare with Branch or Revision…": "与分支或修订比较…",
"Compare with Branch…": "与分支比较…",
"Compare with Clipboard": "与剪贴板比较",
"Compare with Current": "与当前比较",
"Compare with Local": "与本地比较",
"Compilation failed: fix the errors and reload again.": "编译失败:请修复错误后重新加载。",
"Compiles and runs the project: the default jdtls runtime, Run/Debug, and JAVA_HOME for Maven and Gradle.": "用于编译和运行项目:jdtls 的默认运行时、运行/调试,以及 Maven 和 Gradle 的 JAVA_HOME。",
@@ -365,6 +370,7 @@
"Configurations": "配置",
"Configure Editor Tabs…": "配置编辑器标签页…",
"Configure Indents…": "配置缩进…",
"Configure Keymap…": "配置按键映射…",
"Configure…": "配置…",
"Confirm Drop": "确认删除",
"Confirm before exiting the IDE": "退出 IDE 前确认",
@@ -405,6 +411,7 @@
"Copy Path": "复制路径",
"Copy Path from Project Root": "复制项目根目录起的路径",
"Copy Path/Reference…": "复制路径/引用…",
"Copy Problem Description": "复制问题描述",
"Copy Reference": "复制引用",
"Copy Revision Number": "复制修订号",
"Copy Revision Numbers": "复制修订号",
@@ -437,6 +444,7 @@
"Coverage needs the desktop app": "覆盖率需要桌面应用",
"Coverage suites": "覆盖率套件",
"Create": "创建",
"Create Branch": "创建分支",
"Create Draft": "创建草稿",
"Create New Branch": "创建新分支",
"Create New Test…": "创建新测试…",
@@ -720,6 +728,8 @@
"Filter": "过滤",
"Filter TODO Items": "过滤 TODO 项",
"Filter branches": "过滤分支",
"Filter by Branch": "按分支过滤",
"Filter by Date": "按日期过滤",
"Filter by Path": "按路径过滤",
"Filter by User": "按用户过滤",
"Filter by message": "按消息过滤",
@@ -782,6 +792,7 @@
"Frames": "帧",
"From Template…": "从模板…",
"From settings.xml, else ~/.m2/repository": "取自 settings.xml,否则为 ~/.m2/repository",
"From:": "从:",
"Function": "函数",
"Function component": "函数组件",
"Function with return type": "带返回类型的函数",
@@ -824,6 +835,7 @@
"Greyscale": "灰度",
"Group": "组",
"Group by Directory": "按目录分组",
"Group by Severity": "按严重性分组",
"Groups": "组",
"Gutter Icons": "装订区域图标",
"HACK comment": "HACK 注释",
@@ -864,6 +876,7 @@
"HotSwap: compile and reload the changed classes into the running Java debug session": "HotSwap:编译已更改的类并重新加载到正在运行的 Java 调试会话中",
"Hovering a variable while suspended evaluates it in the selected frame.": "挂起时悬停变量会在所选帧中计算它。",
"IDE Updates": "IDE 更新",
"IDE and Project Settings": "IDE 和项目设置",
"IDE font": "IDE 字体",
"IDE font size": "IDE 字体大小",
"Installed {name}": "已安装 {name}",
@@ -890,6 +903,11 @@
"Incoming commits": "传入的提交",
"Indent": "缩进",
"Indent Block": "缩进块",
"Indent Each Line": "缩进每一行",
"Collapse Recursively": "递归折叠",
"Expand Recursively": "递归展开",
"Move Caret to Matching Brace": "将文本光标移至匹配的大括号",
"Toggle Insert/Overwrite": "切换插入/改写",
"Indentation": "缩进",
"Indents Detection": "缩进检测",
"Index": "索引",
@@ -959,6 +977,7 @@
"Just Restart": "仅重启",
"Keep": "保留",
"Keep Column": "保留列",
"Keep index": "保留索引",
"Keep local history for": "本地历史记录保留时长",
"Keep trailing spaces on caret line": "保留光标所在行的尾随空格",
"Key pair": "密钥对",
@@ -1117,6 +1136,7 @@
"Name:": "名称:",
"Named scope": "命名范围",
"Names (e.g. .DS_Store, *.pyc) hidden from the project tree, navigation bar, search, indexing and Git changes.": "从项目树、导航栏、搜索、索引和 Git 更改中隐藏的名称(例如 .DS_Store、*.pyc)。",
"Narrow down on typing": "输入时缩小列表范围",
"Navigate": "导航",
"Navigation": "导航",
"Navigation Bar": "导航栏",
@@ -1226,6 +1246,7 @@
"No run or test configuration of the project matches this file.": "项目中没有与此文件匹配的运行或测试配置。",
"No search results": "没有搜索结果",
"No special permissions.": "没有特殊权限。",
"No stashes": "没有储藏",
"No structure to show": "没有可显示的结构",
"No surround templates for this language": "此语言没有包围模板",
"No tools match.": "没有匹配的工具。",
@@ -1258,6 +1279,7 @@
"Offset 0x": "偏移 0x",
"Older output is discarded": "较早的输出将被丢弃",
"Only in comments": "仅在注释中",
"Only one usage in Project Files": "项目文件中只有一个用法",
"Open": "打开",
"Open Diff": "打开差异",
"Open File": "打开文件",
@@ -1309,6 +1331,8 @@
"Other": "其他",
"Other File Types": "其他文件类型",
"Other hints (chaining, closing labels…)": "其他提示(链式调用、结束标签…)",
"Other names or emails (comma-separated):": "其他名称或电子邮件(逗号分隔):",
"Other revisions (comma-separated):": "其他修订(逗号分隔):",
"Otherwise line breakpoints can only be set in files of languages that have a debugger.": "否则只能在有调试器的语言文件中设置行断点。",
"Outgoing commits": "传出的提交",
"Output": "输出",
@@ -1334,6 +1358,10 @@
"Passed as -s (mirrors, servers, proxies).": "以 -s 传入(镜像、服务器、代理)。",
"Passed to rustfmt when reformatting, e.g. --edition 2024": "重新格式化时传递给 rustfmt,例如 --edition 2024",
"Passed to the JVM running jdtls (java.jdt.ls.vmargs), e.g. -Xmx2g. A Lombok agent is added automatically when the project uses Lombok.": "传递给运行 jdtls 的 JVM (java.jdt.ls.vmargs),例如 -Xmx2g。项目使用 Lombok 时会自动添加 Lombok 代理。",
"Pop stash": "弹出储藏",
"Preview Source": "预览源代码",
"Reinstate index": "恢复索引",
"Rerun Search": "重新运行搜索",
"Right Bottom": "右下",
"Right Top": "右上",
"Runs jdtls only, like IDEA's JDK for importer; the project keeps compiling and running with the Project JDK. jdtls needs JDK 21 or newer (recent releases 25). The jdtls executable itself is set in Language Servers.": "仅用于运行 jdtls(类似 IDEA 的「导入器 JDK」);项目仍使用项目 JDK 编译和运行。jdtls 需要 JDK 21 或更高版本(较新版本需要 25)。jdtls 可执行文件本身在「语言服务器」中设置。",
@@ -1642,6 +1670,16 @@
"Restore Size": "恢复大小",
"Restore detected roots": "恢复检测到的根",
"Result": "结果",
"Select a problem to see its details": "选择一个问题以查看详情",
"Select an item to preview": "选择一项以预览",
"Severity": "严重性",
"Show Quick Fixes": "显示快速修复",
"Sort By": "排序依据",
"Tags": "标签",
"The start date is after the end date": "开始日期晚于结束日期",
"To:": "到:",
"Unstash Changes": "取消储藏更改",
"Unstash…": "取消储藏…",
"result": "个结果",
"results": "个结果",
"Results": "结果",
@@ -1855,6 +1893,7 @@
"Show Log": "显示日志",
"Show Memory Indicator Details": "显示内存指示器详细信息",
"Show Passed": "显示已通过",
"Show Preview": "显示预览",
"Show Raw": "显示原始内容",
"Show Replace": "显示替换",
"Show Run/Debug window when a process is started": "进程启动时显示运行/调试窗口",
@@ -2353,6 +2392,7 @@
"with NO BOM": "不带 BOM",
"with statement": "with 语句",
"{count} rows exported": "已导出 {count} 行",
"{index} of {total}": "第 {index} 个,共 {total} 个",
"{name} failed": "{name}失败",
"{name} has unsaved changes": "{name} 有未保存的更改",
"{name} installed": "{name} 已安装",
@@ -2451,5 +2491,32 @@
"Show Commit Message History": "显示提交消息历史记录",
"Don't warn about this target": "不再对此目标发出警告",
"Target branch": "目标分支",
"Checked out {name}": "已签出 {name}"
"Checked out {name}": "已签出 {name}",
"Pin Window": "固定窗口",
"Search Context": "搜索上下文",
"Anywhere": "任意位置",
"In Comments": "在注释中",
"In String Literals": "在字符串字面量中",
"Except Comments": "注释除外",
"Except String Literals": "字符串字面量除外",
"Except Comments and String Literals": "注释和字符串字面量除外",
"Recent Replacements": "最近的替换",
"No recent replacements": "没有最近的替换",
"↵ Replace Selected · ⇧⌥↵ Replace All · ⌘↵ Open in Find Window": "↵ 替换所选 · ⇧⌥↵ 全部替换 · ⌘↵ 在查找窗口中打开",
"Replace {count} Occurrences": "替换 {count} 处",
"Add Mnemonic Bookmark": "添加助记符书签",
"Change Mnemonic": "更改助记符",
"Assign Mnemonic": "分配助记符",
"Assigned bookmark": "已分配的书签",
"Current bookmark": "当前书签",
"No bookmarks": "没有书签",
"Edit Bookmark Description": "编辑书签描述",
"Toggle Bookmark with Mnemonic": "使用助记符切换书签",
"Toggle Bookmark {n}": "切换书签 {n}",
"Go to Bookmark {n}": "转到书签 {n}",
"Select In": "选择位置",
"Select In…": "选择位置…",
"Next Result": "下一个结果",
"Previous Result": "上一个结果",
"Open Result": "打开结果"
}
+3 -1
Переглянути файл
@@ -73,6 +73,7 @@ export interface DebuggerSettingsValid {
export interface RunSettingsValid {
confirmRerun: boolean;
confirmTerminate: boolean;
showOnStart: boolean;
terminationTimeoutMs: number;
}
@@ -211,7 +212,7 @@ export const DEFAULT_WORKFLOW: WorkflowSettings = {
skipLibraryCode: true,
python: { attachSubprocess: true, gevent: false },
},
run: { confirmRerun: true, showOnStart: true, terminationTimeoutMs: 3000 },
run: { confirmRerun: true, confirmTerminate: true, showOnStart: true, terminationTimeoutMs: 3000 },
terminal: {
startDirectory: "",
env: {},
@@ -341,6 +342,7 @@ export function validateRun(raw: unknown): RunSettingsValid {
const v = obj(raw);
return {
confirmRerun: bool(v.confirmRerun, d.confirmRerun),
confirmTerminate: bool(v.confirmTerminate, d.confirmTerminate),
showOnStart: bool(v.showOnStart, d.showOnStart),
terminationTimeoutMs: num(v.terminationTimeoutMs, d.terminationTimeoutMs, WORKFLOW_LIMITS.terminationTimeoutMs, true),
};