Порівняти коміти

...
Автор SHA1 Повідомлення Дата
l0ng-ai 983c0cb3f4 fix(debugger): keep the frames | variables split, put the views after the toolbar, badge live sessions
- The divider between frames and variables went back to its default on every start; it is now remembered.
- The Debug tool window's toolbar now leads with the session actions and is followed by the Threads & Variables / Console / Memory tabs.
- The Debug stripe button shows how many debug sessions are live, as the Run button does for runs.

Refs #3112
2026-11-13 18:55:30 +08:00
l0ng-ai fdc8bc2d4c fix(editor): leave reference counts to Code Vision's usages (#3139)
csharp-ls adds a "3 Reference(s)" lens above each declaration, next to Code Vision's own "3 usages". It duplicated the count, ignored the Code Vision settings, and was a second set of lens rows replaced after every edit. Language-server lenses that are only a reference count are now left out; Settings › Editor › Inlay Hints › Code Vision › Usages governs the count.

Refs #3107
2026-11-13 18:41:34 +08:00
l0ng-ai 497d4766d7 fix(vcs): lay out a merge commit's file side by side, each pane scrolling (#3138)
Opening a file of a merge commit from the Git log stacked its three panes in a column the editor could not scroll: only the first parent's text showed, without the changes, and neither the wheel nor the arrow keys moved it. The panes now sit side by side under a label row, each scrolls and they stay in step.

Fixes #3108
2026-11-13 18:38:56 +08:00
6 змінених файлів з 126 додано та 36 видалено

No files matched your search

+24 -2
Переглянути файл
@@ -46,7 +46,7 @@ it("keeps mapped lenses across edits instead of clearing them (#2710)", async ()
data: [
{
range: { start: { line: 1, col: 0 }, end: { line: 1, col: 3 } },
command: { title: "1 reference", command: "csharp.showReferences", arguments: [] },
command: { title: "Run test", command: "csharp.runTest", arguments: [] },
},
],
});
@@ -67,5 +67,27 @@ it("keeps mapped lenses across edits instead of clearing them (#2710)", async ()
const after = view.state.facet(codeLenses);
expect(after).toHaveLength(1);
expect(after[0]!.pos).toBe(posBefore + 1);
expect(after[0]!.title).toBe("1 reference");
expect(after[0]!.title).toBe("Run test");
});
it("leaves reference counts to Code Vision's usages", async () => {
vi.useFakeTimers();
const lens = (line: number, title: string) => ({
range: { start: { line, col: 0 }, end: { line, col: 1 } },
command: { title, command: "csharp.showReferences", arguments: [] },
});
ipc.lspCodeLens.mockResolvedValue({
status: "ok",
data: [lens(0, "3 Reference(s)"), lens(1, "1 reference"), lens(2, "12 references"), lens(3, "2 implementations")],
});
const view = new EditorView({
state: EditorState.create({
doc: "a\nb\nc\nd",
extensions: serverCodeLenses({ doc: 1, path: "/p/a.cs", language: "csharp", uiMode: null, readOnly: false }),
}),
parent: document.body,
});
views.push(view);
await vi.advanceTimersByTimeAsync(400);
expect(view.state.facet(codeLenses).map((l) => l.title)).toEqual(["2 implementations"]);
});
+7 -1
Переглянути файл
@@ -6,6 +6,12 @@ import { call, commands } from "@/platform/ipc";
import { onNavigationProvidersChanged } from "@/editor/navigationProviders";
import { posToOffset } from "./positions";
import { onLspRefresh, onServerSettled } from "./availability";
/**
* A server's reference count ("3 references", csharp-ls's "3 Reference(s)"): Code Vision's
* Usages shows the same count above the declaration, under its own setting.
*/
const REFERENCE_COUNT = /^\s*\d+\s+references?(\(s\))?\s*$/i;
const set = StateEffect.define<CodeLens[]>();
const field = StateField.define<CodeLens[]>({
create: () => [],
@@ -62,7 +68,7 @@ export function serverCodeLenses(ctx: EditorDocContext): Extension {
view.dispatch({
effects: set.of(
result.flatMap((l) =>
l.command
l.command && !REFERENCE_COUNT.test(l.command.title)
? [
{
pos: posToOffset(text, l.range.start),
+6
Переглянути файл
@@ -48,6 +48,11 @@ import { forgetMemory } from "./memory/memoryState";
const useAnyOverlay = () =>
useOverlays((s) => !!(s.popover || s.evaluate || s.breakpoints || s.chooser || s.attach || s.smartStep));
/** The Debug stripe button's badge, like Run's: how many debug sessions (child sessions aside) are live. */
function useLiveSessionCount(): number | null {
return useDebug((s) => Object.values(s.sessions).filter((x) => x.parent === null && isLive(x)).length || null);
}
export function activate(): () => void {
useDebugRunSessions.setState({
Console: lazyComponent(() => import("./ui/DebugConsole").then((m) => m.DebugServiceConsole)),
@@ -71,6 +76,7 @@ export function activate(): () => void {
order: 20,
component: lazyComponent(() => import("./ui/DebugToolWindow").then((m) => m.DebugToolWindow)),
headerContent: lazyComponent(() => import("./ui/DebugToolWindow").then((m) => m.DebugTabs)),
useBadge: useLiveSessionCount,
}),
registerEditorKind(heapEditorKind),
installHeapContext(),
+30 -25
Переглянути файл
@@ -176,30 +176,7 @@ export function DebugToolWindow() {
return (
<div className={styles.root}>
<div className={styles.toolbar} role="toolbar" aria-label={tx("Debugger actions")}>
<div role="tablist" aria-label={tx("Debug views")} className={styles.tabs}>
{(
[
["variables", "Threads & Variables"],
["console", "Console"],
...(hasMemory ? [["memory", "Memory"] as const] : []),
] as const
).map(([id, label]) => (
<button
key={id}
type="button"
role="tab"
id={`debug-tab-${id}`}
aria-selected={tab === id}
aria-controls={`debug-panel-${id}`}
className={clsx(styles.tab, tab === id && styles.tabActive)}
onClick={() => setView({ session: active.startedAt, status: active.status, tab: id })}
>
{tx(label)}
</button>
))}
</div>
{/* IDEA's XDebugger.ToolWindow.TopToolbar3 (+ its "More" group). */}
<span className={styles.sep} aria-hidden />
{/* IDEA's XDebugger.ToolWindow.TopToolbar3 (+ its "More" group), then the views. */}
<ToolButton action="debug.rerun" icon="rerun" />
<ToolButton action="debug.stop" icon="stop" tone="danger" />
<span className={styles.sep} aria-hidden />
@@ -233,6 +210,29 @@ export function DebugToolWindow() {
onClick={() => setMuted(!muted)}
/>
<MoreActions />
<span className={styles.sep} aria-hidden />
<div role="tablist" aria-label={tx("Debug views")} className={styles.tabs}>
{(
[
["variables", "Threads & Variables"],
["console", "Console"],
...(hasMemory ? [["memory", "Memory"] as const] : []),
] as const
).map(([id, label]) => (
<button
key={id}
type="button"
role="tab"
id={`debug-tab-${id}`}
aria-selected={tab === id}
aria-controls={`debug-panel-${id}`}
className={clsx(styles.tab, tab === id && styles.tabActive)}
onClick={() => setView({ session: active.startedAt, status: active.status, tab: id })}
>
{tx(label)}
</button>
))}
</div>
</div>
<div role="tabpanel" id={`debug-panel-${tab}`} aria-labelledby={`debug-tab-${tab}`} className={styles.panel}>
{tab === "variables" ? (
@@ -251,7 +251,12 @@ export function DebugToolWindow() {
function ThreadsAndVariables({ session }: { session: DebugSession }) {
return (
<SplitGroup orientation="horizontal" id="debug-threads-variables" className={styles.split}>
<SplitGroup
orientation="horizontal"
id="debug-threads-variables"
storageKey="openidea.debug.threadsVariables"
className={styles.split}
>
<SplitPanel id="frames" defaultSize="34%" minSize="160px">
<FramesPane session={session} />
</SplitPanel>
+48
Переглянути файл
@@ -444,6 +444,54 @@
display: flex;
flex-direction: column;
}
/* Go to a merge commit's file: parent 1 | merge result | parent 2, each pane scrolling itself. */
.mergeDiff {
flex: 1;
height: 100%;
min-height: 0;
display: flex;
flex-direction: column;
}
.mergeDiffLabels {
flex: none;
height: var(--h-row);
display: grid;
grid-template-columns: 1fr 1fr 1fr;
border-bottom: 1px solid var(--border);
color: var(--fg-secondary);
font-size: var(--fs-small);
}
.mergeDiffLabels > span {
display: flex;
align-items: center;
min-width: 0;
padding: 0 12px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.mergeDiffLabels > span + span {
border-left: 1px solid var(--border);
}
.mergeDiffPanes {
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
.mergeDiffPane {
min-width: 0;
min-height: 0;
display: flex;
overflow: hidden;
}
.mergeDiffPane + .mergeDiffPane {
border-left: 1px solid var(--border);
}
.mergeDiffPane > :global(.cm-editor) {
flex: 1;
min-width: 0;
}
.subjectLine {
color: var(--fg-strong);
font-weight: 600;
+11 -8
Переглянути файл
@@ -51,10 +51,7 @@ export function GitMergeDiff({ input }: { input: EditorInput }) {
}
for (const [i, text] of texts.entries()) {
const pane = document.createElement("div");
pane.style.cssText = "flex:1;min-width:0;display:flex;flex-direction:column";
const label = document.createElement("div");
label.textContent = `${sides[i]!.rev.slice(0, 8)} · ${sides[i]!.path}`;
pane.append(label);
pane.className = styles.mergeDiffPane!;
host.current.append(pane);
const view = new EditorView({
parent: pane,
@@ -86,10 +83,16 @@ export function GitMergeDiff({ input }: { input: EditorInput }) {
element?.replaceChildren();
};
}, [data]);
const labels = [data.parents[0]!, { rev: data.revision, path: data.path }, data.parents[1]!];
return (
<>
<div role={error ? "alert" : undefined}>{error}</div>
<div className={styles.revisionSource} style={{ display: "flex" }} ref={host} />
</>
<div className={styles.mergeDiff}>
{error && <div role="alert">{error}</div>}
<div className={styles.mergeDiffLabels}>
{labels.map((side, i) => (
<span key={i}>{`${side.rev.slice(0, 8)} · ${side.path}`}</span>
))}
</div>
<div className={styles.mergeDiffPanes} ref={host} />
</div>
);
}