Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Use go list all to get pkgname to use in completions #1092

Merged
merged 3 commits into from
Jul 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 4 additions & 23 deletions src/goImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { parseFilePrelude, isVendorSupported, getBinPath, getCurrentGoWorkspaceF
import { documentSymbols } from './goOutline';
import { promptForMissingTool } from './goInstallTools';
import path = require('path');
import { getRelativePackagePath } from './goPackages';

export function listPackages(excludeImportedPkgs: boolean = false): Thenable<string[]> {
let importsPromise = excludeImportedPkgs && vscode.window.activeTextEditor ? getImports(vscode.window.activeTextEditor.document) : Promise.resolve([]);
Expand Down Expand Up @@ -51,30 +52,10 @@ export function listPackages(excludeImportedPkgs: boolean = false): Thenable<str
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;
}
let relativePkgPath = getRelativePackagePath(currentFileDirPath, currentWorkspace, pkg);
if (relativePkgPath) {
pkgSet.add(relativePkgPath);
}
// 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();
Expand Down
2 changes: 2 additions & 0 deletions src/goMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { addTags, removeTags } from './goModifytags';
import { parseLiveFile } from './goLiveErrors';
import { GoCodeLensProvider } from './goCodelens';
import { implCursor } from './goImpl';
import { goListAll } from './goPackages';

export let errorDiagnosticCollection: vscode.DiagnosticCollection;
let warningDiagnosticCollection: vscode.DiagnosticCollection;
Expand All @@ -44,6 +45,7 @@ export function activate(ctx: vscode.ExtensionContext): void {
let toolsGopath = vscode.workspace.getConfiguration('go')['toolsGopath'];

updateGoPathGoRootFromConfig().then(() => {
goListAll();
offerToInstallTools();
let langServerAvailable = checkLanguageServer();
if (langServerAvailable) {
Expand Down
97 changes: 97 additions & 0 deletions src/goPackages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import vscode = require('vscode');
import cp = require('child_process');
import path = require('path');
import { getGoRuntimePath } from './goPath';
import { isVendorSupported, getCurrentGoWorkspaceFromGOPATH } from './util';

let allPkgs = new Map<string, string>();
let goListAllCompleted: boolean = false;

/**
* Runs go list all
* @returns Map<string, string> mapping between package import path and package name
*/
export function goListAll(): Promise<Map<string, string>> {
let goRuntimePath = getGoRuntimePath();

if (!goRuntimePath) {
vscode.window.showInformationMessage('Cannot find "go" binary. Update PATH or GOROOT appropriately');
return Promise.resolve(null);
}

if (goListAllCompleted) {
return Promise.resolve(allPkgs);
}
return new Promise<Map<string, string>>((resolve, reject) => {
cp.execFile(goRuntimePath, ['list', '-f', '{{.Name}};{{.ImportPath}}', 'all'], (err, stdout, stderr) => {
if (err) return reject();
stdout.split('\n').forEach(pkgDetail => {
if (!pkgDetail || !pkgDetail.trim() || pkgDetail.indexOf(';') === -1) return;
let [pkgName, pkgPath] = pkgDetail.trim().split(';');
if (pkgName !== 'main') {
allPkgs.set(pkgPath, pkgName);
}
});
goListAllCompleted = true;
return resolve(allPkgs);
});
});
}

/**
* Returns mapping of import path and package name for packages
* @param filePath. Used to determine the right relative path for vendor pkgs
* @returns Map<string, string> mapping between package import path and package name
*/
export function getAllPackageDetails(filePath: string): Promise<Map<string, string>> {

return Promise.all([isVendorSupported(), goListAll()]).then(values => {
let isVendorSupported = values[0];
let pkgs: Map<string, string> = values[1];
let currentFileDirPath = path.dirname(filePath);
let currentWorkspace = getCurrentGoWorkspaceFromGOPATH(currentFileDirPath);
if (!isVendorSupported || !currentWorkspace) {
return pkgs;
}

let pkgMap = new Map<string, string>();
pkgs.forEach((pkgName, pkgPath) => {
let relativePkgPath = getRelativePackagePath(currentFileDirPath, currentWorkspace, pkgPath);
if (relativePkgPath) {
pkgMap.set(relativePkgPath, pkgs.get(pkgPath));
}
});
return pkgMap;
});

}

/**
* If given pkgPath is not vendor pkg, then the same pkgPath is returned
* Else, the import path for the vendor pkg relative to given filePath is returned.
*/
export function getRelativePackagePath(currentFileDirPath: string, currentWorkspace: string, pkgPath: string): string {
let magicVendorString = '/vendor/';
let vendorIndex = pkgPath.indexOf(magicVendorString);
if (vendorIndex === -1) {
magicVendorString = 'vendor/';
if (pkgPath.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, pkgPath.substr(0, vendorIndex));
let relativePathForVendorPkg = pkgPath.substring(vendorIndex + magicVendorString.length);

if (relativePathForVendorPkg && currentFileDirPath.startsWith(rootProjectForVendorPkg)) {
return relativePathForVendorPkg;
}
return '';
}

return pkgPath;
}

78 changes: 34 additions & 44 deletions src/goSuggest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import cp = require('child_process');
import { dirname, basename } from 'path';
import { getBinPath, parameters, parseFilePrelude, isPositionInString, goKeywords, getToolsEnvVars } from './util';
import { promptForMissingTool } from './goInstallTools';
import { listPackages, getTextEditForAddImport } from './goImport';
import { getTextEditForAddImport } from './goImport';
import { getAllPackageDetails } from './goPackages';

function vscodeKindFromGoCodeClass(kind: string): vscode.CompletionItemKind {
switch (kind) {
Expand All @@ -34,15 +35,10 @@ interface GoCodeSuggestion {
type: string;
}

interface PackageInfo {
name: string;
path: string;
}

export class GoCompletionItemProvider implements vscode.CompletionItemProvider {

private gocodeConfigurationComplete = false;
private pkgsList: PackageInfo[] = [];
private pkgsList = new Map<string, string>();

public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable<vscode.CompletionItem[]> {
return this.provideCompletionItemsInternal(document, position, token, vscode.workspace.getConfiguration('go'));
Expand Down Expand Up @@ -219,21 +215,11 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider {
}
// TODO: Shouldn't lib-path also be set?
private ensureGoCodeConfigured(): Thenable<void> {
let pkgPromise = listPackages(false).then((pkgs: string[]) => {
this.pkgsList = pkgs.map(pkg => {
let index = pkg.lastIndexOf('/');
let pkgName = index === -1 ? pkg : pkg.substr(index + 1);
// pkgs from gopkg.in will be of the form gopkg.in/user/somepkg.v3
if (pkg.match(/gopkg\.in\/.*\.v\d+/)) {
pkgName = pkgName.substr(0, pkgName.lastIndexOf('.v'));
}
return {
name: pkgName,
path: pkg
};
});
getAllPackageDetails(vscode.window.activeTextEditor.document.fileName).then(pkgMap => {
this.pkgsList = pkgMap;
});
let configPromise = new Promise<void>((resolve, reject) => {

return new Promise<void>((resolve, reject) => {
// TODO: Since the gocode daemon is shared amongst clients, shouldn't settings be
// adjusted per-invocation to avoid conflicts from other gocode-using programs?
if (this.gocodeConfigurationComplete) {
Expand All @@ -244,33 +230,34 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider {
let env = getToolsEnvVars();
cp.execFile(gocode, ['set', 'propose-builtins', 'true'], { env }, (err, stdout, stderr) => {
cp.execFile(gocode, ['set', 'autobuild', autobuild], {}, (err, stdout, stderr) => {
resolve();
return resolve();
});
});
});
return Promise.all([pkgPromise, configPromise]).then(() => {
return Promise.resolve();
});

}

// Return importable packages that match given word as Completion Items
private getMatchingPackages(word: string, suggestionSet: Set<string>): vscode.CompletionItem[] {
if (!word) return [];
let completionItems = this.pkgsList.filter((pkgInfo: PackageInfo) => {
return pkgInfo.name.startsWith(word) && !suggestionSet.has(pkgInfo.name);
}).map((pkgInfo: PackageInfo) => {
let item = new vscode.CompletionItem(pkgInfo.name, vscode.CompletionItemKind.Keyword);
item.detail = pkgInfo.path;
item.documentation = 'Imports the package';
item.insertText = pkgInfo.name;
item.command = {
title: 'Import Package',
command: 'go.import.add',
arguments: [pkgInfo.path]
};
// Add same sortText to the unimported packages so that they appear after the suggestions from gocode
item.sortText = 'z';
return item;
let completionItems = [];

this.pkgsList.forEach((pkgName: string, pkgPath: string) => {
if (pkgName.startsWith(word) && !suggestionSet.has(pkgName)) {

let item = new vscode.CompletionItem(pkgName, vscode.CompletionItemKind.Keyword);
item.detail = pkgPath;
item.documentation = 'Imports the package';
item.insertText = pkgName;
item.command = {
title: 'Import Package',
command: 'go.import.add',
arguments: [pkgPath]
};
// Add same sortText to the unimported packages so that they appear after the suggestions from gocode
item.sortText = 'z';
completionItems.push(item);
}
});
return completionItems;
}
Expand All @@ -283,14 +270,17 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider {
return;
}

let [_, pkgName] = wordmatches;
let [_, pkgNameFromWord] = wordmatches;
// Word is isolated. Now check pkgsList for a match
let matchingPackages = this.pkgsList.filter(pkgInfo => {
return pkgInfo.name === pkgName;
let matchingPackages = [];
this.pkgsList.forEach((pkgName: string, pkgPath: string) => {
if (pkgNameFromWord === pkgName) {
matchingPackages.push(pkgPath);
}
});

if (matchingPackages && matchingPackages.length === 1) {
return matchingPackages[0].path;
return matchingPackages[0];
}
}
}
Loading