@@ -40,6 +40,7 @@ type DiffView struct {
40 height int
40 height int
42 xOffset int
42 xOffset int
43 style Style
44 style Style
45 isComputed bool
46 isComputed bool
@@ -137,6 +138,12 @@ func (dv *DiffView) XOffset(xOffset int) *DiffView {
137 return dv
138 return dv
141+// YOffset sets the vertical offset for the DiffView.
142+func (dv *DiffView) YOffset(yOffset int) *DiffView {
143+ dv.yOffset = yOffset
140// String returns the string representation of the DiffView.
147// String returns the string representation of the DiffView.
141func (dv *DiffView) String() string {
148func (dv *DiffView) String() string {
142 if err := dv.computeDiff(); err != nil {
149 if err := dv.computeDiff(); err != nil {
@@ -296,17 +303,23 @@ func (dv *DiffView) renderUnified() string {
296 var b strings.Builder
303 var b strings.Builder
298 fullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth)
305 fullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth)
299− printedLines := 0
306+ printedLines := -dv.yOffset
308+ write := func(s string) {
309+ if printedLines >= 0 {
302 for i, h := range dv.unified.Hunks {
315 for i, h := range dv.unified.Hunks {
303 if dv.lineNumbers {
316 if dv.lineNumbers {
304− b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
317+ write(dv.style.DividerLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
305− b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
318+ write(dv.style.DividerLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
307 content := ansi.Truncate(dv.hunkLineFor(h), dv.fullCodeWidth, "…")
320 content := ansi.Truncate(dv.hunkLineFor(h), dv.fullCodeWidth, "…")
308− b.WriteString(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(content))
321+ write(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(content))
309− b.WriteRune('\n')
322+ write("\n")
310 printedLines++
323 printedLines++
312 beforeLine := h.FromLine
325 beforeLine := h.FromLine
@@ -319,13 +332,13 @@ outer:
319 isLastLine := j+1 == len(h.Lines)
332 isLastLine := j+1 == len(h.Lines)
320 if hasReachedHeight && (!isLastHunk || !isLastLine) {
333 if hasReachedHeight && (!isLastHunk || !isLastLine) {
321 if dv.lineNumbers {
334 if dv.lineNumbers {
322− b.WriteString(dv.style.EqualLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
335+ write(dv.style.EqualLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
323− b.WriteString(dv.style.EqualLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
336+ write(dv.style.EqualLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
325− b.WriteString(fullContentStyle.Render(
338+ write(fullContentStyle.Render(
326 dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(" …"),
339 dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(" …"),
328− b.WriteRune('\n')
341+ write("\n")
329 break outer
342 break outer
@@ -338,36 +351,36 @@ outer:
338 switch l.Kind {
351 switch l.Kind {
339 case udiff.Equal:
352 case udiff.Equal:
340 if dv.lineNumbers {
353 if dv.lineNumbers {
341− b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
354+ write(dv.style.EqualLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
342− b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
355+ write(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
344− b.WriteString(fullContentStyle.Render(
357+ write(fullContentStyle.Render(
345 dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", " ") + content),
358 dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", " ") + content),
347 beforeLine++
360 beforeLine++
348 afterLine++
361 afterLine++
349 case udiff.Insert:
362 case udiff.Insert:
350 if dv.lineNumbers {
363 if dv.lineNumbers {
351− b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
364+ write(dv.style.InsertLine.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
352− b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
365+ write(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
354− b.WriteString(fullContentStyle.Render(
367+ write(fullContentStyle.Render(
355 dv.style.InsertLine.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) +
368 dv.style.InsertLine.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) +
356 dv.style.InsertLine.Code.Width(dv.codeWidth).Render(content),
369 dv.style.InsertLine.Code.Width(dv.codeWidth).Render(content),
358 afterLine++
371 afterLine++
359 case udiff.Delete:
372 case udiff.Delete:
360 if dv.lineNumbers {
373 if dv.lineNumbers {
361− b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
374+ write(dv.style.DeleteLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
362− b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(" ", dv.afterNumDigits)))
375+ write(dv.style.DeleteLine.LineNumber.Render(pad(" ", dv.afterNumDigits)))
364− b.WriteString(fullContentStyle.Render(
377+ write(fullContentStyle.Render(
365 dv.style.DeleteLine.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) +
378 dv.style.DeleteLine.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) +
366 dv.style.DeleteLine.Code.Width(dv.codeWidth).Render(content),
379 dv.style.DeleteLine.Code.Width(dv.codeWidth).Render(content),
368 beforeLine++
381 beforeLine++
370− b.WriteRune('\n')
383+ write("\n")
372 printedLines++
385 printedLines++
@@ -375,11 +388,11 @@ outer:
376 for printedLines < dv.height {
389 for printedLines < dv.height {
377 if dv.lineNumbers {
390 if dv.lineNumbers {
378− b.WriteString(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
391+ write(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
379− b.WriteString(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.afterNumDigits)))
392+ write(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.afterNumDigits)))
381− b.WriteString(dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render(" "))
394+ write(dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render(" "))
382− b.WriteRune('\n')
395+ write("\n")
383 printedLines++
396 printedLines++
@@ -392,20 +405,26 @@ func (dv *DiffView) renderSplit() string {
393 beforeFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth)
406 beforeFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth)
394 afterFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth + btoi(dv.extraColOnAfter))
407 afterFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth + btoi(dv.extraColOnAfter))
395− printedLines := 0
408+ printedLines := -dv.yOffset
410+ write := func(s string) {
411+ if printedLines >= 0 {
398 for i, h := range dv.splitHunks {
417 for i, h := range dv.splitHunks {
399 if dv.lineNumbers {
418 if dv.lineNumbers {
400− b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
419+ write(dv.style.DividerLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
402 content := ansi.Truncate(dv.hunkLineFor(dv.unified.Hunks[i]), dv.fullCodeWidth, "…")
421 content := ansi.Truncate(dv.hunkLineFor(dv.unified.Hunks[i]), dv.fullCodeWidth, "…")
403− b.WriteString(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(content))
422+ write(dv.style.DividerLine.Code.Width(dv.fullCodeWidth).Render(content))
404 if dv.lineNumbers {
423 if dv.lineNumbers {
405− b.WriteString(dv.style.DividerLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
424+ write(dv.style.DividerLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
407− b.WriteString(dv.style.DividerLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
426+ write(dv.style.DividerLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
408− b.WriteRune('\n')
427+ write("\n")
409 printedLines++
428 printedLines++
411 beforeLine := h.fromLine
430 beforeLine := h.fromLine
@@ -418,18 +437,18 @@ outer:
418 isLastLine := j+1 == len(h.lines)
437 isLastLine := j+1 == len(h.lines)
419 if hasReachedHeight && (!isLastHunk || !isLastLine) {
438 if hasReachedHeight && (!isLastHunk || !isLastLine) {
420 if dv.lineNumbers {
439 if dv.lineNumbers {
421− b.WriteString(dv.style.EqualLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
440+ write(dv.style.EqualLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
423− b.WriteString(beforeFullContentStyle.Render(
442+ write(beforeFullContentStyle.Render(
424 dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(" …"),
443 dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(" …"),
426 if dv.lineNumbers {
445 if dv.lineNumbers {
427− b.WriteString(dv.style.EqualLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
446+ write(dv.style.EqualLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
429− b.WriteString(afterFullContentStyle.Render(
448+ write(afterFullContentStyle.Render(
430 dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(" …"),
449 dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(" …"),
432− b.WriteRune('\n')
451+ write("\n")
433 break outer
452 break outer
@@ -452,24 +471,24 @@ outer:
453 case l.before == nil:
472 case l.before == nil:
454 if dv.lineNumbers {
473 if dv.lineNumbers {
455− b.WriteString(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
474+ write(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
457− b.WriteString(beforeFullContentStyle.Render(
476+ write(beforeFullContentStyle.Render(
458 dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render(" "),
477 dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render(" "),
460 case l.before.Kind == udiff.Equal:
479 case l.before.Kind == udiff.Equal:
461 if dv.lineNumbers {
480 if dv.lineNumbers {
462− b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
481+ write(dv.style.EqualLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
464− b.WriteString(beforeFullContentStyle.Render(
483+ write(beforeFullContentStyle.Render(
465 dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(ternary(leadingBeforeEllipsis, " …", " ") + beforeContent),
484 dv.style.EqualLine.Code.Width(dv.fullCodeWidth).Render(ternary(leadingBeforeEllipsis, " …", " ") + beforeContent),
467 beforeLine++
486 beforeLine++
468 case l.before.Kind == udiff.Delete:
487 case l.before.Kind == udiff.Delete:
469 if dv.lineNumbers {
488 if dv.lineNumbers {
470− b.WriteString(dv.style.DeleteLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
489+ write(dv.style.DeleteLine.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
472− b.WriteString(beforeFullContentStyle.Render(
491+ write(beforeFullContentStyle.Render(
473 dv.style.DeleteLine.Symbol.Render(ternary(leadingBeforeEllipsis, "-…", "- ")) +
492 dv.style.DeleteLine.Symbol.Render(ternary(leadingBeforeEllipsis, "-…", "- ")) +
474 dv.style.DeleteLine.Code.Width(dv.codeWidth).Render(beforeContent),
493 dv.style.DeleteLine.Code.Width(dv.codeWidth).Render(beforeContent),
@@ -479,31 +498,31 @@ outer:
480 case l.after == nil:
499 case l.after == nil:
481 if dv.lineNumbers {
500 if dv.lineNumbers {
482− b.WriteString(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.afterNumDigits)))
501+ write(dv.style.MissingLine.LineNumber.Render(pad(" ", dv.afterNumDigits)))
484− b.WriteString(afterFullContentStyle.Render(
503+ write(afterFullContentStyle.Render(
485 dv.style.MissingLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "),
504 dv.style.MissingLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "),
487 case l.after.Kind == udiff.Equal:
506 case l.after.Kind == udiff.Equal:
488 if dv.lineNumbers {
507 if dv.lineNumbers {
489− b.WriteString(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
508+ write(dv.style.EqualLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
491− b.WriteString(afterFullContentStyle.Render(
510+ write(afterFullContentStyle.Render(
492 dv.style.EqualLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(ternary(leadingAfterEllipsis, " …", " ") + afterContent),
511 dv.style.EqualLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(ternary(leadingAfterEllipsis, " …", " ") + afterContent),
494 afterLine++
513 afterLine++
495 case l.after.Kind == udiff.Insert:
514 case l.after.Kind == udiff.Insert:
496 if dv.lineNumbers {
515 if dv.lineNumbers {
497− b.WriteString(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
516+ write(dv.style.InsertLine.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
499− b.WriteString(afterFullContentStyle.Render(
518+ write(afterFullContentStyle.Render(
500 dv.style.InsertLine.Symbol.Render(ternary(leadingAfterEllipsis, "+…", "+ ")) +
519 dv.style.InsertLine.Symbol.Render(ternary(leadingAfterEllipsis, "+…", "+ ")) +
501 dv.style.InsertLine.Code.Width(dv.codeWidth+btoi(dv.extraColOnAfter)).Render(afterContent),
520 dv.style.InsertLine.Code.Width(dv.codeWidth+btoi(dv.extraColOnAfter)).Render(afterContent),
503 afterLine++
522 afterLine++
506− b.WriteRune('\n')
525+ write("\n")
508 printedLines++
527 printedLines++
@@ -511,14 +530,14 @@ outer:
512 for printedLines < dv.height {
531 for printedLines < dv.height {
513 if dv.lineNumbers {
532 if dv.lineNumbers {
514− b.WriteString(dv.style.MissingLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
533+ write(dv.style.MissingLine.LineNumber.Render(pad("…", dv.beforeNumDigits)))
516− b.WriteString(dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render(" "))
535+ write(dv.style.MissingLine.Code.Width(dv.fullCodeWidth).Render(" "))
517 if dv.lineNumbers {
536 if dv.lineNumbers {
518− b.WriteString(dv.style.MissingLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
537+ write(dv.style.MissingLine.LineNumber.Render(pad("…", dv.afterNumDigits)))
520− b.WriteString(dv.style.MissingLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
539+ write(dv.style.MissingLine.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
521− b.WriteRune('\n')
540+ write("\n")
522 printedLines++
541 printedLines++