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 { basename } from 'path' ;
9+ import { ProjectJSONContribution } from './projectJSONContribution' ;
10+ import { XHRRequest , configure as configureXHR , xhr } from 'request-light' ;
11+
12+ import { CompletionItem , CompletionItemProvider , CompletionList , TextDocument , Position , Hover , HoverProvider ,
13+ CancellationToken , Range , TextEdit , MarkedString , DocumentSelector , languages , workspace , Disposable } from 'vscode' ;
14+
15+ export interface ISuggestionsCollector {
16+ add ( suggestion : CompletionItem ) : void ;
17+ error ( message :string ) : void ;
18+ log ( message :string ) : void ;
19+ setAsIncomplete ( ) : void ;
20+ }
21+
22+ export interface IJSONContribution {
23+ getDocumentSelector ( ) : DocumentSelector ;
24+ getInfoContribution ( fileName : string , location : Location ) : Thenable < MarkedString [ ] > ;
25+ collectPropertySuggestions ( fileName : string , location : Location , currentWord : string , addValue : boolean , isLast :boolean , result : ISuggestionsCollector ) : Thenable < any > ;
26+ collectValueSuggestions ( fileName : string , location : Location , result : ISuggestionsCollector ) : Thenable < any > ;
27+ collectDefaultSuggestions ( fileName : string , result : ISuggestionsCollector ) : Thenable < any > ;
28+ resolveSuggestion ?( item : CompletionItem ) : Thenable < CompletionItem > ;
29+ }
30+
31+ export function addJSONProviders ( ) : Disposable {
32+ let subscriptions : Disposable [ ] = [ ] ;
33+
34+ // configure the XHR library with the latest proxy settings
35+ function configureHttpRequest ( ) {
36+ let httpSettings = workspace . getConfiguration ( 'http' ) ;
37+ configureXHR ( httpSettings . get < string > ( 'proxy' ) , httpSettings . get < boolean > ( 'proxyStrictSSL' ) ) ;
38+ }
39+
40+ configureHttpRequest ( ) ;
41+ subscriptions . push ( workspace . onDidChangeConfiguration ( e => configureHttpRequest ( ) ) ) ;
42+
43+ // register completion and hove providers for JSON setting file(s)
44+ let contributions = [ new ProjectJSONContribution ( xhr ) ] ;
45+ contributions . forEach ( contribution => {
46+ let selector = contribution . getDocumentSelector ( ) ;
47+ subscriptions . push ( languages . registerCompletionItemProvider ( selector , new JSONCompletionItemProvider ( contribution ) ) ) ;
48+ subscriptions . push ( languages . registerHoverProvider ( selector , new JSONHoverProvider ( contribution ) ) ) ;
49+ } ) ;
50+
51+ return Disposable . from ( ...subscriptions ) ;
52+ }
53+
54+ export class JSONHoverProvider implements HoverProvider {
55+
56+ constructor ( private jsonContribution : IJSONContribution ) {
57+ }
58+
59+ public provideHover ( document : TextDocument , position : Position , token : CancellationToken ) : Thenable < Hover > {
60+ let fileName = basename ( document . fileName ) ;
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 ( 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+
98+ let fileName = basename ( document . fileName ) ;
99+
100+ let currentWord = this . getCurrentWord ( document , position ) ;
101+ let overwriteRange = null ;
102+ let items : CompletionItem [ ] = [ ] ;
103+ let isIncomplete = false ;
104+
105+ let offset = document . offsetAt ( position ) ;
106+ let location = getLocation ( document . getText ( ) , offset ) ;
107+
108+ let node = location . previousNode ;
109+ 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' ) ) {
110+ overwriteRange = new Range ( document . positionAt ( node . offset ) , document . positionAt ( node . offset + node . length ) ) ;
111+ } else {
112+ overwriteRange = new Range ( document . positionAt ( offset - currentWord . length ) , position ) ;
113+ }
114+
115+ let proposed : { [ key : string ] : boolean } = { } ;
116+ let collector : ISuggestionsCollector = {
117+ add : ( suggestion : CompletionItem ) => {
118+ if ( ! proposed [ suggestion . label ] ) {
119+ proposed [ suggestion . label ] = true ;
120+ if ( overwriteRange ) {
121+ suggestion . textEdit = TextEdit . replace ( overwriteRange , suggestion . insertText ) ;
122+ }
123+
124+ items . push ( suggestion ) ;
125+ }
126+ } ,
127+ setAsIncomplete : ( ) => isIncomplete = true ,
128+ error : ( message : string ) => console . error ( message ) ,
129+ log : ( message : string ) => console . log ( message )
130+ } ;
131+
132+ let collectPromise : Thenable < any > = null ;
133+
134+ if ( location . isAtPropertyKey ) {
135+ let addValue = ! location . previousNode || ! location . previousNode . columnOffset && ( offset == ( location . previousNode . offset + location . previousNode . length ) ) ;
136+ let scanner = createScanner ( document . getText ( ) , true ) ;
137+ scanner . setPosition ( offset ) ;
138+ scanner . scan ( ) ;
139+ let isLast = scanner . getToken ( ) === SyntaxKind . CloseBraceToken || scanner . getToken ( ) === SyntaxKind . EOF ;
140+ collectPromise = this . jsonContribution . collectPropertySuggestions ( fileName , location , currentWord , addValue , isLast , collector ) ;
141+ } else {
142+ if ( location . path . length === 0 ) {
143+ collectPromise = this . jsonContribution . collectDefaultSuggestions ( fileName , collector ) ;
144+ } else {
145+ collectPromise = this . jsonContribution . collectValueSuggestions ( fileName , location , collector ) ;
146+ }
147+ }
148+ if ( collectPromise ) {
149+ return collectPromise . then ( ( ) => {
150+ if ( items . length > 0 ) {
151+ return new CompletionList ( items , isIncomplete ) ;
152+ }
153+ return null ;
154+ } ) ;
155+ }
156+ return null ;
157+ }
158+
159+ private getCurrentWord ( document : TextDocument , position : Position ) {
160+ var i = position . character - 1 ;
161+ var text = document . lineAt ( position . line ) . text ;
162+ while ( i >= 0 && ' \t\n\r\v":{[,' . indexOf ( text . charAt ( i ) ) === - 1 ) {
163+ i -- ;
164+ }
165+ return text . substring ( i + 1 , position . character ) ;
166+ }
167+ }
0 commit comments