@@ -22,6 +22,7 @@ import (
22
22
// Table represents a table instance with content and rendering capabilities.
23
23
type Table struct {
24
24
writer io.Writer // Destination for table output
25
+ counters []tw.Counter // Counters for indices
25
26
rows [][][]string // Row data, supporting multi-line cells
26
27
headers [][]string // Header content
27
28
footers [][]string // Footer content
@@ -510,6 +511,28 @@ func (t *Table) Render() error {
510
511
return t .render ()
511
512
}
512
513
514
+ // Lines returns the total number of lines rendered.
515
+ // This method is only effective if the WithLineCounter() option was used during
516
+ // table initialization and must be called *after* Render().
517
+ // It actively searches for the default tw.LineCounter among all active counters.
518
+ // It returns -1 if the line counter was not enabled.
519
+ func (t * Table ) Lines () int {
520
+ for _ , counter := range t .counters {
521
+ if lc , ok := counter .(* tw.LineCounter ); ok {
522
+ return lc .Total ()
523
+ }
524
+ }
525
+ // use -1 to indicate no line counter is attached
526
+ return - 1
527
+ }
528
+
529
+ // Counters returns the slice of all active counter instances.
530
+ // This is useful when multiple counters are enabled.
531
+ // It must be called *after* Render().
532
+ func (t * Table ) Counters () []tw.Counter {
533
+ return t .counters
534
+ }
535
+
513
536
// Trimmer trims whitespace from a string based on the Table’s configuration.
514
537
// It conditionally applies strings.TrimSpace to the input string if the TrimSpace behavior
515
538
// is enabled in t.config.Behavior, otherwise returning the string unchanged. This method
@@ -1361,20 +1384,38 @@ func (t *Table) prepareWithMerges(content [][]string, config tw.CellConfig, posi
1361
1384
func (t * Table ) render () error {
1362
1385
t .ensureInitialized ()
1363
1386
1387
+ // Save the original writer and schedule its restoration upon function exit.
1388
+ // This guarantees the table's writer is restored even if errors occur.
1389
+ originalWriter := t .writer
1390
+ defer func () {
1391
+ t .writer = originalWriter
1392
+ }()
1393
+
1394
+ // If a counter is active, wrap the writer in a MultiWriter.
1395
+ if len (t .counters ) > 0 {
1396
+ // The slice must be of type io.Writer.
1397
+ // Start it with the original destination writer.
1398
+ allWriters := []io.Writer {originalWriter }
1399
+
1400
+ // Append each counter to the slice of writers.
1401
+ for _ , c := range t .counters {
1402
+ allWriters = append (allWriters , c )
1403
+ }
1404
+
1405
+ // Create a MultiWriter that broadcasts to the original writer AND all counters.
1406
+ t .writer = io .MultiWriter (allWriters ... )
1407
+ }
1408
+
1364
1409
if t .config .Stream .Enable {
1365
1410
t .logger .Warn ("Render() called in streaming mode. Use Start/Append/Close methods instead." )
1366
1411
return errors .New ("render called in streaming mode; use Start/Append/Close" )
1367
1412
}
1368
1413
1369
- // Calculate and cache numCols for THIS batch render pass
1370
- t .batchRenderNumCols = t .maxColumns () // Calculate ONCE
1371
- t .isBatchRenderNumColsSet = true // Mark the cache as active for this render pass
1372
- t .logger .Debugf ("Render(): Set batchRenderNumCols to %d and isBatchRenderNumColsSet to true." , t .batchRenderNumCols )
1373
-
1414
+ // Calculate and cache the column count for this specific batch render pass.
1415
+ t .batchRenderNumCols = t .maxColumns ()
1416
+ t .isBatchRenderNumColsSet = true
1374
1417
defer func () {
1375
1418
t .isBatchRenderNumColsSet = false
1376
- // t.batchRenderNumCols = 0; // Optional: reset to 0, or leave as is.
1377
- // Since isBatchRenderNumColsSet is false, its value won't be used by getNumColsToUse.
1378
1419
t .logger .Debugf ("Render(): Cleared isBatchRenderNumColsSet to false (batchRenderNumCols was %d)." , t .batchRenderNumCols )
1379
1420
}()
1380
1421
@@ -1383,9 +1424,10 @@ func (t *Table) render() error {
1383
1424
(t .caption .Spot >= tw .SpotTopLeft && t .caption .Spot <= tw .SpotBottomRight )
1384
1425
1385
1426
var tableStringBuffer * strings.Builder
1386
- targetWriter := t .writer
1387
- originalWriter := t .writer // Save original writer for restoration if needed
1427
+ targetWriter := t .writer // Can be the original writer or the MultiWriter.
1388
1428
1429
+ // If a caption is present, the main table content must be rendered to an
1430
+ // in-memory buffer first to calculate its final width.
1389
1431
if isTopOrBottomCaption {
1390
1432
tableStringBuffer = & strings.Builder {}
1391
1433
targetWriter = tableStringBuffer
@@ -1394,17 +1436,15 @@ func (t *Table) render() error {
1394
1436
t .logger .Debugf ("No caption detected. Rendering table core directly to writer." )
1395
1437
}
1396
1438
1397
- // Render Table Core
1439
+ // Point the table's writer to the target (either the final destination or the buffer).
1398
1440
t .writer = targetWriter
1399
1441
ctx , mctx , err := t .prepareContexts ()
1400
1442
if err != nil {
1401
- t .writer = originalWriter
1402
1443
t .logger .Errorf ("prepareContexts failed: %v" , err )
1403
1444
return errors .Newf ("failed to prepare table contexts" ).Wrap (err )
1404
1445
}
1405
1446
1406
1447
if err := ctx .renderer .Start (t .writer ); err != nil {
1407
- t .writer = originalWriter
1408
1448
t .logger .Errorf ("Renderer Start() error: %v" , err )
1409
1449
return errors .Newf ("renderer start failed" ).Wrap (err )
1410
1450
}
@@ -1436,18 +1476,21 @@ func (t *Table) render() error {
1436
1476
renderError = true
1437
1477
}
1438
1478
1439
- t .writer = originalWriter // Restore original writer
1479
+ // Restore the writer to the original for the caption-handling logic.
1480
+ // This is necessary because the caption must be written to the final
1481
+ // destination, not the temporary buffer used for the table body.
1482
+ t .writer = originalWriter
1440
1483
1441
1484
if renderError {
1442
- return firstRenderErr // Return error from core rendering if any
1485
+ return firstRenderErr
1443
1486
}
1444
1487
1445
- // Caption Handling & Final Output ---
1488
+ // Caption Handling & Final Output
1446
1489
if isTopOrBottomCaption {
1447
1490
renderedTableContent := tableStringBuffer .String ()
1448
1491
t .logger .Debugf ("[Render] Table core buffer length: %d" , len (renderedTableContent ))
1449
1492
1450
- // Check if the buffer is empty AND borders are enabled
1493
+ // Handle edge case where table is empty but should have borders.
1451
1494
shouldHaveBorders := t .renderer != nil && (t .renderer .Config ().Borders .Top .Enabled () || t .renderer .Config ().Borders .Bottom .Enabled ())
1452
1495
if len (renderedTableContent ) == 0 && shouldHaveBorders {
1453
1496
var sb strings.Builder
@@ -1499,7 +1542,7 @@ func (t *Table) render() error {
1499
1542
1500
1543
t .hasPrinted = true
1501
1544
t .logger .Info ("Render() completed." )
1502
- return nil // Success
1545
+ return nil
1503
1546
}
1504
1547
1505
1548
// renderFooter renders the table's footer section with borders and padding.
0 commit comments