Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Add live error feedback as you type #903

Merged
merged 10 commits into from
Apr 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ This extension adds rich language support for the Go language to VS Code, includ
- Generate unit tests skeleton (using `gotests`)
- Add Imports (using `gopkgs`)
- Add/Remove Tags on struct fields (using `gomodifytags`)
- Semantic/Syntactic error reporting as you type (using `gotype-live`)
- [_partially implemented_] Debugging (using `delve`)

### IDE Features
Expand Down
20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,26 @@
},
"description": "Tags and options configured here will be used by the Add Tags command to add tags to struct fields. If promptForTags is true, then user will be prompted for tags and options. By default, json tags are added."
},
"go.liveErrors": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": false,
"description": "If true, runs gotype on the file currently being edited and reports any semantic or syntactic errors found."
},
"delay": {
"type": "number",
"default": 500,
"description": "The number of milliseconds to delay before execution. Resets with each keystroke."
}
},
"default": {
"enabled": false,
"delay": 500
},
"description": "Use gotype on the file currently being edited and report any semantic or syntactic errors found after configured delay."
},
"go.removeTags": {
"type": "object",
"properties": {
Expand Down
4 changes: 4 additions & 0 deletions src/goInstallTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { showGoStatus, hideGoStatus } from './goStatus';
import { getGoRuntimePath, resolvePath } from './goPath';
import { outputChannel } from './goStatus';
import { getBinPath, getToolsGopath, getGoVersion, SemVersion, isVendorSupported } from './util';
import { goLiveErrorsEnabled } from './goLiveErrors';

let updatesDeclinedTools: string[] = [];

Expand All @@ -28,6 +29,9 @@ function getTools(goVersion: SemVersion): { [key: string]: string } {
'gorename': 'golang.org/x/tools/cmd/gorename',
'gomodifytags': 'github.com/fatih/gomodifytags'
};
if (goLiveErrorsEnabled()) {
tools['gotype-live'] = 'github.com/tylerb/gotype-live';
}

// Install the doc/def tool that was chosen by the user
if (goConfig['docsTool'] === 'godoc') {
Expand Down
88 changes: 88 additions & 0 deletions src/goLiveErrors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use strict';

import vscode = require('vscode');
import { byteOffsetAt, getBinPath } from './util';
import cp = require('child_process');
import path = require('path');
import { promptForMissingTool } from './goInstallTools';
import { errorDiagnosticCollection } from './goMain';

// Interface for settings configuration for adding and removing tags
interface GoLiveErrorsConfig {
delay: number;
enabled: boolean;
}

let runner;

export function goLiveErrorsEnabled() {
let goConfig = <GoLiveErrorsConfig>vscode.workspace.getConfiguration('go')['liveErrors'];
if (goConfig === null || goConfig === undefined || !goConfig.enabled) {
return false;
}
let autoSave = vscode.workspace.getConfiguration('files')['autoSave'];
if (autoSave !== null && autoSave !== undefined && autoSave !== 'off') {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it is closed but to be honest, I don't see a reason why autosave is checked at all. I use autosave onFocusChange and why would it be clashing with a linter running after some delay? Even autosave afterDelay makes little sense to check as it may just be some much higher value than linter delay.

Copy link
Contributor

@ramya-rao-a ramya-rao-a Apr 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was erring on the side of caution.

Good point on the fact that auto save onFocusChange and onWindowChange has no affect whatsoever.

I was concerned with the autosave on delay. Then you have build and gotype both running and giving essentially similar results and overriding each other.

On second thoughts, it is definitely being over cautious. We can remove the check in the next update. And leave it up to the user to choose between the 2 features if the experience is not pleasant

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

return false;
}
return goConfig.enabled;
}

// parseLiveFile runs the gotype command in live mode to check for any syntactic or
// semantic errors and reports them immediately
export function parseLiveFile(e: vscode.TextDocumentChangeEvent) {
if (e.document.isUntitled) {
return;
}
if (e.document.languageId !== 'go') {
return;
}
if (!goLiveErrorsEnabled()) {
return;
}

if (runner != null) {
clearTimeout(runner);
}
runner = setTimeout(function(){
processFile(e);
runner = null;
}, vscode.workspace.getConfiguration('go')['liveErrors']['delay']);
}

// processFile does the actual work once the timeout has fired
function processFile(e: vscode.TextDocumentChangeEvent) {
let uri = e.document.uri;
let gotypeLive = getBinPath('gotype-live');
let fileContents = e.document.getText();
let fileName = e.document.fileName;
let args = ['-e', '-a', '-lf=' + fileName, path.dirname(fileName)];
let p = cp.execFile(gotypeLive, args, (err, stdout, stderr) => {
if (err && (<any>err).code === 'ENOENT') {
promptForMissingTool('gotype-live');
return;
}

errorDiagnosticCollection.delete(uri);

if (err) {
// we want to take the error path here because the command we are calling
// returns a non-zero exit status if the checks fail
let diagnostics = [];

stderr.split('\n').forEach(error => {
if (error === null || error.length === 0) {
return;
}
// extract the line, column and error message from the gotype output
let [_, line, column, message] = /^.+:(\d+):(\d+):\s+(.+)/.exec(error);

let range = new vscode.Range(+line - 1, +column, +line - 1, +column);
let diagnostic = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Error);
diagnostics.push(diagnostic);
});

errorDiagnosticCollection.set(uri, diagnostics);
}
});
p.stdin.end(fileContents);
}
30 changes: 20 additions & 10 deletions src/goMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ import { isGoPathSet, getBinPath, sendTelemetryEvent } from './util';
import { LanguageClient } from 'vscode-languageclient';
import { clearCacheForTools } from './goPath';
import { addTags, removeTags } from './goModifytags';
import { parseLiveFile } from './goLiveErrors';

let diagnosticCollection: vscode.DiagnosticCollection;
export let errorDiagnosticCollection: vscode.DiagnosticCollection;
let warningDiagnosticCollection: vscode.DiagnosticCollection;

export function activate(ctx: vscode.ExtensionContext): void {
let useLangServer = vscode.workspace.getConfiguration('go')['useLanguageServer'];
Expand Down Expand Up @@ -81,12 +83,15 @@ export function activate(ctx: vscode.ExtensionContext): void {
ctx.subscriptions.push(vscode.languages.registerRenameProvider(GO_MODE, new GoRenameProvider()));
ctx.subscriptions.push(vscode.languages.registerCodeActionsProvider(GO_MODE, new GoCodeActionProvider()));

diagnosticCollection = vscode.languages.createDiagnosticCollection('go');
ctx.subscriptions.push(diagnosticCollection);
errorDiagnosticCollection = vscode.languages.createDiagnosticCollection('go-error');
ctx.subscriptions.push(errorDiagnosticCollection);
warningDiagnosticCollection = vscode.languages.createDiagnosticCollection('go-warning');
ctx.subscriptions.push(warningDiagnosticCollection);
vscode.workspace.onDidChangeTextDocument(removeCodeCoverage, null, ctx.subscriptions);
vscode.workspace.onDidChangeTextDocument(removeTestStatus, null, ctx.subscriptions);
vscode.window.onDidChangeActiveTextEditor(showHideStatus, null, ctx.subscriptions);
vscode.window.onDidChangeActiveTextEditor(getCodeCoverage, null, ctx.subscriptions);
vscode.workspace.onDidChangeTextDocument(parseLiveFile, null, ctx.subscriptions);

startBuildOnSaveWatcher(ctx.subscriptions);

Expand Down Expand Up @@ -231,9 +236,9 @@ function runBuilds(document: vscode.TextDocument, goConfig: vscode.WorkspaceConf

let uri = document.uri;
check(uri.fsPath, goConfig).then(errors => {
diagnosticCollection.clear();
errorDiagnosticCollection.clear();

let diagnosticMap: Map<string, vscode.Diagnostic[]> = new Map();
let diagnosticMap: Map<string, Map<vscode.DiagnosticSeverity, vscode.Diagnostic[]>> = new Map();

errors.forEach(error => {
let canonicalFile = vscode.Uri.file(error.file).toString();
Expand All @@ -247,16 +252,21 @@ function runBuilds(document: vscode.TextDocument, goConfig: vscode.WorkspaceConf
endColumn = text.length - trailing.length;
}
let range = new vscode.Range(error.line - 1, startColumn, error.line - 1, endColumn);
let diagnostic = new vscode.Diagnostic(range, error.msg, mapSeverityToVSCodeSeverity(error.severity));
let severity = mapSeverityToVSCodeSeverity(error.severity);
let diagnostic = new vscode.Diagnostic(range, error.msg, severity);
let diagnostics = diagnosticMap.get(canonicalFile);
if (!diagnostics) {
diagnostics = [];
diagnostics = new Map<vscode.DiagnosticSeverity, vscode.Diagnostic[]>();
}
diagnostics.push(diagnostic);
if (!diagnostics[severity]) {
diagnostics[severity] = [];
}
diagnostics[severity].push(diagnostic);
diagnosticMap.set(canonicalFile, diagnostics);
});
diagnosticMap.forEach((diags, file) => {
diagnosticCollection.set(vscode.Uri.parse(file), diags);
diagnosticMap.forEach((diagMap, file) => {
errorDiagnosticCollection.set(vscode.Uri.parse(file), diagMap[vscode.DiagnosticSeverity.Error]);
warningDiagnosticCollection.set(vscode.Uri.parse(file), diagMap[vscode.DiagnosticSeverity.Warning]);
});
}).catch(err => {
vscode.window.showInformationMessage('Error: ' + err);
Expand Down