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 }