@@ -187,6 +187,9 @@ namespace FourSlash {
187187
188188 // The current caret position in the active file
189189 public currentCaretPosition = 0 ;
190+ // The position of the end of the current selection, or -1 if nothing is selected
191+ public selectionEnd = - 1 ;
192+
190193 public lastKnownMarker = "" ;
191194
192195 // The file that's currently 'opened'
@@ -433,11 +436,19 @@ namespace FourSlash {
433436
434437 public goToPosition ( pos : number ) {
435438 this . currentCaretPosition = pos ;
439+ this . selectionEnd = - 1 ;
440+ }
441+
442+ public select ( startMarker : string , endMarker : string ) {
443+ const start = this . getMarkerByName ( startMarker ) , end = this . getMarkerByName ( endMarker ) ;
444+ this . goToPosition ( start . position ) ;
445+ this . selectionEnd = end . position ;
436446 }
437447
438448 public moveCaretRight ( count = 1 ) {
439449 this . currentCaretPosition += count ;
440450 this . currentCaretPosition = Math . min ( this . currentCaretPosition , this . getFileContent ( this . activeFile . fileName ) . length ) ;
451+ this . selectionEnd = - 1 ;
441452 }
442453
443454 // Opens a file given its 0-based index or fileName
@@ -980,9 +991,9 @@ namespace FourSlash {
980991 }
981992
982993 for ( const reference of expectedReferences ) {
983- const { fileName, start, end} = reference ;
994+ const { fileName, start, end } = reference ;
984995 if ( reference . marker && reference . marker . data ) {
985- const { isWriteAccess, isDefinition} = reference . marker . data ;
996+ const { isWriteAccess, isDefinition } = reference . marker . data ;
986997 this . verifyReferencesWorker ( actualReferences , fileName , start , end , isWriteAccess , isDefinition ) ;
987998 }
988999 else {
@@ -1193,7 +1204,7 @@ namespace FourSlash {
11931204 displayParts : ts . SymbolDisplayPart [ ] ,
11941205 documentation : ts . SymbolDisplayPart [ ] ,
11951206 tags : ts . JSDocTagInfo [ ]
1196- ) {
1207+ ) {
11971208
11981209 const actualQuickInfo = this . languageService . getQuickInfoAtPosition ( this . activeFile . fileName , this . currentCaretPosition ) ;
11991210 assert . equal ( actualQuickInfo . kind , kind , this . messageAtLastKnownMarker ( "QuickInfo kind" ) ) ;
@@ -1789,19 +1800,16 @@ namespace FourSlash {
17891800 // We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track
17901801 // of the incremental offset from each edit to the next. We assume these edit ranges don't overlap
17911802
1792- edits = edits . sort ( ( a , b ) => a . span . start - b . span . start ) ;
1793- for ( let i = 0 ; i < edits . length - 1 ; i ++ ) {
1794- const firstEditSpan = edits [ i ] . span ;
1795- const firstEditEnd = firstEditSpan . start + firstEditSpan . length ;
1796- assert . isTrue ( firstEditEnd <= edits [ i + 1 ] . span . start ) ;
1797- }
1803+ // Copy this so we don't ruin someone else's copy
1804+ edits = JSON . parse ( JSON . stringify ( edits ) ) ;
17981805
17991806 // Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters
18001807 const oldContent = this . getFileContent ( fileName ) ;
18011808 let runningOffset = 0 ;
18021809
1803- for ( const edit of edits ) {
1804- const offsetStart = edit . span . start + runningOffset ;
1810+ for ( let i = 0 ; i < edits . length ; i ++ ) {
1811+ const edit = edits [ i ] ;
1812+ const offsetStart = edit . span . start ;
18051813 const offsetEnd = offsetStart + edit . span . length ;
18061814 this . editScriptAndUpdateMarkers ( fileName , offsetStart , offsetEnd , edit . newText ) ;
18071815 const editDelta = edit . newText . length - edit . span . length ;
@@ -1816,8 +1824,13 @@ namespace FourSlash {
18161824 }
18171825 }
18181826 runningOffset += editDelta ;
1819- // TODO: Consider doing this at least some of the time for higher fidelity. Currently causes a failure (bug 707150)
1820- // this.languageService.getScriptLexicalStructure(fileName);
1827+
1828+ // Update positions of any future edits affected by this change
1829+ for ( let j = i + 1 ; j < edits . length ; j ++ ) {
1830+ if ( edits [ j ] . span . start >= edits [ i ] . span . start ) {
1831+ edits [ j ] . span . start += editDelta ;
1832+ }
1833+ }
18211834 }
18221835
18231836 if ( isFormattingEdit ) {
@@ -1901,7 +1914,7 @@ namespace FourSlash {
19011914 this . goToPosition ( len ) ;
19021915 }
19031916
1904- public goToRangeStart ( { fileName, start} : Range ) {
1917+ public goToRangeStart ( { fileName, start } : Range ) {
19051918 this . openFile ( fileName ) ;
19061919 this . goToPosition ( start ) ;
19071920 }
@@ -2075,7 +2088,7 @@ namespace FourSlash {
20752088 return result ;
20762089 }
20772090
2078- private rangeText ( { fileName, start, end} : Range ) : string {
2091+ private rangeText ( { fileName, start, end } : Range ) : string {
20792092 return this . getFileContent ( fileName ) . slice ( start , end ) ;
20802093 }
20812094
@@ -2361,7 +2374,7 @@ namespace FourSlash {
23612374 private applyCodeActions ( actions : ts . CodeAction [ ] , index ?: number ) : void {
23622375 if ( index === undefined ) {
23632376 if ( ! ( actions && actions . length === 1 ) ) {
2364- this . raiseError ( `Should find exactly one codefix, but ${ actions ? actions . length : "none" } found. ${ actions ? actions . map ( a => `${ Harness . IO . newLine ( ) } "${ a . description } "` ) : "" } ` ) ;
2377+ this . raiseError ( `Should find exactly one codefix, but ${ actions ? actions . length : "none" } found. ${ actions ? actions . map ( a => `${ Harness . IO . newLine ( ) } "${ a . description } "` ) : "" } ` ) ;
23652378 }
23662379 index = 0 ;
23672380 }
@@ -2736,6 +2749,30 @@ namespace FourSlash {
27362749 }
27372750 }
27382751
2752+ private getSelection ( ) {
2753+ return ( {
2754+ pos : this . currentCaretPosition ,
2755+ end : this . selectionEnd === - 1 ? this . currentCaretPosition : this . selectionEnd
2756+ } ) ;
2757+ }
2758+
2759+ public verifyRefactorAvailable ( negative : boolean , name ?: string , subName ?: string ) {
2760+ const selection = this . getSelection ( ) ;
2761+
2762+ let refactors = this . languageService . getApplicableRefactors ( this . activeFile . fileName , selection ) || [ ] ;
2763+ if ( name ) {
2764+ refactors = refactors . filter ( r => r . name === name && ( subName === undefined || r . actions . some ( a => a . name === subName ) ) ) ;
2765+ }
2766+ const isAvailable = refactors . length > 0 ;
2767+
2768+ if ( negative && isAvailable ) {
2769+ this . raiseError ( `verifyApplicableRefactorAvailableForRange failed - expected no refactor but found some.` ) ;
2770+ }
2771+ else if ( ! negative && ! isAvailable ) {
2772+ this . raiseError ( `verifyApplicableRefactorAvailableForRange failed - expected a refactor but found none.` ) ;
2773+ }
2774+ }
2775+
27392776 public verifyApplicableRefactorAvailableForRange ( negative : boolean ) {
27402777 const ranges = this . getRanges ( ) ;
27412778 if ( ! ( ranges && ranges . length === 1 ) ) {
@@ -2752,6 +2789,20 @@ namespace FourSlash {
27522789 }
27532790 }
27542791
2792+ public applyRefactor ( refactorName : string , actionName : string ) {
2793+ const range = this . getSelection ( ) ;
2794+ const refactors = this . languageService . getApplicableRefactors ( this . activeFile . fileName , range ) ;
2795+ const refactor = ts . find ( refactors , r => r . name === refactorName ) ;
2796+ if ( ! refactor ) {
2797+ this . raiseError ( `The expected refactor: ${ refactorName } is not available at the marker location.` ) ;
2798+ }
2799+
2800+ const editInfo = this . languageService . getEditsForRefactor ( this . activeFile . fileName , this . formatCodeSettings , range , refactorName , actionName ) ;
2801+ for ( const edit of editInfo . edits ) {
2802+ this . applyEdits ( edit . fileName , edit . textChanges , /*isFormattingEdit*/ false ) ;
2803+ }
2804+ }
2805+
27552806 public verifyFileAfterApplyingRefactorAtMarker (
27562807 markerName : string ,
27572808 expectedContent : string ,
@@ -3496,6 +3547,10 @@ namespace FourSlashInterface {
34963547 public file ( indexOrName : any , content ?: string , scriptKindName ?: string ) : void {
34973548 this . state . openFile ( indexOrName , content , scriptKindName ) ;
34983549 }
3550+
3551+ public select ( startMarker : string , endMarker : string ) {
3552+ this . state . select ( startMarker , endMarker ) ;
3553+ }
34993554 }
35003555
35013556 export class VerifyNegatable {
@@ -3617,6 +3672,10 @@ namespace FourSlashInterface {
36173672 public applicableRefactorAvailableForRange ( ) {
36183673 this . state . verifyApplicableRefactorAvailableForRange ( this . negative ) ;
36193674 }
3675+
3676+ public refactorAvailable ( name ?: string , subName ?: string ) {
3677+ this . state . verifyRefactorAvailable ( this . negative , name , subName ) ;
3678+ }
36203679 }
36213680
36223681 export class Verify extends VerifyNegatable {
@@ -4012,6 +4071,10 @@ namespace FourSlashInterface {
40124071 public disableFormatting ( ) {
40134072 this . state . enableFormatting = false ;
40144073 }
4074+
4075+ public applyRefactor ( refactorName : string , actionName : string ) {
4076+ this . state . applyRefactor ( refactorName , actionName ) ;
4077+ }
40154078 }
40164079
40174080 export class Debug {
0 commit comments