@@ -187,6 +187,9 @@ namespace FourSlash {
187
187
188
188
// The current caret position in the active file
189
189
public currentCaretPosition = 0 ;
190
+ // The position of the end of the current selection, or -1 if nothing is selected
191
+ public selectionEnd = - 1 ;
192
+
190
193
public lastKnownMarker = "" ;
191
194
192
195
// The file that's currently 'opened'
@@ -433,11 +436,19 @@ namespace FourSlash {
433
436
434
437
public goToPosition ( pos : number ) {
435
438
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 ;
436
446
}
437
447
438
448
public moveCaretRight ( count = 1 ) {
439
449
this . currentCaretPosition += count ;
440
450
this . currentCaretPosition = Math . min ( this . currentCaretPosition , this . getFileContent ( this . activeFile . fileName ) . length ) ;
451
+ this . selectionEnd = - 1 ;
441
452
}
442
453
443
454
// Opens a file given its 0-based index or fileName
@@ -980,9 +991,9 @@ namespace FourSlash {
980
991
}
981
992
982
993
for ( const reference of expectedReferences ) {
983
- const { fileName, start, end} = reference ;
994
+ const { fileName, start, end } = reference ;
984
995
if ( reference . marker && reference . marker . data ) {
985
- const { isWriteAccess, isDefinition} = reference . marker . data ;
996
+ const { isWriteAccess, isDefinition } = reference . marker . data ;
986
997
this . verifyReferencesWorker ( actualReferences , fileName , start , end , isWriteAccess , isDefinition ) ;
987
998
}
988
999
else {
@@ -1193,7 +1204,7 @@ namespace FourSlash {
1193
1204
displayParts : ts . SymbolDisplayPart [ ] ,
1194
1205
documentation : ts . SymbolDisplayPart [ ] ,
1195
1206
tags : ts . JSDocTagInfo [ ]
1196
- ) {
1207
+ ) {
1197
1208
1198
1209
const actualQuickInfo = this . languageService . getQuickInfoAtPosition ( this . activeFile . fileName , this . currentCaretPosition ) ;
1199
1210
assert . equal ( actualQuickInfo . kind , kind , this . messageAtLastKnownMarker ( "QuickInfo kind" ) ) ;
@@ -1789,19 +1800,16 @@ namespace FourSlash {
1789
1800
// We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track
1790
1801
// of the incremental offset from each edit to the next. We assume these edit ranges don't overlap
1791
1802
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 ) ) ;
1798
1805
1799
1806
// Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters
1800
1807
const oldContent = this . getFileContent ( fileName ) ;
1801
1808
let runningOffset = 0 ;
1802
1809
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 ;
1805
1813
const offsetEnd = offsetStart + edit . span . length ;
1806
1814
this . editScriptAndUpdateMarkers ( fileName , offsetStart , offsetEnd , edit . newText ) ;
1807
1815
const editDelta = edit . newText . length - edit . span . length ;
@@ -1816,8 +1824,13 @@ namespace FourSlash {
1816
1824
}
1817
1825
}
1818
1826
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
+ }
1821
1834
}
1822
1835
1823
1836
if ( isFormattingEdit ) {
@@ -1901,7 +1914,7 @@ namespace FourSlash {
1901
1914
this . goToPosition ( len ) ;
1902
1915
}
1903
1916
1904
- public goToRangeStart ( { fileName, start} : Range ) {
1917
+ public goToRangeStart ( { fileName, start } : Range ) {
1905
1918
this . openFile ( fileName ) ;
1906
1919
this . goToPosition ( start ) ;
1907
1920
}
@@ -2075,7 +2088,7 @@ namespace FourSlash {
2075
2088
return result ;
2076
2089
}
2077
2090
2078
- private rangeText ( { fileName, start, end} : Range ) : string {
2091
+ private rangeText ( { fileName, start, end } : Range ) : string {
2079
2092
return this . getFileContent ( fileName ) . slice ( start , end ) ;
2080
2093
}
2081
2094
@@ -2361,7 +2374,7 @@ namespace FourSlash {
2361
2374
private applyCodeActions ( actions : ts . CodeAction [ ] , index ?: number ) : void {
2362
2375
if ( index === undefined ) {
2363
2376
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 } "` ) : "" } ` ) ;
2365
2378
}
2366
2379
index = 0 ;
2367
2380
}
@@ -2736,6 +2749,30 @@ namespace FourSlash {
2736
2749
}
2737
2750
}
2738
2751
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
+
2739
2776
public verifyApplicableRefactorAvailableForRange ( negative : boolean ) {
2740
2777
const ranges = this . getRanges ( ) ;
2741
2778
if ( ! ( ranges && ranges . length === 1 ) ) {
@@ -2752,6 +2789,20 @@ namespace FourSlash {
2752
2789
}
2753
2790
}
2754
2791
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
+
2755
2806
public verifyFileAfterApplyingRefactorAtMarker (
2756
2807
markerName : string ,
2757
2808
expectedContent : string ,
@@ -3496,6 +3547,10 @@ namespace FourSlashInterface {
3496
3547
public file ( indexOrName : any , content ?: string , scriptKindName ?: string ) : void {
3497
3548
this . state . openFile ( indexOrName , content , scriptKindName ) ;
3498
3549
}
3550
+
3551
+ public select ( startMarker : string , endMarker : string ) {
3552
+ this . state . select ( startMarker , endMarker ) ;
3553
+ }
3499
3554
}
3500
3555
3501
3556
export class VerifyNegatable {
@@ -3617,6 +3672,10 @@ namespace FourSlashInterface {
3617
3672
public applicableRefactorAvailableForRange ( ) {
3618
3673
this . state . verifyApplicableRefactorAvailableForRange ( this . negative ) ;
3619
3674
}
3675
+
3676
+ public refactorAvailable ( name ?: string , subName ?: string ) {
3677
+ this . state . verifyRefactorAvailable ( this . negative , name , subName ) ;
3678
+ }
3620
3679
}
3621
3680
3622
3681
export class Verify extends VerifyNegatable {
@@ -4012,6 +4071,10 @@ namespace FourSlashInterface {
4012
4071
public disableFormatting ( ) {
4013
4072
this . state . enableFormatting = false ;
4014
4073
}
4074
+
4075
+ public applyRefactor ( refactorName : string , actionName : string ) {
4076
+ this . state . applyRefactor ( refactorName , actionName ) ;
4077
+ }
4015
4078
}
4016
4079
4017
4080
export class Debug {
0 commit comments