2
2
* Copyright (C) Microsoft Corporation. All rights reserved.
3
3
*--------------------------------------------------------*/
4
4
5
+ import * as path from "path" ;
5
6
import vscode = require( 'vscode' ) ;
6
7
import {
7
- languages ,
8
8
TextDocument ,
9
9
TextEdit ,
10
10
FormattingOptions ,
11
11
CancellationToken ,
12
12
DocumentFormattingEditProvider ,
13
13
DocumentRangeFormattingEditProvider ,
14
14
Range ,
15
+ TextEditor
15
16
} from 'vscode' ;
16
- import { LanguageClient , RequestType , NotificationType } from 'vscode-languageclient' ;
17
+ import { LanguageClient , RequestType } from 'vscode-languageclient' ;
17
18
import Window = vscode . window ;
18
19
import { IFeature } from '../feature' ;
19
20
import * as Settings from '../settings' ;
@@ -60,7 +61,7 @@ interface ScriptRegion {
60
61
61
62
interface MarkerCorrection {
62
63
name : string ;
63
- edits : ScriptRegion [ ]
64
+ edits : ScriptRegion [ ] ;
64
65
}
65
66
66
67
function editComparer ( leftOperand : ScriptRegion , rightOperand : ScriptRegion ) : number {
@@ -81,7 +82,57 @@ function editComparer(leftOperand: ScriptRegion, rightOperand: ScriptRegion): nu
81
82
}
82
83
}
83
84
85
+ class DocumentLocker {
86
+ private lockedDocuments : Object ;
87
+
88
+ constructor ( ) {
89
+ this . lockedDocuments = new Object ( ) ;
90
+ }
91
+
92
+ isLocked ( document : TextDocument ) : boolean {
93
+ return this . isLockedInternal ( this . getKey ( document ) ) ;
94
+ }
95
+
96
+ lock ( document : TextDocument , unlockWhenDone ?: Thenable < any > ) : void {
97
+ this . lockInternal ( this . getKey ( document ) , unlockWhenDone ) ;
98
+ }
99
+
100
+ unlock ( document : TextDocument ) : void {
101
+ this . unlockInternal ( this . getKey ( document ) ) ;
102
+ }
103
+
104
+ unlockAll ( ) : void {
105
+ Object . keys ( this . lockedDocuments ) . slice ( ) . forEach ( documentKey => this . unlockInternal ( documentKey ) ) ;
106
+ }
107
+
108
+ private getKey ( document : TextDocument ) : string {
109
+ return document . uri . toString ( ) ;
110
+ }
111
+
112
+ private lockInternal ( documentKey : string , unlockWhenDone ?: Thenable < any > ) : void {
113
+ if ( ! this . isLockedInternal ( documentKey ) ) {
114
+ this . lockedDocuments [ documentKey ] = true ;
115
+ }
116
+
117
+ if ( unlockWhenDone !== undefined ) {
118
+ unlockWhenDone . then ( ( ) => this . unlockInternal ( documentKey ) ) ;
119
+ }
120
+ }
121
+
122
+ private unlockInternal ( documentKey : string ) : void {
123
+ if ( this . isLockedInternal ( documentKey ) ) {
124
+ delete this . lockedDocuments [ documentKey ] ;
125
+ }
126
+ }
127
+
128
+ private isLockedInternal ( documentKey : string ) : boolean {
129
+ return this . lockedDocuments . hasOwnProperty ( documentKey ) ;
130
+ }
131
+ }
132
+
84
133
class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider , DocumentRangeFormattingEditProvider {
134
+ private static documentLocker = new DocumentLocker ( ) ;
135
+ private static statusBarTracker = new Object ( ) ;
85
136
private languageClient : LanguageClient ;
86
137
87
138
// The order in which the rules will be executed starting from the first element.
@@ -95,6 +146,10 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
95
146
// hence we keep this as an option but set it true by default.
96
147
private aggregateUndoStop : boolean ;
97
148
149
+ private get emptyPromise ( ) : Promise < TextEdit [ ] > {
150
+ return Promise . resolve ( TextEdit [ 0 ] ) ;
151
+ }
152
+
98
153
constructor ( aggregateUndoStop = true ) {
99
154
this . aggregateUndoStop = aggregateUndoStop ;
100
155
}
@@ -112,19 +167,54 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
112
167
options : FormattingOptions ,
113
168
token : CancellationToken ) : TextEdit [ ] | Thenable < TextEdit [ ] > {
114
169
115
- let textEdits : Thenable < TextEdit [ ] > = this . executeRulesInOrder ( document , range , options , 0 ) ;
116
- AnimatedStatusBar . showAnimatedStatusBarMessage ( "Formatting PowerShell document" , textEdits ) ;
170
+ let editor : TextEditor = this . getEditor ( document ) ;
171
+ if ( editor === undefined ) {
172
+ return this . emptyPromise ;
173
+ }
174
+
175
+ // Check if the document is already being formatted.
176
+ // If so, then ignore the formatting request.
177
+ if ( this . isDocumentLocked ( document ) ) {
178
+ return this . emptyPromise ;
179
+ }
180
+
181
+ let textEdits : Thenable < TextEdit [ ] > = this . executeRulesInOrder ( editor , range , options , 0 ) ;
182
+ this . lockDocument ( document , textEdits ) ;
183
+ PSDocumentFormattingEditProvider . showStatusBar ( document , textEdits ) ;
117
184
return textEdits ;
118
185
}
119
186
120
- executeRulesInOrder (
121
- document : TextDocument ,
187
+ setLanguageClient ( languageClient : LanguageClient ) : void {
188
+ this . languageClient = languageClient ;
189
+
190
+ // setLanguageClient is called while restarting a session,
191
+ // so this makes sure we clean up the document locker and
192
+ // any residual status bars
193
+ PSDocumentFormattingEditProvider . documentLocker . unlockAll ( ) ;
194
+ PSDocumentFormattingEditProvider . disposeAllStatusBars ( ) ;
195
+ }
196
+
197
+ private getEditor ( document : TextDocument ) : TextEditor {
198
+ return Window . visibleTextEditors . find ( ( e , n , obj ) => { return e . document === document ; } ) ;
199
+ }
200
+
201
+ private isDocumentLocked ( document : TextDocument ) : boolean {
202
+ return PSDocumentFormattingEditProvider . documentLocker . isLocked ( document ) ;
203
+ }
204
+
205
+ private lockDocument ( document : TextDocument , unlockWhenDone : Thenable < any > ) : void {
206
+ PSDocumentFormattingEditProvider . documentLocker . lock ( document , unlockWhenDone ) ;
207
+ }
208
+
209
+ private executeRulesInOrder (
210
+ editor : TextEditor ,
122
211
range : Range ,
123
212
options : FormattingOptions ,
124
213
index : number ) : Thenable < TextEdit [ ] > {
125
214
if ( this . languageClient !== null && index < this . ruleOrder . length ) {
126
- let rule = this . ruleOrder [ index ] ;
215
+ let rule : string = this . ruleOrder [ index ] ;
127
216
let uniqueEdits : ScriptRegion [ ] = [ ] ;
217
+ let document : TextDocument = editor . document ;
128
218
let edits : ScriptRegion [ ] ;
129
219
130
220
return this . languageClient . sendRequest (
@@ -165,22 +255,29 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
165
255
// we do not return a valid array because our text edits
166
256
// need to be executed in a particular order and it is
167
257
// easier if we perform the edits ourselves
168
- return this . applyEdit ( uniqueEdits , range , 0 , index ) ;
258
+ return this . applyEdit ( editor , uniqueEdits , range , 0 , index ) ;
169
259
} )
170
260
. then ( ( ) => {
171
261
// execute the same rule again if we left out violations
172
262
// on the same line
263
+ let newIndex : number = index + 1 ;
173
264
if ( uniqueEdits . length !== edits . length ) {
174
- return this . executeRulesInOrder ( document , range , options , index ) ;
265
+ newIndex = index ;
175
266
}
176
- return this . executeRulesInOrder ( document , range , options , index + 1 ) ;
267
+
268
+ return this . executeRulesInOrder ( editor , range , options , newIndex ) ;
177
269
} ) ;
178
270
} else {
179
- return Promise . resolve ( TextEdit [ 0 ] ) ;
271
+ return this . emptyPromise ;
180
272
}
181
273
}
182
274
183
- applyEdit ( edits : ScriptRegion [ ] , range : Range , markerIndex : number , ruleIndex : number ) : Thenable < void > {
275
+ private applyEdit (
276
+ editor : TextEditor ,
277
+ edits : ScriptRegion [ ] ,
278
+ range : Range ,
279
+ markerIndex : number ,
280
+ ruleIndex : number ) : Thenable < void > {
184
281
if ( markerIndex >= edits . length ) {
185
282
return ;
186
283
}
@@ -194,7 +291,7 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
194
291
edit . endLineNumber - 1 ,
195
292
edit . endColumnNumber - 1 ) ;
196
293
if ( range === null || range . contains ( editRange ) ) {
197
- return Window . activeTextEditor . edit ( ( editBuilder ) => {
294
+ return editor . edit ( ( editBuilder ) => {
198
295
editBuilder . replace (
199
296
editRange ,
200
297
edit . text ) ;
@@ -203,15 +300,15 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
203
300
undoStopAfter : undoStopAfter ,
204
301
undoStopBefore : undoStopBefore
205
302
} ) . then ( ( isEditApplied ) => {
206
- return this . applyEdit ( edits , range , markerIndex + 1 , ruleIndex ) ;
303
+ return this . applyEdit ( editor , edits , range , markerIndex + 1 , ruleIndex ) ;
207
304
} ) ; // TODO handle rejection
208
305
}
209
306
else {
210
- return this . applyEdit ( edits , range , markerIndex + 1 , ruleIndex ) ;
307
+ return this . applyEdit ( editor , edits , range , markerIndex + 1 , ruleIndex ) ;
211
308
}
212
309
}
213
310
214
- getSelectionRange ( document : TextDocument ) : Range {
311
+ private getSelectionRange ( document : TextDocument ) : Range {
215
312
let editor = vscode . window . visibleTextEditors . find ( editor => editor . document === document ) ;
216
313
if ( editor !== undefined ) {
217
314
return editor . selection as Range ;
@@ -220,11 +317,7 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
220
317
return null ;
221
318
}
222
319
223
- setLanguageClient ( languageClient : LanguageClient ) : void {
224
- this . languageClient = languageClient ;
225
- }
226
-
227
- getSettings ( rule : string ) : any {
320
+ private getSettings ( rule : string ) : any {
228
321
let psSettings : Settings . ISettings = Settings . load ( Utils . PowerShellLanguageId ) ;
229
322
let ruleSettings = new Object ( ) ;
230
323
ruleSettings [ "Enable" ] = true ;
@@ -247,6 +340,25 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
247
340
settings [ rule ] = ruleSettings ;
248
341
return settings ;
249
342
}
343
+
344
+ private static showStatusBar ( document : TextDocument , hideWhenDone : Thenable < any > ) : void {
345
+ let statusBar = AnimatedStatusBar . showAnimatedStatusBarMessage ( "Formatting PowerShell document" , hideWhenDone ) ;
346
+ this . statusBarTracker [ document . uri . toString ( ) ] = statusBar ;
347
+ hideWhenDone . then ( ( ) => {
348
+ this . disposeStatusBar ( document . uri . toString ( ) ) ;
349
+ } ) ;
350
+ }
351
+
352
+ private static disposeStatusBar ( documentUri : string ) {
353
+ if ( this . statusBarTracker . hasOwnProperty ( documentUri ) ) {
354
+ this . statusBarTracker [ documentUri ] . dispose ( ) ;
355
+ delete this . statusBarTracker [ documentUri ] ;
356
+ }
357
+ }
358
+
359
+ private static disposeAllStatusBars ( ) {
360
+ Object . keys ( this . statusBarTracker ) . slice ( ) . forEach ( ( key ) => this . disposeStatusBar ( key ) ) ;
361
+ }
250
362
}
251
363
252
364
export class DocumentFormatterFeature implements IFeature {
0 commit comments