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