Compare commits

..
Author SHA1 Message Date
l0ng-ai 4a667b2cd2 fix(vcs): lay out a merge commit's file side by side, each pane scrolling
Opening a file of a merge commit from the Git log stacked its three panes (parent 1, merge result, parent 2) in a column the editor could not scroll: only the first parent's text showed, without the changes, and the wheel and arrow keys did not move it. The panes now sit side by side under a label row, each one scrolls and they stay in step.

Fixes #3108
2026-11-13 18:38:28 +08:00
4 changed files with 5 additions and 62 deletions

No files matched your search

+2 -24
View File
@@ -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: "Run test", command: "csharp.runTest", arguments: [] },
command: { title: "1 reference", command: "csharp.showReferences", arguments: [] },
},
],
});
@@ -67,27 +67,5 @@ 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("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"]);
expect(after[0]!.title).toBe("1 reference");
});
+1 -7
View File
@@ -6,12 +6,6 @@ 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: () => [],
@@ -68,7 +62,7 @@ export function serverCodeLenses(ctx: EditorDocContext): Extension {
view.dispatch({
effects: set.of(
result.flatMap((l) =>
l.command && !REFERENCE_COUNT.test(l.command.title)
l.command
? [
{
pos: posToOffset(text, l.range.start),
@@ -301,32 +301,6 @@ it("offers workspace removal for every member root", () => {
);
});
it("offers Git and Find Usages in workspace members outside the workspace folder", () => {
useProject.setState({
project: {
root: "/work/workspaces/demo",
name: "demo",
settings: {},
trusted: true,
isGit: false,
workspace: { version: 1, name: "demo", projects: ["/work/repos/a", "/work/repos/b"] },
},
});
setContext("vcsAvailable", true);
try {
for (const target of [node("/work/repos/a", true, true), node("/work/repos/a/src", true)])
expect(sub(contextMenuItems([target]), "Git")).toContain("Commit…");
expect(sub(contextMenuItems([node("/work/repos/a/x.py")]), "Git")).toContain("Annotate with Git Blame");
expect(labels(contextMenuItems([node("/work/repos/a/x.py")]))).toContain("Find Usages");
// Project structure is the workspace folder's: its roots can't name a folder outside it.
expect(labels(contextMenuItems([node("/work/repos/a/src", true)]))).not.toContain("Mark Directory as");
// Scratches stay outside.
expect(sub(contextMenuItems([node("/config/scratches/s.py")]), "Git")).toBeUndefined();
} finally {
clearContext("vcsAvailable");
}
});
it("does not offer removal on the attached primary", () => {
useProject.setState({
project: {
+2 -5
View File
@@ -151,9 +151,6 @@ export function contextMenuItems(nodes: ProjectNode[]): MenuItem[] {
const project = useProject.getState().project;
// Scratches and other contributed roots live outside the project: no VCS, roots or LSP usages.
const inProject = !!project && nodes.every((n) => isSameOrInside(n.path, project.root));
// Workspace members may live outside the workspace folder; they have VCS and usages too.
const memberRoots = project ? [project.root, ...(project.workspace?.projects ?? [])] : [];
const inWorkspace = nodes.every((n) => memberRoots.some((root) => isSameOrInside(n.path, root)));
const items: MenuItem[] = [];
const push = (...list: Entry[]) => list.forEach((i) => i && items.push(i));
@@ -231,7 +228,7 @@ export function contextMenuItems(nodes: ProjectNode[]): MenuItem[] {
shortcut: keyLabel("project.open"),
run: () => files.forEach((f) => void openFile(f.path)),
},
inWorkspace &&
inProject &&
singleFile && {
id: "findUsages",
label: "Find Usages",
@@ -341,7 +338,7 @@ export function contextMenuItems(nodes: ProjectNode[]): MenuItem[] {
);
// Git › (`vcsAvailable` also covers repositories nested below a non-Git project root)
if (inWorkspace && (project?.isGit || getContext().vcsAvailable === true)) {
if (inProject && (project?.isGit || getContext().vcsAvailable === true)) {
const arg = vcsArg(nodes);
const dirs = nodes.some((n) => n.dir);
push(