blob: dbc27c222afaca3f1a95052402f7c31c606c0b1c [file] [log] [blame]
// Copyright 2024 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NINJA_STATUS_TABLE_H_
#define NINJA_STATUS_TABLE_H_
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string>
#include <unordered_map>
#include <vector>
#include "async_loop.h"
/// A class used to display a table of pending commands during
/// the build on smart terminals, using ANSI sequences whenever possible.
///
/// Concrete implementations should override the GetCommandDescription()
/// and PrintOnCurrentLine() method to work on smart terminals.
///
/// Usage is the following:
///
/// 1) Create instance, passing configuration information and
/// a valid AsyncLoop reference.
///
/// 2) Call `BuildStarted()` when the build starts. Similarly, call
/// `BuildEnded()` when it stops.
///
/// 3) Call `CommandStarted(command)` whenever a new command is started, where
/// |command| is a unique pointer value for the command, whose description
/// can be returned by `GetCommandDescription(command)`.
///
/// 4) Call `CommandEnded(command)` when a given command ended. This does not
/// update the table, only internal counters.
///
/// 5) Call `SetStatus(status_line)` whenever the status line changes.
/// It is needed to restore the cursor position after printing the table,
/// unfortunately (there is no good way to save cursor positions with
/// various terminal emulators).
///
/// 6) Call `ClearTable()` to clear the table (e.g. before printing something,
/// or at the end of the build). Call `UpdateTable()` to print the table
/// if needed (e.g. if it was cleared previously, or if enough time has
/// passed since the last call).
///
class StatusTable {
public:
/// An opaque type describing a given unique command.
using CommandPointer = const void*;
/// Configuration information for a new StatusTable instance.
///
/// |max_commands| is the maximum number of commands to print in the table.
/// A value of 0 completely disables the feature.
///
/// |refresh_timeout_ms| is the periodic refresh timeout used by the
/// internal timer. A negative value disables the feature, and the table
/// will only be updated on ClearTable() and UpdateTable() calls.
///
struct Config {
size_t max_commands = 0;
int64_t refresh_timeout_ms = -1;
};
/// Constructor. |max_commands| is the maximum number of commands to print
/// (a value of 0 disables the feature). |refresh_timeout_ms| is the minimum
/// refresh timeout (a value of 0 disables the timer). The |async_loop|
/// reference is used to create a timer for periodic updates.
StatusTable(const Config& config, AsyncLoop& async_loop);
/// Destructor.
virtual ~StatusTable();
/// Call this when starting a new build.
void BuildStarted();
/// Call this when a new command is starting.
void CommandStarted(CommandPointer command);
/// Call this when a started command completes.
/// It is a runtime error to use a |command| value that does not
/// match a previous CommandStart() call, that was not previously
/// finished.
void CommandEnded(CommandPointer command);
/// Call this when the build has completed.
void BuildEnded();
/// Call this to update the status at the top of the table, and update
/// the commands below it if needed.
void SetStatus(const std::string& status);
/// Call this to clear the table, if any.
void ClearTable();
/// Call this to update the table if needed.
void UpdateTable();
/// Disable periodic timer updates. Only PrintTable() and ClearTable()
/// will update the output from now on.
void DisableTimer();
/// Enable periodic updates through AsyncLoop timers. This ensures the
/// table is automatically updated every config.refresh_timeout_ms
/// milliseconds if there are no calls to UpdateTable() or ClearTable()
/// in between.
void EnableTimer();
protected:
/// The following methods can be overriden by sub-classes.
/// Both GetCommandDescription() and PrintOnCurrentLine() should be
/// overridden for proper command output in smart terminals.
/// Return a string describing a command.
/// This method should be overridden by derived classes, as the default
/// simply returns "command <number>".
virtual std::string GetCommandDescription(CommandPointer command) const;
/// Print |line| from the start of the current line, and place the cursor
/// right after it, clearing anything after it. This must *not* print more
/// than the terminal width, nor move the cursor to the next line.
///
/// This method should be overridden by derived classes, as the default
/// prints on the current line without trying to limit the width, then
/// does an ANSI "erase from cursor to end of line" sequence.
virtual void PrintOnCurrentLine(const std::string& line);
/// The following methods can be overriden for tests. Their default behavior
/// is to use standard ANSI sequences, and eventually the above two methods.
/// Jump to the next line, then print |line| on it just like
/// PrintOnCurrentLine.
virtual void PrintOnNextLine(const std::string& line);
/// Move down to the next line, then clear it completely. The cursor can
/// stay on the same column.
virtual void ClearNextLine();
/// Move up |lines_count| lines. The cursor can stay on the same column.
virtual void MoveUp(size_t lines_count);
/// Flush all previous commands to final terminal.
virtual void Flush();
private:
/// Called to refresh the pending list if needed.
void Refresh(int64_t current_time_ms);
/// Support for printing pending commands below the status on smart terminals.
void PrintPending(int64_t cur_time_millis);
Config config_;
size_t last_command_count_ = 0;
int64_t start_build_time_ms_ = 0;
int64_t last_update_time_ms_ = -1;
std::string last_status_;
AsyncLoop& async_loop_;
AsyncTimer timer_;
// Record pending commands. This maps an opaque command pointer
// to its start time in milliseconds.
using CommandMap = std::unordered_map<CommandPointer, int64_t>;
CommandMap pending_commands_;
// Used on each PrintPending() call to minimize heap allocations.
// Note that CommandMap::value_type.first has type |const Edge* const| and
// thus CommandMap::value_type is not copyable and cannot be used as an
// std::vector<> item type.
using CommandInfo = std::pair<CommandPointer, int64_t>;
std::vector<CommandInfo> sorted_pending_commands_;
};
#endif // NINJA_STATUS_TABLE_H_