mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-01 05:51:32 +01:00
Fixes #3204: [json] project.json intellisense broken due to server change
This commit is contained in:
@@ -4,19 +4,34 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {MarkedString, CompletionItemKind} from 'vscode-languageserver';
|
||||
import {MarkedString, CompletionItemKind, CompletionItem} from 'vscode-languageserver';
|
||||
import Strings = require('../utils/strings');
|
||||
import nls = require('../utils/nls');
|
||||
import {IXHRResponse, getErrorStatusDescription} from '../utils/httpRequest';
|
||||
import {IJSONWorkerContribution, ISuggestionsCollector} from '../jsonContributions';
|
||||
import {IRequestService} from '../jsonSchemaService';
|
||||
import {JSONLocation} from '../jsonLocation';
|
||||
|
||||
let LIMIT = 40;
|
||||
const FEED_INDEX_URL = 'https://api.nuget.org/v3/index.json';
|
||||
const LIMIT = 30;
|
||||
const RESOLVE_ID = 'ProjectJSONContribution-';
|
||||
|
||||
const CACHE_EXPIRY = 1000 * 60 * 5; // 5 minutes
|
||||
|
||||
interface NugetServices {
|
||||
'SearchQueryService'?: string;
|
||||
'SearchAutocompleteService'?: string;
|
||||
'PackageBaseAddress/3.0.0'?: string;
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export class ProjectJSONContribution implements IJSONWorkerContribution {
|
||||
|
||||
private requestService : IRequestService;
|
||||
|
||||
private cachedProjects: { [id: string]: { version: string, description: string, time: number }} = {};
|
||||
private cacheSize: number = 0;
|
||||
private nugetIndexPromise: Thenable<NugetServices>;
|
||||
|
||||
public constructor(requestService: IRequestService) {
|
||||
this.requestService = requestService;
|
||||
}
|
||||
@@ -24,6 +39,67 @@ export class ProjectJSONContribution implements IJSONWorkerContribution {
|
||||
private isProjectJSONFile(resource: string): boolean {
|
||||
return Strings.endsWith(resource, '/project.json');
|
||||
}
|
||||
|
||||
private completeWithCache(id: string, item: CompletionItem) : boolean {
|
||||
let entry = this.cachedProjects[id];
|
||||
if (entry) {
|
||||
if (new Date().getTime() - entry.time > CACHE_EXPIRY) {
|
||||
delete this.cachedProjects[id];
|
||||
this.cacheSize--;
|
||||
return false;
|
||||
}
|
||||
item.detail = entry.version;
|
||||
item.documentation = entry.description;
|
||||
item.insertText = item.insertText.replace(/\{\{\}\}/, '{{' + entry.version + '}}');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private addCached(id: string, version: string, description: string) {
|
||||
this.cachedProjects[id] = { version, description, time: new Date().getTime()};
|
||||
this.cacheSize++;
|
||||
if (this.cacheSize > 50) {
|
||||
let currentTime = new Date().getTime() ;
|
||||
for (var id in this.cachedProjects) {
|
||||
let entry = this.cachedProjects[id];
|
||||
if (currentTime - entry.time > CACHE_EXPIRY) {
|
||||
delete this.cachedProjects[id];
|
||||
this.cacheSize--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getNugetIndex() : Thenable<NugetServices> {
|
||||
if (!this.nugetIndexPromise) {
|
||||
this.nugetIndexPromise = this.makeJSONRequest<any>(FEED_INDEX_URL).then(indexContent => {
|
||||
let services : NugetServices = {};
|
||||
if (indexContent && Array.isArray(indexContent.resources)) {
|
||||
let resources = <any[]> indexContent.resources;
|
||||
for (let i = resources.length - 1; i >= 0; i--) {
|
||||
let type = resources[i]['@type'];
|
||||
let id = resources[i]['@id'];
|
||||
if (type && id) {
|
||||
services[type] = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
return services;
|
||||
});
|
||||
}
|
||||
return this.nugetIndexPromise;
|
||||
}
|
||||
|
||||
private getNugetService(serviceType: string) : Thenable<string> {
|
||||
return this.getNugetIndex().then(services => {
|
||||
let serviceURL = services[serviceType];
|
||||
if (!serviceURL) {
|
||||
return Promise.reject<string>(nls.localize('json.nugget.error.missingservice', 'NuGet index document is missing service {0}', serviceType));
|
||||
}
|
||||
return serviceURL;
|
||||
});
|
||||
}
|
||||
|
||||
public collectDefaultSuggestions(resource: string, result: ISuggestionsCollector): Thenable<any> {
|
||||
if (this.isProjectJSONFile(resource)) {
|
||||
@@ -40,106 +116,88 @@ export class ProjectJSONContribution implements IJSONWorkerContribution {
|
||||
return null;
|
||||
}
|
||||
|
||||
public collectPropertySuggestions(resource: string, location: JSONLocation, currentWord: string, addValue: boolean, isLast:boolean, result: ISuggestionsCollector) : Thenable<any> {
|
||||
if (this.isProjectJSONFile(resource) && (location.matches(['dependencies']) || location.matches(['frameworks', '*', 'dependencies']) || location.matches(['frameworks', '*', 'frameworkAssemblies']))) {
|
||||
let queryUrl : string;
|
||||
if (currentWord.length > 0) {
|
||||
queryUrl = 'https://www.nuget.org/api/v2/Packages?'
|
||||
+ '$filter=Id%20ge%20\''
|
||||
+ encodeURIComponent(currentWord)
|
||||
+ '\'%20and%20Id%20lt%20\''
|
||||
+ encodeURIComponent(currentWord + 'z')
|
||||
+ '\'%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
+ '&$select=Id,Version,Description&$format=json&$top=' + LIMIT;
|
||||
} else {
|
||||
queryUrl = 'https://www.nuget.org/api/v2/Packages?'
|
||||
+ '$filter=IsAbsoluteLatestVersion%20eq%20true'
|
||||
+ '&$orderby=DownloadCount%20desc&$top=' + LIMIT
|
||||
+ '&$select=Id,Version,DownloadCount,Description&$format=json';
|
||||
}
|
||||
|
||||
return this.requestService({
|
||||
url : queryUrl
|
||||
}).then((success) => {
|
||||
if (success.status === 200) {
|
||||
try {
|
||||
let obj = JSON.parse(success.responseText);
|
||||
if (Array.isArray(obj.d)) {
|
||||
let results = <any[]> obj.d;
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
let curr = results[i];
|
||||
let name = curr.Id;
|
||||
let version = curr.Version;
|
||||
if (name) {
|
||||
let documentation = curr.Description;
|
||||
let typeLabel = curr.Version;
|
||||
let insertText = JSON.stringify(name);
|
||||
if (addValue) {
|
||||
insertText += ': "{{' + version + '}}"';
|
||||
if (!isLast) {
|
||||
insertText += ',';
|
||||
}
|
||||
}
|
||||
result.add({ kind: CompletionItemKind.Property, label: name, insertText: insertText, detail: typeLabel, documentation: documentation });
|
||||
}
|
||||
}
|
||||
if (results.length === LIMIT) {
|
||||
result.setAsIncomplete();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
} else {
|
||||
result.error(nls.localize('json.nugget.error.repoaccess', 'Request to the nuget repository failed: {0}', success.responseText));
|
||||
return 0;
|
||||
private makeJSONRequest<T>(url: string) : Thenable<T> {
|
||||
return this.requestService({
|
||||
url : url
|
||||
}).then(success => {
|
||||
if (success.status === 200) {
|
||||
try {
|
||||
return <T> JSON.parse(success.responseText);
|
||||
} catch (e) {
|
||||
return Promise.reject<T>(nls.localize('json.nugget.error.invalidformat', '{0} is not a valid JSON document', url));
|
||||
}
|
||||
}, (error) => {
|
||||
result.error(nls.localize('json.nugget.error.repoaccess', 'Request to the nuget repository failed: {0}', error.responseText));
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return Promise.reject<T>(nls.localize('json.nugget.error.indexaccess', 'Request to {0} failed: {1}', url, success.responseText));
|
||||
}, (error: IXHRResponse) => {
|
||||
return Promise.reject<T>(nls.localize('json.nugget.error.access', 'Request to {0} failed: {1}', url, getErrorStatusDescription(error.status)));
|
||||
});
|
||||
}
|
||||
|
||||
public collectValueSuggestions(resource: string, location: JSONLocation, currentKey: string, result: ISuggestionsCollector): Thenable<any> {
|
||||
public collectPropertySuggestions(resource: string, location: JSONLocation, currentWord: string, addValue: boolean, isLast:boolean, result: ISuggestionsCollector) : Thenable<any> {
|
||||
if (this.isProjectJSONFile(resource) && (location.matches(['dependencies']) || location.matches(['frameworks', '*', 'dependencies']) || location.matches(['frameworks', '*', 'frameworkAssemblies']))) {
|
||||
let queryUrl = 'https://www.myget.org/F/aspnetrelease/api/v2/Packages?'
|
||||
+ '$filter=Id%20eq%20\''
|
||||
+ encodeURIComponent(currentKey)
|
||||
+ '\'&$select=Version,IsAbsoluteLatestVersion&$format=json&$top=' + LIMIT;
|
||||
|
||||
return this.requestService({
|
||||
url : queryUrl
|
||||
}).then((success) => {
|
||||
try {
|
||||
let obj = JSON.parse(success.responseText);
|
||||
if (Array.isArray(obj.d)) {
|
||||
let results = <any[]> obj.d;
|
||||
return this.getNugetService('SearchAutocompleteService').then(service => {
|
||||
let queryUrl : string;
|
||||
if (currentWord.length > 0) {
|
||||
queryUrl = service + '?q=' + encodeURIComponent(currentWord) +'&take=' + LIMIT;
|
||||
} else {
|
||||
queryUrl = service + '?take=' + LIMIT;
|
||||
}
|
||||
return this.makeJSONRequest<any>(queryUrl).then(resultObj => {
|
||||
if (Array.isArray(resultObj.data)) {
|
||||
let results = <any[]> resultObj.data;
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
let curr = results[i];
|
||||
let version = curr.Version;
|
||||
if (version) {
|
||||
let name = JSON.stringify(version);
|
||||
let isLatest = curr.IsAbsoluteLatestVersion === 'true';
|
||||
let label = name;
|
||||
let documentation = '';
|
||||
if (isLatest) {
|
||||
documentation = nls.localize('json.nugget.versiondescription.suggest', 'The currently latest version of the package');
|
||||
let name = results[i];
|
||||
let insertText = JSON.stringify(name);
|
||||
if (addValue) {
|
||||
insertText += ': "{{}}"';
|
||||
if (!isLast) {
|
||||
insertText += ',';
|
||||
}
|
||||
result.add({ kind: CompletionItemKind.Class, label: label, insertText: name, documentation: documentation });
|
||||
}
|
||||
let item : CompletionItem = { kind: CompletionItemKind.Property, label: name, insertText: insertText };
|
||||
if (!this.completeWithCache(name, item)) {
|
||||
item.data = RESOLVE_ID + name;
|
||||
}
|
||||
result.add(item);
|
||||
}
|
||||
if (results.length === LIMIT) {
|
||||
result.setAsIncomplete();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
return 0;
|
||||
}, (error) => {
|
||||
return 0;
|
||||
}, error => {
|
||||
result.error(error);
|
||||
});
|
||||
}, error => {
|
||||
result.error(error);
|
||||
});
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
public collectValueSuggestions(resource: string, location: JSONLocation, currentKey: string, result: ISuggestionsCollector): Thenable<any> {
|
||||
if (this.isProjectJSONFile(resource) && (location.matches(['dependencies']) || location.matches(['frameworks', '*', 'dependencies']) || location.matches(['frameworks', '*', 'frameworkAssemblies']))) {
|
||||
return this.getNugetService('PackageBaseAddress/3.0.0').then(service => {
|
||||
let queryUrl = service + currentKey + '/index.json';
|
||||
return this.makeJSONRequest<any>(queryUrl).then(obj => {
|
||||
if (Array.isArray(obj.versions)) {
|
||||
let results = <any[]> obj.versions;
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
let curr = results[i];
|
||||
let name = JSON.stringify(curr);
|
||||
let label = name;
|
||||
let documentation = '';
|
||||
result.add({ kind: CompletionItemKind.Class, label: label, insertText: name, documentation: documentation });
|
||||
}
|
||||
if (results.length === LIMIT) {
|
||||
result.setAsIncomplete();
|
||||
}
|
||||
}
|
||||
}, error => {
|
||||
result.error(error);
|
||||
});
|
||||
}, error => {
|
||||
result.error(error);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
@@ -149,40 +207,63 @@ export class ProjectJSONContribution implements IJSONWorkerContribution {
|
||||
if (this.isProjectJSONFile(resource) && (location.matches(['dependencies', '*']) || location.matches(['frameworks', '*', 'dependencies', '*']) || location.matches(['frameworks', '*', 'frameworkAssemblies', '*']))) {
|
||||
let pack = location.getSegments()[location.getSegments().length - 1];
|
||||
|
||||
let htmlContent : MarkedString[] = [];
|
||||
htmlContent.push(nls.localize('json.nugget.package.hover', '{0}', pack));
|
||||
|
||||
let queryUrl = 'https://www.myget.org/F/aspnetrelease/api/v2/Packages?'
|
||||
+ '$filter=Id%20eq%20\''
|
||||
+ encodeURIComponent(pack)
|
||||
+ '\'%20and%20IsAbsoluteLatestVersion%20eq%20true'
|
||||
+ '&$select=Version,Description&$format=json&$top=5';
|
||||
|
||||
return this.requestService({
|
||||
url : queryUrl
|
||||
}).then((success) => {
|
||||
let content = success.responseText;
|
||||
if (content) {
|
||||
try {
|
||||
let obj = JSON.parse(content);
|
||||
if (obj.d && obj.d[0]) {
|
||||
let res = obj.d[0];
|
||||
if (res.Description) {
|
||||
htmlContent.push(res.Description);
|
||||
}
|
||||
if (res.Version) {
|
||||
htmlContent.push(nls.localize('json.nugget.version.hover', 'Latest version: {0}', res.Version));
|
||||
return this.getNugetService('SearchQueryService').then(service => {
|
||||
let queryUrl = service + '?q=' + encodeURIComponent(pack) +'&take=' + 5;
|
||||
return this.makeJSONRequest<any>(queryUrl).then(resultObj => {
|
||||
let htmlContent : MarkedString[] = [];
|
||||
htmlContent.push(nls.localize('json.nugget.package.hover', '{0}', pack));
|
||||
if (Array.isArray(resultObj.data)) {
|
||||
let results = <any[]> resultObj.data;
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
let res = results[i];
|
||||
this.addCached(res.id, res.version, res.description);
|
||||
if (res.id === pack) {
|
||||
if (res.description) {
|
||||
htmlContent.push(res.description);
|
||||
}
|
||||
if (res.version) {
|
||||
htmlContent.push(nls.localize('json.nugget.version.hover', 'Latest version: {0}', res.version));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return htmlContent;
|
||||
return htmlContent;
|
||||
}, (error) => {
|
||||
return null;
|
||||
});
|
||||
}, (error) => {
|
||||
return htmlContent;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public resolveSuggestion(item: CompletionItem) : Thenable<CompletionItem> {
|
||||
if (item.data && Strings.startsWith(item.data, RESOLVE_ID)) {
|
||||
let pack = item.data.substring(RESOLVE_ID.length);
|
||||
if (this.completeWithCache(pack, item)) {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
return this.getNugetService('SearchQueryService').then(service => {
|
||||
let queryUrl = service + '?q=' + encodeURIComponent(pack) +'&take=' + 10;
|
||||
return this.makeJSONRequest<any>(queryUrl).then(resultObj => {
|
||||
let itemResolved = false;
|
||||
if (Array.isArray(resultObj.data)) {
|
||||
let results = <any[]> resultObj.data;
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
let curr = results[i];
|
||||
this.addCached(curr.id, curr.version, curr.description);
|
||||
if (curr.id === pack) {
|
||||
this.completeWithCache(pack, item);
|
||||
itemResolved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return itemResolved ? item : null;
|
||||
});
|
||||
});
|
||||
};
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user