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

Commit

Permalink
Use go list all to get pkgname to use in completions Fixes #647
Browse files Browse the repository at this point in the history
  • Loading branch information
ramya-rao-a committed Jul 16, 2017
1 parent 1b6efbe commit 78125e6
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 67 deletions.
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];
}
}
}

0 comments on commit 78125e6

Please # to comment.