@@ -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
@@ -967,9 +978,9 @@ namespace FourSlash {
967
978
}
968
979
969
980
for ( const reference of expectedReferences ) {
970
- const { fileName, start, end} = reference ;
981
+ const { fileName, start, end } = reference ;
971
982
if ( reference . marker && reference . marker . data ) {
972
- const { isWriteAccess, isDefinition} = reference . marker . data ;
983
+ const { isWriteAccess, isDefinition } = reference . marker . data ;
973
984
this . verifyReferencesWorker ( actualReferences , fileName , start , end , isWriteAccess , isDefinition ) ;
974
985
}
975
986
else {
@@ -1180,7 +1191,7 @@ namespace FourSlash {
1180
1191
displayParts : ts . SymbolDisplayPart [ ] ,
1181
1192
documentation : ts . SymbolDisplayPart [ ] ,
1182
1193
tags : ts . JSDocTagInfo [ ]
1183
- ) {
1194
+ ) {
1184
1195
1185
1196
const actualQuickInfo = this . languageService . getQuickInfoAtPosition ( this . activeFile . fileName , this . currentCaretPosition ) ;
1186
1197
assert . equal ( actualQuickInfo . kind , kind , this . messageAtLastKnownMarker ( "QuickInfo kind" ) ) ;
@@ -1776,19 +1787,16 @@ namespace FourSlash {
1776
1787
// We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track
1777
1788
// of the incremental offset from each edit to the next. We assume these edit ranges don't overlap
1778
1789
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 ) ) ;
1785
1792
1786
1793
// Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters
1787
1794
const oldContent = this . getFileContent ( fileName ) ;
1788
1795
let runningOffset = 0 ;
1789
1796
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 ;
1792
1800
const offsetEnd = offsetStart + edit . span . length ;
1793
1801
this . editScriptAndUpdateMarkers ( fileName , offsetStart , offsetEnd , edit . newText ) ;
1794
1802
const editDelta = edit . newText . length - edit . span . length ;
@@ -1803,8 +1811,13 @@ namespace FourSlash {
1803
1811
}
1804
1812
}
1805
1813
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
+ }
1808
1821
}
1809
1822
1810
1823
if ( isFormattingEdit ) {
@@ -1888,7 +1901,7 @@ namespace FourSlash {
1888
1901
this . goToPosition ( len ) ;
1889
1902
}
1890
1903
1891
- public goToRangeStart ( { fileName, start} : Range ) {
1904
+ public goToRangeStart ( { fileName, start } : Range ) {
1892
1905
this . openFile ( fileName ) ;
1893
1906
this . goToPosition ( start ) ;
1894
1907
}
@@ -2062,7 +2075,7 @@ namespace FourSlash {
2062
2075
return result ;
2063
2076
}
2064
2077
2065
- private rangeText ( { fileName, start, end} : Range ) : string {
2078
+ private rangeText ( { fileName, start, end } : Range ) : string {
2066
2079
return this . getFileContent ( fileName ) . slice ( start , end ) ;
2067
2080
}
2068
2081
@@ -2348,7 +2361,7 @@ namespace FourSlash {
2348
2361
private applyCodeActions ( actions : ts . CodeAction [ ] , index ?: number ) : void {
2349
2362
if ( index === undefined ) {
2350
2363
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 } "` ) : "" } ` ) ;
2352
2365
}
2353
2366
index = 0 ;
2354
2367
}
@@ -2723,6 +2736,30 @@ namespace FourSlash {
2723
2736
}
2724
2737
}
2725
2738
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
+
2726
2763
public verifyApplicableRefactorAvailableForRange ( negative : boolean ) {
2727
2764
const ranges = this . getRanges ( ) ;
2728
2765
if ( ! ( ranges && ranges . length === 1 ) ) {
@@ -2739,6 +2776,20 @@ namespace FourSlash {
2739
2776
}
2740
2777
}
2741
2778
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
+
2742
2793
public verifyFileAfterApplyingRefactorAtMarker (
2743
2794
markerName : string ,
2744
2795
expectedContent : string ,
@@ -3483,6 +3534,10 @@ namespace FourSlashInterface {
3483
3534
public file ( indexOrName : any , content ?: string , scriptKindName ?: string ) : void {
3484
3535
this . state . openFile ( indexOrName , content , scriptKindName ) ;
3485
3536
}
3537
+
3538
+ public select ( startMarker : string , endMarker : string ) {
3539
+ this . state . select ( startMarker , endMarker ) ;
3540
+ }
3486
3541
}
3487
3542
3488
3543
export class VerifyNegatable {
@@ -3604,6 +3659,10 @@ namespace FourSlashInterface {
3604
3659
public applicableRefactorAvailableForRange ( ) {
3605
3660
this . state . verifyApplicableRefactorAvailableForRange ( this . negative ) ;
3606
3661
}
3662
+
3663
+ public refactorAvailable ( name ?: string , subName ?: string ) {
3664
+ this . state . verifyRefactorAvailable ( this . negative , name , subName ) ;
3665
+ }
3607
3666
}
3608
3667
3609
3668
export class Verify extends VerifyNegatable {
@@ -3999,6 +4058,10 @@ namespace FourSlashInterface {
3999
4058
public disableFormatting ( ) {
4000
4059
this . state . enableFormatting = false ;
4001
4060
}
4061
+
4062
+ public applyRefactor ( refactorName : string , actionName : string ) {
4063
+ this . state . applyRefactor ( refactorName , actionName ) ;
4064
+ }
4002
4065
}
4003
4066
4004
4067
export class Debug {
0 commit comments