22 * Copyright (C) Microsoft Corporation. All rights reserved.
33 *--------------------------------------------------------*/
44
5+ import * as path from "path" ;
56import vscode = require( 'vscode' ) ;
67import {
7- languages ,
88 TextDocument ,
99 TextEdit ,
1010 FormattingOptions ,
1111 CancellationToken ,
1212 DocumentFormattingEditProvider ,
1313 DocumentRangeFormattingEditProvider ,
1414 Range ,
15+ TextEditor
1516} from 'vscode' ;
16- import { LanguageClient , RequestType , NotificationType } from 'vscode-languageclient' ;
17+ import { LanguageClient , RequestType } from 'vscode-languageclient' ;
1718import Window = vscode . window ;
1819import { IFeature } from '../feature' ;
1920import * as Settings from '../settings' ;
@@ -60,7 +61,7 @@ interface ScriptRegion {
6061
6162interface MarkerCorrection {
6263 name : string ;
63- edits : ScriptRegion [ ]
64+ edits : ScriptRegion [ ] ;
6465}
6566
6667function editComparer ( leftOperand : ScriptRegion , rightOperand : ScriptRegion ) : number {
@@ -81,7 +82,57 @@ function editComparer(leftOperand: ScriptRegion, rightOperand: ScriptRegion): nu
8182 }
8283}
8384
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+
84133class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider , DocumentRangeFormattingEditProvider {
134+ private static documentLocker = new DocumentLocker ( ) ;
135+ private static statusBarTracker = new Object ( ) ;
85136 private languageClient : LanguageClient ;
86137
87138 // The order in which the rules will be executed starting from the first element.
@@ -95,6 +146,10 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
95146 // hence we keep this as an option but set it true by default.
96147 private aggregateUndoStop : boolean ;
97148
149+ private get emptyPromise ( ) : Promise < TextEdit [ ] > {
150+ return Promise . resolve ( TextEdit [ 0 ] ) ;
151+ }
152+
98153 constructor ( aggregateUndoStop = true ) {
99154 this . aggregateUndoStop = aggregateUndoStop ;
100155 }
@@ -112,19 +167,54 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
112167 options : FormattingOptions ,
113168 token : CancellationToken ) : TextEdit [ ] | Thenable < TextEdit [ ] > {
114169
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 ) ;
117184 return textEdits ;
118185 }
119186
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 ,
122211 range : Range ,
123212 options : FormattingOptions ,
124213 index : number ) : Thenable < TextEdit [ ] > {
125214 if ( this . languageClient !== null && index < this . ruleOrder . length ) {
126- let rule = this . ruleOrder [ index ] ;
215+ let rule : string = this . ruleOrder [ index ] ;
127216 let uniqueEdits : ScriptRegion [ ] = [ ] ;
217+ let document : TextDocument = editor . document ;
128218 let edits : ScriptRegion [ ] ;
129219
130220 return this . languageClient . sendRequest (
@@ -165,22 +255,29 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
165255 // we do not return a valid array because our text edits
166256 // need to be executed in a particular order and it is
167257 // easier if we perform the edits ourselves
168- return this . applyEdit ( uniqueEdits , range , 0 , index ) ;
258+ return this . applyEdit ( editor , uniqueEdits , range , 0 , index ) ;
169259 } )
170260 . then ( ( ) => {
171261 // execute the same rule again if we left out violations
172262 // on the same line
263+ let newIndex : number = index + 1 ;
173264 if ( uniqueEdits . length !== edits . length ) {
174- return this . executeRulesInOrder ( document , range , options , index ) ;
265+ newIndex = index ;
175266 }
176- return this . executeRulesInOrder ( document , range , options , index + 1 ) ;
267+
268+ return this . executeRulesInOrder ( editor , range , options , newIndex ) ;
177269 } ) ;
178270 } else {
179- return Promise . resolve ( TextEdit [ 0 ] ) ;
271+ return this . emptyPromise ;
180272 }
181273 }
182274
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 > {
184281 if ( markerIndex >= edits . length ) {
185282 return ;
186283 }
@@ -194,7 +291,7 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
194291 edit . endLineNumber - 1 ,
195292 edit . endColumnNumber - 1 ) ;
196293 if ( range === null || range . contains ( editRange ) ) {
197- return Window . activeTextEditor . edit ( ( editBuilder ) => {
294+ return editor . edit ( ( editBuilder ) => {
198295 editBuilder . replace (
199296 editRange ,
200297 edit . text ) ;
@@ -203,15 +300,15 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
203300 undoStopAfter : undoStopAfter ,
204301 undoStopBefore : undoStopBefore
205302 } ) . then ( ( isEditApplied ) => {
206- return this . applyEdit ( edits , range , markerIndex + 1 , ruleIndex ) ;
303+ return this . applyEdit ( editor , edits , range , markerIndex + 1 , ruleIndex ) ;
207304 } ) ; // TODO handle rejection
208305 }
209306 else {
210- return this . applyEdit ( edits , range , markerIndex + 1 , ruleIndex ) ;
307+ return this . applyEdit ( editor , edits , range , markerIndex + 1 , ruleIndex ) ;
211308 }
212309 }
213310
214- getSelectionRange ( document : TextDocument ) : Range {
311+ private getSelectionRange ( document : TextDocument ) : Range {
215312 let editor = vscode . window . visibleTextEditors . find ( editor => editor . document === document ) ;
216313 if ( editor !== undefined ) {
217314 return editor . selection as Range ;
@@ -220,11 +317,7 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
220317 return null ;
221318 }
222319
223- setLanguageClient ( languageClient : LanguageClient ) : void {
224- this . languageClient = languageClient ;
225- }
226-
227- getSettings ( rule : string ) : any {
320+ private getSettings ( rule : string ) : any {
228321 let psSettings : Settings . ISettings = Settings . load ( Utils . PowerShellLanguageId ) ;
229322 let ruleSettings = new Object ( ) ;
230323 ruleSettings [ "Enable" ] = true ;
@@ -247,6 +340,25 @@ class PSDocumentFormattingEditProvider implements DocumentFormattingEditProvider
247340 settings [ rule ] = ruleSettings ;
248341 return settings ;
249342 }
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+ }
250362}
251363
252364export class DocumentFormatterFeature implements IFeature {
0 commit comments