mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
feat(debugger): console actions in a toolbar on the left of the debug Console
Scroll to End, Soft-Wrap and Clear sat at the far right of the evaluate row, easy to miss or cut off in a narrow window. The Console tab now has the same vertical toolbar as the Run window: Find, Scroll to End, Soft-Wrap and Clear.
This commit is contained in:
@@ -365,111 +365,120 @@ export function DebugConsole({ session, interactive = true }: { session: DebugSe
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.console}>
|
||||
{findOpen && (
|
||||
<ConsoleFindBar
|
||||
query={query}
|
||||
onChange={changeQuery}
|
||||
options={findOptions}
|
||||
onOptionsChange={changeOptions}
|
||||
count={found.matches.length}
|
||||
active={activeIndex}
|
||||
overflow={found.overflow}
|
||||
invalid={found.invalid}
|
||||
inputRef={findInput}
|
||||
onNext={() => goToMatch(1)}
|
||||
onPrevious={() => goToMatch(-1)}
|
||||
onClose={closeFind}
|
||||
/>
|
||||
)}
|
||||
<div className={styles.consoleArea}>
|
||||
<div
|
||||
ref={scroller}
|
||||
className={clsx(styles.consoleOutput, ansi.palette, !softWrap && styles.noWrap)}
|
||||
onScroll={(e) => {
|
||||
if (programmatic.current) return;
|
||||
const el = e.currentTarget;
|
||||
const atEnd = el.scrollHeight - el.scrollTop - el.clientHeight < 24;
|
||||
if (atEnd !== stick.current) setFollow(atEnd);
|
||||
}}
|
||||
onContextMenu={onContextMenu}
|
||||
role="log"
|
||||
onKeyDown={onOutputKeyDown}
|
||||
aria-label={tx("Debug console")}
|
||||
tabIndex={0}
|
||||
className={styles.consoleToolbar}
|
||||
role="toolbar"
|
||||
aria-orientation="vertical"
|
||||
aria-label={tx("Console actions")}
|
||||
>
|
||||
{!lines?.length && session.launch?.request === "attach" && (
|
||||
<T>The attached process may not provide standard output to the debugger.</T>
|
||||
)}
|
||||
<div ref={inner} className={styles.consoleInner} style={{ height: virtualizer.getTotalSize() }}>
|
||||
{virtualizer.getVirtualItems().map((item) => {
|
||||
const l = lines?.[item.index];
|
||||
if (!l) return null;
|
||||
return (
|
||||
<span
|
||||
key={item.key}
|
||||
data-index={item.index}
|
||||
ref={virtualizer.measureElement}
|
||||
className={clsx(styles.out, styles[`cat-${l.category}`])}
|
||||
style={{ transform: `translateY(${item.start}px)` }}
|
||||
>
|
||||
{findSegments(l.spans, marksByLine.get(item.index) ?? [], current).map(segmentNode)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.repl}>
|
||||
{interactive && (
|
||||
<>
|
||||
<span className={styles.prompt} aria-hidden>
|
||||
›
|
||||
</span>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={styles.evalInput}
|
||||
onFocus={() => setConsoleHistoryBrowser(browseHistory)}
|
||||
onBlur={() => {
|
||||
setConsoleHistoryBrowser(null);
|
||||
completion.close();
|
||||
}}
|
||||
value={input}
|
||||
onChange={(e) => {
|
||||
setInput(e.target.value);
|
||||
completion.onInput();
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={isLive(session) ? "Evaluate in the debug console" : "Session finished"}
|
||||
disabled={!isLive(session)}
|
||||
aria-label={tx("Debug console input")}
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{completion.popup}
|
||||
</>
|
||||
)}
|
||||
<IconButton
|
||||
icon="search"
|
||||
iconSize={14}
|
||||
title="Find…"
|
||||
shortcut={formatBinding(hostBinding("Meta+F"))}
|
||||
pressed={findOpen}
|
||||
onClick={() => (findOpen ? closeFind() : openFind())}
|
||||
/>
|
||||
<IconButton
|
||||
icon="arrow-down"
|
||||
iconSize={14}
|
||||
title="Scroll to End"
|
||||
size="small"
|
||||
iconSize={12}
|
||||
pressed={follow}
|
||||
onClick={() => setFollow((v) => !v)}
|
||||
/>
|
||||
<IconButton
|
||||
icon="soft-wrap"
|
||||
iconSize={14}
|
||||
title="Soft-Wrap"
|
||||
size="small"
|
||||
iconSize={12}
|
||||
pressed={softWrap}
|
||||
onClick={() => setWrapOverride(!softWrap)}
|
||||
/>
|
||||
<IconButton
|
||||
icon="trash"
|
||||
title="Clear Console"
|
||||
size="small"
|
||||
iconSize={12}
|
||||
onClick={() => clearConsole(session.id)}
|
||||
/>
|
||||
<IconButton icon="trash" iconSize={14} title="Clear Console" onClick={() => clearConsole(session.id)} />
|
||||
</div>
|
||||
<div className={styles.console}>
|
||||
{findOpen && (
|
||||
<ConsoleFindBar
|
||||
query={query}
|
||||
onChange={changeQuery}
|
||||
options={findOptions}
|
||||
onOptionsChange={changeOptions}
|
||||
count={found.matches.length}
|
||||
active={activeIndex}
|
||||
overflow={found.overflow}
|
||||
invalid={found.invalid}
|
||||
inputRef={findInput}
|
||||
onNext={() => goToMatch(1)}
|
||||
onPrevious={() => goToMatch(-1)}
|
||||
onClose={closeFind}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
ref={scroller}
|
||||
className={clsx(styles.consoleOutput, ansi.palette, !softWrap && styles.noWrap)}
|
||||
onScroll={(e) => {
|
||||
if (programmatic.current) return;
|
||||
const el = e.currentTarget;
|
||||
const atEnd = el.scrollHeight - el.scrollTop - el.clientHeight < 24;
|
||||
if (atEnd !== stick.current) setFollow(atEnd);
|
||||
}}
|
||||
onContextMenu={onContextMenu}
|
||||
role="log"
|
||||
onKeyDown={onOutputKeyDown}
|
||||
aria-label={tx("Debug console")}
|
||||
tabIndex={0}
|
||||
>
|
||||
{!lines?.length && session.launch?.request === "attach" && (
|
||||
<T>The attached process may not provide standard output to the debugger.</T>
|
||||
)}
|
||||
<div ref={inner} className={styles.consoleInner} style={{ height: virtualizer.getTotalSize() }}>
|
||||
{virtualizer.getVirtualItems().map((item) => {
|
||||
const l = lines?.[item.index];
|
||||
if (!l) return null;
|
||||
return (
|
||||
<span
|
||||
key={item.key}
|
||||
data-index={item.index}
|
||||
ref={virtualizer.measureElement}
|
||||
className={clsx(styles.out, styles[`cat-${l.category}`])}
|
||||
style={{ transform: `translateY(${item.start}px)` }}
|
||||
>
|
||||
{findSegments(l.spans, marksByLine.get(item.index) ?? [], current).map(segmentNode)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.repl}>
|
||||
{interactive && (
|
||||
<>
|
||||
<span className={styles.prompt} aria-hidden>
|
||||
›
|
||||
</span>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={styles.evalInput}
|
||||
onFocus={() => setConsoleHistoryBrowser(browseHistory)}
|
||||
onBlur={() => {
|
||||
setConsoleHistoryBrowser(null);
|
||||
completion.close();
|
||||
}}
|
||||
value={input}
|
||||
onChange={(e) => {
|
||||
setInput(e.target.value);
|
||||
completion.onInput();
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={isLive(session) ? "Evaluate in the debug console" : "Session finished"}
|
||||
disabled={!isLive(session)}
|
||||
aria-label={tx("Debug console input")}
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{completion.popup}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -218,6 +218,23 @@
|
||||
}
|
||||
|
||||
/* console */
|
||||
/* The Console tab: actions in a vertical strip on the left, as in the Run window. */
|
||||
.consoleArea {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
}
|
||||
.consoleToolbar {
|
||||
flex: none;
|
||||
width: 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 4px 0;
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
.console {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
@@ -1935,6 +1935,7 @@
|
||||
"Run a configuration with coverage to see which lines its tests execute.": "使用覆盖率运行配置,即可查看其测试执行了哪些行。",
|
||||
"Run a request from an .http file with the gutter ▶ or ⌘↩.": "使用装订区域中的 ▶ 或 ⌘↩ 运行 .http 文件中的请求。",
|
||||
"Run actions": "运行操作",
|
||||
"Console actions": "控制台操作",
|
||||
"Run and Debug": "运行和调试",
|
||||
"Run code cleanup": "运行代码清理",
|
||||
"Run configurations": "运行配置",
|
||||
|
||||
Reference in New Issue
Block a user