Skip to content

Commit 9edfd99

Browse files
authored
Add Translation Table to Webview (#32)
1 parent 3959760 commit 9edfd99

File tree

8 files changed

+150
-25
lines changed

8 files changed

+150
-25
lines changed

client/src/webview/renderers/correct.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { renderShowAllButton } from "./diagnostics/show-all-button";
1+
import { renderShowAllButton } from "./diagnostics/utils";
22

33
export function getCorrectView(showAllDiagnostics: boolean): string {
44
return /*html*/`

client/src/webview/renderers/diagnostics/derivation-nodes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ function renderJsonTree(
6161
// UnaryDerivationNode
6262
if ("operand" in node) {
6363
const operandHtml = renderJsonTree(error, node.operand, errorId, `${path}.operand`, expandedPaths);
64-
return node.op === "-" ? `(${node.op}${operandHtml})` : `${node.op}${operandHtml}`;
64+
return node.op === "-" ? `${node.op}(${operandHtml})` : `${node.op}${operandHtml}`;
6565
}
6666

6767
// fallback

client/src/webview/renderers/diagnostics/errors.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { renderHeader, renderLocation, renderSection, renderCustomSection } from "./utils";
1+
import { renderHeader, renderLocation, renderSection, renderCustomSection, renderShowAllButton, renderTranslationTable } from "./utils";
2+
import { renderDerivationNode } from "./derivation-nodes";
23
import {
34
ArgumentMismatchError,
45
InvalidRefinementError,
@@ -8,12 +9,10 @@ import {
89
StateConflictError,
910
StateRefinementError,
1011
SyntaxError,
11-
12+
TranslationTable,
1213
} from "../../../types";
13-
import { renderDerivationNode } from "./derivation-nodes";
14-
import { renderShowAllButton } from "./show-all-button";
1514

16-
export function getErrorsView(errors: LJError[], showAll: boolean, currentFile: string | undefined): string {
15+
export function getErrorsView(errors: LJError[], showAll: boolean, currentFile: string | undefined, expandedErrors: Set<number>): string {
1716
const displayDiagnostics = showAll ? errors : errors.filter(error => error.file && error.file?.toLowerCase() === currentFile?.toLowerCase());
1817
const hiddenCount = errors.length - displayDiagnostics.length;
1918
return /*html*/`
@@ -25,11 +24,15 @@ export function getErrorsView(errors: LJError[], showAll: boolean, currentFile:
2524
<p class="info">${`${errors.length} error${errors.length !== 1 ? 's were' : ' was'} found by the LiquidJava verifier.`}</p>
2625
<div class="content">
2726
<ul>
28-
${displayDiagnostics.map((error) => /*html*/`
27+
${displayDiagnostics.map((error, index) => {
28+
const errorIndex = errors.indexOf(error);
29+
const isExpanded = expandedErrors.has(errorIndex);
30+
return /*html*/`
2931
<li class="diagnostic-item error-item">
30-
${renderError(error)}
32+
${renderError(error, errorIndex, isExpanded)}
3133
</li>
32-
`).join("")}
34+
`;
35+
}).join("")}
3336
</ul>
3437
${hiddenCount > 0 ? `<p class="more-indicator">(+${hiddenCount} error${hiddenCount !== 1 ? 's' : ''})</p>` : ''}
3538
</div>
@@ -63,9 +66,27 @@ const errorContentRenderers: Partial<Record<LJError['type'], (error: LJError) =>
6366
`
6467
};
6568

66-
export function renderError(error: LJError): string {
69+
export function renderError(error: LJError, errorIndex: number, isExpanded: boolean): string {
6770
const header = renderHeader(error);
6871
const content = errorContentRenderers[error.type]?.(error) ?? '';
6972
const location = renderLocation(error);
70-
return /*html*/`${header}${content}${location}`;
73+
const extra = renderExtra(error, errorIndex, isExpanded);
74+
return /*html*/`${header}${content}${location}${extra}`;
7175
}
76+
77+
function renderExtra(error: LJError, errorIndex: number, isExpanded: boolean): string {
78+
const button = /*html*/`
79+
<button class="show-more-button" data-error-index="${errorIndex}" title="Toggle show extra information about the diagnostic">
80+
${isExpanded ? '↑' : '↓'}
81+
</button>
82+
`;
83+
if (!isExpanded) return button;
84+
85+
let extra = button;
86+
extra += '<div class="extra-content">';
87+
if (error.hasOwnProperty('translationTable')) {
88+
extra += renderTranslationTable((error as any).translationTable as TranslationTable);
89+
}
90+
extra += '</div>';
91+
return extra;
92+
}

client/src/webview/renderers/diagnostics/show-all-button.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

client/src/webview/renderers/diagnostics/utils.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LJDiagnostic } from "../../../types";
1+
import { LJDiagnostic, TranslationTable } from "../../../types";
22

33
export const renderCustomSection = (title: string, body: string): string =>
44
`<div class="section"><strong>${title}:</strong><div>${body}</div></div>`;
@@ -18,3 +18,53 @@ export const renderLocation = (diagnostic: LJDiagnostic): string => {
1818
const link = `<a href="#" class="link location-link" data-file="${diagnostic.file}" data-line="${line}" data-column="${column}">${simpleFile}:${line}</a>`;
1919
return renderCustomSection("Location", `<pre>${link}</pre>`);
2020
};
21+
22+
export function renderShowAllButton(showAll: boolean): string {
23+
return /*html*/`
24+
<button class="show-all-button" title="Toggle filter diagnostics by current file">
25+
${showAll ? 'Show in File' : 'Show All'}
26+
</button>
27+
`;
28+
}
29+
30+
export function renderTranslationTable(translationTable: TranslationTable): string {
31+
const entries = Object.entries(translationTable).sort((a, b) => a[0].localeCompare(b[0])); // sort by variable name
32+
if (entries.length === 0) return '';
33+
34+
return /*html*/`
35+
<div class="translation-table">
36+
<h3>Translation Table</h3>
37+
<table>
38+
<thead>
39+
<tr>
40+
<th>Variable</th>
41+
<th>Code</th>
42+
<th>Location</th>
43+
</tr>
44+
</thead>
45+
<tbody>
46+
${entries.map(([variable, placement]: [string, any]) => {
47+
const simpleFile = placement.position.file.split('/').pop() || placement.position.file;
48+
const link =
49+
/*html*/`<a
50+
href="#"
51+
class="link location-link"
52+
data-file="${placement.position.file}"
53+
data-line="${placement.position.line}"
54+
data-column="${placement.position.column}"
55+
>
56+
${simpleFile}:${placement.position.line}
57+
</a>`;
58+
return /*html*/`
59+
<tr>
60+
<td><code>${variable}</code></td>
61+
<td><code>${placement.text}</code></td>
62+
<td>${link}</td>
63+
</tr>
64+
`;
65+
}).join('')}
66+
</tbody>
67+
</table>
68+
</div>
69+
`;
70+
}

client/src/webview/renderers/diagnostics/warnings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ExternalClassNotFoundWarning, ExternalMethodNotFoundWarning, LJWarning } from "../../../types";
2-
import { renderHeader, renderLocation, renderCustomSection, renderSection } from "./utils";
2+
import { renderHeader, renderLocation, renderSection } from "./utils";
33

44
export function getWarningsView(warnings: LJWarning[], showAllDiagnostics: boolean, currentFile: string | undefined): string {
55
return /*html*/`

client/src/webview/script.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export function getScript(vscode: any, document: any, window: any) {
1717
let fileWarnings: LJWarning[] = [];
1818
let showAllDiagnostics = false;
1919
let currentFile: string | undefined;
20+
let expandedErrors = new Set<number>();
2021

2122
// initial state
2223
root.innerHTML = getLoadingView();
@@ -71,6 +72,21 @@ export function getScript(vscode: any, document: any, window: any) {
7172
updateView();
7273
return;
7374
}
75+
76+
// toggle show more/less for errors
77+
if (target.classList.contains('show-more-button')) {
78+
e.stopPropagation();
79+
const errorIndex = parseInt(target.getAttribute('data-error-index') || '-1', 10);
80+
if (errorIndex >= 0) {
81+
if (expandedErrors.has(errorIndex)) {
82+
expandedErrors.delete(errorIndex);
83+
} else {
84+
expandedErrors.add(errorIndex);
85+
}
86+
updateView();
87+
}
88+
return;
89+
}
7490
});
7591

7692
window.addEventListener('message', event => {
@@ -91,7 +107,7 @@ export function getScript(vscode: any, document: any, window: any) {
91107
});
92108

93109
function updateView() {
94-
let mainView = fileErrors.length > 0 ? getErrorsView(fileErrors, showAllDiagnostics, currentFile) : getCorrectView(showAllDiagnostics);
110+
let mainView = fileErrors.length > 0 ? getErrorsView(fileErrors, showAllDiagnostics, currentFile, expandedErrors) : getCorrectView(showAllDiagnostics);
95111
let warningsView = fileWarnings.length > 0 ? getWarningsView(fileWarnings, showAllDiagnostics, currentFile) : '';
96112
root.innerHTML = mainView + warningsView;
97113
}

client/src/webview/styles.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export function getStyles(): string {
6161
}
6262
.diagnostic-item {
6363
background-color: var(--vscode-textBlockQuote-background);
64-
padding: 1rem;
64+
padding: 0.5rem 1rem;
6565
margin-bottom: 1rem;
6666
border-radius: 4px;
6767
}
@@ -156,7 +156,23 @@ export function getStyles(): string {
156156
button:hover {
157157
background-color: var(--vscode-button-hoverBackground);
158158
}
159-
159+
.show-more-button {
160+
display: block;
161+
width: 100%;
162+
margin: 0.5rem auto;
163+
padding: 0.5rem;
164+
background-color: transparent;
165+
border: none;
166+
color: var(--vscode-foreground);
167+
opacity: 0.7;
168+
font-size: 1rem;
169+
}
170+
.show-more-button:hover {
171+
background-color: var(--vscode-editor-background);
172+
}
173+
.extra-content {
174+
margin-top: 1rem;
175+
}
160176
.tooltip:hover::after {
161177
content: attr(data-tooltip);
162178
position: absolute;
@@ -195,5 +211,35 @@ export function getStyles(): string {
195211
.info {
196212
margin: 1rem 0;
197213
}
214+
table {
215+
width: 100%;
216+
border-collapse: collapse;
217+
margin: 0rem 0;
218+
background-color: var(--vscode-editor-background);
219+
border-radius: 4px;
220+
overflow: hidden;
221+
}
222+
th {
223+
text-align: left;
224+
padding: 0.75rem;
225+
font-weight: bold;
226+
border-bottom: 1px solid var(--vscode-panel-border);
227+
color: var(--vscode-foreground);
228+
}
229+
td {
230+
padding: 0.75rem;
231+
border-bottom: 1px solid var(--vscode-panel-border);
232+
color: var(--vscode-foreground);
233+
}
234+
tbody tr:last-child td {
235+
border-bottom: none;
236+
}
237+
td code {
238+
background-color: transparent;
239+
padding: 0;
240+
border-radius: 3px;
241+
font-family: var(--vscode-editor-font-family);
242+
font-size: 0.9em;
243+
}
198244
`;
199245
}

0 commit comments

Comments
 (0)