blob: fb592c580d3f21decff97cabcea706f6f9c23524 [file] [log] [blame]
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------*/
'use strict';
import cp = require('child_process');
import path = require('path');
import vscode = require('vscode');
import { toolExecutionEnvironment } from './goEnv';
import { promptForMissingTool } from './goInstallTools';
import { buildDiagnosticCollection } from './goMain';
import { isModSupported } from './goModules';
import { getBinPath, getGoConfig } from './util';
// Interface for settings configuration for adding and removing tags
interface GoLiveErrorsConfig {
delay: number;
enabled: boolean;
}
let runner: NodeJS.Timer;
export function goLiveErrorsEnabled() {
const goConfig = getGoConfig();
// If the language server is enabled, there is no need for live errors.
if (goConfig['useLanguageServer'] === true) {
return false;
}
const liveErrorsConfig = <GoLiveErrorsConfig>goConfig['liveErrors'];
if (liveErrorsConfig === null || liveErrorsConfig === undefined || !liveErrorsConfig.enabled) {
return false;
}
const files = vscode.workspace.getConfiguration('files', null);
const autoSave = files['autoSave'];
const autoSaveDelay = files['autoSaveDelay'];
if (
autoSave !== null &&
autoSave !== undefined &&
autoSave === 'afterDelay' &&
autoSaveDelay < liveErrorsConfig.delay * 1.5
) {
return false;
}
return liveErrorsConfig.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(() => {
processFile(e);
runner = null;
}, getGoConfig(e.document.uri)['liveErrors']['delay']);
}
// processFile does the actual work once the timeout has fired
async function processFile(e: vscode.TextDocumentChangeEvent) {
const isMod = await isModSupported(e.document.uri);
if (isMod) {
return;
}
const gotypeLive = getBinPath('gotype-live');
if (!path.isAbsolute(gotypeLive)) {
return promptForMissingTool('gotype-live');
}
const fileContents = e.document.getText();
const fileName = e.document.fileName;
const args = ['-e', '-a', '-lf=' + fileName, path.dirname(fileName)];
const env = toolExecutionEnvironment();
const p = cp.execFile(gotypeLive, args, { env }, (err, stdout, stderr) => {
if (err && (<any>err).code === 'ENOENT') {
promptForMissingTool('gotype-live');
return;
}
buildDiagnosticCollection.clear();
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
const diagnosticMap: Map<string, vscode.Diagnostic[]> = new Map();
stderr.split('\n').forEach((error) => {
if (error === null || error.length === 0) {
return;
}
// extract the line, column and error message from the gotype output
const [_, file, line, column, message] = /^(.+):(\d+):(\d+):\s+(.+)/.exec(error);
// get canonical file path
const canonicalFilePath = vscode.Uri.file(file).toString();
const range = new vscode.Range(+line - 1, +column, +line - 1, +column);
const diagnostic = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Error);
diagnostic.source = 'go';
const diagnostics = diagnosticMap.get(canonicalFilePath) || [];
diagnostics.push(diagnostic);
diagnosticMap.set(canonicalFilePath, diagnostics);
});
diagnosticMap.forEach((diagnostics, file) => {
buildDiagnosticCollection.set(vscode.Uri.parse(file), diagnostics);
});
}
});
if (p.pid) {
p.stdin.end(fileContents);
}
}