diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index 784f04aab..7cb3f5fc9 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -754,6 +754,15 @@ type lineType struct { // which they hold as the overwrite it caused. nil while the cells spell the // line as it was written, as they do for most lines. asWritten []byte + + // metadata holds the OSC 1717 records that the line's cells carry, each once, in + // the order they first appear. They are collected when the write cursor moves on + // to the next line, so that a reader can take them from here instead of going + // through every cell of every line. metadataCollected is false until then, and + // again once anything is written to the line; a reader then takes them from the + // cells. + metadata []string + metadataCollected bool } // textAsWritten returns the line's text as its writer wrote it, escape sequences @@ -766,6 +775,34 @@ func (l *lineType) textAsWritten() string { return l.cells.String() } +// diffLineMetadata returns the OSC 1717 records that the line's cells carry (see +// lineType.metadata). +func (l *lineType) diffLineMetadata() []string { + if l.metadataCollected { + return l.metadata + } + return metadataOfCells(l.cells) +} + +// collectMetadata keeps the records that the line's cells carry, for readers to take +// without going through the cells (see lineType.metadata). +func (l *lineType) collectMetadata() { + l.metadata = metadataOfCells(l.cells) + l.metadataCollected = true +} + +// metadataOfCells returns the OSC 1717 records that the given cells carry, each once, +// in the order they first appear. +func metadataOfCells(cells []cell) []string { + var metadata []string + for _, c := range cells { + if c.metadata != "" && !slices.Contains(metadata, c.metadata) { + metadata = append(metadata, c.metadata) + } + } + return metadata +} + // trailingFillAttributes describes the fg/bg colors that draw() should // use for cells past the end of a wrapped segment's content. On a source // line this records what the writer asked for via '\x1b[K' (and so opts @@ -1153,6 +1190,7 @@ func (b *viewBuffer) writeCells(cells []cell) { } } b.lines[b.wy].cells = line[:newLen] + b.lines[b.wy].metadataCollected = false b.wx += len(cells) } @@ -1244,6 +1282,7 @@ func (b *viewBuffer) write(v *View, p []byte) { } advanceToNextLine := func() { + b.lines[b.wy].collectMetadata() b.seekWrite(0, b.wy+1) if b.wy >= len(b.lines) { b.lines = append(b.lines, lineType{}) @@ -2170,13 +2209,7 @@ func diffLineContentsFrom(buf *viewBuffer, from int) []DiffLineContent { contents := make([]DiffLineContent, len(lines)) for i := range lines { line := &lines[i] - var metadata []string - for _, c := range line.cells { - if c.metadata != "" && !slices.Contains(metadata, c.metadata) { - metadata = append(metadata, c.metadata) - } - } - contents[i] = DiffLineContent{Text: line.textAsWritten(), Metadata: metadata} + contents[i] = DiffLineContent{Text: line.textAsWritten(), Metadata: line.diffLineMetadata()} } return contents } diff --git a/pkg/gocui/view_test.go b/pkg/gocui/view_test.go index 7daf10ced..a8e1cea56 100644 --- a/pkg/gocui/view_test.go +++ b/pkg/gocui/view_test.go @@ -335,6 +335,25 @@ func TestDiffLineContentsWithRecordsCoveringNoCell(t *testing.T) { }, v.DiffLineContents()) } +func TestDiffLineContentsOfARewrittenLine(t *testing.T) { + v := NewView("name", 0, 0, 80, 10, OutputNormal) + v.writeString(strings.Join([]string{ + osc1717("1;c;1;;foo.txt") + "line1", + osc1717("1;c;2;;foo.txt") + "line2", + "last line", + }, "\n")) + + // The records of a line that is written again are the ones it is written with, + // even when the line's earlier records were taken from it already. + v.OverwriteLines(0, osc1717("1;a;7;;bar.txt")+"new1") + + assert.Equal(t, []DiffLineContent{ + {Text: "new1", Metadata: []string{"1;a;7;;bar.txt"}}, + {Text: "line2", Metadata: []string{"1;c;2;;foo.txt"}}, + {Text: "last line"}, + }, v.DiffLineContents()) +} + func TestDiffLineContentsSwallowsHandshake(t *testing.T) { v := NewView("name", 0, 0, 80, 10, OutputNormal)