blob: 008b8d57c544a17de1841c390a2ded3700bcede1 [file] [log] [blame]
//===-- CommandLineStatusOutput.cpp ---------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "CommandLineStatusOutput.h"
#include "llvm/Support/Process.h"
#include <cassert>
#include <cstdio>
namespace {
struct CommandLineStatusOutputImpl {
/// The output stream.
FILE* fp{nullptr};
/// Whether the output stream honors '\r'.
bool termHonorsCarriageReturn{false};
/// Whether the stream has current output.
bool hasOutput{false};
/// Whether the stream has been closed.
bool isClosed{false};
/// The number of characters written to the current line.
int numCurrentCharacters{0};
CommandLineStatusOutputImpl() {}
~CommandLineStatusOutputImpl() {
if (isOpen()) {
std::string error;
close(&error);
}
}
bool isOpen() const {
return fp != nullptr;
}
bool open(std::string* error_out) {
assert(!isOpen() && !isClosed);
fp = stdout;
// Detect the features of the output.
int fd = fileno(fp);
if (llvm::sys::Process::FileDescriptorIsDisplayed(fd)) {
// If the terminal is a tty, check the TERM variable.
const char* term = ::getenv("TERM");
// We assume the terminal honors '\r' if it is either not-"dumb", or it is
// "dumb" and we are inside Emacs (comint-mode reports as "dumb").
if (term) {
termHonorsCarriageReturn = (term != std::string("dumb") ||
::getenv("INSIDE_EMACS") != nullptr);
}
}
return true;
}
bool close(std::string* error_out) {
if (hasOutput) {
fprintf(fp, "\n");
fflush(fp);
}
fp = nullptr;
isClosed = true; // Don't allow re-opening.
return true;
}
bool canUpdateCurrentLine() const {
assert(isOpen());
return termHonorsCarriageReturn;
}
void clearOutput() {
assert(isOpen());
assert(canUpdateCurrentLine());
if (hasOutput) {
// Clear the line before writing, this tends to produce better results
// than clearing the unwritten tail of the line written below.
fprintf(fp, "\r%*s\r", numCurrentCharacters, "");
fflush(fp);
hasOutput = false;
}
}
int getNumColumns() {
auto result = llvm::sys::Process::StandardOutColumns();
// If we were unable to query the terminal, just use a default.
if (!result) {
return 80;
}
return result;
}
void setCurrentLine(const std::string& text) {
assert(isOpen());
assert(text.find('\r') == std::string::npos);
assert(text.find('\n') == std::string::npos);
// Clear the line before writing, this tends to produce better results than
// clearing the unwritten tail of the line written below.
clearOutput();
// Write the line, trimming it to fit in the current terminal.
int columns = getNumColumns();
if (columns > 0 && (int)text.size() > columns) {
// Elide the middle of the text.
int midpoint = columns / 2;
std::string elided = text.substr(0, std::max(0, midpoint - 2)) + "..." +
text.substr(text.size() - (columns - (midpoint + 1)));
assert(columns < 3 || (int)elided.size() == columns);
fprintf(fp, "%s", elided.c_str());
numCurrentCharacters = elided.size();
} else {
fprintf(fp, "%s", text.c_str());
numCurrentCharacters = text.size();
}
fflush(fp);
hasOutput = numCurrentCharacters != 0;
}
void setOrWriteLine(const std::string& text) {
if (canUpdateCurrentLine())
return setCurrentLine(text);
writeText(text + "\n");
}
void finishLine() {
assert(isOpen());
// Finish the current line, if necessary.
if (canUpdateCurrentLine() && hasOutput) {
fputc('\n', fp);
fflush(fp);
hasOutput = false;
}
}
void writeText(const std::string& text) {
assert(isOpen());
assert(text.size() && text.back() == '\n');
// Clear the current output, if present.
if (hasOutput)
clearOutput();
fwrite(text.c_str(), text.size(), 1, fp);
fflush(fp);
}
};
}
namespace llbuild {
namespace commands {
CommandLineStatusOutput::CommandLineStatusOutput()
: impl(new CommandLineStatusOutputImpl())
{
}
CommandLineStatusOutput::~CommandLineStatusOutput() {
delete static_cast<CommandLineStatusOutputImpl*>(impl);
}
bool CommandLineStatusOutput::open(std::string* error_out) {
return static_cast<CommandLineStatusOutputImpl*>(impl)->open(error_out);
}
bool CommandLineStatusOutput::close(std::string* error_out) {
return static_cast<CommandLineStatusOutputImpl*>(impl)->close(error_out);
}
bool CommandLineStatusOutput::canUpdateCurrentLine() const {
return
static_cast<CommandLineStatusOutputImpl*>(impl)->canUpdateCurrentLine();
}
void CommandLineStatusOutput::clearOutput() {
return static_cast<CommandLineStatusOutputImpl*>(impl)->clearOutput();
}
void CommandLineStatusOutput::setCurrentLine(const std::string& text) {
return
static_cast<CommandLineStatusOutputImpl*>(impl)->setCurrentLine(text);
}
void CommandLineStatusOutput::setOrWriteLine(const std::string& text) {
return
static_cast<CommandLineStatusOutputImpl*>(impl)->setOrWriteLine(text);
}
void CommandLineStatusOutput::finishLine() {
return static_cast<CommandLineStatusOutputImpl*>(impl)->finishLine();
}
void CommandLineStatusOutput::writeText(const std::string& text) {
return
static_cast<CommandLineStatusOutputImpl*>(impl)->writeText(text);
}
}
}