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