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