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