additional features for parsing incomplete declarations and providing code actions to resolve them

This commit is contained in:
Michael Becker 2025-04-02 21:41:30 -04:00
parent b58def7680
commit 51722cb03d
5 changed files with 231 additions and 18 deletions

View File

@ -25,6 +25,8 @@ import { ZqInstance } from './zq/ZqInstance';
import { ZqInstanceReference } from './zq/ZqInstanceReference'; import { ZqInstanceReference } from './zq/ZqInstanceReference';
import { ZqObject } from './zq/ZqObject'; import { ZqObject } from './zq/ZqObject';
import { ZqParser } from './zq/parser/ZqParser'; import { ZqParser } from './zq/parser/ZqParser';
import { ZqObjectDefinition } from './zq/ZqObjectDefinition';
import { ZqlCodeActionsProvider } from './zqlEditor/ZqlCodeActionsProvider';
const cp = require('child_process'); const cp = require('child_process');
@ -230,6 +232,8 @@ export function openWebBrowser(url: vscode.Uri) {
// Your extension is activated the very first time the command is executed // Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
registerZqlEditorSupport(context);
outputChannel = vscode.window.createOutputChannel("Mocha"); outputChannel = vscode.window.createOutputChannel("Mocha");
outputChannel.appendLine("Mocha for VSCode activated"); outputChannel.appendLine("Mocha for VSCode activated");
@ -278,9 +282,18 @@ export function activate(context: vscode.ExtensionContext) {
let decl = ZqParser.parse(line); let decl = ZqParser.parse(line);
if (decl !== null) { if (decl !== null) {
(decl as ZqFunctionDefinition).isStub = false; if ((decl as ZqFunctionDefinition).isStub) {
ed.edit(edit => edit.replace(currentLineRange, decl.toString())); (decl as ZqFunctionDefinition).isStub = false;
ed.edit(edit => edit.replace(currentLineRange, decl.toString()));
}
else {
vscode.window.showErrorMessage("This ZQ function already has an implementation!");
}
}
else {
vscode.window.showErrorMessage("Please place the cursor on a ZQ function before importing its definition!");
} }
} }
} }
@ -379,7 +392,8 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(cmd_suv_manager); context.subscriptions.push(cmd_suv_manager);
let cmd_suv_refresh = vscode.commands.registerCommand('mocha.suvmanager.suv_refresh', () => { let cmd_suv_refresh = vscode.commands.registerCommand('mocha.suvmanager.suv_refresh', () => {
vscode.window.showInformationMessage('Add code here to display SUV Manager panel!');
treeDataProvider.refresh();
}); });
context.subscriptions.push(cmd_suv_refresh); context.subscriptions.push(cmd_suv_refresh);
@ -500,16 +514,38 @@ export function activate(context: vscode.ExtensionContext) {
}); });
context.subscriptions.push(cmd_suv_up); context.subscriptions.push(cmd_suv_up);
const relationshipDecorations: vscode.TextEditorDecorationType[] = []; let relationshipDecorations: vscode.TextEditorDecorationType[] = [];
const noAttrsDecorationType = vscode.window.createTextEditorDecorationType({
before:
{
contentText: "<no attributes>",
margin: "0px 4px 0px 16px",
color: "#999999"
}
});
context.subscriptions.push(vscode.window.onDidChangeTextEditorSelection((e) => { context.subscriptions.push(vscode.window.onDidChangeTextEditorSelection((e) => {
if (e.textEditor.document.languageId === "zql") { if (e.textEditor.document.languageId === "zql") {
let clz = ZqParser.parse(e.textEditor.document.getText()) as ZqClass; let clz = ZqParser.parse(e.textEditor.document.getText()) as ZqClass;
if (clz !== null) if (clz !== null)
{ {
if (clz.attributes.length === 0) {
let attrsDef : ZqObjectDefinition | undefined = clz.sectionDefinitions.get('attributes');
if (attrsDef !== undefined) {
e.textEditor.setDecorations(noAttrsDecorationType, [
new vscode.Range(attrsDef.lineIndex + 1 + 2/*hack*/, 0,
attrsDef.lineIndex + 1 + 2 /*hack*/, 0)
]);
}
} else {
e.textEditor.setDecorations(noAttrsDecorationType, []);
}
relationshipDecorations.forEach(element => { relationshipDecorations.forEach(element => {
e.textEditor.setDecorations(element, []); e.textEditor.setDecorations(element, []);
}); });
relationshipDecorations = [];
clz.relationships.forEach(rel => { clz.relationships.forEach(rel => {
let n = ""; let n = "";
@ -630,6 +666,25 @@ function indentLines(count: number, lines: string[]): string {
retval += indent; retval += indent;
return retval; return retval;
} }
function registerZqlEditorSupport(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider('zql', new ZqlCodeActionsProvider(), {
providedCodeActionKinds: ZqlCodeActionsProvider.providedCodeActionKinds
}));
// const emojiDiagnostics = vscode.languages.createDiagnosticCollection("emoji");
// context.subscriptions.push(emojiDiagnostics);
//subscribeToDocumentChanges(context, emojiDiagnostics);
/*
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider('zql', new Emojinfo(), {
providedCodeActionKinds: Emojinfo.providedCodeActionKinds
})
);
*/
}
/* /*
// This method is called when your extension is activated // This method is called when your extension is activated

View File

@ -16,9 +16,16 @@
// along with mocha-vscode. If not, see <https://www.gnu.org/licenses/>. // along with mocha-vscode. If not, see <https://www.gnu.org/licenses/>.
import { ZqInstance } from "./ZqInstance"; import { ZqInstance } from "./ZqInstance";
import { ZqObjectDefinition } from "./ZqObjectDefinition";
export class ZqClass extends ZqInstance { export class ZqClass extends ZqInstance {
private _sectionDefinitions: Map<string, ZqObjectDefinition> = new Map<string, ZqObjectDefinition>();
public get sectionDefinitions() : Map<string, ZqObjectDefinition> {
return this._sectionDefinitions;
}
toString(): string { toString(): string {
let r = ""; let r = "";

View File

@ -22,12 +22,40 @@ import { ZqClass } from "../ZqClass";
import { ZqFunctionDefinition } from "../ZqFunctionDefinition"; import { ZqFunctionDefinition } from "../ZqFunctionDefinition";
import { ZqInstanceReference } from "../ZqInstanceReference"; import { ZqInstanceReference } from "../ZqInstanceReference";
import { ZqObject } from "../ZqObject"; import { ZqObject } from "../ZqObject";
import { ZqObjectDefinition } from "../ZqObjectDefinition";
import { ZqRelationship } from "../ZqRelationship"; import { ZqRelationship } from "../ZqRelationship";
import { ZqParserContext } from "./ZqParserContext"; import { ZqParserContext } from "./ZqParserContext";
import { ZqTokenInfo } from "./ZqTokenInfo"; import { ZqTokenInfo } from "./ZqTokenInfo";
export class ZqParser { export class ZqParser {
public static readonly instanceDeclarationRegExp = new RegExp("\\s*(\\w*)\\s*(?:,\\s*(\\d*\\$\\d*))?\\s*");
public static readonly attributeDeclarationRegExp = new RegExp("\\s*(\\w*)\\s*(?:,\\s*(\\d*\\$\\d*))?\\s*(?:\\:\\s*(\\w*))?\\s*");
static parseInstanceDeclaration(lineText: string): ZqInstanceReference | null {
let result = ZqParser.instanceDeclarationRegExp.exec(lineText);
let name = result?.at(1);
let instanceKey = result?.at(2);
if (name === undefined) {
return null;
}
return new ZqInstanceReference(name === undefined ? "" : name, instanceKey === undefined ? null : InstanceKey.parse(instanceKey));
}
static parseAttributeDeclaration(lineText: string): ZqAttribute | null {
let result = ZqParser.attributeDeclarationRegExp.exec(lineText);
let name = result?.at(1);
let instanceKey = result?.at(2);
let dataType = result?.at(3);
if (name === undefined) {
return null;
}
return new ZqAttribute(name === undefined ? "" : name, instanceKey === undefined ? null : InstanceKey.parse(instanceKey), dataType === undefined ? null : dataType);
}
static parse(line: string): ZqObject { static parse(line: string): ZqObject {
let idx: number = 0; let idx: number = 0;
@ -71,12 +99,14 @@ export class ZqParser {
obj.isStatic = nextStatic; obj.isStatic = nextStatic;
obj.isStub = nextStub; obj.isStub = nextStub;
obj.name = line.substring(token.nextStart, line.indexOf('(', token.nextStart)).trim(); let name = result?.at(5);
token.nextStart = token.nextStart + obj.name.length + 1; if (name !== undefined) {
obj.name = name;
}
token.nextStart = token.nextStart + line.length + 1;
retval = obj; retval = obj;
break;
} }
line = line.substring(token.nextStart).trim();
// idx = token.NextStart; // idx = token.NextStart;
idx = 0; idx = 0;
} }
@ -112,21 +142,15 @@ function parseClass(line: string, NextStart: number) {
while (!ctx.endOfStream) { while (!ctx.endOfStream) {
if (section === "attributes") { if (section === "attributes") {
let line = ctx.readLine(); let line = ctx.readLine().trim();
if (line === '') { if (line === '') {
section = ""; section = "";
continue; continue;
} }
let re = new RegExp("\\s*(\\w*)\\s*(?:,\\s*(\\d*\\$\\d*))\\s*:\\s*(\\w*)\\s*"); let result = ZqParser.parseAttributeDeclaration(line);
let result = re.exec(line); if (result !== null) {
obj.attributes.push(result);
let name = result?.at(1);
let instanceKey = result?.at(2);
let dataType = result?.at(3);
if (name !== undefined && instanceKey !== undefined && dataType !== undefined) {
obj.attributes.push(new ZqAttribute(name, InstanceKey.parse(instanceKey), dataType));
} }
} }
else if (section === "relationships" || section === "instances") { else if (section === "relationships" || section === "instances") {
@ -161,10 +185,15 @@ function parseClass(line: string, NextStart: number) {
} }
} }
else if (section === "") { else if (section === "") {
while (ctx.peekLine().trim() === '') {
ctx.readLine();
}
let tok = ctx.readToken(); let tok = ctx.readToken();
if (tok !== null) { if (tok !== null) {
if (tok.token === ":") { if (tok.token === ":") {
section = tok.value; section = tok.value;
obj.sectionDefinitions.set(section, new ZqObjectDefinition(ctx.lineIndex, ctx.columnIndex));
ctx.readLine(); ctx.readLine();
} }
} }

View File

@ -83,6 +83,17 @@ export class ZqParserContext {
return before; return before;
} }
peekLine() : string {
let idxNewline = this._value.indexOf("\n", this._currentIndex);
let retval = this._value.substring(this._currentIndex);
if (idxNewline > -1) {
retval = this._value.substring(this._currentIndex, idxNewline);
}
return retval;
}
readToken(): ZqTokenInfo | null { readToken(): ZqTokenInfo | null {
let end = this._value.length; let end = this._value.length;

View File

@ -0,0 +1,111 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
//
// This file is part of mocha-vscode.
//
// mocha-vscode is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// mocha-vscode is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with mocha-vscode. If not, see <https://www.gnu.org/licenses/>.
import * as vscode from 'vscode';
import { InstanceKey } from '../mocha/core/InstanceKey';
import { ZqParser } from '../zq/parser/ZqParser';
import { ZqClass } from '../zq/ZqClass';
export class ZqlCodeActionsProvider implements vscode.CodeActionProvider {
public static readonly providedCodeActionKinds = [
vscode.CodeActionKind.QuickFix
];
private createIidImportFix(document: vscode.TextDocument, range: vscode.Range, type : string, instanceId: InstanceKey): vscode.CodeAction {
const fix = new vscode.CodeAction("Complete instance declaration from Mocha", vscode.CodeActionKind.QuickFix);
fix.edit = new vscode.WorkspaceEdit();
let line = document.lineAt(range.start);
let lineText = line.text.trim();
if (type === "attribute") {
let attr = ZqParser.parseAttributeDeclaration(lineText);
if (attr !== null) {
if (attr.instanceKey === null) {
attr.instanceKey = InstanceKey.parse(instanceId.toString());
}
if (attr.dataType === null) {
attr.dataType = "text";
}
fix.edit.replace(document.uri, line.range, " " + attr.toString());
}
}
else if (type === "instance") {
let inst = ZqParser.parseInstanceDeclaration(lineText);
if (inst !== null) {
if (inst.instanceKey === null) {
inst.instanceKey = InstanceKey.parse(instanceId.toString());
}
fix.edit.replace(document.uri, line.range, " " + inst.toString());
}
}
return fix;
}
public provideCodeActions(document: vscode.TextDocument, range: vscode.Range): vscode.CodeAction[] | undefined {
let sectionName = "";
let lineIndex = range.start.line;
while (lineIndex > 0) {
let szline = document.lineAt(new vscode.Position(lineIndex, 0));
if (szline.text.endsWith(':')) {
sectionName = szline.text.substring(0, szline.text.length - 1);
break;
}
lineIndex--;
}
let ary : vscode.CodeAction[] = [];
let line = document.lineAt(range.start).text.trim();
if (line !== sectionName + ":") {
if (sectionName === "attributes") {
// parse attribute declaration
let result = ZqParser.parseAttributeDeclaration(line);
if (result !== null) {
if (result.instanceKey === null || result.dataType === null) {
const fixImportIidFromMocha = this.createIidImportFix(document, range, "attribute", new InstanceKey(1, 1234));
ary.push(fixImportIidFromMocha);
}
}
}
else if (sectionName === "instances") {
// parse attribute declaration
let result = ZqParser.parseInstanceDeclaration(line);
if (result !== null) {
if (result.instanceKey === null) {
const fixImportIidFromMocha = this.createIidImportFix(document, range, "instance", new InstanceKey(1, 1234));
ary.push(fixImportIidFromMocha);
}
}
}
}
/*
const COMMAND = 'mocha.suvManager.suv_select';
const commandAction = new vscode.CodeAction('Learn more...', vscode.CodeActionKind.Empty);
commandAction.command = { command: COMMAND, title: 'Learn more about emojis', tooltip: 'This will open the unicode emoji page.' };
ary.push(commandAction);
*/
return ary;
}
}