blob: 5edc21a1a9899c525463466f0d3d93dfcf40e2cb [file] [log] [blame]
Colin Crossce525352019-06-08 18:58:00 -07001// Copyright 2019 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
15package terminal
16
17import (
18 "fmt"
19 "strings"
20 "sync"
21
22 "android/soong/ui/status"
23)
24
25type smartStatusOutput struct {
26 writer Writer
27 formatter formatter
28
29 lock sync.Mutex
30
31 haveBlankLine bool
32}
33
34// NewSmartStatusOutput returns a StatusOutput that represents the
35// current build status similarly to Ninja's built-in terminal
36// output.
37func NewSmartStatusOutput(w Writer, formatter formatter) status.StatusOutput {
38 return &smartStatusOutput{
39 writer: w,
40 formatter: formatter,
41
42 haveBlankLine: true,
43 }
44}
45
46func (s *smartStatusOutput) Message(level status.MsgLevel, message string) {
47 if level < status.StatusLvl {
48 return
49 }
50
51 str := s.formatter.message(level, message)
52
53 s.lock.Lock()
54 defer s.lock.Unlock()
55
56 if level > status.StatusLvl {
57 s.print(str)
58 } else {
59 s.statusLine(str)
60 }
61}
62
63func (s *smartStatusOutput) StartAction(action *status.Action, counts status.Counts) {
64 str := action.Description
65 if str == "" {
66 str = action.Command
67 }
68
69 progress := s.formatter.progress(counts)
70
71 s.lock.Lock()
72 defer s.lock.Unlock()
73
74 s.statusLine(progress + str)
75}
76
77func (s *smartStatusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
78 str := result.Description
79 if str == "" {
80 str = result.Command
81 }
82
83 progress := s.formatter.progress(counts) + str
84
85 output := s.formatter.result(result)
86
87 s.lock.Lock()
88 defer s.lock.Unlock()
89
90 if output != "" {
91 s.statusLine(progress)
92 s.requestLine()
93 s.print(output)
94 } else {
95 s.statusLine(progress)
96 }
97}
98
99func (s *smartStatusOutput) Flush() {
100 s.lock.Lock()
101 defer s.lock.Unlock()
102
103 s.requestLine()
104}
105
106func (s *smartStatusOutput) requestLine() {
107 if !s.haveBlankLine {
108 fmt.Fprintln(s.writer)
109 s.haveBlankLine = true
110 }
111}
112
113func (s *smartStatusOutput) print(str string) {
114 if !s.haveBlankLine {
115 fmt.Fprint(s.writer, "\r", "\x1b[K")
116 s.haveBlankLine = true
117 }
118 fmt.Fprint(s.writer, str)
119 if len(str) == 0 || str[len(str)-1] != '\n' {
120 fmt.Fprint(s.writer, "\n")
121 }
122}
123
124func (s *smartStatusOutput) statusLine(str string) {
125 idx := strings.IndexRune(str, '\n')
126 if idx != -1 {
127 str = str[0:idx]
128 }
129
130 // Limit line width to the terminal width, otherwise we'll wrap onto
131 // another line and we won't delete the previous line.
132 //
133 // Run this on every line in case the window has been resized while
134 // we're printing. This could be optimized to only re-run when we get
135 // SIGWINCH if it ever becomes too time consuming.
136 if max, ok := s.writer.termWidth(); ok {
137 if len(str) > max {
138 // TODO: Just do a max. Ninja elides the middle, but that's
139 // more complicated and these lines aren't that important.
140 str = str[:max]
141 }
142 }
143
144 // Move to the beginning on the line, print the output, then clear
145 // the rest of the line.
146 fmt.Fprint(s.writer, "\r", str, "\x1b[K")
147 s.haveBlankLine = false
148}