package printer12import (3"io"4"text/tabwriter"5)67// A trimmer is an io.Writer which filters tabwriter.Escape characters,8// trailing blanks and tabs from lines, and converting \f and \v characters9// into \n and \t (if no text/tabwriter is used when printing).10//11// Text wrapped by tabwriter.Escape characters is written to the underlying12// io.Writer unmodified.13type trimmer struct {14next io.Writer15state int16space []byte17}1819const (20trimStateSpace = iota // Trimmer is reading space characters21trimStateEscape // Trimmer is reading escaped characters22trimStateText // Trimmer is reading text23)2425func (t *trimmer) discardWhitespace() {26t.state = trimStateSpace27t.space = t.space[0:0]28}2930func (t *trimmer) Write(data []byte) (n int, err error) {31// textStart holds the index of the start of a chunk of text not containing32// whitespace. It is reset every time a new chunk of text is encountered.33var textStart int3435for off, b := range data {36// Convert \v to \t37if b == '\v' {38b = '\t'39}4041switch t.state {42case trimStateSpace:43// Accumulate tabs and spaces in t.space until finding a non-tab or44// non-space character.45//46// If we find a newline, we write it directly and discard our pending47// whitespace (so that trailing whitespace up to the newline is ignored).48//49// If we find a tabwriter.Escape or text character we transition states.50switch b {51case '\t', ' ':52t.space = append(t.space, b)53case '\n', '\f':54// Discard all unwritten whitespace before the end of the line and write55// a newline.56t.discardWhitespace()57_, err = t.next.Write([]byte("\n"))58case tabwriter.Escape:59_, err = t.next.Write(t.space)60t.state = trimStateEscape61textStart = off + 1 // Skip escape character62default:63// Non-space character. Write our pending whitespace64// and then move to text state.65_, err = t.next.Write(t.space)66t.state = trimStateText67textStart = off68}6970case trimStateText:71// We're reading a chunk of text. Accumulate characters in the chunk72// until we find a whitespace character or a tabwriter.Escape.73switch b {74case '\t', ' ':75_, err = t.next.Write(data[textStart:off])76t.discardWhitespace()77t.space = append(t.space, b)78case '\n', '\f':79_, err = t.next.Write(data[textStart:off])80t.discardWhitespace()81if err == nil {82_, err = t.next.Write([]byte("\n"))83}84case tabwriter.Escape:85_, err = t.next.Write(data[textStart:off])86t.state = trimStateEscape87textStart = off + 1 // +1: skip tabwriter.Escape88}8990case trimStateEscape:91// Accumulate everything until finding the closing tabwriter.Escape.92if b == tabwriter.Escape {93_, err = t.next.Write(data[textStart:off])94t.discardWhitespace()95}9697default:98panic("unreachable")99}100if err != nil {101return off, err102}103}104n = len(data)105106// Flush the remainder of the text (as long as it's not whitespace).107switch t.state {108case trimStateEscape, trimStateText:109_, err = t.next.Write(data[textStart:n])110t.discardWhitespace()111}112113return114}115116117