@@ -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
@@ -967,9 +978,9 @@ namespace FourSlash {
967978 }
968979
969980 for ( const reference of expectedReferences ) {
970- const { fileName, start, end} = reference ;
981+ const { fileName, start, end } = reference ;
971982 if ( reference . marker && reference . marker . data ) {
972- const { isWriteAccess, isDefinition} = reference . marker . data ;
983+ const { isWriteAccess, isDefinition } = reference . marker . data ;
973984 this . verifyReferencesWorker ( actualReferences , fileName , start , end , isWriteAccess , isDefinition ) ;
974985 }
975986 else {
@@ -1180,7 +1191,7 @@ namespace FourSlash {
11801191 displayParts : ts . SymbolDisplayPart [ ] ,
11811192 documentation : ts . SymbolDisplayPart [ ] ,
11821193 tags : ts . JSDocTagInfo [ ]
1183- ) {
1194+ ) {
11841195
11851196 const actualQuickInfo = this . languageService . getQuickInfoAtPosition ( this . activeFile . fileName , this . currentCaretPosition ) ;
11861197 assert . equal ( actualQuickInfo . kind , kind , this . messageAtLastKnownMarker ( "QuickInfo kind" ) ) ;
@@ -1776,19 +1787,16 @@ namespace FourSlash {
17761787 // We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track
17771788 // of the incremental offset from each edit to the next. We assume these edit ranges don't overlap
17781789
1779- edits = edits . sort ( ( a , b ) => a . span . start - b . span . start ) ;
1780- for ( let i = 0 ; i < edits . length - 1 ; i ++ ) {
1781- const firstEditSpan = edits [ i ] . span ;
1782- const firstEditEnd = firstEditSpan . start + firstEditSpan . length ;
1783- assert . isTrue ( firstEditEnd <= edits [ i + 1 ] . span . start ) ;
1784- }
1790+ // Copy this so we don't ruin someone else's copy
1791+ edits = JSON . parse ( JSON . stringify ( edits ) ) ;
17851792
17861793 // Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters
17871794 const oldContent = this . getFileContent ( fileName ) ;
17881795 let runningOffset = 0 ;
17891796
1790- for ( const edit of edits ) {
1791- const offsetStart = edit . span . start + runningOffset ;
1797+ for ( let i = 0 ; i < edits . length ; i ++ ) {
1798+ const edit = edits [ i ] ;
1799+ const offsetStart = edit . span . start ;
17921800 const offsetEnd = offsetStart + edit . span . length ;
17931801 this . editScriptAndUpdateMarkers ( fileName , offsetStart , offsetEnd , edit . newText ) ;
17941802 const editDelta = edit . newText . length - edit . span . length ;
@@ -1803,8 +1811,13 @@ namespace FourSlash {
18031811 }
18041812 }
18051813 runningOffset += editDelta ;
1806- // TODO: Consider doing this at least some of the time for higher fidelity. Currently causes a failure (bug 707150)
1807- // this.languageService.getScriptLexicalStructure(fileName);
1814+
1815+ // Update positions of any future edits affected by this change
1816+ for ( let j = i + 1 ; j < edits . length ; j ++ ) {
1817+ if ( edits [ j ] . span . start >= edits [ i ] . span . start ) {
1818+ edits [ j ] . span . start += editDelta ;
1819+ }
1820+ }
18081821 }
18091822
18101823 if ( isFormattingEdit ) {
@@ -1888,7 +1901,7 @@ namespace FourSlash {
18881901 this . goToPosition ( len ) ;
18891902 }
18901903
1891- public goToRangeStart ( { fileName, start} : Range ) {
1904+ public goToRangeStart ( { fileName, start } : Range ) {
18921905 this . openFile ( fileName ) ;
18931906 this . goToPosition ( start ) ;
18941907 }
@@ -2062,7 +2075,7 @@ namespace FourSlash {
20622075 return result ;
20632076 }
20642077
2065- private rangeText ( { fileName, start, end} : Range ) : string {
2078+ private rangeText ( { fileName, start, end } : Range ) : string {
20662079 return this . getFileContent ( fileName ) . slice ( start , end ) ;
20672080 }
20682081
@@ -2348,7 +2361,7 @@ namespace FourSlash {
23482361 private applyCodeActions ( actions : ts . CodeAction [ ] , index ?: number ) : void {
23492362 if ( index === undefined ) {
23502363 if ( ! ( actions && actions . length === 1 ) ) {
2351- this . raiseError ( `Should find exactly one codefix, but ${ actions ? actions . length : "none" } found. ${ actions ? actions . map ( a => `${ Harness . IO . newLine ( ) } "${ a . description } "` ) : "" } ` ) ;
2364+ this . raiseError ( `Should find exactly one codefix, but ${ actions ? actions . length : "none" } found. ${ actions ? actions . map ( a => `${ Harness . IO . newLine ( ) } "${ a . description } "` ) : "" } ` ) ;
23522365 }
23532366 index = 0 ;
23542367 }
@@ -2723,6 +2736,30 @@ namespace FourSlash {
27232736 }
27242737 }
27252738
2739+ private getSelection ( ) {
2740+ return ( {
2741+ pos : this . currentCaretPosition ,
2742+ end : this . selectionEnd === - 1 ? this . currentCaretPosition : this . selectionEnd
2743+ } ) ;
2744+ }
2745+
2746+ public verifyRefactorAvailable ( negative : boolean , name ?: string , subName ?: string ) {
2747+ const selection = this . getSelection ( ) ;
2748+
2749+ let refactors = this . languageService . getApplicableRefactors ( this . activeFile . fileName , selection ) || [ ] ;
2750+ if ( name ) {
2751+ refactors = refactors . filter ( r => r . name === name && ( subName === undefined || r . actions . some ( a => a . name === subName ) ) ) ;
2752+ }
2753+ const isAvailable = refactors . length > 0 ;
2754+
2755+ if ( negative && isAvailable ) {
2756+ this . raiseError ( `verifyApplicableRefactorAvailableForRange failed - expected no refactor but found some: ${ refactors . map ( r => r . name ) . join ( ", " ) } ` ) ;
2757+ }
2758+ else if ( ! negative && ! isAvailable ) {
2759+ this . raiseError ( `verifyApplicableRefactorAvailableForRange failed - expected a refactor but found none.` ) ;
2760+ }
2761+ }
2762+
27262763 public verifyApplicableRefactorAvailableForRange ( negative : boolean ) {
27272764 const ranges = this . getRanges ( ) ;
27282765 if ( ! ( ranges && ranges . length === 1 ) ) {
@@ -2739,6 +2776,20 @@ namespace FourSlash {
27392776 }
27402777 }
27412778
2779+ public applyRefactor ( refactorName : string , actionName : string ) {
2780+ const range = this . getSelection ( ) ;
2781+ const refactors = this . languageService . getApplicableRefactors ( this . activeFile . fileName , range ) ;
2782+ const refactor = ts . find ( refactors , r => r . name === refactorName ) ;
2783+ if ( ! refactor ) {
2784+ this . raiseError ( `The expected refactor: ${ refactorName } is not available at the marker location.` ) ;
2785+ }
2786+
2787+ const editInfo = this . languageService . getEditsForRefactor ( this . activeFile . fileName , this . formatCodeSettings , range , refactorName , actionName ) ;
2788+ for ( const edit of editInfo . edits ) {
2789+ this . applyEdits ( edit . fileName , edit . textChanges , /*isFormattingEdit*/ false ) ;
2790+ }
2791+ }
2792+
27422793 public verifyFileAfterApplyingRefactorAtMarker (
27432794 markerName : string ,
27442795 expectedContent : string ,
@@ -3483,6 +3534,10 @@ namespace FourSlashInterface {
34833534 public file ( indexOrName : any , content ?: string , scriptKindName ?: string ) : void {
34843535 this . state . openFile ( indexOrName , content , scriptKindName ) ;
34853536 }
3537+
3538+ public select ( startMarker : string , endMarker : string ) {
3539+ this . state . select ( startMarker , endMarker ) ;
3540+ }
34863541 }
34873542
34883543 export class VerifyNegatable {
@@ -3604,6 +3659,10 @@ namespace FourSlashInterface {
36043659 public applicableRefactorAvailableForRange ( ) {
36053660 this . state . verifyApplicableRefactorAvailableForRange ( this . negative ) ;
36063661 }
3662+
3663+ public refactorAvailable ( name ?: string , subName ?: string ) {
3664+ this . state . verifyRefactorAvailable ( this . negative , name , subName ) ;
3665+ }
36073666 }
36083667
36093668 export class Verify extends VerifyNegatable {
@@ -3999,6 +4058,10 @@ namespace FourSlashInterface {
39994058 public disableFormatting ( ) {
40004059 this . state . enableFormatting = false ;
40014060 }
4061+
4062+ public applyRefactor ( refactorName : string , actionName : string ) {
4063+ this . state . applyRefactor ( refactorName , actionName ) ;
4064+ }
40024065 }
40034066
40044067 export class Debug {
0 commit comments