mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
* 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.
549 lines
18 KiB
TypeScript
549 lines
18 KiB
TypeScript
/** Debugger actions (ids match `keymaps/intellij-macos.ts`). */
|
||
import { registerActions, type Action } from "@/platform/actions";
|
||
import { call, commands, isTauri, type RunConfig, type RunId } from "@/platform/ipc";
|
||
import { useProject } from "@/platform/project";
|
||
import { activeEditor } from "@/editor/api";
|
||
import { maybeEditorService, notifications, toolWindowService } from "@/workbench/contributions";
|
||
import { canPlaceBreakpoint } from "./breakpoints/languages";
|
||
import {
|
||
removeAllBreakpoints,
|
||
setMuted,
|
||
toggleBreakpoint,
|
||
toggleTemporaryBreakpoint,
|
||
useBreakpoints,
|
||
} from "./breakpoints/store";
|
||
import { removeAllSpecial, specialAt, toggleFieldAt, toggleMethodAt } from "./breakpoints/special";
|
||
import { fieldAt, methodAt } from "./breakpoints/symbols";
|
||
import { toggleBreakpointSmart } from "./breakpoints/toggle";
|
||
import { isJavaAttachTarget, isJavaDebuggable, javaAttachConfig, javaLaunchConfig } from "./java";
|
||
import { reloadChangedClasses } from "./hotswap";
|
||
import { debugUnsupportedReason, launchConfigFor } from "./launch";
|
||
import {
|
||
activeSession,
|
||
isLive,
|
||
rerunSession,
|
||
revealFrame,
|
||
rootOf,
|
||
sessionSupports,
|
||
showExecutionPoint,
|
||
startSession,
|
||
stopSession,
|
||
useDebug,
|
||
type DebugSession,
|
||
} from "./state";
|
||
import { focusedDebugView } from "./ui/focusedView";
|
||
import { expressionAt } from "./editor/valueTooltip";
|
||
import { pausedLineTargets, startInlineSmartStep } from "./editor/smartStep";
|
||
import {
|
||
openBreakpointsDialog,
|
||
openConfigChooser,
|
||
openEvaluateDialog,
|
||
openSmartStepChooser,
|
||
useOverlays,
|
||
} from "./ui/popoverStore";
|
||
|
||
function isRunConfig(arg: unknown): arg is RunConfig {
|
||
return !!arg && typeof arg === "object" && "command" in arg && "args" in arg && "debugAdapter" in arg;
|
||
}
|
||
|
||
function errorText(e: unknown) {
|
||
return e instanceof Error ? e.message : String(e);
|
||
}
|
||
|
||
async function report(title: string, p: Promise<unknown>) {
|
||
try {
|
||
await p;
|
||
} catch (e) {
|
||
notifications().notify({ group: "run", level: "error", title, body: errorText(e), key: "debug.action" });
|
||
}
|
||
}
|
||
|
||
/** The paused session and the thread to act on. */
|
||
function pausedTarget(): { s: DebugSession; thread: number } | null {
|
||
const s = activeSession();
|
||
if (!s || s.status !== "paused") return null;
|
||
const thread = s.selectedThread ?? s.stoppedThread ?? s.threads[0]?.id;
|
||
return thread == null ? null : { s, thread };
|
||
}
|
||
|
||
function notSupported(what: string, s: DebugSession) {
|
||
notifications().notify({
|
||
group: "run",
|
||
level: "info",
|
||
key: "debug.unsupported",
|
||
title: `${what} is not supported by ${s.adapter || "this debugger"}`,
|
||
});
|
||
}
|
||
|
||
/** The selected frame of the paused session (top frame when none is selected). */
|
||
function selectedFrame(s: DebugSession) {
|
||
return s.frames.find((f) => f.id === s.selectedFrame) ?? s.frames[0] ?? null;
|
||
}
|
||
|
||
/**
|
||
* Smart Step Into: the calls on the current line, picked in the editor (the
|
||
* dialog when they cannot be placed); one call (or none) steps straight in, like IDEA.
|
||
*/
|
||
async function smartStepInto() {
|
||
const t = pausedTarget();
|
||
if (!t) return;
|
||
const frame = selectedFrame(t.s);
|
||
if (!frame || !sessionSupports("stepInTargets", t.s)) {
|
||
await call(commands.debugStep(t.s.id, t.thread, "into"));
|
||
return;
|
||
}
|
||
const targets = pausedLineTargets(await call(commands.debugStepInTargets(t.s.id, frame.id)));
|
||
if (targets.length <= 1) {
|
||
await call(
|
||
targets[0]
|
||
? commands.debugStepInTarget(t.s.id, t.thread, targets[0].id)
|
||
: commands.debugStep(t.s.id, t.thread, "into"),
|
||
);
|
||
return;
|
||
}
|
||
if (!startInlineSmartStep(t.s.id, t.thread, targets)) openSmartStepChooser(t.s.id, t.thread, targets);
|
||
}
|
||
|
||
/** Opens the Attach to Process popup and loads the process list. */
|
||
export async function openAttachPopup() {
|
||
if (!isTauri()) {
|
||
notifications().notify({ group: "run", level: "warning", title: "Attaching needs the desktop app" });
|
||
return;
|
||
}
|
||
useOverlays.setState({ attach: { processes: null, error: null } });
|
||
try {
|
||
const processes = await call(commands.debugAttachableProcesses());
|
||
if (useOverlays.getState().attach) useOverlays.setState({ attach: { processes, error: null } });
|
||
} catch (e) {
|
||
if (useOverlays.getState().attach)
|
||
useOverlays.setState({ attach: { processes: [], error: errorText(e) || "Cannot list processes" } });
|
||
}
|
||
}
|
||
|
||
function cursor(): { path: string; line: number; selection: string } | null {
|
||
const ed = activeEditor();
|
||
if (!ed) return null;
|
||
const { state } = ed.view;
|
||
const main = state.selection.main;
|
||
return {
|
||
path: ed.ctx.path,
|
||
line: state.doc.lineAt(main.head).number - 1,
|
||
selection: state.sliceDoc(main.from, main.to),
|
||
};
|
||
}
|
||
|
||
/** Evaluate's initial text, as in IDEA: the selection, else the expression at the caret. */
|
||
function evaluateSeed(): string {
|
||
const ed = activeEditor();
|
||
if (!ed) return "";
|
||
const { state } = ed.view;
|
||
const sel = state.selection.main;
|
||
if (!sel.empty) return state.sliceDoc(sel.from, sel.to);
|
||
return expressionAt(ed.view, sel.head)?.text ?? "";
|
||
}
|
||
|
||
async function startConfig(arg: unknown) {
|
||
const root = useProject.getState().project?.root;
|
||
if (!root) return;
|
||
let config: RunConfig | null = isRunConfig(arg) ? arg : null;
|
||
if (!config) {
|
||
if (!isTauri()) {
|
||
notifications().notify({ group: "run", level: "warning", title: "Debugging needs the desktop app" });
|
||
return;
|
||
}
|
||
const configs = (await call(commands.runConfigs())).filter((c) => c.debugAdapter);
|
||
if (!configs.length) {
|
||
notifications().notify({
|
||
group: "run",
|
||
level: "info",
|
||
title: "Nothing to debug",
|
||
body: "No run configuration in this project has a debug adapter.",
|
||
});
|
||
return;
|
||
}
|
||
if (configs.length > 1) {
|
||
openConfigChooser(configs);
|
||
return;
|
||
}
|
||
config = configs[0]!;
|
||
}
|
||
if (isJavaDebuggable(config)) {
|
||
await maybeEditorService()
|
||
?.saveAll()
|
||
.catch(() => undefined);
|
||
await startSession(await javaLaunchConfig(config, root));
|
||
return;
|
||
}
|
||
const launch = launchConfigFor(config, root);
|
||
if (!launch) {
|
||
notifications().notify({
|
||
group: "run",
|
||
level: "warning",
|
||
title: `“${config.name}” can't be debugged`,
|
||
body: debugUnsupportedReason(config) ?? "It has no debug adapter.",
|
||
});
|
||
return;
|
||
}
|
||
await maybeEditorService()
|
||
?.saveAll()
|
||
.catch(() => undefined);
|
||
await startSession(launch);
|
||
}
|
||
|
||
export function registerDebugActions(): () => void {
|
||
const paused = "debugSessionPaused";
|
||
const step = (id: string, title: string, kind: "over" | "into" | "out", icon: string): Action => ({
|
||
id,
|
||
title,
|
||
category: "Debug",
|
||
icon,
|
||
when: paused,
|
||
run: () => {
|
||
const t = pausedTarget();
|
||
if (t) void report(title, call(commands.debugStep(t.s.id, t.thread, kind)));
|
||
},
|
||
});
|
||
|
||
return registerActions([
|
||
{
|
||
id: "debug.startConfig",
|
||
title: "Debug…",
|
||
category: "Debug",
|
||
icon: "debug",
|
||
description: "Start debugging a run configuration",
|
||
run: ({ arg }) => report("Cannot start debugging", startConfig(arg)),
|
||
},
|
||
{
|
||
id: "debug.attach",
|
||
title: "Attach Debugger",
|
||
category: "Debug",
|
||
hidden: true,
|
||
description:
|
||
"Attach the Java debugger to a JVM listening for JDWP (arg: { name, module, hostName, port, process })",
|
||
run: ({ arg }) =>
|
||
report(
|
||
"Cannot attach the debugger",
|
||
(async () => {
|
||
const root = useProject.getState().project?.root;
|
||
if (!root || !isJavaAttachTarget(arg)) return;
|
||
const launch = await javaAttachConfig(arg, root);
|
||
await startSession(launch, { process: (arg.process ?? null) as RunId | null, rerunnable: false });
|
||
})(),
|
||
),
|
||
},
|
||
{
|
||
id: "debug.stop",
|
||
title: "Stop",
|
||
category: "Debug",
|
||
icon: "stop",
|
||
when: "debugSessionActive",
|
||
run: () => {
|
||
const s = activeSession() ?? Object.values(useDebug.getState().sessions).find(isLive);
|
||
if (s) void stopSession(s.id);
|
||
},
|
||
},
|
||
{
|
||
id: "debug.reloadChangedClasses",
|
||
title: "Reload Changed Classes",
|
||
description: "HotSwap: compile and reload the changed classes into the running Java debug session",
|
||
category: "Debug",
|
||
icon: "refresh",
|
||
keywords: ["hotswap", "hot swap", "hot code replace"],
|
||
when: "debugCanHotSwap",
|
||
run: () => reloadChangedClasses(),
|
||
},
|
||
{
|
||
id: "debug.rerun",
|
||
title: "Rerun",
|
||
category: "Debug",
|
||
icon: "rerun",
|
||
when: "debugCanRerun",
|
||
// ⌘R / Ctrl+F5 rerun only in the Debug tool window (⌘R is Replace in the editor).
|
||
keyWhen: "activeToolWindow == 'debug'",
|
||
run: () => {
|
||
const s = activeSession();
|
||
if (s) void rerunSession(rootOf(s).id);
|
||
},
|
||
},
|
||
{
|
||
id: "debug.resume",
|
||
title: "Resume Program",
|
||
category: "Debug",
|
||
icon: "resume",
|
||
when: paused,
|
||
run: () => {
|
||
const t = pausedTarget();
|
||
if (t) void report("Resume failed", call(commands.debugResume(t.s.id, t.thread)));
|
||
},
|
||
},
|
||
{
|
||
id: "debug.pause",
|
||
title: "Pause Program",
|
||
category: "Debug",
|
||
icon: "pause",
|
||
when: "debugSessionActive && !debugSessionPaused",
|
||
run: async () => {
|
||
const s = activeSession();
|
||
if (!s || !isLive(s)) return;
|
||
let thread = s.selectedThread ?? s.threads[0]?.id;
|
||
if (thread == null) {
|
||
const threads = await call(commands.debugThreads(s.id)).catch(() => []);
|
||
thread = threads[0]?.id ?? 0;
|
||
}
|
||
await report("Pause failed", call(commands.debugPause(s.id, thread)));
|
||
},
|
||
},
|
||
step("debug.stepOver", "Step Over", "over", "step-over"),
|
||
step("debug.stepInto", "Step Into", "into", "step-into"),
|
||
step("debug.stepOut", "Step Out", "out", "step-out"),
|
||
{
|
||
id: "debug.forceStepOver",
|
||
title: "Force Step Over",
|
||
category: "Debug",
|
||
icon: "force-step-over",
|
||
description: "Step over, ignoring breakpoints in the called code",
|
||
when: paused,
|
||
run: () => {
|
||
const t = pausedTarget();
|
||
if (t) void report("Force Step Over failed", call(commands.debugForceStepOver(t.s.id, t.thread)));
|
||
},
|
||
},
|
||
{
|
||
// DAP has no "ignore stepping filters" flag: adapters step into what their
|
||
// launch options (justMyCode, skipFiles, stepFilters) allow.
|
||
id: "debug.forceStepInto",
|
||
title: "Force Step Into",
|
||
category: "Debug",
|
||
icon: "force-step-into",
|
||
when: paused,
|
||
run: () => {
|
||
const t = pausedTarget();
|
||
if (t) void report("Force Step Into failed", call(commands.debugStep(t.s.id, t.thread, "into")));
|
||
},
|
||
},
|
||
{
|
||
id: "debug.smartStepInto",
|
||
title: "Smart Step Into",
|
||
category: "Debug",
|
||
icon: "smart-step-into",
|
||
description: "Choose which call on the current line to step into",
|
||
when: paused,
|
||
run: () => void report("Smart Step Into failed", smartStepInto()),
|
||
},
|
||
{
|
||
id: "debug.showExecutionPoint",
|
||
title: "Show Execution Point",
|
||
category: "Debug",
|
||
icon: "show-execution-point",
|
||
when: paused,
|
||
run: () => {
|
||
const s = activeSession();
|
||
if (s) void showExecutionPoint(s.id);
|
||
},
|
||
},
|
||
{
|
||
id: "debug.resetFrame",
|
||
title: "Reset Frame",
|
||
category: "Debug",
|
||
icon: "reset-frame",
|
||
keywords: ["drop frame", "pop frame", "restart frame"],
|
||
description: "Return to the start of the selected frame's method",
|
||
when: paused,
|
||
run: () => {
|
||
const t = pausedTarget();
|
||
const frame = t && selectedFrame(t.s);
|
||
if (!t || !frame) return;
|
||
if (!sessionSupports("restartFrame", t.s)) {
|
||
notSupported("Reset Frame", t.s);
|
||
return;
|
||
}
|
||
void report("Reset Frame failed", call(commands.debugRestartFrame(t.s.id, frame.id)));
|
||
},
|
||
},
|
||
{
|
||
id: "debug.setValue",
|
||
title: "Set Value…",
|
||
category: "Debug",
|
||
icon: "edit",
|
||
keyWhen: "debugVariablesFocus",
|
||
when: paused,
|
||
run: () => focusedDebugView()?.setValue?.(),
|
||
},
|
||
{
|
||
id: "debug.jumpToSource",
|
||
title: "Jump to Source",
|
||
category: "Debug",
|
||
keyWhen: "debugFramesFocus || debugVariablesFocus",
|
||
when: paused,
|
||
run: () => {
|
||
const view = focusedDebugView();
|
||
if (view?.jumpToSource) {
|
||
view.jumpToSource();
|
||
return;
|
||
}
|
||
const s = activeSession();
|
||
const frame = s && selectedFrame(s);
|
||
if (frame) void revealFrame(frame, true);
|
||
},
|
||
},
|
||
{
|
||
id: "debug.attachToProcess",
|
||
title: "Attach to Process…",
|
||
category: "Debug",
|
||
icon: "attach-to-process",
|
||
description: "Attach the debugger to a running Java, Node.js, Python, Go or native process",
|
||
run: () => void openAttachPopup(),
|
||
},
|
||
{
|
||
id: "debug.runToCursor",
|
||
title: "Run to Cursor",
|
||
category: "Debug",
|
||
icon: "run-to-cursor",
|
||
when: paused,
|
||
run: () => {
|
||
const t = pausedTarget();
|
||
const c = cursor();
|
||
if (t && c)
|
||
void report("Run to Cursor failed", call(commands.debugRunTo(t.s.id, t.thread, c.path, c.line, false)));
|
||
},
|
||
},
|
||
{
|
||
id: "debug.forceRunToCursor",
|
||
title: "Force Run to Cursor",
|
||
category: "Debug",
|
||
icon: "force-run-to-cursor",
|
||
description: "Run to the caret, ignoring breakpoints on the way",
|
||
when: paused,
|
||
run: () => {
|
||
const t = pausedTarget();
|
||
const c = cursor();
|
||
if (t && c)
|
||
void report("Force Run to Cursor failed", call(commands.debugRunTo(t.s.id, t.thread, c.path, c.line, true)));
|
||
},
|
||
},
|
||
{
|
||
id: "debug.toggleBreakpoint",
|
||
title: "Toggle Line Breakpoint",
|
||
category: "Debug",
|
||
icon: "breakpoint",
|
||
run: () => {
|
||
const c = cursor();
|
||
if (!c) return;
|
||
const exists = useBreakpoints.getState().byFile[c.path]?.some((b) => b.line === c.line);
|
||
const language = activeEditor()?.ctx.language ?? "";
|
||
if (!exists && !canPlaceBreakpoint(language)) {
|
||
notifications().notify({
|
||
level: "info",
|
||
key: "debug.breakpointNotAllowed",
|
||
title: "Breakpoints can't be placed in this file",
|
||
body: "Enable Settings › Build, Execution, Deployment › Debugger › Allow placing breakpoints in any file.",
|
||
});
|
||
return;
|
||
}
|
||
const ed = activeEditor();
|
||
if (ed) toggleBreakpointSmart(c.path, c.line, language, ed.view.state.doc.toString());
|
||
else toggleBreakpoint(c.path, c.line);
|
||
},
|
||
},
|
||
{
|
||
id: "debug.toggleTemporaryBreakpoint",
|
||
title: "Toggle Temporary Line Breakpoint",
|
||
category: "Debug",
|
||
icon: "breakpoint",
|
||
description: "A line breakpoint that is removed after it is hit",
|
||
run: () => {
|
||
const c = cursor();
|
||
if (c && canPlaceBreakpoint(activeEditor()?.ctx.language ?? "")) toggleTemporaryBreakpoint(c.path, c.line);
|
||
},
|
||
},
|
||
{
|
||
id: "debug.toggleMethodBreakpoint",
|
||
title: "Toggle Method Breakpoint",
|
||
category: "Debug",
|
||
icon: "breakpoint-method",
|
||
run: () => {
|
||
const ed = activeEditor();
|
||
const c = cursor();
|
||
if (!ed || !c) return;
|
||
const existing = specialAt(c.path).find((b) => b.kind === "method" && b.line === c.line);
|
||
const method = methodAt(ed.ctx.language, ed.view.state.doc.toString(), c.line);
|
||
if (!existing && !method) {
|
||
notifications().notify({
|
||
level: "info",
|
||
key: "debug.noMethod",
|
||
title: "No method declaration on this line",
|
||
body: "Put the caret on a method declaration, or add a method breakpoint by name in View Breakpoints.",
|
||
});
|
||
return;
|
||
}
|
||
toggleMethodAt(c.path, c.line, method?.name ?? "");
|
||
},
|
||
},
|
||
{
|
||
id: "debug.toggleFieldWatchpoint",
|
||
title: "Toggle Field Watchpoint",
|
||
category: "Debug",
|
||
icon: "breakpoint-field",
|
||
run: () => {
|
||
const ed = activeEditor();
|
||
const c = cursor();
|
||
if (!ed || !c) return;
|
||
const existing = specialAt(c.path).find((b) => b.kind === "field" && b.line === c.line);
|
||
const field = fieldAt(ed.ctx.language, ed.view.state.doc.toString(), c.line);
|
||
if (!existing && !field) {
|
||
notifications().notify({
|
||
level: "info",
|
||
key: "debug.noField",
|
||
title: "No field declaration on this line",
|
||
body: "Field watchpoints can be set on Java field declarations, or from the Variables view while suspended.",
|
||
});
|
||
return;
|
||
}
|
||
toggleFieldAt(c.path, c.line, field ?? { dataId: "", label: "" });
|
||
},
|
||
},
|
||
{
|
||
id: "debug.viewBreakpoints",
|
||
title: "View Breakpoints…",
|
||
category: "Debug",
|
||
icon: "breakpoint",
|
||
run: () => {
|
||
const c = cursor();
|
||
const bp = c ? useBreakpoints.getState().byFile[c.path]?.find((b) => b.line === c.line) : undefined;
|
||
openBreakpointsDialog(c && bp ? { path: c.path, id: bp.id } : undefined);
|
||
},
|
||
},
|
||
{
|
||
id: "debug.muteBreakpoints",
|
||
title: "Mute Breakpoints",
|
||
category: "Debug",
|
||
keywords: ["unmute", "disable all"],
|
||
toggled: () => useBreakpoints.getState().muted,
|
||
run: () => setMuted(!useBreakpoints.getState().muted),
|
||
},
|
||
{
|
||
id: "debug.removeAllBreakpoints",
|
||
title: "Remove All Breakpoints",
|
||
category: "Debug",
|
||
run: () => {
|
||
removeAllBreakpoints();
|
||
removeAllSpecial();
|
||
},
|
||
},
|
||
{
|
||
id: "debug.evaluate",
|
||
title: "Evaluate Expression…",
|
||
category: "Debug",
|
||
when: "debugSessionActive",
|
||
run: () => openEvaluateDialog(evaluateSeed()),
|
||
},
|
||
{
|
||
id: "debug.showToolWindow",
|
||
title: "Show Debug Tool Window",
|
||
category: "Debug",
|
||
hidden: true,
|
||
run: () => toolWindowService().show("debug", true),
|
||
},
|
||
]);
|
||
}
|