Skip to content

Commit

Permalink
Resolve the URL for the NuGet v3 AutoComplete API at extension startup.
Browse files Browse the repository at this point in the history
  • Loading branch information
tintoy committed Aug 13, 2017
1 parent e287373 commit 34f448d
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 48 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## v0.0.2

* Resolve the URL for the NuGet v3 AutoComplete API at extension startup.

## v0.0.1

* Initial release.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ An extension for VS Code that provides auto-complete when editing `<PackageRefer

## Installation

Since this extension is not yet available from the VS marketplace, simply [download](https://github.com/tintoy/msbuild-project-tools-vscode/releases/latest) the VSIX package for the latest release and install it by choosing "Install from VSIX" from the menu on the top right of the extensions panel.
You can install this extension from the [VS marketplace](https://marketplace.visualstudio.com/items?itemName=tintoy.msbuild-project-tools), or simply [download](https://github.com/tintoy/msbuild-project-tools-vscode/releases/latest) the VSIX package for the latest release and install it by choosing "Install from VSIX" from the menu on the top right of the extensions panel.

## Notes

Expand Down
31 changes: 1 addition & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "msbuild-project-tools",
"displayName": "MSBuild project tools",
"description": "Tools for working with MSBuild project files (such as auto-complete for package Ids / versions).",
"version": "0.0.1",
"version": "0.0.2",
"publisher": "tintoy",
"license": "MIT",
"repository": {
Expand Down Expand Up @@ -57,7 +57,6 @@
"dependencies": {
"axios": "^0.16.2",
"mz": "^2.6.0",
"nuget-client": "^0.1.2",
"rxjs": "^5.4.2",
"xmldom": "^0.1.27"
}
Expand Down
55 changes: 54 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

import { default as axios } from 'axios';
import * as vscode from 'vscode';

import { PackageReferenceCompletionProvider } from './providers/package-reference-completion';
Expand All @@ -10,10 +11,14 @@ import { PackageReferenceCompletionProvider } from './providers/package-referenc
* @param context The extension context.
*/
export async function activate(context: vscode.ExtensionContext): Promise<void> {
const nugetEndPointURLs = await getNuGetV3AutoCompleteEndPoints();

context.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
{ language: 'xml', pattern: '**/*.*proj' },
new PackageReferenceCompletionProvider()
new PackageReferenceCompletionProvider(
nugetEndPointURLs[0] // For now, just default to using the primary.
)
)
);
}
Expand All @@ -24,3 +29,51 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
export function deactivate(): void {
// Nothing to clean up.
}

/**
* Get the current end-points URLs for the NuGet v3 AutoComplete API.
*/
async function getNuGetV3AutoCompleteEndPoints(): Promise<string[]> {
const nugetIndexResponse = await axios.get('https://api.nuget.org/v3/index.json');

const index: NuGetIndex = nugetIndexResponse.data;
const autoCompleteEndPoints = index.resources
.filter(
resource => resource['@type'] === 'SearchAutocompleteService'
)
.map(
resource => resource['@id']
);

return autoCompleteEndPoints;
}

/**
* Represents the index response from the NuGet v3 API.
*/
export interface NuGetIndex {
/**
* Available API resources.
*/
resources: NuGetApiResource[];
}

/**
* Represents a NuGet API resource.
*/
export interface NuGetApiResource {
/**
* The resource Id (end-point URL).
*/
'@id': string;

/**
* The resource type.
*/
'@type': string;

/**
* An optional comment describing the resource.
*/
comment?: string;
}
34 changes: 20 additions & 14 deletions src/providers/package-reference-completion.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { NuGetClient } from 'nuget-client';
import * as vscode from 'vscode';
import * as xmldom from 'xmldom';

Expand Down Expand Up @@ -32,15 +31,12 @@ export class PackageReferenceCompletionProvider implements vscode.CompletionItem
*/
private packageVersionCache = new Map<string, string[]>();

/**
* Client for the NuGet API.
*/
private nugetClient = new NuGetClient();

/**
* Create a new {@link PackageReferenceCompletionProvider}.
*
* @param nugetAutoCompleteUrls The URL of the NuGet API end-point to use.
*/
constructor() {}
constructor(private nugetAutoCompleteUrl: string) {}

/**
* Provide completion items for the specified document position.
Expand Down Expand Up @@ -78,6 +74,7 @@ export class PackageReferenceCompletionProvider implements vscode.CompletionItem
let packageVersion: string;
let wantPackageId = false;
let wantPackageVersion = false;
let isLocallyFiltered = false;
for (let i = 0; i < elements.length; i++) {
const element = elements[i];

Expand All @@ -100,16 +97,19 @@ export class PackageReferenceCompletionProvider implements vscode.CompletionItem
if (wantPackageId) {
// TODO: Use NuGetClient from 'nuget-client'.

// TODO: Don't hard-code the base URL; resolve it from https://api.nuget.org/v3/index.json (resources[@type='SearchAutocompleteService']).
// TODO: Don't hard-code the use of the primary base URL; use Promise.race to call both end-points.
const response = await axios.get(
`https://api-v2v3search-0.nuget.org/autocomplete?q=${encodeURIComponent(packageId)}&take=${pageSize}&prerelease=true`
`${this.nugetAutoCompleteUrl}?q=${encodeURIComponent(packageId)}&take=${pageSize}&prerelease=true`
);

const availablePackageIds = response.data.data as string[];
availablePackageIds.sort();
for (const availablePackageId of availablePackageIds) {
if (packageId && !availablePackageId.startsWith(packageId))
if (packageId && !availablePackageId.startsWith(packageId)) {
isLocallyFiltered = true;

continue;
}

const completionItem = new vscode.CompletionItem(availablePackageId, vscode.CompletionItemKind.Module);
completionItems.push(completionItem);
Expand All @@ -119,23 +119,29 @@ export class PackageReferenceCompletionProvider implements vscode.CompletionItem
} else if (packageId && wantPackageVersion) {
// TODO: Use NuGetClient from 'nuget-client'.

// TODO: Don't hard-code the base URL; resolve it from https://api.nuget.org/v3/index.json (resources[@type='SearchAutocompleteService']).
// TODO: Don't hard-code the use of the primary base URL; use Promise.race to call both end-points.
const response = await axios.get(
`https://api-v2v3search-0.nuget.org/autocomplete?id=${encodeURIComponent(packageId)}&take=${pageSize}&prerelease=true`
`${this.nugetAutoCompleteUrl}?id=${encodeURIComponent(packageId)}&take=${pageSize}&prerelease=true`
);

const availablePackageVersions = response.data.data as string[];
availablePackageVersions.sort();
if (availablePackageVersions) {
for (const availablePackageVersion of availablePackageVersions) {
if (packageVersion && !availablePackageVersion.startsWith(packageVersion)) {
isLocallyFiltered = true;

continue;
}

const completionItem = new vscode.CompletionItem(availablePackageVersion, vscode.CompletionItemKind.Value);
completionItems.push(completionItem);
}
}
}

// If the list is incomplete, VSCode will call us again as the user continues to type.
const moreResultsAvailable = completionItems.length >= pageSize;
// If the list is locally-filtered or incomplete, VSCode will call us again as the user continues to type.
const moreResultsAvailable = isLocallyFiltered || completionItems.length >= pageSize;

return new vscode.CompletionList(completionItems, moreResultsAvailable);
}
Expand Down

0 comments on commit 34f448d

Please sign in to comment.