blob: 999a2d0f18854c56bfc236bf676bab6a4ccaca41 [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"
Colin Cross097ed2a2019-06-08 21:48:58 -070019 "io"
Colin Cross49036be2019-06-11 23:01:36 -070020 "os"
21 "os/signal"
Colin Crossce525352019-06-08 18:58:00 -070022 "strings"
23 "sync"
Colin Cross49036be2019-06-11 23:01:36 -070024 "syscall"
Colin Crossce525352019-06-08 18:58:00 -070025
26 "android/soong/ui/status"
27)
28
29type smartStatusOutput struct {
Colin Cross097ed2a2019-06-08 21:48:58 -070030 writer io.Writer
Colin Crossce525352019-06-08 18:58:00 -070031 formatter formatter
32
33 lock sync.Mutex
34
35 haveBlankLine bool
Colin Cross49036be2019-06-11 23:01:36 -070036
37 termWidth int
38 sigwinch chan os.Signal
Colin Crossce525352019-06-08 18:58:00 -070039}
40
41// NewSmartStatusOutput returns a StatusOutput that represents the
42// current build status similarly to Ninja's built-in terminal
43// output.
Colin Cross097ed2a2019-06-08 21:48:58 -070044func NewSmartStatusOutput(w io.Writer, formatter formatter) status.StatusOutput {
Colin Cross49036be2019-06-11 23:01:36 -070045 s := &smartStatusOutput{
Colin Crossce525352019-06-08 18:58:00 -070046 writer: w,
47 formatter: formatter,
48
49 haveBlankLine: true,
Colin Cross49036be2019-06-11 23:01:36 -070050
51 sigwinch: make(chan os.Signal),
Colin Crossce525352019-06-08 18:58:00 -070052 }
Colin Cross49036be2019-06-11 23:01:36 -070053
54 s.updateTermSize()
55
56 s.startSigwinch()
57
58 return s
Colin Crossce525352019-06-08 18:58:00 -070059}
60
61func (s *smartStatusOutput) Message(level status.MsgLevel, message string) {
62 if level < status.StatusLvl {
63 return
64 }
65
66 str := s.formatter.message(level, message)
67
68 s.lock.Lock()
69 defer s.lock.Unlock()
70
71 if level > status.StatusLvl {
72 s.print(str)
73 } else {
74 s.statusLine(str)
75 }
76}
77
78func (s *smartStatusOutput) StartAction(action *status.Action, counts status.Counts) {
79 str := action.Description
80 if str == "" {
81 str = action.Command
82 }
83
84 progress := s.formatter.progress(counts)
85
86 s.lock.Lock()
87 defer s.lock.Unlock()
88
89 s.statusLine(progress + str)
90}
91
92func (s *smartStatusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
93 str := result.Description
94 if str == "" {
95 str = result.Command
96 }
97
98 progress := s.formatter.progress(counts) + str
99
100 output := s.formatter.result(result)
101
102 s.lock.Lock()
103 defer s.lock.Unlock()
104
105 if output != "" {
106 s.statusLine(progress)
107 s.requestLine()
108 s.print(output)
109 } else {
110 s.statusLine(progress)
111 }
112}
113
114func (s *smartStatusOutput) Flush() {
115 s.lock.Lock()
116 defer s.lock.Unlock()
117
Colin Cross49036be2019-06-11 23:01:36 -0700118 s.stopSigwinch()
119
Colin Crossce525352019-06-08 18:58:00 -0700120 s.requestLine()
121}
122
Colin Crosse0df1a32019-06-09 19:40:08 -0700123func (s *smartStatusOutput) Write(p []byte) (int, error) {
124 s.lock.Lock()
125 defer s.lock.Unlock()
126 s.print(string(p))
127 return len(p), nil
128}
129
Colin Crossce525352019-06-08 18:58:00 -0700130func (s *smartStatusOutput) requestLine() {
131 if !s.haveBlankLine {
132 fmt.Fprintln(s.writer)
133 s.haveBlankLine = true
134 }
135}
136
137func (s *smartStatusOutput) print(str string) {
138 if !s.haveBlankLine {
139 fmt.Fprint(s.writer, "\r", "\x1b[K")
140 s.haveBlankLine = true
141 }
142 fmt.Fprint(s.writer, str)
143 if len(str) == 0 || str[len(str)-1] != '\n' {
144 fmt.Fprint(s.writer, "\n")
145 }
146}
147
148func (s *smartStatusOutput) statusLine(str string) {
149 idx := strings.IndexRune(str, '\n')
150 if idx != -1 {
151 str = str[0:idx]
152 }
153
154 // Limit line width to the terminal width, otherwise we'll wrap onto
155 // another line and we won't delete the previous line.
Colin Cross49036be2019-06-11 23:01:36 -0700156 if s.termWidth > 0 {
157 str = s.elide(str)
Colin Crossce525352019-06-08 18:58:00 -0700158 }
159
Colin Cross00bdfd82019-06-11 11:16:23 -0700160 // Move to the beginning on the line, turn on bold, print the output,
161 // turn off bold, then clear the rest of the line.
162 start := "\r\x1b[1m"
163 end := "\x1b[0m\x1b[K"
164 fmt.Fprint(s.writer, start, str, end)
Colin Crossce525352019-06-08 18:58:00 -0700165 s.haveBlankLine = false
166}
Colin Cross49036be2019-06-11 23:01:36 -0700167
168func (s *smartStatusOutput) elide(str string) string {
169 if len(str) > s.termWidth {
170 // TODO: Just do a max. Ninja elides the middle, but that's
171 // more complicated and these lines aren't that important.
172 str = str[:s.termWidth]
173 }
174
175 return str
176}
177
178func (s *smartStatusOutput) startSigwinch() {
179 signal.Notify(s.sigwinch, syscall.SIGWINCH)
180 go func() {
181 for _ = range s.sigwinch {
182 s.lock.Lock()
183 s.updateTermSize()
184 s.lock.Unlock()
185 }
186 }()
187}
188
189func (s *smartStatusOutput) stopSigwinch() {
190 signal.Stop(s.sigwinch)
191 close(s.sigwinch)
192}
193
194func (s *smartStatusOutput) updateTermSize() {
195 if w, ok := termWidth(s.writer); ok {
196 s.termWidth = w
197 }
198}