blob: d0970318438abd14ad702d16bb2cfc8235b06cb7 [file]
/* eslint-disable no-prototype-builtins */
/* 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 vscode = require('vscode');
import { CommandFactory } from './commands';
import { getGoConfig } from './config';
import { toolExecutionEnvironment } from './goEnv';
import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
import { byteOffsetAt, getBinPath, getFileArchive } from './util';
import { TelemetryKey, telemetryReporter } from './goTelemetry';
const COMMAND = 'gopls.modify_tags';
// Interface for the output from gomodifytags
interface GomodifytagsOutput {
start: number;
end: number;
lines: string[];
}
// Interface for the arguments passed to gopls.modify_tags command. URI and range
// are required parameters collected by the extension based on the open editor,
// and the rest of the args are collected by user input or user settings.
interface GoModifyTagsArgs {
URI: string;
range: vscode.Range;
add?: string;
addOptions?: string;
remove?: string;
removeOptions?: string;
transform?: string;
valueFormat?: string;
clear?: boolean;
clearOptions?: boolean;
}
// Interface for settings configuration for adding and removing tags
interface GoTagsConfig {
[key: string]: any;
tags: string;
options: string;
promptForTags: boolean;
template: string;
}
export const addTags: CommandFactory = (_ctx, goCtx) => async (commandArgs: GoTagsConfig) => {
const useGoplsCommand = goCtx.serverInfo?.Commands?.includes(COMMAND);
if (useGoplsCommand) {
const args = getCommonArgs();
if (!args) {
return;
}
const [tags, options, transformValue, template] = await getTagsAndOptions(getGoConfig()?.addTags, commandArgs);
if (!tags && !options) {
return;
}
if (tags) {
args.add = tags;
}
if (options) {
args.addOptions = options;
}
if (transformValue) {
args.transform = transformValue;
}
if (template) {
args.valueFormat = template;
}
await vscode.commands.executeCommand(COMMAND, args);
} else {
const args = getCommonArgsOld();
if (!args) {
return;
}
const [tags, options, transformValue, template] = await getTagsAndOptions(getGoConfig()?.addTags, commandArgs);
if (!tags && !options) {
return;
}
if (tags) {
args.push('--add-tags');
args.push(tags);
}
if (options) {
args.push('--add-options');
args.push(options);
}
if (transformValue) {
args.push('--transform');
args.push(transformValue);
}
if (template) {
args.push('--template');
args.push(template);
}
runGomodifytags(args);
}
};
export const removeTags: CommandFactory = (_ctx, goCtx) => async (commandArgs: GoTagsConfig) => {
const useGoplsCommand = goCtx.serverInfo?.Commands?.includes(COMMAND);
if (useGoplsCommand) {
const args = getCommonArgs();
if (!args) {
return;
}
const [tags, options] = await getTagsAndOptions(getGoConfig()?.removeTags, commandArgs);
if (!tags && !options) {
args.clear = true;
args.clearOptions = true;
}
if (tags) {
args.remove = tags;
}
if (options) {
args.removeOptions = options;
}
vscode.commands.executeCommand(COMMAND, args);
} else {
const args = getCommonArgsOld();
if (!args) {
return;
}
const [tags, options] = await getTagsAndOptions(getGoConfig()?.removeTags, commandArgs);
if (!tags && !options) {
args.push('--clear-tags');
args.push('--clear-options');
}
if (tags) {
args.push('--remove-tags');
args.push(tags);
}
if (options) {
args.push('--remove-options');
args.push(options);
}
runGomodifytags(args);
}
};
// getCommonArgsOld produces the flags used for executing the gomodifytags binary.
function getCommonArgsOld(): string[] | undefined {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active.');
return undefined;
}
if (!editor.document.fileName.endsWith('.go')) {
vscode.window.showInformationMessage('Current file is not a Go file.');
return undefined;
}
const args = ['-modified', '-file', editor.document.fileName, '-format', 'json'];
if (
editor.selection.start.line === editor.selection.end.line &&
editor.selection.start.character === editor.selection.end.character
) {
// Add tags to the whole struct
const offset = byteOffsetAt(editor.document, editor.selection.start);
args.push('-offset');
args.push(offset.toString());
} else if (editor.selection.start.line <= editor.selection.end.line) {
// Add tags to selected lines
args.push('-line');
args.push(`${editor.selection.start.line + 1},${editor.selection.end.line + 1}`);
}
return args;
}
// getCommonArgs produces the args used for calling the gopls.modify_tags command.
function getCommonArgs(): GoModifyTagsArgs | undefined {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active.');
return undefined;
}
if (!editor.document.fileName.endsWith('.go')) {
vscode.window.showInformationMessage('Current file is not a Go file.');
return undefined;
}
const args: GoModifyTagsArgs = {
URI: editor.document.uri.toString(),
range: editor.selection
};
return args;
}
async function getTagsAndOptions(config: GoTagsConfig, commandArgs: GoTagsConfig): Promise<(string | undefined)[]> {
const tags = commandArgs && commandArgs.tags ? commandArgs.tags : config.tags;
const options = commandArgs && commandArgs.options ? commandArgs.options : config.options;
const promptForTags = commandArgs && commandArgs.promptForTags ? commandArgs.promptForTags : config.promptForTags;
const transformValue: string = commandArgs && commandArgs.transform ? commandArgs.transform : config.transform;
const format: string = commandArgs && commandArgs.template ? commandArgs.template : config.template;
if (!promptForTags) {
return Promise.resolve([tags, options, transformValue, format]);
}
const inputTags = await vscode.window.showInputBox({
value: tags,
prompt: 'Enter comma separated tag names'
});
const inputOptions = await vscode.window.showInputBox({
value: options,
prompt: 'Enter comma separated options'
});
const transformOption = await vscode.window.showInputBox({
value: transformValue,
prompt: 'Enter transform value'
});
const template = await vscode.window.showInputBox({
value: format,
prompt: 'Enter template value'
});
return [inputTags, inputOptions, transformOption, template];
}
async function runGomodifytags(args: string[]) {
telemetryReporter.add(TelemetryKey.TOOL_USAGE_GOMODIFYTAGS, 1);
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const gomodifytags = getBinPath('gomodifytags');
const input = getFileArchive(editor.document);
const p = cp.execFile(gomodifytags, args, { env: toolExecutionEnvironment() }, (err, stdout, stderr) => {
if (err && (<any>err).code === 'ENOENT') {
promptForMissingTool('gomodifytags');
return;
}
if (err && (<any>err).code === 2 && args.indexOf('--template') > 0) {
vscode.window.showInformationMessage(
'Cannot modify tags: you might be using a' + 'version that does not support --template'
);
promptForUpdatingTool('gomodifytags');
return;
}
if (err) {
vscode.window.showInformationMessage(`Cannot modify tags: ${stderr}`);
return;
}
const output = <GomodifytagsOutput>JSON.parse(stdout);
editor.edit((editBuilder) => {
editBuilder.replace(new vscode.Range(output.start - 1, 0, output.end, 0), output.lines.join('\n') + '\n');
});
});
if (p.pid) {
p.stdin?.end(input);
}
}