Skip to content

Commit c4c977c

Browse files
Merge pull request #1236 from DustinCampbell/project-json-dependency-supports
Move project.json dependency completion & hover to vscode-omnisharp
2 parents 875986a + a720c09 commit c4c977c

File tree

4 files changed

+407
-2
lines changed

4 files changed

+407
-2
lines changed

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@
3333
"fs-extra": "^1.0.0",
3434
"http-proxy-agent": "^1.0.0",
3535
"https-proxy-agent": "^1.0.0",
36+
"jsonc-parser": "^0.3.0",
3637
"lodash.debounce": "^4.0.8",
3738
"mkdirp": "^0.5.1",
3839
"open": "*",
40+
"request-light": "^0.2.0",
3941
"semver": "*",
4042
"tmp": "0.0.28",
4143
"vscode-debugprotocol": "^1.6.1",
+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
'use strict';
6+
7+
import { Location, getLocation, createScanner, SyntaxKind } from 'jsonc-parser';
8+
import { ProjectJSONContribution } from './projectJSONContribution';
9+
import { configure as configureXHR, xhr } from 'request-light';
10+
11+
import {
12+
CompletionItem, CompletionItemProvider, CompletionList, TextDocument, Position, Hover, HoverProvider,
13+
CancellationToken, Range, TextEdit, MarkedString, DocumentSelector, languages, workspace, Disposable
14+
} from 'vscode';
15+
16+
export interface ISuggestionsCollector {
17+
add(suggestion: CompletionItem): void;
18+
error(message: string): void;
19+
log(message: string): void;
20+
setAsIncomplete(): void;
21+
}
22+
23+
export interface IJSONContribution {
24+
getDocumentSelector(): DocumentSelector;
25+
getInfoContribution(fileName: string, location: Location): Thenable<MarkedString[]>;
26+
collectPropertySuggestions(fileName: string, location: Location, currentWord: string, addValue: boolean, isLast: boolean, result: ISuggestionsCollector): Thenable<any>;
27+
collectValueSuggestions(fileName: string, location: Location, result: ISuggestionsCollector): Thenable<any>;
28+
collectDefaultSuggestions(fileName: string, result: ISuggestionsCollector): Thenable<any>;
29+
resolveSuggestion?(item: CompletionItem): Thenable<CompletionItem>;
30+
}
31+
32+
export function addJSONProviders(): Disposable {
33+
let subscriptions: Disposable[] = [];
34+
35+
// configure the XHR library with the latest proxy settings
36+
function configureHttpRequest() {
37+
let httpSettings = workspace.getConfiguration('http');
38+
configureXHR(httpSettings.get<string>('proxy'), httpSettings.get<boolean>('proxyStrictSSL'));
39+
}
40+
41+
configureHttpRequest();
42+
subscriptions.push(workspace.onDidChangeConfiguration(e => configureHttpRequest()));
43+
44+
// register completion and hove providers for JSON setting file(s)
45+
let contributions = [new ProjectJSONContribution(xhr)];
46+
contributions.forEach(contribution => {
47+
let selector = contribution.getDocumentSelector();
48+
subscriptions.push(languages.registerCompletionItemProvider(selector, new JSONCompletionItemProvider(contribution)));
49+
subscriptions.push(languages.registerHoverProvider(selector, new JSONHoverProvider(contribution)));
50+
});
51+
52+
return Disposable.from(...subscriptions);
53+
}
54+
55+
export class JSONHoverProvider implements HoverProvider {
56+
57+
constructor(private jsonContribution: IJSONContribution) {
58+
}
59+
60+
public provideHover(document: TextDocument, position: Position, token: CancellationToken): Thenable<Hover> {
61+
let offset = document.offsetAt(position);
62+
let location = getLocation(document.getText(), offset);
63+
let node = location.previousNode;
64+
if (node && node.offset <= offset && offset <= node.offset + node.length) {
65+
let promise = this.jsonContribution.getInfoContribution(document.fileName, location);
66+
if (promise) {
67+
return promise.then(htmlContent => {
68+
let range = new Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
69+
let result: Hover = {
70+
contents: htmlContent,
71+
range: range
72+
};
73+
return result;
74+
});
75+
}
76+
}
77+
return null;
78+
}
79+
}
80+
81+
export class JSONCompletionItemProvider implements CompletionItemProvider {
82+
83+
constructor(private jsonContribution: IJSONContribution) {
84+
}
85+
86+
public resolveCompletionItem(item: CompletionItem, token: CancellationToken): Thenable<CompletionItem> {
87+
if (this.jsonContribution.resolveSuggestion) {
88+
let resolver = this.jsonContribution.resolveSuggestion(item);
89+
if (resolver) {
90+
return resolver;
91+
}
92+
}
93+
return Promise.resolve(item);
94+
}
95+
96+
public provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): Thenable<CompletionList> {
97+
let currentWord = this.getCurrentWord(document, position);
98+
let overwriteRange = null;
99+
let items: CompletionItem[] = [];
100+
let isIncomplete = false;
101+
102+
let offset = document.offsetAt(position);
103+
let location = getLocation(document.getText(), offset);
104+
105+
let node = location.previousNode;
106+
if (node && node.offset <= offset && offset <= node.offset + node.length && (node.type === 'property' || node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
107+
overwriteRange = new Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
108+
} else {
109+
overwriteRange = new Range(document.positionAt(offset - currentWord.length), position);
110+
}
111+
112+
let proposed: { [key: string]: boolean } = {};
113+
let collector: ISuggestionsCollector = {
114+
add: (suggestion: CompletionItem) => {
115+
if (!proposed[suggestion.label]) {
116+
proposed[suggestion.label] = true;
117+
if (overwriteRange) {
118+
suggestion.textEdit = TextEdit.replace(overwriteRange, <string>suggestion.insertText);
119+
}
120+
121+
items.push(suggestion);
122+
}
123+
},
124+
setAsIncomplete: () => isIncomplete = true,
125+
error: (message: string) => console.error(message),
126+
log: (message: string) => console.log(message)
127+
};
128+
129+
let collectPromise: Thenable<any> = null;
130+
131+
if (location.isAtPropertyKey) {
132+
let addValue = !location.previousNode || !location.previousNode.columnOffset && (offset == (location.previousNode.offset + location.previousNode.length));
133+
let scanner = createScanner(document.getText(), true);
134+
scanner.setPosition(offset);
135+
scanner.scan();
136+
let isLast = scanner.getToken() === SyntaxKind.CloseBraceToken || scanner.getToken() === SyntaxKind.EOF;
137+
collectPromise = this.jsonContribution.collectPropertySuggestions(document.fileName, location, currentWord, addValue, isLast, collector);
138+
} else {
139+
if (location.path.length === 0) {
140+
collectPromise = this.jsonContribution.collectDefaultSuggestions(document.fileName, collector);
141+
} else {
142+
collectPromise = this.jsonContribution.collectValueSuggestions(document.fileName, location, collector);
143+
}
144+
}
145+
if (collectPromise) {
146+
return collectPromise.then(() => {
147+
if (items.length > 0) {
148+
return new CompletionList(items, isIncomplete);
149+
}
150+
return null;
151+
});
152+
}
153+
return null;
154+
}
155+
156+
private getCurrentWord(document: TextDocument, position: Position) {
157+
let i = position.character - 1;
158+
let text = document.lineAt(position.line).text;
159+
while (i >= 0 && ' \t\n\r\v":{[,'.indexOf(text.charAt(i)) === -1) {
160+
i--;
161+
}
162+
return text.substring(i + 1, position.character);
163+
}
164+
}

0 commit comments

Comments
 (0)