| /* eslint-disable @typescript-eslint/no-explicit-any */ |
| /*--------------------------------------------------------- |
| * 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 { getGoConfig } from './config'; |
| import { toolExecutionEnvironment } from './goEnv'; |
| import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools'; |
| import { getBinPath } from './util'; |
| import { killProcessTree } from './utils/processUtils'; |
| |
| export class GoDocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider { |
| public provideDocumentFormattingEdits( |
| document: vscode.TextDocument, |
| options: vscode.FormattingOptions, |
| token: vscode.CancellationToken |
| ): vscode.ProviderResult<vscode.TextEdit[]> { |
| if (vscode.window.visibleTextEditors.every((e) => e.document.fileName !== document.fileName)) { |
| return []; |
| } |
| |
| const filename = document.fileName; |
| const goConfig = getGoConfig(document.uri); |
| const formatFlags = goConfig['formatFlags'].slice() || []; |
| |
| // Ignore -w because we don't want to write directly to disk. |
| if (formatFlags.indexOf('-w') > -1) { |
| formatFlags.splice(formatFlags.indexOf('-w'), 1); |
| } |
| |
| const formatTool = getFormatTool(goConfig); |
| |
| // Handle issues: |
| // https://github.com/Microsoft/vscode-go/issues/613 |
| // https://github.com/Microsoft/vscode-go/issues/630 |
| if (formatTool === 'goimports' || formatTool === 'goreturns' || formatTool === 'gofumports') { |
| formatFlags.push('-srcdir', filename); |
| } |
| |
| // Since goformat supports the style flag, set tabsize if the user hasn't. |
| if (formatTool === 'goformat' && formatFlags.length === 0 && options.insertSpaces) { |
| formatFlags.push('-style=indent=' + options.tabSize); |
| } |
| |
| return this.runFormatter(formatTool, formatFlags, document, token).then( |
| (edits) => edits, |
| (err) => { |
| if (typeof err === 'string' && err.startsWith('flag provided but not defined: -srcdir')) { |
| promptForUpdatingTool(formatTool); |
| return Promise.resolve([]); |
| } |
| if (err) { |
| console.log(err); |
| return Promise.reject('Check the console in dev tools to find errors when formatting.'); |
| } |
| } |
| ); |
| } |
| |
| private runFormatter( |
| formatTool: string, |
| formatFlags: string[], |
| document: vscode.TextDocument, |
| token: vscode.CancellationToken |
| ): Thenable<vscode.TextEdit[]> { |
| const formatCommandBinPath = getBinPath(formatTool); |
| |
| return new Promise<vscode.TextEdit[]>((resolve, reject) => { |
| if (!path.isAbsolute(formatCommandBinPath)) { |
| promptForMissingTool(formatTool); |
| return reject(); |
| } |
| |
| const env = toolExecutionEnvironment(); |
| const cwd = path.dirname(document.fileName); |
| let stdout = ''; |
| let stderr = ''; |
| |
| // Use spawn instead of exec to avoid maxBufferExceeded error |
| const p = cp.spawn(formatCommandBinPath, formatFlags, { env, cwd }); |
| token.onCancellationRequested(() => !p.killed && killProcessTree(p)); |
| p.stdout.setEncoding('utf8'); |
| p.stdout.on('data', (data) => (stdout += data)); |
| p.stderr.on('data', (data) => (stderr += data)); |
| p.on('error', (err) => { |
| if (err && (<any>err).code === 'ENOENT') { |
| promptForMissingTool(formatTool); |
| return reject(); |
| } |
| }); |
| p.on('close', (code) => { |
| if (code !== 0) { |
| return reject(stderr); |
| } |
| |
| // Return the complete file content in the edit. |
| // VS Code will calculate minimal edits to be applied |
| const fileStart = new vscode.Position(0, 0); |
| const fileEnd = document.lineAt(document.lineCount - 1).range.end; |
| const textEdits: vscode.TextEdit[] = [ |
| new vscode.TextEdit(new vscode.Range(fileStart, fileEnd), stdout) |
| ]; |
| return resolve(textEdits); |
| }); |
| if (p.pid) { |
| p.stdin.end(document.getText()); |
| } |
| }); |
| } |
| } |
| |
| export function usingCustomFormatTool(goConfig: { [key: string]: any }): boolean { |
| const formatTool = getFormatTool(goConfig); |
| switch (formatTool) { |
| case 'goreturns': |
| return false; |
| case 'goimports': |
| return false; |
| case 'gofmt': |
| return false; |
| case 'gofumpt': |
| // TODO(rstambler): Prompt to configure setting in gopls. |
| return false; |
| case 'gofumports': |
| // TODO(rstambler): Prompt to configure setting in gopls. |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| export function getFormatTool(goConfig: { [key: string]: any }): string { |
| if (goConfig['formatTool'] === 'default') { |
| return 'goimports'; |
| } |
| return goConfig['formatTool']; |
| } |