From 143f888eed4cd9b1503f7176bafa7fc74900cba2 Mon Sep 17 00:00:00 2001 From: barakliato Date: Fri, 15 Dec 2017 17:26:26 +0200 Subject: [PATCH 01/20] add flag to silent the eval alert + reload namespace command + auto reload on save --- src/clojureEval.ts | 20 ++++++++++++------ src/clojureMain.ts | 15 ++++++++++---- src/clojureReloadNamespace.ts | 39 +++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 src/clojureReloadNamespace.ts diff --git a/src/clojureEval.ts b/src/clojureEval.ts index 75d5971..900ddd1 100644 --- a/src/clojureEval.ts +++ b/src/clojureEval.ts @@ -4,11 +4,19 @@ import { cljConnection } from './cljConnection'; import { cljParser } from './cljParser'; import { nreplClient } from './nreplClient'; -export function clojureEval(outputChannel: vscode.OutputChannel): void { +function getAlertOnEvalResult() { + const configName = 'aletOnEval'; + let editorConfig = vscode.workspace.getConfiguration('editor'); + const globalEditorFormatOnSave = editorConfig && editorConfig.has(configName) && editorConfig.get(configName) === true; + let clojureConfig = vscode.workspace.getConfiguration('clojureVSCode'); + return ((clojureConfig.aletOnEval || globalEditorFormatOnSave)); +} + +export function clojureEval(outputChannel: vscode.OutputChannel): void { evaluate(outputChannel, false); } -export function clojureEvalAndShowResult(outputChannel: vscode.OutputChannel): void { +export function clojureEvalAndShowResult(outputChannel: vscode.OutputChannel): void { evaluate(outputChannel, true); } @@ -46,8 +54,8 @@ function evaluate(outputChannel: vscode.OutputChannel, showResults: boolean): vo }); } -function handleError(outputChannel: vscode.OutputChannel, selection: vscode.Selection, showResults: boolean, session: string): Promise { - if (!showResults) +export function handleError(outputChannel: vscode.OutputChannel, selection: vscode.Selection, showResults: boolean, session: string): Promise { + if (!showResults && getAlertOnEvalResult()) vscode.window.showErrorMessage('Compilation error'); return nreplClient.stacktrace(session) @@ -75,8 +83,8 @@ function handleError(outputChannel: vscode.OutputChannel, selection: vscode.Sele }); } -function handleSuccess(outputChannel: vscode.OutputChannel, showResults: boolean, respObjs: any[]): void { - if (!showResults) { +export function handleSuccess(outputChannel: vscode.OutputChannel, showResults: boolean, respObjs: any[]): void { + if (!showResults && getAlertOnEvalResult()) { vscode.window.showInformationMessage('Successfully compiled'); } else { respObjs.forEach(respObj => { diff --git a/src/clojureMain.ts b/src/clojureMain.ts index 9614418..297a0db 100644 --- a/src/clojureMain.ts +++ b/src/clojureMain.ts @@ -11,16 +11,17 @@ import { JarContentProvider } from './jarContentProvider'; import { nreplController } from './nreplController'; import { cljConnection } from './cljConnection'; import { formatFile, maybeActivateFormatOnSave } from './clojureFormat'; +import { reloadNamespaceCommand, getReloadOnFileSave } from './clojureReloadNamespace'; export function activate(context: vscode.ExtensionContext) { cljConnection.setCljContext(context); context.subscriptions.push(nreplController); cljConnection.disconnect(false); - var config = vscode.workspace.getConfiguration('clojureVSCode'); + var config = vscode.workspace.getConfiguration('clojureVSCode'); if (config.autoStartNRepl) { cljConnection.startNRepl(); } - + maybeActivateFormatOnSave(); vscode.commands.registerCommand('clojureVSCode.manuallyConnectToNRepl', cljConnection.manuallyConnect); @@ -30,9 +31,8 @@ export function activate(context: vscode.ExtensionContext) { const evaluationResultChannel = vscode.window.createOutputChannel('Evaluation results'); vscode.commands.registerCommand('clojureVSCode.eval', () => clojureEval(evaluationResultChannel)); vscode.commands.registerCommand('clojureVSCode.evalAndShowResult', () => clojureEvalAndShowResult(evaluationResultChannel)); - vscode.commands.registerTextEditorCommand('clojureVSCode.formatFile', formatFile); - + vscode.commands.registerTextEditorCommand('clojureVSCode.reloadNamespace', ()=> { reloadNamespaceCommand(evaluationResultChannel); }); context.subscriptions.push(vscode.languages.registerCompletionItemProvider(CLOJURE_MODE, new ClojureCompletionItemProvider(), '.', '/')); context.subscriptions.push(vscode.languages.registerDefinitionProvider(CLOJURE_MODE, new ClojureDefinitionProvider())); context.subscriptions.push(vscode.languages.registerHoverProvider(CLOJURE_MODE, new ClojureHoverProvider())); @@ -40,6 +40,13 @@ export function activate(context: vscode.ExtensionContext) { vscode.workspace.registerTextDocumentContentProvider('jar', new JarContentProvider()); vscode.languages.setLanguageConfiguration(CLOJURE_MODE.language, new ClojureLanguageConfiguration()); + + if(getReloadOnFileSave()) { + vscode.workspace.onDidSaveTextDocument( + function (textDocument: vscode.TextDocument) { + reloadNamespaceCommand(evaluationResultChannel); + }, this); + } } export function deactivate() { } diff --git a/src/clojureReloadNamespace.ts b/src/clojureReloadNamespace.ts new file mode 100644 index 0000000..31cc4c3 --- /dev/null +++ b/src/clojureReloadNamespace.ts @@ -0,0 +1,39 @@ +import * as vscode from 'vscode'; +import { cljConnection } from './cljConnection'; +import { cljParser } from './cljParser'; +import { nreplClient } from './nreplClient'; +import { handleError } from './clojureEval'; + +export function getReloadOnFileSave() { + const configName = 'autoReloadNamespaceOnSave'; + let editorConfig = vscode.workspace.getConfiguration('editor'); + const globalEditorFormatOnSave = editorConfig && editorConfig.has(configName) && editorConfig.get(configName) === true; + let clojureConfig = vscode.workspace.getConfiguration('clojureVSCode'); + return ((clojureConfig.autoReloadNamespaceOnSave || globalEditorFormatOnSave)); +} + +export function reloadNamespaceCommand( + outputChannel: vscode.OutputChannel) { + + if (!cljConnection.isConnected()) { + vscode.window.showWarningMessage('You should connect to nREPL first to reload namespace.'); + return; + } + + const textDocument = vscode.window.activeTextEditor.document; + const text = textDocument.getText(); + const ns = cljParser.getNamespace(text); + const commantText = `(require '${ns} :reload)`; + const fileName = textDocument.fileName; + cljConnection.sessionForFilename(fileName).then(session => { + let response = nreplClient.evaluateFile(commantText, fileName, session.id); + + response.then(respObjs => { + if (!!respObjs[0].ex) + return handleError(outputChannel, + new vscode.Selection(0,0,0,0), + false, + respObjs[0].session); + }) + }); +} \ No newline at end of file From 76ea2528ba563e02dbaac37a83ba702b1c424beb Mon Sep 17 00:00:00 2001 From: barakliato Date: Sat, 16 Dec 2017 22:10:13 +0200 Subject: [PATCH 02/20] move common methods to one function for code reusability --- src/clojureEval.ts | 47 ++++++++++++++++++----------------- src/clojureMain.ts | 1 + src/clojureReloadNamespace.ts | 33 +++++++++++------------- 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/src/clojureEval.ts b/src/clojureEval.ts index 900ddd1..8e332e1 100644 --- a/src/clojureEval.ts +++ b/src/clojureEval.ts @@ -3,13 +3,10 @@ import * as vscode from 'vscode'; import { cljConnection } from './cljConnection'; import { cljParser } from './cljParser'; import { nreplClient } from './nreplClient'; +import {readBooleanConfiguration} from './utils'; function getAlertOnEvalResult() { - const configName = 'aletOnEval'; - let editorConfig = vscode.workspace.getConfiguration('editor'); - const globalEditorFormatOnSave = editorConfig && editorConfig.has(configName) && editorConfig.get(configName) === true; - let clojureConfig = vscode.workspace.getConfiguration('clojureVSCode'); - return ((clojureConfig.aletOnEval || globalEditorFormatOnSave)); + return readBooleanConfiguration('aletOnEval'); } export function clojureEval(outputChannel: vscode.OutputChannel): void { @@ -20,6 +17,21 @@ export function clojureEvalAndShowResult(outputChannel: vscode.OutputChannel): v evaluate(outputChannel, true); } +export function evaluateText(outputChannel: vscode.OutputChannel, + showResults: boolean, + fileName: string, + text: string): Promise { + return cljConnection.sessionForFilename(fileName).then(session => { + return (fileName.length === 0 && session.type == 'ClojureScript') + // Piggieback's evalFile() ignores the text sent as part of the request + // and just loads the whole file content from disk. So we use eval() + // here, which as a drawback will give us a random temporary filename in + // the stacktrace should an exception occur. + ? nreplClient.evaluate(text, session.id) + : nreplClient.evaluateFile(text, fileName, session.id); + }); +} + function evaluate(outputChannel: vscode.OutputChannel, showResults: boolean): void { if (!cljConnection.isConnected()) { vscode.window.showWarningMessage('You should connect to nREPL first to evaluate code.'); @@ -34,24 +46,13 @@ function evaluate(outputChannel: vscode.OutputChannel, showResults: boolean): vo text = `(ns ${ns})\n${editor.document.getText(selection)}`; } - cljConnection.sessionForFilename(editor.document.fileName).then(session => { - let response; - if (!selection.isEmpty && session.type == 'ClojureScript') { - // Piggieback's evalFile() ignores the text sent as part of the request - // and just loads the whole file content from disk. So we use eval() - // here, which as a drawback will give us a random temporary filename in - // the stacktrace should an exception occur. - response = nreplClient.evaluate(text, session.id); - } else { - response = nreplClient.evaluateFile(text, editor.document.fileName, session.id); - } - response.then(respObjs => { - if (!!respObjs[0].ex) - return handleError(outputChannel, selection, showResults, respObjs[0].session); - - return handleSuccess(outputChannel, showResults, respObjs); - }) - }); + evaluateText(outputChannel, showResults, editor.document.fileName, text) + .then(respObjs => { + if (!!respObjs[0].ex) + return handleError(outputChannel, selection, showResults, respObjs[0].session); + + return handleSuccess(outputChannel, showResults, respObjs); + }); } export function handleError(outputChannel: vscode.OutputChannel, selection: vscode.Selection, showResults: boolean, session: string): Promise { diff --git a/src/clojureMain.ts b/src/clojureMain.ts index 297a0db..32d6e48 100644 --- a/src/clojureMain.ts +++ b/src/clojureMain.ts @@ -41,6 +41,7 @@ export function activate(context: vscode.ExtensionContext) { vscode.workspace.registerTextDocumentContentProvider('jar', new JarContentProvider()); vscode.languages.setLanguageConfiguration(CLOJURE_MODE.language, new ClojureLanguageConfiguration()); + console.log(getReloadOnFileSave()); if(getReloadOnFileSave()) { vscode.workspace.onDidSaveTextDocument( function (textDocument: vscode.TextDocument) { diff --git a/src/clojureReloadNamespace.ts b/src/clojureReloadNamespace.ts index 31cc4c3..ee13540 100644 --- a/src/clojureReloadNamespace.ts +++ b/src/clojureReloadNamespace.ts @@ -1,15 +1,11 @@ import * as vscode from 'vscode'; import { cljConnection } from './cljConnection'; import { cljParser } from './cljParser'; -import { nreplClient } from './nreplClient'; -import { handleError } from './clojureEval'; +import { handleError, evaluateText } from './clojureEval'; +import {readBooleanConfiguration} from './utils'; -export function getReloadOnFileSave() { - const configName = 'autoReloadNamespaceOnSave'; - let editorConfig = vscode.workspace.getConfiguration('editor'); - const globalEditorFormatOnSave = editorConfig && editorConfig.has(configName) && editorConfig.get(configName) === true; - let clojureConfig = vscode.workspace.getConfiguration('clojureVSCode'); - return ((clojureConfig.autoReloadNamespaceOnSave || globalEditorFormatOnSave)); +export function getReloadOnFileSave() :boolean { + return readBooleanConfiguration('autoReloadNamespaceOnSave') } export function reloadNamespaceCommand( @@ -25,15 +21,14 @@ export function reloadNamespaceCommand( const ns = cljParser.getNamespace(text); const commantText = `(require '${ns} :reload)`; const fileName = textDocument.fileName; - cljConnection.sessionForFilename(fileName).then(session => { - let response = nreplClient.evaluateFile(commantText, fileName, session.id); - - response.then(respObjs => { - if (!!respObjs[0].ex) - return handleError(outputChannel, - new vscode.Selection(0,0,0,0), - false, - respObjs[0].session); - }) - }); + + evaluateText(outputChannel, false, fileName, commantText) + .then(respObjs => { + return (!!respObjs[0].ex) + ? Promise.reject(handleError(outputChannel, + new vscode.Selection(0,0,0,0), + false, + respObjs[0].session)) + : Promise.resolve(); + }) } \ No newline at end of file From 6a3b146d7577e53589f3374ec94f087186e69747 Mon Sep 17 00:00:00 2001 From: barak liato Date: Sat, 16 Dec 2017 22:26:11 +0200 Subject: [PATCH 03/20] add utils.ts --- src/utils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/utils.ts diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..4178b08 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,8 @@ +import * as vscode from 'vscode'; + +export function readBooleanConfiguration(configName) { + let editorConfig = vscode.workspace.getConfiguration('editor'); + const globalEditorConfig = editorConfig && editorConfig.has(configName) && editorConfig.get(configName) === true; + let clojureConfig = vscode.workspace.getConfiguration('clojureVSCode'); + return ((clojureConfig[configName] || globalEditorConfig)); +} \ No newline at end of file From f05f222f11c066bfa78030c6ae11b42f7498ae63 Mon Sep 17 00:00:00 2001 From: barak liato Date: Sat, 16 Dec 2017 22:26:51 +0200 Subject: [PATCH 04/20] remove console.log --- src/clojureMain.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/clojureMain.ts b/src/clojureMain.ts index 32d6e48..470fc7b 100644 --- a/src/clojureMain.ts +++ b/src/clojureMain.ts @@ -40,8 +40,7 @@ export function activate(context: vscode.ExtensionContext) { vscode.workspace.registerTextDocumentContentProvider('jar', new JarContentProvider()); vscode.languages.setLanguageConfiguration(CLOJURE_MODE.language, new ClojureLanguageConfiguration()); - - console.log(getReloadOnFileSave()); + if(getReloadOnFileSave()) { vscode.workspace.onDidSaveTextDocument( function (textDocument: vscode.TextDocument) { From ed950891065b38c7621a6d4096b3def72d2e1f3b Mon Sep 17 00:00:00 2001 From: barak liato Date: Sun, 17 Dec 2017 07:54:23 +0200 Subject: [PATCH 05/20] fix bud were error return from eval use promise reject in then instead of wrapping the returned promise --- src/clojureReloadNamespace.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/clojureReloadNamespace.ts b/src/clojureReloadNamespace.ts index ee13540..90e4eff 100644 --- a/src/clojureReloadNamespace.ts +++ b/src/clojureReloadNamespace.ts @@ -25,10 +25,11 @@ export function reloadNamespaceCommand( evaluateText(outputChannel, false, fileName, commantText) .then(respObjs => { return (!!respObjs[0].ex) - ? Promise.reject(handleError(outputChannel, - new vscode.Selection(0,0,0,0), - false, - respObjs[0].session)) + ? handleError(outputChannel, + new vscode.Selection(0,0,0,0), + false, + respObjs[0].session) + .then(value=>Promise.reject(value)) : Promise.resolve(); }) } \ No newline at end of file From 912ae63b57a9ffdc4df77f9bbcc124c396ce65c5 Mon Sep 17 00:00:00 2001 From: barak liato Date: Sun, 17 Dec 2017 10:03:42 +0200 Subject: [PATCH 06/20] ignore files that dosen't ends with .clj when reloading namespace --- src/clojureReloadNamespace.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/clojureReloadNamespace.ts b/src/clojureReloadNamespace.ts index 90e4eff..9401ecd 100644 --- a/src/clojureReloadNamespace.ts +++ b/src/clojureReloadNamespace.ts @@ -17,10 +17,14 @@ export function reloadNamespaceCommand( } const textDocument = vscode.window.activeTextEditor.document; + const fileName = textDocument.fileName; + if(!fileName.endsWith(".clj")) { + return; + } + const text = textDocument.getText(); const ns = cljParser.getNamespace(text); - const commantText = `(require '${ns} :reload)`; - const fileName = textDocument.fileName; + const commantText = `(require '${ns} :reload)`; evaluateText(outputChannel, false, fileName, commantText) .then(respObjs => { From 151b6efae65404a4b97cc9fe3d6decec13436bed Mon Sep 17 00:00:00 2001 From: barak liato Date: Sun, 17 Dec 2017 13:57:17 +0200 Subject: [PATCH 07/20] remove the alert when the repl is not connected --- src/clojureReloadNamespace.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/clojureReloadNamespace.ts b/src/clojureReloadNamespace.ts index 9401ecd..1c26316 100644 --- a/src/clojureReloadNamespace.ts +++ b/src/clojureReloadNamespace.ts @@ -11,8 +11,7 @@ export function getReloadOnFileSave() :boolean { export function reloadNamespaceCommand( outputChannel: vscode.OutputChannel) { - if (!cljConnection.isConnected()) { - vscode.window.showWarningMessage('You should connect to nREPL first to reload namespace.'); + if (!cljConnection.isConnected()) { return; } From c01f8f8c7be4bec7c86eab8106a4342f7ade04b4 Mon Sep 17 00:00:00 2001 From: barak liato Date: Mon, 18 Dec 2017 11:42:19 +0200 Subject: [PATCH 08/20] add eastwood linter support --- src/cljConnection.ts | 2 +- src/cljParser.ts | 34 +++++++++ src/clojureLintingProvider.ts | 128 ++++++++++++++++++++++++++++++++++ src/clojureMain.ts | 5 ++ 4 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/clojureLintingProvider.ts diff --git a/src/cljConnection.ts b/src/cljConnection.ts index b1ff8ec..9475016 100644 --- a/src/cljConnection.ts +++ b/src/cljConnection.ts @@ -225,4 +225,4 @@ export const cljConnection = { startNRepl, disconnect, sessionForFilename -}; +}; \ No newline at end of file diff --git a/src/cljParser.ts b/src/cljParser.ts index 77936de..48229c1 100644 --- a/src/cljParser.ts +++ b/src/cljParser.ts @@ -1,3 +1,5 @@ +import { TextDocument, Range, Position, CompletionList } from "vscode"; + interface ExpressionInfo { functionName: string; parameterPosition: number; @@ -135,8 +137,40 @@ const getNamespace = (text: string): string => { return m ? m[1] : 'user'; }; +//assume the position is before the block directly +const getBlockRange = (editor: TextDocument, line: number, column: number): Range => { + const lastLineNumber = editor.lineCount - 1; + const lastLine = editor.lineAt(lastLineNumber); + const range = new Range(line, column, lastLineNumber, lastLine.range.end.character); + const text = editor.getText(range); + + let count = 0; + let endPosition = new Position(line, column); + let c = column; + for(let l = line; l < range.end.line;) { + const currentLine = editor.lineAt(l); + for(;c < currentLine.range.end.character;c++) { + if(text[c] === '(') + count++; + + if(text[c] === ')') + count--; + } + + if(count === 0) { + return new Range(line - 1, column, l - 1, c); + } + + l++; + c = 0; + } + + return new Range(line, column, line, column); +} + export const cljParser = { R_CLJ_WHITE_SPACE, getExpressionInfo, getNamespace, + getBlockRange }; diff --git a/src/clojureLintingProvider.ts b/src/clojureLintingProvider.ts new file mode 100644 index 0000000..7a77129 --- /dev/null +++ b/src/clojureLintingProvider.ts @@ -0,0 +1,128 @@ +import * as vscode from 'vscode'; +import * as nreplConnection from './cljConnection'; +import { nreplClient } from './nreplClient'; +import { resolve } from 'dns'; +import { Diagnostic, Range, TextDocument } from 'vscode'; +import { cljParser } from './cljParser'; +import { CLOJURE_MODE } from './clojureMode'; + +interface LinterResult { + + msg: string; + line: number; + column: number; + linter: string; +} + +const errorsSeverity: string[] = ["bad-arglists", + "misplaced-docstrings", + "wrong-arity", + "wrong-ns-form", + "wrong-pre-post", + "wrong-tag"]; +const warningsSeverity: string[] = [":constant-test", + "def-in-def", + "deprecations", + "keyword-typos", + "local-shadows-var", + "redefd-vars", + "suspicious-expression", + "suspicious-test", + "unused-locals", + "unused-meta-on-macro", + "unused-namespaces", + "unused-private-vars"]; +const infoSeverity: string[] = ["no-ns-form-found", + "unlimited-use", + "unused-ret-vals", + "unused-ret-vals-in-try"]; + +export class ClojureLintingProvider { + + private getLintCommand(ns): string { + return `(do (require '[eastwood.lint]) + (require '[clojure.data.json]) + (->> + (eastwood.lint/lint {:namespaces ['${ns}]}) + :warnings + (map #(select-keys % [:msg :line :column :linter])) + (vec) + (clojure.data.json/write-str)))`; + } + + private diagnosticCollection: vscode.DiagnosticCollection; + + private parseLintSuccessResponse(response: string): LinterResult[] { + + const parsedToString = JSON.parse(response); + return JSON.parse(parsedToString); + } + + private getSeverity(type: string) { + if(errorsSeverity.indexOf(type) > 0) { + return vscode.DiagnosticSeverity.Error; + } else if(warningsSeverity.indexOf(type) > 0) { + return vscode.DiagnosticSeverity.Warning; + } else if(infoSeverity.indexOf(type) > 0) { + return vscode.DiagnosticSeverity.Information; + } + else { + return vscode.DiagnosticSeverity.Hint; + } + } + + private createDiagnosticFromLintResult(document: TextDocument, warning: LinterResult): Diagnostic { + const blockRange = cljParser.getBlockRange(document, warning.line, warning.column); + const severity = this.getSeverity(warning.linter); + return new Diagnostic(blockRange, warning.msg, severity); + } + + private createDiagnosticCollectionFromLintResult(document: TextDocument, result: LinterResult[]): Diagnostic[] { + return result.map((item)=>{ return this.createDiagnosticFromLintResult(document, item); }); + } + + private lint(textDocument: vscode.TextDocument) :void { + if (textDocument.languageId !== CLOJURE_MODE.language && nreplConnection.cljConnection.isConnected) { + return; + } + + nreplConnection.cljConnection + .sessionForFilename(textDocument.fileName) + .then(value=>{ + const ns = cljParser.getNamespace(textDocument.getText()); + cljParser.getExpressionInfo + if(ns.length > 0) { + const command = this.getLintCommand(ns); + nreplClient.evaluate(command) + .then(result => { + try { + let warnings: LinterResult[] = this.parseLintSuccessResponse(result[0].value); + const diagnostics = this.createDiagnosticCollectionFromLintResult(textDocument, warnings); + this.diagnosticCollection.set(textDocument.uri, diagnostics); + } catch(e) { + console.error(e); + } + }, err=> { + console.error(err); + }); + } + }); + } + + public activate(subscriptions: vscode.Disposable[]) { + subscriptions.push(this); + this.diagnosticCollection = vscode.languages.createDiagnosticCollection(); + + vscode.workspace.onDidOpenTextDocument(this.lint, this, subscriptions); + vscode.workspace.onDidCloseTextDocument((textDocument)=> { + this.diagnosticCollection.delete(textDocument.uri); + }, null, subscriptions); + + vscode.workspace.onDidSaveTextDocument(this.lint, this); + } + + public dispose(): void { + this.diagnosticCollection.clear(); + this.diagnosticCollection.dispose(); + } +} \ No newline at end of file diff --git a/src/clojureMain.ts b/src/clojureMain.ts index 470fc7b..977443b 100644 --- a/src/clojureMain.ts +++ b/src/clojureMain.ts @@ -12,8 +12,10 @@ import { nreplController } from './nreplController'; import { cljConnection } from './cljConnection'; import { formatFile, maybeActivateFormatOnSave } from './clojureFormat'; import { reloadNamespaceCommand, getReloadOnFileSave } from './clojureReloadNamespace'; +import { ClojureLintingProvider } from './clojureLintingProvider'; export function activate(context: vscode.ExtensionContext) { + cljConnection.setCljContext(context); context.subscriptions.push(nreplController); cljConnection.disconnect(false); @@ -47,6 +49,9 @@ export function activate(context: vscode.ExtensionContext) { reloadNamespaceCommand(evaluationResultChannel); }, this); } + + let linter = new ClojureLintingProvider; + linter.activate(context.subscriptions); } export function deactivate() { } From fe2e5410f780b4b3777f7c223b9339ee89216313 Mon Sep 17 00:00:00 2001 From: barak liato Date: Mon, 18 Dec 2017 13:38:59 +0200 Subject: [PATCH 09/20] add exception support in linter --- src/clojureLintingProvider.ts | 100 ++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/src/clojureLintingProvider.ts b/src/clojureLintingProvider.ts index 7a77129..84b246d 100644 --- a/src/clojureLintingProvider.ts +++ b/src/clojureLintingProvider.ts @@ -6,14 +6,34 @@ import { Diagnostic, Range, TextDocument } from 'vscode'; import { cljParser } from './cljParser'; import { CLOJURE_MODE } from './clojureMode'; -interface LinterResult { - - msg: string; +interface LinterWarningResult { + + msg: string; line: number; column: number; linter: string; } +interface LinterErrorData { + column: number; + "end-column": number; + line: number; + "end-line": number; + file: string; +} + +interface LinterError { + cause: string; + data: LinterErrorData; +} + +interface LinterResult { + + err: string; + "err-data": LinterError; + warnings: LinterWarningResult[]; +} + const errorsSeverity: string[] = ["bad-arglists", "misplaced-docstrings", "wrong-arity", @@ -42,17 +62,19 @@ export class ClojureLintingProvider { private getLintCommand(ns): string { return `(do (require '[eastwood.lint]) (require '[clojure.data.json]) - (->> - (eastwood.lint/lint {:namespaces ['${ns}]}) - :warnings - (map #(select-keys % [:msg :line :column :linter])) - (vec) + (-> ;(eastwood.lint/lint {:namespaces ['${ns}]}) + (eastwood.lint/lint {:namespaces ['madlan.server.sms-api]}) + (select-keys [:warnings :err :err-data]) + (update :warnings (fn [x] (map #(select-keys % [:msg :line :column :linter]) x))) + (update :err-data :exception) + (update :err-data Throwable->map) + (update :err-data #(select-keys % [:cause :data])) (clojure.data.json/write-str)))`; } private diagnosticCollection: vscode.DiagnosticCollection; - private parseLintSuccessResponse(response: string): LinterResult[] { + private parseLintSuccessResponse(response: string): LinterResult { const parsedToString = JSON.parse(response); return JSON.parse(parsedToString); @@ -71,14 +93,36 @@ export class ClojureLintingProvider { } } - private createDiagnosticFromLintResult(document: TextDocument, warning: LinterResult): Diagnostic { + private createDiagnosticFromLintResult(document: TextDocument, warning: LinterWarningResult): Diagnostic { const blockRange = cljParser.getBlockRange(document, warning.line, warning.column); const severity = this.getSeverity(warning.linter); return new Diagnostic(blockRange, warning.msg, severity); } - private createDiagnosticCollectionFromLintResult(document: TextDocument, result: LinterResult[]): Diagnostic[] { - return result.map((item)=>{ return this.createDiagnosticFromLintResult(document, item); }); + private createDiagnosticCollectionFromLintResult(document: TextDocument, result: LinterResult): Diagnostic[] { + let warnings = result.warnings.map((item)=>{ return this.createDiagnosticFromLintResult(document, item); }); + if(result.err) { + const errData = result['err-data']; + if(document.fileName.endsWith(errData.data.file)) { + warnings.push({ + range: new Range(errData.data.line, errData.data.column, errData.data['end-line'], errData.data['end-column']), + message: errData.cause, + source: "Linter Exception", + severity: vscode.DiagnosticSeverity.Error, + code: -1 + }); + } else { + warnings.push({ + range: new Range(1, 1, 1, 1), + message: `Exception from different namespace ${errData.data.file};${errData.data.line}:${errData.data.column} - ${errData.cause}`, + source: "Linter Exception", + severity: vscode.DiagnosticSeverity.Error, + code: -1 + }); + }; + } + + return warnings; } private lint(textDocument: vscode.TextDocument) :void { @@ -88,23 +132,23 @@ export class ClojureLintingProvider { nreplConnection.cljConnection .sessionForFilename(textDocument.fileName) - .then(value=>{ - const ns = cljParser.getNamespace(textDocument.getText()); - cljParser.getExpressionInfo + .then(value => { + const ns = cljParser.getNamespace(textDocument.getText()); if(ns.length > 0) { - const command = this.getLintCommand(ns); - nreplClient.evaluate(command) - .then(result => { - try { - let warnings: LinterResult[] = this.parseLintSuccessResponse(result[0].value); - const diagnostics = this.createDiagnosticCollectionFromLintResult(textDocument, warnings); - this.diagnosticCollection.set(textDocument.uri, diagnostics); - } catch(e) { - console.error(e); - } - }, err=> { - console.error(err); - }); + const command = this.getLintCommand(ns); + nreplClient.evaluate(command) + .then(result => { + try { + let lintResult: LinterResult = this.parseLintSuccessResponse(result[0].value); + const diagnostics = this.createDiagnosticCollectionFromLintResult(textDocument, lintResult); + this.diagnosticCollection.set(textDocument.uri, diagnostics); + } catch(e) { + console.error(e); + } + + }, err=> { + console.error(err); + }); } }); } From 86a924974afb5925489b53d6b1eb16863f7bec90 Mon Sep 17 00:00:00 2001 From: barak liato Date: Mon, 18 Dec 2017 14:01:37 +0200 Subject: [PATCH 10/20] point to the correct line on exception --- src/clojureLintingProvider.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/clojureLintingProvider.ts b/src/clojureLintingProvider.ts index 84b246d..07e341a 100644 --- a/src/clojureLintingProvider.ts +++ b/src/clojureLintingProvider.ts @@ -95,7 +95,7 @@ export class ClojureLintingProvider { private createDiagnosticFromLintResult(document: TextDocument, warning: LinterWarningResult): Diagnostic { const blockRange = cljParser.getBlockRange(document, warning.line, warning.column); - const severity = this.getSeverity(warning.linter); + const severity = this.getSeverity(warning.linter); return new Diagnostic(blockRange, warning.msg, severity); } @@ -105,22 +105,13 @@ export class ClojureLintingProvider { const errData = result['err-data']; if(document.fileName.endsWith(errData.data.file)) { warnings.push({ - range: new Range(errData.data.line, errData.data.column, errData.data['end-line'], errData.data['end-column']), + range: new Range(errData.data.line - 1, errData.data.column, errData.data['end-line'] - 1, errData.data['end-column']), message: errData.cause, source: "Linter Exception", severity: vscode.DiagnosticSeverity.Error, code: -1 }); - } else { - warnings.push({ - range: new Range(1, 1, 1, 1), - message: `Exception from different namespace ${errData.data.file};${errData.data.line}:${errData.data.column} - ${errData.cause}`, - source: "Linter Exception", - severity: vscode.DiagnosticSeverity.Error, - code: -1 - }); - }; - } + }} return warnings; } From e0452f98d14b5fd4d5ab6352beed910c80a494e9 Mon Sep 17 00:00:00 2001 From: barak liato Date: Mon, 18 Dec 2017 14:13:08 +0200 Subject: [PATCH 11/20] take one character back in linter exception --- src/clojureLintingProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clojureLintingProvider.ts b/src/clojureLintingProvider.ts index 07e341a..aa86899 100644 --- a/src/clojureLintingProvider.ts +++ b/src/clojureLintingProvider.ts @@ -105,7 +105,7 @@ export class ClojureLintingProvider { const errData = result['err-data']; if(document.fileName.endsWith(errData.data.file)) { warnings.push({ - range: new Range(errData.data.line - 1, errData.data.column, errData.data['end-line'] - 1, errData.data['end-column']), + range: new Range(errData.data.line - 1, errData.data.column - 1, errData.data['end-line'] - 1, errData.data['end-column'] - 1), message: errData.cause, source: "Linter Exception", severity: vscode.DiagnosticSeverity.Error, From 96bba74b4720c05d4cbe3aded76d9f32bca91226 Mon Sep 17 00:00:00 2001 From: barak liato Date: Mon, 18 Dec 2017 14:23:12 +0200 Subject: [PATCH 12/20] clear the problems before linting on save --- src/clojureLintingProvider.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/clojureLintingProvider.ts b/src/clojureLintingProvider.ts index aa86899..bf6fd3a 100644 --- a/src/clojureLintingProvider.ts +++ b/src/clojureLintingProvider.ts @@ -153,7 +153,10 @@ export class ClojureLintingProvider { this.diagnosticCollection.delete(textDocument.uri); }, null, subscriptions); - vscode.workspace.onDidSaveTextDocument(this.lint, this); + vscode.workspace.onDidSaveTextDocument((textDocument: vscode.TextDocument) => { + this.diagnosticCollection.delete(textDocument.uri); + this.lint(textDocument); + }, this); } public dispose(): void { From c31595641267cf6b5b97212ac1ba381ed8c4db20 Mon Sep 17 00:00:00 2001 From: barak liato Date: Mon, 18 Dec 2017 17:54:59 +0200 Subject: [PATCH 13/20] fix bug in linter --- src/clojureLintingProvider.ts | 37 +++++++++++++++++++++++++---------- src/clojureMain.ts | 2 +- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/clojureLintingProvider.ts b/src/clojureLintingProvider.ts index bf6fd3a..bedce82 100644 --- a/src/clojureLintingProvider.ts +++ b/src/clojureLintingProvider.ts @@ -5,6 +5,7 @@ import { resolve } from 'dns'; import { Diagnostic, Range, TextDocument } from 'vscode'; import { cljParser } from './cljParser'; import { CLOJURE_MODE } from './clojureMode'; +import { handleError } from './clojureEval'; interface LinterWarningResult { @@ -59,23 +60,32 @@ const infoSeverity: string[] = ["no-ns-form-found", export class ClojureLintingProvider { + private outputChannel: vscode.OutputChannel; + + constructor(channel: vscode.OutputChannel) { + this.outputChannel = channel; + } + private getLintCommand(ns): string { + console.log(ns); return `(do (require '[eastwood.lint]) (require '[clojure.data.json]) - (-> ;(eastwood.lint/lint {:namespaces ['${ns}]}) - (eastwood.lint/lint {:namespaces ['madlan.server.sms-api]}) + (-> (eastwood.lint/lint {:namespaces ['${ns}]}) (select-keys [:warnings :err :err-data]) - (update :warnings (fn [x] (map #(select-keys % [:msg :line :column :linter]) x))) + (update :warnings (fn [x] (map #(select-keys % [:msg :line :column :linter]) x))) (update :err-data :exception) - (update :err-data Throwable->map) - (update :err-data #(select-keys % [:cause :data])) + ((fn [data] + (if-let [err-data (:err-data data)] + (-> data + (update :err-data Throwable->map) + (update :err-data #(select-keys % [:cause :data]))) + data))) (clojure.data.json/write-str)))`; } private diagnosticCollection: vscode.DiagnosticCollection; - private parseLintSuccessResponse(response: string): LinterResult { - + private parseLintSuccessResponse(response: string): LinterResult { const parsedToString = JSON.parse(response); return JSON.parse(parsedToString); } @@ -130,9 +140,16 @@ export class ClojureLintingProvider { nreplClient.evaluate(command) .then(result => { try { - let lintResult: LinterResult = this.parseLintSuccessResponse(result[0].value); - const diagnostics = this.createDiagnosticCollectionFromLintResult(textDocument, lintResult); - this.diagnosticCollection.set(textDocument.uri, diagnostics); + if(!!result[0].ex) { + handleError(this.outputChannel, + new vscode.Selection(0,0,0,0), + false, + result[0].session); + } else { + let lintResult: LinterResult = this.parseLintSuccessResponse(result[0].value); + const diagnostics = this.createDiagnosticCollectionFromLintResult(textDocument, lintResult); + this.diagnosticCollection.set(textDocument.uri, diagnostics); + } } catch(e) { console.error(e); } diff --git a/src/clojureMain.ts b/src/clojureMain.ts index 977443b..6ad1411 100644 --- a/src/clojureMain.ts +++ b/src/clojureMain.ts @@ -50,7 +50,7 @@ export function activate(context: vscode.ExtensionContext) { }, this); } - let linter = new ClojureLintingProvider; + let linter = new ClojureLintingProvider(evaluationResultChannel); linter.activate(context.subscriptions); } From f21075efd4d9bbbae6eb13a018cb85396f59c135 Mon Sep 17 00:00:00 2001 From: barak liato Date: Mon, 18 Dec 2017 23:30:51 +0200 Subject: [PATCH 14/20] don't color the entire file when there is EOF exception --- src/clojureLintingProvider.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/clojureLintingProvider.ts b/src/clojureLintingProvider.ts index bedce82..01dcafd 100644 --- a/src/clojureLintingProvider.ts +++ b/src/clojureLintingProvider.ts @@ -114,8 +114,12 @@ export class ClojureLintingProvider { if(result.err) { const errData = result['err-data']; if(document.fileName.endsWith(errData.data.file)) { + const startLine = errData.data.line - 1; + const startChar = errData.data.column - 1; + const endLine = errData.data['end-line'] == null ? startLine : errData.data['end-line'] - 1; + const endChar = errData.data['end-column'] == null ? startChar : errData.data['end-column'] - 1; warnings.push({ - range: new Range(errData.data.line - 1, errData.data.column - 1, errData.data['end-line'] - 1, errData.data['end-column'] - 1), + range: new Range(errData.data.line - 1, errData.data.column - 1, endLine, endChar), message: errData.cause, source: "Linter Exception", severity: vscode.DiagnosticSeverity.Error, From 901e09f1768208cf893129ca2679db40aee84310 Mon Sep 17 00:00:00 2001 From: barak liato Date: Wed, 20 Dec 2017 10:59:09 +0200 Subject: [PATCH 15/20] handle more errors --- src/cljParser.ts | 4 ++-- src/clojureLintingProvider.ts | 42 ++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/cljParser.ts b/src/cljParser.ts index 48229c1..2110507 100644 --- a/src/cljParser.ts +++ b/src/cljParser.ts @@ -138,7 +138,7 @@ const getNamespace = (text: string): string => { }; //assume the position is before the block directly -const getBlockRange = (editor: TextDocument, line: number, column: number): Range => { +const getDirectlyBeforeBlockRange = (editor: TextDocument, line: number, column: number): Range => { const lastLineNumber = editor.lineCount - 1; const lastLine = editor.lineAt(lastLineNumber); const range = new Range(line, column, lastLineNumber, lastLine.range.end.character); @@ -172,5 +172,5 @@ export const cljParser = { R_CLJ_WHITE_SPACE, getExpressionInfo, getNamespace, - getBlockRange + getDirectlyBeforeBlockRange }; diff --git a/src/clojureLintingProvider.ts b/src/clojureLintingProvider.ts index 01dcafd..ba03410 100644 --- a/src/clojureLintingProvider.ts +++ b/src/clojureLintingProvider.ts @@ -25,6 +25,7 @@ interface LinterErrorData { interface LinterError { cause: string; + trace: number; data: LinterErrorData; } @@ -66,8 +67,7 @@ export class ClojureLintingProvider { this.outputChannel = channel; } - private getLintCommand(ns): string { - console.log(ns); + private getLintCommand(ns: string): string { return `(do (require '[eastwood.lint]) (require '[clojure.data.json]) (-> (eastwood.lint/lint {:namespaces ['${ns}]}) @@ -78,7 +78,14 @@ export class ClojureLintingProvider { (if-let [err-data (:err-data data)] (-> data (update :err-data Throwable->map) - (update :err-data #(select-keys % [:cause :data]))) + (update :err-data #(select-keys % [:cause :data :trace])) + (update-in [:err-data :trace] + (fn [trace] + (->> trace + (filter #(clojure.string/starts-with? (.getClassName %) (str "${ns}" "$"))) + (first) + ((fn [x] (if x (.getLineNumber x))))))) + (or data)) data))) (clojure.data.json/write-str)))`; } @@ -104,34 +111,42 @@ export class ClojureLintingProvider { } private createDiagnosticFromLintResult(document: TextDocument, warning: LinterWarningResult): Diagnostic { - const blockRange = cljParser.getBlockRange(document, warning.line, warning.column); + const blockRange = cljParser.getDirectlyBeforeBlockRange(document, warning.line, warning.column); const severity = this.getSeverity(warning.linter); return new Diagnostic(blockRange, warning.msg, severity); } private createDiagnosticCollectionFromLintResult(document: TextDocument, result: LinterResult): Diagnostic[] { - let warnings = result.warnings.map((item)=>{ return this.createDiagnosticFromLintResult(document, item); }); - if(result.err) { + let warnings = result.warnings.map((item)=>{ return this.createDiagnosticFromLintResult(document, item); }); + if(result.err) { const errData = result['err-data']; - if(document.fileName.endsWith(errData.data.file)) { + if(errData.data != null && document.fileName.endsWith(errData.data.file)) { const startLine = errData.data.line - 1; const startChar = errData.data.column - 1; const endLine = errData.data['end-line'] == null ? startLine : errData.data['end-line'] - 1; - const endChar = errData.data['end-column'] == null ? startChar : errData.data['end-column'] - 1; - warnings.push({ + const endChar = errData.data['end-column'] == null ? startChar : errData.data['end-column'] - 1; + warnings.push({ range: new Range(errData.data.line - 1, errData.data.column - 1, endLine, endChar), message: errData.cause, source: "Linter Exception", severity: vscode.DiagnosticSeverity.Error, code: -1 }); + } else { + warnings.push({ + range: new Range(errData.trace - 1, 0, errData.trace - 1, 0), + message: errData.cause, + source: "Linter Exception", + severity: vscode.DiagnosticSeverity.Error, + code: -1 + }); }} return warnings; } private lint(textDocument: vscode.TextDocument) :void { - if (textDocument.languageId !== CLOJURE_MODE.language && nreplConnection.cljConnection.isConnected) { + if (textDocument.languageId !== CLOJURE_MODE.language || !nreplConnection.cljConnection.isConnected) { return; } @@ -142,14 +157,15 @@ export class ClojureLintingProvider { if(ns.length > 0) { const command = this.getLintCommand(ns); nreplClient.evaluate(command) - .then(result => { + .then(result => { try { - if(!!result[0].ex) { + if(!!result[0].ex) { + console.log(result); handleError(this.outputChannel, new vscode.Selection(0,0,0,0), false, result[0].session); - } else { + } else { let lintResult: LinterResult = this.parseLintSuccessResponse(result[0].value); const diagnostics = this.createDiagnosticCollectionFromLintResult(textDocument, lintResult); this.diagnosticCollection.set(textDocument.uri, diagnostics); From c6c1a233c8d6efb6e2b8a15e059bb88a655e0405 Mon Sep 17 00:00:00 2001 From: barak liato Date: Wed, 20 Dec 2017 14:11:59 +0200 Subject: [PATCH 16/20] add command and props --- package.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/package.json b/package.json index ffb951e..df60983 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,10 @@ { "name": "Alessandro Decina", "email": "alessandro.d@gmail.com" + }, + { + "name": "Barak Liato", + "email": "barakliato@gmail.com" } ], "license": "MIT", @@ -80,6 +84,10 @@ { "command": "clojureVSCode.formatFile", "title": "Clojure: Format file or selection" + }, + { + "command": "reloadNamespace", + "title": "reload the current namespace" } ], "configuration": { @@ -95,6 +103,16 @@ "type": "boolean", "default": false, "description": "Format the code on save." + }, + "clojureVSCode.alertOnEval": { + "type": "boolean", + "default": true, + "description": "show pop up alert on eval." + }, + "clojureVSCode.autoReloadNamespaceOnSave": { + "type": "boolean", + "default": true, + "description": "auto reload the file upon save." } } } From ac08f91e58ee961f34d792fc3615864eed21cd7a Mon Sep 17 00:00:00 2001 From: barak liato Date: Wed, 20 Dec 2017 14:14:35 +0200 Subject: [PATCH 17/20] change the command reloadNamespace to clojureVSCode.reloadNamespace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index df60983..e818c5a 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "title": "Clojure: Format file or selection" }, { - "command": "reloadNamespace", + "command": "clojureVSCode.reloadNamespace", "title": "reload the current namespace" } ], From 4bf27fe3de82ef867f2716acd679fd13207febd3 Mon Sep 17 00:00:00 2001 From: barak liato Date: Sun, 24 Dec 2017 17:06:25 +0200 Subject: [PATCH 18/20] disable linting if eastwood is not available - check if it becomes available over time for some reason --- src/clojureLintingProvider.ts | 202 +++++++++++++++++++--------------- 1 file changed, 116 insertions(+), 86 deletions(-) diff --git a/src/clojureLintingProvider.ts b/src/clojureLintingProvider.ts index ba03410..c7da669 100644 --- a/src/clojureLintingProvider.ts +++ b/src/clojureLintingProvider.ts @@ -8,11 +8,11 @@ import { CLOJURE_MODE } from './clojureMode'; import { handleError } from './clojureEval'; interface LinterWarningResult { - + msg: string; - line: number; - column: number; - linter: string; + line: number; + column: number; + linter: string; } interface LinterErrorData { @@ -30,44 +30,45 @@ interface LinterError { } interface LinterResult { - + err: string; "err-data": LinterError; - warnings: LinterWarningResult[]; + warnings: LinterWarningResult[]; } -const errorsSeverity: string[] = ["bad-arglists", - "misplaced-docstrings", - "wrong-arity", - "wrong-ns-form", - "wrong-pre-post", - "wrong-tag"]; -const warningsSeverity: string[] = [":constant-test", - "def-in-def", - "deprecations", - "keyword-typos", - "local-shadows-var", - "redefd-vars", - "suspicious-expression", - "suspicious-test", - "unused-locals", - "unused-meta-on-macro", - "unused-namespaces", - "unused-private-vars"]; -const infoSeverity: string[] = ["no-ns-form-found", - "unlimited-use", - "unused-ret-vals", - "unused-ret-vals-in-try"]; +const errorsSeverity: string[] = ["bad-arglists", + "misplaced-docstrings", + "wrong-arity", + "wrong-ns-form", + "wrong-pre-post", + "wrong-tag"]; +const warningsSeverity: string[] = [":constant-test", + "def-in-def", + "deprecations", + "keyword-typos", + "local-shadows-var", + "redefd-vars", + "suspicious-expression", + "suspicious-test", + "unused-locals", + "unused-meta-on-macro", + "unused-namespaces", + "unused-private-vars"]; +const infoSeverity: string[] = ["no-ns-form-found", + "unlimited-use", + "unused-ret-vals", + "unused-ret-vals-in-try"]; export class ClojureLintingProvider { - + private outputChannel: vscode.OutputChannel; + private eastwoodInstalled: boolean = false; constructor(channel: vscode.OutputChannel) { this.outputChannel = channel; } - private getLintCommand(ns: string): string { + private getLintCommand(ns: string): string { return `(do (require '[eastwood.lint]) (require '[clojure.data.json]) (-> (eastwood.lint/lint {:namespaces ['${ns}]}) @@ -88,21 +89,21 @@ export class ClojureLintingProvider { (or data)) data))) (clojure.data.json/write-str)))`; - } + } - private diagnosticCollection: vscode.DiagnosticCollection; + private diagnosticCollection: vscode.DiagnosticCollection; - private parseLintSuccessResponse(response: string): LinterResult { - const parsedToString = JSON.parse(response); - return JSON.parse(parsedToString); + private parseLintSuccessResponse(response: string): LinterResult { + const parsedToString = JSON.parse(response); + return JSON.parse(parsedToString); } private getSeverity(type: string) { - if(errorsSeverity.indexOf(type) > 0) { + if (errorsSeverity.indexOf(type) > 0) { return vscode.DiagnosticSeverity.Error; - } else if(warningsSeverity.indexOf(type) > 0) { + } else if (warningsSeverity.indexOf(type) > 0) { return vscode.DiagnosticSeverity.Warning; - } else if(infoSeverity.indexOf(type) > 0) { + } else if (infoSeverity.indexOf(type) > 0) { return vscode.DiagnosticSeverity.Information; } else { @@ -110,94 +111,123 @@ export class ClojureLintingProvider { } } - private createDiagnosticFromLintResult(document: TextDocument, warning: LinterWarningResult): Diagnostic { - const blockRange = cljParser.getDirectlyBeforeBlockRange(document, warning.line, warning.column); - const severity = this.getSeverity(warning.linter); - return new Diagnostic(blockRange, warning.msg, severity); - } + private createDiagnosticFromLintResult(document: TextDocument, warning: LinterWarningResult): Diagnostic { + const blockRange = cljParser.getDirectlyBeforeBlockRange(document, warning.line, warning.column); + const severity = this.getSeverity(warning.linter); + return new Diagnostic(blockRange, warning.msg, severity); + } private createDiagnosticCollectionFromLintResult(document: TextDocument, result: LinterResult): Diagnostic[] { - let warnings = result.warnings.map((item)=>{ return this.createDiagnosticFromLintResult(document, item); }); - if(result.err) { + let warnings = result.warnings.map((item) => { return this.createDiagnosticFromLintResult(document, item); }); + if (result.err) { const errData = result['err-data']; - if(errData.data != null && document.fileName.endsWith(errData.data.file)) { + if (errData.data != null && document.fileName.endsWith(errData.data.file)) { const startLine = errData.data.line - 1; const startChar = errData.data.column - 1; const endLine = errData.data['end-line'] == null ? startLine : errData.data['end-line'] - 1; - const endChar = errData.data['end-column'] == null ? startChar : errData.data['end-column'] - 1; - warnings.push({ + const endChar = errData.data['end-column'] == null ? startChar : errData.data['end-column'] - 1; + warnings.push({ range: new Range(errData.data.line - 1, errData.data.column - 1, endLine, endChar), message: errData.cause, source: "Linter Exception", severity: vscode.DiagnosticSeverity.Error, code: -1 }); - } else { - warnings.push({ + } else { + warnings.push({ range: new Range(errData.trace - 1, 0, errData.trace - 1, 0), message: errData.cause, source: "Linter Exception", severity: vscode.DiagnosticSeverity.Error, code: -1 }); - }} + } + } return warnings; } - private lint(textDocument: vscode.TextDocument) :void { - if (textDocument.languageId !== CLOJURE_MODE.language || !nreplConnection.cljConnection.isConnected) { + private isEastwoodInstalled(textDocument: vscode.TextDocument) { + return nreplConnection.cljConnection + .sessionForFilename(textDocument.fileName) + .then(value => { + return nreplClient.evaluate("(require '[eastwood.lint])") + .then(result => { + try { + return result[0].ex == null; + } catch (e) { + return false; + } + }, () => false); + }, () => false); + } + + private lint(textDocument: vscode.TextDocument): void { + + if (!this.eastwoodInstalled) { + this.isEastwoodInstalled(textDocument) + .then(value => { + this.eastwoodInstalled = value || false; + if (this.eastwoodInstalled) + this.lint(textDocument); + }); + + return; + } + + if (textDocument.languageId !== CLOJURE_MODE.language + || !nreplConnection.cljConnection.isConnected + || !this.eastwoodInstalled) { return; } nreplConnection.cljConnection - .sessionForFilename(textDocument.fileName) - .then(value => { - const ns = cljParser.getNamespace(textDocument.getText()); - if(ns.length > 0) { - const command = this.getLintCommand(ns); - nreplClient.evaluate(command) - .then(result => { - try { - if(!!result[0].ex) { - console.log(result); - handleError(this.outputChannel, - new vscode.Selection(0,0,0,0), - false, - result[0].session); - } else { - let lintResult: LinterResult = this.parseLintSuccessResponse(result[0].value); - const diagnostics = this.createDiagnosticCollectionFromLintResult(textDocument, lintResult); - this.diagnosticCollection.set(textDocument.uri, diagnostics); - } - } catch(e) { - console.error(e); - } - - }, err=> { - console.error(err); - }); + .sessionForFilename(textDocument.fileName) + .then(value => { + const ns = cljParser.getNamespace(textDocument.getText()); + if (ns.length > 0) { + const command = this.getLintCommand(ns); + nreplClient.evaluate(command) + .then(result => { + try { + if (!!result[0].ex) { + handleError(this.outputChannel, + new vscode.Selection(0, 0, 0, 0), + false, + result[0].session); + } else { + let lintResult: LinterResult = this.parseLintSuccessResponse(result[0].value); + const diagnostics = this.createDiagnosticCollectionFromLintResult(textDocument, lintResult); + this.diagnosticCollection.set(textDocument.uri, diagnostics); + } + } catch (e) { + console.error(e); + } + + }, err => { + console.error(err); + }); } - }); + }); } - public activate(subscriptions: vscode.Disposable[]) { + public activate(subscriptions: vscode.Disposable[]) { subscriptions.push(this); this.diagnosticCollection = vscode.languages.createDiagnosticCollection(); vscode.workspace.onDidOpenTextDocument(this.lint, this, subscriptions); - vscode.workspace.onDidCloseTextDocument((textDocument)=> { + vscode.workspace.onDidCloseTextDocument((textDocument) => { this.diagnosticCollection.delete(textDocument.uri); }, null, subscriptions); vscode.workspace.onDidSaveTextDocument((textDocument: vscode.TextDocument) => { - this.diagnosticCollection.delete(textDocument.uri); - this.lint(textDocument); + this.diagnosticCollection.delete(textDocument.uri); + this.lint(textDocument); }, this); } - + public dispose(): void { this.diagnosticCollection.clear(); - this.diagnosticCollection.dispose(); + this.diagnosticCollection.dispose(); } } \ No newline at end of file From 979495907c0717a290850842204c4f4ea2a53597 Mon Sep 17 00:00:00 2001 From: barak liato Date: Mon, 25 Dec 2017 23:34:11 +0200 Subject: [PATCH 19/20] during work on symbol search + fix a bug in linter --- package.json | 3 ++- src/clojureLintingProvider.ts | 3 ++- src/clojureMain.ts | 5 +++++ src/clojureReloadNamespace.ts | 28 ++++++++++++++-------------- src/searchSymbolCommand.ts | 10 ++++++++++ 5 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 src/searchSymbolCommand.ts diff --git a/package.json b/package.json index e818c5a..e2d2351 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,8 @@ "Other" ], "activationEvents": [ - "onLanguage:clojure" + "onCommand:clojureVSCode.startNRepl", + "onCommand:clojureVSCode.manuallyConnectToNRepl" ], "main": "./out/src/clojureMain", "contributes": { diff --git a/src/clojureLintingProvider.ts b/src/clojureLintingProvider.ts index c7da669..95583cc 100644 --- a/src/clojureLintingProvider.ts +++ b/src/clojureLintingProvider.ts @@ -134,8 +134,9 @@ export class ClojureLintingProvider { code: -1 }); } else { + const line = errData.trace != null ? 0 : 0; warnings.push({ - range: new Range(errData.trace - 1, 0, errData.trace - 1, 0), + range: new Range(line, 0, line, 0), message: errData.cause, source: "Linter Exception", severity: vscode.DiagnosticSeverity.Error, diff --git a/src/clojureMain.ts b/src/clojureMain.ts index 6ad1411..f0663e3 100644 --- a/src/clojureMain.ts +++ b/src/clojureMain.ts @@ -13,6 +13,7 @@ import { cljConnection } from './cljConnection'; import { formatFile, maybeActivateFormatOnSave } from './clojureFormat'; import { reloadNamespaceCommand, getReloadOnFileSave } from './clojureReloadNamespace'; import { ClojureLintingProvider } from './clojureLintingProvider'; +import { searchSymbol } from './searchSymbolCommand'; export function activate(context: vscode.ExtensionContext) { @@ -35,6 +36,10 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('clojureVSCode.evalAndShowResult', () => clojureEvalAndShowResult(evaluationResultChannel)); vscode.commands.registerTextEditorCommand('clojureVSCode.formatFile', formatFile); vscode.commands.registerTextEditorCommand('clojureVSCode.reloadNamespace', ()=> { reloadNamespaceCommand(evaluationResultChannel); }); + + + vscode.commands.registerCommand('clojureVSCode.clojureVSCode.searchSymbol', searchSymbol); + context.subscriptions.push(vscode.languages.registerCompletionItemProvider(CLOJURE_MODE, new ClojureCompletionItemProvider(), '.', '/')); context.subscriptions.push(vscode.languages.registerDefinitionProvider(CLOJURE_MODE, new ClojureDefinitionProvider())); context.subscriptions.push(vscode.languages.registerHoverProvider(CLOJURE_MODE, new ClojureHoverProvider())); diff --git a/src/clojureReloadNamespace.ts b/src/clojureReloadNamespace.ts index 1c26316..62198d9 100644 --- a/src/clojureReloadNamespace.ts +++ b/src/clojureReloadNamespace.ts @@ -2,37 +2,37 @@ import * as vscode from 'vscode'; import { cljConnection } from './cljConnection'; import { cljParser } from './cljParser'; import { handleError, evaluateText } from './clojureEval'; -import {readBooleanConfiguration} from './utils'; +import { readBooleanConfiguration } from './utils'; -export function getReloadOnFileSave() :boolean { +export function getReloadOnFileSave(): boolean { return readBooleanConfiguration('autoReloadNamespaceOnSave') } -export function reloadNamespaceCommand( +export function reloadNamespaceCommand( outputChannel: vscode.OutputChannel) { - if (!cljConnection.isConnected()) { + if (!cljConnection.isConnected()) { return; } const textDocument = vscode.window.activeTextEditor.document; const fileName = textDocument.fileName; - if(!fileName.endsWith(".clj")) { + if (!fileName.endsWith(".clj")) { return; } const text = textDocument.getText(); const ns = cljParser.getNamespace(text); - const commantText = `(require '${ns} :reload)`; + const commantText = `(require '${ns} :reload)`; - evaluateText(outputChannel, false, fileName, commantText) + return evaluateText(outputChannel, false, fileName, commantText) .then(respObjs => { return (!!respObjs[0].ex) - ? handleError(outputChannel, - new vscode.Selection(0,0,0,0), - false, - respObjs[0].session) - .then(value=>Promise.reject(value)) - : Promise.resolve(); - }) + ? handleError(outputChannel, + new vscode.Selection(0, 0, 0, 0), + false, + respObjs[0].session) + .then(value => Promise.reject(value)) + : Promise.resolve(); + }); } \ No newline at end of file diff --git a/src/searchSymbolCommand.ts b/src/searchSymbolCommand.ts new file mode 100644 index 0000000..32f5f70 --- /dev/null +++ b/src/searchSymbolCommand.ts @@ -0,0 +1,10 @@ +import * as vscode from 'vscode'; +import { cljConnection } from './cljConnection'; +import { nreplClient } from './nreplClient'; +import { window } from 'vscode'; + + +export function searchSymbol() { + + +} \ No newline at end of file From 522afaafb88e0e423014b97cf5a75cc454efdd61 Mon Sep 17 00:00:00 2001 From: barak liato Date: Sat, 30 Dec 2017 12:48:00 +0200 Subject: [PATCH 20/20] add search by symbol --- package.json | 4 ++ src/clojureMain.ts | 16 ++++-- src/clojureReferenceProvider.ts | 93 +++++++++++++++++++++++++++++++++ src/searchSymbolCommand.ts | 10 ---- 4 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 src/clojureReferenceProvider.ts delete mode 100644 src/searchSymbolCommand.ts diff --git a/package.json b/package.json index e2d2351..822333e 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,10 @@ { "command": "clojureVSCode.reloadNamespace", "title": "reload the current namespace" + }, + { + "command": "clojureVSCode.searchSymbol", + "title": "Try to find the symbol and add its namespace to the decleration" } ], "configuration": { diff --git a/src/clojureMain.ts b/src/clojureMain.ts index f0663e3..4eb2c0d 100644 --- a/src/clojureMain.ts +++ b/src/clojureMain.ts @@ -13,7 +13,7 @@ import { cljConnection } from './cljConnection'; import { formatFile, maybeActivateFormatOnSave } from './clojureFormat'; import { reloadNamespaceCommand, getReloadOnFileSave } from './clojureReloadNamespace'; import { ClojureLintingProvider } from './clojureLintingProvider'; -import { searchSymbol } from './searchSymbolCommand'; +import { ClojureReferenceProvider } from './clojureReferenceProvider'; export function activate(context: vscode.ExtensionContext) { @@ -38,7 +38,7 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerTextEditorCommand('clojureVSCode.reloadNamespace', ()=> { reloadNamespaceCommand(evaluationResultChannel); }); - vscode.commands.registerCommand('clojureVSCode.clojureVSCode.searchSymbol', searchSymbol); + //vscode.commands.registerTextEditorCommand('clojureVSCode.searchSymbol', searchSymbol); context.subscriptions.push(vscode.languages.registerCompletionItemProvider(CLOJURE_MODE, new ClojureCompletionItemProvider(), '.', '/')); context.subscriptions.push(vscode.languages.registerDefinitionProvider(CLOJURE_MODE, new ClojureDefinitionProvider())); @@ -56,7 +56,17 @@ export function activate(context: vscode.ExtensionContext) { } let linter = new ClojureLintingProvider(evaluationResultChannel); - linter.activate(context.subscriptions); + linter.activate(context.subscriptions); + + context.subscriptions.push( + vscode.languages.registerReferenceProvider( + CLOJURE_MODE, new ClojureReferenceProvider() + ) + ); + + // context.subscriptions.push( + // vscode.languages.registerReferenceProvider() + // CLOJURE_MODE, new ClojureCompletionItemProvider(), '')); } export function deactivate() { } diff --git a/src/clojureReferenceProvider.ts b/src/clojureReferenceProvider.ts new file mode 100644 index 0000000..e28e8e0 --- /dev/null +++ b/src/clojureReferenceProvider.ts @@ -0,0 +1,93 @@ +import * as vscode from 'vscode'; +import { cljConnection } from './cljConnection'; +import { nreplClient } from './nreplClient'; +import { Uri, Location } from 'vscode'; +import {JarContentProvider} from './jarContentProvider'; +import { evaluateText } from './clojureEval'; +import { isNullOrUndefined } from 'util'; +import { cljParser } from './cljParser'; + + +class InfoResult { + public file: string; + public line: number; + public column: number; + + public fileToUri(): Uri { + return vscode.Uri.parse(this.file); + } +} + +export class ClojureReferenceProvider implements vscode.ReferenceProvider { + + private jarProvider: JarContentProvider = new JarContentProvider(); + + public provideReferences(document: vscode.TextDocument, + position: vscode.Position, + context: vscode.ReferenceContext, + token: vscode.CancellationToken) + : vscode.ProviderResult + { + if(!cljConnection.isConnected) + return []; + + const referenceRange = document.getWordRangeAtPosition(position); + const fileName = document.fileName; + + let symbolText = document.getText(referenceRange); + if(isNullOrUndefined(symbolText) || symbolText.length === 0) + return null; + + //TODO: instead of ignoring the namespace need to find the reference + const symbolParts = symbolText.split("/"); + let searchTerm; + if(symbolParts.length === 1) + searchTerm = `(clojure.repl/apropos #"^${symbolText}.{0,3}$")`; + else + searchTerm = `(clojure.repl/apropos #"^${symbolParts[1]}$")`; + + const command = `(clojure.repl/apropos #"^${searchTerm}.{0,3}$")`; + return cljConnection.sessionForFilename(fileName).then(session => { + return nreplClient.evaluateFile(command, fileName, session.id); + }).then((result)=>{ + try { + return result[0].value + .substring(1, result[0].value.length - 1) + .split(' '); + } catch(e) { + console.error(e); + return []; + } + }, console.error).then((symboles: Array) =>{ + const symbolesPromises = + symboles.map((item)=> { + if(isNullOrUndefined(item) || item.length === 0) + return Promise.resolve(null); + + const symbolesParts = item.split('/'); + return nreplClient.info(symbolesParts[1], symbolesParts[0]); + }); + + + return Promise.all(symbolesPromises) + .then((value: InfoResult[])=> { + + return value.filter(item=>item != null) + .map(item=>{ + try { + const fileUri: vscode.Uri = vscode.Uri.parse(item.file); + const range = new vscode.Range(item.line, item.column, item.line, item.column); + return new vscode.Location(fileUri, range); + } catch(e) { + console.error(e); + return null; + } + }); + + }, (e)=> { + console.error(e); + return []; + }); + }); + } +} \ No newline at end of file diff --git a/src/searchSymbolCommand.ts b/src/searchSymbolCommand.ts deleted file mode 100644 index 32f5f70..0000000 --- a/src/searchSymbolCommand.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as vscode from 'vscode'; -import { cljConnection } from './cljConnection'; -import { nreplClient } from './nreplClient'; -import { window } from 'vscode'; - - -export function searchSymbol() { - - -} \ No newline at end of file