blob: a52fdc2b557d8620da83f4f9edfa36971bfefead [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
107func (s *smartStatusOutput) requestLine() {
108 if !s.haveBlankLine {
109 fmt.Fprintln(s.writer)
110 s.haveBlankLine = true
111 }
112}
113
114func (s *smartStatusOutput) print(str string) {
115 if !s.haveBlankLine {
116 fmt.Fprint(s.writer, "\r", "\x1b[K")
117 s.haveBlankLine = true
118 }
119 fmt.Fprint(s.writer, str)
120 if len(str) == 0 || str[len(str)-1] != '\n' {
121 fmt.Fprint(s.writer, "\n")
122 }
123}
124
125func (s *smartStatusOutput) statusLine(str string) {
126 idx := strings.IndexRune(str, '\n')
127 if idx != -1 {
128 str = str[0:idx]
129 }
130
131 // Limit line width to the terminal width, otherwise we'll wrap onto
132 // another line and we won't delete the previous line.
133 //
134 // Run this on every line in case the window has been resized while
135 // we're printing. This could be optimized to only re-run when we get
136 // SIGWINCH if it ever becomes too time consuming.
Colin Cross097ed2a2019-06-08 21:48:58 -0700137 if max, ok := termWidth(s.writer); ok {
Colin Crossce525352019-06-08 18:58:00 -0700138 if len(str) > max {
139 // TODO: Just do a max. Ninja elides the middle, but that's
140 // more complicated and these lines aren't that important.
141 str = str[:max]
142 }
143 }
144
145 // Move to the beginning on the line, print the output, then clear
146 // the rest of the line.
147 fmt.Fprint(s.writer, "\r", str, "\x1b[K")
148 s.haveBlankLine = false
149}