@@ -1584,7 +1584,7 @@ namespace FourSlash {
15841584 }
15851585 }
15861586
1587- public printCurrentFileState ( makeWhitespaceVisible = false , makeCaretVisible = true ) {
1587+ public printCurrentFileState ( makeWhitespaceVisible : boolean , makeCaretVisible : boolean ) {
15881588 for ( const file of this . testData . files ) {
15891589 const active = ( this . activeFile === file ) ;
15901590 Harness . IO . log ( `=== Script (${ file . fileName } ) ${ ( active ? "(active, cursor at |)" : "" ) } ===` ) ;
@@ -1637,9 +1637,7 @@ namespace FourSlash {
16371637 const checkCadence = ( count >> 2 ) + 1 ;
16381638
16391639 for ( let i = 0 ; i < count ; i ++ ) {
1640- // Make the edit
1641- this . languageServiceAdapterHost . editScript ( this . activeFile . fileName , offset , offset + 1 , ch ) ;
1642- this . updateMarkersForEdit ( this . activeFile . fileName , offset , offset + 1 , ch ) ;
1640+ this . editScriptAndUpdateMarkers ( this . activeFile . fileName , offset , offset + 1 , ch ) ;
16431641
16441642 if ( i % checkCadence === 0 ) {
16451643 this . checkPostEditInvariants ( ) ;
@@ -1650,21 +1648,15 @@ namespace FourSlash {
16501648 const edits = this . languageService . getFormattingEditsAfterKeystroke ( this . activeFile . fileName , offset , ch , this . formatCodeSettings ) ;
16511649 if ( edits . length ) {
16521650 offset += this . applyEdits ( this . activeFile . fileName , edits , /*isFormattingEdit*/ true ) ;
1653- // this.checkPostEditInvariants();
16541651 }
16551652 }
16561653 }
16571654
1658- // Move the caret to wherever we ended up
1659- this . currentCaretPosition = offset ;
1660-
1661- this . fixCaretPosition ( ) ;
16621655 this . checkPostEditInvariants ( ) ;
16631656 }
16641657
16651658 public replace ( start : number , length : number , text : string ) {
1666- this . languageServiceAdapterHost . editScript ( this . activeFile . fileName , start , start + length , text ) ;
1667- this . updateMarkersForEdit ( this . activeFile . fileName , start , start + length , text ) ;
1659+ this . editScriptAndUpdateMarkers ( this . activeFile . fileName , start , start + length , text ) ;
16681660 this . checkPostEditInvariants ( ) ;
16691661 }
16701662
@@ -1674,28 +1666,17 @@ namespace FourSlash {
16741666 const checkCadence = ( count >> 2 ) + 1 ;
16751667
16761668 for ( let i = 0 ; i < count ; i ++ ) {
1669+ this . currentCaretPosition -- ;
16771670 offset -- ;
1678- // Make the edit
1679- this . languageServiceAdapterHost . editScript ( this . activeFile . fileName , offset , offset + 1 , ch ) ;
1680- this . updateMarkersForEdit ( this . activeFile . fileName , offset , offset + 1 , ch ) ;
1671+ this . editScriptAndUpdateMarkers ( this . activeFile . fileName , offset , offset + 1 , ch ) ;
16811672
16821673 if ( i % checkCadence === 0 ) {
16831674 this . checkPostEditInvariants ( ) ;
16841675 }
16851676
1686- // Handle post-keystroke formatting
1687- if ( this . enableFormatting ) {
1688- const edits = this . languageService . getFormattingEditsAfterKeystroke ( this . activeFile . fileName , offset , ch , this . formatCodeSettings ) ;
1689- if ( edits . length ) {
1690- offset += this . applyEdits ( this . activeFile . fileName , edits , /*isFormattingEdit*/ true ) ;
1691- }
1692- }
1677+ // Don't need to examine formatting because there are no formatting changes on backspace.
16931678 }
16941679
1695- // Move the caret to wherever we ended up
1696- this . currentCaretPosition = offset ;
1697-
1698- this . fixCaretPosition ( ) ;
16991680 this . checkPostEditInvariants ( ) ;
17001681 }
17011682
@@ -1706,14 +1687,13 @@ namespace FourSlash {
17061687 const checkCadence = ( text . length >> 2 ) + 1 ;
17071688
17081689 for ( let i = 0 ; i < text . length ; i ++ ) {
1709- // Make the edit
17101690 const ch = text . charAt ( i ) ;
1711- this . languageServiceAdapterHost . editScript ( this . activeFile . fileName , offset , offset , ch ) ;
1691+ this . editScriptAndUpdateMarkers ( this . activeFile . fileName , offset , offset , ch ) ;
17121692 if ( highFidelity ) {
17131693 this . languageService . getBraceMatchingAtPosition ( this . activeFile . fileName , offset ) ;
17141694 }
17151695
1716- this . updateMarkersForEdit ( this . activeFile . fileName , offset , offset , ch ) ;
1696+ this . currentCaretPosition ++ ;
17171697 offset ++ ;
17181698
17191699 if ( highFidelity ) {
@@ -1740,32 +1720,24 @@ namespace FourSlash {
17401720 }
17411721 }
17421722
1743- // Move the caret to wherever we ended up
1744- this . currentCaretPosition = offset ;
1745- this . fixCaretPosition ( ) ;
17461723 this . checkPostEditInvariants ( ) ;
17471724 }
17481725
17491726 // Enters text as if the user had pasted it
17501727 public paste ( text : string ) {
17511728 const start = this . currentCaretPosition ;
1752- let offset = this . currentCaretPosition ;
1753- this . languageServiceAdapterHost . editScript ( this . activeFile . fileName , offset , offset , text ) ;
1754- this . updateMarkersForEdit ( this . activeFile . fileName , offset , offset , text ) ;
1729+ this . editScriptAndUpdateMarkers ( this . activeFile . fileName , this . currentCaretPosition , this . currentCaretPosition , text ) ;
17551730 this . checkPostEditInvariants ( ) ;
1756- offset += text . length ;
1731+ const offset = this . currentCaretPosition += text . length ;
17571732
17581733 // Handle formatting
17591734 if ( this . enableFormatting ) {
17601735 const edits = this . languageService . getFormattingEditsForRange ( this . activeFile . fileName , start , offset , this . formatCodeSettings ) ;
17611736 if ( edits . length ) {
1762- offset += this . applyEdits ( this . activeFile . fileName , edits , /*isFormattingEdit*/ true ) ;
1737+ this . applyEdits ( this . activeFile . fileName , edits , /*isFormattingEdit*/ true ) ;
17631738 }
17641739 }
17651740
1766- // Move the caret to wherever we ended up
1767- this . currentCaretPosition = offset ;
1768- this . fixCaretPosition ( ) ;
17691741
17701742 this . checkPostEditInvariants ( ) ;
17711743 }
@@ -1793,30 +1765,34 @@ namespace FourSlash {
17931765 Utils . assertStructuralEquals ( incrementalSourceFile , referenceSourceFile ) ;
17941766 }
17951767
1796- private fixCaretPosition ( ) {
1797- // The caret can potentially end up between the \r and \n, which is confusing. If
1798- // that happens, move it back one character
1799- if ( this . currentCaretPosition > 0 ) {
1800- const ch = this . getFileContent ( this . activeFile . fileName ) . substring ( this . currentCaretPosition - 1 , this . currentCaretPosition ) ;
1801- if ( ch === "\r" ) {
1802- this . currentCaretPosition -- ;
1803- }
1804- }
1805- }
1806-
1807- private applyEdits ( fileName : string , edits : ts . TextChange [ ] , isFormattingEdit = false ) : number {
1768+ /**
1769+ * @returns The number of characters added to the file as a result of the edits.
1770+ * May be negative.
1771+ */
1772+ private applyEdits ( fileName : string , edits : ts . TextChange [ ] , isFormattingEdit : boolean ) : number {
18081773 // We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track
1809- // of the incremental offset from each edit to the next. Assumption is that these edit ranges don't overlap
1810- let runningOffset = 0 ;
1774+ // of the incremental offset from each edit to the next. We assume these edit ranges don't overlap
1775+
18111776 edits = edits . sort ( ( a , b ) => a . span . start - b . span . start ) ;
1777+ for ( let i = 0 ; i < edits . length - 1 ; i ++ ) {
1778+ const firstEditSpan = edits [ i ] . span ;
1779+ const firstEditEnd = firstEditSpan . start + firstEditSpan . length ;
1780+ assert . isTrue ( firstEditEnd <= edits [ i + 1 ] . span . start ) ;
1781+ }
1782+
18121783 // Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters
18131784 const oldContent = this . getFileContent ( fileName ) ;
1785+ let runningOffset = 0 ;
18141786
18151787 for ( const edit of edits ) {
1816- this . languageServiceAdapterHost . editScript ( fileName , edit . span . start + runningOffset , ts . textSpanEnd ( edit . span ) + runningOffset , edit . newText ) ;
1817- this . updateMarkersForEdit ( fileName , edit . span . start + runningOffset , ts . textSpanEnd ( edit . span ) + runningOffset , edit . newText ) ;
1818- const change = ( edit . span . start - ts . textSpanEnd ( edit . span ) ) + edit . newText . length ;
1819- runningOffset += change ;
1788+ const offsetStart = edit . span . start + runningOffset ;
1789+ const offsetEnd = offsetStart + edit . span . length ;
1790+ this . editScriptAndUpdateMarkers ( fileName , offsetStart , offsetEnd , edit . newText ) ;
1791+ const editDelta = edit . newText . length - edit . span . length ;
1792+ if ( offsetStart <= this . currentCaretPosition ) {
1793+ this . currentCaretPosition += editDelta ;
1794+ }
1795+ runningOffset += editDelta ;
18201796 // TODO: Consider doing this at least some of the time for higher fidelity. Currently causes a failure (bug 707150)
18211797 // this.languageService.getScriptLexicalStructure(fileName);
18221798 }
@@ -1828,6 +1804,7 @@ namespace FourSlash {
18281804 this . raiseError ( "Formatting operation destroyed non-whitespace content" ) ;
18291805 }
18301806 }
1807+
18311808 return runningOffset ;
18321809 }
18331810
@@ -1843,23 +1820,21 @@ namespace FourSlash {
18431820
18441821 public formatDocument ( ) {
18451822 const edits = this . languageService . getFormattingEditsForDocument ( this . activeFile . fileName , this . formatCodeSettings ) ;
1846- this . currentCaretPosition += this . applyEdits ( this . activeFile . fileName , edits , /*isFormattingEdit*/ true ) ;
1847- this . fixCaretPosition ( ) ;
1823+ this . applyEdits ( this . activeFile . fileName , edits , /*isFormattingEdit*/ true ) ;
18481824 }
18491825
18501826 public formatSelection ( start : number , end : number ) {
18511827 const edits = this . languageService . getFormattingEditsForRange ( this . activeFile . fileName , start , end , this . formatCodeSettings ) ;
1852- this . currentCaretPosition += this . applyEdits ( this . activeFile . fileName , edits , /*isFormattingEdit*/ true ) ;
1853- this . fixCaretPosition ( ) ;
1828+ this . applyEdits ( this . activeFile . fileName , edits , /*isFormattingEdit*/ true ) ;
18541829 }
18551830
18561831 public formatOnType ( pos : number , key : string ) {
18571832 const edits = this . languageService . getFormattingEditsAfterKeystroke ( this . activeFile . fileName , pos , key , this . formatCodeSettings ) ;
1858- this . currentCaretPosition += this . applyEdits ( this . activeFile . fileName , edits , /*isFormattingEdit*/ true ) ;
1859- this . fixCaretPosition ( ) ;
1833+ this . applyEdits ( this . activeFile . fileName , edits , /*isFormattingEdit*/ true ) ;
18601834 }
18611835
1862- private updateMarkersForEdit ( fileName : string , minChar : number , limChar : number , text : string ) {
1836+ private editScriptAndUpdateMarkers ( fileName : string , editStart : number , editEnd : number , newText : string ) {
1837+ this . languageServiceAdapterHost . editScript ( fileName , editStart , editEnd , newText ) ;
18631838 for ( const marker of this . testData . markers ) {
18641839 if ( marker . fileName === fileName ) {
18651840 marker . position = updatePosition ( marker . position ) ;
@@ -1874,14 +1849,14 @@ namespace FourSlash {
18741849 }
18751850
18761851 function updatePosition ( position : number ) {
1877- if ( position > minChar ) {
1878- if ( position < limChar ) {
1852+ if ( position > editStart ) {
1853+ if ( position < editEnd ) {
18791854 // Inside the edit - mark it as invalidated (?)
18801855 return - 1 ;
18811856 }
18821857 else {
18831858 // Move marker back/forward by the appropriate amount
1884- return position + ( minChar - limChar ) + text . length ;
1859+ return position + ( editStart - editEnd ) + newText . length ;
18851860 }
18861861 }
18871862 else {
@@ -2127,8 +2102,8 @@ namespace FourSlash {
21272102 const actual = this . getFileContent ( this . activeFile . fileName ) ;
21282103 if ( normalizeNewLines ( actual ) !== normalizeNewLines ( text ) ) {
21292104 throw new Error ( "verifyCurrentFileContent\n" +
2130- "\tExpected: \"" + text + "\"\n" +
2131- "\t Actual: \"" + actual + "\"" ) ;
2105+ "\tExpected: \"" + TestState . makeWhitespaceVisible ( text ) + "\"\n" +
2106+ "\t Actual: \"" + TestState . makeWhitespaceVisible ( actual ) + "\"" ) ;
21322107 }
21332108 }
21342109
@@ -2784,7 +2759,7 @@ namespace FourSlash {
27842759 const editInfo = this . languageService . getEditsForRefactor ( this . activeFile . fileName , formattingOptions , markerPos , refactorNameToApply , actionName ) ;
27852760
27862761 for ( const edit of editInfo . edits ) {
2787- this . applyEdits ( edit . fileName , edit . textChanges ) ;
2762+ this . applyEdits ( edit . fileName , edit . textChanges , /*isFormattingEdit*/ false ) ;
27882763 }
27892764 const actualContent = this . getFileContent ( this . activeFile . fileName ) ;
27902765
@@ -2979,7 +2954,6 @@ ${code}
29792954 f ( test , goTo , verify , edit , debug , format , cancellation , FourSlashInterface . Classification , FourSlash . verifyOperationIsCancelled ) ;
29802955 }
29812956 catch ( err ) {
2982- // Debugging: FourSlash.currentTestState.printCurrentFileState();
29832957 throw err ;
29842958 }
29852959 }
@@ -4028,11 +4002,11 @@ namespace FourSlashInterface {
40284002 }
40294003
40304004 public printCurrentFileState ( ) {
4031- this . state . printCurrentFileState ( ) ;
4005+ this . state . printCurrentFileState ( /*makeWhitespaceVisible*/ false , /*makeCaretVisible*/ true ) ;
40324006 }
40334007
40344008 public printCurrentFileStateWithWhitespace ( ) {
4035- this . state . printCurrentFileState ( /*makeWhitespaceVisible*/ true ) ;
4009+ this . state . printCurrentFileState ( /*makeWhitespaceVisible*/ true , /*makeCaretVisible*/ true ) ;
40364010 }
40374011
40384012 public printCurrentFileStateWithoutCaret ( ) {
0 commit comments