github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/cmd/nin/line_printer.go (about)

     1  // Copyright 2013 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"runtime"
    21  )
    22  
    23  // Prints lines of text, possibly overprinting previously printed lines
    24  // if the terminal supports it.
    25  type linePrinter struct {
    26  	// Whether we can do fancy terminal control codes.
    27  	smartTerminal bool
    28  
    29  	// Whether we can use ISO 6429 (ANSI) color sequences.
    30  	supportsColor bool
    31  
    32  	// Whether the caret is at the beginning of a blank line.
    33  	haveBlankLine bool
    34  
    35  	// Whether console is locked.
    36  	consoleLocked bool
    37  
    38  	// Buffered current line while console is locked.
    39  	lineBuffer string
    40  
    41  	// Buffered line type while console is locked.
    42  	elide bool
    43  
    44  	// Buffered console output while console is locked.
    45  	outputBuffer string
    46  
    47  	//console *void
    48  }
    49  
    50  func (l *linePrinter) isSmartTerminal() bool {
    51  	return l.smartTerminal
    52  }
    53  func (l *linePrinter) setSmartTerminal(smart bool) {
    54  	l.smartTerminal = smart
    55  }
    56  
    57  func newLinePrinter() linePrinter {
    58  	l := linePrinter{
    59  		haveBlankLine: true,
    60  	}
    61  	/*
    62  		if os.Getenv("TERM") != "dumb" {
    63  			if runtime.GOOS != "windows" {
    64  				// Don't panic for now.
    65  				//l.smartTerminal = isatty(1)
    66  			} else {
    67  				// Don't panic for now.
    68  				//console = GetStdHandle(STD_OUTPUT_HANDLE)
    69  				//var csbi CONSOLE_SCREEN_BUFFER_INFO
    70  				//smartTerminal = GetConsoleScreenBufferInfo(console, &csbi)
    71  			}
    72  		}
    73  	*/
    74  	l.supportsColor = l.smartTerminal
    75  	if !l.supportsColor {
    76  		f := os.Getenv("CLICOLOR_FORCE")
    77  		l.supportsColor = f != "" && f != "0"
    78  	}
    79  	// Try enabling ANSI escape sequence support on Windows 10 terminals.
    80  	if runtime.GOOS == "windows" {
    81  		if l.supportsColor {
    82  			panic("TODO")
    83  			/*
    84  				var mode DWORD
    85  				if GetConsoleMode(console, &mode) {
    86  					if !SetConsoleMode(console, mode|ENABLE_VIRTUAL_TERMINAL_PROCESSING) {
    87  						supportsColor = false
    88  					}
    89  				}
    90  			*/
    91  		}
    92  	}
    93  	return l
    94  }
    95  
    96  // Overprints the current line. If type is ELIDE, elides toPrint to fit on
    97  // one line.
    98  func (l *linePrinter) Print(toPrint string, elide bool) {
    99  	if l.consoleLocked {
   100  		l.lineBuffer = toPrint
   101  		l.elide = elide
   102  		return
   103  	}
   104  
   105  	if l.smartTerminal {
   106  		fmt.Printf("\r") // Print over previous line, if any.
   107  		// On Windows, calling a C library function writing to stdout also handles
   108  		// pausing the executable when the "Pause" key or Ctrl-S is pressed.
   109  	}
   110  
   111  	if l.smartTerminal && elide {
   112  		l.haveBlankLine = false
   113  		if runtime.GOOS == "windows" {
   114  			panic("TODO")
   115  			/*
   116  				var csbi CONSOLE_SCREEN_BUFFER_INFO
   117  				GetConsoleScreenBufferInfo(l.console, &csbi)
   118  				toPrint = ElideMiddle(toPrint, csbi.dwSize.X)
   119  				if l.supportsColor {
   120  					// this means ENABLE_VIRTUAL_TERMINAL_PROCESSING
   121  					// succeeded
   122  					fmt.Printf("%s\x1B[K", toPrint) // Clear to end of line.
   123  					fflush(stdout)
   124  				} else {
   125  					// We don't want to have the cursor spamming back and forth, so instead of
   126  					// printf use WriteConsoleOutput which updates the contents of the buffer,
   127  					// but doesn't move the cursor position.
   128  					bufSize := COORD{csbi.dwSize.X, 1}
   129  					zeroZero := COORD{0, 0}
   130  					target := SMALL_RECT{csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y,
   131  						csbi.dwCursorPosition.X + csbi.dwSize.X - 1,
   132  						csbi.dwCursorPosition.Y}
   133  					charData := make([]CHAR_INFO, csbi.dwSize.X)
   134  					for i := 0; i < csbi.dwSize.X; i++ {
   135  						if i < len(toPrint) {
   136  							charData[i].Char.AsciiChar = toPrint[i]
   137  						} else {
   138  							charData[i].Char.AsciiChar = ' '
   139  						}
   140  						charData[i].Attributes = csbi.wAttributes
   141  					}
   142  					WriteConsoleOutput(l.console, &charData[0], bufSize, zeroZero, &target)
   143  				}
   144  			*/
   145  		} else {
   146  			panic("TODO")
   147  			/*
   148  				// Limit output to width of the terminal if provided so we don't cause
   149  				// line-wrapping.
   150  				var size winsize
   151  				if ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) == 0 && size.wsCol {
   152  					toPrint = ElideMiddle(toPrint, size.wsCol)
   153  				}
   154  				fmt.Printf("%s", toPrint)
   155  				fmt.Printf("\x1B[K") // Clear to end of line.
   156  				fflush(stdout)
   157  			*/
   158  		}
   159  	} else {
   160  		fmt.Printf("%s\n", toPrint)
   161  	}
   162  }
   163  
   164  // Print the given data to the console, or buffer it if it is locked.
   165  func (l *linePrinter) PrintOrBuffer(data string) {
   166  	if l.consoleLocked {
   167  		l.outputBuffer += data
   168  	} else {
   169  		// Avoid printf and C strings, since the actual output might contain null
   170  		// bytes like UTF-16 does (yuck).
   171  		_, _ = os.Stdout.WriteString(data)
   172  	}
   173  }
   174  
   175  // Prints a string on a new line, not overprinting previous output.
   176  func (l *linePrinter) PrintOnNewLine(toPrint string) {
   177  	if l.consoleLocked && len(l.lineBuffer) != 0 {
   178  		l.outputBuffer += l.lineBuffer
   179  		l.outputBuffer += "\n"
   180  		l.lineBuffer = ""
   181  	}
   182  	if !l.haveBlankLine {
   183  		l.PrintOrBuffer("\n")
   184  	}
   185  	if len(toPrint) != 0 {
   186  		l.PrintOrBuffer(toPrint)
   187  	}
   188  	l.haveBlankLine = len(toPrint) == 0 || toPrint[0] == '\n'
   189  }
   190  
   191  // Lock or unlock the console.  Any output sent to the LinePrinter while the
   192  // console is locked will not be printed until it is unlocked.
   193  func (l *linePrinter) SetConsoleLocked(locked bool) {
   194  	if locked == l.consoleLocked {
   195  		return
   196  	}
   197  
   198  	if locked {
   199  		l.PrintOnNewLine("")
   200  	}
   201  
   202  	l.consoleLocked = locked
   203  
   204  	if !locked {
   205  		l.PrintOnNewLine(l.outputBuffer)
   206  		if len(l.lineBuffer) != 0 {
   207  			l.Print(l.lineBuffer, l.elide)
   208  		}
   209  		l.outputBuffer = ""
   210  		l.lineBuffer = ""
   211  	}
   212  }