дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
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.
Цей коміт міститься в:
1 джерело
d41daa8721
коміт
697a9b0ccb
6 files changed
+69
-7
No files matched your search
@@ -108,11 +108,15 @@
|
||||
color: var(--fg-faint);
|
||||
}
|
||||
.label {
|
||||
flex: none;
|
||||
max-width: 60%;
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.label:has(+ .preview) {
|
||||
flex: none;
|
||||
max-width: 60%;
|
||||
}
|
||||
.preview {
|
||||
min-width: 80px;
|
||||
flex: 0 1 auto;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OutlineItem } from "@/platform/ipc";
|
||||
import { lensTargets } from "./codeLens";
|
||||
|
||||
const item = (name: string, kind: OutlineItem["kind"], line: number, children: OutlineItem[] = []): OutlineItem => {
|
||||
const r = { start: { line, col: 0 }, end: { line, col: 1 } };
|
||||
return { name, kind, detail: null, range: r, selection_range: r, children };
|
||||
};
|
||||
|
||||
describe("usages lens targets", () => {
|
||||
it("skips callbacks and anonymous functions", () => {
|
||||
const items = [
|
||||
item("Cart", "class", 0, [
|
||||
item("count", "method", 1, [item("reduce() callback", "function", 2)]),
|
||||
item("<function>", "function", 3),
|
||||
]),
|
||||
];
|
||||
expect(lensTargets(items).map((t) => t.name)).toEqual(["Cart", "count"]);
|
||||
});
|
||||
});
|
||||
@@ -40,12 +40,15 @@ export interface LensTarget {
|
||||
col: number;
|
||||
}
|
||||
|
||||
/** Servers name callbacks and anonymous functions (`<function>`, `map() callback`); they have no usages. */
|
||||
const anonymous = (name: string) => /^<.*>$/.test(name) || / callback$/.test(name);
|
||||
|
||||
/** Declarations worth a usages lens, in document order. Exported for tests. */
|
||||
export function lensTargets(items: readonly OutlineItem[]): LensTarget[] {
|
||||
const out: LensTarget[] = [];
|
||||
const walk = (list: readonly OutlineItem[]) => {
|
||||
for (const it of list) {
|
||||
if (LENS_KINDS.has(it.kind)) out.push({ name: it.name, line: it.selection_range.start.line, col: it.selection_range.start.col });
|
||||
if (LENS_KINDS.has(it.kind) && !anonymous(it.name)) out.push({ name: it.name, line: it.selection_range.start.line, col: it.selection_range.start.col });
|
||||
if (it.children.length) walk(it.children);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
locateStepTargets,
|
||||
smartStepTargets,
|
||||
startInlineSmartStep,
|
||||
targetsOnLine,
|
||||
} from "../editor/smartStep";
|
||||
|
||||
const ipc = vi.hoisted(() => ({ debugStepInTarget: vi.fn() }));
|
||||
@@ -51,6 +52,14 @@ describe("locating Smart Step Into targets", () => {
|
||||
]);
|
||||
expect(locateStepTargets(line, 3, [target(1, "missing()")])).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps only the paused line's calls when the adapter lists the whole function", () => {
|
||||
const line = " total = add(total, square(i))";
|
||||
const all = [target(0, "square(i)"), target(1, "add(total, square(i))"), target(2, 'print("total", total)')];
|
||||
expect(targetsOnLine(line, 11, all).map((t) => t.id)).toEqual([0, 1]);
|
||||
expect(targetsOnLine(line, 11, [{ ...target(5, "print()"), line: 11 }, { ...target(6, "add()"), line: 12 }]).map((t) => t.id)).toEqual([5]);
|
||||
expect(targetsOnLine("pass", 0, all)).toBe(all);
|
||||
});
|
||||
});
|
||||
|
||||
describe("picking in the editor", () => {
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
} from "./state";
|
||||
import { focusedDebugView } from "./ui/focusedView";
|
||||
import { expressionAt } from "./editor/valueTooltip";
|
||||
import { startInlineSmartStep } from "./editor/smartStep";
|
||||
import { pausedLineTargets, startInlineSmartStep } from "./editor/smartStep";
|
||||
import {
|
||||
openBreakpointsDialog,
|
||||
openConfigChooser,
|
||||
@@ -92,7 +92,7 @@ async function smartStepInto() {
|
||||
await call(commands.debugStep(t.s.id, t.thread, "into"));
|
||||
return;
|
||||
}
|
||||
const targets = await call(commands.debugStepInTargets(t.s.id, frame.id));
|
||||
const targets = pausedLineTargets(await call(commands.debugStepInTargets(t.s.id, frame.id)));
|
||||
if (targets.length <= 1) {
|
||||
await call(
|
||||
targets[0]
|
||||
|
||||
@@ -23,6 +23,11 @@ export function calleeName(label: string): string | null {
|
||||
return name && [...name].every((c) => IDENT.test(c)) ? name : null;
|
||||
}
|
||||
|
||||
/** A call of `name`: `name(` not preceded by an identifier character. */
|
||||
function callPattern(name: string, flags: string): RegExp {
|
||||
return new RegExp(`(?<![\\p{L}\\p{N}_$])${name.replace(/\$/g, "\\$")}\\s*\\(`, flags);
|
||||
}
|
||||
|
||||
function identifierEnd(text: string, from: number): number {
|
||||
let i = from;
|
||||
while (i < text.length && IDENT.test(text[i]!)) i++;
|
||||
@@ -46,8 +51,7 @@ export function locateStepTargets(lineText: string, line: number, targets: StepI
|
||||
} else {
|
||||
const name = calleeName(t.label);
|
||||
if (!name) return null;
|
||||
const re = new RegExp(`(?<![\\p{L}\\p{N}_$])${name.replace(/\$/g, "\\$")}\\s*\\(`, "gu");
|
||||
for (const m of lineText.matchAll(re)) {
|
||||
for (const m of lineText.matchAll(callPattern(name, "gu"))) {
|
||||
if (used.has(m.index!)) continue;
|
||||
from = m.index!;
|
||||
to = from + name.length;
|
||||
@@ -61,6 +65,28 @@ export function locateStepTargets(lineText: string, line: number, targets: StepI
|
||||
return out.sort((a, b) => a.from - b.from);
|
||||
}
|
||||
|
||||
/**
|
||||
* Targets that belong to the paused line. Some adapters (debugpy) list every
|
||||
* call left in the function without positions; those are kept only when their
|
||||
* callee appears on the line. Unchanged when nothing would remain.
|
||||
*/
|
||||
export function targetsOnLine(lineText: string, line: number, targets: StepInTarget[]): StepInTarget[] {
|
||||
const kept = targets.filter((t) => {
|
||||
if (t.line != null) return t.line === line;
|
||||
if (t.column != null) return true;
|
||||
const name = calleeName(t.label);
|
||||
return !name || callPattern(name, "u").test(lineText);
|
||||
});
|
||||
return kept.length ? kept : targets;
|
||||
}
|
||||
|
||||
/** Targets of the paused line, when that line is open in an editor. */
|
||||
export function pausedLineTargets(targets: StepInTarget[]): StepInTarget[] {
|
||||
const { path, line } = useExecutionPoint.getState();
|
||||
const doc = path ? editorShowing(path)?.view.state.doc : null;
|
||||
return doc && line < doc.lines ? targetsOnLine(doc.line(line + 1).text, line, targets) : targets;
|
||||
}
|
||||
|
||||
/** Editor to pick in: the active one when it shows `path`, else any showing it. */
|
||||
function editorShowing(path: string): TextEditorHandle | null {
|
||||
const active = activeEditor();
|
||||
|
||||
Reference in new issue
Заблокувати користувача