1*333d2b36SAndroid Build Coastguard Worker// Copyright 2019 Google Inc. All rights reserved. 2*333d2b36SAndroid Build Coastguard Worker// 3*333d2b36SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*333d2b36SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*333d2b36SAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*333d2b36SAndroid Build Coastguard Worker// 7*333d2b36SAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*333d2b36SAndroid Build Coastguard Worker// 9*333d2b36SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*333d2b36SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*333d2b36SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*333d2b36SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*333d2b36SAndroid Build Coastguard Worker// limitations under the License. 14*333d2b36SAndroid Build Coastguard Worker 15*333d2b36SAndroid Build Coastguard Workerpackage terminal 16*333d2b36SAndroid Build Coastguard Worker 17*333d2b36SAndroid Build Coastguard Workerimport ( 18*333d2b36SAndroid Build Coastguard Worker "fmt" 19*333d2b36SAndroid Build Coastguard Worker "io" 20*333d2b36SAndroid Build Coastguard Worker "os" 21*333d2b36SAndroid Build Coastguard Worker "os/signal" 22*333d2b36SAndroid Build Coastguard Worker "strconv" 23*333d2b36SAndroid Build Coastguard Worker "strings" 24*333d2b36SAndroid Build Coastguard Worker "sync" 25*333d2b36SAndroid Build Coastguard Worker "syscall" 26*333d2b36SAndroid Build Coastguard Worker "time" 27*333d2b36SAndroid Build Coastguard Worker 28*333d2b36SAndroid Build Coastguard Worker "android/soong/ui/status" 29*333d2b36SAndroid Build Coastguard Worker) 30*333d2b36SAndroid Build Coastguard Worker 31*333d2b36SAndroid Build Coastguard Workerconst tableHeightEnVar = "SOONG_UI_TABLE_HEIGHT" 32*333d2b36SAndroid Build Coastguard Worker 33*333d2b36SAndroid Build Coastguard Workertype actionTableEntry struct { 34*333d2b36SAndroid Build Coastguard Worker action *status.Action 35*333d2b36SAndroid Build Coastguard Worker startTime time.Time 36*333d2b36SAndroid Build Coastguard Worker} 37*333d2b36SAndroid Build Coastguard Worker 38*333d2b36SAndroid Build Coastguard Workertype smartStatusOutput struct { 39*333d2b36SAndroid Build Coastguard Worker writer io.Writer 40*333d2b36SAndroid Build Coastguard Worker formatter formatter 41*333d2b36SAndroid Build Coastguard Worker 42*333d2b36SAndroid Build Coastguard Worker lock sync.Mutex 43*333d2b36SAndroid Build Coastguard Worker 44*333d2b36SAndroid Build Coastguard Worker haveBlankLine bool 45*333d2b36SAndroid Build Coastguard Worker 46*333d2b36SAndroid Build Coastguard Worker tableMode bool 47*333d2b36SAndroid Build Coastguard Worker tableHeight int 48*333d2b36SAndroid Build Coastguard Worker requestedTableHeight int 49*333d2b36SAndroid Build Coastguard Worker termWidth, termHeight int 50*333d2b36SAndroid Build Coastguard Worker 51*333d2b36SAndroid Build Coastguard Worker runningActions []actionTableEntry 52*333d2b36SAndroid Build Coastguard Worker ticker *time.Ticker 53*333d2b36SAndroid Build Coastguard Worker done chan bool 54*333d2b36SAndroid Build Coastguard Worker sigwinch chan os.Signal 55*333d2b36SAndroid Build Coastguard Worker sigwinchHandled chan bool 56*333d2b36SAndroid Build Coastguard Worker 57*333d2b36SAndroid Build Coastguard Worker // Once there is a failure, we stop printing command output so the error 58*333d2b36SAndroid Build Coastguard Worker // is easier to find 59*333d2b36SAndroid Build Coastguard Worker haveFailures bool 60*333d2b36SAndroid Build Coastguard Worker // If we are dropping errors, then at the end, we report a message to go 61*333d2b36SAndroid Build Coastguard Worker // look in the verbose log if you want that command output. 62*333d2b36SAndroid Build Coastguard Worker postFailureActionCount int 63*333d2b36SAndroid Build Coastguard Worker} 64*333d2b36SAndroid Build Coastguard Worker 65*333d2b36SAndroid Build Coastguard Worker// NewSmartStatusOutput returns a StatusOutput that represents the 66*333d2b36SAndroid Build Coastguard Worker// current build status similarly to Ninja's built-in terminal 67*333d2b36SAndroid Build Coastguard Worker// output. 68*333d2b36SAndroid Build Coastguard Workerfunc NewSmartStatusOutput(w io.Writer, formatter formatter) status.StatusOutput { 69*333d2b36SAndroid Build Coastguard Worker s := &smartStatusOutput{ 70*333d2b36SAndroid Build Coastguard Worker writer: w, 71*333d2b36SAndroid Build Coastguard Worker formatter: formatter, 72*333d2b36SAndroid Build Coastguard Worker 73*333d2b36SAndroid Build Coastguard Worker haveBlankLine: true, 74*333d2b36SAndroid Build Coastguard Worker 75*333d2b36SAndroid Build Coastguard Worker tableMode: true, 76*333d2b36SAndroid Build Coastguard Worker 77*333d2b36SAndroid Build Coastguard Worker done: make(chan bool), 78*333d2b36SAndroid Build Coastguard Worker sigwinch: make(chan os.Signal), 79*333d2b36SAndroid Build Coastguard Worker } 80*333d2b36SAndroid Build Coastguard Worker 81*333d2b36SAndroid Build Coastguard Worker if env, ok := os.LookupEnv(tableHeightEnVar); ok { 82*333d2b36SAndroid Build Coastguard Worker h, _ := strconv.Atoi(env) 83*333d2b36SAndroid Build Coastguard Worker s.tableMode = h > 0 84*333d2b36SAndroid Build Coastguard Worker s.requestedTableHeight = h 85*333d2b36SAndroid Build Coastguard Worker } 86*333d2b36SAndroid Build Coastguard Worker 87*333d2b36SAndroid Build Coastguard Worker if w, h, ok := termSize(s.writer); ok { 88*333d2b36SAndroid Build Coastguard Worker s.termWidth, s.termHeight = w, h 89*333d2b36SAndroid Build Coastguard Worker s.computeTableHeight() 90*333d2b36SAndroid Build Coastguard Worker } else { 91*333d2b36SAndroid Build Coastguard Worker s.tableMode = false 92*333d2b36SAndroid Build Coastguard Worker } 93*333d2b36SAndroid Build Coastguard Worker 94*333d2b36SAndroid Build Coastguard Worker if s.tableMode { 95*333d2b36SAndroid Build Coastguard Worker // Add empty lines at the bottom of the screen to scroll back the existing history 96*333d2b36SAndroid Build Coastguard Worker // and make room for the action table. 97*333d2b36SAndroid Build Coastguard Worker // TODO: read the cursor position to see if the empty lines are necessary? 98*333d2b36SAndroid Build Coastguard Worker for i := 0; i < s.tableHeight; i++ { 99*333d2b36SAndroid Build Coastguard Worker fmt.Fprintln(w) 100*333d2b36SAndroid Build Coastguard Worker } 101*333d2b36SAndroid Build Coastguard Worker 102*333d2b36SAndroid Build Coastguard Worker // Hide the cursor to prevent seeing it bouncing around 103*333d2b36SAndroid Build Coastguard Worker fmt.Fprintf(s.writer, ansi.hideCursor()) 104*333d2b36SAndroid Build Coastguard Worker 105*333d2b36SAndroid Build Coastguard Worker // Configure the empty action table 106*333d2b36SAndroid Build Coastguard Worker s.actionTable() 107*333d2b36SAndroid Build Coastguard Worker 108*333d2b36SAndroid Build Coastguard Worker // Start a tick to update the action table periodically 109*333d2b36SAndroid Build Coastguard Worker s.startActionTableTick() 110*333d2b36SAndroid Build Coastguard Worker } 111*333d2b36SAndroid Build Coastguard Worker 112*333d2b36SAndroid Build Coastguard Worker s.startSigwinch() 113*333d2b36SAndroid Build Coastguard Worker 114*333d2b36SAndroid Build Coastguard Worker return s 115*333d2b36SAndroid Build Coastguard Worker} 116*333d2b36SAndroid Build Coastguard Worker 117*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) Message(level status.MsgLevel, message string) { 118*333d2b36SAndroid Build Coastguard Worker if level < status.StatusLvl { 119*333d2b36SAndroid Build Coastguard Worker return 120*333d2b36SAndroid Build Coastguard Worker } 121*333d2b36SAndroid Build Coastguard Worker 122*333d2b36SAndroid Build Coastguard Worker str := s.formatter.message(level, message) 123*333d2b36SAndroid Build Coastguard Worker 124*333d2b36SAndroid Build Coastguard Worker s.lock.Lock() 125*333d2b36SAndroid Build Coastguard Worker defer s.lock.Unlock() 126*333d2b36SAndroid Build Coastguard Worker 127*333d2b36SAndroid Build Coastguard Worker if level > status.StatusLvl { 128*333d2b36SAndroid Build Coastguard Worker s.print(str) 129*333d2b36SAndroid Build Coastguard Worker } else { 130*333d2b36SAndroid Build Coastguard Worker s.statusLine(str) 131*333d2b36SAndroid Build Coastguard Worker } 132*333d2b36SAndroid Build Coastguard Worker} 133*333d2b36SAndroid Build Coastguard Worker 134*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) StartAction(action *status.Action, counts status.Counts) { 135*333d2b36SAndroid Build Coastguard Worker startTime := time.Now() 136*333d2b36SAndroid Build Coastguard Worker 137*333d2b36SAndroid Build Coastguard Worker str := action.Description 138*333d2b36SAndroid Build Coastguard Worker if str == "" { 139*333d2b36SAndroid Build Coastguard Worker str = action.Command 140*333d2b36SAndroid Build Coastguard Worker } 141*333d2b36SAndroid Build Coastguard Worker 142*333d2b36SAndroid Build Coastguard Worker progress := s.formatter.progress(counts) 143*333d2b36SAndroid Build Coastguard Worker 144*333d2b36SAndroid Build Coastguard Worker s.lock.Lock() 145*333d2b36SAndroid Build Coastguard Worker defer s.lock.Unlock() 146*333d2b36SAndroid Build Coastguard Worker 147*333d2b36SAndroid Build Coastguard Worker s.runningActions = append(s.runningActions, actionTableEntry{ 148*333d2b36SAndroid Build Coastguard Worker action: action, 149*333d2b36SAndroid Build Coastguard Worker startTime: startTime, 150*333d2b36SAndroid Build Coastguard Worker }) 151*333d2b36SAndroid Build Coastguard Worker 152*333d2b36SAndroid Build Coastguard Worker s.statusLine(progress + str) 153*333d2b36SAndroid Build Coastguard Worker} 154*333d2b36SAndroid Build Coastguard Worker 155*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) FinishAction(result status.ActionResult, counts status.Counts) { 156*333d2b36SAndroid Build Coastguard Worker str := result.Description 157*333d2b36SAndroid Build Coastguard Worker if str == "" { 158*333d2b36SAndroid Build Coastguard Worker str = result.Command 159*333d2b36SAndroid Build Coastguard Worker } 160*333d2b36SAndroid Build Coastguard Worker 161*333d2b36SAndroid Build Coastguard Worker progress := s.formatter.progress(counts) + str 162*333d2b36SAndroid Build Coastguard Worker 163*333d2b36SAndroid Build Coastguard Worker output := s.formatter.result(result) 164*333d2b36SAndroid Build Coastguard Worker 165*333d2b36SAndroid Build Coastguard Worker s.lock.Lock() 166*333d2b36SAndroid Build Coastguard Worker defer s.lock.Unlock() 167*333d2b36SAndroid Build Coastguard Worker 168*333d2b36SAndroid Build Coastguard Worker for i, runningAction := range s.runningActions { 169*333d2b36SAndroid Build Coastguard Worker if runningAction.action == result.Action { 170*333d2b36SAndroid Build Coastguard Worker s.runningActions = append(s.runningActions[:i], s.runningActions[i+1:]...) 171*333d2b36SAndroid Build Coastguard Worker break 172*333d2b36SAndroid Build Coastguard Worker } 173*333d2b36SAndroid Build Coastguard Worker } 174*333d2b36SAndroid Build Coastguard Worker 175*333d2b36SAndroid Build Coastguard Worker s.statusLine(progress) 176*333d2b36SAndroid Build Coastguard Worker 177*333d2b36SAndroid Build Coastguard Worker // Stop printing when there are failures, but don't skip actions that also have their own errors. 178*333d2b36SAndroid Build Coastguard Worker if output != "" { 179*333d2b36SAndroid Build Coastguard Worker if !s.haveFailures || result.Error != nil { 180*333d2b36SAndroid Build Coastguard Worker s.requestLine() 181*333d2b36SAndroid Build Coastguard Worker s.print(output) 182*333d2b36SAndroid Build Coastguard Worker } else { 183*333d2b36SAndroid Build Coastguard Worker s.postFailureActionCount++ 184*333d2b36SAndroid Build Coastguard Worker } 185*333d2b36SAndroid Build Coastguard Worker } 186*333d2b36SAndroid Build Coastguard Worker 187*333d2b36SAndroid Build Coastguard Worker if result.Error != nil { 188*333d2b36SAndroid Build Coastguard Worker s.haveFailures = true 189*333d2b36SAndroid Build Coastguard Worker } 190*333d2b36SAndroid Build Coastguard Worker} 191*333d2b36SAndroid Build Coastguard Worker 192*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) Flush() { 193*333d2b36SAndroid Build Coastguard Worker if s.tableMode { 194*333d2b36SAndroid Build Coastguard Worker // Stop the action table tick outside of the lock to avoid lock ordering issues between s.done and 195*333d2b36SAndroid Build Coastguard Worker // s.lock, the goroutine in startActionTableTick can get blocked on the lock and be unable to read 196*333d2b36SAndroid Build Coastguard Worker // from the channel. 197*333d2b36SAndroid Build Coastguard Worker s.stopActionTableTick() 198*333d2b36SAndroid Build Coastguard Worker } 199*333d2b36SAndroid Build Coastguard Worker 200*333d2b36SAndroid Build Coastguard Worker s.lock.Lock() 201*333d2b36SAndroid Build Coastguard Worker defer s.lock.Unlock() 202*333d2b36SAndroid Build Coastguard Worker 203*333d2b36SAndroid Build Coastguard Worker s.stopSigwinch() 204*333d2b36SAndroid Build Coastguard Worker 205*333d2b36SAndroid Build Coastguard Worker if s.postFailureActionCount > 0 { 206*333d2b36SAndroid Build Coastguard Worker s.requestLine() 207*333d2b36SAndroid Build Coastguard Worker if s.postFailureActionCount == 1 { 208*333d2b36SAndroid Build Coastguard Worker s.print(fmt.Sprintf("There was 1 action that completed after the action that failed. See verbose.log.gz for its output.")) 209*333d2b36SAndroid Build Coastguard Worker } else { 210*333d2b36SAndroid Build Coastguard Worker s.print(fmt.Sprintf("There were %d actions that completed after the action that failed. See verbose.log.gz for their output.", s.postFailureActionCount)) 211*333d2b36SAndroid Build Coastguard Worker } 212*333d2b36SAndroid Build Coastguard Worker } 213*333d2b36SAndroid Build Coastguard Worker 214*333d2b36SAndroid Build Coastguard Worker s.requestLine() 215*333d2b36SAndroid Build Coastguard Worker 216*333d2b36SAndroid Build Coastguard Worker s.runningActions = nil 217*333d2b36SAndroid Build Coastguard Worker 218*333d2b36SAndroid Build Coastguard Worker if s.tableMode { 219*333d2b36SAndroid Build Coastguard Worker // Update the table after clearing runningActions to clear it 220*333d2b36SAndroid Build Coastguard Worker s.actionTable() 221*333d2b36SAndroid Build Coastguard Worker 222*333d2b36SAndroid Build Coastguard Worker // Reset the scrolling region to the whole terminal 223*333d2b36SAndroid Build Coastguard Worker fmt.Fprintf(s.writer, ansi.resetScrollingMargins()) 224*333d2b36SAndroid Build Coastguard Worker _, height, _ := termSize(s.writer) 225*333d2b36SAndroid Build Coastguard Worker // Move the cursor to the top of the now-blank, previously non-scrolling region 226*333d2b36SAndroid Build Coastguard Worker fmt.Fprintf(s.writer, ansi.setCursor(height-s.tableHeight, 1)) 227*333d2b36SAndroid Build Coastguard Worker // Turn the cursor back on 228*333d2b36SAndroid Build Coastguard Worker fmt.Fprintf(s.writer, ansi.showCursor()) 229*333d2b36SAndroid Build Coastguard Worker } 230*333d2b36SAndroid Build Coastguard Worker} 231*333d2b36SAndroid Build Coastguard Worker 232*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) Write(p []byte) (int, error) { 233*333d2b36SAndroid Build Coastguard Worker s.lock.Lock() 234*333d2b36SAndroid Build Coastguard Worker defer s.lock.Unlock() 235*333d2b36SAndroid Build Coastguard Worker s.print(string(p)) 236*333d2b36SAndroid Build Coastguard Worker return len(p), nil 237*333d2b36SAndroid Build Coastguard Worker} 238*333d2b36SAndroid Build Coastguard Worker 239*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) requestLine() { 240*333d2b36SAndroid Build Coastguard Worker if !s.haveBlankLine { 241*333d2b36SAndroid Build Coastguard Worker fmt.Fprintln(s.writer) 242*333d2b36SAndroid Build Coastguard Worker s.haveBlankLine = true 243*333d2b36SAndroid Build Coastguard Worker } 244*333d2b36SAndroid Build Coastguard Worker} 245*333d2b36SAndroid Build Coastguard Worker 246*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) print(str string) { 247*333d2b36SAndroid Build Coastguard Worker if !s.haveBlankLine { 248*333d2b36SAndroid Build Coastguard Worker fmt.Fprint(s.writer, "\r", ansi.clearToEndOfLine()) 249*333d2b36SAndroid Build Coastguard Worker s.haveBlankLine = true 250*333d2b36SAndroid Build Coastguard Worker } 251*333d2b36SAndroid Build Coastguard Worker fmt.Fprint(s.writer, str) 252*333d2b36SAndroid Build Coastguard Worker if len(str) == 0 || str[len(str)-1] != '\n' { 253*333d2b36SAndroid Build Coastguard Worker fmt.Fprint(s.writer, "\n") 254*333d2b36SAndroid Build Coastguard Worker } 255*333d2b36SAndroid Build Coastguard Worker} 256*333d2b36SAndroid Build Coastguard Worker 257*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) statusLine(str string) { 258*333d2b36SAndroid Build Coastguard Worker idx := strings.IndexRune(str, '\n') 259*333d2b36SAndroid Build Coastguard Worker if idx != -1 { 260*333d2b36SAndroid Build Coastguard Worker str = str[0:idx] 261*333d2b36SAndroid Build Coastguard Worker } 262*333d2b36SAndroid Build Coastguard Worker 263*333d2b36SAndroid Build Coastguard Worker // Limit line width to the terminal width, otherwise we'll wrap onto 264*333d2b36SAndroid Build Coastguard Worker // another line and we won't delete the previous line. 265*333d2b36SAndroid Build Coastguard Worker str = elide(str, s.termWidth) 266*333d2b36SAndroid Build Coastguard Worker 267*333d2b36SAndroid Build Coastguard Worker // Move to the beginning on the line, turn on bold, print the output, 268*333d2b36SAndroid Build Coastguard Worker // turn off bold, then clear the rest of the line. 269*333d2b36SAndroid Build Coastguard Worker start := "\r" + ansi.bold() 270*333d2b36SAndroid Build Coastguard Worker end := ansi.regular() + ansi.clearToEndOfLine() 271*333d2b36SAndroid Build Coastguard Worker fmt.Fprint(s.writer, start, str, end) 272*333d2b36SAndroid Build Coastguard Worker s.haveBlankLine = false 273*333d2b36SAndroid Build Coastguard Worker} 274*333d2b36SAndroid Build Coastguard Worker 275*333d2b36SAndroid Build Coastguard Workerfunc elide(str string, width int) string { 276*333d2b36SAndroid Build Coastguard Worker if width > 0 && len(str) > width { 277*333d2b36SAndroid Build Coastguard Worker // TODO: Just do a max. Ninja elides the middle, but that's 278*333d2b36SAndroid Build Coastguard Worker // more complicated and these lines aren't that important. 279*333d2b36SAndroid Build Coastguard Worker str = str[:width] 280*333d2b36SAndroid Build Coastguard Worker } 281*333d2b36SAndroid Build Coastguard Worker 282*333d2b36SAndroid Build Coastguard Worker return str 283*333d2b36SAndroid Build Coastguard Worker} 284*333d2b36SAndroid Build Coastguard Worker 285*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) startActionTableTick() { 286*333d2b36SAndroid Build Coastguard Worker s.ticker = time.NewTicker(time.Second) 287*333d2b36SAndroid Build Coastguard Worker go func() { 288*333d2b36SAndroid Build Coastguard Worker for { 289*333d2b36SAndroid Build Coastguard Worker select { 290*333d2b36SAndroid Build Coastguard Worker case <-s.ticker.C: 291*333d2b36SAndroid Build Coastguard Worker s.lock.Lock() 292*333d2b36SAndroid Build Coastguard Worker s.actionTable() 293*333d2b36SAndroid Build Coastguard Worker s.lock.Unlock() 294*333d2b36SAndroid Build Coastguard Worker case <-s.done: 295*333d2b36SAndroid Build Coastguard Worker return 296*333d2b36SAndroid Build Coastguard Worker } 297*333d2b36SAndroid Build Coastguard Worker } 298*333d2b36SAndroid Build Coastguard Worker }() 299*333d2b36SAndroid Build Coastguard Worker} 300*333d2b36SAndroid Build Coastguard Worker 301*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) stopActionTableTick() { 302*333d2b36SAndroid Build Coastguard Worker s.ticker.Stop() 303*333d2b36SAndroid Build Coastguard Worker s.done <- true 304*333d2b36SAndroid Build Coastguard Worker} 305*333d2b36SAndroid Build Coastguard Worker 306*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) startSigwinch() { 307*333d2b36SAndroid Build Coastguard Worker signal.Notify(s.sigwinch, syscall.SIGWINCH) 308*333d2b36SAndroid Build Coastguard Worker go func() { 309*333d2b36SAndroid Build Coastguard Worker for _ = range s.sigwinch { 310*333d2b36SAndroid Build Coastguard Worker s.lock.Lock() 311*333d2b36SAndroid Build Coastguard Worker s.updateTermSize() 312*333d2b36SAndroid Build Coastguard Worker if s.tableMode { 313*333d2b36SAndroid Build Coastguard Worker s.actionTable() 314*333d2b36SAndroid Build Coastguard Worker } 315*333d2b36SAndroid Build Coastguard Worker s.lock.Unlock() 316*333d2b36SAndroid Build Coastguard Worker if s.sigwinchHandled != nil { 317*333d2b36SAndroid Build Coastguard Worker s.sigwinchHandled <- true 318*333d2b36SAndroid Build Coastguard Worker } 319*333d2b36SAndroid Build Coastguard Worker } 320*333d2b36SAndroid Build Coastguard Worker }() 321*333d2b36SAndroid Build Coastguard Worker} 322*333d2b36SAndroid Build Coastguard Worker 323*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) stopSigwinch() { 324*333d2b36SAndroid Build Coastguard Worker signal.Stop(s.sigwinch) 325*333d2b36SAndroid Build Coastguard Worker close(s.sigwinch) 326*333d2b36SAndroid Build Coastguard Worker} 327*333d2b36SAndroid Build Coastguard Worker 328*333d2b36SAndroid Build Coastguard Worker// computeTableHeight recomputes s.tableHeight based on s.termHeight and s.requestedTableHeight. 329*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) computeTableHeight() { 330*333d2b36SAndroid Build Coastguard Worker tableHeight := s.requestedTableHeight 331*333d2b36SAndroid Build Coastguard Worker if tableHeight == 0 { 332*333d2b36SAndroid Build Coastguard Worker tableHeight = s.termHeight / 4 333*333d2b36SAndroid Build Coastguard Worker if tableHeight < 1 { 334*333d2b36SAndroid Build Coastguard Worker tableHeight = 1 335*333d2b36SAndroid Build Coastguard Worker } else if tableHeight > 10 { 336*333d2b36SAndroid Build Coastguard Worker tableHeight = 10 337*333d2b36SAndroid Build Coastguard Worker } 338*333d2b36SAndroid Build Coastguard Worker } 339*333d2b36SAndroid Build Coastguard Worker if tableHeight > s.termHeight-1 { 340*333d2b36SAndroid Build Coastguard Worker tableHeight = s.termHeight - 1 341*333d2b36SAndroid Build Coastguard Worker } 342*333d2b36SAndroid Build Coastguard Worker s.tableHeight = tableHeight 343*333d2b36SAndroid Build Coastguard Worker} 344*333d2b36SAndroid Build Coastguard Worker 345*333d2b36SAndroid Build Coastguard Worker// updateTermSize recomputes the table height after a SIGWINCH and pans any existing text if 346*333d2b36SAndroid Build Coastguard Worker// necessary. 347*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) updateTermSize() { 348*333d2b36SAndroid Build Coastguard Worker if w, h, ok := termSize(s.writer); ok { 349*333d2b36SAndroid Build Coastguard Worker oldScrollingHeight := s.termHeight - s.tableHeight 350*333d2b36SAndroid Build Coastguard Worker 351*333d2b36SAndroid Build Coastguard Worker s.termWidth, s.termHeight = w, h 352*333d2b36SAndroid Build Coastguard Worker 353*333d2b36SAndroid Build Coastguard Worker if s.tableMode { 354*333d2b36SAndroid Build Coastguard Worker s.computeTableHeight() 355*333d2b36SAndroid Build Coastguard Worker 356*333d2b36SAndroid Build Coastguard Worker scrollingHeight := s.termHeight - s.tableHeight 357*333d2b36SAndroid Build Coastguard Worker 358*333d2b36SAndroid Build Coastguard Worker // If the scrolling region has changed, attempt to pan the existing text so that it is 359*333d2b36SAndroid Build Coastguard Worker // not overwritten by the table. 360*333d2b36SAndroid Build Coastguard Worker if scrollingHeight < oldScrollingHeight { 361*333d2b36SAndroid Build Coastguard Worker pan := oldScrollingHeight - scrollingHeight 362*333d2b36SAndroid Build Coastguard Worker if pan > s.tableHeight { 363*333d2b36SAndroid Build Coastguard Worker pan = s.tableHeight 364*333d2b36SAndroid Build Coastguard Worker } 365*333d2b36SAndroid Build Coastguard Worker fmt.Fprint(s.writer, ansi.panDown(pan)) 366*333d2b36SAndroid Build Coastguard Worker } 367*333d2b36SAndroid Build Coastguard Worker } 368*333d2b36SAndroid Build Coastguard Worker } 369*333d2b36SAndroid Build Coastguard Worker} 370*333d2b36SAndroid Build Coastguard Worker 371*333d2b36SAndroid Build Coastguard Workerfunc (s *smartStatusOutput) actionTable() { 372*333d2b36SAndroid Build Coastguard Worker scrollingHeight := s.termHeight - s.tableHeight 373*333d2b36SAndroid Build Coastguard Worker 374*333d2b36SAndroid Build Coastguard Worker // Update the scrolling region in case the height of the terminal changed 375*333d2b36SAndroid Build Coastguard Worker 376*333d2b36SAndroid Build Coastguard Worker fmt.Fprint(s.writer, ansi.setScrollingMargins(1, scrollingHeight)) 377*333d2b36SAndroid Build Coastguard Worker 378*333d2b36SAndroid Build Coastguard Worker // Write as many status lines as fit in the table 379*333d2b36SAndroid Build Coastguard Worker for tableLine := 0; tableLine < s.tableHeight; tableLine++ { 380*333d2b36SAndroid Build Coastguard Worker if tableLine >= s.tableHeight { 381*333d2b36SAndroid Build Coastguard Worker break 382*333d2b36SAndroid Build Coastguard Worker } 383*333d2b36SAndroid Build Coastguard Worker // Move the cursor to the correct line of the non-scrolling region 384*333d2b36SAndroid Build Coastguard Worker fmt.Fprint(s.writer, ansi.setCursor(scrollingHeight+1+tableLine, 1)) 385*333d2b36SAndroid Build Coastguard Worker 386*333d2b36SAndroid Build Coastguard Worker if tableLine < len(s.runningActions) { 387*333d2b36SAndroid Build Coastguard Worker runningAction := s.runningActions[tableLine] 388*333d2b36SAndroid Build Coastguard Worker 389*333d2b36SAndroid Build Coastguard Worker seconds := int(time.Since(runningAction.startTime).Round(time.Second).Seconds()) 390*333d2b36SAndroid Build Coastguard Worker 391*333d2b36SAndroid Build Coastguard Worker desc := runningAction.action.Description 392*333d2b36SAndroid Build Coastguard Worker if desc == "" { 393*333d2b36SAndroid Build Coastguard Worker desc = runningAction.action.Command 394*333d2b36SAndroid Build Coastguard Worker } 395*333d2b36SAndroid Build Coastguard Worker 396*333d2b36SAndroid Build Coastguard Worker color := "" 397*333d2b36SAndroid Build Coastguard Worker if seconds >= 60 { 398*333d2b36SAndroid Build Coastguard Worker color = ansi.red() + ansi.bold() 399*333d2b36SAndroid Build Coastguard Worker } else if seconds >= 30 { 400*333d2b36SAndroid Build Coastguard Worker color = ansi.yellow() + ansi.bold() 401*333d2b36SAndroid Build Coastguard Worker } 402*333d2b36SAndroid Build Coastguard Worker 403*333d2b36SAndroid Build Coastguard Worker durationStr := fmt.Sprintf(" %2d:%02d ", seconds/60, seconds%60) 404*333d2b36SAndroid Build Coastguard Worker desc = elide(desc, s.termWidth-len(durationStr)) 405*333d2b36SAndroid Build Coastguard Worker durationStr = color + durationStr + ansi.regular() 406*333d2b36SAndroid Build Coastguard Worker fmt.Fprint(s.writer, durationStr, desc) 407*333d2b36SAndroid Build Coastguard Worker } 408*333d2b36SAndroid Build Coastguard Worker fmt.Fprint(s.writer, ansi.clearToEndOfLine()) 409*333d2b36SAndroid Build Coastguard Worker } 410*333d2b36SAndroid Build Coastguard Worker 411*333d2b36SAndroid Build Coastguard Worker // Move the cursor back to the last line of the scrolling region 412*333d2b36SAndroid Build Coastguard Worker fmt.Fprint(s.writer, ansi.setCursor(scrollingHeight, 1)) 413*333d2b36SAndroid Build Coastguard Worker} 414*333d2b36SAndroid Build Coastguard Worker 415*333d2b36SAndroid Build Coastguard Workervar ansi = ansiImpl{} 416*333d2b36SAndroid Build Coastguard Worker 417*333d2b36SAndroid Build Coastguard Workertype ansiImpl struct{} 418*333d2b36SAndroid Build Coastguard Worker 419*333d2b36SAndroid Build Coastguard Workerfunc (ansiImpl) clearToEndOfLine() string { 420*333d2b36SAndroid Build Coastguard Worker return "\x1b[K" 421*333d2b36SAndroid Build Coastguard Worker} 422*333d2b36SAndroid Build Coastguard Worker 423*333d2b36SAndroid Build Coastguard Workerfunc (ansiImpl) setCursor(row, column int) string { 424*333d2b36SAndroid Build Coastguard Worker // Direct cursor address 425*333d2b36SAndroid Build Coastguard Worker return fmt.Sprintf("\x1b[%d;%dH", row, column) 426*333d2b36SAndroid Build Coastguard Worker} 427*333d2b36SAndroid Build Coastguard Worker 428*333d2b36SAndroid Build Coastguard Workerfunc (ansiImpl) setScrollingMargins(top, bottom int) string { 429*333d2b36SAndroid Build Coastguard Worker // Set Top and Bottom Margins DECSTBM 430*333d2b36SAndroid Build Coastguard Worker return fmt.Sprintf("\x1b[%d;%dr", top, bottom) 431*333d2b36SAndroid Build Coastguard Worker} 432*333d2b36SAndroid Build Coastguard Worker 433*333d2b36SAndroid Build Coastguard Workerfunc (ansiImpl) resetScrollingMargins() string { 434*333d2b36SAndroid Build Coastguard Worker // Set Top and Bottom Margins DECSTBM 435*333d2b36SAndroid Build Coastguard Worker return fmt.Sprintf("\x1b[r") 436*333d2b36SAndroid Build Coastguard Worker} 437*333d2b36SAndroid Build Coastguard Worker 438*333d2b36SAndroid Build Coastguard Workerfunc (ansiImpl) red() string { 439*333d2b36SAndroid Build Coastguard Worker return "\x1b[31m" 440*333d2b36SAndroid Build Coastguard Worker} 441*333d2b36SAndroid Build Coastguard Worker 442*333d2b36SAndroid Build Coastguard Workerfunc (ansiImpl) yellow() string { 443*333d2b36SAndroid Build Coastguard Worker return "\x1b[33m" 444*333d2b36SAndroid Build Coastguard Worker} 445*333d2b36SAndroid Build Coastguard Worker 446*333d2b36SAndroid Build Coastguard Workerfunc (ansiImpl) bold() string { 447*333d2b36SAndroid Build Coastguard Worker return "\x1b[1m" 448*333d2b36SAndroid Build Coastguard Worker} 449*333d2b36SAndroid Build Coastguard Worker 450*333d2b36SAndroid Build Coastguard Workerfunc (ansiImpl) regular() string { 451*333d2b36SAndroid Build Coastguard Worker return "\x1b[0m" 452*333d2b36SAndroid Build Coastguard Worker} 453*333d2b36SAndroid Build Coastguard Worker 454*333d2b36SAndroid Build Coastguard Workerfunc (ansiImpl) showCursor() string { 455*333d2b36SAndroid Build Coastguard Worker return "\x1b[?25h" 456*333d2b36SAndroid Build Coastguard Worker} 457*333d2b36SAndroid Build Coastguard Worker 458*333d2b36SAndroid Build Coastguard Workerfunc (ansiImpl) hideCursor() string { 459*333d2b36SAndroid Build Coastguard Worker return "\x1b[?25l" 460*333d2b36SAndroid Build Coastguard Worker} 461*333d2b36SAndroid Build Coastguard Worker 462*333d2b36SAndroid Build Coastguard Workerfunc (ansiImpl) panDown(lines int) string { 463*333d2b36SAndroid Build Coastguard Worker return fmt.Sprintf("\x1b[%dS", lines) 464*333d2b36SAndroid Build Coastguard Worker} 465*333d2b36SAndroid Build Coastguard Worker 466*333d2b36SAndroid Build Coastguard Workerfunc (ansiImpl) panUp(lines int) string { 467*333d2b36SAndroid Build Coastguard Worker return fmt.Sprintf("\x1b[%dT", lines) 468*333d2b36SAndroid Build Coastguard Worker} 469