diff --git a/package-lock.json b/package-lock.json index 2520e08c82eaec118d9fe699488c1f166d6cca99..bc887e0098f567bc3831eb3cd1ea2f1656f76c87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2206,9 +2206,9 @@ } }, "vscode-uri": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.1.tgz", - "integrity": "sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", + "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==" }, "which": { "version": "2.0.2", diff --git a/package.json b/package.json index 7fe80b6b99e24d137a57736d4326dfa5f4d4dd03..6ec7be9a82a716096052decddbcc0db63f56a394 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "stream-to-array": "^2.3.0", "ts-xor": "^1.0.8", "vscode-api": "0.0.0", - "vscode-uri": "^2.1.1", + "vscode-uri": "^2.1.2", "without": "^1.2.3" } } diff --git a/server/src/ErrorMatcher.ts b/server/src/ErrorMatcher.ts deleted file mode 100644 index 4f6c94d34bc4f4a611f0cbb62ada970b21fe7f87..0000000000000000000000000000000000000000 --- a/server/src/ErrorMatcher.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver' -import { TextDocument, Position } from 'vscode-languageserver-textdocument' -import * as fs from 'fs'; -import * as readline from 'readline' -import { Uri } from 'vscode'; - -export class ErrorMatcher { - - - /** - * Reads the error file, casts the errors to a readable form, sorts them by origin - * @param errorPath the path to the file where the ndjson errors are stored - */ - async readErrors(errorPath : string) : Promise<Map<Uri, Set<Error | Warning | Information>>> { - let result : Map<Uri, Set<Error | Warning | Information>> = new Map() - - var stream = fs.createReadStream(errorPath) - - const rl = readline.createInterface({ - input: stream, - crlfDelay: Infinity - }); - - type ErrorInfromationWarning = Error | Information | Warning - for await (const line of rl) { - - let obj: ErrorInfromationWarning = JSON.parse(line) - - let serveity: DiagnosticSeverity - let content: JBody - let path : Uri - - - - if (isError(obj)) { - path = Uri.parse(obj.error.file) - } - else { - if (isWarning(obj)) { - path = Uri.parse(obj.warning.file) - - } else { - path = Uri.parse(obj.information.file) - - } - } - - if(!result.has(path)){ - result.set(path, new Set()) - } - - result.get(path)!.add(obj) - } - - return result - } - - - matchErrors(infos : Set<Error | Information | Warning>) : Set<Diagnostic>{ - let result : Set<Diagnostic> - type ErrorInfromationWarning = Error | Information | Warning - - for (let info : ErrorInfromationWarning in infos){ - - let serveity : DiagnosticSeverity - - if(isError(info)){ - serveity = DiagnosticSeverity.Error - - }else{ - if(isWarning(info)){ - serveity = DiagnosticSeverity.Warning - }else{ - serveity = DiagnosticSeverity.Warning - }} - - - let diagnostic = { - severity: serveity, - range: { - start: - { - character: in.start.col, - line: content.start.line - 1 - }, - end: { - character: content.end.col, - line: content.end.line - 1 - } - }, - message: content.message, - source: 'prob_prolog', - - }; - diagnostics.add(diagnostic) - } - ) - - } - - async matchError(target: TextDocument, errorPath: string): Promise<Set<Diagnostic>> { - var diagnostics: Set<Diagnostic> = new Set() - - var stream = fs.createReadStream(errorPath) - - const rl = readline.createInterface({ - input: stream, - crlfDelay: Infinity - }); - - - for await (const line of rl) { - - try { - let obj: XOR3<Error, Warning, Information> = JSON.parse(line) - - let diagnostic: Diagnostic - let serveity: DiagnosticSeverity - let content: JBody - - if (obj.error) { - content = obj.error - serveity = DiagnosticSeverity.Error - } - else { - if (obj.warning) { - content = obj.warning - serveity = DiagnosticSeverity.Warning - } else { - content = obj.information - serveity = DiagnosticSeverity.Information - } - } - - - - if(content.start.line == -1 && content.start.col == -1 && content.end.line == -1 && content.end.col == -1){ - console.log("Error without position") - let targetLine = getFirstOccurence(target).line - console.log("new pos is: " + targetLine) - content.start = {line : targetLine , col: 0 } - content.end = {line : targetLine , col: Number.MAX_VALUE } - } - - - - diagnostic = { - severity: serveity, - range: { - start: - { - character: content.start.col, - line: content.start.line - 1 - }, - end: { - character: content.end.col, - line: content.end.line - 1 - } - }, - message: content.message, - source: 'prob_prolog', - - }; - diagnostics.add(diagnostic) - } - catch (e) { - throw new Error("unexpected syntax while parsing _error.json") - } - } - return diagnostics - - } - - -} - - -export declare type XOR3<T, U, V> = (T | U | V) extends object ? ((Without<T, U> & U) & (Without<V, U> & U)) | - ((Without<V, T> & T) & (Without<U, T> & T)) | - ((Without<T, V> & V) & (Without<U, V> & V)) : T | U | V; - - -export declare type Without<T, U> = { - [P in Exclude<keyof T, keyof U>]?: never; -}; - - -export interface CommonInfo{} - -export interface Information extends CommonInfo { - information : JBody -} - - -export interface Error extends CommonInfo { - error: JBody; - -} - -export interface Warning extends CommonInfo{ - warning: JBody -} - -export interface JBody { - message: string; - type: string; - file: string; - start: StartOrEnd; - end: StartOrEnd; -} - -export interface StartOrEnd { - line: number; - col: number; -} - -function getFirstOccurence(textdocument : TextDocument) : Position { - let counter : number = textdocument.getText().search(/\S|$/) - let pos : Position = textdocument.positionAt(counter) - pos.line = pos.line + 1 - return pos -} - - -function isError (potential : CommonInfo) : potential is Error{ - if((potential as Error).error){ - return true - }else{ - return false - } -} - -function isWarning (potential : CommonInfo) : potential is Warning{ - if((potential as Warning).warning){ - return true - }else{ - return false - } -} - -function isInformation (potential : CommonInfo) : potential is Information{ - if((potential as Information).information){ - return true - }else{ - return false - } -} \ No newline at end of file diff --git a/server/src/errorHandler.ts b/server/src/errorHandler.ts new file mode 100644 index 0000000000000000000000000000000000000000..74de5c04398b11cd7bcd0877e5c19b1f8f685852 --- /dev/null +++ b/server/src/errorHandler.ts @@ -0,0 +1,138 @@ +import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver' +import { TextDocument, Position } from 'vscode-languageserver-textdocument' +import * as fs from 'fs'; +import * as readline from 'readline' +import { Uri } from 'vscode'; + + + +/** + * Reads the error file, casts the errors to a readable form, sorts them by origin + * @param errorPath the path to the file where the ndjson errors are stored + */ +export async function readErrors(errorPath: string): Promise<Map<string, Set<NDJSON>>> { + let result: Map<string, Set<NDJSON>> = new Map() + + var stream = fs.createReadStream(errorPath) + + const rl = readline.createInterface({ + input: stream, + crlfDelay: Infinity + }); + + for await (const line of rl) { + + let obj: NDJSON = JSON.parse(line) + + let path: string = obj.details.file + + + if (!result.has(path)) { + result.set(path, new Set()) + } + + result.get(path)!.add(obj) + } + + return result +} + +/** + * Builds diagnostics for a given Set of infos + * @param infos the set of infos to build diagnostics with + */ +export function matchErrors(infos: Set<NDJSON>, file?: TextDocument | undefined): Array<Diagnostic> { + let result: Array<Diagnostic> = new Array() + + for (var info of infos) { + + + let serveity: DiagnosticSeverity = DiagnosticSeverity.Error + + if (info.type == "error") { + serveity = DiagnosticSeverity.Error + } + + if (info.type == "warning") { + serveity = DiagnosticSeverity.Warning + } + + if (info.type == "information") { + serveity = DiagnosticSeverity.Information + } + + let content: JBody = info.details + + // Managing case we get conent with no error massage + if (content.start.line == -1 && content.start.col == -1 && content.end.line == -1 && content.end.col == -1) { + if (file != undefined) { + //Due to server architecture we only have the most actual document + let targetLine = getFirstOccurence(file).line + content.start = { line: targetLine, col: 0 } + content.end = { line: targetLine, col: Number.MAX_VALUE } + } else { + //Fallback if an error occurs on differnt document + content.start = { line: 1, col: 0 } + content.end = { line: 1, col: Number.MAX_VALUE } + } + } + + let diagnostic = { + severity: serveity, + range: { + start: + { + character: content.start.col, + line: content.start.line - 1 + }, + end: { + character: content.end.col, + line: content.end.line - 1 + } + }, + message: content.message, + source: 'prob_prolog', + + }; + + result.push(diagnostic) + } + + return result + +} + + +////////////// Representation of the NDJSON File +export interface NDJSON { + type: string + details: JBody +} + +export interface JBody { + message: string; + type: string; + file: string; + start: StartOrEnd; + end: StartOrEnd; +} + +export interface StartOrEnd { + line: number; + col: number; +} + +/////////////////// + + +/** + * Returns the exakt positiion of the first occurence of an non whitespace token + * @param textdocument the Textdocument to analzie + */ +function getFirstOccurence(textdocument: TextDocument): Position { + let counter: number = textdocument.getText().search(/\S|$/) + let pos: Position = textdocument.positionAt(counter) + pos.line = pos.line + 1 + return pos +} + diff --git a/server/src/server.ts b/server/src/server.ts index 5953b0e2fabeb4112428c737dd37aa43dce03f04..dd293b53c1a9622a76e28c503cf3f92c5beff1be 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -9,8 +9,7 @@ import { TextDocumentPositionParams, TextDocumentSyncKind, InitializeResult, - TextDocumentItem, - + } from 'vscode-languageserver'; import { @@ -18,14 +17,9 @@ import { } from 'vscode-languageserver-textdocument'; -import {Proposed} from 'vscode-languageserver-protocol' - - - - import { URI } from 'vscode-uri'; import * as fs from 'fs'; -import {ErrorMatcher} from './ErrorMatcher'; +import { NDJSON, readErrors, matchErrors } from './errorHandler'; import * as wordComplition from './wordCompletion' import * as path from 'path'; @@ -34,7 +28,6 @@ import * as path from 'path'; // Create a connection for the server. The connection uses Node's IPC as a transport. // Also include all preview / proposed LSP features. let connection = createConnection(ProposedFeatures.all); -let test = createConnection(ProposedFeatures.all) // Create a simple text document manager. The text document manager // supports full document sync only @@ -64,9 +57,9 @@ connection.onInitialize((params: InitializeParams) => { capabilities.textDocument.publishDiagnostics.relatedInformation ); - - + + const result: InitializeResult = { capabilities: { textDocumentSync: TextDocumentSyncKind.Full, @@ -101,21 +94,21 @@ connection.onInitialized(() => { // The settings interface Settings { maxNumberOfProblems: number - strictChecks : boolean - wdChecks : boolean - performanceHints : boolean + strictChecks: boolean + wdChecks: boolean + performanceHints: boolean //PYTHONSETTING - probHome : string + probHome: string } -const defaultSettings: Settings = { - maxNumberOfProblems: 1000, +const defaultSettings: Settings = { + maxNumberOfProblems: 1000, probHome: "~/prob_prolog/probcli.sh", - strictChecks : false, - wdChecks : false, + strictChecks: false, + wdChecks: false, //PYTHONDEFAULT - performanceHints : false + performanceHints: false }; let globalSettings: Settings = defaultSettings; @@ -170,76 +163,82 @@ documents.onDidSave(change => { async function validateTextDocument(textDocument: TextDocument): Promise<void> { let settings = await getDocumentSettings(textDocument.uri) // Waiting for correct setting; otherwise allways default - - let documentPath:path.ParsedPath = path.parse(URI.parse(textDocument.uri).path); + let documentPath: path.ParsedPath = path.parse(URI.parse(textDocument.uri).path); + + let errorPath: string = documentPath.dir + '/_error.json' - let errorPath:string = documentPath.dir+'/_error.json' + const { exec } = require('child_process'); - const {exec} = require('child_process'); - - fs.writeFile(errorPath, "", () =>{}) //Insure a clean error file + fs.writeFile(errorPath, "", () => { }) //Insure a clean error file - let command:string = getCommand(URI.parse(textDocument.uri).path, errorPath, settings) - - let diagnostics : Array<Diagnostic> = new Array() - let diagnosticsPromise : Promise<Set<Diagnostic>> + let command: string = getCommand(URI.parse(textDocument.uri).path, errorPath, settings) - console.log(command) - if(correctPath(settings.probHome)) - { - exec(command, (err:string, stdout:string, stderr:string) => { - let bla = new ErrorMatcher() - diagnosticsPromise = bla.matchError(textDocument, errorPath) + //console.log(command) + if (correctPath(settings.probHome)) { + exec(command, (err: string, stdout: string, stderr: string) => { + let errorMap: Promise<Map<string, Set<NDJSON>>> = readErrors(errorPath) + errorMap.then(function (result: Map<string, Set<NDJSON>>) { - diagnosticsPromise.then(function(result:Set<Diagnostic>){ - diagnostics = Array.from(result) - connection.sendDiagnostics({ uri: textDocument.uri, diagnostics}); - }, function(err){ - connection.sendNotification("parse_error_prob", "there are things wrong with the parse results " + err) + let diagnostics: Array<Diagnostic> = new Array() + + for (let entry of result) { + if (entry[0] == textDocument.uri) { + diagnostics = matchErrors(entry[1], textDocument) + } else { + diagnostics = matchErrors(entry[1]) + } + console.log("sending to " + entry[0]) + connection.sendDiagnostics({ uri: entry[0], diagnostics }); + } + + }, function (err) { + connection.sendNotification("parse_error_prob", "there are things wrong with the parse results " + err) + }); }) - }); } - } -function getCommand(documentPath : string, errorPath : string, settings: Settings) : string{ + + + +function getCommand(documentPath: string, errorPath: string, settings: Settings): string { let wdCmd = "" let strict = "" let perf = "" - console.log(documentPath) - //PYTHONVAR +// console.log(documentPath) + //PYTHONVAR - if(settings.wdChecks == true){ + if (settings.wdChecks == true) { wdCmd = " -wd-check -release_java_parser " } - if(settings.strictChecks == true){ + if (settings.strictChecks == true) { strict = " -p STRICT_CLASH_CHECKING TRUE -p TYPE_CHECK_DEFINITIONS TRUE -lint " } - if(settings.performanceHints == true){ + if (settings.performanceHints == true) { perf = " -p PERFORMANCE_INFO TRUE " } //PYTHONIF - - return settings.probHome + ' -p MAX_INITIALISATIONS 0 -version ' - + perf - + strict - + wdCmd - /*PYTHONCMD*/ - + documentPath - +" -p " - + "NDJSON_ERROR_LOG_FILE " - + errorPath + + return settings.probHome + ' -p MAX_INITIALISATIONS 0 -version ' + + perf + + strict + + wdCmd + /*PYTHONCMD*/ + + documentPath + + " -p " + + "NDJSON_ERROR_LOG_FILE " + + errorPath } -function correctPath(path:string): boolean{ - try{ +function correctPath(path: string): boolean { + try { fs.accessSync(path) - }catch(e){ + } catch (e) { connection.sendNotification("path_error_prob", path + " seems to be the wrong path to ProB") return false } @@ -249,7 +248,7 @@ function correctPath(path:string): boolean{ // This handler provides the initial list of the completion items. connection.onCompletion( - + (textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => { return wordComplition.selectCompletion(textDocumentPosition) } @@ -261,7 +260,7 @@ connection.onCompletion( //Can be used to enrich completion with more infos connection.onCompletionResolve( - (item : CompletionItem) => {return item}) + (item: CompletionItem) => { return item }) // for open, change and close text document events diff --git a/tsconfig.json b/tsconfig.json index a1a93135cbea3958e0d4e8cf6533236c71d578d0..ed91ffa45fb1c9fc4b9efb4b0c46c67aee4cbb01 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "esModuleInterop": true, "module": "commonjs", "target": "es6", "outDir": "out",