Skip to content

Commit

Permalink
Langium (#72)
Browse files Browse the repository at this point in the history
Added compositions and patterns into langium grammar, added load
statements
  • Loading branch information
mosser authored Aug 6, 2024
2 parents fcfc226 + bb681b3 commit 1a136b0
Show file tree
Hide file tree
Showing 25 changed files with 1,122 additions and 650 deletions.
7 changes: 7 additions & 0 deletions langium/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

### Unreleased

### v0.2.1 (2024-08-06)
- Leader: Cass Braun
- Features:
- Added patterns and compositions to grammar with basic language support
- Added improved completion for justification diagrams and compositions
- Added basic quick fix

### v0.2.0 (2024-07-19)

- Leader: Cass Braun
Expand Down
4 changes: 2 additions & 2 deletions langium/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions langium/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Language Support for the jPipe language",
"author": "McMaster Centre for Software Certification (McSCert)",
"publisher": "mcscert",
"version": "0.2.0",
"version": "0.2.1",
"license": "MIT",
"icon": "images/logo.png",
"repository": {
Expand All @@ -26,8 +26,6 @@
"package": "npm run check-types && node esbuild.mjs --production"
},
"dependencies": {
"chalk": "~5.3.0",
"commander": "~11.0.0",
"langium": "~3.0.0",
"vscode-languageclient": "~9.0.1",
"vscode-languageserver": "~9.0.1"
Expand Down Expand Up @@ -55,6 +53,12 @@
"configuration": {
"title": "jPipe Configuration",
"properties": {
"jpipe.developerMode": {
"type": "boolean",
"scope": "window",
"default": false,
"markdownDescription": "When developer mode is turned on, all automatic fixes are disabled"
},
"jpipe.jarFile": {
"type": "string",
"scope": "window",
Expand Down
176 changes: 156 additions & 20 deletions langium/src/extension/image-generation/image-generator.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import * as vscode from 'vscode';
import util from "node:util";
import { SaveImageCommand } from './save-image-command.js';
import { Command, CommandUser } from '../managers/command-manager.js';
import { EventSubscriber, isTextEditor } from '../managers/event-manager.js';
import { ConfigKey, ConfigurationManager } from '../managers/configuration-manager.js';

export class ImageGenerator implements CommandUser{
export class ImageGenerator implements CommandUser, EventSubscriber<vscode.TextEditor | undefined>{
// New channel created in vscode terminal for user debugging.
private output_channel: vscode.OutputChannel;

// Defines the command needed to execute the extension.
private save_image_command: SaveImageCommand;

//configuration manager to fetch configurations from
private configuration: ConfigurationManager;

private editor!: vscode.TextEditor; //current editor
private document!: vscode.TextDocument; //current document
private directory!: vscode.WorkspaceFolder; //current directory

//possible image types and associated commands
private types: ImageType[];

constructor(save_image_command: SaveImageCommand, output_channel: vscode.OutputChannel) {
this.save_image_command = save_image_command;
constructor(configuration: ConfigurationManager, output_channel: vscode.OutputChannel) {
this.output_channel = output_channel;

this.configuration = configuration;

this.types = [
{
exe_command: "jpipe.downloadPNG",
Expand All @@ -26,6 +33,9 @@ export class ImageGenerator implements CommandUser{
format: Format.SVG
}
];

//automatically registers to start
this.update(vscode.window.activeTextEditor);
}

//used to register commands with command manager
Expand All @@ -34,29 +44,152 @@ export class ImageGenerator implements CommandUser{
this.types.forEach( (type) =>{
command_list.push({
command: type.exe_command,
callback: () => {this.saveImage(type.format)}
callback: () => {this.generate({format: type.format, save_image: true})}
});
});

return command_list;
}

//Generates an image for the selected justification diagram
public async saveImage(format: Format): Promise<void> {
//updater functions
public async update(data: vscode.TextEditor | undefined): Promise<void>{
if(isTextEditor(data)){
const {editor, document, directory} = this.updateEditor(data);

this.editor = editor;
this.document = document;
this.directory = directory;
}
}

public async generate(command_settings: CommandSettings): Promise<{stdout: any}>{
const { exec } = require('node:child_process');
const execPromise = util.promisify(exec);

// Execute the command, and wait for the result (must be synchronous).
// TODO: Validate that this actually executes synchronously.
try{
let command = await this.save_image_command.makeCommand({ format: format, save_image: true});
const {stdout, stderr} = await execPromise(command);
const command = await this.makeCommand(command_settings);
const output: {stdout: any, stderr: any} = await execPromise(command);

this.output_channel.appendLine(output.stderr.toString());

return {stdout: output.stdout};
}

this.output_channel.appendLine(stdout.toString());
this.output_channel.appendLine(stderr.toString());
} catch (error: any){
this.output_channel.appendLine(error.toString());
//creates the command based on command settings
private async makeCommand(command_settings: CommandSettings): Promise<string>{
let jar_file = this.configuration.getConfiguration(ConfigKey.JARFILE);

let input_file = this.document.uri;

let diagram_name = this.findDiagramName(this.document,this.editor);

let format = this.getFormat(command_settings);

let log_level = this.configuration.getConfiguration(ConfigKey.LOGLEVEL);

let command = 'java -jar ' + jar_file + ' -i ' + input_file.path + ' -d '+ diagram_name + ' --format ' + format + ' --log-level ' + log_level;

this.output_channel.appendLine("Made using jar file: " + jar_file.toString());
if(command_settings.save_image){
let output_file = await this.makeOutputPath(diagram_name, command_settings);
command += ' -o ' + output_file.path;
}

return command;
}

//returns the current diagram name
public getDiagramName(): string{
return this.findDiagramName(this.document, this.editor);
}

//helper function to get the diagram name from the document
private findDiagramName(document: vscode.TextDocument, editor: vscode.TextEditor): string{
let diagram_name: string | undefined;
let match: RegExpExecArray | null;
let i = 0;

let lines = document.getText().split("\n");
let line_num = editor.selection.active.line + 1;

while(i < lines.length && (i < line_num || diagram_name===null)){
match = /justification .*/i.exec(lines[i]) || /pattern .*/i.exec(lines[i]);

if (match){
diagram_name = match[0].split(' ')[1];
}

i++;
}

if(diagram_name === undefined){
throw new Error("Diagram name not found");
}

return diagram_name;
}

//helper function to set default format
private getFormat(command_settings: CommandSettings): Format{
let format = command_settings.format;

if(format === undefined){
format = Format.PNG;
}

return format;
}

//creates the output filepath by asking for user input
private async makeOutputPath(diagram_name: string, command_settings: CommandSettings): Promise<vscode.Uri>{
if(command_settings.format === undefined){
command_settings.format = Format.PNG;
}

let default_output_file = vscode.Uri.joinPath(this.directory.uri, diagram_name + "." + command_settings.format.toLowerCase());

let save_dialog_options: vscode.SaveDialogOptions = {
defaultUri: default_output_file,
saveLabel: "Save proof model",
filters: {
'Images': [Format.PNG.toLowerCase(), Format.SVG.toLowerCase()]
}
}

let output_file = await vscode.window.showSaveDialog(save_dialog_options);

if(!output_file){
throw new Error("Please enter a save location");
}

return output_file;
}

//helper function to perform updates related to a new text editor
private updateEditor(editor: vscode.TextEditor | undefined): {editor: vscode.TextEditor, document: vscode.TextDocument, directory: vscode.WorkspaceFolder}{
let document: vscode.TextDocument;
let directory: vscode.WorkspaceFolder;

if(!editor){
editor = this.editor;
document = this.document;
directory = this.directory;
}else{
document = editor.document;
directory = this.getDirectory(document);
}

return{editor, document, directory};
}

//helper function to get directory for updating
private getDirectory(document: vscode.TextDocument): vscode.WorkspaceFolder{
let directory = vscode.workspace.getWorkspaceFolder(document.uri);

if(!directory){
directory = this.directory;
}

return directory;
}
}

Expand All @@ -70,4 +203,7 @@ export enum Format{
SVG = "SVG",
}


type CommandSettings = {
format?: Format,
save_image: boolean
}
42 changes: 17 additions & 25 deletions langium/src/extension/image-generation/preview-provider.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import * as vscode from 'vscode'
import util from "node:util";
import { SaveImageCommand } from './save-image-command.js';
import { Format } from './image-generator.js';
import * as vscode from 'vscode';
import { Format, ImageGenerator } from './image-generator.js';
import { Command, CommandUser } from '../managers/command-manager.js';
import { EventSubscriber, isTextEditor, isTextEditorSelectionChangeEvent } from '../managers/event-manager.js';


//altered from editorReader
export class PreviewProvider implements vscode.CustomTextEditorProvider, CommandUser, EventSubscriber<vscode.TextEditor | undefined>, EventSubscriber<vscode.TextEditorSelectionChangeEvent> {

Expand All @@ -31,15 +30,15 @@ export class PreviewProvider implements vscode.CustomTextEditorProvider, Command
// Global text panel used to display the jd code.
private static textPanel: Thenable<vscode.TextEditor>;

private static save_image_command: SaveImageCommand;
private static image_generator: ImageGenerator;

constructor(save_image_command: SaveImageCommand, output_channel: vscode.OutputChannel) {
constructor(image_generator: ImageGenerator, output_channel: vscode.OutputChannel) {
// Without any initial data, must be empty string to prevent null error.
PreviewProvider.svg_data = "";
this.output_channel = output_channel;
PreviewProvider.updating = false;
PreviewProvider.webviewDisposed = true;
PreviewProvider.save_image_command = save_image_command;
PreviewProvider.image_generator = image_generator;

vscode.window.registerCustomEditorProvider(PreviewProvider.ext_command, this);
}
Expand All @@ -51,16 +50,14 @@ export class PreviewProvider implements vscode.CustomTextEditorProvider, Command
public async update(editor: vscode.TextEditor | undefined): Promise<void>;
public async update(changes: vscode.TextEditorSelectionChangeEvent): Promise<void>;
public async update(data: (vscode.TextEditor | undefined) | vscode.TextEditorSelectionChangeEvent): Promise<void>{
if(PreviewProvider.webviewDisposed){
return;
if(!PreviewProvider.webviewDisposed){
if(isTextEditorSelectionChangeEvent(data)){
this.updateTextSelection(data);
}
else if(isTextEditor(data)){
this.updateEditor(data);
}
}

if(isTextEditorSelectionChangeEvent(data)){
this.updateTextSelection(data);
}
else if(isTextEditor(data)){
this.updateEditor(data);
}
}

private async createWebview(): Promise<void>{
Expand Down Expand Up @@ -149,15 +146,10 @@ export class PreviewProvider implements vscode.CustomTextEditorProvider, Command

// Executes the jar file for updated SVG
public async updateSVG(): Promise<void> {
const { exec } = require('node:child_process');
const execPromise = util.promisify(exec);

try{
let command = await PreviewProvider.save_image_command.makeCommand({format: Format.SVG, save_image: false});
PreviewProvider.webviewPanel.title = PreviewProvider.save_image_command.getDiagramName();

const {stdout, stderr} = await execPromise(command);
this.output_channel.appendLine(stderr.toString());
const {stdout} = await PreviewProvider.image_generator.generate({format: Format.SVG, save_image: false})

PreviewProvider.webviewPanel.title = PreviewProvider.image_generator.getDiagramName();
PreviewProvider.svg_data = stdout;
}catch (error: any){
this.output_channel.appendLine(error.toString());
Expand Down Expand Up @@ -192,7 +184,7 @@ export class PreviewProvider implements vscode.CustomTextEditorProvider, Command
//helper function to update text selection
private async updateTextSelection(event: vscode.TextEditorSelectionChangeEvent){
if (event !== undefined && event.textEditor.document.languageId=="jpipe" && !PreviewProvider.webviewDisposed){
let new_diagram = PreviewProvider.save_image_command.getDiagramName();
let new_diagram = PreviewProvider.image_generator.getDiagramName();

let token : vscode.CancellationTokenSource = new vscode.CancellationTokenSource();

Expand Down
Loading

0 comments on commit 1a136b0

Please sign in to comment.