diff --git a/src/goImport.ts b/src/goImport.ts index d773252fd..1485c21af 100644 --- a/src/goImport.ts +++ b/src/goImport.ts @@ -20,10 +20,19 @@ export async function listPackages(excludeImportedPkgs: boolean = false): Promis ? await getImports(vscode.window.activeTextEditor.document) : []; const pkgMap = await getImportablePackages(vscode.window.activeTextEditor.document.fileName, true); - - return Array.from(pkgMap.keys()) - .filter(pkg => !importedPkgs.some(imported => imported === pkg)) - .sort(); + const stdLibs: string[] = []; + const nonStdLibs: string[] = []; + pkgMap.forEach((value, key) => { + if (importedPkgs.some(imported => imported === key)) { + return; + } + if (value.isStd) { + stdLibs.push(key); + } else { + nonStdLibs.push(key); + } + }); + return [...stdLibs.sort(), ...nonStdLibs.sort()]; } /** @@ -100,7 +109,7 @@ export function getTextEditForAddImport(arg: string): vscode.TextEdit[] { } } -export function addImport(arg: {importPath: string, from: string}) { +export function addImport(arg: { importPath: string, from: string }) { const p = (arg && arg.importPath) ? Promise.resolve(arg.importPath) : askUserForImport(); p.then(imp => { /* __GDPR__ @@ -108,7 +117,7 @@ export function addImport(arg: {importPath: string, from: string}) { "from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - sendTelemetryEvent('addImportCmd', { from: (arg && arg.from) || 'cmd'}); + sendTelemetryEvent('addImportCmd', { from: (arg && arg.from) || 'cmd' }); const edits = getTextEditForAddImport(imp); if (edits && edits.length > 0) { const edit = new vscode.WorkspaceEdit(); diff --git a/src/goPackages.ts b/src/goPackages.ts index 46ea53680..d14630e17 100644 --- a/src/goPackages.ts +++ b/src/goPackages.ts @@ -10,12 +10,17 @@ import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools'; import { envPath, fixDriveCasingInWindows, getCurrentGoWorkspaceFromGOPATH } from './goPath'; import { getBinPath, getCurrentGoPath, getGoVersion, getToolsEnvVars, isVendorSupported, sendTelemetryEvent } from './util'; -type GopkgsDone = (res: Map) => void; +type GopkgsDone = (res: Map) => void; interface Cache { - entry: Map; + entry: Map; lastHit: number; } +export interface PackageInfo { + name: string; + isStd: boolean; +} + let gopkgsNotified: boolean = false; let cacheTimeout: number = 5000; @@ -26,16 +31,16 @@ const allPkgsCache: Map = new Map(); const pkgRootDirs = new Map(); -function gopkgs(workDir?: string): Promise> { +function gopkgs(workDir?: string): Promise> { const gopkgsBinPath = getBinPath('gopkgs'); if (!path.isAbsolute(gopkgsBinPath)) { promptForMissingTool('gopkgs'); - return Promise.resolve(new Map()); + return Promise.resolve(new Map()); } const t0 = Date.now(); - return new Promise>((resolve, reject) => { - const args = ['-format', '{{.Name}};{{.ImportPath}}']; + return new Promise>((resolve, reject) => { + const args = ['-format', '{{.Name}};{{.ImportPath}};{{.Dir}}']; if (workDir) { args.push('-workDir', workDir); } @@ -48,7 +53,7 @@ function gopkgs(workDir?: string): Promise> { cmd.stderr.on('data', d => errchunks.push(d)); cmd.on('error', e => err = e); cmd.on('close', () => { - const pkgs = new Map(); + const pkgs = new Map(); if (err && err.code === 'ENOENT') { return promptForMissingTool('gopkgs'); } @@ -64,7 +69,7 @@ function gopkgs(workDir?: string): Promise> { console.log(`Running gopkgs failed with "${errorMsg}"\nCheck if you can run \`gopkgs -format {{.Name}};{{.ImportPath}}\` in a terminal successfully.`); return resolve(pkgs); } - + const goroot = process.env['GOROOT']; const output = chunks.join(''); if (output.indexOf(';') === -1) { // User might be using the old gopkgs tool, prompt to update @@ -75,19 +80,23 @@ function gopkgs(workDir?: string): Promise> { } const index = pkgPath.lastIndexOf('/'); const pkgName = index === -1 ? pkgPath : pkgPath.substr(index + 1); - pkgs.set(pkgPath, pkgName); + pkgs.set(pkgPath, { + name: pkgName, + isStd: !pkgPath.includes('.') + }); }); return resolve(pkgs); } - output.split('\n').forEach((pkgDetail) => { if (!pkgDetail || !pkgDetail.trim() || pkgDetail.indexOf(';') === -1) { return; } - const [pkgName, pkgPath] = pkgDetail.trim().split(';'); - pkgs.set(pkgPath, pkgName); + const [pkgName, pkgPath, pkgDir] = pkgDetail.trim().split(';'); + pkgs.set(pkgPath, { + name: pkgName, + isStd: goroot === null ? false : pkgDir.startsWith(goroot) + }); }); - const timeTaken = Date.now() - t0; /* __GDPR__ "gopkgs" : { @@ -102,10 +111,10 @@ function gopkgs(workDir?: string): Promise> { }); } -function getAllPackagesNoCache(workDir: string): Promise> { - return new Promise>((resolve, reject) => { +function getAllPackagesNoCache(workDir: string): Promise> { + return new Promise>((resolve, reject) => { // Use subscription style to guard costly/long running invocation - const callback = function(pkgMap: Map) { + const callback = function(pkgMap: Map) { resolve(pkgMap); }; @@ -134,7 +143,7 @@ function getAllPackagesNoCache(workDir: string): Promise> { * @argument workDir. The workspace directory of the project. * @returns Map mapping between package import path and package name */ -export async function getAllPackages(workDir: string): Promise> { +export async function getAllPackages(workDir: string): Promise> { const cache = allPkgsCache.get(workDir); const useCache = cache && (new Date().getTime() - cache.lastHit) < cacheTimeout; if (useCache) { @@ -163,7 +172,7 @@ export async function getAllPackages(workDir: string): Promise mapping between package import path and package name */ -export function getImportablePackages(filePath: string, useCache: boolean = false): Promise> { +export function getImportablePackages(filePath: string, useCache: boolean = false): Promise> { filePath = fixDriveCasingInWindows(filePath); const fileDirPath = path.dirname(filePath); @@ -171,24 +180,24 @@ export function getImportablePackages(filePath: string, useCache: boolean = fals const workDir = foundPkgRootDir || fileDirPath; const cache = allPkgsCache.get(workDir); - const getAllPackagesPromise: Promise> = useCache && cache + const getAllPackagesPromise: Promise> = useCache && cache ? Promise.race([getAllPackages(workDir), cache.entry]) : getAllPackages(workDir); return Promise.all([isVendorSupported(), getAllPackagesPromise]).then(([vendorSupported, pkgs]) => { - const pkgMap = new Map(); + const pkgMap = new Map(); if (!pkgs) { return pkgMap; } const currentWorkspace = getCurrentGoWorkspaceFromGOPATH(getCurrentGoPath(), fileDirPath); - pkgs.forEach((pkgName, pkgPath) => { - if (pkgName === 'main') { + pkgs.forEach((info, pkgPath) => { + if (info.name === 'main') { return; } if (!vendorSupported || !currentWorkspace) { - pkgMap.set(pkgPath, pkgName); + pkgMap.set(pkgPath, info); return; } @@ -208,7 +217,7 @@ export function getImportablePackages(filePath: string, useCache: boolean = fals const allowToImport = isAllowToImportPackage(fileDirPath, currentWorkspace, relativePkgPath); if (allowToImport) { - pkgMap.set(relativePkgPath, pkgName); + pkgMap.set(relativePkgPath, info); } }); return pkgMap; diff --git a/src/goSuggest.ts b/src/goSuggest.ts index 76ef2f123..fd3c8042d 100644 --- a/src/goSuggest.ts +++ b/src/goSuggest.ts @@ -11,7 +11,7 @@ import cp = require('child_process'); import { getTextEditForAddImport } from './goImport'; import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools'; import { isModSupported } from './goModules'; -import { getImportablePackages } from './goPackages'; +import { getImportablePackages, PackageInfo } from './goPackages'; import { getCurrentGoWorkspaceFromGOPATH } from './goPath'; import { byteOffsetAt, getBinPath, getCurrentGoPath, getGoConfig, getParametersAndReturnType, getToolsEnvVars, goBuiltinTypes, goKeywords, guessPackageNameFromFile, isPositionInComment, isPositionInString, parseFilePrelude, runGodoc } from './util'; @@ -58,7 +58,7 @@ const exportedMemberRegex = /(const|func|type|var)(\s+\(.*\))?\s+([A-Z]\w*)/; const gocodeNoSupportForgbMsgKey = 'dontshowNoSupportForgb'; export class GoCompletionItemProvider implements vscode.CompletionItemProvider, vscode.Disposable { - private pkgsList = new Map(); + private pkgsList = new Map(); private killMsgShown: boolean = false; private setGocodeOptions: boolean = true; private isGoMod: boolean = false; @@ -467,8 +467,8 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider, */ private getPackageImportPath(input: string): string[] { const matchingPackages: any[] = []; - this.pkgsList.forEach((pkgName: string, pkgPath: string) => { - if (input === pkgName) { + this.pkgsList.forEach((info: PackageInfo, pkgPath: string) => { + if (input === info.name) { matchingPackages.push(pkgPath); } }); @@ -528,7 +528,7 @@ function getKeywordCompletions(currentWord: string): vscode.CompletionItem[] { * @param allPkgMap Map of all available packages and their import paths * @param importedPackages List of imported packages. Used to prune imported packages out of available packages */ -function getPackageCompletions(document: vscode.TextDocument, currentWord: string, allPkgMap: Map, importedPackages: string[] = []): vscode.CompletionItem[] { +function getPackageCompletions(document: vscode.TextDocument, currentWord: string, allPkgMap: Map, importedPackages: string[] = []): vscode.CompletionItem[] { const cwd = path.dirname(document.fileName); const goWorkSpace = getCurrentGoWorkspaceFromGOPATH(getCurrentGoPath(), cwd); const workSpaceFolder = vscode.workspace.getWorkspaceFolder(document.uri); @@ -536,7 +536,8 @@ function getPackageCompletions(document: vscode.TextDocument, currentWord: strin const completionItems: any[] = []; - allPkgMap.forEach((pkgName: string, pkgPath: string) => { + allPkgMap.forEach((info: PackageInfo, pkgPath: string) => { + const pkgName = info.name; if (pkgName.startsWith(currentWord) && importedPackages.indexOf(pkgName) === -1) { const item = new vscode.CompletionItem(pkgName, vscode.CompletionItemKind.Keyword); diff --git a/test/integration/extension.test.ts b/test/integration/extension.test.ts index 1a6694786..d2ab5ed15 100644 --- a/test/integration/extension.test.ts +++ b/test/integration/extension.test.ts @@ -552,7 +552,7 @@ It returns the number of bytes written and any write error encountered. vendorSupportPromise.then(async (vendorSupport: boolean) => { const gopkgsPromise = getAllPackages(workDir).then(pkgMap => { - const pkgs = Array.from(pkgMap.keys()).filter(p => pkgMap.get(p) !== 'main'); + const pkgs = Array.from(pkgMap.keys()).filter(p => pkgMap.get(p).name !== 'main'); if (vendorSupport) { vendorPkgsFullPath.forEach(pkg => { assert.equal(pkgs.indexOf(pkg) > -1, true, `Package not found by goPkgs: ${pkg}`);