@@ -250,7 +250,49 @@ extension AsyncCodeBlock {
250250 _ highlightedCode: [ NSMutableAttributedString ] ,
251251 commonPrecedingSpaceCount: Int ,
252252 diffResult: CodeDiff . SnippetDiff
253- ) { }
253+ ) {
254+ for (index, mutableString) in highlightedCode. enumerated ( ) {
255+ guard let line = diffResult. line ( at: index, in: \. newSnippet) else { continue }
256+ guard case let . mutated( changes) = line. diff, !changes. isEmpty else { continue }
257+
258+ for change in changes {
259+ if change. offset == 0 ,
260+ change. element. count - commonPrecedingSpaceCount
261+ == mutableString. string. count
262+ {
263+ // ignore the whole line change
264+ continue
265+ }
266+
267+ let offset = change. offset - commonPrecedingSpaceCount
268+ let range = NSRange (
269+ location: max ( 0 , offset) ,
270+ length: max ( 0 , change. element. count + ( offset < 0 ? offset : 0 ) )
271+ )
272+ mutableString. addAttributes ( [
273+ . backgroundColor: NSColor . systemGreen. withAlphaComponent ( 0.2 ) ,
274+ ] , range: range)
275+ }
276+ }
277+
278+ let lastLineIndex = highlightedCode. endIndex - 1
279+ if lastLineIndex >= 0 {
280+ if let line = diffResult. line ( at: lastLineIndex, in: \. oldSnippet) ,
281+ case let . mutated( changes) = line. diff,
282+ !changes. isEmpty
283+ {
284+ let lastLine = highlightedCode [ lastLineIndex]
285+ let removedSuffix = line. text. suffix ( max (
286+ 0 ,
287+ line. text. count - lastLine. string. count
288+ ) )
289+ lastLine. append ( . init( string: String ( removedSuffix) , attributes: [
290+ . foregroundColor: NSColor . systemRed. withAlphaComponent ( 0.4 ) ,
291+ . backgroundColor: NSColor . systemRed. withAlphaComponent ( 0.1 ) ,
292+ ] ) )
293+ }
294+ }
295+ }
254296 }
255297
256298 @Perceptible
@@ -259,17 +301,10 @@ extension AsyncCodeBlock {
259301
260302 @PerceptionIgnored var originalCode : String ?
261303 @PerceptionIgnored var code : String ?
262- @PerceptionIgnored private var debounceFunction : DebounceFunction < AsyncCodeBlock > ?
263304 @PerceptionIgnored private var diffTask : Task < Void , Error > ?
264305
265- init ( ) {
266- debounceFunction = . init( duration: 0.1 , block: { view in
267- self . diff ( for: view)
268- } )
269- }
270-
271306 func diff( for view: AsyncCodeBlock ) {
272- Task { @ MainActor in await debounceFunction ? ( view) }
307+ performDiff ( for : view)
273308 }
274309
275310 private func performDiff( for view: AsyncCodeBlock ) {
@@ -412,6 +447,39 @@ extension AsyncCodeBlock {
412447}
413448
414449#Preview( " Updating Content " ) {
415- EmptyView ( )
450+ struct UpdateContent : View {
451+ @State var index = 0
452+ struct Case {
453+ let code : String
454+ let originalCode : String
455+ }
456+
457+ let cases : [ Case ] = [
458+ . init( code: " foo(123) " , originalCode: " bar(234) " ) ,
459+ . init( code: " bar(456) " , originalCode: " baz(567) " ) ,
460+ ]
461+
462+ var body : some View {
463+ VStack {
464+ Button ( " Update " ) {
465+ index = ( index + 1 ) % cases. count
466+ }
467+ AsyncCodeBlock (
468+ code: cases [ index] . code,
469+ originalCode: cases [ index] . originalCode,
470+ language: " swift " ,
471+ startLineIndex: 10 ,
472+ scenario: " " ,
473+ font: . monospacedSystemFont( ofSize: 12 , weight: . regular) ,
474+ droppingLeadingSpaces: true ,
475+ proposedForegroundColor: . primary,
476+ dimmedCharacterCount: . init( prefix: 0 , suffix: 0 )
477+ )
478+ }
479+ }
480+ }
481+
482+ return UpdateContent ( )
483+ . frame ( width: 400 , height: 200 )
416484}
417485
0 commit comments