Skip to content

Commit d1085aa

Browse files
authored
Highlight VC Binders (#101)
1 parent 0008180 commit d1085aa

5 files changed

Lines changed: 104 additions & 40 deletions

File tree

client/src/webview/script.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@ export function getScript(vscode: VSCodeApi, document: Document, window: Window)
135135
const vcImplicationStepButton = target.closest?.('.vc-step-btn');
136136
if (vcImplicationStepButton) {
137137
e.stopPropagation();
138-
handleVCImplicationStepClick(vcImplicationStepButton);
138+
handleVCImplicationStepClick(vcImplicationStepButton, () => {
139+
root.querySelector<HTMLElement>('.highlight-var-btn.selected')?.classList.remove('selected');
140+
vscode.postMessage({ type: 'highlight', range: null });
141+
});
139142
return;
140143
}
141144

client/src/webview/styles.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,13 @@ export function getStyles(): string {
728728
.highlight-var-btn.selected {
729729
background-color: var(--vscode-button-background);
730730
}
731+
.vc-binder.highlight-var-btn {
732+
margin: 0;
733+
padding: 0 0.15rem;
734+
}
735+
.vc-binder.highlight-var-btn.selected {
736+
color: var(--lj-token-identifier);
737+
}
731738
.highlight-var-btn.error,
732739
.diagnostic-reveal-btn.error {
733740
background-color: color-mix(in srgb, var(--vscode-errorForeground) 80%, transparent);

client/src/webview/views/diagnostics/vc-changes.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { VCImplication } from "../../../types/vc-implications";
2+
import type { TranslationTable } from "../../../types/diagnostics";
23
import { renderHighlightedInlineExpression } from "../../highlighting";
34
import { escapeHtml } from "../../utils";
5+
import { renderSourceHighlightButton } from "../sections";
46

57
type ChangeKind = "unchanged" | "removed" | "added";
68
type DiffOperation<T> = { kind: ChangeKind; value: T };
@@ -34,11 +36,29 @@ function getImplicationLines(node: VCImplication): string[] {
3436
return lines;
3537
}
3638

37-
export function renderVCLine(line: string, className = "", predicateContent?: string): string {
39+
function renderBinder(binder: string, type: string, translationTable?: TranslationTable): string {
40+
const placement = translationTable?.[binder.slice(1)];
41+
if (!placement?.position?.file) {
42+
return `<span class="vc-node vc-binder" title="${escapeHtml(type)}">${escapeHtml(binder)}</span>`;
43+
}
44+
return renderSourceHighlightButton(
45+
escapeHtml(binder),
46+
type,
47+
placement.position,
48+
"vc-node vc-binder",
49+
);
50+
}
51+
52+
export function renderVCLine(
53+
line: string,
54+
className = "",
55+
predicateContent?: string,
56+
translationTable?: TranslationTable,
57+
): string {
3858
const { binder, type, predicate } = parseImplicationLine(line);
3959
return /*html*/`
4060
<div class="vc-line ${className}">
41-
${binder ? /*html*/`<div class="vc-binder-cell"><span class="vc-node vc-binder" title="${type}">${escapeHtml(binder)}</span></div>` : ""}
61+
${binder ? /*html*/`<div class="vc-binder-cell">${renderBinder(binder, type, translationTable)}</div>` : ""}
4262
<div class="vc-predicate-cell"><span class="vc-node">${predicateContent ?? renderHighlightedInlineExpression(predicate)}</span></div>
4363
</div>
4464
`;
@@ -168,43 +188,56 @@ function alignChangedLines(removed: string[], added: string[]): Array<[string |
168188
return lines;
169189
}
170190

171-
function renderChangedDestinationLines(removed: string[], added: string[]): string {
191+
function renderChangedDestinationLines(
192+
removed: string[],
193+
added: string[],
194+
translationTable?: TranslationTable,
195+
): string {
172196
if (added.length === 0) return "";
173197

174198
return alignChangedLines(removed, added)
175199
.map(([before, after]) => {
176200
if (after === undefined) return "";
177-
if (before === undefined) return renderVCLine(after, "vc-change-line");
201+
if (before === undefined) return renderVCLine(after, "vc-change-line", undefined, translationTable);
178202
const change = renderDestinationTokenDiff(
179203
parseImplicationLine(before).predicate,
180204
parseImplicationLine(after).predicate,
181205
);
182-
return renderVCLine(after, change.hasAddedContent ? "" : "vc-change-line", change.content);
206+
return renderVCLine(
207+
after,
208+
change.hasAddedContent ? "" : "vc-change-line",
209+
change.content,
210+
translationTable,
211+
);
183212
})
184213
.join("");
185214
}
186215

187-
export function renderImplication(node: VCImplication): string {
216+
export function renderImplication(node: VCImplication, translationTable?: TranslationTable): string {
188217
return getImplicationLines(node)
189-
.map(line => renderVCLine(line))
218+
.map(line => renderVCLine(line, "", undefined, translationTable))
190219
.join("");
191220
}
192221

193-
export function renderImplicationChange(before: VCImplication, after: VCImplication): string {
222+
export function renderImplicationChange(
223+
before: VCImplication,
224+
after: VCImplication,
225+
translationTable?: TranslationTable,
226+
): string {
194227
const operations = diffSequence(getImplicationLines(before), getImplicationLines(after));
195228
let html = "";
196229
const changed = { removed: [] as string[], added: [] as string[] };
197230

198231
const flushChanges = () => {
199-
html += renderChangedDestinationLines(changed.removed, changed.added);
232+
html += renderChangedDestinationLines(changed.removed, changed.added, translationTable);
200233
changed.removed.length = 0;
201234
changed.added.length = 0;
202235
};
203236

204237
for (const operation of operations) {
205238
if (operation.kind === "unchanged") {
206239
flushChanges();
207-
html += renderVCLine(operation.value);
240+
html += renderVCLine(operation.value, "", undefined, translationTable);
208241
continue;
209242
}
210243
changed[operation.kind].push(operation.value);

client/src/webview/views/diagnostics/vc-implications.ts

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
import type { RefinementMismatchError } from "../../../types/diagnostics";
1+
import type { RefinementMismatchError, TranslationTable } from "../../../types/diagnostics";
22
import type { VCSimplificationResult } from "../../../types/vc-implications";
33
import { renderHighlightedExpression } from "../../highlighting";
44
import { renderCodicon } from "../../icons";
55
import { escapeHtml } from "../../utils";
66
import { renderImplication, renderImplicationChange } from "./vc-changes";
77

8-
const stepIndexes = new Map<string, number>(); // errorId => step index, preserved across re-renders
9-
const simplificationSteps = new Map<string, VCSimplificationResult[]>(); // errorId => simplification steps
8+
// state to preserve across re-renders
9+
type VCState = {
10+
steps: VCSimplificationResult[];
11+
translationTable: TranslationTable;
12+
stepIndex: number;
13+
};
14+
15+
const vcStates = new Map<string, VCState>(); // errorId => VCState
1016

1117
function renderStepButton(errorId: string, step: "previous" | "next", disabled: boolean): string {
1218
const label = `${step === "previous" ? "Previous" : "Next"} simplification`;
@@ -40,45 +46,49 @@ function renderStepHeader(
4046
`;
4147
}
4248

43-
function getTargetStepIndex(errorId: string, step: string | null): number | undefined {
44-
const steps = simplificationSteps.get(errorId);
45-
if (!steps) return;
46-
47-
const index = stepIndexes.get(errorId) ?? 0;
48-
const targetIndex = step === "previous" ? index + 1 : step === "next" ? index - 1 : -1;
49-
if (targetIndex < 0 || targetIndex >= steps.length) return;
49+
function getTargetStepIndex(state: VCState, step: string | null): number | undefined {
50+
const targetIndex = step === "previous"
51+
? state.stepIndex + 1
52+
: step === "next"
53+
? state.stepIndex - 1
54+
: -1;
55+
if (targetIndex < 0 || targetIndex >= state.steps.length) return;
5056
return targetIndex;
5157
}
5258

5359
function renderSelectedStep(errorId: string, previousIndex?: number): string {
54-
const steps = simplificationSteps.get(errorId);
55-
if (!steps) return "";
60+
const state = vcStates.get(errorId);
61+
if (!state) return "";
5662

57-
const index = Math.min(stepIndexes.get(errorId) ?? 0, steps.length - 1);
58-
const current = steps[index];
63+
const { steps, translationTable, stepIndex } = state;
64+
const current = steps[stepIndex];
5965
const previous = previousIndex === undefined ? undefined : steps[previousIndex];
6066
const implication = previous
61-
? `<div class="vc-chain">${renderImplicationChange(previous.implication, current.implication)}</div>`
62-
: `<div class="vc-chain">${renderImplication(current.implication)}</div>`;
67+
? `<div class="vc-chain">${renderImplicationChange(previous.implication, current.implication, translationTable)}</div>`
68+
: `<div class="vc-chain">${renderImplication(current.implication, translationTable)}</div>`;
6369

6470
return /*html*/`
65-
${steps.length > 1 ? renderStepHeader(errorId, current, index, steps.length) : ""}
71+
${steps.length > 1 ? renderStepHeader(errorId, current, stepIndex, steps.length) : ""}
6672
${implication}
6773
`;
6874
}
6975

70-
export function handleVCImplicationStepClick(target: Element): boolean {
76+
export function handleVCImplicationStepClick(target: Element, onStepChanged?: () => void): boolean {
7177
const errorId = target.getAttribute("data-error-id");
7278
const step = target.getAttribute("data-vc-step");
7379
if (!errorId || (target as HTMLButtonElement).disabled) return false;
7480

75-
const currentIndex = stepIndexes.get(errorId) ?? 0;
76-
const targetIndex = getTargetStepIndex(errorId, step);
81+
const state = vcStates.get(errorId);
82+
if (!state) return false;
83+
84+
const currentIndex = state.stepIndex;
85+
const targetIndex = getTargetStepIndex(state, step);
7786
const container = target.closest?.(".vc-container");
7887
if (targetIndex === undefined) return false;
7988

80-
stepIndexes.set(errorId, targetIndex);
89+
state.stepIndex = targetIndex;
8190
if (container) container.innerHTML = renderSelectedStep(errorId, currentIndex);
91+
onStepChanged?.();
8292
return true;
8393
}
8494

@@ -98,10 +108,8 @@ export function renderVCImplication(
98108
for (let current: VCSimplificationResult | null = result; current; current = current.origin) {
99109
steps.push(current);
100110
}
101-
simplificationSteps.set(errorId, steps);
102-
103-
const index = Math.min(stepIndexes.get(errorId) ?? 0, steps.length - 1);
104-
stepIndexes.set(errorId, index);
111+
const stepIndex = Math.min(vcStates.get(errorId)?.stepIndex ?? 0, steps.length - 1);
112+
vcStates.set(errorId, { steps, translationTable: error.translationTable, stepIndex });
105113

106114
return /*html*/ `
107115
<div class="container vc-container" data-error-id="${errorId}">

client/src/webview/views/sections.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,30 @@ export function renderVariableHighlightButton(variable: LJVariable): string {
4545
const displayName = getSimpleName(variable.name);
4646
const position = variable.position;
4747
if (!position || !position.file) return `<code>${displayName}</code>`;
48+
return renderSourceHighlightButton(
49+
`<code>${renderHighlightedInlineExpression(displayName)}</code>`,
50+
variable.type,
51+
position,
52+
);
53+
}
54+
55+
export function renderSourceHighlightButton(
56+
content: string,
57+
title: string,
58+
position: SourcePosition & { file: string },
59+
className = "",
60+
): string {
4861
return /*html*/`
4962
<button
50-
class="highlight-var-btn"
51-
title="${variable.type}"
63+
class="highlight-var-btn${className ? ` ${className}` : ""}"
64+
title="${escapeHtml(title)}"
5265
data-start-line="${position.lineStart}"
5366
data-start-column="${position.colStart}"
5467
data-end-line="${position.lineEnd}"
5568
data-end-column="${position.colEnd}"
56-
data-file="${position.file}"
69+
data-file="${escapeHtml(position.file)}"
5770
>
58-
<code>${renderHighlightedInlineExpression(displayName)}</code>
71+
${content}
5972
</button>
6073
`;
6174
}

0 commit comments

Comments
 (0)