diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..b5ff360 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "jacobfriedman.vsc-logtalk" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a73a41..22a6bfa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1,4 @@ { + "logtalk.home.path": "/home/jfriedman/logtalk3", + "logtalk.executable.path": "lvmlgt.sh" } \ No newline at end of file diff --git a/README.md b/README.md index f893b1f..c494510 100755 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ Feel free to report bugs or suggestions via [issues](https://github.com/arthwang ## Development -This extension has been package and tested with node v14.17.5 (latest LTS). +This extension has been package and tested with node v12. You may install the extension directly from the .vsix file included in this repo. `vsix:make` makes the .vsix file, `vsix:install` installs it. @@ -178,6 +178,7 @@ You may install the extension directly from the .vsix file included in this repo ### Version 4: +- Regex overhaul & document lint - Logtalk linter does not run upon opening a document or workspace . - F8 performs `logtalk_make`. - F9 loads via `logtalk_load`. diff --git a/package.json b/package.json index 63f10e1..9eba06e 100755 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vsc-logtalk", "displayName": "VSC-Logtalk", "description": "Support for Logtalk language", - "version": "0.4.2", + "version": "0.4.4", "publisher": "jacobfriedman", "icon": "images/logtalk.png", "license": "MIT", @@ -294,7 +294,7 @@ "test": "tsc ./tests/runTest.ts", "syntax4logtalk": "yaml2json --pretty --save ./syntaxes/logtalk.tmLanguage.yaml", "vsix:make": "vsce package --baseImagesUrl https://raw.githubusercontent.com/llvm/llvm-project/master/clang-tools-extra/clangd/clients/clangd-vscode/", - "vsix:install": "code --install-extension vsc-logtalk-0.4.2.vsix" + "vsix:install": "code --install-extension vsc-logtalk-0.4.4.vsix" }, "devDependencies": { "@types/bluebird": "^3.5.25", diff --git a/src/features/logtalkLinter.ts b/src/features/logtalkLinter.ts index ae20db5..d1f0d22 100755 --- a/src/features/logtalkLinter.ts +++ b/src/features/logtalkLinter.ts @@ -32,11 +32,15 @@ export default class LogtalkLinter implements CodeActionProvider { private diagnostics: { [docName: string]: Diagnostic[] } = {}; private filePathIds: { [id: string]: string } = {}; private sortedDiagIndex: { [docName: string]: number[] } = {}; - private msgRegex = /([^:]+):\s*([^:]+):(\d+):((\d+):)?((\d+):)?\s*([\s\S]*)/; + private msgRegex = /([^:]+):\s*([^:]+):(\d+):?\s*([\s\S]*)/; + + private msgRegexSingle = /(\*|\!)+\s{5}(.+)\n[\*|\!]+\s{7}(.+)\n[\*|\!]\s{7}in file\s(\S+).+line\s(\d+)/; + private msgRegexMulti = /(\*|\!)+\s{5}(.+)\n[\*|\!]+\s{7}(.+)\n[\*|\!]\s{7}in file\s(\S+).+between lines\s(\d+)-(\d+)/; + private executable: string; private documentListener: Disposable; private openDocumentListener: Disposable; - private outputChannel: OutputChannel = null; + public outputChannel: OutputChannel = null; constructor(private context: ExtensionContext) { this.executable = null; @@ -53,24 +57,27 @@ export default class LogtalkLinter implements CodeActionProvider { return codeActions; } private parseIssue(issue: string) { - let match = issue.match(this.msgRegex); - if (match == null) return null; - let fileName = this.filePathIds[match[2]] - ? this.filePathIds[match[2]] - : match[2]; + let match = issue.match(this.msgRegexSingle) || issue.match(this.msgRegexMulti); + if (match == null) { return null; } + let severity: DiagnosticSeverity; - if (match[1] == "ERROR") severity = DiagnosticSeverity.Error; - else if (match[1] == "Warning") severity = DiagnosticSeverity.Warning; - let line = parseInt(match[3]) - 1; - // move up to above line if the line to mark error is empty - line = line < 0 ? 0 : line; - let fromCol = match[5] ? parseInt(match[5]) : 0; - fromCol = fromCol < 0 ? 0 : fromCol; - let toCol = match[7] ? parseInt(match[7]) : 200; + if(match[1] == '*') { + severity = DiagnosticSeverity.Warning + } else { + severity = DiagnosticSeverity.Error + } + + let fileName = this.filePathIds[match[4]] + ? this.filePathIds[match[4]] + : match[4]; + + let line = parseInt(match[5]); + let fromCol = 0 + let toCol = 200 // Default horizontal range let fromPos = new Position(line, fromCol); - let toPos = new Position(line, toCol); + let toPos = match.length == 7 ? new Position(parseInt(match[6]), toCol) : new Position(line, toCol); let range = new Range(fromPos, toPos); - let errMsg = match[8]; + let errMsg = match[2] + ' ' + match[3]; let diag = new Diagnostic(range, errMsg, severity); if (diag) { if (!this.diagnostics[fileName]) { @@ -79,118 +86,46 @@ export default class LogtalkLinter implements CodeActionProvider { this.diagnostics[fileName].push(diag); } } - } - public doPlint(textDocument: TextDocument, sendString) { - if (textDocument.languageId != "logtalk") { - return; - } + } + public lint(textDocument: TextDocument, message) { + console.log(message); this.diagnostics = {}; this.sortedDiagIndex = {}; this.diagnosticCollection.delete(textDocument.uri); - let args: string[] = [], - goals: string = `logtalk_load('${textDocument.fileName}').`, - lineErr: string = ""; - - let cwd = dirname(textDocument.fileName); - - let child = spawn(this.executable, args, {cwd}) - .on("process", process => { - console.log('spawned!'); - if (process.pid) { - process.stdin.write(goals); - process.stdin.end(); - this.outputChannel.clear(); - } - }) - .on("stdout", out => { - console.log("lintout:" + out + "\n"); - }) - .on("stderr", (errStr: string) => { - console.log("linterr: " + errStr); - if (lineErr === "") { - let type: string; - let regex = /^(\*|\!)\s*(.+)/; - let match = errStr.match(regex); - if (match) { - if (match[1] === "*") { - type = "Warning"; - } - if (match[1] === "!") { - type = "ERROR"; - } - lineErr = type + ":" + match[2]; - } - } else if (/in file/.test(errStr)) { - let regex = /in file (\S+).+lines?\s+(\d+)/; - let match = errStr.match(regex); - let errMsg: string; - if (match) { - lineErr = lineErr.replace(":", `:${match[1]}:${match[2]}`); - this.parseIssue(lineErr + "\n"); - lineErr = ""; - } - } - }) - .then(result => { - console.log('fulfilled'); - if (lineErr) { - this.parseIssue(lineErr + "\n"); - } - for (let doc in this.diagnostics) { - let index = this.diagnostics[doc] - .map((diag, i) => { - return [diag.range.start.line, i]; - }) - .sort((a, b) => { - return a[0] - b[0]; - }); - this.sortedDiagIndex[doc] = index.map(item => { - return item[1]; - }); - this.diagnosticCollection.set(Uri.file(doc), this.diagnostics[doc]); - } - this.outputChannel.clear(); - for (let doc in this.sortedDiagIndex) { - let si = this.sortedDiagIndex[doc]; - for (let i = 0; i < si.length; i++) { - let diag = this.diagnostics[doc][si[i]]; - let severity = - diag.severity === DiagnosticSeverity.Error ? "ERROR" : "Warning"; - let msg = `${basename(doc)}:${diag.range.start.line + - 1}:\t${severity}:\t${diag.message}\n`; - this.outputChannel.append(msg); - } - if (si.length > 0) { - this.outputChannel.show(true); - } - } - }) - .catch(error => { - let message: string = null; - console.log(error); - if ((error).code === "ENOENT") { - message = - "Cannot lint the logtalk file. The Logtalk executable was not found. Use the 'logtalk.executable.path' setting to configure"; - } else { - message = error.message - ? error.message - : `Failed to run logtalk executable using path: ${this.executable}. Reason is unknown.`; - } - this.outputMsg(message); + this.parseIssue(message); + + console.log('Single:', message.match(this.msgRegexSingle)); + console.log('Multi:', message.match(this.msgRegexMulti)); + + for (let doc in this.diagnostics) { + let index = this.diagnostics[doc] + .map((diag, i) => { + return [diag.range.start.line, i]; + }) + .sort((a, b) => { + return a[0] - b[0]; + }); + this.sortedDiagIndex[doc] = index.map(item => { + return item[1]; }); - } - - /*public triggerLinter(textDocument: TextDocument) { - if (textDocument.languageId !== "logtalk") { - return; + this.diagnosticCollection.set(Uri.file(doc), this.diagnostics[doc]); + } + for (let doc in this.sortedDiagIndex) { + let si = this.sortedDiagIndex[doc]; + for (let i = 0; i < si.length; i++) { + let diag = this.diagnostics[doc][si[i]]; + let severity = + diag.severity === DiagnosticSeverity.Error ? "ERROR" : "Warning"; + this.outputChannel.append(message) + } + if (si.length > 0) { + this.outputChannel.show(true); + } } - - this.doPlint(textDocument); } - */ private loadConfiguration(): void { let section = workspace.getConfiguration("logtalk"); @@ -203,10 +138,6 @@ export default class LogtalkLinter implements CodeActionProvider { this.openDocumentListener.dispose(); } } - - this.documentListener = workspace.onDidSaveTextDocument(e => this.doPlint, this); - /* workspace.textDocuments.forEach(this.triggerLinter, this); */ - } public activate(subscriptions): void { @@ -289,6 +220,7 @@ export default class LogtalkLinter implements CodeActionProvider { position = diagnostics[si[i]].range.start; } } + editor.revealRange(diagnostics[si[i]].range, TextEditorRevealType.InCenter); diagnostics.forEach(item => { @@ -302,5 +234,6 @@ export default class LogtalkLinter implements CodeActionProvider { }); editor.selection = new Selection(position, position); this.outputChannel.show(true); + } } diff --git a/src/features/logtalkTerminal.ts b/src/features/logtalkTerminal.ts index 86a8226..2702071 100755 --- a/src/features/logtalkTerminal.ts +++ b/src/features/logtalkTerminal.ts @@ -3,16 +3,15 @@ import { Terminal, window, workspace, TextDocument, Disposable, OutputChannel, Uri, ExtensionContext } from "vscode"; import * as path from "path"; import * as jsesc from "jsesc"; -import * as cp from "child_process"; import * as fs from "fs"; +import * as cp from 'child_process'; import { spawn } from "process-promises"; import LogtalkLinter from "./logtalkLinter"; import { isFunction } from "util"; - export default class LogtalkTerminal { private static _context: ExtensionContext; - private static _messageProcess; + private static _messages : any; private static _terminal: Terminal; private static _testerExec: string; private static _testerArgs: string[]; @@ -32,7 +31,6 @@ export default class LogtalkTerminal { public static init(context: ExtensionContext): Disposable { LogtalkTerminal._context = context; - LogtalkTerminal._messageProcess = {} let section = workspace.getConfiguration("logtalk"); LogtalkTerminal._testerExec = section.get("tester.script", "logtalk_tester"); @@ -49,7 +47,10 @@ export default class LogtalkTerminal { return (window).onDidCloseTerminal(terminal => { LogtalkTerminal._terminal = null; terminal.dispose(); - LogtalkTerminal._messageProcess.kill('SIGHUP'); + if ('kill' in LogtalkTerminal._messages) { + LogtalkTerminal._messages.kill('SIGHUP'); + } + }); } @@ -61,41 +62,15 @@ export default class LogtalkTerminal { let section = workspace.getConfiguration("logtalk"); if (section) { - let pathLogtalkHome = jsesc(section.get("home.path", "logtalk")), - pathLogtalkMessageFile = `'${pathLogtalkHome}/coding/vscode/.messages'`; - - fs.unlink(pathLogtalkMessageFile, (error) => { - if (error) { - return new Error(`Could not remove the logtalk message file ${pathLogtalkMessageFile}.`); - } else { - fs.writeFile(`${pathLogtalkMessageFile}`, '', (error) => { - if(error) { - return new Error(`Could not create the logtalk message file ${pathLogtalkMessageFile}.`) - } else { - - LogtalkTerminal._messageProcess = cp.spawn(`tail -f ${pathLogtalkMessageFile}`); - - LogtalkTerminal._messageProcess.stdout.on('data', (data) => { - console.log(`stdout: ${data}`); - }); - - let executable = jsesc(section.get("executable.path", "logtalk")); - let args = section.get("terminal.runtimeArgs"); - LogtalkTerminal._terminal = (window).createTerminal( - "Logtalk", - executable, - args - ); - let goals = `logtalk_load(coding('vscode/vscode_message_streamer.lgt')).\r`; - LogtalkTerminal.sendString(goals, false); - - } - }) - } - - - }) - + let executable = jsesc(section.get("executable.path", "logtalk")); + let args = section.get("terminal.runtimeArgs"); + LogtalkTerminal._terminal = (window).createTerminal( + "Logtalk", + executable, + args + ); + let goals = `logtalk_load(coding('vscode/vscode_message_streamer.lgt')).\r`; + LogtalkTerminal.sendString(goals, false); } else { throw new Error("configuration settings error: logtalk"); @@ -116,29 +91,42 @@ export default class LogtalkTerminal { public static async loadDocument(uri: Uri, linter: LogtalkLinter) { const file: string = await LogtalkTerminal.ensureFile(uri); + let textDocument = null; let working_directory: string = path.dirname(uri.fsPath); - await workspace.openTextDocument(uri); let logtalkHome: string = ''; let section = workspace.getConfiguration("logtalk"); + if (section) { logtalkHome = jsesc(section.get("home.path", "logtalk")); } else { throw new Error("configuration settings error: logtalk"); } - // await workspace.openTextDocument(uri).then( - // (textDocument: TextDocument) => { - // linter.doPlint(textDocument, LogtalkTerminal.sendString) - // }); - + let pathLogtalkMessageFile = `${logtalkHome}/coding/vscode/.messages`; + // Remove the temp messages file + cp.spawn('rm', [`${pathLogtalkMessageFile}`]); + + LogtalkTerminal._messages = cp.spawn('tail', ['-f',`${pathLogtalkMessageFile}`, '-n','0']) + + await workspace.openTextDocument(uri).then((document: TextDocument) => { textDocument = document }); LogtalkTerminal.createLogtalkTerm(); + + // Linting + linter.outputChannel.clear(); + let message = ''; + LogtalkTerminal._messages.stdout.on('data', function(data) { + let output = data.toString('ascii'); + message += output; + console.log(data) + let last = data.slice(data.length-7, data.length); + if(last.toString() == '* \n' || last.toString() == '! \n') { + linter.lint(textDocument, message); + message = ''; + } + }); + LogtalkTerminal.sendString(`logtalk_load('${file}').\r`, false); - - // LogtalkTerminal.spawnScript( - // ["logtalk_load", "logtalk.document.load", LogtalkTerminal._testerExec], - // LogtalkTerminal._testerExec, - // LogtalkTerminal._testerArgs - // ); + } public static async make(uri: Uri) { diff --git a/test/errors.lgt b/test/errors.lgt index adeec4a..d68d533 100644 --- a/test/errors.lgt +++ b/test/errors.lgt @@ -1 +1 @@ -:- initialization(logtalk_load([examples('errors/errors_loader'), examples('errors/warnings_loader')])). \ No newline at end of file +:- initialization(logtalk_load([examples('errors/errors_loader'), examples('errors/warnings_loader')])). diff --git a/vsc-logtalk-0.4.2.vsix b/vsc-logtalk-0.4.4.vsix similarity index 99% rename from vsc-logtalk-0.4.2.vsix rename to vsc-logtalk-0.4.4.vsix index 73023f5..edcbbd5 100644 Binary files a/vsc-logtalk-0.4.2.vsix and b/vsc-logtalk-0.4.4.vsix differ