Skip to content

Handling of ASCII HT "\t" in OutputLinePrefix #18

@erikb495

Description

@erikb495

I was having trouble with the handling of tabs in OutputLinePrefix. This patch adds a TabWidth option which defaults to 8.

	// TabWidth sets the perceived size of a `\t` (horizontal tab).
	// This is used in the computation of the width of OutputLinePrefix
	// (only).  Setting TabWidth to 0 disables TabWidth calculations.
	// Default: 8
	TabWidth int

The handling of tabs has always been a problem in UNIX. It starts going bad at the tty driver and goes downhill from there. I assume it's just as bad in Linux, BSD, and Mach based systems like OS-X.

Here's the patch with all of the debugging code left in, but turned off. It's based on "github.com/bbrks/wrap/v2@v2.5.0".

*** ./wrapper.go	2025/07/26 13:49:43	1.1
--- ./wrapper.go	2025/07/26 21:18:22
***************
*** 1,6 ****
--- 1,7 ----
  package wrap
  
  import (
+ 	"log"
  	"strings"
  	"unicode/utf8"
  )
***************
*** 8,13 ****
--- 9,15 ----
  const (
  	defaultBreakpoints = " -"
  	defaultNewline     = "\n"
+ 	defaultTabWidth    = 8
  )
  
  // Wrapper contains settings for customisable word-wrapping.
***************
*** 30,37 ****
  	// Default: ""
  	OutputLineSuffix string
  
! 	// LimitIncludesPrefixSuffix can be set to false if you don't want prefixes
! 	// and suffixes to be included in the length limits.
  	// Default: true
  	LimitIncludesPrefixSuffix bool
  
--- 32,40 ----
  	// Default: ""
  	OutputLineSuffix string
  
! 	// LimitIncludesPrefixSuffix can be set to false if you don't
! 	// want prefixes and suffixes to be included in the length
! 	// limits.
  	// Default: true
  	LimitIncludesPrefixSuffix bool
  
***************
*** 50,57 ****
  	// Default: false
  	StripTrailingNewline bool
  
! 	// CutLongWords will cause a hard-wrap in the middle of a word if the word's length exceeds the given limit.
  	CutLongWords bool
  }
  
  // NewWrapper returns a new instance of a Wrapper initialised with defaults.
--- 53,68 ----
  	// Default: false
  	StripTrailingNewline bool
  
! 	// CutLongWords will cause a hard-wrap in the middle of a word if
! 	// the word's length exceeds the given limit.
! 	// Default: false
  	CutLongWords bool
+ 
+ 	// TabWidth sets the perceived size of a `\t` (horizontal tab).
+ 	// This is used in the computation of the width of OutputLinePrefix
+ 	// (only).  Setting TabWidth to 0 disables TabWidth calculations.
+ 	// Default: 8
+ 	TabWidth int
  }
  
  // NewWrapper returns a new instance of a Wrapper initialised with defaults.
***************
*** 59,64 ****
--- 70,76 ----
  	return Wrapper{
  		Breakpoints:               defaultBreakpoints,
  		Newline:                   defaultNewline,
+ 		TabWidth:                  defaultTabWidth,
  		LimitIncludesPrefixSuffix: true,
  	}
  }
***************
*** 75,81 ****
  	// Subtract the length of the prefix and suffix from the limit
  	// so we don't break length limits when using them.
  	if w.LimitIncludesPrefixSuffix {
! 		limit -= utf8.RuneCountInString(w.OutputLinePrefix) + utf8.RuneCountInString(w.OutputLineSuffix)
  	}
  
  	var ret string
--- 87,115 ----
  	// Subtract the length of the prefix and suffix from the limit
  	// so we don't break length limits when using them.
  	if w.LimitIncludesPrefixSuffix {
! 		totalTabWidth := 0
! 		effectiveOutputLinePrefixWidth :=
! 			utf8.RuneCountInString(w.OutputLinePrefix)
! 		if w.TabWidth > 0 {
! 			if false {
! 				log.Printf("Build #3a effectiveOutputLinePrefixWidth=%d\n",
! 					effectiveOutputLinePrefixWidth)
! 			}
! 			totalTabWidth =
! 				strings.Count(w.OutputLinePrefix, "\t") *
! 					(w.TabWidth - 1) // Minus 1 for the rune itself.
! 			effectiveOutputLinePrefixWidth += totalTabWidth
! 			if false {
! 				log.Printf("Build #3b totalTabWidth=%d\n", totalTabWidth)
! 				log.Printf("Build #3c effectiveOutputLinePrefixWidth(updated)=%d\n",
! 					effectiveOutputLinePrefixWidth)
! 				log.Printf("Build #3d strings.Count(w.OutputLinePrefix, '\\t')=%d\n",
! 					strings.Count(w.OutputLinePrefix, "\t"))
! 				log.Printf("Build #3e:\t %v \n", []byte(w.OutputLinePrefix))
! 			}
! 		}
! 		limit -= effectiveOutputLinePrefixWidth +
! 			utf8.RuneCountInString(w.OutputLineSuffix)
  	}
  
  	var ret string

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions