Commit 253a0e5c authored by Timon Stampfli's avatar Timon Stampfli
Browse files

Merge branch 'no-startup-messages' into 'master'

removing distracting startup messages

See merge request !10
parents a21ea039 65ab1c00
Pipeline #5637 passed with stages
in 1 minute and 1 second
......@@ -24,18 +24,6 @@
"js-tokens": "^4.0.0"
}
},
"@sgarciac/bombadil": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@sgarciac/bombadil/-/bombadil-0.0.7.tgz",
"integrity": "sha1-/9lSGfg2XQFNwq84ED/Dn9KP8zY=",
"requires": {
"chevrotain": "^0.28.3",
"lodash.every": "^4.6.0",
"lodash.includes": "^4.3.0",
"lodash.last": "^3.0.0",
"moment": "^2.18.1"
}
},
"@types/events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
......@@ -892,11 +880,6 @@
"parse5": "^3.0.1"
}
},
"chevrotain": {
"version": "0.28.3",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-0.28.3.tgz",
"integrity": "sha1-nj7WCkzduPPPjvlNm8aYs/W5fcM="
},
"chokidar": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
......@@ -3393,21 +3376,6 @@
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"lodash.every": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.every/-/lodash.every-4.6.0.tgz",
"integrity": "sha1-64mYS+vENkJ5uzrvu9HKGb+mxqc="
},
"lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
},
"lodash.last": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz",
"integrity": "sha1-JC9mMRLdTG5jcoxgo8kJ0b2tvUw="
},
"log-symbols": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
......@@ -3703,11 +3671,6 @@
}
}
},
"moment": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
......
......@@ -285,7 +285,6 @@
},
"dependencies": {
"fs-extra": "^7.0.0",
"@sgarciac/bombadil": "0.0.7",
"node-fetch": "^2.1.2",
"request-light": "^0.2.2",
"vscode-json-languageservice": "^3.1.0",
......
{
"properties": {
"foo": { "type": "string" },
"bar": { "type": "number", "maximum": 3 }
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { MarkedString, CompletionItemKind, CompletionItem } from 'vscode-languageserver';
import Strings = require('../utils/strings');
import { JSONWorkerContribution, JSONPath, CompletionsCollector } from 'vscode-json-languageservice';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
let globProperties: CompletionItem[] = [
{ kind: CompletionItemKind.Value, label: localize('assocLabelFile', "Files with Extension"), insertText: '"*.{{extension}}": "{{language}}"', documentation: localize('assocDescriptionFile', "Map all files matching the glob pattern in their filename to the language with the given identifier.") },
{ kind: CompletionItemKind.Value, label: localize('assocLabelPath', "Files with Path"), insertText: '"/{{path to file}}/*.{{extension}}": "{{language}}"', documentation: localize('assocDescriptionPath', "Map all files matching the absolute path glob pattern in their path to the language with the given identifier.") }
];
export class FileAssociationContribution implements JSONWorkerContribution {
private languageIds: string[] = [];
constructor() {
}
public setLanguageIds(ids: string[]): void {
this.languageIds = ids;
}
private isSettingsFile(resource: string): boolean {
return Strings.endsWith(resource, '/settings.json');
}
public async collectDefaultCompletions(resource: string, result: CompletionsCollector): Promise<any> {
return null;
}
public async collectPropertyCompletions(resource: string, location: JSONPath, currentWord: string, addValue: boolean, isLast: boolean, result: CompletionsCollector): Promise<any> {
if (this.isSettingsFile(resource) && location.length === 1 && location[0] === 'files.associations') {
globProperties.forEach(e => {
e.filterText = e.insertText;
result.add(e);
});
}
return null;
}
public async collectValueCompletions(resource: string, location: JSONPath, currentKey: string, result: CompletionsCollector): Promise<any> {
if (this.isSettingsFile(resource) && location.length === 1 && location[0] === 'files.associations') {
this.languageIds.forEach(l => {
result.add({
kind: CompletionItemKind.Value,
label: l,
insertText: JSON.stringify('{{' + l + '}}'),
filterText: JSON.stringify(l)
});
});
}
return null;
}
public async getInfoContribution(resource: string, location: JSONPath): Promise<MarkedString[]> {
return [];
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { MarkedString, CompletionItemKind, CompletionItem } from 'vscode-languageserver';
import Strings = require('../utils/strings');
import { JSONWorkerContribution, JSONPath, CompletionsCollector } from 'vscode-json-languageservice';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
let globProperties: CompletionItem[] = [
{ kind: CompletionItemKind.Value, label: localize('fileLabel', "Files by Extension"), insertText: '"**/*.{{extension}}": true', documentation: localize('fileDescription', "Match all files of a specific file extension.") },
{ kind: CompletionItemKind.Value, label: localize('filesLabel', "Files with Multiple Extensions"), insertText: '"**/*.{ext1,ext2,ext3}": true', documentation: localize('filesDescription', "Match all files with any of the file extensions.") },
{ kind: CompletionItemKind.Value, label: localize('derivedLabel', "Files with Siblings by Name"), insertText: '"**/*.{{source-extension}}": { "when": "$(basename).{{target-extension}}" }', documentation: localize('derivedDescription', "Match files that have siblings with the same name but a different extension.") },
{ kind: CompletionItemKind.Value, label: localize('topFolderLabel', "Folder by Name (Top Level)"), insertText: '"{{name}}": true', documentation: localize('topFolderDescription', "Match a top level folder with a specific name.") },
{ kind: CompletionItemKind.Value, label: localize('topFoldersLabel', "Folders with Multiple Names (Top Level)"), insertText: '"{folder1,folder2,folder3}": true', documentation: localize('topFoldersDescription', "Match multiple top level folders.") },
{ kind: CompletionItemKind.Value, label: localize('folderLabel', "Folder by Name (Any Location)"), insertText: '"**/{{name}}": true', documentation: localize('folderDescription', "Match a folder with a specific name in any location.") },
];
let globValues: CompletionItem[] = [
{ kind: CompletionItemKind.Value, label: localize('trueLabel', "true"), insertText: 'true', documentation: localize('trueDescription', "Enable the pattern.") },
{ kind: CompletionItemKind.Value, label: localize('falseLabel', "false"), insertText: 'false', documentation: localize('falseDescription', "Disable the pattern.") },
{ kind: CompletionItemKind.Value, label: localize('derivedLabel', "Files with Siblings by Name"), insertText: '{ "when": "$(basename).{{extension}}" }', documentation: localize('siblingsDescription', "Match files that have siblings with the same name but a different extension.") }
];
export class GlobPatternContribution implements JSONWorkerContribution {
constructor() {
}
private isSettingsFile(resource: string): boolean {
return Strings.endsWith(resource, '/settings.json');
}
public async collectDefaultCompletions(resource: string, result: CompletionsCollector): Promise<any> {
return null;
}
public async collectPropertyCompletions(resource: string, location: JSONPath, currentWord: string, addValue: boolean, isLast: boolean, result: CompletionsCollector): Promise<any> {
if (this.isSettingsFile(resource) && location.length === 1 && ((location[0] === 'files.exclude') || (location[0] === 'search.exclude'))) {
globProperties.forEach(e => {
e.filterText = e.insertText;
result.add(e);
});
}
return null;
}
public async collectValueCompletions(resource: string, location: JSONPath, currentKey: string, result: CompletionsCollector): Promise<any> {
if (this.isSettingsFile(resource) && location.length === 1 && ((location[0] === 'files.exclude') || (location[0] === 'search.exclude'))) {
globValues.forEach(e => {
e.filterText = e.insertText;
result.add(e);
});
}
return null;
}
public async getInfoContribution(resource: string, location: JSONPath): Promise<MarkedString[]> {
return [];
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { MarkedString, CompletionItemKind, CompletionItem } from 'vscode-languageserver';
import Strings = require('../utils/strings');
import { XHRResponse, getErrorStatusDescription } from 'request-light';
import { JSONWorkerContribution, JSONPath, CompletionsCollector } from 'vscode-json-languageservice';
import { xhr } from 'request-light';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
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 JSONWorkerContribution {
private cachedProjects: { [id: string]: { version: string, description: string, time: number } } = {};
private cacheSize: number = 0;
private nugetIndexPromise: Thenable<NugetServices> | null = null;
public constructor() {
}
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 (let 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 = {
'SearchQueryService': '',
'SearchAutocompleteService': '',
'PackageBaseAddress/3.0.0': ''
};
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>(localize('json.nugget.error.missingservice', 'NuGet index document is missing service {0}', serviceType));
}
return serviceURL;
});
}
public async collectDefaultCompletions(resource: string, result: CompletionsCollector): Promise<any> {
if (this.isProjectJSONFile(resource)) {
let defaultValue = {
'version': '{{1.0.0-*}}',
'dependencies': {},
'frameworks': {
'net461': {},
'netcoreapp1.0': {}
}
};
result.add({ kind: CompletionItemKind.Class, label: localize('json.project.default', 'Default project.json'), insertText: JSON.stringify(defaultValue, null, '\t'), documentation: '' });
}
return null;
}
private async makeJSONRequest<T>(url: string): Promise<T> {
return xhr({
url: url
}).then(success => {
if (success.status === 200) {
try {
return <T>JSON.parse(success.responseText);
} catch (e) {
return Promise.reject<T>(localize('json.nugget.error.invalidformat', '{0} is not a valid JSON document', url));
}
}
return Promise.reject<T>(localize('json.nugget.error.indexaccess', 'Request to {0} failed: {1}', url, success.responseText));
}, (error: XHRResponse) => {
return Promise.reject<T>(localize('json.nugget.error.access', 'Request to {0} failed: {1}', url, getErrorStatusDescription(error.status)));
});
}
public async collectPropertyCompletions(resource: string, location: JSONPath, currentWord: string, addValue: boolean, isLast: boolean, result: CompletionsCollector): Promise<any> {
if (this.isProjectJSONFile(resource) && (matches(location, ['dependencies']) || matches(location, ['frameworks', '*', 'dependencies']) || matches(location, ['frameworks', '*', 'frameworkAssemblies']))) {
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 name = results[i];
let insertText = JSON.stringify(name);
if (addValue) {
insertText += ': "{{}}"';
if (!isLast) {
insertText += ',';
}
}
let item: CompletionItem = { kind: CompletionItemKind.Property, label: name, insertText: insertText, filterText: JSON.stringify(name) };
if (!this.completeWithCache(name, item)) {
item.data = RESOLVE_ID + name;
}
result.add(item);
}
if (results.length === LIMIT) {
result.setAsIncomplete();
}
}
}, error => {
result.error(error);
});
}, error => {
result.error(error);
});
};
return null;
}
public async collectValueCompletions(resource: string, location: JSONPath, currentKey: string, result: CompletionsCollector): Promise<any> {
if (this.isProjectJSONFile(resource) && (matches(location, ['dependencies']) || matches(location, ['frameworks', '*', 'dependencies']) || matches(location, ['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;
}
public async getInfoContribution(resource: string, location: JSONPath): Promise<MarkedString[]> {
if (this.isProjectJSONFile(resource) && (matches(location, ['dependencies', '*']) || matches(location, ['frameworks', '*', 'dependencies', '*']) || matches(location, ['frameworks', '*', 'frameworkAssemblies', '*']))) {
let pack = <string>location[location.length - 1];
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(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(MarkedString.fromPlainText(res.description));
}
if (res.version) {
htmlContent.push(MarkedString.fromPlainText(localize('json.nugget.version.hover', 'Latest version: {0}', res.version)));
}
break;
}
}
}
return htmlContent;
}, (error) => {
return [];
});
}, (error) => {
return [];
});
}
return [];
}
public async resolveSuggestion(item: CompletionItem): Promise<CompletionItem | null> {
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;
}
}
function matches(segments: JSONPath, pattern: string[]) {
let k = 0;
for (let i = 0; k < pattern.length && i < segments.length; i++) {
if (pattern[k] === segments[i] || pattern[k] === '*') {
k++;
} else if (pattern[k] !== '**') {
return false;
}
}
return k === pattern.length;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TextDocument } from 'vscode-languageserver';
export interface LanguageModelCache<T> {
get(document: TextDocument): T;
onDocumentRemoved(document: TextDocument): void;
dispose(): void;
}
export function getLanguageModelCache<T>(maxEntries: number, cleanupIntervalTimeInSec: number, parse: (document: TextDocument) => T): LanguageModelCache<T> {
let languageModels: { [uri: string]: { version: number, languageId: string, cTime: number, languageModel: T } } = {};
let nModels = 0;
let cleanupInterval: NodeJS.Timeout | undefined = void 0;
if (cleanupIntervalTimeInSec > 0) {
cleanupInterval = setInterval(() => {
let cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000;
let uris = Object.keys(languageModels);
for (let uri of uris) {
let languageModelInfo = languageModels[uri];
if (languageModelInfo.cTime < cutoffTime) {
delete languageModels[uri];
nModels--;
}
}
}, cleanupIntervalTimeInSec * 1000);
}
return {
get(document: TextDocument): T {
let version = document.version;
let languageId = document.languageId;
let languageModelInfo = languageModels[document.uri];
if (languageModelInfo && languageModelInfo.version === version && languageModelInfo.languageId === languageId) {
languageModelInfo.cTime = Date.now();
return languageModelInfo.languageModel;
}
let languageModel = parse(document);
languageModels[document.uri] = { languageModel, version, languageId, cTime: Date.now() };
if (!languageModelInfo) {