@@ -15,130 +15,94 @@ export type Edit = {
1515export type ChangeType = 'added' | 'modified' | 'deleted' ;
1616
1717export type DiffInfo = {
18- changeType : ChangeType ;
19- oldLineNumbers ?: number [ ] ;
20- ghostAnchorLine ?: number ;
21- hunkId : number ;
18+ changeType : ChangeType ;
19+ oldLineNumbers ?: number [ ] ;
20+ ghostAnchorLine ?: number ;
21+ hunkId : number ;
2222} ;
2323
2424export function computeGitChanges (
25- original : string , current : string
25+ original : string , current : string
2626) : Map < number , DiffInfo > {
27- const changes = new Map < number , DiffInfo > ( ) ;
28- const currentLineCount = current === '' ? 1 : current . split ( '\n' ) . length ;
29- const patch = JsDiff . createTwoFilesPatch (
30- 'a' , 'b' , original , current , '' , '' , { context : 0 }
31- ) ;
32-
33- const lines = patch . split ( '\n' ) ;
34- let hunkId = 0 ;
35- let lastChangeWasConsecutive = false ;
36-
37- for ( let i = 0 ; i < lines . length ; i ++ ) {
38- const line = lines [ i ] ;
39-
40- if ( line . startsWith ( '@@ -' ) ) {
41- const headerMatch = line . match ( / \+ ( \d + ) (?: , ( \d + ) ) ? / ) ;
42- if ( headerMatch ) {
43- const oldHeaderMatch = line . match ( / @ @ - ( \d + ) (?: , ( \d + ) ) ? / ) ;
44- let newLine = parseInt ( headerMatch [ 1 ] , 10 ) ;
45- let oldLine = oldHeaderMatch ? parseInt ( oldHeaderMatch [ 1 ] , 10 ) : 1 ;
46- i ++ ;
47- lastChangeWasConsecutive = false ;
48-
49- let iterations = 0 ;
50- while ( i < lines . length && ! lines [ i ] . startsWith ( '@@' ) ) {
51- iterations ++ ;
52- if ( iterations > 1000 ) {
53- console . error ( 'INFINITE LOOP DETECTED at i =' , i , 'line:' , lines [ i ] ) ;
54- break ;
55- }
56-
57- const currentLine = lines [ i ] ;
58-
59- if ( currentLine . startsWith ( '\\' ) ) {
60- i ++ ;
61- continue ;
62- }
63-
64- if ( currentLine . startsWith ( '-' ) || currentLine . startsWith ( '+' ) ) {
65- const deletedLineNumbers : number [ ] = [ ] ;
66- const addedLineNumbers : number [ ] = [ ] ;
67-
68- while ( i < lines . length && lines [ i ] . startsWith ( '-' ) ) {
69- deletedLineNumbers . push ( oldLine ) ;
70- oldLine ++ ;
71- i ++ ;
72- }
73-
74- if ( i < lines . length && lines [ i ] . startsWith ( '\\' ) ) {
75- i ++ ;
76- }
77-
78- while ( i < lines . length && lines [ i ] . startsWith ( '+' ) ) {
79- addedLineNumbers . push ( newLine ) ;
80- newLine ++ ;
81- i ++ ;
82- }
83-
84- if ( i < lines . length && lines [ i ] . startsWith ( '\\' ) ) {
85- i ++ ;
86- }
87-
88- if ( deletedLineNumbers . length > 0 && addedLineNumbers . length > 0 ) {
89- for ( const lineNum of addedLineNumbers ) {
90- changes . set ( lineNum , {
91- changeType : 'modified' ,
92- oldLineNumbers : deletedLineNumbers ,
93- hunkId : hunkId ,
94- } ) ;
95- }
96- } else if ( addedLineNumbers . length > 0 ) {
97- // added
98- for ( const lineNum of addedLineNumbers ) {
99- changes . set ( lineNum , {
100- changeType : 'added' ,
101- hunkId : hunkId ,
102- } ) ;
103- }
104- } else if ( deletedLineNumbers . length > 0 ) {
105- // deleted
106- // JsDiff can emit +0 for deletions before the first line.
107- // ghostAnchorLine is the line BEFORE which ghost lines appear.
108- // markerLine is the line where the deletion marker appears in gutter
109- // (aligned with the ghost anchor line in our renderer model).
110- const ghostAnchorLine = Math . max ( 1 , newLine + 1 ) ;
111- const markerLine = Math . max ( 1 , Math . min ( ghostAnchorLine , currentLineCount ) ) ;
112- changes . set ( markerLine , {
113- changeType : 'deleted' ,
114- oldLineNumbers : deletedLineNumbers ,
115- ghostAnchorLine,
116- hunkId : hunkId ,
117- } ) ;
27+ const changes = new Map < number , DiffInfo > ( ) ;
28+ const diffs = JsDiff . diffLines ( original , current ) ;
29+ const currentLineCount = current === '' ? 1 : current . split ( '\n' ) . length ;
30+
31+ let oldLine = 1 ;
32+ let newLine = 1 ;
33+ let hunkId = 0 ;
34+ let inChangeBlock = false ;
35+
36+ const countLines = ( value : string ) : number => {
37+ if ( value === '' ) {
38+ return 0 ;
39+ }
40+ const parts = value . split ( '\n' ) ;
41+ return value . endsWith ( '\n' ) ? parts . length - 1 : parts . length ;
42+ } ;
43+
44+ for ( let i = 0 ; i < diffs . length ; i ++ ) {
45+ const diff = diffs [ i ] ;
46+ const { added, removed } = diff ;
47+ const count = countLines ( diff . value ) ;
48+
49+ if ( added || removed ) {
50+ inChangeBlock = true ;
51+
52+ if ( removed ) {
53+ const deletedLineNumbers : number [ ] = [ ] ;
54+ for ( let j = 0 ; j < count ; j ++ ) {
55+ deletedLineNumbers . push ( oldLine + j ) ;
56+ }
57+ oldLine += count ;
58+
59+ const next = diffs [ i + 1 ] ;
60+ if ( next ?. added ) {
61+ const addedCount = countLines ( next . value ) ;
62+ for ( let j = 0 ; j < addedCount ; j ++ ) {
63+ changes . set ( newLine + j , {
64+ changeType : 'modified' ,
65+ oldLineNumbers : deletedLineNumbers ,
66+ hunkId : hunkId ,
67+ } ) ;
68+ }
69+ newLine += addedCount ;
70+ i ++ ; // skip the added block
71+ } else {
72+ const ghostAnchorLine = Math . max ( 1 , newLine ) ;
73+ const markerLine = Math . max ( 1 , Math . min ( ghostAnchorLine , currentLineCount ) ) ;
74+
75+ changes . set ( markerLine , {
76+ changeType : 'deleted' ,
77+ oldLineNumbers : deletedLineNumbers ,
78+ ghostAnchorLine,
79+ hunkId : hunkId ,
80+ } ) ;
81+ }
82+ } else {
83+ // Pure addition
84+ for ( let j = 0 ; j < count ; j ++ ) {
85+ changes . set ( newLine + j , {
86+ changeType : 'added' ,
87+ hunkId : hunkId ,
88+ } ) ;
89+ }
90+ newLine += count ;
11891 }
119-
120- lastChangeWasConsecutive = true ;
121- continue ;
122- } else if ( currentLine . startsWith ( ' ' ) ) {
123- if ( lastChangeWasConsecutive ) {
124- hunkId ++ ;
125- lastChangeWasConsecutive = false ;
92+ } else {
93+ // Context lines (unchanged)
94+ if ( inChangeBlock ) {
95+ hunkId ++ ;
96+ inChangeBlock = false ;
12697 }
127- oldLine ++ ;
128- newLine ++ ;
129- i ++ ;
130- } else {
131- i ++ ;
132- }
133- }
134- // At the end of a @@ hunk, reset for next hunk
135- if ( lastChangeWasConsecutive ) {
136- hunkId ++ ;
98+ oldLine += count ;
99+ newLine += count ;
137100 }
138- i -- ;
139- }
140101 }
141- }
142102
143- return changes ;
103+ if ( inChangeBlock ) {
104+ hunkId ++ ;
105+ }
106+
107+ return changes ;
144108}
0 commit comments