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 6 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
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": "Tags and options configured here will be used to configure how the live errors system behaves."
Copy link
Contributor

Choose a reason for hiding this comment

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

copy paste error ? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm.. I don't see an error. What am I missing? :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I meant that the description needs an update :)

},
"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
100 changes: 100 additions & 0 deletions src/goLiveErrors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
'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 { diagnosticCollection } from './goMain';

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

let runner;

// 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;
}

let config = <GoLiveErrorsConfig>vscode.workspace.getConfiguration('go')['liveErrors'];
Copy link
Contributor

Choose a reason for hiding this comment

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

why not re-use goLiveErrorsEnabled() ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I use config.delay a few lines below, otherwise I would have reused that function, yeah.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm re-using it now, since we are also checking for autoSave.

if (config == null || !config.enabled) {
return;
}

if (runner != null) {
clearTimeout(runner);
}
runner = setTimeout(function(){
processFile(e);
runner = null;
}, config.delay);
}

export function goLiveErrorsEnabled() {
return <GoLiveErrorsConfig>vscode.workspace.getConfiguration('go')['liveErrors'].enabled;
Copy link
Contributor

Choose a reason for hiding this comment

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

add a null/undefined check for vscode.workspace.getConfiguration('go')['liveErrors'] before accessing the enabled property

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, I think that when auto save is enabled, we shouldn't be enabling live errors. That would just be redundant work being done by build and gotype.

Can you add that check here?

}

// 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;
}

// 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 newDiagnostics = [];
if (!newDiagnostics) {
newDiagnostics = [];
}

// grab a copy of the existing diagnostics that are being reported for the
// current file
let oldDiagnostics = diagnosticCollection.get(uri);

// delete the existing diagnostics for the current file
//
// error-level diagnostics will be reported by this process, so we want to
// clear out the existing errors to avoid getting duplicates
diagnosticCollection.delete(uri);
Copy link
Contributor

Choose a reason for hiding this comment

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

gotype-live should be reporting any error-level diagnostics that we'd see during a build

If the above assumption is indeed true, I'd still suggest having 2 separate diagnostic collections.

One for warnings (this will be fed by the linting and vetting), the other for errors (this will be fed by build and live errors).

This way you don't have to copy the warnings from currentDiagnostics.
The rest of your code here (delete diagnostics for uri etc.) can remain

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not too picky about this one though. Perf wise, it shouldn't be a problem because you are only touching diagnostics for a single file. But it just feels wrong (gut wise) to be copying array items everytime liveError is run

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried using two separate diagnostics, but when I called .set on the live diagnostic object, it erased the warning diagnostics that were there. It appeared to me that you can only have one diagnostic object in use, per file, at a time.

I may have done something wrong, though. Is it possible for me to use two separate diagnostic objects on the same file and the same time and the diagnostics from both objects will be displayed?


// we want to keep all non-error level diagnostics that were previously
// reported, so add them back in
oldDiagnostics.forEach((value) => {
if (value.severity !== vscode.DiagnosticSeverity.Error) {
newDiagnostics.push(value);
}
});

if (err) {
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);
newDiagnostics.push(diagnostic);
});
}
diagnosticCollection.set(uri, newDiagnostics);
});
p.stdin.end(fileContents);
}
4 changes: 3 additions & 1 deletion src/goMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ 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 diagnosticCollection: vscode.DiagnosticCollection;

export function activate(ctx: vscode.ExtensionContext): void {
let useLangServer = vscode.workspace.getConfiguration('go')['useLanguageServer'];
Expand Down Expand Up @@ -87,6 +88,7 @@ export function activate(ctx: vscode.ExtensionContext): void {
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