mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:05 +00:00
feat(debugger): Browse the debug console history and open diff settings from the keyboard (#162)
- Debug console: typed expressions are kept per project, and Browse Console History (⌥⌘E / Ctrl+Alt+E) lists them, most recent first, to put one back into the input. - Diff viewer: Diff Settings (⇧⌘D / Ctrl+Shift+D) opens the viewer options (side-by-side / unified, whitespace, highlighting, collapsing, scroll sync) as a popup menu.
This commit is contained in:
@@ -4,7 +4,7 @@ import { call, commands, isCancelled, isTauri, nextRequestId, type RunConfig, ty
|
||||
import { downloadJdkActions } from "@/platform/jdk";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { activeEditor } from "@/editor/api";
|
||||
import { maybeEditorService, notifications, toolWindowService } from "@/workbench/contributions";
|
||||
import { maybeEditorService, notifications, showStatusInfo, toolWindowService } from "@/workbench/contributions";
|
||||
import { canPlaceBreakpoint } from "./breakpoints/languages";
|
||||
import {
|
||||
removeAllBreakpoints,
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
type DebugSession,
|
||||
} from "./state";
|
||||
import { focusedDebugView } from "./ui/focusedView";
|
||||
import { browseFocusedConsoleHistory } from "./ui/expressionHistory";
|
||||
import { expressionAt, valueHint } from "./editor/valueTooltip";
|
||||
import { showHint } from "@/editor/lsp/hint";
|
||||
import { pausedLineTargets, startInlineSmartStep } from "./editor/smartStep";
|
||||
@@ -610,6 +611,16 @@ export function registerDebugActions(): () => void {
|
||||
when: "debugSessionActive",
|
||||
run: () => openEvaluateDialog(evaluateSeed()),
|
||||
},
|
||||
{
|
||||
id: "debug.browseConsoleHistory",
|
||||
title: "Browse Console History",
|
||||
category: "Debug",
|
||||
keywords: ["history", "previous expressions", "repl"],
|
||||
when: "debugSessionActive",
|
||||
run: () => {
|
||||
if (!browseFocusedConsoleHistory()) showStatusInfo(tx("Focus the debug console input first"));
|
||||
},
|
||||
},
|
||||
{
|
||||
// ⌥⌘F8: the selection (or expression at the caret) evaluated in a value hint, no dialog.
|
||||
id: "debug.quickEvaluate",
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
type DebugSession,
|
||||
} from "../state";
|
||||
import { VariablesView, type VarRoot } from "./VariablesView";
|
||||
import { pushHistory, readHistory, setConsoleHistoryBrowser } from "./expressionHistory";
|
||||
import styles from "./DebugToolWindow.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
@@ -542,8 +543,9 @@ function ConsoleView({ session }: { session: DebugSession }) {
|
||||
const scroller = useRef<HTMLDivElement>(null);
|
||||
const stick = useRef(true);
|
||||
const [input, setInput] = useState("");
|
||||
const history = useRef<string[]>([]);
|
||||
const history = useRef<string[]>(readHistory("console"));
|
||||
const historyPos = useRef(-1);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = scroller.current;
|
||||
@@ -553,7 +555,8 @@ function ConsoleView({ session }: { session: DebugSession }) {
|
||||
const submit = async () => {
|
||||
const e = input.trim();
|
||||
if (!e) return;
|
||||
history.current = [e, ...history.current.filter((h) => h !== e)].slice(0, 100);
|
||||
pushHistory("console", e);
|
||||
history.current = readHistory("console");
|
||||
historyPos.current = -1;
|
||||
setInput("");
|
||||
appendConsole(session.id, "input", `> ${e}\n`);
|
||||
@@ -565,6 +568,24 @@ function ConsoleView({ session }: { session: DebugSession }) {
|
||||
}
|
||||
};
|
||||
|
||||
/** IDEA's Browse History popup: the expressions typed before, most recent first. */
|
||||
const browseHistory = () => {
|
||||
const el = inputRef.current;
|
||||
const r = el?.getBoundingClientRect();
|
||||
const items: MenuItem[] = history.current.length
|
||||
? history.current.slice(0, 30).map((h, i) => ({
|
||||
id: String(i),
|
||||
label: h.length > 120 ? `${h.slice(0, 120)}…` : h,
|
||||
run: () => {
|
||||
setInput(h);
|
||||
historyPos.current = i;
|
||||
el?.focus();
|
||||
},
|
||||
}))
|
||||
: [{ id: "none", label: "No history", disabled: true }];
|
||||
showContextMenu({ x: r?.left ?? 120, y: (r?.top ?? 200) - 4 }, items);
|
||||
};
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
@@ -607,7 +628,10 @@ function ConsoleView({ session }: { session: DebugSession }) {
|
||||
›
|
||||
</span>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={styles.evalInput}
|
||||
onFocus={() => setConsoleHistoryBrowser(browseHistory)}
|
||||
onBlur={() => setConsoleHistoryBrowser(null)}
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Per-project history of expressions typed into the debugger (breakpoint
|
||||
* conditions, Evaluate), most recent first, as IDEA keeps them.
|
||||
* conditions, Evaluate, the debug console), most recent first, as IDEA keeps them.
|
||||
*/
|
||||
import { useProject } from "@/platform/project";
|
||||
|
||||
export type HistoryKind = "condition" | "evaluate";
|
||||
export type HistoryKind = "condition" | "evaluate" | "console";
|
||||
|
||||
const PREFIX = "openidea.debugger.history.";
|
||||
const MAX = 50;
|
||||
@@ -36,3 +36,16 @@ export function pushHistory(kind: HistoryKind, expression: string) {
|
||||
// Storage unavailable: no history.
|
||||
}
|
||||
}
|
||||
|
||||
/** Browse History (⌥⌘E) of the focused debug console input, while one has focus. */
|
||||
let browseConsoleHistory: (() => void) | null = null;
|
||||
|
||||
export function setConsoleHistoryBrowser(fn: (() => void) | null) {
|
||||
browseConsoleHistory = fn;
|
||||
}
|
||||
|
||||
export function browseFocusedConsoleHistory(): boolean {
|
||||
if (!browseConsoleHistory) return false;
|
||||
browseConsoleHistory();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { setContext } from "@/platform/context";
|
||||
import { call, commands } from "@/platform/ipc";
|
||||
import { bindingsFor, normalizeBinding, shortcutLabel, strokeOf } from "@/platform/keymap";
|
||||
import { tx } from "@/platform/i18n";
|
||||
import { IconButton, Select, Spinner, T } from "@/ui";
|
||||
import { IconButton, Select, Spinner, T, showContextMenu } from "@/ui";
|
||||
import { editorService, type EditorInput } from "@/workbench/contributions";
|
||||
import { languageFor, viewerBase } from "./cm";
|
||||
import { applyChunk, arrowControls, chunkAt, hoverRevert } from "./diffControls";
|
||||
@@ -602,9 +602,70 @@ export function DiffEditor({ input }: { input: EditorInput }) {
|
||||
const neighbour = chainNeighbour(data, dir);
|
||||
if (neighbour) void openDiff(neighbour, { preview: true });
|
||||
};
|
||||
const nav = useRef<DiffNavigator>({ next: () => step(1), prev: () => step(-1), jumpToSource, applyChange, file });
|
||||
const toolbarRef = useRef<HTMLDivElement | null>(null);
|
||||
const showSettings = () => {
|
||||
const r = toolbarRef.current?.getBoundingClientRect();
|
||||
showContextMenu({ x: r ? r.left + 8 : 120, y: r ? r.bottom : 120 }, [
|
||||
{ kind: "header", label: "Viewer" },
|
||||
{
|
||||
id: "split",
|
||||
label: "Side-by-side Viewer",
|
||||
checked: opts.mode === "split",
|
||||
run: () => setOpts({ mode: "split" }),
|
||||
},
|
||||
{
|
||||
id: "unified",
|
||||
label: "Unified Viewer",
|
||||
checked: opts.mode === "unified",
|
||||
run: () => setOpts({ mode: "unified" }),
|
||||
},
|
||||
{ kind: "separator" },
|
||||
{
|
||||
id: "ignore",
|
||||
label: "Ignore Whitespace",
|
||||
submenu: IGNORE_OPTIONS.map((o) => ({
|
||||
id: o.value,
|
||||
label: o.label,
|
||||
checked: opts.ignore === o.value,
|
||||
run: () => setOpts({ ignore: o.value }),
|
||||
})),
|
||||
},
|
||||
{
|
||||
id: "highlight",
|
||||
label: "Highlighting",
|
||||
submenu: HIGHLIGHT_OPTIONS.map((o) => ({
|
||||
id: o.value,
|
||||
label: o.label,
|
||||
checked: opts.highlight === o.value,
|
||||
run: () => setOpts({ highlight: o.value }),
|
||||
})),
|
||||
},
|
||||
{ kind: "separator" },
|
||||
{
|
||||
id: "collapse",
|
||||
label: "Collapse Unchanged Fragments",
|
||||
checked: opts.collapse,
|
||||
run: () => setOpts({ collapse: !opts.collapse }),
|
||||
},
|
||||
{
|
||||
id: "sync",
|
||||
label: "Synchronize Scrolling",
|
||||
checked: opts.syncScroll,
|
||||
disabled: opts.mode !== "split",
|
||||
run: () => setOpts({ syncScroll: !opts.syncScroll }),
|
||||
},
|
||||
]);
|
||||
};
|
||||
const nav = useRef<DiffNavigator>({
|
||||
next: () => step(1),
|
||||
prev: () => step(-1),
|
||||
jumpToSource,
|
||||
applyChange,
|
||||
file,
|
||||
showSettings,
|
||||
});
|
||||
useEffect(() => {
|
||||
nav.current = { next: () => step(1), prev: () => step(-1), jumpToSource, applyChange, file };
|
||||
nav.current = { next: () => step(1), prev: () => step(-1), jumpToSource, applyChange, file, showSettings };
|
||||
});
|
||||
|
||||
useEffect(
|
||||
@@ -638,7 +699,7 @@ export function DiffEditor({ input }: { input: EditorInput }) {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={styles.toolbar} role="toolbar" aria-label={tx("Diff")}>
|
||||
<div ref={toolbarRef} className={styles.toolbar} role="toolbar" aria-label={tx("Diff")}>
|
||||
<IconButton
|
||||
icon="arrow-up"
|
||||
title="Previous Difference"
|
||||
|
||||
@@ -125,6 +125,15 @@ export function registerCompareActions(): () => void {
|
||||
when: "diffEditorFocus",
|
||||
run: () => focusedDiffNavigator()?.prev(),
|
||||
},
|
||||
{
|
||||
id: "diff.showSettings",
|
||||
title: "Diff Settings",
|
||||
category: "Diff",
|
||||
icon: "settings",
|
||||
keywords: ["ignore whitespace", "highlighting", "side-by-side", "unified"],
|
||||
when: "diffEditorFocus",
|
||||
run: () => focusedDiffNavigator()?.showSettings(),
|
||||
},
|
||||
{
|
||||
id: "diff.nextFile",
|
||||
title: "Compare Next File",
|
||||
|
||||
@@ -8,6 +8,8 @@ export interface DiffNavigator {
|
||||
applyChange(source: "a" | "b"): boolean;
|
||||
/** Open the next / previous file of the diff chain (changes of a commit or change list). */
|
||||
file(dir: 1 | -1): void;
|
||||
/** The viewer's settings as a popup menu (⇧⌘D). */
|
||||
showSettings(): void;
|
||||
}
|
||||
|
||||
let focused: DiffNavigator | null = null;
|
||||
|
||||
@@ -282,6 +282,7 @@ export const intellijMacos: Record<string, string[]> = {
|
||||
"debug.jumpToSource": ["F4", "Meta+Down"],
|
||||
"debug.evaluate": ["Alt+F8"],
|
||||
"debug.quickEvaluate": ["Meta+Alt+F8"],
|
||||
"debug.browseConsoleHistory": ["Meta+Alt+E"],
|
||||
|
||||
// VCS
|
||||
"vcs.operations": ["Ctrl+V"],
|
||||
@@ -321,6 +322,7 @@ export const intellijMacos: Record<string, string[]> = {
|
||||
"diff.applyRightSide": ["Ctrl+Meta+Left"],
|
||||
"diff.nextFile": ["Ctrl+Shift+Right"],
|
||||
"diff.prevFile": ["Ctrl+Shift+Left"],
|
||||
"diff.showSettings": ["Meta+Shift+D"],
|
||||
// After Show Diff: ⌘D is Compare With… in the project view.
|
||||
"project.compareWith": ["Meta+D"],
|
||||
// Editing extras, image viewer (keyWhen: image focused) and Markdown editor layouts
|
||||
|
||||
@@ -276,6 +276,7 @@ export const intellijWindows: Record<string, string[]> = {
|
||||
"debug.jumpToSource": ["F4"],
|
||||
"debug.evaluate": ["Alt+F8"],
|
||||
"debug.quickEvaluate": ["Ctrl+Alt+F8"],
|
||||
"debug.browseConsoleHistory": ["Ctrl+Alt+E"],
|
||||
|
||||
// VCS
|
||||
"vcs.operations": ["Alt+`"],
|
||||
@@ -319,6 +320,7 @@ export const intellijWindows: Record<string, string[]> = {
|
||||
"diff.applyRightSide": ["Ctrl+Alt+A"],
|
||||
"diff.nextFile": ["Alt+Shift+Right"],
|
||||
"diff.prevFile": ["Alt+Shift+Left"],
|
||||
"diff.showSettings": ["Ctrl+Shift+D"],
|
||||
// After Show Diff: Ctrl+D is Compare With… in the project view.
|
||||
"project.compareWith": ["Ctrl+D"],
|
||||
// Editing extras, image viewer (keyWhen: image focused) and Markdown editor layouts
|
||||
|
||||
@@ -54,6 +54,7 @@ export const vscodeMacos: Record<string, string[]> = {
|
||||
"project.copyElement": [],
|
||||
"run.chooseDebugConfig": [],
|
||||
"debug.quickEvaluate": [],
|
||||
"debug.browseConsoleHistory": [],
|
||||
"editor.nextSplitter": [],
|
||||
"editor.prevSplitter": [],
|
||||
"toolwindow.resizeLeft": [],
|
||||
@@ -329,6 +330,7 @@ export const vscodeMacos: Record<string, string[]> = {
|
||||
"diff.applyRightSide": [],
|
||||
"diff.nextFile": [],
|
||||
"diff.prevFile": [],
|
||||
"diff.showSettings": [],
|
||||
"diff.prevDifference": ["Shift+F7"],
|
||||
"diff.jumpToSource": [],
|
||||
// HTTP Client / Database console: run the request or statement at the caret.
|
||||
|
||||
@@ -4607,5 +4607,11 @@
|
||||
"Compare Next File": "比较下一个文件",
|
||||
"Compare Previous File": "比较上一个文件",
|
||||
"Context Info": "上下文信息",
|
||||
"No context information here": "此处没有上下文信息"
|
||||
"No context information here": "此处没有上下文信息",
|
||||
"Diff Settings": "差异设置",
|
||||
"Viewer": "查看器",
|
||||
"Ignore Whitespace": "忽略空白",
|
||||
"Browse Console History": "浏览控制台历史",
|
||||
"Focus the debug console input first": "请先聚焦调试控制台输入框",
|
||||
"No history": "无历史记录"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user