blob: 5d99b9f56fd85d8c46bc2ba42ccbf028338bfbd9 [file] [log] [blame]
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------*/
'use strict';
import vscode = require('vscode');
import cp = require('child_process');
import { parseFilePrelude, isVendorSupported, getBinPath, getCurrentGoWorkspaceFromGOPATH } from './util';
import { documentSymbols } from './goOutline';
import { promptForMissingTool } from './goInstallTools';
import path = require('path');
export function listPackages(excludeImportedPkgs: boolean = false): Thenable<string[]> {
let importsPromise = excludeImportedPkgs && vscode.window.activeTextEditor ? getImports(vscode.window.activeTextEditor.document.fileName) : Promise.resolve([]);
let vendorSupportPromise = isVendorSupported();
let goPkgsPromise = new Promise<string[]>((resolve, reject) => {
cp.execFile(getBinPath('gopkgs'), [], (err, stdout, stderr) => {
if (err && (<any>err).code === 'ENOENT') {
promptForMissingTool('gopkgs');
return reject();
}
let lines = stdout.toString().split('\n');
if (lines[lines.length - 1] === '') {
// Drop the empty entry from the final '\n'
lines.pop();
}
return resolve(lines);
});
});
return vendorSupportPromise.then((vendorSupport: boolean) => {
return Promise.all<string[]>([goPkgsPromise, importsPromise]).then(values => {
let pkgs = values[0];
let importedPkgs = values [1];
if (!vendorSupport) {
if (importedPkgs.length > 0) {
pkgs = pkgs.filter(element => {
return importedPkgs.indexOf(element) === -1;
});
}
return pkgs.sort();
}
let currentFileDirPath = path.dirname(vscode.window.activeTextEditor.document.fileName);
let currentWorkspace = getCurrentGoWorkspaceFromGOPATH(currentFileDirPath);
let pkgSet = new Set<string>();
pkgs.forEach(pkg => {
if (!pkg || importedPkgs.indexOf(pkg) > -1) {
return;
}
let magicVendorString = '/vendor/';
let vendorIndex = pkg.indexOf(magicVendorString);
if (vendorIndex === -1) {
magicVendorString = 'vendor/';
if (pkg.startsWith(magicVendorString)) {
vendorIndex = 0;
}
}
// Check if current file and the vendor pkg belong to the same root project
// If yes, then vendor pkg can be replaced with its relative path to the "vendor" folder
// If not, then the vendor pkg should not be allowed to be imported.
if (vendorIndex > -1) {
let rootProjectForVendorPkg = path.join(currentWorkspace, pkg.substr(0, vendorIndex));
let relativePathForVendorPkg = pkg.substring(vendorIndex + magicVendorString.length);
if (relativePathForVendorPkg && currentFileDirPath.startsWith(rootProjectForVendorPkg)) {
pkgSet.add(relativePathForVendorPkg);
}
return;
}
// pkg is not a vendor project
pkgSet.add(pkg);
});
return Array.from(pkgSet).sort();
});
});
}
/**
* Returns the imported packages in the given file
*
* @param fileName File system path of the file whose imports need to be returned
* @returns Array of imported package paths wrapped in a promise
*/
function getImports(fileName: string): Promise<string[]> {
let options = { fileName: fileName, importsOnly: true };
return documentSymbols(options).then(symbols => {
if (!symbols || !symbols[0] || !symbols[0].children) {
return [];
}
// imports will be of the form { type: 'import', label: '"math"'}
let imports = symbols[0].children.filter(x => x.type === 'import').map(x => x.label.substr(1, x.label.length - 2));
return imports;
});
}
function askUserForImport(): Thenable<string> {
return listPackages(true).then(packages => {
return vscode.window.showQuickPick(packages);
});
}
export function getTextEditForAddImport(arg: string): vscode.TextEdit {
// Import name wasn't provided
if (arg === undefined) {
return null;
}
let {imports, pkg} = parseFilePrelude(vscode.window.activeTextEditor.document.getText());
let multis = imports.filter(x => x.kind === 'multi');
if (multis.length > 0) {
// There is a multiple import declaration, add to the last one
let closeParenLine = multis[multis.length - 1].end;
return vscode.TextEdit.insert(new vscode.Position(closeParenLine, 0), '\t"' + arg + '"\n');
} else if (imports.length > 0) {
// There are only single import declarations, add after the last one
let lastSingleImport = imports[imports.length - 1].end;
return vscode.TextEdit.insert(new vscode.Position(lastSingleImport + 1, 0), 'import "' + arg + '"\n');
} else if (pkg && pkg.start >= 0) {
// There are no import declarations, but there is a package declaration
return vscode.TextEdit.insert(new vscode.Position(pkg.start + 1, 0), '\nimport (\n\t"' + arg + '"\n)\n');
} else {
// There are no imports and no package declaration - give up
return null;
}
}
export function addImport(arg: string) {
let p = arg ? Promise.resolve(arg) : askUserForImport();
p.then(imp => {
let edit = getTextEditForAddImport(imp);
if (edit) {
vscode.window.activeTextEditor.edit(editBuilder => {
editBuilder.insert(edit.range.start, edit.newText);
});
}
});
}