on main help sign in
perf(diffview): only compute syntax highlight for printed lines
commita9bd37f04a9574a9d0c4144a17ec7710e0821507 authorAndrey Nering <andreynering@users.noreply.github.com> dateFri 20 Jun 2025, 10:30 (1 year ago) parenta723e4a
1 file changed+182−165
internal/exp/diffview/diffview.go+182−165
internal/exp/diffview/diffview.go+182−165
@@ -362,23 +362,29 @@ func (dv *DiffView) renderUnified() string {
362362
363363 fullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth)
364364 printedLines := -dv.yOffset
365−
366− write := func(s string) {
367− if printedLines >= 0 {
368− b.WriteString(s)
369− }
365+ shouldWrite := func() bool { return printedLines >= 0 }
366+
367+ getContent := func(in string, ls LineStyle) (content string, leadingEllipsis bool) {
368+ content = strings.TrimSuffix(in, "\n")
369+ content = dv.hightlightCode(content, ls.Code.GetBackground())
370+ content = ansi.GraphemeWidth.Cut(content, dv.xOffset, len(content))
371+ content = ansi.Truncate(content, dv.codeWidth, "…")
372+ leadingEllipsis = dv.xOffset > 0 && strings.TrimSpace(content) != ""
373+ return
370374 }
371375
372376outer:
373377 for i, h := range dv.unified.Hunks {
374− ls := dv.style.DividerLine
375− if dv.lineNumbers {
376− write(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
377− write(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
378+ if shouldWrite() {
379+ ls := dv.style.DividerLine
380+ if dv.lineNumbers {
381+ b.WriteString(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
382+ b.WriteString(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
383+ }
384+ content := ansi.Truncate(dv.hunkLineFor(h), dv.fullCodeWidth, "…")
385+ b.WriteString(ls.Code.Width(dv.fullCodeWidth).Render(content))
386+ b.WriteString("\n")
378387 }
379− content := ansi.Truncate(dv.hunkLineFor(h), dv.fullCodeWidth, "…")
380− write(ls.Code.Width(dv.fullCodeWidth).Render(content))
381− write("\n")
382388 printedLines++
383389
384390 beforeLine := h.FromLine
@@ -390,80 +396,82 @@ outer:
390396 isLastHunk := i+1 == len(dv.unified.Hunks)
391397 isLastLine := j+1 == len(h.Lines)
392398 if hasReachedHeight && (!isLastHunk || !isLastLine) {
393− ls := dv.lineStyleForType(l.Kind)
394− if dv.lineNumbers {
395− write(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
396− write(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
399+ if shouldWrite() {
400+ ls := dv.lineStyleForType(l.Kind)
401+ if dv.lineNumbers {
402+ b.WriteString(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
403+ b.WriteString(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
404+ }
405+ b.WriteString(fullContentStyle.Render(
406+ ls.Code.Width(dv.fullCodeWidth).Render(" …"),
407+ ))
408+ b.WriteRune('\n')
397409 }
398− write(fullContentStyle.Render(
399− ls.Code.Width(dv.fullCodeWidth).Render(" …"),
400− ))
401− write("\n")
402410 break outer
403411 }
404412
405− getContent := func(ls LineStyle) string {
406− content := strings.TrimSuffix(l.Content, "\n")
407− content = dv.hightlightCode(content, ls.Code.GetBackground())
408− content = ansi.GraphemeWidth.Cut(content, dv.xOffset, len(content))
409− content = ansi.Truncate(content, dv.codeWidth, "…")
410− return content
411− }
412−
413− leadingEllipsis := dv.xOffset > 0 && strings.TrimSpace(content) != ""
414−
415413 switch l.Kind {
416414 case udiff.Equal:
417− ls := dv.style.EqualLine
418− content := getContent(ls)
419− if dv.lineNumbers {
420− write(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
421− write(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
415+ if shouldWrite() {
416+ ls := dv.style.EqualLine
417+ content, leadingEllipsis := getContent(l.Content, ls)
418+ if dv.lineNumbers {
419+ b.WriteString(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
420+ b.WriteString(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
421+ }
422+ b.WriteString(fullContentStyle.Render(
423+ ls.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", " ") + content),
424+ ))
422425 }
423− write(fullContentStyle.Render(
424− ls.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", " ") + content),
425− ))
426426 beforeLine++
427427 afterLine++
428428 case udiff.Insert:
429− ls := dv.style.InsertLine
430− content := getContent(ls)
431− if dv.lineNumbers {
432− write(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
433− write(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
429+ if shouldWrite() {
430+ ls := dv.style.InsertLine
431+ content, leadingEllipsis := getContent(l.Content, ls)
432+ if dv.lineNumbers {
433+ b.WriteString(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
434+ b.WriteString(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
435+ }
436+ b.WriteString(fullContentStyle.Render(
437+ ls.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) +
438+ ls.Code.Width(dv.codeWidth).Render(content),
439+ ))
434440 }
435− write(fullContentStyle.Render(
436− ls.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) +
437− ls.Code.Width(dv.codeWidth).Render(content),
438− ))
439441 afterLine++
440442 case udiff.Delete:
441− ls := dv.style.DeleteLine
442− content := getContent(ls)
443− if dv.lineNumbers {
444− write(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
445− write(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
443+ if shouldWrite() {
444+ ls := dv.style.DeleteLine
445+ content, leadingEllipsis := getContent(l.Content, ls)
446+ if dv.lineNumbers {
447+ b.WriteString(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
448+ b.WriteString(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
449+ }
450+ b.WriteString(fullContentStyle.Render(
451+ ls.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) +
452+ ls.Code.Width(dv.codeWidth).Render(content),
453+ ))
446454 }
447− write(fullContentStyle.Render(
448− ls.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) +
449− ls.Code.Width(dv.codeWidth).Render(content),
450− ))
451455 beforeLine++
452456 }
453− write("\n")
457+ if shouldWrite() {
458+ b.WriteRune('\n')
459+ }
454460
455461 printedLines++
456462 }
457463 }
458464
459465 for printedLines < dv.height {
460− ls := dv.style.MissingLine
461− if dv.lineNumbers {
462− write(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
463− write(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
466+ if shouldWrite() {
467+ ls := dv.style.MissingLine
468+ if dv.lineNumbers {
469+ b.WriteString(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
470+ b.WriteString(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
471+ }
472+ b.WriteString(ls.Code.Width(dv.fullCodeWidth).Render(" "))
473+ b.WriteRune('\n')
464474 }
465− write(ls.Code.Width(dv.fullCodeWidth).Render(" "))
466− write("\n")
467475 printedLines++
468476 }
469477
@@ -477,26 +485,32 @@ func (dv *DiffView) renderSplit() string {
477485 beforeFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth)
478486 afterFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth + btoi(dv.extraColOnAfter))
479487 printedLines := -dv.yOffset
480−
481− write := func(s string) {
482− if printedLines >= 0 {
483− b.WriteString(s)
484− }
488+ shouldWrite := func() bool { return printedLines >= 0 }
489+
490+ getContent := func(in string, ls LineStyle) (content string, leadingEllipsis bool) {
491+ content = strings.TrimSuffix(in, "\n")
492+ content = dv.hightlightCode(content, ls.Code.GetBackground())
493+ content = ansi.GraphemeWidth.Cut(content, dv.xOffset, len(content))
494+ content = ansi.Truncate(content, dv.codeWidth, "…")
495+ leadingEllipsis = dv.xOffset > 0 && strings.TrimSpace(content) != ""
496+ return
485497 }
486498
487499outer:
488500 for i, h := range dv.splitHunks {
489− ls := dv.style.DividerLine
490− if dv.lineNumbers {
491− write(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
492− }
493− content := ansi.Truncate(dv.hunkLineFor(dv.unified.Hunks[i]), dv.fullCodeWidth, "…")
494− write(ls.Code.Width(dv.fullCodeWidth).Render(content))
495− if dv.lineNumbers {
496− write(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
501+ if shouldWrite() {
502+ ls := dv.style.DividerLine
503+ if dv.lineNumbers {
504+ b.WriteString(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
505+ }
506+ content := ansi.Truncate(dv.hunkLineFor(dv.unified.Hunks[i]), dv.fullCodeWidth, "…")
507+ b.WriteString(ls.Code.Width(dv.fullCodeWidth).Render(content))
508+ if dv.lineNumbers {
509+ b.WriteString(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
510+ }
511+ b.WriteString(ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
512+ b.WriteRune('\n')
497513 }
498− write(ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
499− write("\n")
500514 printedLines++
501515
502516 beforeLine := h.fromLine
@@ -508,126 +522,129 @@ outer:
508522 isLastHunk := i+1 == len(dv.unified.Hunks)
509523 isLastLine := j+1 == len(h.lines)
510524 if hasReachedHeight && (!isLastHunk || !isLastLine) {
511− ls := dv.style.MissingLine
512− if l.before != nil {
513− ls = dv.lineStyleForType(l.before.Kind)
514− }
515− if dv.lineNumbers {
516− write(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
525+ if shouldWrite() {
526+ ls := dv.style.MissingLine
527+ if l.before != nil {
528+ ls = dv.lineStyleForType(l.before.Kind)
529+ }
530+ if dv.lineNumbers {
531+ b.WriteString(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
532+ }
533+ b.WriteString(beforeFullContentStyle.Render(
534+ ls.Code.Width(dv.fullCodeWidth).Render(" …"),
535+ ))
536+ ls = dv.style.MissingLine
537+ if l.after != nil {
538+ ls = dv.lineStyleForType(l.after.Kind)
539+ }
540+ if dv.lineNumbers {
541+ b.WriteString(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
542+ }
543+ b.WriteString(afterFullContentStyle.Render(
544+ ls.Code.Width(dv.fullCodeWidth).Render(" …"),
545+ ))
546+ b.WriteRune('\n')
517547 }
518− write(beforeFullContentStyle.Render(
519− ls.Code.Width(dv.fullCodeWidth).Render(" …"),
520− ))
521− ls = dv.style.MissingLine
522− if l.after != nil {
523− ls = dv.lineStyleForType(l.after.Kind)
524− }
525− if dv.lineNumbers {
526− write(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
527− }
528− write(afterFullContentStyle.Render(
529− ls.Code.Width(dv.fullCodeWidth).Render(" …"),
530− ))
531− write("\n")
532548 break outer
533549 }
534550
535− getContent := func(content string, ls LineStyle) string {
536− content = strings.TrimSuffix(content, "\n")
537− content = dv.hightlightCode(content, ls.Code.GetBackground())
538− content = ansi.GraphemeWidth.Cut(content, dv.xOffset, len(content))
539− content = ansi.Truncate(content, dv.codeWidth, "…")
540− return content
541− }
542− getLeadingEllipsis := func(content string) bool {
543− return dv.xOffset > 0 && strings.TrimSpace(content) != ""
544− }
545−
546551 switch {
547552 case l.before == nil:
548− ls := dv.style.MissingLine
549− if dv.lineNumbers {
550− write(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
553+ if shouldWrite() {
554+ ls := dv.style.MissingLine
555+ if dv.lineNumbers {
556+ b.WriteString(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
557+ }
558+ b.WriteString(beforeFullContentStyle.Render(
559+ ls.Code.Width(dv.fullCodeWidth).Render(" "),
560+ ))
551561 }
552− write(beforeFullContentStyle.Render(
553− ls.Code.Width(dv.fullCodeWidth).Render(" "),
554− ))
555562 case l.before.Kind == udiff.Equal:
556− ls := dv.style.EqualLine
557− content := getContent(l.before.Content, ls)
558− leadingEllipsis := getLeadingEllipsis(content)
559− if dv.lineNumbers {
560− write(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
563+ if shouldWrite() {
564+ ls := dv.style.EqualLine
565+ content, leadingEllipsis := getContent(l.before.Content, ls)
566+ if dv.lineNumbers {
567+ b.WriteString(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
568+ }
569+ b.WriteString(beforeFullContentStyle.Render(
570+ ls.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", " ") + content),
571+ ))
561572 }
562− write(beforeFullContentStyle.Render(
563− ls.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", " ") + content),
564− ))
565573 beforeLine++
566574 case l.before.Kind == udiff.Delete:
567− ls := dv.style.DeleteLine
568− content := getContent(l.before.Content, ls)
569− leadingEllipsis := getLeadingEllipsis(content)
570− if dv.lineNumbers {
571− write(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
575+ if shouldWrite() {
576+ ls := dv.style.DeleteLine
577+ content, leadingEllipsis := getContent(l.before.Content, ls)
578+ if dv.lineNumbers {
579+ b.WriteString(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
580+ }
581+ b.WriteString(beforeFullContentStyle.Render(
582+ ls.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) +
583+ ls.Code.Width(dv.codeWidth).Render(content),
584+ ))
572585 }
573− write(beforeFullContentStyle.Render(
574− ls.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) +
575− ls.Code.Width(dv.codeWidth).Render(content),
576− ))
577586 beforeLine++
578587 }
579588
580589 switch {
581590 case l.after == nil:
582− ls := dv.style.MissingLine
583− if dv.lineNumbers {
584− write(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
591+ if shouldWrite() {
592+ ls := dv.style.MissingLine
593+ if dv.lineNumbers {
594+ b.WriteString(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
595+ }
596+ b.WriteString(afterFullContentStyle.Render(
597+ ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "),
598+ ))
585599 }
586− write(afterFullContentStyle.Render(
587− ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "),
588− ))
589600 case l.after.Kind == udiff.Equal:
590− ls := dv.style.EqualLine
591− content := getContent(l.after.Content, ls)
592− leadingEllipsis := getLeadingEllipsis(content)
593− if dv.lineNumbers {
594− write(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
601+ if shouldWrite() {
602+ ls := dv.style.EqualLine
603+ content, leadingEllipsis := getContent(l.after.Content, ls)
604+ if dv.lineNumbers {
605+ b.WriteString(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
606+ }
607+ b.WriteString(afterFullContentStyle.Render(
608+ ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(ternary(leadingEllipsis, " …", " ") + content),
609+ ))
595610 }
596− write(afterFullContentStyle.Render(
597− ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(ternary(leadingEllipsis, " …", " ") + content),
598− ))
599611 afterLine++
600612 case l.after.Kind == udiff.Insert:
601− ls := dv.style.InsertLine
602− content := getContent(l.after.Content, ls)
603− leadingEllipsis := getLeadingEllipsis(content)
604− if dv.lineNumbers {
605− write(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
613+ if shouldWrite() {
614+ ls := dv.style.InsertLine
615+ content, leadingEllipsis := getContent(l.after.Content, ls)
616+ if dv.lineNumbers {
617+ b.WriteString(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
618+ }
619+ b.WriteString(afterFullContentStyle.Render(
620+ ls.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) +
621+ ls.Code.Width(dv.codeWidth+btoi(dv.extraColOnAfter)).Render(content),
622+ ))
606623 }
607− write(afterFullContentStyle.Render(
608− ls.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) +
609− ls.Code.Width(dv.codeWidth+btoi(dv.extraColOnAfter)).Render(content),
610− ))
611624 afterLine++
612625 }
613626
614− write("\n")
627+ if shouldWrite() {
628+ b.WriteRune('\n')
629+ }
615630
616631 printedLines++
617632 }
618633 }
619634
620635 for printedLines < dv.height {
621− ls := dv.style.MissingLine
622− if dv.lineNumbers {
623− write(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
624− }
625− write(ls.Code.Width(dv.fullCodeWidth).Render(" "))
626− if dv.lineNumbers {
627− write(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
636+ if shouldWrite() {
637+ ls := dv.style.MissingLine
638+ if dv.lineNumbers {
639+ b.WriteString(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
640+ }
641+ b.WriteString(ls.Code.Width(dv.fullCodeWidth).Render(" "))
642+ if dv.lineNumbers {
643+ b.WriteString(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
644+ }
645+ b.WriteString(ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
646+ b.WriteRune('\n')
628647 }
629− write(ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
630− write("\n")
631648 printedLines++
632649 }
633650