Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion client/src/services/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function registerEvents(context: vscode.ExtensionContext) {
*/
export async function onActiveFileChange(editor: vscode.TextEditor) {
extension.file = normalizeFilePath(editor.document.uri.fsPath);
extension.stateMachine = undefined;
extension.webview?.sendMessage({ type: "file", file: extension.file });
await updateStateMachine(editor.document);
handleContextUpdate(editor.selection);
Expand Down Expand Up @@ -70,4 +71,4 @@ function handleContextUpdate(selection: vscode.Selection) {
extension.context.allVars = allVars;
updateErrorAtCursor();
extension.webview?.sendMessage({ type: "context", context: extension.context, errorAtCursor: extension.errorAtCursor });
}
}
7 changes: 4 additions & 3 deletions client/src/services/state-machine.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import * as vscode from "vscode";
import { extension } from "../state";
import { LJStateMachine } from "../types/fsm";
import { normalizeFilePath } from "../utils/utils";

/**
* Requests the state machine for the given document from the language server
* @param document The text document
*/
export async function updateStateMachine(document: vscode.TextDocument) {
const sm = await extension.client?.sendRequest<LJStateMachine>("liquidjava/fsm", { uri: document.uri.toString() });
const file = normalizeFilePath(document.uri.fsPath);
const sm = await extension.client?.sendRequest<LJStateMachine | null>("liquidjava/fsm", { uri: document.uri.toString() });
if (file !== extension.file) return;

// dont update diagram if it hasnt changed to a new one
if (!sm || JSON.stringify(sm) === JSON.stringify(extension.stateMachine)) return;
extension.stateMachine = sm;
extension.webview?.sendMessage({ type: "fsm", sm });
}
2 changes: 1 addition & 1 deletion client/src/services/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function registerWebview(context: vscode.ExtensionContext) {
if (extension.file) extension.webview?.sendMessage({ type: "file", file: extension.file });
if (extension.diagnostics) extension.webview?.sendMessage({ type: "diagnostics", diagnostics: extension.diagnostics });
if (extension.context) extension.webview?.sendMessage({ type: "context", context: extension.context , errorAtCursor: extension.errorAtCursor });
if (extension.stateMachine) extension.webview?.sendMessage({ type: "fsm", sm: extension.stateMachine });
if (extension.stateMachine !== undefined) extension.webview?.sendMessage({ type: "fsm", sm: extension.stateMachine });
if (extension.status) extension.webview?.sendMessage({ type: "status", status: extension.status });
if (pendingDiagnosticReveal) {
extension.webview?.sendMessage({ type: "revealDiagnostic", diagnostic: pendingDiagnosticReveal });
Expand Down
2 changes: 1 addition & 1 deletion client/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class ExtensionState {
// application state
file?: string;
diagnostics?: LJDiagnostic[];
stateMachine?: LJStateMachine;
stateMachine?: LJStateMachine | null;
context?: LJContext;
currentSelection?: Range;
errorAtCursor?: RefinementMismatchError;
Expand Down
4 changes: 4 additions & 0 deletions client/src/types/diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Range } from './context';
import type { LJStateMachine } from './fsm';
import type { VCSimplificationResult } from './vc-implications';

// Type definitions used for LiquidJava diagnostics
Expand Down Expand Up @@ -62,6 +63,7 @@ export type RefinementError = BaseDiagnostic & {
found: VCSimplificationResult;
customMessage: string;
counterexample: string;
declarationPosition: SourcePosition | null;
}

export type StateConflictError = BaseDiagnostic & {
Expand All @@ -78,6 +80,8 @@ export type StateRefinementError = BaseDiagnostic & {
expected: string;
found: VCSimplificationResult;
customMessage: string;
declarationPosition: SourcePosition | null;
stateMachine: LJStateMachine | null;
}

export type ArgumentMismatchError = BaseDiagnostic & {
Expand Down
37 changes: 31 additions & 6 deletions client/src/webview/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export function getScript(vscode: VSCodeApi, document: Document, window: Window)
let diagnostics: LJDiagnostic[] | undefined;
let showAllDiagnostics = false;
let currentFile: string;
let stateMachine: LJStateMachine;
let stateMachine: LJStateMachine | undefined;
let diagnosticStateMachine: LJStateMachine | undefined;
let context: LJContext;
let errorAtCursor: RefinementMismatchError;
let selectedTab: NavTab = 'diagnostics';
Expand Down Expand Up @@ -131,6 +132,24 @@ export function getScript(vscode: VSCodeApi, document: Document, window: Window)
return;
}

const diagnosticStateMachineButton = target.closest?.('.diagnostic-state-machine-btn');
if (diagnosticStateMachineButton) {
e.preventDefault();
e.stopPropagation();

const errorIndex = parseInt(diagnosticStateMachineButton.getAttribute('data-error-index') || '-1', 10);
const diagnostic = getDisplayDiagnostics(diagnostics ?? [], showAllDiagnostics, currentFile)
.filter(d => d.category === 'error')[errorIndex];
if (diagnostic?.type !== 'state-refinement-error' || !diagnostic.stateMachine) return;

selectedTab = 'fsm';
diagnosticStateMachine = diagnostic.stateMachine;
showDiagramConditions = false;
currentDiagram = '';
updateView();
return;
}

// VC implication simplification step buttons
const vcImplicationStepButton = target.closest?.('.vc-step-btn');
if (vcImplicationStepButton) {
Expand Down Expand Up @@ -237,6 +256,7 @@ export function getScript(vscode: VSCodeApi, document: Document, window: Window)
const tab = target.getAttribute('data-tab') as NavTab;
if (tab && tab !== selectedTab) {
vscode.postMessage({ type: 'highlight', range: null });
if (tab === 'fsm') diagnosticStateMachine = undefined;
selectedTab = tab;
updateView();
}
Expand Down Expand Up @@ -309,11 +329,15 @@ export function getScript(vscode: VSCodeApi, document: Document, window: Window)
case 'file':
currentFile = msg.file;
if (diagnostics && !showAllDiagnostics && selectedTab === 'diagnostics') updateView();
diagnosticStateMachine = undefined;
stateMachine = undefined;
currentDiagram = '';
if (selectedTab === 'fsm') updateView();
break;
case 'fsm':
stateMachine = msg.sm as LJStateMachine;
stateMachine = (msg.sm as LJStateMachine | null) ?? undefined;
showDiagramConditions = false;
if (selectedTab === 'fsm') updateView();
if (selectedTab === 'fsm' && !diagnosticStateMachine) updateView();
break;
case 'context':
context = msg.context as LJContext;
Expand Down Expand Up @@ -348,10 +372,11 @@ export function getScript(vscode: VSCodeApi, document: Document, window: Window)
: renderLoading();
break;
case 'fsm': {
const diagram = createMermaidDiagram(stateMachine, diagramOrientation, showDiagramConditions);
const displayedStateMachine = diagnosticStateMachine ?? stateMachine;
const diagram = createMermaidDiagram(displayedStateMachine, diagramOrientation, showDiagramConditions);
currentDiagram = diagram;
renderStateMachineView(root, stateMachine, diagram, diagramOrientation, showDiagramConditions);
if (stateMachine) renderMermaidDiagram(document, window);
renderStateMachineView(root, displayedStateMachine, diagram, diagramOrientation, showDiagramConditions);
if (displayedStateMachine) renderMermaidDiagram(document, window);
break;
}
case 'context':
Expand Down
22 changes: 12 additions & 10 deletions client/src/webview/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,16 @@ export function getStyles(): string {
line-height: 1;
pointer-events: none;
}
.copy-diagnostic-btn,
.diagnostic-context-btn {
.diagnostic-actions {
position: absolute;
top: 0.5rem;
right: 0.5rem;
display: flex;
gap: 0.25rem;
}
.copy-diagnostic-btn,
.diagnostic-context-btn,
.diagnostic-state-machine-btn {
display: inline-flex;
align-items: center;
justify-content: center;
Expand All @@ -193,20 +199,16 @@ export function getStyles(): string {
opacity: 0.65;
transition: background-color 0.16s ease, border-color 0.16s ease, opacity 0.16s ease, transform 0.16s ease;
}
.copy-diagnostic-btn {
right: 0.5rem;
}
.diagnostic-context-btn {
right: 2.5rem;
}
.copy-diagnostic-btn:hover,
.diagnostic-context-btn:hover {
.diagnostic-context-btn:hover,
.diagnostic-state-machine-btn:hover {
background: var(--vscode-editor-background);
border-color: var(--vscode-widget-border);
opacity: 1;
}
.copy-diagnostic-btn:disabled,
.diagnostic-context-btn:disabled {
.diagnostic-context-btn:disabled,
.diagnostic-state-machine-btn:disabled {
opacity: 0.8;
cursor: default;
}
Expand Down
2 changes: 1 addition & 1 deletion client/src/webview/views/diagnostics/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export async function copyDiagnosticToClipboard(button: any, displayDiagnostics:
}

export function formatDiagnosticForClipboard(diagnostic: LJDiagnostic): string {
const skippedFields = new Set(['category', 'type', 'translationTable', 'position', 'file']);
const skippedFields = new Set(['category', 'type', 'translationTable', 'position', 'file', 'declarationPosition', 'stateMachine']);
const lines: string[] = [];

Object.entries(diagnostic).forEach(([key, value]) => {
Expand Down
14 changes: 11 additions & 3 deletions client/src/webview/views/diagnostics/errors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { renderDiagnosticDataAttributes, renderExpressionSection, renderDiagnosticHeader, renderCustomSection, renderLocation, renderDiagnosticContextButton } from "../sections";
import { renderDiagnosticDataAttributes, renderExpressionSection, renderDiagnosticHeader, renderCustomSection, renderLocation, renderDiagnosticContextButton, renderDiagnosticStateMachineButton } from "../sections";
import { renderCounterexample } from "./counterexample";
import { renderVCImplication } from "./vc-implications";
import type {
Expand All @@ -21,8 +21,11 @@ export function renderErrors(errors: LJError[]): string {
${errors.map((error, index) => {
return /*html*/`
<li class="diagnostic-item error-item" ${renderDiagnosticDataAttributes(error)}>
${renderDiagnosticContextAction(error)}
${renderCopyDiagnosticButton('error', index)}
<div class="diagnostic-actions">
${renderDiagnosticStateMachineAction(error, index)}
${renderDiagnosticContextAction(error)}
${renderCopyDiagnosticButton('error', index)}
</div>
${renderError(error)}
</li>
`;
Expand Down Expand Up @@ -74,3 +77,8 @@ function renderDiagnosticContextAction(error: LJError): string {
if (error.type !== 'refinement-error' && error.type !== 'state-refinement-error') return "";
return renderDiagnosticContextButton(error.position);
}

function renderDiagnosticStateMachineAction(error: LJError, index: number): string {
if (error.type !== 'state-refinement-error' || !error.stateMachine) return "";
return renderDiagnosticStateMachineButton(index);
}
4 changes: 3 additions & 1 deletion client/src/webview/views/diagnostics/warnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export function renderWarnings(warnings: LJWarning[]): string {
<ul>
${warnings.map((warning, index) => /*html*/`
<li class="diagnostic-item warning-item" ${renderDiagnosticDataAttributes(warning)}>
${renderCopyDiagnosticButton('warning', index)}
<div class="diagnostic-actions">
${renderCopyDiagnosticButton('warning', index)}
</div>
${renderWarning(warning)}
</li>
`).join("")}
Expand Down
22 changes: 20 additions & 2 deletions client/src/webview/views/sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,18 @@ export function renderDiagnosticDataAttributes(diagnostic: LJDiagnostic): string
}

export const renderLocation = (diagnostic: LJDiagnostic): string => {
if (!diagnostic.position || !diagnostic.file) return "";
return renderCustomSection("Location", /*html*/`<pre>${renderLocationLink(diagnostic.position)}</pre>`);
const positions: SourcePosition[] = [];
if (diagnostic.position && diagnostic.file) positions.push(diagnostic.position);

const declarationPosition = diagnostic.type === 'refinement-error' || diagnostic.type === 'state-refinement-error'
? diagnostic.declarationPosition
: null;
if (declarationPosition?.file) positions.push(declarationPosition);

if (positions.length === 0) return "";
const title = positions.length === 1 ? "Location" : "Locations";
const links = positions.map(renderLocationLink).join("\n");
return renderCustomSection(title, /*html*/`<pre>${links}</pre>`);
};

export function renderVariableHighlightButton(variable: LJVariable): string {
Expand Down Expand Up @@ -69,6 +79,14 @@ export function renderDiagnosticContextButton(position?: SourcePosition | null):
});
}

export function renderDiagnosticStateMachineButton(errorIndex: number): string {
return renderCodiconButton("type-hierarchy", {
className: "diagnostic-state-machine-btn",
title: "View state machine",
attributes: `data-error-index="${errorIndex}"`,
});
}

export function renderDiagnosticRevealButton(position: SourcePosition, content: string): string {
if (!position.file) return `<code>${content}</code>`
return /*html*/`
Expand Down
3 changes: 3 additions & 0 deletions server/src/main/java/dtos/errors/RefinementErrorDTO.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dtos.errors;

import dtos.diagnostics.SourcePositionDTO;
import dtos.diagnostics.VCSimplificationResultDTO;
import liquidjava.diagnostics.errors.RefinementError;
import liquidjava.rj_language.ast.formatter.ExpressionFormatter;
Expand All @@ -13,13 +14,15 @@ public class RefinementErrorDTO extends LJErrorDTO {
public final VCSimplificationResultDTO found;
public final String customMessage;
public final String counterexample;
public final SourcePositionDTO declarationPosition;

public RefinementErrorDTO(RefinementError error) {
super("refinement-error", error);
this.expected = error.getExpected() == null ? null : ExpressionFormatter.format(error.getExpected());
this.found = VCSimplificationResultDTO.from(error.getFound());
this.customMessage = error.getCustomMessage();
this.counterexample = error.getCounterExampleString();
this.declarationPosition = SourcePositionDTO.from(error.getDeclarationPosition());
}

public static RefinementErrorDTO from(RefinementError error) {
Expand Down
10 changes: 10 additions & 0 deletions server/src/main/java/dtos/errors/StateRefinementErrorDTO.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package dtos.errors;

import java.io.File;

import dtos.diagnostics.SourcePositionDTO;
import dtos.diagnostics.VCSimplificationResultDTO;
import fsm.StateMachine;
import fsm.StateMachineParser;
import liquidjava.diagnostics.errors.StateRefinementError;
import liquidjava.rj_language.ast.formatter.ExpressionFormatter;

Expand All @@ -12,12 +17,17 @@ public class StateRefinementErrorDTO extends LJErrorDTO {
public final String expected;
public final VCSimplificationResultDTO found;
public final String customMessage;
public final SourcePositionDTO declarationPosition;
public final StateMachine stateMachine;

public StateRefinementErrorDTO(StateRefinementError error) {
super("state-refinement-error", error);
this.expected = error.getExpected() == null ? null : ExpressionFormatter.format(error.getExpected());
this.found = VCSimplificationResultDTO.from(error.getFoundSimplification());
this.customMessage = error.getCustomMessage();
this.declarationPosition = SourcePositionDTO.from(error.getDeclarationPosition());
this.stateMachine = declarationPosition == null || declarationPosition.file() == null ? null
: StateMachineParser.parse(new File(declarationPosition.file()).toURI().toString());
}

public static StateRefinementErrorDTO from(StateRefinementError error) {
Expand Down