refactor(copy): share content extent between selection and extraction
- commit
- ce725b2e96392431d16202e042ba36a1a7213ab2
- author
- brice · · 2026-10-05 07:12 UTC
- parent
- 668edcc
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +68 -26
M internal/usecase/copy/document.go +20 -2
| @@ -299,9 +299,9 @@ func (d *Document) rangeText(r CellRange, logicalEnd bool) (string, bool, bool) | |||
| 299 | 299 | start = head.Col | |
| 300 | 300 | ||
| 301 | 301 | bound := d.snapshot.Bound(r.Row) | |
| 302 | - | soft := bound.Soft && !logicalEnd && end >= len(row)-1 | |
| 302 | + | soft := bound.Soft && !logicalEnd && end >= d.contentEnd(r.Row)-1 | |
| 303 | 303 | if soft { | |
| 304 | - | end = min(end, min(max(bound.End, 0), len(row))-1) | |
| 304 | + | end = min(end, d.contentEnd(r.Row)-1) | |
| 305 | 305 | } else { | |
| 306 | 306 | end = lastContentCol(row, start, end) | |
| 307 | 307 | } | |
| @@ -316,6 +316,24 @@ func (d *Document) rangeText(r CellRange, logicalEnd bool) (string, bool, bool) | |||
| 316 | 316 | return d.cellsText(row, start, tail.Col), soft, true | |
| 317 | 317 | } | |
| 318 | 318 | ||
| 319 | + | // contentEnd returns the exclusive display-cell end of row's copyable content. | |
| 320 | + | // Soft rows end at their logical bound; hard rows drop blank padding. | |
| 321 | + | func (d *Document) contentEnd(row int) int { | |
| 322 | + | cells := d.Row(row) | |
| 323 | + | if len(cells) == 0 { | |
| 324 | + | return 0 | |
| 325 | + | } | |
| 326 | + | bound := d.snapshot.Bound(row) | |
| 327 | + | if bound.Soft { | |
| 328 | + | return min(max(bound.End, 0), len(cells)) | |
| 329 | + | } | |
| 330 | + | last := lastContentCol(cells, 0, len(cells)-1) | |
| 331 | + | if last < 0 { | |
| 332 | + | return 0 | |
| 333 | + | } | |
| 334 | + | return glyphEnd(cells, last) + 1 | |
| 335 | + | } | |
| 336 | + | ||
| 319 | 337 | // lastContentCol returns the rightmost column in [start, end] holding content, | |
| 320 | 338 | // or start-1 when the span is blank. Continuation cells belong to the glyph on | |
| 321 | 339 | // their left, so they never count on their own. | |
| @@ -299,9 +299,9 @@ func (d *Document) rangeText(r CellRange, logicalEnd bool) (string, bool, bool) | |||||
| 299 | start = head.Col | 299 | start = head.Col | ||
| 300 | 300 | ||||
| 301 | bound := d.snapshot.Bound(r.Row) | 301 | bound := d.snapshot.Bound(r.Row) | ||
| 302 | - | soft := bound.Soft && !logicalEnd && end >= len(row)-1 | 302 | + | soft := bound.Soft && !logicalEnd && end >= d.contentEnd(r.Row)-1 |
| 303 | if soft { | 303 | if soft { | ||
| 304 | - | end = min(end, min(max(bound.End, 0), len(row))-1) | 304 | + | end = min(end, d.contentEnd(r.Row)-1) |
| 305 | } else { | 305 | } else { | ||
| 306 | end = lastContentCol(row, start, end) | 306 | end = lastContentCol(row, start, end) | ||
| 307 | } | 307 | } | ||
| @@ -316,6 +316,24 @@ func (d *Document) rangeText(r CellRange, logicalEnd bool) (string, bool, bool) | |||||
| 316 | return d.cellsText(row, start, tail.Col), soft, true | 316 | return d.cellsText(row, start, tail.Col), soft, true | ||
| 317 | } | 317 | } | ||
| 318 | 318 | ||||
| 319 | + | // contentEnd returns the exclusive display-cell end of row's copyable content. | |||
| 320 | + | // Soft rows end at their logical bound; hard rows drop blank padding. | |||
| 321 | + | func (d *Document) contentEnd(row int) int { | |||
| 322 | + | cells := d.Row(row) | |||
| 323 | + | if len(cells) == 0 { | |||
| 324 | + | return 0 | |||
| 325 | + | } | |||
| 326 | + | bound := d.snapshot.Bound(row) | |||
| 327 | + | if bound.Soft { | |||
| 328 | + | return min(max(bound.End, 0), len(cells)) | |||
| 329 | + | } | |||
| 330 | + | last := lastContentCol(cells, 0, len(cells)-1) | |||
| 331 | + | if last < 0 { | |||
| 332 | + | return 0 | |||
| 333 | + | } | |||
| 334 | + | return glyphEnd(cells, last) + 1 | |||
| 335 | + | } | |||
| 336 | + | ||||
| 319 | // lastContentCol returns the rightmost column in [start, end] holding content, | 337 | // lastContentCol returns the rightmost column in [start, end] holding content, | ||
| 320 | // or start-1 when the span is blank. Continuation cells belong to the glyph on | 338 | // or start-1 when the span is blank. Continuation cells belong to the glyph on | ||
| 321 | // their left, so they never count on their own. | 339 | // their left, so they never count on their own. | ||
M internal/usecase/copy/document_test.go +48 -24
| @@ -236,21 +236,45 @@ func TestDocumentExtractRanges(t *testing.T) { | |||
| 236 | 236 | } | |
| 237 | 237 | } | |
| 238 | 238 | ||
| 239 | - | func TestDocumentExtractTrimsPaddingAndJoinsWrappedRows(t *testing.T) { | |
| 240 | - | // pad renders s into a width-w row, blank-filled like a real terminal grid. | |
| 241 | - | pad := func(s string, w int) []renderer.Cell { | |
| 242 | - | cells := make([]renderer.Cell, w) | |
| 243 | - | for i := range cells { | |
| 244 | - | cells[i] = renderer.BlankCell() | |
| 245 | - | } | |
| 246 | - | for i, r := range []rune(s) { | |
| 247 | - | if i >= w { | |
| 248 | - | break | |
| 249 | - | } | |
| 250 | - | cells[i] = renderer.Cell{Rune: r} | |
| 239 | + | // padRow blank-fills a terminal row before adding text. | |
| 240 | + | func padRow(s string, w int) []renderer.Cell { | |
| 241 | + | cells := make([]renderer.Cell, w) | |
| 242 | + | for i := range cells { | |
| 243 | + | cells[i] = renderer.BlankCell() | |
| 244 | + | } | |
| 245 | + | for i, r := range []rune(s) { | |
| 246 | + | if i >= w { | |
| 247 | + | break | |
| 251 | 248 | } | |
| 252 | - | return cells | |
| 249 | + | cells[i] = renderer.Cell{Rune: r} | |
| 253 | 250 | } | |
| 251 | + | return cells | |
| 252 | + | } | |
| 253 | + | ||
| 254 | + | func TestDocumentContentEnd(t *testing.T) { | |
| 255 | + | tests := []struct { | |
| 256 | + | name string | |
| 257 | + | cells []renderer.Cell | |
| 258 | + | bounds []vt.LineBound | |
| 259 | + | want int | |
| 260 | + | }{ | |
| 261 | + | {"hard short", padRow("ab", 8), []vt.LineBound{{End: 2}}, 2}, | |
| 262 | + | {"inner spaces", padRow("a b", 8), []vt.LineBound{{End: 3}}, 3}, | |
| 263 | + | {"blank", padRow("", 8), []vt.LineBound{{}}, 0}, | |
| 264 | + | {"soft trailing spaces", padRow("abc ", 8), []vt.LineBound{{End: 8, Soft: true}}, 8}, | |
| 265 | + | {"soft partial bound", padRow("abc", 8), []vt.LineBound{{End: 5, Soft: true}}, 5}, | |
| 266 | + | {"wide glyph", []renderer.Cell{{Rune: 'a'}, {Rune: '界'}, {Continuation: true}, renderer.BlankCell(), renderer.BlankCell(), renderer.BlankCell()}, []vt.LineBound{{End: 3}}, 3}, | |
| 267 | + | {"nil bounds", padRow("ab", 8), nil, 2}, | |
| 268 | + | } | |
| 269 | + | for _, tt := range tests { | |
| 270 | + | t.Run(tt.name, func(t *testing.T) { | |
| 271 | + | doc := NewDocument(NewSnapshotFromLines([][]renderer.Cell{tt.cells}, tt.bounds, 8, 1), "") | |
| 272 | + | require.Equal(t, tt.want, doc.contentEnd(0)) | |
| 273 | + | }) | |
| 274 | + | } | |
| 275 | + | } | |
| 276 | + | ||
| 277 | + | func TestDocumentExtractTrimsPaddingAndJoinsWrappedRows(t *testing.T) { | |
| 254 | 278 | ||
| 255 | 279 | tests := []struct { | |
| 256 | 280 | name string | |
| @@ -261,28 +285,28 @@ func TestDocumentExtractTrimsPaddingAndJoinsWrappedRows(t *testing.T) { | |||
| 261 | 285 | }{ | |
| 262 | 286 | { | |
| 263 | 287 | name: "hard row drops its padding", | |
| 264 | - | rows: [][]renderer.Cell{pad("ab", 8)}, | |
| 288 | + | rows: [][]renderer.Cell{padRow("ab", 8)}, | |
| 265 | 289 | bounds: []vt.LineBound{{End: 2}}, | |
| 266 | 290 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}}, | |
| 267 | 291 | want: "ab", | |
| 268 | 292 | }, | |
| 269 | 293 | { | |
| 270 | 294 | name: "soft row joins without a newline", | |
| 271 | - | rows: [][]renderer.Cell{pad("abcdefgh", 8), pad("ij", 8)}, | |
| 295 | + | rows: [][]renderer.Cell{padRow("abcdefgh", 8), padRow("ij", 8)}, | |
| 272 | 296 | bounds: []vt.LineBound{{End: 8, Soft: true}, {End: 2}}, | |
| 273 | 297 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}}, | |
| 274 | 298 | want: "abcdefghij", | |
| 275 | 299 | }, | |
| 276 | 300 | { | |
| 277 | 301 | name: "soft row keeps a real trailing space", | |
| 278 | - | rows: [][]renderer.Cell{pad("abc ", 8), pad("de", 8)}, | |
| 302 | + | rows: [][]renderer.Cell{padRow("abc ", 8), padRow("de", 8)}, | |
| 279 | 303 | bounds: []vt.LineBound{{End: 8, Soft: true}, {End: 2}}, | |
| 280 | 304 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}}, | |
| 281 | 305 | want: "abc de", | |
| 282 | 306 | }, | |
| 283 | 307 | { | |
| 284 | 308 | name: "three consecutive soft rows", | |
| 285 | - | rows: [][]renderer.Cell{pad("aaaa", 4), pad("bbbb", 4), pad("cccc", 4), pad("dd", 4)}, | |
| 309 | + | rows: [][]renderer.Cell{padRow("aaaa", 4), padRow("bbbb", 4), padRow("cccc", 4), padRow("dd", 4)}, | |
| 286 | 310 | bounds: []vt.LineBound{ | |
| 287 | 311 | {End: 4, Soft: true}, {End: 4, Soft: true}, {End: 4, Soft: true}, {End: 2}, | |
| 288 | 312 | }, | |
| @@ -294,35 +318,35 @@ func TestDocumentExtractTrimsPaddingAndJoinsWrappedRows(t *testing.T) { | |||
| 294 | 318 | }, | |
| 295 | 319 | { | |
| 296 | 320 | name: "hard rows keep their newline", | |
| 297 | - | rows: [][]renderer.Cell{pad("ab", 8), pad("cd", 8)}, | |
| 321 | + | rows: [][]renderer.Cell{padRow("ab", 8), padRow("cd", 8)}, | |
| 298 | 322 | bounds: []vt.LineBound{{End: 2}, {End: 2}}, | |
| 299 | 323 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}}, | |
| 300 | 324 | want: "ab\ncd", | |
| 301 | 325 | }, | |
| 302 | 326 | { | |
| 303 | 327 | name: "sparse ranges do not join across a row gap", | |
| 304 | - | rows: [][]renderer.Cell{pad("ab", 8), pad("ignored", 8), pad("cd", 8)}, | |
| 328 | + | rows: [][]renderer.Cell{padRow("ab", 8), padRow("ignored", 8), padRow("cd", 8)}, | |
| 305 | 329 | bounds: []vt.LineBound{{End: 8, Soft: true}, {End: 7}, {End: 2}}, | |
| 306 | 330 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 2, Start: 0, End: 7}}, | |
| 307 | 331 | want: "ab\ncd", | |
| 308 | 332 | }, | |
| 309 | 333 | { | |
| 310 | 334 | name: "invalid trailing range does not prevent final emitted row trimming", | |
| 311 | - | rows: [][]renderer.Cell{pad("ab", 8)}, | |
| 335 | + | rows: [][]renderer.Cell{padRow("ab", 8)}, | |
| 312 | 336 | bounds: []vt.LineBound{{End: 8, Soft: true}}, | |
| 313 | 337 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 99, Start: 0, End: 7}}, | |
| 314 | 338 | want: "ab", | |
| 315 | 339 | }, | |
| 316 | 340 | { | |
| 317 | 341 | name: "blank row survives as an empty line", | |
| 318 | - | rows: [][]renderer.Cell{pad("ab", 8), pad("", 8), pad("cd", 8)}, | |
| 342 | + | rows: [][]renderer.Cell{padRow("ab", 8), padRow("", 8), padRow("cd", 8)}, | |
| 319 | 343 | bounds: []vt.LineBound{{End: 2}, {End: 0}, {End: 2}}, | |
| 320 | 344 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}, {Row: 2, Start: 0, End: 7}}, | |
| 321 | 345 | want: "ab\n\ncd", | |
| 322 | 346 | }, | |
| 323 | 347 | { | |
| 324 | 348 | name: "the last row is trimmed even when soft", | |
| 325 | - | rows: [][]renderer.Cell{pad("ab", 8), pad("cd ", 8)}, | |
| 349 | + | rows: [][]renderer.Cell{padRow("ab", 8), padRow("cd ", 8)}, | |
| 326 | 350 | bounds: []vt.LineBound{{End: 2}, {End: 8, Soft: true}}, | |
| 327 | 351 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}}, | |
| 328 | 352 | want: "ab\ncd", | |
| @@ -333,7 +357,7 @@ func TestDocumentExtractTrimsPaddingAndJoinsWrappedRows(t *testing.T) { | |||
| 333 | 357 | // so End stops at 3 and column 3 is abandoned padding. | |
| 334 | 358 | rows: [][]renderer.Cell{ | |
| 335 | 359 | {{Rune: 'a'}, {Rune: '界'}, {Continuation: true}, renderer.BlankCell()}, | |
| 336 | - | pad("b", 4), | |
| 360 | + | padRow("b", 4), | |
| 337 | 361 | }, | |
| 338 | 362 | bounds: []vt.LineBound{{End: 3, Soft: true}, {End: 1}}, | |
| 339 | 363 | ranges: []CellRange{{Row: 0, Start: 0, End: 3}, {Row: 1, Start: 0, End: 3}}, | |
| @@ -341,7 +365,7 @@ func TestDocumentExtractTrimsPaddingAndJoinsWrappedRows(t *testing.T) { | |||
| 341 | 365 | }, | |
| 342 | 366 | { | |
| 343 | 367 | name: "selection stopping mid soft row", | |
| 344 | - | rows: [][]renderer.Cell{pad("abcdefgh", 8), pad("ij", 8)}, | |
| 368 | + | rows: [][]renderer.Cell{padRow("abcdefgh", 8), padRow("ij", 8)}, | |
| 345 | 369 | bounds: []vt.LineBound{{End: 8, Soft: true}, {End: 2}}, | |
| 346 | 370 | ranges: []CellRange{{Row: 0, Start: 2, End: 4}}, | |
| 347 | 371 | want: "cde", | |
| @@ -236,21 +236,45 @@ func TestDocumentExtractRanges(t *testing.T) { | |||||
| 236 | } | 236 | } | ||
| 237 | } | 237 | } | ||
| 238 | 238 | ||||
| 239 | - | func TestDocumentExtractTrimsPaddingAndJoinsWrappedRows(t *testing.T) { | 239 | + | // padRow blank-fills a terminal row before adding text. |
| 240 | - | // pad renders s into a width-w row, blank-filled like a real terminal grid. | 240 | + | func padRow(s string, w int) []renderer.Cell { |
| 241 | - | pad := func(s string, w int) []renderer.Cell { | 241 | + | cells := make([]renderer.Cell, w) |
| 242 | - | cells := make([]renderer.Cell, w) | 242 | + | for i := range cells { |
| 243 | - | for i := range cells { | 243 | + | cells[i] = renderer.BlankCell() |
| 244 | - | cells[i] = renderer.BlankCell() | 244 | + | } |
| 245 | - | } | 245 | + | for i, r := range []rune(s) { |
| 246 | - | for i, r := range []rune(s) { | 246 | + | if i >= w { |
| 247 | - | if i >= w { | 247 | + | break |
| 248 | - | break | |||
| 249 | - | } | |||
| 250 | - | cells[i] = renderer.Cell{Rune: r} | |||
| 251 | } | 248 | } | ||
| 252 | - | return cells | 249 | + | cells[i] = renderer.Cell{Rune: r} |
| 253 | } | 250 | } | ||
| 251 | + | return cells | |||
| 252 | + | } | |||
| 253 | + | ||||
| 254 | + | func TestDocumentContentEnd(t *testing.T) { | |||
| 255 | + | tests := []struct { | |||
| 256 | + | name string | |||
| 257 | + | cells []renderer.Cell | |||
| 258 | + | bounds []vt.LineBound | |||
| 259 | + | want int | |||
| 260 | + | }{ | |||
| 261 | + | {"hard short", padRow("ab", 8), []vt.LineBound{{End: 2}}, 2}, | |||
| 262 | + | {"inner spaces", padRow("a b", 8), []vt.LineBound{{End: 3}}, 3}, | |||
| 263 | + | {"blank", padRow("", 8), []vt.LineBound{{}}, 0}, | |||
| 264 | + | {"soft trailing spaces", padRow("abc ", 8), []vt.LineBound{{End: 8, Soft: true}}, 8}, | |||
| 265 | + | {"soft partial bound", padRow("abc", 8), []vt.LineBound{{End: 5, Soft: true}}, 5}, | |||
| 266 | + | {"wide glyph", []renderer.Cell{{Rune: 'a'}, {Rune: '界'}, {Continuation: true}, renderer.BlankCell(), renderer.BlankCell(), renderer.BlankCell()}, []vt.LineBound{{End: 3}}, 3}, | |||
| 267 | + | {"nil bounds", padRow("ab", 8), nil, 2}, | |||
| 268 | + | } | |||
| 269 | + | for _, tt := range tests { | |||
| 270 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 271 | + | doc := NewDocument(NewSnapshotFromLines([][]renderer.Cell{tt.cells}, tt.bounds, 8, 1), "") | |||
| 272 | + | require.Equal(t, tt.want, doc.contentEnd(0)) | |||
| 273 | + | }) | |||
| 274 | + | } | |||
| 275 | + | } | |||
| 276 | + | ||||
| 277 | + | func TestDocumentExtractTrimsPaddingAndJoinsWrappedRows(t *testing.T) { | |||
| 254 | 278 | ||||
| 255 | tests := []struct { | 279 | tests := []struct { | ||
| 256 | name string | 280 | name string | ||
| @@ -261,28 +285,28 @@ func TestDocumentExtractTrimsPaddingAndJoinsWrappedRows(t *testing.T) { | |||||
| 261 | }{ | 285 | }{ | ||
| 262 | { | 286 | { | ||
| 263 | name: "hard row drops its padding", | 287 | name: "hard row drops its padding", | ||
| 264 | - | rows: [][]renderer.Cell{pad("ab", 8)}, | 288 | + | rows: [][]renderer.Cell{padRow("ab", 8)}, |
| 265 | bounds: []vt.LineBound{{End: 2}}, | 289 | bounds: []vt.LineBound{{End: 2}}, | ||
| 266 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}}, | 290 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}}, | ||
| 267 | want: "ab", | 291 | want: "ab", | ||
| 268 | }, | 292 | }, | ||
| 269 | { | 293 | { | ||
| 270 | name: "soft row joins without a newline", | 294 | name: "soft row joins without a newline", | ||
| 271 | - | rows: [][]renderer.Cell{pad("abcdefgh", 8), pad("ij", 8)}, | 295 | + | rows: [][]renderer.Cell{padRow("abcdefgh", 8), padRow("ij", 8)}, |
| 272 | bounds: []vt.LineBound{{End: 8, Soft: true}, {End: 2}}, | 296 | bounds: []vt.LineBound{{End: 8, Soft: true}, {End: 2}}, | ||
| 273 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}}, | 297 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}}, | ||
| 274 | want: "abcdefghij", | 298 | want: "abcdefghij", | ||
| 275 | }, | 299 | }, | ||
| 276 | { | 300 | { | ||
| 277 | name: "soft row keeps a real trailing space", | 301 | name: "soft row keeps a real trailing space", | ||
| 278 | - | rows: [][]renderer.Cell{pad("abc ", 8), pad("de", 8)}, | 302 | + | rows: [][]renderer.Cell{padRow("abc ", 8), padRow("de", 8)}, |
| 279 | bounds: []vt.LineBound{{End: 8, Soft: true}, {End: 2}}, | 303 | bounds: []vt.LineBound{{End: 8, Soft: true}, {End: 2}}, | ||
| 280 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}}, | 304 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}}, | ||
| 281 | want: "abc de", | 305 | want: "abc de", | ||
| 282 | }, | 306 | }, | ||
| 283 | { | 307 | { | ||
| 284 | name: "three consecutive soft rows", | 308 | name: "three consecutive soft rows", | ||
| 285 | - | rows: [][]renderer.Cell{pad("aaaa", 4), pad("bbbb", 4), pad("cccc", 4), pad("dd", 4)}, | 309 | + | rows: [][]renderer.Cell{padRow("aaaa", 4), padRow("bbbb", 4), padRow("cccc", 4), padRow("dd", 4)}, |
| 286 | bounds: []vt.LineBound{ | 310 | bounds: []vt.LineBound{ | ||
| 287 | {End: 4, Soft: true}, {End: 4, Soft: true}, {End: 4, Soft: true}, {End: 2}, | 311 | {End: 4, Soft: true}, {End: 4, Soft: true}, {End: 4, Soft: true}, {End: 2}, | ||
| 288 | }, | 312 | }, | ||
| @@ -294,35 +318,35 @@ func TestDocumentExtractTrimsPaddingAndJoinsWrappedRows(t *testing.T) { | |||||
| 294 | }, | 318 | }, | ||
| 295 | { | 319 | { | ||
| 296 | name: "hard rows keep their newline", | 320 | name: "hard rows keep their newline", | ||
| 297 | - | rows: [][]renderer.Cell{pad("ab", 8), pad("cd", 8)}, | 321 | + | rows: [][]renderer.Cell{padRow("ab", 8), padRow("cd", 8)}, |
| 298 | bounds: []vt.LineBound{{End: 2}, {End: 2}}, | 322 | bounds: []vt.LineBound{{End: 2}, {End: 2}}, | ||
| 299 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}}, | 323 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}}, | ||
| 300 | want: "ab\ncd", | 324 | want: "ab\ncd", | ||
| 301 | }, | 325 | }, | ||
| 302 | { | 326 | { | ||
| 303 | name: "sparse ranges do not join across a row gap", | 327 | name: "sparse ranges do not join across a row gap", | ||
| 304 | - | rows: [][]renderer.Cell{pad("ab", 8), pad("ignored", 8), pad("cd", 8)}, | 328 | + | rows: [][]renderer.Cell{padRow("ab", 8), padRow("ignored", 8), padRow("cd", 8)}, |
| 305 | bounds: []vt.LineBound{{End: 8, Soft: true}, {End: 7}, {End: 2}}, | 329 | bounds: []vt.LineBound{{End: 8, Soft: true}, {End: 7}, {End: 2}}, | ||
| 306 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 2, Start: 0, End: 7}}, | 330 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 2, Start: 0, End: 7}}, | ||
| 307 | want: "ab\ncd", | 331 | want: "ab\ncd", | ||
| 308 | }, | 332 | }, | ||
| 309 | { | 333 | { | ||
| 310 | name: "invalid trailing range does not prevent final emitted row trimming", | 334 | name: "invalid trailing range does not prevent final emitted row trimming", | ||
| 311 | - | rows: [][]renderer.Cell{pad("ab", 8)}, | 335 | + | rows: [][]renderer.Cell{padRow("ab", 8)}, |
| 312 | bounds: []vt.LineBound{{End: 8, Soft: true}}, | 336 | bounds: []vt.LineBound{{End: 8, Soft: true}}, | ||
| 313 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 99, Start: 0, End: 7}}, | 337 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 99, Start: 0, End: 7}}, | ||
| 314 | want: "ab", | 338 | want: "ab", | ||
| 315 | }, | 339 | }, | ||
| 316 | { | 340 | { | ||
| 317 | name: "blank row survives as an empty line", | 341 | name: "blank row survives as an empty line", | ||
| 318 | - | rows: [][]renderer.Cell{pad("ab", 8), pad("", 8), pad("cd", 8)}, | 342 | + | rows: [][]renderer.Cell{padRow("ab", 8), padRow("", 8), padRow("cd", 8)}, |
| 319 | bounds: []vt.LineBound{{End: 2}, {End: 0}, {End: 2}}, | 343 | bounds: []vt.LineBound{{End: 2}, {End: 0}, {End: 2}}, | ||
| 320 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}, {Row: 2, Start: 0, End: 7}}, | 344 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}, {Row: 2, Start: 0, End: 7}}, | ||
| 321 | want: "ab\n\ncd", | 345 | want: "ab\n\ncd", | ||
| 322 | }, | 346 | }, | ||
| 323 | { | 347 | { | ||
| 324 | name: "the last row is trimmed even when soft", | 348 | name: "the last row is trimmed even when soft", | ||
| 325 | - | rows: [][]renderer.Cell{pad("ab", 8), pad("cd ", 8)}, | 349 | + | rows: [][]renderer.Cell{padRow("ab", 8), padRow("cd ", 8)}, |
| 326 | bounds: []vt.LineBound{{End: 2}, {End: 8, Soft: true}}, | 350 | bounds: []vt.LineBound{{End: 2}, {End: 8, Soft: true}}, | ||
| 327 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}}, | 351 | ranges: []CellRange{{Row: 0, Start: 0, End: 7}, {Row: 1, Start: 0, End: 7}}, | ||
| 328 | want: "ab\ncd", | 352 | want: "ab\ncd", | ||
| @@ -333,7 +357,7 @@ func TestDocumentExtractTrimsPaddingAndJoinsWrappedRows(t *testing.T) { | |||||
| 333 | // so End stops at 3 and column 3 is abandoned padding. | 357 | // so End stops at 3 and column 3 is abandoned padding. | ||
| 334 | rows: [][]renderer.Cell{ | 358 | rows: [][]renderer.Cell{ | ||
| 335 | {{Rune: 'a'}, {Rune: '界'}, {Continuation: true}, renderer.BlankCell()}, | 359 | {{Rune: 'a'}, {Rune: '界'}, {Continuation: true}, renderer.BlankCell()}, | ||
| 336 | - | pad("b", 4), | 360 | + | padRow("b", 4), |
| 337 | }, | 361 | }, | ||
| 338 | bounds: []vt.LineBound{{End: 3, Soft: true}, {End: 1}}, | 362 | bounds: []vt.LineBound{{End: 3, Soft: true}, {End: 1}}, | ||
| 339 | ranges: []CellRange{{Row: 0, Start: 0, End: 3}, {Row: 1, Start: 0, End: 3}}, | 363 | ranges: []CellRange{{Row: 0, Start: 0, End: 3}, {Row: 1, Start: 0, End: 3}}, | ||
| @@ -341,7 +365,7 @@ func TestDocumentExtractTrimsPaddingAndJoinsWrappedRows(t *testing.T) { | |||||
| 341 | }, | 365 | }, | ||
| 342 | { | 366 | { | ||
| 343 | name: "selection stopping mid soft row", | 367 | name: "selection stopping mid soft row", | ||
| 344 | - | rows: [][]renderer.Cell{pad("abcdefgh", 8), pad("ij", 8)}, | 368 | + | rows: [][]renderer.Cell{padRow("abcdefgh", 8), padRow("ij", 8)}, |
| 345 | bounds: []vt.LineBound{{End: 8, Soft: true}, {End: 2}}, | 369 | bounds: []vt.LineBound{{End: 8, Soft: true}, {End: 2}}, | ||
| 346 | ranges: []CellRange{{Row: 0, Start: 2, End: 4}}, | 370 | ranges: []CellRange{{Row: 0, Start: 2, End: 4}}, | ||
| 347 | want: "cde", | 371 | want: "cde", | ||