chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:36:49 +08:00
commit e7ae061fa2
2645 changed files with 497412 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,43 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <string>
#include <vector>
namespace Term
{
class Arguments
{
public:
Arguments();
static std::size_t argc();
static std::vector<std::string> argv();
std::string operator[](const std::size_t& arg) const;
private:
static void parse();
static std::vector<std::string> m_args;
static bool m_parsed;
};
class Argc
{
public:
Argc();
operator unsigned int();
operator unsigned int() const;
};
static const Arguments argv;
static const Argc argc;
} // namespace Term
@@ -0,0 +1,47 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <streambuf>
namespace Term
{
class Buffer final : public std::streambuf
{
public:
enum class Type : std::uint8_t
{
Unbuffered,
LineBuffered,
FullBuffered
};
explicit Buffer(const Term::Buffer::Type& type = Term::Buffer::Type::LineBuffered, const std::streamsize& size = BUFSIZ);
~Buffer() override;
Buffer(const Buffer&) = delete;
Buffer(Buffer&&) = delete;
Buffer& operator=(Buffer&&) = delete;
Buffer& operator=(const Buffer&) = delete;
protected:
int_type underflow() override;
int_type overflow(int c = std::char_traits<Term::Buffer::char_type>::eof()) override;
int sync() override;
private:
void setType(const Term::Buffer::Type& type);
std::streambuf* setbuf(char* s, std::streamsize n) override;
std::string m_buffer;
Term::Buffer::Type m_type{Term::Buffer::Type::LineBuffered};
};
} // namespace Term
@@ -0,0 +1,91 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/terminfo.hpp"
#include <array>
#include <cstdint>
#include <string>
namespace Term
{
class Color
{
public:
enum class Type : std::uint8_t
{
Unset,
NoColor,
Bit3,
Bit4,
Bit8,
Bit24
};
///
/// @brief The 3bit/4bit colors for the terminal.
/// Get the foreground color: Color4 + 30, Background color: Color4 + 40
/// See https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit.
///
enum class Name : std::uint8_t
{
Black = 0, ///< Black FG: 30, BG: 40
Red = 1, ///< Red FG: 31, BG: 41
Green = 2, ///< Green FG: 32, BG: 42
Yellow = 3, ///< Yellow FG: 33, BG: 43
Blue = 4, ///< Blue FG: 34, BG: 44
Magenta = 5, ///< Magenta FG: 35, BG: 45
Cyan = 6, ///< Cyan FG: 36, BG: 46
White = 7, ///< White FG: 37, BG: 47
Default = 9, ///< Use the default terminal color, FG: 39, BG: 49
Gray = 60, ///< Gray FG: 90, BG: 100
BrightBlack = 60, ///< BrightBlack FG: 90, BG: 100
BrightRed = 61, ///< BrightRed FG: 91, BG: 101
BrightGreen = 62, ///< BrightGreen FG: 92, BG: 102
BrightYellow = 63, ///< BrightYellow FG: 93, BG: 103
BrightBlue = 64, ///< BrightBlue FG: 94, BG: 104
BrightMagenta = 65, ///< BrightMagenta FG: 95, BG: 105
BrightCyan = 66, ///< BrightCyan FG: 96, BG: 106
BrightWhite = 67 ///< BrightWhite FG: 97, BG: 107
};
bool operator==(const Color&) const;
bool operator!=(const Color&) const;
Color();
Color(const Term::Color::Name& name);
Color(const std::uint8_t& color);
Color(const std::uint8_t& red, const std::uint8_t& green, const std::uint8_t& blue);
Type getType() const;
Name to3bits() const;
Name to4bits() const;
std::uint8_t to8bits() const;
std::array<std::uint8_t, 3> to24bits() const;
private:
Type m_Type{Type::Unset};
union
{
std::uint8_t m_bit8;
std::array<std::uint8_t, 3> m_bit24;
};
};
std::string color_bg(const Term::Color::Name& name);
std::string color_bg(const std::uint8_t& value);
std::string color_bg(const std::uint8_t& red, const std::uint8_t& green, const std::uint8_t& blue);
std::string color_bg(const Color& color);
std::string color_fg(const Term::Color::Name& name);
std::string color_fg(const std::uint8_t& value);
std::string color_fg(const std::uint8_t& red, const std::uint8_t& green, const std::uint8_t& blue);
std::string color_fg(const Color& color);
} // namespace Term
@@ -0,0 +1,57 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/position.hpp"
#include <string>
namespace Term
{
class Cursor
{
public:
Cursor() = default;
explicit Cursor(const Position& position);
std::size_t row() const;
std::size_t column() const;
bool empty() const;
bool operator==(const Term::Cursor& cursor) const;
bool operator!=(const Term::Cursor& cursor) const;
private:
Position m_position;
};
// returns the current cursor position (row, column) (Y, X)
Term::Cursor cursor_position();
// move the cursor to the given (row, column) / (Y, X)
std::string cursor_move(const std::size_t& row, const std::size_t& column);
// move the cursor the given rows up
std::string cursor_up(const std::size_t& rows);
// move the cursor the given rows down
std::string cursor_down(const std::size_t& rows);
// move the cursor the given columns left
std::string cursor_left(const std::size_t& columns);
// move the cursor the given columns right
std::string cursor_right(const std::size_t& columns);
// the ANSI code to generate a cursor position report
std::string cursor_position_report();
// turn off the cursor
std::string cursor_off();
// turn on the cursor
std::string cursor_on();
// clears the screen from the current cursor position to the end of the screen
std::string clear_eol();
} // namespace Term
@@ -0,0 +1,93 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/cursor.hpp"
#include "cpp-terminal/focus.hpp"
#include "cpp-terminal/key.hpp"
#include "cpp-terminal/mouse.hpp"
#include "cpp-terminal/screen.hpp"
#include <cstdint>
#include <string>
namespace Term
{
class Event
{
public:
enum class Type
{
Empty,
Key,
Screen,
Cursor,
Focus,
Mouse,
CopyPaste,
};
~Event();
Event();
Event(const std::string& str);
Event(const Term::Key& key);
Event(const Term::Screen& screen);
Event(const Term::Cursor& cursor);
Event(const Term::Focus& focus);
Event(const Term::Mouse& mouse);
Event(const Term::Event& event);
Event(Term::Event&& event) noexcept;
Event& operator=(Event&& other) noexcept;
Event& operator=(const Term::Event& event);
bool empty() const;
Type type() const;
operator Term::Key() const;
operator Term::Screen() const;
operator Term::Cursor() const;
operator Term::Focus() const;
operator Term::Mouse() const;
operator std::string() const;
// getters
Key* get_if_key();
const Key* get_if_key() const;
Screen* get_if_screen();
const Screen* get_if_screen() const;
Cursor* get_if_cursor();
const Cursor* get_if_cursor() const;
Focus* get_if_focus();
const Focus* get_if_focus() const;
Mouse* get_if_mouse();
const Mouse* get_if_mouse() const;
std::string* get_if_copy_paste();
const std::string* get_if_copy_paste() const;
private:
void parse(const std::string& str);
union container
{
container();
~container();
container(const container&) = delete;
container(container&&) = delete;
container& operator=(const container&) = delete;
container& operator=(container&&) = delete;
Term::Key m_Key;
Term::Cursor m_Cursor;
Term::Screen m_Screen;
Term::Focus m_Focus;
Term::Mouse m_Mouse;
std::string m_string;
};
Type m_Type{Type::Empty};
container m_container;
};
} // namespace Term
@@ -0,0 +1,50 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstdint>
#include <exception>
#include <string>
namespace Term
{
class Exception : public std::exception
{
public:
explicit Exception(const std::string& message) noexcept;
Exception(const std::int64_t& code, const std::string& message) noexcept;
Exception(const Exception&) = default;
Exception(Exception&&) = default;
Exception& operator=(Exception&&) = default;
Exception& operator=(const Exception&) = default;
const char* what() const noexcept override;
std::int64_t code() const noexcept;
std::string message() const noexcept;
std::string context() const noexcept;
~Exception() noexcept override = default;
protected:
explicit Exception(const std::int64_t& code) noexcept;
virtual void build_what() const noexcept;
void setMessage(const std::string& message) noexcept;
void setContext(const std::string& context) noexcept;
void setWhat(const std::string& what) const noexcept;
static const constexpr std::size_t m_maxSize{256};
private:
std::int64_t m_code{0};
std::string m_message;
std::string m_context;
mutable std::string m_what;
};
} // namespace Term
@@ -0,0 +1,65 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstdint>
namespace Term
{
/**
* @brief Class to return the focus of the terminal
*
*/
class Focus
{
public:
enum class Type : std::int8_t
{
Unknown = -1, ///< The terminal focus is \b unknown.
Out = 0, ///< The terminal focus is \b out.
In = 1, ///< The terminal focus is \b in.
};
Focus() = default;
explicit Focus(const Term::Focus::Type& type);
/**
* @brief Get the type of focus.
*
* @return Term::Focus::Type
*/
Term::Focus::Type type() const;
/**
* @brief Check is the focus is \b in.
*
* @return true : The terminal has focus \b in.
* @return false : The terminal has focus \b out.
*/
bool in() const;
/**
* @brief Check is the focus is \b out.
*
* @return true : The terminal has focus \b out.
* @return false : The terminal has focus \b in.
*/
bool out() const;
bool operator==(const Term::Focus& focus) const;
bool operator!=(const Term::Focus& focus) const;
private:
Term::Focus::Type m_focus{Term::Focus::Type::Unknown};
};
} // namespace Term
@@ -0,0 +1,19 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/event.hpp"
namespace Term
{
Term::Event read_event();
} // namespace Term
@@ -0,0 +1,25 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/iostream_initializer.hpp"
#include "cpp-terminal/stream.hpp"
namespace Term
{
extern TIstream& cin;
extern TOstream& cout;
extern TOstream& cerr;
extern TOstream& clog;
static const IOStreamInitializer IO_stream_initializer; //NOLINT(cert-err58-cpp,fuchsia-statically-constructed-objects)
} // namespace Term
@@ -0,0 +1,31 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstddef>
namespace Term
{
class IOStreamInitializer
{
public:
~IOStreamInitializer() noexcept;
IOStreamInitializer() noexcept;
IOStreamInitializer(const IOStreamInitializer&) = delete;
IOStreamInitializer(IOStreamInitializer&&) = delete;
IOStreamInitializer& operator=(IOStreamInitializer&&) = delete;
IOStreamInitializer& operator=(const IOStreamInitializer&) = delete;
private:
static std::size_t m_counter;
};
} // namespace Term
+450
View File
@@ -0,0 +1,450 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstdint>
#include <string>
namespace Term
{
class MetaKey
{
public:
enum class Value : std::int32_t
{
// Last utf8 codepoint is U+10FFFF (000100001111111111111111) So:
None = 0,
Alt = (1UL << 22UL),
Ctrl = (1UL << 23UL),
};
constexpr MetaKey() : value(static_cast<std::int32_t>(Value::None)) {}
constexpr MetaKey(const MetaKey& key) = default;
inline MetaKey& operator=(const MetaKey& key) = default;
constexpr MetaKey(const Value& v) : value(static_cast<std::int32_t>(v)) {}
inline MetaKey& operator=(const Value& v)
{
this->value = static_cast<std::int32_t>(v);
return *this;
}
explicit constexpr MetaKey(std::int32_t val) : value(val) {}
inline MetaKey& operator=(std::int32_t val)
{
this->value = val;
return *this;
}
explicit constexpr operator std::int32_t() const { return this->value; }
constexpr bool hasAlt() const { return (this->value & static_cast<std::int32_t>(MetaKey::Value::Alt)) == static_cast<std::int32_t>(MetaKey::Value::Alt); }
constexpr bool hasCtrl() const { return (this->value & static_cast<std::int32_t>(MetaKey::Value::Ctrl)) == static_cast<std::int32_t>(MetaKey::Value::Ctrl); }
friend constexpr MetaKey operator+(MetaKey l, MetaKey r) { return MetaKey(l.value | r.value); }
friend constexpr MetaKey operator+(MetaKey::Value l, MetaKey::Value r) { return MetaKey(l) + MetaKey(r); }
friend constexpr MetaKey operator+(MetaKey l, MetaKey::Value r) { return l + MetaKey(r); }
friend constexpr MetaKey operator+(MetaKey::Value l, MetaKey r) { return MetaKey(l) + r; }
MetaKey& operator+=(MetaKey r) { return *this = *this + r; }
MetaKey& operator+=(MetaKey::Value r) { return *this = *this + r; }
friend constexpr bool operator==(MetaKey l, MetaKey r) { return l.value == r.value; }
friend constexpr bool operator==(MetaKey l, MetaKey::Value r) { return l == MetaKey(r); }
friend constexpr bool operator==(MetaKey::Value l, MetaKey r) { return MetaKey(l) == r; }
friend constexpr bool operator!=(MetaKey l, MetaKey r) { return !(l == r); }
friend constexpr bool operator!=(MetaKey l, MetaKey::Value r) { return !(l == r); }
friend constexpr bool operator!=(MetaKey::Value l, MetaKey r) { return !(l == r); }
friend constexpr bool operator<(MetaKey l, MetaKey r) { return l.value < r.value; }
friend constexpr bool operator>=(MetaKey l, MetaKey r) { return !(l < r); }
friend constexpr bool operator>=(MetaKey l, MetaKey::Value r) { return !(l < r); }
friend constexpr bool operator>=(MetaKey::Value l, MetaKey r) { return !(l < r); }
friend constexpr bool operator>(MetaKey l, MetaKey r) { return r < l; }
friend constexpr bool operator>(MetaKey l, MetaKey::Value r) { return r < l; }
friend constexpr bool operator>(MetaKey::Value l, MetaKey r) { return r < l; }
friend constexpr bool operator<=(MetaKey l, MetaKey r) { return !(l > r); }
friend constexpr bool operator<=(MetaKey l, MetaKey::Value r) { return !(l > r); }
friend constexpr bool operator<=(MetaKey::Value l, MetaKey r) { return !(l > r); }
std::int32_t value;
};
class Key
{
public:
using value_type = std::int32_t;
enum Value : std::int32_t
{
NoKey = -1,
// Now use << to for detecting special key + key press
// Begin ASCII (some ASCII names has been change to their Ctrl_+key part) the value is different to be able to do
Ctrl_Arobase = 0,
Ctrl_A = 1,
Ctrl_B = 2,
Ctrl_C = 3,
Ctrl_D = 4,
Ctrl_E = 5,
Ctrl_F = 6,
Ctrl_G = 7,
Ctrl_H = 8, /*corresponds to Backspace*/
Ctrl_I = 9, /*corresponds to Tab*/
Ctrl_J = 10,
Ctrl_K = 11,
Ctrl_L = 12,
Ctrl_M = 13, /*corresponds to Enter*/
Ctrl_N = 14,
Ctrl_O = 15,
Ctrl_P = 16,
Ctrl_Q = 17,
Ctrl_R = 18,
Ctrl_S = 19,
Ctrl_T = 20,
Ctrl_U = 21,
Ctrl_V = 22,
Ctrl_W = 23,
Ctrl_X = 24,
Ctrl_Y = 25,
Ctrl_Z = 26,
Ctrl_OpenBracket = 27, /*corresponds to Escape*/
Ctrl_BackSlash = 28,
Ctrl_CloseBracket = 29,
Ctrl_Caret = 30,
Ctrl_Underscore = 31,
Space = 32,
ExclamationMark = 33,
Quote = 34,
Hash = 35,
Dollar = 36,
Percent = 37,
Ampersand = 38,
Apostrophe = 39,
OpenParenthesis = 40,
CloseParenthesis = 41,
Asterisk = 42,
Plus = 43,
Comma = 44,
Hyphen = 45,
Minus = 45,
Period = 46,
Slash = 47,
Zero = 48,
One = 49,
Two = 50,
Three = 51,
Four = 52,
Five = 53,
Six = 54,
Seven = 55,
Eight = 56,
Nine = 57,
Colon = 58,
Semicolon = 59,
LessThan = 60,
OpenChevron = 60,
Equal = 61,
GreaterThan = 62,
CloseChevron = 62,
QuestionMark = 63,
Arobase = 64,
A = 65,
B = 66,
C = 67,
D = 68,
E = 69,
F = 70,
G = 71,
H = 72,
I = 73,
J = 74,
K = 75,
L = 76,
M = 77,
N = 78,
O = 79,
P = 80,
Q = 81,
R = 82,
S = 83,
T = 84,
U = 85,
V = 86,
W = 87,
X = 88,
Y = 89,
Z = 90,
OpenBracket = 91,
Backslash = 92,
CloseBracket = 93,
Caret = 94,
Underscore = 95,
GraveAccent = 96,
a = 97,
b = 98,
c = 99,
d = 100,
e = 101,
f = 102,
g = 103,
h = 104,
i = 105,
j = 106,
k = 107,
l = 108,
m = 109,
n = 110,
o = 111,
p = 112,
q = 113,
r = 114,
s = 115,
t = 116,
u = 117,
v = 118,
w = 119,
x = 120,
y = 121,
z = 122,
OpenBrace = 123,
VerticalBar = 124,
Close_Brace = 125,
Tilde = 126,
CTRL_QuestionMark = 127, /*corresponds to DEL*/
// Very useful CTRL_* alternative names
Null = 0,
Backspace = 8,
Tab = 9,
Enter = 13,
Esc = 27,
Del = 127,
//
// End ASCII
// Extended ASCII goes up to 255
// Last Unicode codepage 0x10FFFF
ArrowLeft = 0x10FFFF + 1,
ArrowRight = 0x10FFFF + 2,
ArrowUp = 0x10FFFF + 3,
ArrowDown = 0x10FFFF + 4,
Numeric5 = 0x10FFFF + 5,
Home = 0x10FFFF + 6,
Insert = 0x10FFFF + 7,
End = 0x10FFFF + 8,
PageUp = 0x10FFFF + 9,
PageDown = 0x10FFFF + 10,
F1 = 0x10FFFF + 11,
F2 = 0x10FFFF + 12,
F3 = 0x10FFFF + 13,
F4 = 0x10FFFF + 14,
F5 = 0x10FFFF + 15,
F6 = 0x10FFFF + 16,
F7 = 0x10FFFF + 17,
F8 = 0x10FFFF + 18,
F9 = 0x10FFFF + 19,
F10 = 0x10FFFF + 20,
F11 = 0x10FFFF + 21,
F12 = 0x10FFFF + 22,
F13 = 0x10FFFF + 23,
F14 = 0x10FFFF + 24,
F15 = 0x10FFFF + 25,
F16 = 0x10FFFF + 26,
F17 = 0x10FFFF + 27,
F18 = 0x10FFFF + 28,
F19 = 0x10FFFF + 29,
F20 = 0x10FFFF + 30,
F21 = 0x10FFFF + 31,
F22 = 0x10FFFF + 32,
F23 = 0x10FFFF + 33,
F24 = 0x10FFFF + 34,
PrintScreen = 0x10FFFF + 35,
Menu = 0x10FFFF + 36,
};
constexpr Key() : value(NoKey) {}
constexpr Key(const Key& key) = default;
inline Key& operator=(const Key& key) = default;
constexpr Key(const Value& v) : value(static_cast<std::int32_t>(v)) {}
inline Key& operator=(const Value& v)
{
this->value = static_cast<std::int32_t>(v);
return *this;
}
explicit constexpr Key(char val) : value(static_cast<std::int32_t>(val)) {}
inline Key& operator=(char val)
{
value = static_cast<std::int32_t>(val);
return *this;
}
constexpr Key(std::int32_t val) : value(val) {}
inline Key& operator=(std::int32_t val)
{
value = val;
return *this;
}
explicit constexpr Key(std::size_t val) : value(static_cast<std::int32_t>(val)) {}
inline Key& operator=(std::size_t val)
{
value = static_cast<std::int32_t>(val);
return *this;
}
explicit constexpr Key(char32_t val) : value(static_cast<std::int32_t>(val)) {}
inline Key& operator=(char32_t val)
{
value = static_cast<std::int32_t>(val);
return *this;
}
constexpr operator std::int32_t() const { return this->value; }
friend constexpr bool operator==(Key l, Key r) { return l.value == r.value; }
friend constexpr bool operator==(Key l, char r) { return l == Key(r); }
friend constexpr bool operator==(char l, Key r) { return Key(l) == r; }
friend constexpr bool operator==(Key l, char32_t r) { return l == Key(r); }
friend constexpr bool operator==(char32_t l, Key r) { return Key(l) == r; }
friend constexpr bool operator==(Key l, std::int32_t r) { return l == Key(r); }
friend constexpr bool operator==(std::int32_t l, Key r) { return Key(l) == r; }
friend constexpr bool operator==(Key l, std::size_t r) { return static_cast<std::size_t>(l.value) == r; }
friend constexpr bool operator==(std::size_t l, Key r) { return l == static_cast<std::size_t>(r.value); }
friend constexpr bool operator!=(Key l, Key r) { return !(l == r); }
friend constexpr bool operator!=(Key l, char r) { return !(l == r); }
friend constexpr bool operator!=(char l, Key r) { return !(l == r); }
friend constexpr bool operator!=(Key l, char32_t r) { return !(l == r); }
friend constexpr bool operator!=(char32_t l, Key r) { return !(l == r); }
friend constexpr bool operator!=(Key l, std::int32_t r) { return !(l == r); }
friend constexpr bool operator!=(std::int32_t l, Key r) { return !(l == r); }
friend constexpr bool operator!=(Key l, std::size_t r) { return !(l == r); }
friend constexpr bool operator!=(std::size_t l, Key r) { return !(l == r); }
friend constexpr bool operator<(Key l, Key r) { return l.value < r.value; }
friend constexpr bool operator<(Key l, char r) { return l < Key(r); }
friend constexpr bool operator<(char l, Key r) { return Key(l) < r; }
friend constexpr bool operator<(Key l, char32_t r) { return l < Key(r); }
friend constexpr bool operator<(char32_t l, Key r) { return Key(l) < r; }
friend constexpr bool operator<(Key l, std::int32_t r) { return l < Key(r); }
friend constexpr bool operator<(std::int32_t l, Key r) { return Key(l) < r; }
friend constexpr bool operator<(Key l, std::size_t r) { return static_cast<std::size_t>(l.value) < r; }
friend constexpr bool operator<(std::size_t l, Key r) { return l < static_cast<std::size_t>(r.value); }
friend constexpr bool operator>=(Key l, Key r) { return !(l < r); }
friend constexpr bool operator>=(Key l, char r) { return !(l < r); }
friend constexpr bool operator>=(char l, Key r) { return !(l < r); }
friend constexpr bool operator>=(Key l, char32_t r) { return !(l < r); }
friend constexpr bool operator>=(char32_t l, Key r) { return !(l < r); }
friend constexpr bool operator>=(Key l, std::int32_t r) { return !(l < r); }
friend constexpr bool operator>=(std::int32_t l, Key r) { return !(l < r); }
friend constexpr bool operator>=(Key l, std::size_t r) { return !(l < r); }
friend constexpr bool operator>=(std::size_t l, Key r) { return !(l < r); }
friend constexpr bool operator>(Key l, Key r) { return r < l; }
friend constexpr bool operator>(Key l, char r) { return r < l; }
friend constexpr bool operator>(char l, Key r) { return r < l; }
friend constexpr bool operator>(Key l, char32_t r) { return r < l; }
friend constexpr bool operator>(char32_t l, Key r) { return r < l; }
friend constexpr bool operator>(Key l, std::int32_t r) { return r < l; }
friend constexpr bool operator>(std::int32_t l, Key r) { return r < l; }
friend constexpr bool operator>(Key l, std::size_t r) { return r < l; }
friend constexpr bool operator>(std::size_t l, Key r) { return r < l; }
friend constexpr bool operator<=(Key l, Key r) { return !(l > r); }
friend constexpr bool operator<=(Key l, char r) { return !(l > r); }
friend constexpr bool operator<=(char l, Key r) { return !(l > r); }
friend constexpr bool operator<=(Key l, char32_t r) { return !(l > r); }
friend constexpr bool operator<=(char32_t l, Key r) { return !(l > r); }
friend constexpr bool operator<=(Key l, std::int32_t r) { return !(l > r); }
friend constexpr bool operator<=(std::int32_t l, Key r) { return !(l > r); }
friend constexpr bool operator<=(Key l, std::size_t r) { return !(l > r); }
friend constexpr bool operator<=(std::size_t l, Key r) { return !(l > r); }
constexpr bool iscntrl() const { return (*this >= Key::Null && *this <= Key::Ctrl_Underscore) || *this == Key::Del; }
constexpr bool isblank() const { return *this == Key::Tab || *this == Key::Space; }
constexpr bool isspace() const { return this->isblank() || (*this >= Key::Ctrl_J && *this <= Key::Enter); }
constexpr bool isupper() const { return *this >= Key::A && *this <= Key::Z; }
constexpr bool islower() const { return *this >= Key::a && *this <= Key::z; }
constexpr bool isalpha() const { return (this->isupper() || this->islower()); }
constexpr bool isdigit() const { return *this >= Key::Zero && *this <= Key::Nine; }
constexpr bool isxdigit() const { return this->isdigit() || (*this >= Key::A && *this <= Key::F) || (*this >= Key::a && *this <= Key::f); }
constexpr bool isalnum() const { return (this->isdigit() || this->isalpha()); }
constexpr bool ispunct() const { return (*this >= Key::ExclamationMark && *this <= Key::Slash) || (*this >= Key::Colon && *this <= Key::Arobase) || (*this >= Key::OpenBracket && *this <= Key::GraveAccent) || (*this >= Key::OpenBrace && *this <= Key::Tilde); }
constexpr bool isgraph() const { return (this->isalnum() || this->ispunct()); }
constexpr bool isprint() const { return (this->isgraph() || *this == Key::Space); }
constexpr bool isunicode() const { return *this >= Key::Null && this->value <= 0x10FFFFL; }
constexpr Key tolower() const { return (this->isalpha() && this->isupper()) ? Key(this->value + 32) : *this; }
constexpr Key toupper() const { return (this->isalpha() && this->islower()) ? Key(this->value - 32) : *this; }
// Detect if *this is convertible to ANSII
constexpr bool isASCII() const { return *this >= Key::Null && *this <= Key::Del; }
// Detect if *this is convertible to Extended ANSII
constexpr bool isExtendedASCII() const { return *this >= Key::Null && this->value <= 255L; }
// Detect if *this has CTRL+*
constexpr bool hasCtrlAll() const { return this->iscntrl() || ((this->value & static_cast<std::int32_t>(MetaKey::Value::Ctrl)) == static_cast<std::int32_t>(MetaKey::Value::Ctrl)); }
// Detect if *this has CTRL+* (excluding the CTRL+* that can be access with standard *this Tab Backspace Enter...)
constexpr bool hasCtrl() const
{
// Need to suppress the TAB etc...
return ((this->iscntrl() || this->hasCtrlAll()) && *this != Key::Backspace && *this != Key::Tab && *this != Key::Esc && *this != Key::Enter && *this != Key::Del);
}
// Detect if key has ALT+*
constexpr bool hasAlt() const { return (this->value & static_cast<std::int32_t>(MetaKey::Value::Alt)) == static_cast<std::int32_t>(MetaKey::Value::Alt); }
constexpr bool empty() const { return (this->value == Key::NoKey); }
void append_name(std::string& strOut) const;
std::string name() const;
std::string str() const;
// member variable value
// cannot be Key::Value and has to be std::int32_t because it can also have numbers
// that are not named within the enum. Otherwise it would be undefined behaviour
std::int32_t value;
};
constexpr bool operator==(Key l, MetaKey r) { return static_cast<std::int32_t>(l) == static_cast<std::int32_t>(r); }
constexpr bool operator==(MetaKey l, Key r) { return static_cast<std::int32_t>(l) == static_cast<std::int32_t>(r); }
constexpr bool operator<(MetaKey l, Key r) { return static_cast<std::int32_t>(l) < static_cast<std::int32_t>(r); }
constexpr bool operator<(Key l, MetaKey r) { return static_cast<std::int32_t>(l) < static_cast<std::int32_t>(r); }
constexpr bool operator!=(Key l, MetaKey r) { return static_cast<std::int32_t>(l) != static_cast<std::int32_t>(r); }
constexpr bool operator!=(MetaKey l, Key r) { return static_cast<std::int32_t>(l) != static_cast<std::int32_t>(r); }
constexpr bool operator>=(MetaKey l, Key r) { return static_cast<std::int32_t>(l) >= static_cast<std::int32_t>(r); }
constexpr bool operator>=(Key l, MetaKey r) { return static_cast<std::int32_t>(l) >= static_cast<std::int32_t>(r); }
constexpr bool operator>(MetaKey l, Key r) { return static_cast<std::int32_t>(l) > static_cast<std::int32_t>(r); }
constexpr bool operator>(Key l, MetaKey r) { return static_cast<std::int32_t>(l) > static_cast<std::int32_t>(r); }
constexpr bool operator<=(MetaKey l, Key r) { return static_cast<std::int32_t>(l) <= static_cast<std::int32_t>(r); }
constexpr bool operator<=(Key l, MetaKey r) { return static_cast<std::int32_t>(l) <= static_cast<std::int32_t>(r); }
constexpr Key operator+(MetaKey metakey, Key key) { return Key(key.value + ((metakey == MetaKey::Value::Ctrl && !key.hasCtrlAll() && !key.empty()) ? static_cast<std::int32_t>(MetaKey::Value::Ctrl) : 0) + ((metakey == MetaKey::Value::Alt && !key.hasAlt() && !key.empty()) ? static_cast<std::int32_t>(MetaKey::Value::Alt) : 0)); }
constexpr Key operator+(Key key, MetaKey meta) { return meta + key; }
constexpr Key operator+(MetaKey::Value l, Key r) { return MetaKey(l) + r; }
constexpr Key operator+(Key l, MetaKey::Value r) { return l + MetaKey(r); }
constexpr Key operator+(MetaKey::Value l, Key::value_type r) { return MetaKey(l) + Key(r); }
constexpr Key operator+(Key::value_type l, MetaKey::Value r) { return Key(l) + MetaKey(r); }
} // namespace Term
@@ -0,0 +1,78 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstddef>
#include <cstdint>
namespace Term
{
class Button
{
public:
enum class Type : std::int8_t
{
None = -1,
Left,
Wheel,
Right,
Button1,
Button2,
Button3,
Button4,
Button5,
Button6,
Button7,
Button8,
};
enum class Action : std::int8_t
{
None = 0,
Pressed,
Released,
DoubleClicked,
RolledUp,
RolledDown,
ToRight,
ToLeft,
};
Button() = default;
Button(const Term::Button::Type& type, const Term::Button::Action& action) : m_type(type), m_action(action) {}
Term::Button::Action action() const noexcept;
Term::Button::Type type() const noexcept;
bool operator==(const Term::Button& button) const;
bool operator!=(const Term::Button& button) const;
private:
Term::Button::Type m_type{Term::Button::Type::None};
Term::Button::Action m_action{Term::Button::Action::None};
};
class Mouse
{
public:
Mouse() = default;
Mouse(const Term::Button& buttons, const std::uint16_t& row, const std::uint16_t& column) : m_buttons(buttons), m_row(row), m_column(column) {}
std::size_t row() const noexcept;
std::size_t column() const noexcept;
Term::Button getButton() const noexcept;
bool is(const Term::Button::Type& type, const Term::Button::Action& action) const noexcept;
bool is(const Term::Button::Type& type) const noexcept;
bool operator==(const Term::Mouse& mouse) const;
bool operator!=(const Term::Mouse& mouse) const;
private:
Term::Button m_buttons;
std::uint16_t m_row{0};
std::uint16_t m_column{0};
};
} // namespace Term
@@ -0,0 +1,50 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstdint>
#include <initializer_list>
#include <vector>
namespace Term
{
///
/// @brief Option to set-up the terminal.
///
enum class Option : std::int16_t
{
Raw = 1, ///< Set terminal in \b raw mode.
Cooked = -1, ///< Set terminal in \b cooked mode.
ClearScreen = 2, ///< Clear the screen (and restore its states when the program stops).
NoClearScreen = -2, ///< Doesn't clear the screen.
SignalKeys = 3, ///< Enable the signal keys (Ctrl+C, etc...), if activated these keys will have their default OS behaviour.
NoSignalKeys = -3, ///< Disable the signal keys (Ctrl+C, etc...) will not be processed by the OS and will appears has standard combination keys.
Cursor = 4, ///< Show the cursor.
NoCursor = -4 ///< Hide the cursor (and restore its states when the program stops).
};
class Options
{
public:
Options() = default;
Options(const std::initializer_list<Term::Option>& option);
template<typename... Args> explicit Options(const Args&&... args) : m_Options(std::initializer_list<Term::Option>{args...}) { clean(); }
bool operator==(const Options& options) const;
bool operator!=(const Options& options) const;
bool has(const Option& option) const noexcept;
private:
void clean();
std::vector<Term::Option> m_Options;
};
} // namespace Term
@@ -0,0 +1,54 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstddef>
#include <cstdint>
namespace Term
{
class Row
{
public:
Row() = default;
explicit Row(const std::uint16_t& row) : m_row(row) {}
operator std::size_t() const noexcept { return m_row; }
private:
std::uint16_t m_row{0};
};
class Column
{
public:
Column() = default;
explicit Column(const std::uint16_t& column) : m_column(column) {}
operator std::size_t() const noexcept { return m_column; }
private:
std::uint16_t m_column{0};
};
class Position
{
public:
Position() = default;
Position(const Row& row, const Column& column) : m_row(row), m_column(column) {};
Position(const Column& column, const Row& row) : m_row(row), m_column(column) {};
const Row& row() const noexcept { return m_row; }
const Column& column() const noexcept { return m_column; }
private:
Row m_row{1};
Column m_column{1};
};
} // namespace Term
@@ -0,0 +1,30 @@
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads REQUIRED MODULE)
# configure version information
configure_file(version.cpp.in version.cpp)
add_library(cpp-terminal-private INTERFACE)
target_sources(cpp-terminal-private INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/version.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/return_code.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/file_initializer.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/exception.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/unicode.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/conversion.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/args.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/terminal_impl.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/tty.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/terminfo.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/input.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/screen.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/cursor.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/file.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/env.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/blocking_queue.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/sigwinch.cpp>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/signals.cpp>
)
target_link_libraries(cpp-terminal-private INTERFACE Warnings::Warnings Threads::Threads)
target_compile_options(cpp-terminal-private INTERFACE $<$<CXX_COMPILER_ID:MSVC>:/utf-8>)
target_include_directories(cpp-terminal-private INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
add_library(cpp-terminal::cpp-terminal-private ALIAS cpp-terminal-private)
@@ -0,0 +1 @@
# Private folder for cpp-terminal
@@ -0,0 +1,97 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/args.hpp"
#include <ios>
#if defined(_WIN32)
#include <memory>
// clang-format off
#pragma warning( push )
#pragma warning( disable : 4668)
#include <windows.h>
#include <processenv.h>
#include "cpp-terminal/private/unicode.hpp"
// clang-format on
#elif defined(__APPLE__)
#include <crt_externs.h>
#else
#include <algorithm>
#include <cstddef>
#include <fstream>
#include <limits>
#endif
void Term::Arguments::parse()
{
if(m_parsed) { return; }
#if defined(_WIN32)
int argc{0};
std::unique_ptr<LPWSTR[], void (*)(wchar_t**)> wargv = std::unique_ptr<LPWSTR[], void (*)(wchar_t**)>(CommandLineToArgvW(GetCommandLineW(), &argc), [](wchar_t** ptr) { LocalFree(ptr); });
if(wargv == nullptr)
{
m_parsed = false;
return;
}
else
{
m_args.reserve(static_cast<std::size_t>(argc));
for(std::size_t i = 0; i != static_cast<std::size_t>(argc); ++i) { m_args.push_back(Term::Private::to_narrow(&wargv.get()[i][0])); }
m_parsed = true;
}
#elif defined(__APPLE__)
std::size_t argc{static_cast<std::size_t>(*_NSGetArgc())};
m_args.reserve(argc);
char** argv{*_NSGetArgv()};
for(std::size_t i = 0; i != argc; ++i) { m_args.push_back(argv[i]); }
m_parsed = true;
#else
std::string cmdline;
std::fstream file_stream;
const std::fstream::iostate old_iostate{file_stream.exceptions()};
try
{
file_stream.exceptions(std::ifstream::failbit | std::ifstream::badbit);
file_stream.open("/proc/self/cmdline", std::fstream::in | std::fstream::binary);
file_stream.ignore(std::numeric_limits<std::streamsize>::max());
cmdline.resize(static_cast<std::size_t>(file_stream.gcount()));
file_stream.seekg(0, std::ios_base::beg);
file_stream.get(&cmdline[0], static_cast<std::streamsize>(cmdline.size())); //NOLINT(readability-container-data-pointer)
file_stream.exceptions(old_iostate);
if(file_stream.is_open()) { file_stream.close(); }
m_args.reserve(static_cast<std::size_t>(std::count(cmdline.begin(), cmdline.end(), '\0')));
for(std::string::iterator it = cmdline.begin(); it != cmdline.end(); it = std::find(it, cmdline.end(), '\0') + 1) { m_args.emplace_back(cmdline.data() + (it - cmdline.begin())); }
m_parsed = true;
}
catch(...)
{
file_stream.exceptions(old_iostate);
if(file_stream.is_open()) { file_stream.close(); }
m_args.clear();
m_parsed = false;
}
#endif
}
std::size_t Term::Arguments::argc()
{
parse();
return m_args.size();
}
std::vector<std::string> Term::Arguments::argv()
{
parse();
return m_args;
}
bool Term::Arguments::m_parsed = false;
std::vector<std::string> Term::Arguments::m_args = std::vector<std::string>(); //NOLINT(fuchsia-statically-constructed-objects)
@@ -0,0 +1,52 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/private/blocking_queue.hpp"
Term::Event Term::Private::BlockingQueue::pop()
{
const std::lock_guard<std::mutex> lock(m_mutex);
Term::Event value = this->m_queue.front();
m_queue.pop();
return value;
}
void Term::Private::BlockingQueue::push(const Term::Event& value, const std::size_t& occurrence)
{
for(std::size_t i = 0; i != occurrence; ++i)
{
const std::lock_guard<std::mutex> lock(m_mutex);
m_queue.push(value);
m_cv.notify_all();
}
}
void Term::Private::BlockingQueue::push(const Term::Event&& value, const std::size_t& occurrence)
{
for(std::size_t i = 0; i != occurrence; ++i)
{
const std::lock_guard<std::mutex> lock(m_mutex);
m_queue.push(value);
m_cv.notify_all();
}
}
bool Term::Private::BlockingQueue::empty()
{
const std::lock_guard<std::mutex> lock(m_mutex);
return m_queue.empty();
}
std::size_t Term::Private::BlockingQueue::size()
{
const std::lock_guard<std::mutex> lock(m_mutex);
return m_queue.size();
}
void Term::Private::BlockingQueue::wait_for_events(std::unique_lock<std::mutex>& lock) { m_cv.wait(lock); }
@@ -0,0 +1,47 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/event.hpp"
#include <condition_variable>
#include <mutex>
#include <queue>
namespace Term
{
namespace Private
{
class BlockingQueue
{
public:
~BlockingQueue() = default;
BlockingQueue() = default;
BlockingQueue(const BlockingQueue& other) = delete;
BlockingQueue(BlockingQueue&& other) = delete;
BlockingQueue& operator=(const BlockingQueue& other) = delete;
BlockingQueue& operator=(BlockingQueue&& other) = delete;
Term::Event pop();
void push(const Term::Event& value, const std::size_t& occurrence = 1);
void push(const Term::Event&& value, const std::size_t& occurrence = 1);
bool empty();
std::size_t size();
void wait_for_events(std::unique_lock<std::mutex>& lock);
private:
std::mutex m_mutex;
std::queue<Term::Event> m_queue;
std::condition_variable m_cv;
};
} // namespace Private
} // namespace Term
@@ -0,0 +1,74 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/private/conversion.hpp"
#include "cpp-terminal/exception.hpp"
#include <array>
#include <string>
namespace Term
{
namespace Private
{
static constexpr std::uint8_t UTF8_ACCEPT{0};
static constexpr std::uint8_t UTF8_REJECT{0xf};
std::uint8_t utf8_decode_step(std::uint8_t state, std::uint8_t octet, std::uint32_t* cpp)
{
static const constexpr std::array<std::uint32_t, 0x10> utf8ClassTab{0x88888888UL, 0x88888888UL, 0x99999999UL, 0x99999999UL, 0xaaaaaaaaUL, 0xaaaaaaaaUL, 0xaaaaaaaaUL, 0xaaaaaaaaUL, 0x222222ffUL, 0x22222222UL, 0x22222222UL, 0x22222222UL, 0x3333333bUL, 0x33433333UL, 0xfff5666cUL, 0xffffffffUL};
static const constexpr std::array<std::uint32_t, 0x10> utf8StateTab{0xfffffff0UL, 0xffffffffUL, 0xfffffff1UL, 0xfffffff3UL, 0xfffffff4UL, 0xfffffff7UL, 0xfffffff6UL, 0xffffffffUL, 0x33f11f0fUL, 0xf3311f0fUL, 0xf33f110fUL, 0xfffffff2UL, 0xfffffff5UL, 0xffffffffUL, 0xffffffffUL, 0xffffffffUL};
const std::uint8_t reject{static_cast<std::uint8_t>(state >> 3UL)};
const std::uint8_t nonAscii{static_cast<std::uint8_t>(octet >> 7UL)};
const std::uint8_t class_{static_cast<std::uint8_t>(!nonAscii ? 0 : (0xf & (utf8ClassTab[(octet >> 3) & 0xf] >> (4 * (octet & 7)))))};
*cpp = (state == UTF8_ACCEPT ? (octet & (0xffU >> class_)) : ((octet & 0x3fU) | (*cpp << 6)));
return (reject ? 0xf : (0xf & (utf8StateTab[class_] >> (4 * (state & 7)))));
}
std::u32string utf8_to_utf32(const std::string& str)
{
std::uint32_t codepoint{0};
std::uint8_t state{UTF8_ACCEPT};
std::u32string ret;
for(char idx: str)
{
state = utf8_decode_step(state, static_cast<std::uint8_t>(idx), &codepoint);
if(state == UTF8_ACCEPT) { ret.push_back(codepoint); }
else if(state == UTF8_REJECT) { throw Term::Exception("Invalid byte in UTF8 encoded string"); }
}
if(state != UTF8_ACCEPT) { throw Term::Exception("Expected more bytes in UTF8 encoded string"); }
return ret;
}
bool is_valid_utf8_code_unit(const std::string& str)
{
static const constexpr std::uint8_t b1OOOOOOO{128};
static const constexpr std::uint8_t b11OOOOOO{192};
static const constexpr std::uint8_t b111OOOOO{224};
static const constexpr std::uint8_t b1111OOOO{240};
static const constexpr std::uint8_t b11111OOO{248};
switch(str.size())
{
case 1: return (static_cast<std::uint8_t>(str[0]) & b1OOOOOOO) == 0;
case 2: return ((static_cast<std::uint8_t>(str[0]) & b111OOOOO) == b11OOOOOO) && ((static_cast<std::uint8_t>(str[1]) & b11OOOOOO) == b1OOOOOOO);
case 3: return ((static_cast<std::uint8_t>(str[0]) & b1111OOOO) == b111OOOOO) && ((static_cast<std::uint8_t>(str[1]) & b11OOOOOO) == b1OOOOOOO) && ((static_cast<std::uint8_t>(str[2]) & b11OOOOOO) == b1OOOOOOO);
case 4: return ((static_cast<std::uint8_t>(str[0]) & b11111OOO) == b1111OOOO) && ((static_cast<std::uint8_t>(str[1]) & b11OOOOOO) == b1OOOOOOO) && ((static_cast<std::uint8_t>(str[2]) & b11OOOOOO) == b1OOOOOOO) && ((static_cast<std::uint8_t>(str[3]) & b11OOOOOO) == b1OOOOOOO);
default: return false;
}
}
} // namespace Private
} // namespace Term
@@ -0,0 +1,29 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace Term
{
namespace Private
{
std::uint8_t utf8_decode_step(std::uint8_t state, std::uint8_t octet, std::uint32_t* cpp);
std::u32string utf8_to_utf32(const std::string& str);
bool is_valid_utf8_code_unit(const std::string& str);
} // namespace Private
} // namespace Term
@@ -0,0 +1,77 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/cursor.hpp"
#include "file_initializer.hpp"
#if defined(_WIN32)
#pragma warning(push)
#pragma warning(disable : 4668)
#include <windows.h>
#pragma warning(pop)
#else
#include <sys/ioctl.h>
#include <termios.h>
#endif
#include "cpp-terminal/private/file.hpp"
Term::Cursor Term::cursor_position()
{
static const Term::Private::FileInitializer files_init;
if(Term::Private::in.null()) { return {}; }
#if defined(_WIN32)
CONSOLE_SCREEN_BUFFER_INFO inf;
if(GetConsoleScreenBufferInfo(Private::out.handle(), &inf)) return Term::Cursor({Row(inf.dwCursorPosition.Y + 1), Column(inf.dwCursorPosition.X + 1)});
else
return {};
#else
std::string ret;
std::size_t nread{0};
Term::Private::in.lockIO();
// Hack to be sure we can do this all the time "Cooked" or "Raw" mode
::termios actual;
if(!Private::out.null())
{
if(tcgetattr(Private::out.fd(), &actual) == -1) { return {}; }
}
::termios raw = actual;
// Put terminal in raw mode
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
// This disables output post-processing, requiring explicit \r\n. We
// keep it enabled, so that in C++, one can still just use std::endl
// for EOL instead of "\r\n".
// raw.c_oflag &= ~(OPOST);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN);
raw.c_lflag &= ~ISIG;
raw.c_cc[VMIN] = 1;
raw.c_cc[VTIME] = 0;
if(!Private::out.null()) tcsetattr(Private::out.fd(), TCSAFLUSH, &raw);
Term::Private::out.write(Term::cursor_position_report());
while(nread == 0) { ::ioctl(Private::in.fd(), FIONREAD, &nread); }
ret = Term::Private::in.read();
tcsetattr(Private::out.fd(), TCSAFLUSH, &actual);
Term::Private::in.unlockIO();
try
{
if(ret[0] == '\033' && ret[1] == '[' && ret[ret.size() - 1] == 'R')
{
std::size_t found = ret.find(';', 2);
if(found != std::string::npos) { return Cursor({Row(std::stoi(ret.substr(2, found - 2))), Column(std::stoi(ret.substr(found + 1, ret.size() - (found + 2))))}); }
return {};
}
return {};
}
catch(...)
{
return {};
}
#endif
}
@@ -0,0 +1,28 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/private/env.hpp"
#include "cpp-terminal/private/unicode.hpp"
std::pair<bool, std::string> Term::Private::getenv(const std::string& key)
{
#if defined(_WIN32)
std::size_t size{0};
_wgetenv_s(&size, nullptr, 0, Term::Private::to_wide(key).c_str());
std::wstring ret;
if(size == 0 || size > ret.max_size()) return {false, std::string()};
ret.reserve(size);
_wgetenv_s(&size, &ret[0], size, Term::Private::to_wide(key).c_str());
return {true, Term::Private::to_narrow(ret)};
#else
if(std::getenv(key.c_str()) != nullptr) { return {true, static_cast<std::string>(std::getenv(key.c_str()))}; }
return {false, std::string()};
#endif
}
@@ -0,0 +1,33 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
// This header should be used only in files contained inside platforms folder !!!
#pragma once
#include <string>
#include <utility>
namespace Term
{
namespace Private
{
///
/// @brief Value of an environment variables.
///
/// @param env The environment variable.
/// @return \b std::pair<bool,std::string> with \b bool set to \b true if the environment variable is set and \b std::string set to the value of environment variable.
/// @warning Internal use only.
///
std::pair<bool, std::string> getenv(const std::string& env);
} // namespace Private
} // namespace Term
@@ -0,0 +1,240 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/private/exception.hpp"
#include "cpp-terminal/exception.hpp"
#include "cpp-terminal/version.hpp"
#include "return_code.hpp"
#include <atomic>
#include <exception>
#if defined(_WIN32)
#include "cpp-terminal/private/unicode.hpp"
#include <memory>
#pragma warning(push)
#pragma warning(disable : 4668)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma warning(pop)
#if defined(MessageBox)
#undef MessageBox
#endif
#else
#include <cstring>
#endif
#include <cerrno>
#include <cstdio>
#include <string>
Term::Exception::Exception(const std::string& message) noexcept : m_message(message) {}
Term::Exception::Exception(const std::int64_t& code, const std::string& message) noexcept : m_code(code), m_message(message) {}
const char* Term::Exception::what() const noexcept
{
build_what();
return m_what.c_str();
}
std::int64_t Term::Exception::code() const noexcept { return m_code; }
std::string Term::Exception::message() const noexcept { return m_message; }
std::string Term::Exception::context() const noexcept { return m_context; }
Term::Exception::Exception(const std::int64_t& code) noexcept : m_code(code) {}
void Term::Exception::build_what() const noexcept
{
if(0 == m_code) { m_what = m_message; }
else { m_what = "error " + std::to_string(m_code) + ": " + m_message; }
}
void Term::Exception::setMessage(const std::string& message) noexcept { m_message = message; }
void Term::Exception::setContext(const std::string& context) noexcept { m_context = context; }
void Term::Exception::setWhat(const std::string& what) const noexcept { m_what = what; }
#if defined(_WIN32)
std::int64_t Term::Private::WindowsError::error() const noexcept { return m_error; }
bool Term::Private::WindowsError::check_value() const noexcept { return m_check_value; }
Term::Private::WindowsError& Term::Private::WindowsError::check_if(const bool& ret) noexcept
{
m_error = static_cast<std::int64_t>(GetLastError());
m_check_value = ret;
return *this;
}
void Term::Private::WindowsError::throw_exception(const std::string& str) const
{
if(m_check_value) { throw Term::Private::WindowsException(m_error, str); }
}
Term::Private::WindowsException::WindowsException(const std::int64_t& error, const std::string& context) : Term::Exception(static_cast<std::int64_t>(error))
{
setContext(context);
wchar_t* ptr{nullptr};
const DWORD cchMsg{FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, nullptr, static_cast<uint32_t>(code()), MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), reinterpret_cast<wchar_t*>(&ptr), 0, nullptr)};
if(cchMsg > 0)
{
auto deleter = [](void* p)
{
if(p != nullptr) { ::LocalFree(p); }
};
std::unique_ptr<wchar_t, decltype(deleter)> ptrBuffer(ptr, deleter);
std::string ret{Term::Private::to_narrow(ptrBuffer.get())};
if(ret.size() >= 2 && ret[ret.size() - 1] == '\n' && ret[ret.size() - 2] == '\r') ret.erase(ret.size() - 2);
setMessage(ret);
}
else { throw Term::Exception(::GetLastError(), "Error in FormatMessageW"); }
}
void Term::Private::WindowsException::build_what() const noexcept
{
std::string what{std::string("windows error ") + std::to_string(code()) + std::string(": ") + message().c_str()};
if(!context().empty()) what += " [" + context() + "]";
setWhat(what);
}
#endif
Term::Private::Errno::Errno() noexcept
{
#if defined(_WIN32)
_set_errno(0);
#else
errno = {0}; //NOSONAR
#endif
}
Term::Private::Errno::~Errno() noexcept
{
#if defined(_WIN32)
_set_errno(0);
#else
errno = {0}; //NOSONAR
#endif
}
std::int64_t Term::Private::Errno::error() const noexcept { return m_errno; }
bool Term::Private::Errno::check_value() const noexcept { return m_check_value; }
Term::Private::Errno& Term::Private::Errno::check_if(const bool& ret) noexcept
{
#if defined(_WIN32)
int err{static_cast<int>(m_errno)};
_get_errno(&err);
#else
m_errno = static_cast<std::uint32_t>(errno); //NOSONAR
#endif
m_check_value = {ret};
return *this;
}
void Term::Private::Errno::throw_exception(const std::string& str) const
{
if(m_check_value) { throw Term::Private::ErrnoException(m_errno, str); }
}
Term::Private::ErrnoException::ErrnoException(const std::int64_t& error, const std::string& context) : Term::Exception(error)
{
setContext(context);
#if defined(_WIN32)
std::wstring message(m_maxSize, L'\0');
message = _wcserror_s(&message[0], message.size(), static_cast<int>(error));
setMessage(Term::Private::to_narrow(message.c_str()));
#else
std::string message(m_maxSize, '\0');
message = ::strerror_r(static_cast<std::int32_t>(error), &message[0], message.size()); // NOLINT(readability-container-data-pointer)
setMessage(message);
#endif
}
void Term::Private::ErrnoException::build_what() const noexcept
{
std::string what{"errno " + std::to_string(code()) + ": " + message()};
if(!context().empty()) { what += +" [" + context() + "]"; }
setWhat(what);
}
void Term::Private::ExceptionHandler(const ExceptionDestination& destination) noexcept
{
try
{
std::exception_ptr exception{std::current_exception()};
if(exception != nullptr) { std::rethrow_exception(exception); }
}
catch(const Term::Exception& exception)
{
switch(destination)
{
case ExceptionDestination::MessageBox:
#if defined(_WIN32)
MessageBoxW(nullptr, Term::Private::to_wide(exception.what()).c_str(), Term::Private::to_wide("cpp-terminal v" + Term::Version::string()).c_str(), MB_OK | MB_ICONERROR);
break;
#endif
case ExceptionDestination::StdErr:
#if defined(_WIN32)
(void)(fputws(Term::Private::to_wide(std::string("cpp-terminal v" + Term::Version::string() + "\n" + exception.what() + "\n")).c_str(), stderr));
#else
(void)(fputs(std::string("cpp-terminal v" + Term::Version::string() + "\n" + exception.what() + "\n").c_str(), stderr));
#endif
break;
default: break;
}
}
catch(const std::exception& exception)
{
switch(destination)
{
case ExceptionDestination::MessageBox:
#if defined(_WIN32)
MessageBoxW(nullptr, Term::Private::to_wide(exception.what()).c_str(), Term::Private::to_wide("cpp-terminal v" + Term::Version::string()).c_str(), MB_OK | MB_ICONERROR);
break;
#endif
case ExceptionDestination::StdErr:
#if defined(_WIN32)
(void)(fputws(Term::Private::to_wide(std::string("cpp-terminal v" + Term::Version::string() + "\n" + exception.what() + "\n")).c_str(), stderr));
#else
(void)(fputs(std::string("cpp-terminal v" + Term::Version::string() + "\n" + exception.what() + "\n").c_str(), stderr));
#endif
break;
default: break;
}
}
catch(...)
{
switch(destination)
{
case ExceptionDestination::MessageBox:
#if defined(_WIN32)
MessageBoxW(nullptr, Term::Private::to_wide("cpp-terminal v" + Term::Version::string() + "Unknown error").c_str(), Term::Private::to_wide("cpp-terminal v" + Term::Version::string()).c_str(), MB_OK | MB_ICONERROR);
break;
#endif
case ExceptionDestination::StdErr:
#if defined(_WIN32)
(void)(fputws(Term::Private::to_wide("cpp-terminal v" + Term::Version::string() + ": Unknown error\n").c_str(), stderr));
#else
(void)(fputs(("cpp-terminal v" + Term::Version::string() + ": Unknown error\n").c_str(), stderr));
#endif
default: break;
}
}
(void)(std::fflush(stderr));
std::_Exit(Term::returnCode());
}
@@ -0,0 +1,97 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/exception.hpp"
#include <cstdint>
#include <string>
namespace Term
{
namespace Private
{
#if defined(_WIN32)
class WindowsError
{
public:
WindowsError(const WindowsError&) = default;
WindowsError(WindowsError&&) = default;
WindowsError() noexcept = default;
virtual ~WindowsError() noexcept = default;
WindowsError& operator=(WindowsError&&) noexcept = default;
WindowsError& operator=(const WindowsError&) noexcept = default;
std::int64_t error() const noexcept;
bool check_value() const noexcept;
WindowsError& check_if(const bool& ret) noexcept;
void throw_exception(const std::string& str = std::string()) const;
private:
std::int64_t m_error{0};
bool m_check_value{false};
};
class WindowsException : public Term::Exception
{
public:
WindowsException(const std::int64_t& error, const std::string& context = std::string());
~WindowsException() override = default;
private:
void build_what() const noexcept final;
};
#endif
class Errno
{
public:
Errno(const Errno&) noexcept = default;
Errno(Errno&&) noexcept = default;
Errno() noexcept;
virtual ~Errno() noexcept;
Errno& operator=(Errno&&) noexcept = default;
Errno& operator=(const Errno&) noexcept = default;
std::int64_t error() const noexcept;
bool check_value() const noexcept;
Errno& check_if(const bool& ret) noexcept;
void throw_exception(const std::string& str = {}) const;
private:
std::int64_t m_errno{0};
bool m_check_value{false};
};
class ErrnoException : public Term::Exception
{
public:
ErrnoException(const ErrnoException&) = default;
ErrnoException(ErrnoException&&) = default;
explicit ErrnoException(const std::int64_t& error, const std::string& context = {});
~ErrnoException() override = default;
ErrnoException& operator=(ErrnoException&&) = default;
ErrnoException& operator=(const ErrnoException&) = default;
private:
void build_what() const noexcept final;
};
enum class ExceptionDestination : std::uint8_t
{
MessageBox = 0,
StdErr,
};
void ExceptionHandler(const ExceptionDestination& destination = ExceptionDestination::StdErr) noexcept;
} // namespace Private
} // namespace Term
@@ -0,0 +1,188 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/private/file.hpp"
#include "cpp-terminal/private/exception.hpp"
#include "cpp-terminal/tty.hpp"
#include <cstdio>
#include <new>
#if defined(_WIN32)
#include <io.h>
#pragma warning(push)
#pragma warning(disable : 4668)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma warning(pop)
#else
#include <sys/ioctl.h>
#include <unistd.h>
#endif
#include "cpp-terminal/private/unicode.hpp"
#include <array>
#include <fcntl.h>
//FIXME Move this to other file
namespace
{
std::array<char, sizeof(Term::Private::InputFileHandler)> stdin_buffer; //NOLINT(fuchsia-statically-constructed-objects)
std::array<char, sizeof(Term::Private::OutputFileHandler)> stdout_buffer; //NOLINT(fuchsia-statically-constructed-objects)
} // namespace
Term::Private::InputFileHandler& Term::Private::in = reinterpret_cast<Term::Private::InputFileHandler&>(stdin_buffer);
Term::Private::OutputFileHandler& Term::Private::out = reinterpret_cast<Term::Private::OutputFileHandler&>(stdout_buffer);
//
Term::Private::FileHandler::FileHandler(std::recursive_mutex& mutex, const std::string& file, const std::string& mode) noexcept
try : m_mutex(mutex)
{
#if defined(_WIN32)
m_handle = {CreateFile(file.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
if(m_handle == INVALID_HANDLE_VALUE)
{
Term::Private::WindowsError().check_if((m_handle = CreateFile("NUL", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)) == INVALID_HANDLE_VALUE).throw_exception("Problem opening NUL");
m_null = true;
}
Term::Private::Errno().check_if((m_fd = _open_osfhandle(reinterpret_cast<intptr_t>(m_handle), _O_RDWR)) == -1).throw_exception("_open_osfhandle(reinterpret_cast<intptr_t>(m_handle), _O_RDWR)");
Term::Private::Errno().check_if(nullptr == (m_file = _fdopen(m_fd, mode.c_str()))).throw_exception("_fdopen(m_fd, mode.c_str())");
#else
std::size_t flag{O_ASYNC | O_DSYNC | O_NOCTTY | O_SYNC | O_NDELAY};
flag &= ~static_cast<std::size_t>(O_NONBLOCK);
if(mode.find('r') != std::string::npos) { flag |= O_RDONLY; } //NOLINT(abseil-string-find-str-contains)
else if(mode.find('w') != std::string::npos) { flag |= O_WRONLY; } //NOLINT(abseil-string-find-str-contains)
else { flag |= O_RDWR; }
m_fd = {::open(file.c_str(), static_cast<int>(flag))}; //NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
if(m_fd == -1)
{
Term::Private::Errno().check_if((m_fd = ::open("/dev/null", static_cast<int>(flag))) == -1).throw_exception(R"(::open("/dev/null", flag))"); //NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
m_null = true;
}
Term::Private::Errno().check_if(nullptr == (m_file = ::fdopen(m_fd, mode.c_str()))).throw_exception("::fdopen(m_fd, mode.c_str())");
m_handle = m_file;
#endif
Term::Private::Errno().check_if(std::setvbuf(m_file, nullptr, _IONBF, 0) != 0).throw_exception("std::setvbuf(m_file, nullptr, _IONBF, 0)");
}
catch(...)
{
ExceptionHandler(ExceptionDestination::StdErr);
}
Term::Private::FileHandler::~FileHandler() noexcept
try
{
flush();
Term::Private::Errno().check_if(0 != std::fclose(m_file)).throw_exception("std::fclose(m_file)"); //NOLINT(cppcoreguidelines-owning-memory)
}
catch(...)
{
ExceptionHandler(ExceptionDestination::StdErr);
}
bool Term::Private::FileHandler::null() const noexcept { return m_null; }
std::FILE* Term::Private::FileHandler::file() const noexcept { return m_file; }
std::int32_t Term::Private::FileHandler::fd() const noexcept { return m_fd; }
Term::Private::FileHandler::Handle Term::Private::FileHandler::handle() const noexcept { return m_handle; }
std::size_t Term::Private::OutputFileHandler::write(const std::string& str) const
{
#if defined(_WIN32)
DWORD written{0};
if(!str.empty()) { Term::Private::WindowsError().check_if(0 == WriteConsole(handle(), &str[0], static_cast<DWORD>(str.size()), &written, nullptr)).throw_exception("WriteConsole(handle(), &str[0], static_cast<DWORD>(str.size()), &written, nullptr)"); }
return static_cast<std::size_t>(written);
#else
ssize_t written{0};
if(!str.empty()) { Term::Private::Errno().check_if((written = ::write(fd(), str.data(), str.size())) == -1).throw_exception("::write(fd(), str.data(), str.size())"); }
return static_cast<std::size_t>(written);
#endif
}
std::size_t Term::Private::OutputFileHandler::write(const char& character) const
{
#if defined(_WIN32)
DWORD written{0};
Term::Private::WindowsError().check_if(0 == WriteConsole(handle(), &character, 1, &written, nullptr)).throw_exception("WriteConsole(handle(), &character, 1, &written, nullptr)");
return static_cast<std::size_t>(written);
#else
ssize_t written{0};
Term::Private::Errno().check_if((written = ::write(fd(), &character, 1)) == -1).throw_exception("::write(fd(), &character, 1)");
return static_cast<std::size_t>(written);
#endif
}
std::string Term::Private::InputFileHandler::read() const
{
#if defined(_WIN32)
DWORD nread{0};
std::string ret(4096, '\0');
errno = 0;
ReadConsole(Private::in.handle(), &ret[0], static_cast<DWORD>(ret.size()), &nread, nullptr);
return ret.c_str();
#else
#if defined(MAX_INPUT)
static const constexpr std::size_t max_input{MAX_INPUT};
#else
static const constexpr std::size_t max_input{256};
#endif
#if defined(_POSIX_MAX_INPUT)
static const constexpr std::size_t posix_max_input{_POSIX_MAX_INPUT};
#else
static const constexpr std::size_t posix_max_input{256};
#endif
static std::size_t nread{std::max(max_input, posix_max_input)};
if(is_stdin_a_tty()) Term::Private::Errno().check_if(::ioctl(Private::in.fd(), FIONREAD, &nread) != 0).throw_exception("::ioctl(Private::in.fd(), FIONREAD, &nread)"); //NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
std::string ret(nread, '\0');
if(nread != 0)
{
Term::Private::Errno().check_if(::read(Private::in.fd(), &ret[0], ret.size()) == -1).throw_exception("::read(Private::in.fd(), &ret[0], ret.size())"); //NOLINT(readability-container-data-pointer)
}
return ret;
#endif
}
void Term::Private::FileHandler::flush() { Term::Private::Errno().check_if(0 != std::fflush(m_file)).throw_exception("std::fflush(m_file)"); }
void Term::Private::FileHandler::lockIO() { m_mutex.lock(); }
void Term::Private::FileHandler::unlockIO() { m_mutex.unlock(); }
Term::Private::OutputFileHandler::OutputFileHandler(std::recursive_mutex& io_mutex) noexcept
try : FileHandler(io_mutex, m_file, "w")
{
//noop
}
catch(...)
{
ExceptionHandler(ExceptionDestination::StdErr);
}
Term::Private::InputFileHandler::InputFileHandler(std::recursive_mutex& io_mutex) noexcept
try : FileHandler(io_mutex, m_file, "r")
{
//noop
}
catch(...)
{
ExceptionHandler(ExceptionDestination::StdErr);
}
std::string Term::Private::ask(const std::string& str)
{
Term::Private::out.write(str);
std::string ret{Term::Private::in.read()};
for(std::size_t i = 0; i != ret.size(); ++i) { Term::Private::out.write("\b \b"); }
return ret;
}
@@ -0,0 +1,101 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/private/file_initializer.hpp"
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <string>
namespace Term
{
namespace Private
{
class FileHandler
{
public:
#if defined(_WIN32)
using Handle = void*;
#else
using Handle = std::FILE*;
#endif
FileHandler(std::recursive_mutex& mutex, const std::string& file, const std::string& mode) noexcept;
FileHandler(const FileHandler&) = delete;
FileHandler(FileHandler&&) = delete;
FileHandler& operator=(const FileHandler&) = delete;
FileHandler& operator=(FileHandler&&) = delete;
virtual ~FileHandler() noexcept;
Handle handle() const noexcept;
bool null() const noexcept;
std::FILE* file() const noexcept;
std::int32_t fd() const noexcept;
void lockIO();
void unlockIO();
void flush();
private:
// should be static but MacOS don't want it (crash at runtime)
std::recursive_mutex& m_mutex; //NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members)
bool m_null{false};
Handle m_handle{nullptr};
FILE* m_file{nullptr};
std::int32_t m_fd{-1};
};
class OutputFileHandler : public FileHandler
{
public:
explicit OutputFileHandler(std::recursive_mutex& io_mutex) noexcept;
OutputFileHandler(const OutputFileHandler& other) = delete;
OutputFileHandler(OutputFileHandler&& other) = delete;
OutputFileHandler& operator=(OutputFileHandler&& rhs) = delete;
OutputFileHandler& operator=(const OutputFileHandler& rhs) = delete;
~OutputFileHandler() override = default;
std::size_t write(const std::string& str) const;
std::size_t write(const char& character) const;
#if defined(_WIN32)
static const constexpr char* m_file{"CONOUT$"};
#else
static const constexpr char* m_file{"/dev/tty"};
#endif
};
class InputFileHandler : public FileHandler
{
public:
explicit InputFileHandler(std::recursive_mutex& io_mutex) noexcept;
InputFileHandler(const InputFileHandler&) = delete;
InputFileHandler(InputFileHandler&&) = delete;
InputFileHandler& operator=(InputFileHandler&&) = delete;
InputFileHandler& operator=(const InputFileHandler&) = delete;
~InputFileHandler() override = default;
std::string read() const;
#if defined(_WIN32)
static const constexpr char* m_file{"CONIN$"};
#else
static const constexpr char* m_file{"/dev/tty"};
#endif
};
extern InputFileHandler& in;
extern OutputFileHandler& out;
static const FileInitializer file_initializer;
std::string ask(const std::string& str);
} // namespace Private
} // namespace Term
@@ -0,0 +1,133 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/private/file_initializer.hpp"
#include "cpp-terminal/private/exception.hpp"
#include "cpp-terminal/private/file.hpp"
#if defined(_WIN32)
#include "cpp-terminal/private/unicode.hpp"
#include <io.h>
#pragma warning(push)
#pragma warning(disable : 4668)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma warning(pop)
#if defined(MessageBox)
#undef MessageBox
#endif
#else
#include <sys/stat.h>
#endif
bool Term::Private::FileInitializer::m_consoleCreated = {false};
std::size_t Term::Private::FileInitializer::m_counter = {0};
void Term::Private::FileInitializer::attachConsole() noexcept
try
{
#if defined(_WIN32)
// If something happen here we still don't have a console so we can only use a MessageBox to warn the users something is very bad and that they should contact us.
Term::Private::WindowsError error{Term::Private::WindowsError().check_if(AttachConsole(ATTACH_PARENT_PROCESS) == 0)};
bool need_allocation{false};
switch(error.error())
{
case ERROR_SUCCESS: break; // Has been attached
case ERROR_ACCESS_DENIED: need_allocation = false; break; // Already attached that's good !
case ERROR_INVALID_PARAMETER: error.throw_exception("The specified process does not exist !"); break; // Should never happen !
case ERROR_INVALID_HANDLE: need_allocation = true; break; // Need to allocate th console !
}
if(need_allocation)
{
Term::Private::WindowsError().check_if(AllocConsole() == 0).throw_exception("AllocConsole()");
m_consoleCreated = true;
}
#endif
}
catch(...)
{
detachConsole();
ExceptionHandler(ExceptionDestination::MessageBox);
}
void Term::Private::FileInitializer::detachConsole() noexcept
try
{
#if defined(_WIN32)
if(m_consoleCreated) { Term::Private::WindowsError().check_if(0 == FreeConsole()).throw_exception("FreeConsole()"); }
#endif
}
catch(...)
{
ExceptionHandler(ExceptionDestination::MessageBox);
}
Term::Private::FileInitializer::FileInitializer() noexcept
try
{
// MacOS was not happy wish a static mutex in the class so we create it and pass to each class;
static std::recursive_mutex ioMutex;
if(0 == m_counter)
{
attachConsole();
openStandardStreams();
if(nullptr == new(&Term::Private::in) InputFileHandler(ioMutex)) { throw Term::Exception("new(&Term::Private::in) InputFileHandler(ioMutex)"); }
if(nullptr == new(&Term::Private::out) OutputFileHandler(ioMutex)) { throw Term::Exception("new(&Term::Private::out) OutputFileHandler(ioMutex)"); }
}
++m_counter;
}
catch(...)
{
ExceptionHandler(ExceptionDestination::StdErr);
}
Term::Private::FileInitializer::~FileInitializer() noexcept
try
{
--m_counter;
if(0 == m_counter)
{
(&Term::Private::in)->~InputFileHandler(); //NOSONAR(S3432)
(&Term::Private::out)->~OutputFileHandler(); //NOSONAR(S3432)
detachConsole();
}
}
catch(...)
{
ExceptionHandler(ExceptionDestination::StdErr);
}
void Term::Private::FileInitializer::openStandardStreams() noexcept
try
{
#if defined(_WIN32)
FILE* fDummy{nullptr};
if(_fileno(stderr) < 0 || _get_osfhandle(_fileno(stderr)) < 0) { Term::Private::Errno().check_if(_wfreopen_s(&fDummy, L"CONOUT$", L"w", stderr) != 0).throw_exception(R"(_wfreopen_s(&fDummy, L"CONOUT$", L"w", stderr))"); }
if(_fileno(stdout) < 0 || _get_osfhandle(_fileno(stdout)) < 0) { Term::Private::Errno().check_if(_wfreopen_s(&fDummy, L"CONOUT$", L"w", stdout) != 0).throw_exception(R"(_wfreopen_s(&fDummy, L"CONOUT$", L"w", stdout))"); }
if(_fileno(stdin) < 0 || _get_osfhandle(_fileno(stdin)) < 0) { Term::Private::Errno().check_if(_wfreopen_s(&fDummy, L"CONIN$", L"r", stdin) != 0).throw_exception(R"(_wfreopen_s(&fDummy, L"CONIN$", L"r", stdin))"); }
const std::size_t bestSize{BUFSIZ > 4096 ? BUFSIZ : 4096};
#else
if(::fileno(stderr) < 0) { Term::Private::Errno().check_if(nullptr == std::freopen("/dev/tty", "w", stderr)).throw_exception(R"(std::freopen("/dev/tty", "w", stderr))"); } //NOLINT(cppcoreguidelines-owning-memory)
if(::fileno(stdout) < 0) { Term::Private::Errno().check_if(nullptr == std::freopen("/dev/tty", "w", stdout)).throw_exception(R"(std::freopen("/dev/tty", "w", stdout))"); } //NOLINT(cppcoreguidelines-owning-memory)
if(::fileno(stdin) < 0) { Term::Private::Errno().check_if(nullptr == std::freopen("/dev/tty", "r", stdin)).throw_exception(R"(std::freopen("/dev/tty", "r", stdin))"); } //NOLINT(cppcoreguidelines-owning-memory)
struct stat stats = {};
::stat("/dev/tty", &stats);
const std::size_t bestSize{static_cast<std::size_t>(stats.st_blksize) > 0 ? static_cast<std::size_t>(stats.st_blksize) : BUFSIZ}; //NOSONAR(S1774)
#endif
Term::Private::Errno().check_if(std::setvbuf(stderr, nullptr, _IONBF, 0) != 0).throw_exception("std::setvbuf(stderr, nullptr, _IONBF, 0)");
Term::Private::Errno().check_if(std::setvbuf(stdout, nullptr, _IOLBF, bestSize) != 0).throw_exception("std::setvbuf(stdout, nullptr, _IOLBF, bestSize)");
Term::Private::Errno().check_if(std::setvbuf(stdin, nullptr, _IOLBF, bestSize) != 0).throw_exception("std::setvbuf(stdin, nullptr, _IOLBF, bestSize)");
}
catch(...)
{
ExceptionHandler(ExceptionDestination::StdErr);
}
@@ -0,0 +1,58 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstddef>
namespace Term
{
namespace Private
{
class FileInitializer
{
public:
~FileInitializer() noexcept;
FileInitializer() noexcept;
FileInitializer(FileInitializer&&) = delete;
FileInitializer(const FileInitializer&) = delete;
FileInitializer& operator=(const FileInitializer&) = delete;
FileInitializer& operator=(FileInitializer&&) = delete;
private:
static bool m_consoleCreated;
static std::size_t m_counter;
///
///@brief Attach the console.
///
///Check if a console is attached to the process. If not, try to attach to the console. If there is no console, then create one.
///
static void attachConsole() noexcept;
///
///@brief Detach the console.
///
///If a console as been created, then delete it.
///
static void detachConsole() noexcept;
///
///@brief Open the standard streams.
///
///Open \b stdout \b stderr \b stdin and adjust their buffer size and line discipline.
///
static void openStandardStreams() noexcept;
};
} // namespace Private
} // namespace Term
@@ -0,0 +1,342 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#if defined(_WIN32)
#include "cpp-terminal/private/unicode.hpp"
#include <vector>
#pragma warning(push)
#pragma warning(disable : 4668)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma warning(pop)
#elif defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
#include <cerrno>
#include <csignal>
#include <sys/ioctl.h>
#include <thread>
#include <unistd.h>
#else
#include <memory>
#include <sys/epoll.h>
#endif
#include "cpp-terminal/event.hpp"
#include "cpp-terminal/exception.hpp"
#include "cpp-terminal/input.hpp"
#include "cpp-terminal/private/blocking_queue.hpp"
#include "cpp-terminal/private/file.hpp"
#include "cpp-terminal/private/input.hpp"
#include "cpp-terminal/private/sigwinch.hpp"
#include <mutex>
#include <string>
#if defined(_WIN32)
Term::Button::Action getAction(const std::int32_t& old_state, const std::int32_t& state, const std::int32_t& type)
{
if(((state - old_state) >> (type - 1)) == 1) return Term::Button::Action::Pressed;
else if(((old_state - state) >> (type - 1)) == 1)
return Term::Button::Action::Released;
else
return Term::Button::Action::None;
}
Term::Button setButton(const std::int32_t& old_state, const std::int32_t& state)
{
Term::Button::Action action;
action = getAction(old_state, state, FROM_LEFT_1ST_BUTTON_PRESSED);
if(action != Term::Button::Action::None) return Term::Button(Term::Button::Type::Left, action);
action = getAction(old_state, state, FROM_LEFT_2ND_BUTTON_PRESSED);
if(action != Term::Button::Action::None) return Term::Button(Term::Button::Type::Button1, action);
action = getAction(old_state, state, FROM_LEFT_3RD_BUTTON_PRESSED);
if(action != Term::Button::Action::None) return Term::Button(Term::Button::Type::Button2, action);
action = getAction(old_state, state, FROM_LEFT_4TH_BUTTON_PRESSED);
if(action != Term::Button::Action::None) return Term::Button(Term::Button::Type::Button3, action);
action = getAction(old_state, state, RIGHTMOST_BUTTON_PRESSED);
if(action != Term::Button::Action::None) return Term::Button(Term::Button::Type::Right, action);
return Term::Button(Term::Button::Type::None, Term::Button::Action::None);
}
void sendString(Term::Private::BlockingQueue& events, std::wstring& str)
{
if(!str.empty())
{
events.push(Term::Event(Term::Private::to_narrow(str.c_str())));
str.clear();
}
}
#endif
Term::Private::Input::~Input()
{
if(m_thread.joinable()) m_thread.join();
}
std::thread Term::Private::Input::m_thread{};
Term::Private::BlockingQueue Term::Private::Input::m_events;
int Term::Private::Input::m_poll{-1};
void Term::Private::Input::init_thread()
{
#if defined(__linux__)
m_poll = {::epoll_create1(EPOLL_CLOEXEC)};
::epoll_event signal;
signal.events = {EPOLLIN};
signal.data.fd = {Term::Private::Sigwinch::get()};
::epoll_ctl(m_poll, EPOLL_CTL_ADD, Term::Private::Sigwinch::get(), &signal);
::epoll_event input;
input.events = {EPOLLIN};
input.data.fd = {Term::Private::in.fd()};
::epoll_ctl(m_poll, EPOLL_CTL_ADD, Term::Private::in.fd(), &input);
#endif
if(m_thread.joinable()) m_thread.join();
std::thread thread(Term::Private::Input::read_event);
m_thread.swap(thread);
}
void Term::Private::Input::read_event()
{
while(true)
{
#if defined(_WIN32)
WaitForSingleObject(Term::Private::in.handle(), INFINITE);
read_raw();
#elif defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
if(Term::Private::Sigwinch::isSigwinch()) m_events.push(screen_size());
read_raw();
#else
::epoll_event ret;
if(epoll_wait(m_poll, &ret, 1, -1) == 1)
{
if(Term::Private::Sigwinch::isSigwinch(ret.data.fd)) m_events.push(Term::Screen(screen_size()));
else
read_raw();
}
#endif
}
}
#if defined(_WIN32)
void Term::Private::Input::read_windows_key(const std::uint16_t& virtual_key_code, const std::uint32_t& control_key_state, const std::size_t& occurrence)
{
// First check if we have ALT etc (CTRL is already done so skip it)
Term::MetaKey toAdd{Term::MetaKey::Value::None};
if(((control_key_state & LEFT_ALT_PRESSED) == LEFT_ALT_PRESSED) || ((control_key_state & RIGHT_ALT_PRESSED) == RIGHT_ALT_PRESSED)) toAdd += Term::MetaKey::Value::Alt;
if(((control_key_state & LEFT_CTRL_PRESSED) == LEFT_CTRL_PRESSED) || ((control_key_state & RIGHT_CTRL_PRESSED) == RIGHT_CTRL_PRESSED)) toAdd += Term::MetaKey::Value::Ctrl;
switch(virtual_key_code)
{
case VK_CANCEL: //??
case VK_CLEAR: //??
case VK_SHIFT:
case VK_CONTROL:
case VK_MENU:
case VK_PAUSE: //??
case VK_CAPITAL:
case VK_KANA: //??
//case VK_HANGUL: // Same
case VK_JUNJA: // ?
case VK_FINAL: // ?
case VK_HANJA:
//case VK_KANJI: // Same
case VK_CONVERT: // ?
case VK_NONCONVERT: // ?
case VK_ACCEPT: // ?
case VK_MODECHANGE: // ?
break;
case VK_PRIOR: m_events.push(std::move(toAdd + Term::Key(Key::Value::PageUp)), occurrence); break;
case VK_NEXT: m_events.push(std::move(toAdd + Term::Key(Key::Value::PageDown)), occurrence); break;
case VK_END: m_events.push(std::move(toAdd + Term::Key(Key::Value::End)), occurrence); break;
case VK_HOME: m_events.push(std::move(toAdd + Term::Key(Key::Value::Home)), occurrence); break;
case VK_LEFT: m_events.push(std::move(toAdd + Term::Key(Key::Value::ArrowLeft)), occurrence); break;
case VK_UP: m_events.push(std::move(toAdd + Term::Key(Key::Value::ArrowUp)), occurrence); break;
case VK_RIGHT: m_events.push(std::move(toAdd + Term::Key(Key::Value::ArrowRight)), occurrence); break;
case VK_DOWN: m_events.push(std::move(toAdd + Term::Key(Key::Value::ArrowDown)), occurrence); break;
case VK_SELECT: //?
case VK_PRINT: //?
case VK_EXECUTE: //?
break;
case VK_SNAPSHOT: m_events.push(std::move(toAdd + Term::Key(Key::Value::PrintScreen)), occurrence); break;
case VK_INSERT: m_events.push(std::move(toAdd + Term::Key(Key::Value::Insert)), occurrence); break;
case VK_DELETE: m_events.push(std::move(toAdd + Term::Key(Key::Value::Del)), occurrence); break;
case VK_HELP: //?
case VK_LWIN: //Maybe allow to detect Windows key Left and right
case VK_RWIN: //Maybe allow to detect Windows key Left and right
case VK_APPS: //?
case VK_SLEEP: //?
break;
case VK_F1: m_events.push(std::move(toAdd + Term::Key(Key::Value::F1)), occurrence); break;
case VK_F2: m_events.push(std::move(toAdd + Term::Key(Key::Value::F2)), occurrence); break;
case VK_F3: m_events.push(std::move(toAdd + Term::Key(Key::Value::F3)), occurrence); break;
case VK_F4: m_events.push(std::move(toAdd + Term::Key(Key::Value::F4)), occurrence); break;
case VK_F5: m_events.push(std::move(toAdd + Term::Key(Key::Value::F5)), occurrence); break;
case VK_F6: m_events.push(std::move(toAdd + Term::Key(Key::Value::F6)), occurrence); break;
case VK_F7: m_events.push(std::move(toAdd + Term::Key(Key::Value::F7)), occurrence); break;
case VK_F8: m_events.push(std::move(toAdd + Term::Key(Key::Value::F8)), occurrence); break;
case VK_F9: m_events.push(std::move(toAdd + Term::Key(Key::Value::F9)), occurrence); break;
case VK_F10: m_events.push(std::move(toAdd + Term::Key(Key::Value::F10)), occurrence); break;
case VK_F11: m_events.push(std::move(toAdd + Term::Key(Key::Value::F11)), occurrence); break;
case VK_F12: m_events.push(std::move(toAdd + Term::Key(Key::Value::F12)), occurrence); break;
case VK_F13: m_events.push(std::move(toAdd + Term::Key(Key::Value::F13)), occurrence); break;
case VK_F14: m_events.push(std::move(toAdd + Term::Key(Key::Value::F14)), occurrence); break;
case VK_F15: m_events.push(std::move(toAdd + Term::Key(Key::Value::F15)), occurrence); break;
case VK_F16: m_events.push(std::move(toAdd + Term::Key(Key::Value::F16)), occurrence); break;
case VK_F17: m_events.push(std::move(toAdd + Term::Key(Key::Value::F17)), occurrence); break;
case VK_F18: m_events.push(std::move(toAdd + Term::Key(Key::Value::F18)), occurrence); break;
case VK_F19: m_events.push(std::move(toAdd + Term::Key(Key::Value::F19)), occurrence); break;
case VK_F20: m_events.push(std::move(toAdd + Term::Key(Key::Value::F20)), occurrence); break;
case VK_F21: m_events.push(std::move(toAdd + Term::Key(Key::Value::F21)), occurrence); break;
case VK_F22: m_events.push(std::move(toAdd + Term::Key(Key::Value::F22)), occurrence); break;
case VK_F23: m_events.push(std::move(toAdd + Term::Key(Key::Value::F23)), occurrence); break;
case VK_F24: m_events.push(std::move(toAdd + Term::Key(Key::Value::F24)), occurrence); break;
case VK_NUMLOCK:
case VK_SCROLL:
default: break;
}
}
#endif
void Term::Private::Input::read_raw()
{
#ifdef _WIN32
DWORD to_read{0};
GetNumberOfConsoleInputEvents(Private::in.handle(), &to_read);
if(to_read == 0) return;
DWORD read{0};
std::vector<INPUT_RECORD> events{to_read};
if(!ReadConsoleInputW(Private::in.handle(), &events[0], to_read, &read) || read != to_read) Term::Exception("ReadFile() failed");
std::wstring ret;
bool need_windows_size{false};
for(std::size_t i = 0; i != read; ++i)
{
switch(events[i].EventType)
{
case KEY_EVENT:
{
if(events[i].Event.KeyEvent.bKeyDown)
{
if(events[i].Event.KeyEvent.uChar.UnicodeChar == 0) read_windows_key(events[i].Event.KeyEvent.wVirtualKeyCode, events[i].Event.KeyEvent.dwControlKeyState, events[i].Event.KeyEvent.wRepeatCount);
else
ret.append(events[i].Event.KeyEvent.wRepeatCount, events[i].Event.KeyEvent.uChar.UnicodeChar == Term::Key::Del ? static_cast<wchar_t>(Key(Term::Key::Value::Backspace)) : static_cast<wchar_t>(events[i].Event.KeyEvent.uChar.UnicodeChar));
}
break;
}
case FOCUS_EVENT:
{
sendString(m_events, ret);
m_events.push(Event(Focus(static_cast<Term::Focus::Type>(events[i].Event.FocusEvent.bSetFocus))));
break;
}
case MENU_EVENT:
{
sendString(m_events, ret);
break;
}
case MOUSE_EVENT:
{
sendString(m_events, ret);
static MOUSE_EVENT_RECORD old_state;
if(events[i].Event.MouseEvent.dwEventFlags == MOUSE_WHEELED || events[i].Event.MouseEvent.dwEventFlags == MOUSE_HWHEELED)
;
else if(old_state.dwButtonState == events[i].Event.MouseEvent.dwButtonState && old_state.dwMousePosition.X == events[i].Event.MouseEvent.dwMousePosition.X && old_state.dwMousePosition.Y == events[i].Event.MouseEvent.dwMousePosition.Y && old_state.dwEventFlags == events[i].Event.MouseEvent.dwEventFlags)
break;
std::int32_t state{static_cast<std::int32_t>(events[i].Event.MouseEvent.dwButtonState)};
switch(events[i].Event.MouseEvent.dwEventFlags)
{
case 0:
{
m_events.push(Term::Mouse(setButton(static_cast<std::int32_t>(old_state.dwButtonState), state), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
;
break;
}
case MOUSE_MOVED:
{
m_events.push(Term::Mouse(setButton(static_cast<std::int32_t>(old_state.dwButtonState), state), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
;
break;
}
case DOUBLE_CLICK:
{
m_events.push(Term::Mouse(Term::Button(setButton(static_cast<std::int32_t>(old_state.dwButtonState), state).type(), Term::Button::Action::DoubleClicked), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
break;
}
case MOUSE_WHEELED:
{
if(state > 0) m_events.push(Term::Mouse(Button(Term::Button::Type::Wheel, Term::Button::Action::RolledUp), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
else
m_events.push(Term::Mouse(Button(Term::Button::Type::Wheel, Term::Button::Action::RolledDown), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
break;
}
case MOUSE_HWHEELED:
{
if(state > 0) m_events.push(Term::Mouse(Button(Term::Button::Type::Wheel, Term::Button::Action::ToRight), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
else
m_events.push(Term::Mouse(Button(Term::Button::Type::Wheel, Term::Button::Action::ToLeft), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
break;
}
default: break;
}
old_state = events[i].Event.MouseEvent;
break;
}
case WINDOW_BUFFER_SIZE_EVENT:
{
need_windows_size = true; // if we send directly it's too much generations
sendString(m_events, ret);
break;
}
default: break;
}
}
sendString(m_events, ret);
if(need_windows_size == true) { m_events.push(screen_size()); }
#else
Private::in.lockIO();
std::string ret = Term::Private::in.read();
Private::in.unlockIO();
if(!ret.empty()) m_events.push(Event(ret.c_str()));
#endif
}
Term::Private::Input::Input() {}
void Term::Private::Input::startReading()
{
static bool activated{false};
if(!activated)
{
init_thread();
m_thread.detach();
activated = true;
}
}
Term::Event Term::Private::Input::getEvent() { return m_events.pop(); }
Term::Event Term::Private::Input::getEventBlocking()
{
static std::mutex cv_m;
static std::unique_lock<std::mutex> lk(cv_m);
if(m_events.empty()) m_events.wait_for_events(lk);
return m_events.pop();
}
static Term::Private::Input m_input;
Term::Event Term::read_event()
{
m_input.startReading();
return m_input.getEventBlocking();
}
@@ -0,0 +1,48 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/event.hpp"
#include <cstdint>
#include <thread>
namespace Term
{
namespace Private
{
class BlockingQueue;
class Input final
{
public:
Input();
~Input();
static void startReading();
static Term::Event getEvent();
static Term::Event getEventBlocking();
private:
static void read_event();
static void read_raw();
#if defined(_WIN32)
static void read_windows_key(const std::uint16_t& virtual_key_code, const std::uint32_t& control_key_state, const std::size_t& occurrence);
#endif
static void init_thread();
static std::thread m_thread;
static Term::Private::BlockingQueue m_events;
static int m_poll; // for linux
};
} // namespace Private
} // namespace Term
@@ -0,0 +1,42 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#if __cplusplus >= 201703L
#if defined(__GNUC__) && (__GNUC__ > 7)
#define CPP_TERMINAL_NODISCARD [[nodiscard]]
#elif defined(__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ > 8))
#define CPP_TERMINAL_NODISCARD [[nodiscard]]
#else
#define CPP_TERMINAL_NODISCARD
#endif
#if defined(__GNUC__) && (__GNUC__ > 5)
#define CPP_TERMINAL_FALLTHROUGH [[fallthrough]]
#elif defined(__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ > 5))
#define CPP_TERMINAL_FALLTHROUGH [[fallthrough]]
#else
#define CPP_TERMINAL_FALLTHROUGH
#endif
#if defined(__GNUC__) && (__GNUC__ > 5)
#define CPP_TERMINAL_MAYBE_UNUSED [[maybe_unused]]
#elif defined(__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ > 5))
#define CPP_TERMINAL_MAYBE_UNUSED [[maybe_unused]]
#else
#define CPP_TERMINAL_MAYBE_UNUSED
#endif
#else
#define CPP_TERMINAL_NODISCARD
#define CPP_TERMINAL_FALLTHROUGH
#define CPP_TERMINAL_MAYBE_UNUSED
#endif
@@ -0,0 +1,31 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/private/return_code.hpp"
#include "cpp-terminal/private/env.hpp"
#include <cstdlib>
#include <string>
#include <utility>
std::uint16_t Term::returnCode() noexcept
{
static std::uint16_t code{EXIT_FAILURE};
const std::pair<bool, std::string> returnCode{Private::getenv("CPP_TERMINAL_BADSTATE")};
try
{
if(returnCode.first && (std::stoi(returnCode.second) != EXIT_SUCCESS)) { code = static_cast<std::uint16_t>(std::stoi(returnCode.second)); }
}
catch(...)
{
code = EXIT_FAILURE;
}
return code;
}
@@ -0,0 +1,19 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstdint>
namespace Term
{
std::uint16_t returnCode() noexcept;
} // namespace Term
@@ -0,0 +1,35 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/screen.hpp"
#ifdef _WIN32
#pragma warning(push)
#pragma warning(disable : 4668)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma warning(pop)
#else
#include <sys/ioctl.h>
#endif
#include "cpp-terminal/private/file.hpp"
Term::Screen Term::screen_size()
{
#ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO inf;
if(GetConsoleScreenBufferInfo(Private::out.handle(), &inf)) return Term::Screen({Term::Rows(inf.srWindow.Bottom - inf.srWindow.Top + 1), Term::Columns(inf.srWindow.Right - inf.srWindow.Left + 1)});
return {};
#else
struct winsize window{0, 0, 0, 0};
if(ioctl(Private::out.fd(), TIOCGWINSZ, &window) != -1) return Term::Screen({Term::Rows(window.ws_row), Term::Columns(window.ws_col)});
return {};
#endif
}
@@ -0,0 +1,159 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/private/signals.hpp"
#include "cpp-terminal/terminal.hpp"
#include <algorithm>
#include <csignal>
#ifndef NSIG
#define NSIG (_SIGMAX + 1) /* For QNX */
#endif
const std::size_t Term::Private::Signals::m_signals_number{NSIG - 1};
void Term::Private::Signals::setHandler(const sighandler_t& handler) noexcept
{
for(std::size_t signal = 0; signal != m_signals_number; ++signal) { sighandler_t hand = std::signal(signal, handler); }
}
Term::Private::Signals::Signals(std::vector<sighandler_t>& m_han) noexcept
{
const static std::vector<int> ignore{
#if defined(SIGCONT)
SIGCONT,
#endif
#if defined(SIGSTOP)
SIGSTOP,
#endif
#if defined(SIGTSTP)
SIGTSTP,
#endif
#if defined(SIGTTIN)
SIGTTIN,
#endif
#if defined(SIGTTOU)
SIGTTOU,
#endif
};
m_han.reserve(m_signals_number);
for(std::size_t signal = 0; signal != m_signals_number; ++signal)
{
//if(std::find(ignore.begin(),ignore.end(),signal)==ignore.end())
//{
sighandler_t old = std::signal(signal, SIG_DFL);
//sighandler_t dumb=std::signal(signal, old);
m_han.push_back(old);
//}
//else
//{
// std::signal(signal, SIG_IGN);
// m_han.push_back(std::signal(signal, SIG_IGN));
// }
}
}
void Term::Private::Signals::reset_and_raise(int sign, std::vector<sighandler_t>& m_han, Term::Terminal& term) noexcept
{
const static std::vector<int> termin{
#if defined(SIGHUP)
SIGHUP,
#endif
#if defined(SIGHUP)
SIGINT,
#endif
#if defined(SIGQUIT)
SIGQUIT,
#endif
#if defined(SIGQUIT)
SIGILL,
#endif
#if defined(SIGTRAP)
SIGTRAP,
#endif
#if defined(SIGTRAP)
SIGABRT,
#endif
#if defined(SIGIOT)
SIGIOT,
#endif
#if defined(SIGBUS)
SIGBUS,
#endif
#if defined(SIGBUS)
SIGFPE,
#endif
#if defined(SIGKILL)
SIGKILL,
#endif
#if defined(SIGUSR1)
SIGUSR1,
#endif
#if defined(SIGSEGV)
SIGSEGV,
#endif
#if defined(SIGUSR2)
SIGUSR2,
#endif
#if defined(SIGUSR2)
SIGPIPE,
#endif
#if defined(SIGALRM)
SIGALRM,
#endif
#if defined(SIGSTKFLT)
SIGSTKFLT,
#endif
#if defined(SIGCONT)
SIGCONT,
#endif
#if defined(SIGXCPU)
SIGXCPU,
#endif
#if defined(SIGXFSZ)
SIGXFSZ,
#endif
#if defined(SIGVTALRM)
SIGVTALRM,
#endif
#if defined(SIGPROF)
SIGPROF,
#endif
#if defined(SIGPROF)
SIGIO,
#endif
#if defined(SIGPOLL)
SIGPOLL,
#endif
#if defined(SIGPWR)
SIGPWR,
#endif
#if defined(SIGSYS)
SIGSYS,
#endif
#if defined(SIGUNUSED)
SIGUNUSED,
#endif
#if defined(SIGUNUSED)
SIGUNUSED,
#endif
#if defined(SIGTERM)
SIGTERM
#endif
};
if(std::find(termin.begin(), termin.end(), sign) != termin.end())
{
sighandler_t old = std::signal(sign, m_han[sign]);
old = std::signal(sign, m_han[sign]);
term.clean();
std::raise(sign);
}
}
@@ -0,0 +1,33 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstddef>
#include <vector>
using sighandler_t = void (*)(int);
namespace Term
{
class Terminal;
namespace Private
{
class Signals
{
public:
Signals(std::vector<sighandler_t>& m_han) noexcept;
~Signals() noexcept {}
void setHandler(const sighandler_t& handler) noexcept;
static void reset_and_raise(int sign, std::vector<sighandler_t>& m_han, Term::Terminal&) noexcept;
private:
const static std::size_t m_signals_number;
};
} // namespace Private
} // namespace Term
@@ -0,0 +1,108 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/private/sigwinch.hpp"
#include "cpp-terminal/private/exception.hpp"
#if !defined(_WIN32)
#include <csignal>
#include <unistd.h>
#endif
#if defined(__linux__)
#include <sys/signalfd.h>
#endif
#if defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
namespace Term
{
namespace Private
{
volatile std::sig_atomic_t m_signalStatus{0};
static void sigwinchHandler(int sig)
{
if(sig == SIGWINCH) { m_signalStatus = 1; }
else { m_signalStatus = 0; }
}
} // namespace Private
} // namespace Term
#endif
std::int32_t Term::Private::Sigwinch::get() noexcept
{
#if defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
return Term::Private::m_signalStatus;
#else
return m_fd;
#endif
}
std::int32_t Term::Private::Sigwinch::m_fd{-1};
void Term::Private::Sigwinch::registerSigwinch()
{
#if defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
struct sigaction sa;
Term::Private::Errno().check_if(sigemptyset(&sa.sa_mask) != 0).throw_exception("sigemptyset(&sa.sa_mask)");
sa.sa_flags = {0};
sa.sa_handler = {Term::Private::sigwinchHandler};
Term::Private::Errno().check_if(sigaction(SIGWINCH, &sa, nullptr) != 0).throw_exception("sigaction(SIGWINCH, &sa, nullptr)");
#elif defined(__linux__)
::sigset_t windows_event = {};
Term::Private::Errno().check_if(sigemptyset(&windows_event) != 0).throw_exception("sigemptyset(&windows_event)");
Term::Private::Errno().check_if(sigaddset(&windows_event, SIGWINCH) != 0).throw_exception("sigaddset(&windows_event, SIGWINCH)");
Term::Private::Errno().check_if((m_fd = ::signalfd(-1, &windows_event, SFD_NONBLOCK | SFD_CLOEXEC)) == -1).throw_exception("m_fd = ::signalfd(-1, &windows_event, SFD_NONBLOCK | SFD_CLOEXEC)");
#endif
}
void Term::Private::Sigwinch::blockSigwinch()
{
#if !defined(_WIN32)
::sigset_t windows_event = {};
Term::Private::Errno().check_if(sigemptyset(&windows_event) != 0).throw_exception("sigemptyset(&windows_event)");
Term::Private::Errno().check_if(sigaddset(&windows_event, SIGWINCH) != 0).throw_exception("sigaddset(&windows_event, SIGWINCH)");
Term::Private::Errno().check_if(::pthread_sigmask(SIG_BLOCK, &windows_event, nullptr) != 0).throw_exception("::pthread_sigmask(SIG_BLOCK, &windows_event, nullptr)");
#endif
}
void Term::Private::Sigwinch::unblockSigwinch()
{
#if !defined(_WIN32)
::sigset_t windows_event = {};
Term::Private::Errno().check_if(sigemptyset(&windows_event) != 0).throw_exception("sigemptyset(&windows_event)");
Term::Private::Errno().check_if(sigaddset(&windows_event, SIGWINCH) != 0).throw_exception("sigaddset(&windows_event, SIGWINCH)");
Term::Private::Errno().check_if(::pthread_sigmask(SIG_UNBLOCK, &windows_event, nullptr) != 0).throw_exception("::pthread_sigmask(SIG_UNBLOCK, &windows_event, nullptr)");
#endif
}
bool Term::Private::Sigwinch::isSigwinch(const std::int32_t& file_descriptor) noexcept
{
#if defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
if(Term::Private::m_signalStatus == 1)
{
static_cast<void>(file_descriptor); // suppress warning
Term::Private::m_signalStatus = {0};
return true;
}
return false;
#elif defined(__linux__)
if(m_fd == file_descriptor)
{
// read it to clean
::signalfd_siginfo fdsi = {};
::read(m_fd, &fdsi, sizeof(fdsi));
return true;
}
return false;
#else
static_cast<void>(file_descriptor); // suppress warning
return false;
#endif
}
@@ -0,0 +1,33 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstdint>
namespace Term
{
namespace Private
{
class Sigwinch
{
public:
static void registerSigwinch();
static void blockSigwinch();
static void unblockSigwinch();
static bool isSigwinch(const std::int32_t& file_descriptor = -1) noexcept;
static std::int32_t get() noexcept;
private:
static std::int32_t m_fd;
};
} // namespace Private
} // namespace Term
@@ -0,0 +1,232 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/cursor.hpp"
#include "cpp-terminal/private/env.hpp"
#include "cpp-terminal/private/exception.hpp"
#include "cpp-terminal/private/file.hpp"
//#include "cpp-terminal/private/signals.hpp"
#include "cpp-terminal/private/sigwinch.hpp"
#include "cpp-terminal/terminal.hpp"
#if defined(_WIN32)
#include <io.h>
#pragma warning(push)
#pragma warning(disable : 4668)
#include <windows.h>
#pragma warning(pop)
#if !defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING)
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif
#if !defined(DISABLE_NEWLINE_AUTO_RETURN)
#define DISABLE_NEWLINE_AUTO_RETURN 0x0008
#endif
#if !defined(ENABLE_VIRTUAL_TERMINAL_INPUT)
#define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
#endif
#else
#include <termios.h>
#endif
void Term::Terminal::set_unset_utf8()
{
static bool enabled{false};
#if defined(_WIN32)
static UINT out_code_page{0};
static UINT in_code_page{0};
if(!enabled)
{
if((out_code_page = GetConsoleOutputCP()) == 0) throw Term::Private::WindowsException(GetLastError(), "GetConsoleOutputCP()");
if(!SetConsoleOutputCP(CP_UTF8)) throw Term::Private::WindowsException(GetLastError(), "SetConsoleOutputCP(CP_UTF8)");
if((in_code_page = GetConsoleCP()) == 0) throw Term::Private::WindowsException(GetLastError(), "GetConsoleCP()");
if(!SetConsoleCP(CP_UTF8)) throw Term::Private::WindowsException(GetLastError(), "SetConsoleCP(CP_UTF8)");
enabled = true;
}
else
{
if(!SetConsoleOutputCP(out_code_page)) throw Term::Private::WindowsException(GetLastError(), "SetConsoleOutputCP(out_code_page)");
if(!SetConsoleCP(in_code_page)) throw Term::Private::WindowsException(GetLastError(), "SetConsoleCP(in_code_page)");
}
#else
if(!enabled)
{
const Term::Cursor cursor_before{Term::cursor_position()};
Term::Private::out.write("\u001b%G"); // Try to activate UTF-8 (NOT warranty)
const std::string read{Term::Private::in.read()};
const Term::Cursor cursor_after{Term::cursor_position()};
const std::size_t moved{cursor_after.column() - cursor_before.column()};
std::string rem;
rem.reserve(moved * 3);
for(std::size_t i = 0; i != moved; ++i) { rem += "\b \b"; }
Term::Private::out.write(rem);
enabled = 0 == moved;
}
else
{
// Does not return the original charset but, the default defined by standard ISO 8859-1 (ISO 2022);
Term::Private::out.write("\u001b%@");
}
#endif
}
void Term::Terminal::store_and_restore() noexcept
try
{
static bool enabled{false};
#if defined(_WIN32)
static DWORD originalOut{0};
static DWORD originalIn{0};
if(!enabled)
{
Term::Private::WindowsError().check_if(GetConsoleMode(Private::out.handle(), &originalOut) == 0).throw_exception("GetConsoleMode(Private::out.handle(), &originalOut)");
Term::Private::WindowsError().check_if(GetConsoleMode(Private::in.handle(), &originalIn) == 0).throw_exception("GetConsoleMode(Private::in.handle(), &originalIn)");
DWORD in{static_cast<DWORD>((originalIn & ~(ENABLE_QUICK_EDIT_MODE | setFocusEvents() | setMouseEvents())) | (ENABLE_EXTENDED_FLAGS))};
DWORD out{originalOut};
// Check if ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN can be activated, if not we are a legacy terminal.
DWORD test = out;
test |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if(!SetConsoleMode(Private::out.handle(), test)) { SetConsoleMode(Private::out.handle(), out); }
else
{
out |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
in |= ENABLE_VIRTUAL_TERMINAL_INPUT;
}
if(!SetConsoleMode(Private::out.handle(), out)) { throw Term::Private::WindowsException(GetLastError(), "SetConsoleMode(Private::out.handle()"); }
if(!SetConsoleMode(Private::in.handle(), in)) { throw Term::Private::WindowsException(GetLastError(), "SetConsoleMode(Private::in.handle(), in)"); }
enabled = true;
}
else
{
if(!SetConsoleMode(Private::out.handle(), originalOut)) { throw Term::Private::WindowsException(GetLastError(), "SetConsoleMode(Private::out.handle(), originalOut)"); }
if(!SetConsoleMode(Private::in.handle(), originalIn)) { throw Term::Private::WindowsException(GetLastError(), "SetConsoleMode(Private::in.handle(), originalIn)"); }
}
#else
static termios orig_termios;
if(!enabled)
{
Term::Private::Sigwinch::blockSigwinch();
Term::Private::Sigwinch::registerSigwinch();
if(!Private::out.null()) { Term::Private::Errno().check_if(tcgetattr(Private::out.fd(), &orig_termios) == -1).throw_exception("tcgetattr() failed"); }
enabled = true;
}
else
{
Term::Private::Sigwinch::unblockSigwinch();
unsetMouseEvents();
unsetFocusEvents();
if(!Private::out.null()) { Term::Private::Errno().check_if(tcsetattr(Private::out.fd(), TCSAFLUSH, &orig_termios) == -1).throw_exception("tcsetattr() failed in destructor"); }
}
#endif
}
catch(...)
{
ExceptionHandler(Private::ExceptionDestination::StdErr);
}
std::size_t Term::Terminal::setMouseEvents()
{
#if defined(_WIN32)
return static_cast<std::size_t>(ENABLE_MOUSE_INPUT);
#else
return Term::Private::out.write("\u001b[?1002h\u001b[?1003h\u001b[?1006h");
#endif
}
std::size_t Term::Terminal::unsetMouseEvents()
{
#if defined(_WIN32)
return static_cast<std::size_t>(ENABLE_MOUSE_INPUT);
#else
return Term::Private::out.write("\u001b[?1006l\u001b[?1003l\u001b[?1002l");
#endif
}
std::size_t Term::Terminal::setFocusEvents()
{
#if defined(_WIN32)
return static_cast<std::size_t>(ENABLE_WINDOW_INPUT);
#else
return Term::Private::out.write("\u001b[?1004h");
#endif
}
std::size_t Term::Terminal::unsetFocusEvents()
{
#if defined(_WIN32)
return static_cast<std::size_t>(ENABLE_WINDOW_INPUT);
#else
return Term::Private::out.write("\u001b[?1004l");
#endif
}
void Term::Terminal::setMode() const
{
static bool activated{false};
#if defined(_WIN32)
static DWORD flags{0};
if(!activated)
{
if(!Private::out.null())
if(!GetConsoleMode(Private::in.handle(), &flags)) { throw Term::Private::WindowsException(GetLastError()); }
activated = true;
return;
}
DWORD send = flags;
if(m_options.has(Option::Raw))
{
send &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
send |= (setFocusEvents() | setMouseEvents());
}
else if(m_options.has(Option::Cooked))
{
send |= (ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
send &= ~(setFocusEvents() | setMouseEvents());
}
if(m_options.has(Option::NoSignalKeys)) { send &= ~ENABLE_PROCESSED_INPUT; }
else if(m_options.has(Option::SignalKeys)) { send |= ENABLE_PROCESSED_INPUT; }
if(!Private::out.null())
if(!SetConsoleMode(Private::in.handle(), send)) { throw Term::Private::WindowsException(GetLastError()); }
#else
if(!Private::out.null())
{
static ::termios raw = {};
if(!activated)
{
Term::Private::Errno().check_if(tcgetattr(Private::out.fd(), &raw) == -1).throw_exception("tcgetattr(Private::out.fd(), &raw)");
raw.c_cflag &= ~static_cast<std::size_t>(CSIZE | PARENB);
raw.c_cflag |= CS8;
raw.c_cc[VMIN] = 1;
raw.c_cc[VTIME] = 0;
activated = true;
return;
}
::termios send = raw;
if(m_options.has(Option::Raw))
{
send.c_iflag &= ~static_cast<std::size_t>(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | INPCK);
// This disables output post-processing, requiring explicit \r\n. We
// keep it enabled, so that in C++, one can still just use std::endl
// for EOL instead of "\r\n".
//send.c_oflag &= ~static_cast<std::size_t>(OPOST);
send.c_lflag &= ~static_cast<std::size_t>(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
setMouseEvents();
setFocusEvents();
}
else if(m_options.has(Option::Cooked))
{
send = raw;
unsetMouseEvents();
unsetFocusEvents();
}
if(m_options.has(Option::NoSignalKeys)) { send.c_lflag &= ~static_cast<std::size_t>(ISIG); } //FIXME need others flags !
if(m_options.has(Option::SignalKeys)) { send.c_lflag |= ISIG; }
Term::Private::Errno().check_if(tcsetattr(Private::out.fd(), TCSAFLUSH, &send) == -1).throw_exception("tcsetattr(Private::out.fd(), TCSAFLUSH, &send)");
}
#endif
}
@@ -0,0 +1,186 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#ifdef _WIN32
#pragma warning(push)
#pragma warning(disable : 4668)
#include <windows.h>
#pragma warning(pop)
#endif
#include "cpp-terminal/cursor.hpp"
#include "cpp-terminal/private/env.hpp"
#include "cpp-terminal/private/file.hpp"
#include "cpp-terminal/terminfo.hpp"
#include <cstddef>
#include <string>
Term::Terminfo::ColorMode Term::Terminfo::m_colorMode{ColorMode::Unset};
Term::Terminfo::Booleans Term::Terminfo::m_booleans{};
Term::Terminfo::Integers Term::Terminfo::m_integers{};
Term::Terminfo::Strings Term::Terminfo::m_strings{};
bool Term::Terminfo::get(const Term::Terminfo::Bool& key)
{
check();
return m_booleans[static_cast<std::size_t>(key)];
}
std::uint32_t Term::Terminfo::get(const Term::Terminfo::Integer& key)
{
check();
return m_integers[static_cast<std::size_t>(key)];
}
std::string Term::Terminfo::get(const Term::Terminfo::String& key)
{
check();
return m_strings[static_cast<std::size_t>(key)];
}
void Term::Terminfo::set(const Term::Terminfo::Bool& key, const bool& value) { m_booleans[static_cast<std::size_t>(key)] = value; }
void Term::Terminfo::set(const Term::Terminfo::Integer& key, const std::uint32_t& value) { m_integers[static_cast<std::size_t>(key)] = value; }
void Term::Terminfo::set(const Term::Terminfo::String& key, const std::string& value) { m_strings[static_cast<std::size_t>(key)] = value; }
#if defined(_WIN32)
bool WindowsVersionGreater(const DWORD& major, const DWORD& minor, const DWORD& patch)
{
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4191)
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
#endif
NTSTATUS(WINAPI * getVersion)
(PRTL_OSVERSIONINFOW) = (reinterpret_cast<NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW)>(GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "RtlGetVersion")));
#if defined(_MSC_VER)
#pragma warning(pop)
#else
#pragma GCC diagnostic pop
#endif
if(getVersion != nullptr)
{
RTL_OSVERSIONINFOW rovi;
rovi.dwOSVersionInfoSize = sizeof(rovi);
if(getVersion(&rovi) == 0)
{
if(rovi.dwMajorVersion > major || (rovi.dwMajorVersion == major && (rovi.dwMinorVersion > minor || (rovi.dwMinorVersion == minor && rovi.dwBuildNumber >= patch)))) return true;
else
return false;
}
}
return false;
}
#endif
void Term::Terminfo::checkLegacy()
{
#if defined(_WIN32)
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif
if(!m_booleans[static_cast<std::size_t>(Terminfo::Bool::ControlSequences)]) { set(Terminfo::Bool::Legacy, true); }
else
{
DWORD dwOriginalOutMode{0};
GetConsoleMode(Private::out.handle(), &dwOriginalOutMode);
if(!SetConsoleMode(Private::out.handle(), dwOriginalOutMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { set(Terminfo::Bool::Legacy, true); }
else
{
SetConsoleMode(Private::out.handle(), dwOriginalOutMode);
set(Terminfo::Bool::Legacy, false);
}
}
#else
set(Terminfo::Bool::Legacy, false);
#endif
}
void Term::Terminfo::checkTermEnv() { set(Terminfo::String::TermEnv, Private::getenv("TERM").second); }
void Term::Terminfo::checkTerminalName()
{
std::string name;
name = Private::getenv("TERM_PROGRAM").second;
if(name.empty()) { name = Private::getenv("TERMINAL_EMULATOR").second; }
if(Private::getenv("ANSICON").first) { name = "ansicon"; }
set(Term::Terminfo::String::TermName, name);
}
void Term::Terminfo::checkTerminalVersion() { set(Terminfo::String::TermVersion, Private::getenv("TERM_PROGRAM_VERSION").second); }
void Term::Terminfo::check()
{
static bool checked{false};
if(!checked)
{
checkTermEnv();
checkTerminalName();
checkTerminalVersion();
checkControlSequences();
checkLegacy();
checkColorMode();
checkUTF8();
checked = true;
}
}
Term::Terminfo::ColorMode Term::Terminfo::getColorMode()
{
checkColorMode();
return m_colorMode;
}
Term::Terminfo::Terminfo() { check(); }
void Term::Terminfo::checkColorMode()
{
std::string name{m_strings[static_cast<std::size_t>(Terminfo::String::TermName)]};
if(name == "Apple_Terminal") { m_colorMode = Term::Terminfo::ColorMode::Bit8; }
else if(name == "JetBrains-JediTerm") { m_colorMode = Term::Terminfo::ColorMode::Bit24; }
else if(name == "vscode") { m_colorMode = Term::Terminfo::ColorMode::Bit24; }
else if(name == "linux") { m_colorMode = Term::Terminfo::ColorMode::Bit4; }
else if(name == "ansicon") { m_colorMode = Term::Terminfo::ColorMode::Bit4; }
else if(m_strings[static_cast<std::size_t>(Terminfo::String::TermEnv)] == "linux") { m_colorMode = Term::Terminfo::ColorMode::Bit4; }
#if defined(_WIN32)
else if(WindowsVersionGreater(10, 0, 10586) && !m_booleans[static_cast<std::size_t>(Terminfo::Bool::Legacy)]) { m_colorMode = Term::Terminfo::ColorMode::Bit24; }
else if(m_booleans[static_cast<std::size_t>(Terminfo::Bool::Legacy)]) { m_colorMode = Term::Terminfo::ColorMode::Bit4; }
#endif
else { m_colorMode = Term::Terminfo::ColorMode::Bit24; }
std::string colorterm = Private::getenv("COLORTERM").second;
if((colorterm == "truecolor" || colorterm == "24bit") && m_colorMode != ColorMode::Unset) { m_colorMode = Term::Terminfo::ColorMode::Bit24; }
}
void Term::Terminfo::checkControlSequences()
{
#ifdef _WIN32
if(WindowsVersionGreater(10, 0, 10586)) { set(Term::Terminfo::Bool::ControlSequences, true); }
else { set(Term::Terminfo::Bool::ControlSequences, false); }
#else
set(Term::Terminfo::Bool::ControlSequences, true);
#endif
}
void Term::Terminfo::checkUTF8()
{
#if defined(_WIN32)
(GetConsoleOutputCP() == CP_UTF8 && GetConsoleCP() == CP_UTF8) ? set(Terminfo::Bool::UTF8, true) : set(Terminfo::Bool::UTF8, false);
#else
Term::Cursor cursor_before{Term::cursor_position()};
Term::Private::out.write("\xe2\x82\xac"); // € 3bits in utf8 one character
std::string read{Term::Private::in.read()};
Term::Cursor cursor_after{Term::cursor_position()};
std::size_t moved{cursor_after.column() - cursor_before.column()};
if(moved == 1) { set(Terminfo::Bool::UTF8, true); }
else { set(Terminfo::Bool::UTF8, false); }
for(std::size_t i = 0; i != moved; ++i) { Term::Private::out.write("\b \b"); }
#endif
}
@@ -0,0 +1,36 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include <cstdio>
#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
#include "cpp-terminal/tty.hpp"
namespace
{
bool is_a_tty(const FILE* fd)
{
#ifdef _WIN32
return static_cast<bool>(_isatty(_fileno(const_cast<FILE*>(fd))));
#else
return ::isatty(::fileno(const_cast<FILE*>(fd)));
#endif
}
} // namespace
bool Term::is_stdin_a_tty() { return ::is_a_tty(stdin); }
bool Term::is_stdout_a_tty() { return ::is_a_tty(stdout); }
bool Term::is_stderr_a_tty() { return ::is_a_tty(stderr); }
@@ -0,0 +1,80 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/private/unicode.hpp"
#include "cpp-terminal/private/exception.hpp"
#if defined(_WIN32)
#include <limits>
#pragma warning(push)
#pragma warning(disable : 4668)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma warning(pop)
#endif
#include <array>
#if defined(_WIN32)
std::string Term::Private::to_narrow(const std::wstring& in)
{
if(in.empty()) return std::string();
static constexpr DWORD flag{WC_ERR_INVALID_CHARS};
std::size_t in_size{in.size()};
if(in_size > static_cast<size_t>((std::numeric_limits<int>::max)())) throw Term::Exception("String size is to big " + std::to_string(in_size) + "/" + std::to_string((std::numeric_limits<int>::max)()));
const int ret_size{::WideCharToMultiByte(CP_UTF8, flag, in.data(), static_cast<int>(in_size), nullptr, 0, nullptr, nullptr)};
if(ret_size == 0) throw Term::Private::WindowsException(::GetLastError());
std::string ret(static_cast<std::size_t>(ret_size), '\0');
int ret_error{::WideCharToMultiByte(CP_UTF8, flag, in.data(), static_cast<int>(in_size), &ret[0], ret_size, nullptr, nullptr)};
if(ret_error == 0) throw Term::Private::WindowsException(::GetLastError());
return ret;
}
std::wstring Term::Private::to_wide(const std::string& in)
{
if(in.empty()) return std::wstring();
static constexpr DWORD flag{MB_ERR_INVALID_CHARS};
std::size_t in_size{in.size()};
if(in_size > static_cast<size_t>((std::numeric_limits<int>::max)())) throw Term::Exception("String size is to big " + std::to_string(in_size) + "/" + std::to_string((std::numeric_limits<int>::max)()));
const int ret_size{::MultiByteToWideChar(CP_UTF8, flag, in.data(), static_cast<int>(in_size), nullptr, 0)};
if(ret_size == 0) throw Term::Private::WindowsException(::GetLastError());
std::wstring ret(static_cast<std::size_t>(ret_size), '\0');
int ret_error{::MultiByteToWideChar(CP_UTF8, flag, in.data(), static_cast<int>(in_size), &ret[0], ret_size)};
if(ret_error == 0) throw Term::Private::WindowsException(::GetLastError());
return ret;
}
#endif
std::string Term::Private::utf32_to_utf8(const char32_t& codepoint, const bool& exception)
{
static const constexpr std::array<std::uint32_t, 4> size{0x7F, 0x07FF, 0xFFFF, 0x10FFFF};
static const constexpr std::uint8_t mask{0x80};
static const constexpr std::uint8_t add{0x3F};
static const constexpr std::array<std::uint8_t, 3> mask_first{0x1F, 0x0F, 0x07};
static const constexpr std::array<std::uint8_t, 3> add_first{0xC0, 0xE0, 0xF0};
static const constexpr std::array<std::uint8_t, 4> shift{0, 6, 12, 18};
static const constexpr std::uint8_t max_size{4};
std::string ret;
ret.reserve(max_size);
if(codepoint <= size[0]) { ret = {static_cast<char>(codepoint)}; } // Plain ASCII
else if(codepoint <= size[1]) { ret = {static_cast<char>(((codepoint >> shift[1]) & mask_first[0]) | add_first[0]), static_cast<char>(((codepoint >> shift[0]) & add) | mask)}; }
else if(codepoint <= size[2]) { ret = {static_cast<char>(((codepoint >> shift[2]) & mask_first[1]) | add_first[1]), static_cast<char>(((codepoint >> shift[1]) & add) | mask), static_cast<char>(((codepoint >> shift[0]) & add) | mask)}; }
else if(codepoint <= size[3]) { ret = {static_cast<char>(((codepoint >> shift[3]) & mask_first[2]) | add_first[2]), static_cast<char>(((codepoint >> shift[2]) & add) | mask), static_cast<char>(((codepoint >> shift[1]) & add) | mask), static_cast<char>(((codepoint >> shift[0]) & add) | mask)}; }
else if(exception) { throw Term::Exception("Invalid UTF32 codepoint."); }
else { ret = "\xEF\xBF\xBD"; }
return ret;
}
std::string Term::Private::utf32_to_utf8(const std::u32string& str, const bool& exception)
{
std::string ret;
for(const char32_t codepoint: str) { ret.append(utf32_to_utf8(codepoint, exception)); }
return ret;
}
@@ -0,0 +1,47 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <string>
namespace Term
{
namespace Private
{
// utf16 is useless and wstring too so utf16 inside wstring is useless^2 but windows use it so define this functions to deal with it and less the user forget it.
#if defined(_WIN32)
std::string to_narrow(const std::wstring& wstr);
std::wstring to_wide(const std::string& str);
#endif
///
///@brief Encode a codepoint using UTF-8 \b std::string .
///
///@param codepoint The codepoint ( \b char32_t ) on range [0,0x10FFFF] to convert.
///@param exception If \b true throw exception on error, otherwise change the out of range \b codepoint to "replacement character" \b .
///@return \b std::string the UTF-8 value.
///@warning Internal use only.
///
std::string utf32_to_utf8(const char32_t& codepoint, const bool& exception = false);
///
///@brief Encode a \b std::u32string into UTF-8 \b std::string .
///
///@param str The \b std::u32string to convert.
///@param exception If \b true throw exception on error, otherwise change the out of range \b codepoint to "replacement character" \b .
///@return \b std::string encoded in UTF-8.
///@warning Internal use only.
///
std::string utf32_to_utf8(const std::u32string& str, const bool& exception = false);
} // namespace Private
} // namespace Term
@@ -0,0 +1,42 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2023 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#include "cpp-terminal/version.hpp"
// clang-format off
std::uint16_t Term::Version::major() noexcept
{
static std::uint16_t ret{@cpp-terminal_VERSION_MAJOR@};
return ret;
}
std::uint16_t Term::Version::minor() noexcept
{
static std::uint16_t ret{@cpp-terminal_VERSION_MINOR@};
return ret;
}
std::uint16_t Term::Version::patch() noexcept
{
static std::uint16_t ret{@cpp-terminal_VERSION_PATCH@};
return ret;
}
std::string Term::Version::string() noexcept
{
static const char* ret{"@cpp-terminal_VERSION_MAJOR@.@cpp-terminal_VERSION_MINOR@.@cpp-terminal_VERSION_PATCH@"};
return ret;
}
std::string Term::homepage() noexcept
{
static const char* ret{"@cpp-terminal_HOMEPAGE_URL@"};
return ret;
}
// clang-format on
@@ -0,0 +1,84 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/terminal.hpp"
#include "cpp-terminal/window.hpp"
#include <functional>
namespace Term
{
// indicates the results of prompt_blocking() and prompt_non_blocking
enum class Result
{
Yes, ///< Returned if the user chose \b yes.
No, ///< Returned if the user chose \b no.
Error, ///< Returned if no terminal is attached to the program.
None, ///< Returned if the enter key was pressed without additional input.
Abort, ///< Returned if CTRL+C was pressed.
Invalid ///< Returned if the given input did not match the case \b yes of \b no
};
/**
* @brief A simple yes/no prompt, requires the user to press the ENTER key to continue. The arguments are used like this: 1 [2/3]4 <user Input>.
* The immediate switch indicates toggles whether pressing enter for confirming the input is required or not.
*
* @param message
* @param first_option
* @param second_option
* @param prompt_indicator
* @return Result
*/
Result prompt(const std::string& message, const std::string& first_option, const std::string& second_option, const std::string& prompt_indicator, bool);
// indicates the results of prompt_simple()
enum class Result_simple
{
Yes, ///< Returned if the user chose \b yes.
No, ///< Returned if the user chose no or invalid / no input or if no terminal is attached.
Abort ///< Returned if CTRL+C was pressed.
};
/**
* @brief The most simple prompt possible, requires the user to press enter to continue. The arguments are used like this: 1 [y/N]:
* Invalid input, errors (like no attached terminal) all result in \b no as default.
*
* @param message
* @return Result_simple
*/
Result_simple prompt_simple(const std::string& message);
/* Multiline prompt */
// This model contains all the information about the state of the prompt in an
// abstract way, irrespective of where or how it is rendered.
class Model
{
public:
std::string prompt_string; // The string to show as the prompt
std::vector<std::string> lines{""}; // The current input string in the prompt as a vector of lines, without '\n' at the end.
// The current cursor position in the "input" string, starting from (1,1)
std::size_t cursor_col{1};
std::size_t cursor_row{1};
};
std::string concat(const std::vector<std::string>&);
std::vector<std::string> split(const std::string&);
void print_left_curly_bracket(Term::Window&, const std::size_t&, const std::size_t&, const std::size_t&);
void render(Term::Window&, const Model&, const std::size_t&);
std::string prompt_multiline(const std::string&, std::vector<std::string>&, std::function<bool(std::string)>&);
} // namespace Term
@@ -0,0 +1,43 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/size.hpp"
#include <string>
namespace Term
{
class Screen
{
public:
Screen() = default;
explicit Screen(const Size& size);
const Rows& rows() const noexcept;
const Columns& columns() const noexcept;
bool empty() const;
bool operator==(const Term::Screen& screen) const;
bool operator!=(const Term::Screen& screen) const;
private:
Size m_size;
};
// clear the screen
std::string clear_screen();
// save the current terminal state
std::string screen_save();
// load a previously saved terminal state
std::string screen_load();
// get the terminal size
Screen screen_size();
} // namespace Term
@@ -0,0 +1,55 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstddef>
#include <cstdint>
namespace Term
{
class Rows
{
public:
Rows() = default;
explicit Rows(const std::uint16_t& rows) : m_rows(rows) {}
operator std::size_t() const noexcept { return m_rows; }
private:
std::uint16_t m_rows{0};
};
class Columns
{
public:
Columns() = default;
explicit Columns(const std::uint16_t& columns) : m_columns(columns) {}
operator std::size_t() const noexcept { return m_columns; }
private:
std::uint16_t m_columns{0};
};
class Size
{
public:
Size() = default;
Size(const Rows& rows, const Columns& columns) : m_rows(rows), m_columns(columns) {};
Size(const Columns& columns, const Rows& rows) : m_rows(rows), m_columns(columns) {};
std::size_t area() const noexcept { return static_cast<std::size_t>(m_rows) * static_cast<std::size_t>(m_columns); }
const Rows& rows() const noexcept { return m_rows; }
const Columns& columns() const noexcept { return m_columns; }
private:
Rows m_rows;
Columns m_columns;
};
} // namespace Term
@@ -0,0 +1,66 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/buffer.hpp"
#include <istream>
#include <ostream>
namespace Term
{
class TIstream
{
public:
explicit TIstream(const Term::Buffer::Type& type = Term::Buffer::Type::LineBuffered, const std::streamsize& size = BUFSIZ);
TIstream(const TIstream&) = delete;
TIstream(TIstream&& other) = delete;
TIstream& operator=(TIstream&&) = delete;
TIstream& operator=(const TIstream&) = delete;
~TIstream();
std::streambuf* rdbuf() const;
template<typename T> TIstream& operator>>(T& t)
{
m_stream >> t;
return *this;
}
private:
Term::Buffer m_buffer;
std::istream m_stream;
};
class TOstream
{
public:
explicit TOstream(const Term::Buffer::Type& type = Term::Buffer::Type::LineBuffered, const std::streamsize& size = BUFSIZ);
~TOstream();
TOstream(const TOstream&) = delete;
TOstream(TOstream&&) = delete;
TOstream& operator=(TOstream&&) = delete;
TOstream& operator=(const TOstream&) = delete;
template<typename T> TOstream& operator<<(const T& t)
{
m_stream << t;
return *this;
}
TOstream& operator<<(std::ostream& (*t)(std::ostream&))
{
m_stream << t;
return *this;
}
private:
Term::Buffer m_buffer;
std::ostream m_stream;
};
} // namespace Term
+100
View File
@@ -0,0 +1,100 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/iostream.hpp"
#include <cstdint>
#include <string>
namespace Term
{
/*
* Styles for text in the terminal
*/
enum class Style : std::uint8_t
{
Reset = 0, ///< resets all attributes (styles and colors)
Bold = 1, ///< Thick text font
Dim = 2, ///< lighter, slimmer text font
Italic = 3, ///< slightly bend text font
Underline = 4, ///< draws a line below the text
Blink = 5, ///< Sets blinking to less than 150 times per minute
BlinkRapid = 6, ///< MS-DOS ANSI.SYS, 150+ per minute; not widely supported
Reversed = 7, ///< swap foreground and background colors
Conceal = 8, ///< Note: not widely supported
Crossed = 9, ///< strikes through the text, mostly supported
// different fonts
Font0 = 10, ///< Primary or default font
ResetFont = 10,
Font1 = 11,
Font2 = 12,
Font3 = 13,
Font4 = 14,
Font5 = 15,
Font6 = 16,
Font7 = 17,
Font8 = 18,
Font9 = 19,
Font10 = 20, ///< Fraktur / Gothic font
// Double-underline per ECMA-48,[5] 8.3.117 but instead disables bold intensity on several terminals,
// including in the Linux kernel's console before version 4.17
DoublyUnderlinedOrNotBold = 21,
// resets corresponding styles
ResetBold = 22,
ResetDim = 22,
ResetItalic = 23,
ResetUnderline = 24,
ResetBlink = 25,
ResetBlinkRapid = 25,
ResetReversed = 27,
ResetConceal = 28,
ResetCrossed = 29,
// sets the foreground and background color to the implementation defined colors
DefaultForegroundColor = 39,
DefaultBackgroundColor = 49,
Frame = 51,
Encircle = 52,
Overline = 53, // draw a line over the text, barely supported
ResetFrame = 54,
ResetEncircle = 54,
ResetOverline = 55,
// sets the underline color to the implementation defined colors
DefaultUnderlineColor = 59, ///< non standard, implemented in Kitty, VTE, mintty, and iTerm2
BarRight = 60, ///< draw a vertical bar on the right side of the character
DoubleBarRight = 61, ///< draw a double vertical bar to the right
BarLeft = 62, ///< draw a vertical bar on the left side of the character
DoubleBarLeft = 63, ///< draw a double vertical bar to the left
StressMarking = 64, ///< reset all bars left and right double and simple
ResetBar = 65, // resets 60 - 64 inclusive
Superscript = 73, // only implemented in mintty
Subscript = 74, // only implemented in mintty
ResetSuperscript = 75, // only implemented in mintty
ResetSubscript = 75
};
std::string style(const Term::Style& style);
template<class Stream> Stream& operator<<(Stream& stream, const Term::Style& style_type) { return stream << style(style_type); }
// unabigify operator overload
inline Term::TOstream& operator<<(Term::TOstream& term, const Term::Style& style_type) { return term << style(style_type); }
} // namespace Term
@@ -0,0 +1,28 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/terminal_impl.hpp"
#include "cpp-terminal/terminal_initializer.hpp"
#include <string>
namespace Term
{
static const TerminalInitializer terminal_initializer; //NOLINT(cert-err58-cpp,fuchsia-statically-constructed-objects)
extern Term::Terminal& terminal;
// change the title of the terminal, only supported by a few terminals
std::string terminal_title(const std::string& title);
// clear the screen and the scroll-back buffer
std::string clear_buffer();
} // namespace Term
@@ -0,0 +1,63 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/options.hpp"
#include "cpp-terminal/private/signals.hpp"
#include "cpp-terminal/terminal_initializer.hpp"
#include <cstddef>
namespace Term
{
class Terminal
{
public:
friend class Private::Signals;
~Terminal() noexcept;
Terminal() noexcept;
Terminal(const Terminal&) = delete;
Terminal(Terminal&&) = delete;
Terminal& operator=(Terminal&&) = delete;
Terminal& operator=(const Terminal&) = delete;
template<typename... Args> void setOptions(const Args&&... args)
{
m_options = {args...};
applyOptions();
}
Term::Options getOptions() const noexcept;
private:
///
///@brief Store and restore the default state of the terminal. Configure the default mode for cpp-terminal.
///
static void store_and_restore() noexcept;
///
///@brief Set mode raw/cooked.
///First call is to save the good state set-up by cpp-terminal.
///
void setMode() const;
void setOptions();
void applyOptions() const;
static std::size_t setMouseEvents();
static std::size_t unsetMouseEvents();
static std::size_t setFocusEvents();
static std::size_t unsetFocusEvents();
static void set_unset_utf8();
Term::Options m_options;
void clean();
};
} // namespace Term
@@ -0,0 +1,31 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstddef>
namespace Term
{
class TerminalInitializer
{
public:
~TerminalInitializer() noexcept;
TerminalInitializer() noexcept;
TerminalInitializer(const TerminalInitializer&) = delete;
TerminalInitializer(TerminalInitializer&&) = delete;
TerminalInitializer& operator=(TerminalInitializer&&) = delete;
TerminalInitializer& operator=(const TerminalInitializer&) = delete;
private:
static std::size_t m_counter;
};
} // namespace Term
@@ -0,0 +1,93 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <array>
#include <cstdint>
#include <string>
namespace Term
{
class Terminfo
{
public:
// indicates the color mode (basically the original color resolution)
// also used to manually override the original color resolution
enum class ColorMode : std::uint8_t
{
Unset,
// no color was used
NoColor,
// a 3bit color was used
Bit3,
// a 4bit color was used
Bit4,
// a 8bit color was used
Bit8,
// a 24bit (RGB) color was used
Bit24
};
enum class Bool : std::uint8_t
{
UTF8 = 0, ///< terminal has UTF-8 activated.
Legacy, ///< Terminal is in legacy mode (Windows only).
ControlSequences, ///< Terminal support control sequences.
};
enum class String : std::uint8_t
{
TermEnv, ///< TERM environment variable value.
TermName, ///< Name of the terminal program if available.
TermVersion, ///< Terminal version.
};
enum class Integer : std::uint8_t
{
};
static bool get(const Term::Terminfo::Bool& key);
static std::uint32_t get(const Term::Terminfo::Integer& key);
static std::string get(const Term::Terminfo::String& key);
private:
static const constexpr std::size_t BoolNumber{3};
static const constexpr std::size_t StringNumber{3};
static const constexpr std::size_t IntegerNumber{0};
public:
using Booleans = std::array<bool, BoolNumber>;
using Strings = std::array<std::string, StringNumber>;
using Integers = std::array<std::uint32_t, IntegerNumber>;
Terminfo();
static ColorMode getColorMode();
private:
static void check();
static void checkTermEnv();
static void checkTerminalName();
static void checkTerminalVersion();
static void checkColorMode();
static void checkUTF8();
static void checkLegacy();
static void checkControlSequences();
static void set(const Term::Terminfo::Bool& key, const bool& value);
static void set(const Term::Terminfo::Integer& key, const std::uint32_t& value);
static void set(const Term::Terminfo::String& key, const std::string& value);
static ColorMode m_colorMode;
static Booleans m_booleans;
static Integers m_integers;
static Strings m_strings;
};
} // namespace Term
+39
View File
@@ -0,0 +1,39 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
namespace Term
{
///
/// @brief Check if \b stdin is a \b tty.
///
/// @return true : \b stdin is a \b tty.
/// @return false : \b stdin is not a \b tty.
///
bool is_stdin_a_tty();
///
/// @brief Check if \b stdout is a \b tty.
///
/// @return true : \b stdout is a \b tty.
/// @return false : \b stdout is not a \b tty.
///
bool is_stdout_a_tty();
///
/// @brief Check if \b stderr is a \b tty.
///
/// @return true : \b stderr is a \b tty.
/// @return false : \b stderr is not a \b tty.
///
bool is_stderr_a_tty();
} // namespace Term
@@ -0,0 +1,57 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstdint>
#include <string>
namespace Term
{
namespace Version
{
///
///@brief Major version of cpp-terminal.
///
///@return std::uint16_t major version.
///
std::uint16_t major() noexcept;
///
///@brief Minor version of cpp-terminal.
///
///@return std::uint16_t minor version.
///
std::uint16_t minor() noexcept;
///
///@brief Patch version of cpp-terminal.
///
///@return std::uint16_t patch version.
///
std::uint16_t patch() noexcept;
///
///@brief String version of cpp-terminal.
///
///@return std::string version in the format "Major.Minor.Patch"
///
std::string string() noexcept;
} // namespace Version
///
///@brief Homepage of cpp-terminal.
///
///@return std::string return the URL of the cpp-terminal project.
///
std::string homepage() noexcept;
} // namespace Term
@@ -0,0 +1,99 @@
/*
* cpp-terminal
* C++ library for writing multi-platform terminal applications.
*
* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "cpp-terminal/color.hpp"
#include "cpp-terminal/cursor.hpp"
#include "cpp-terminal/size.hpp"
#include "cpp-terminal/style.hpp"
#include <cstddef>
#include <vector>
namespace Term
{
class Screen;
///
/// @brief Represents a rectangular window, as a 2D array of characters and their attributes.
///
/// Represents a rectangular window, as a 2D array of characters and their attributes.
/// The render method can convert this internal representation to a string that when printed will show the Window on the screen.
//
// @note the characters are represented by char32_t, representing their UTF-32 code point.
/// The natural way to represent a character in a terminal would be a "unicode grapheme cluster", but due to a lack of a good library for C++ that could handle those, we simply use a Unicode code point as a character.
///
class Window
{
public:
explicit Window(const Size& size);
explicit Window(const Screen& screen);
const Columns& columns() const noexcept;
const Rows& rows() const noexcept;
void set_char(const std::size_t& column, const std::size_t& row, const char32_t& character);
void set_fg_reset(const std::size_t& column, const std::size_t& row);
void set_bg_reset(const std::size_t& column, const std::size_t& row);
void set_fg(const std::size_t& column, const std::size_t& row, const Color& color);
void set_bg(const std::size_t& column, const std::size_t& row, const Color& color);
void set_style(const std::size_t& column, const std::size_t& row, const Style& style);
void set_cursor_pos(const std::size_t& column, const std::size_t& row);
void set_h(const std::size_t&);
void print_str(const std::size_t& column, const std::size_t&, const std::string&, const std::size_t& = 0, bool = false);
void fill_fg(const std::size_t& column, const std::size_t&, const std::size_t&, const std::size_t&, const Color&);
void fill_bg(const std::size_t& column, const std::size_t&, const std::size_t&, const std::size_t&, const Color&);
void fill_style(const std::size_t& column, const std::size_t&, const std::size_t&, const std::size_t&, const Style&);
void print_border();
void print_rect(const std::size_t& column, const std::size_t&, const std::size_t&, const std::size_t&);
void clear();
bool insideWindow(const std::size_t& column, const std::size_t& row) const;
// TODO: add Window/Screen parameter here, to be used like this:
// old_scr = scr;
// scr.print_str(...)
// scr.render(1, 1, old_scr)
std::string render(const std::size_t&, const std::size_t&, bool);
private:
std::size_t index(const std::size_t& column, const std::size_t& row) const;
Term::Size m_size;
Term::Cursor m_cursor;
std::vector<char32_t> m_chars; // the characters in row first order
std::vector<Term::Color> m_fg;
std::vector<Term::Color> m_bg;
std::vector<bool> m_fg_reset;
std::vector<bool> m_bg_reset;
std::vector<Style> m_style;
char32_t get_char(const std::size_t& column, const std::size_t& row);
bool get_fg_reset(const std::size_t& column, const std::size_t& row);
bool get_bg_reset(const std::size_t& column, const std::size_t& row);
Term::Color get_fg(const std::size_t& column, const std::size_t& row);
Term::Color get_bg(const std::size_t& column, const std::size_t& row);
Term::Style get_style(const std::size_t& column, const std::size_t& row);
};
} // namespace Term
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,169 @@
#ifndef DOCTEST_MPI_H
#define DOCTEST_MPI_H
#ifdef DOCTEST_CONFIG_IMPLEMENT
#include "doctest/extensions/mpi_sub_comm.h"
#include "mpi_reporter.h"
#include <unordered_map>
namespace doctest {
// Each time a MPI_TEST_CASE is executed on N procs,
// we need a sub-communicator of N procs to execute it.
// It is then registered here and can be re-used
// by other tests that requires a sub-comm of the same size
std::unordered_map<int,mpi_sub_comm> sub_comms_by_size;
// Record if at least one MPI_TEST_CASE was registered "skipped"
// because there is not enought procs to execute it
int nb_test_cases_skipped_insufficient_procs = 0;
std::string thread_level_to_string(int thread_lvl);
int mpi_init_thread(int argc, char *argv[], int required_thread_support);
void mpi_finalize();
// Can be safely called before MPI_Init()
// This is needed for MPI_TEST_CASE because we use doctest::skip()
// to prevent execution of tests where there is not enough procs,
// but doctest::skip() is called during test registration, that is, before main(), and hence before MPI_Init()
int mpi_comm_world_size() {
#if defined(OPEN_MPI)
const char* size_str = std::getenv("OMPI_COMM_WORLD_SIZE");
#elif defined(I_MPI_VERSION) || defined(MPI_VERSION) // Intel MPI + MPICH (at least)
const char* size_str = std::getenv("PMI_SIZE"); // see https://community.intel.com/t5/Intel-oneAPI-HPC-Toolkit/Environment-variables-defined-by-intel-mpirun/td-p/1096703
#else
#error "Unknown MPI implementation: please submit an issue or a PR to doctest. Meanwhile, you can look at the output of e.g. `mpirun -np 3 env` to search for an environnement variable that contains the size of MPI_COMM_WORLD and extend this code accordingly"
#endif
if (size_str==nullptr) return 1; // not launched with mpirun/mpiexec, so assume only one process
return std::stoi(size_str);
}
// Record size of MPI_COMM_WORLD with mpi_comm_world_size()
int world_size_before_init = mpi_comm_world_size();
std::string thread_level_to_string(int thread_lvl) {
switch (thread_lvl) {
case MPI_THREAD_SINGLE: return "MPI_THREAD_SINGLE";
case MPI_THREAD_FUNNELED: return "MPI_THREAD_FUNNELED";
case MPI_THREAD_SERIALIZED: return "MPI_THREAD_SERIALIZED";
case MPI_THREAD_MULTIPLE: return "MPI_THREAD_MULTIPLE";
default: return "Invalid MPI thread level";
}
}
int mpi_init_thread(int argc, char *argv[], int required_thread_support) {
int provided_thread_support;
MPI_Init_thread(&argc, &argv, required_thread_support, &provided_thread_support);
int world_size;
MPI_Comm_size(MPI_COMM_WORLD,&world_size);
if (world_size_before_init != world_size) {
DOCTEST_INTERNAL_ERROR(
"doctest found "+std::to_string(world_size_before_init)+" MPI processes before `MPI_Init_thread`,"
" but MPI_COMM_WORLD is actually of size "+std::to_string(world_size)+".\n"
"This is most likely due to your MPI implementation not being well supported by doctest. Please report this issue on GitHub"
);
}
if (provided_thread_support!=required_thread_support) {
std::cout <<
"WARNING: " + thread_level_to_string(required_thread_support) + " was asked, "
+ "but only " + thread_level_to_string(provided_thread_support) + " is provided by the MPI library\n";
}
return provided_thread_support;
}
void mpi_finalize() {
// We need to destroy all created sub-communicators before calling MPI_Finalize()
doctest::sub_comms_by_size.clear();
MPI_Finalize();
}
} // doctest
#else // DOCTEST_CONFIG_IMPLEMENT
#include "doctest/extensions/mpi_sub_comm.h"
#include <unordered_map>
#include <exception>
namespace doctest {
extern std::unordered_map<int,mpi_sub_comm> sub_comms_by_size;
extern int nb_test_cases_skipped_insufficient_procs;
extern int world_size_before_init;
int mpi_comm_world_size();
int mpi_init_thread(int argc, char *argv[], int required_thread_support);
void mpi_finalize();
template<int nb_procs, class F>
void execute_mpi_test_case(F func) {
auto it = sub_comms_by_size.find(nb_procs);
if (it==end(sub_comms_by_size)) {
bool was_emplaced = false;
std::tie(it,was_emplaced) = sub_comms_by_size.emplace(std::make_pair(nb_procs,mpi_sub_comm(nb_procs)));
assert(was_emplaced);
}
const mpi_sub_comm& sub = it->second;
if (sub.comm != MPI_COMM_NULL) {
func(sub.rank,nb_procs,sub.comm,std::integral_constant<int,nb_procs>{});
};
}
inline bool
insufficient_procs(int test_nb_procs) {
static const int world_size = mpi_comm_world_size();
bool insufficient = test_nb_procs>world_size;
if (insufficient) {
++nb_test_cases_skipped_insufficient_procs;
}
return insufficient;
}
} // doctest
#define DOCTEST_MPI_GEN_ASSERTION(rank_to_test, assertion, ...) \
static_assert(rank_to_test<test_nb_procs_as_int_constant.value,"Trying to assert on a rank greater than the number of procs of the test!"); \
if(rank_to_test == test_rank) assertion(__VA_ARGS__)
#define DOCTEST_MPI_WARN(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_WARN,__VA_ARGS__)
#define DOCTEST_MPI_CHECK(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_CHECK,__VA_ARGS__)
#define DOCTEST_MPI_REQUIRE(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_REQUIRE,__VA_ARGS__)
#define DOCTEST_MPI_WARN_FALSE(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_WARN_FALSE,__VA_ARGS__)
#define DOCTEST_MPI_CHECK_FALSE(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_CHECK_FALSE,__VA_ARGS__)
#define DOCTEST_MPI_REQUIRE_FALSE(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_REQUIRE_FALSE,__VA_ARGS__)
#define DOCTEST_CREATE_MPI_TEST_CASE(name,nb_procs,func) \
static void func(DOCTEST_UNUSED int test_rank, DOCTEST_UNUSED int test_nb_procs, DOCTEST_UNUSED MPI_Comm test_comm, DOCTEST_UNUSED std::integral_constant<int,nb_procs>); \
TEST_CASE(name * doctest::description("MPI_TEST_CASE") * doctest::skip(doctest::insufficient_procs(nb_procs))) { \
doctest::execute_mpi_test_case<nb_procs>(func); \
} \
static void func(DOCTEST_UNUSED int test_rank, DOCTEST_UNUSED int test_nb_procs, DOCTEST_UNUSED MPI_Comm test_comm, DOCTEST_UNUSED std::integral_constant<int,nb_procs> test_nb_procs_as_int_constant)
// DOC: test_rank, test_nb_procs, and test_comm are available UNDER THESE SPECIFIC NAMES in the body of the unit test
// DOC: test_nb_procs_as_int_constant is equal to test_nb_procs, but as a compile time value
// (used in CHECK-like macros to assert the checked rank exists)
#define DOCTEST_MPI_TEST_CASE(name,nb_procs) \
DOCTEST_CREATE_MPI_TEST_CASE(name,nb_procs,DOCTEST_ANONYMOUS(DOCTEST_MPI_FUNC))
// == SHORT VERSIONS OF THE MACROS
#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES)
#define MPI_WARN DOCTEST_MPI_WARN
#define MPI_CHECK DOCTEST_MPI_CHECK
#define MPI_REQUIRE DOCTEST_MPI_REQUIRE
#define MPI_WARN_FALSE DOCTEST_MPI_WARN_FALSE
#define MPI_CHECK_FALSE DOCTEST_MPI_CHECK_FALSE
#define MPI_REQUIRE_FALSE DOCTEST_MPI_REQUIRE_FALSE
#define MPI_TEST_CASE DOCTEST_MPI_TEST_CASE
#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
#endif // DOCTEST_CONFIG_IMPLEMENT
#endif // DOCTEST_MPI_H
@@ -0,0 +1,37 @@
//
// doctest_util.h - an accompanying extensions header to the main doctest.h header
//
// Copyright (c) 2016-2023 Viktor Kirilov
//
// Distributed under the MIT Software License
// See accompanying file LICENSE.txt or copy at
// https://opensource.org/licenses/MIT
//
// The documentation can be found at the library's page:
// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md
//
#ifndef DOCTEST_UTIL_H
#define DOCTEST_UTIL_H
#ifndef DOCTEST_LIBRARY_INCLUDED
#include "../doctest.h"
#endif
#include <memory>
#include <vector>
#include <string>
namespace doctest {
inline void applyCommandLine(doctest::Context& ctx, const std::vector<std::string>& args) {
auto doctest_args = std::make_unique<const char*[]>(args.size());
for (size_t i = 0; i < args.size(); ++i) {
doctest_args[i] = args[i].c_str();
}
ctx.applyCommandLine(args.size(), doctest_args.get());
}
} // namespace doctest
#endif // DOCTEST_UTIL_H
@@ -0,0 +1,271 @@
#ifndef DOCTEST_MPI_REPORTER_H
#define DOCTEST_MPI_REPORTER_H
// #include <doctest/doctest.h>
#include <fstream>
#include <string>
#include "mpi.h"
#include <vector>
#include <mutex>
namespace doctest {
extern int nb_test_cases_skipped_insufficient_procs;
int mpi_comm_world_size();
namespace {
// https://stackoverflow.com/a/11826666/1583122
struct NullBuffer : std::streambuf {
int overflow(int c) { return c; }
};
class NullStream : public std::ostream {
public:
NullStream()
: std::ostream(&nullBuff)
{}
private:
NullBuffer nullBuff = {};
};
static NullStream nullStream;
/* \brief Extends the ConsoleReporter of doctest
* Each process writes its results to its own file
* Intended to be used when a test assertion fails and the user wants to know exactly what happens on which process
*/
struct MpiFileReporter : public ConsoleReporter {
std::ofstream logfile_stream = {};
MpiFileReporter(const ContextOptions& co)
: ConsoleReporter(co,logfile_stream)
{
int rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
std::string logfile_name = "doctest_" + std::to_string(rank) + ".log";
logfile_stream = std::ofstream(logfile_name.c_str(), std::fstream::out);
}
};
/* \brief Extends the ConsoleReporter of doctest
* Allows to manage the execution of tests in a parallel framework
* All results are collected on rank 0
*/
struct MpiConsoleReporter : public ConsoleReporter {
private:
static std::ostream& replace_by_null_if_not_rank_0(std::ostream* os) {
int rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if (rank==0) {
return *os;
} else {
return nullStream;
}
}
std::vector<std::pair<std::string, int>> m_failure_str_queue = {};
public:
MpiConsoleReporter(const ContextOptions& co)
: ConsoleReporter(co,replace_by_null_if_not_rank_0(co.cout))
{}
std::string file_line_to_string(const char* file, int line,
const char* tail = ""){
std::stringstream ss;
ss << skipPathFromFilename(file)
<< (opt.gnu_file_line ? ":" : "(")
<< (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option
<< (opt.gnu_file_line ? ":" : "):") << tail;
return ss.str();
}
void test_run_end(const TestRunStats& p) override {
ConsoleReporter::test_run_end(p);
const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;
// -----------------------------------------------------
// > Gather information in rank 0
int n_rank, rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &n_rank);
int g_numAsserts = 0;
int g_numAssertsFailed = 0;
int g_numTestCasesFailed = 0;
MPI_Reduce(&p.numAsserts , &g_numAsserts , 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
MPI_Reduce(&p.numAssertsFailed , &g_numAssertsFailed , 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
MPI_Reduce(&p.numTestCasesFailed, &g_numTestCasesFailed, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
std::vector<int> numAssertsFailedByRank;
if(rank == 0){
numAssertsFailedByRank.resize(static_cast<std::size_t>(n_rank));
}
MPI_Gather(&p.numAssertsFailed, 1, MPI_INT, numAssertsFailedByRank.data(), 1, MPI_INT, 0, MPI_COMM_WORLD);
if(rank == 0) {
separator_to_stream();
s << Color::Cyan << "[doctest] " << Color::None << "assertions on all processes: " << std::setw(6)
<< g_numAsserts << " | "
<< ((g_numAsserts == 0 || anythingFailed) ? Color::None : Color::Green)
<< std::setw(6) << (g_numAsserts - g_numAssertsFailed) << " passed" << Color::None
<< " | " << (g_numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6)
<< g_numAssertsFailed << " failed" << Color::None << " |\n";
if (nb_test_cases_skipped_insufficient_procs>0) {
s << Color::Cyan << "[doctest] " << Color::Yellow << "WARNING: Skipped ";
if (nb_test_cases_skipped_insufficient_procs>1) {
s << nb_test_cases_skipped_insufficient_procs << " tests requiring more than ";
} else {
s << nb_test_cases_skipped_insufficient_procs << " test requiring more than ";
}
if (mpi_comm_world_size()>1) {
s << mpi_comm_world_size() << " MPI processes to run\n";
} else {
s << mpi_comm_world_size() << " MPI process to run\n";
}
}
separator_to_stream();
if(g_numAssertsFailed > 0){
s << Color::Cyan << "[doctest] " << Color::None << "fail on rank:" << std::setw(6) << "\n";
for(std::size_t i = 0; i < numAssertsFailedByRank.size(); ++i){
if( numAssertsFailedByRank[i] > 0 ){
s << std::setw(16) << " -> On rank [" << i << "] with " << numAssertsFailedByRank[i] << " test failed" << std::endl;
}
}
}
s << Color::Cyan << "[doctest] " << Color::None
<< "Status: " << (g_numTestCasesFailed > 0 ? Color::Red : Color::Green)
<< ((g_numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl;
}
}
void test_case_end(const CurrentTestCaseStats& st) override {
if (is_mpi_test_case()) {
// function called by every rank at the end of a test
// if failed assertions happened, they have been sent to rank 0
// here rank zero gathers them and prints them all
int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
std::vector<MPI_Request> requests;
requests.reserve(m_failure_str_queue.size()); // avoid realloc & copy of MPI_Request
for (const std::pair<std::string, int> &failure : m_failure_str_queue)
{
const std::string & failure_str = failure.first;
const int failure_line = failure.second;
int failure_msg_size = static_cast<int>(failure_str.size());
requests.push_back(MPI_REQUEST_NULL);
MPI_Isend(failure_str.c_str(), failure_msg_size, MPI_BYTE,
0, failure_line, MPI_COMM_WORLD, &requests.back()); // Tag = file line
}
// Compute the number of assert with fail among all procs
const int nb_fail_asserts = static_cast<int>(m_failure_str_queue.size());
int nb_fail_asserts_glob = 0;
MPI_Reduce(&nb_fail_asserts, &nb_fail_asserts_glob, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
if(rank == 0) {
MPI_Status status;
MPI_Status status_recv;
using id_string = std::pair<int,std::string>;
std::vector<id_string> msgs(static_cast<std::size_t>(nb_fail_asserts_glob));
for (std::size_t i=0; i<static_cast<std::size_t>(nb_fail_asserts_glob); ++i) {
MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
int count;
MPI_Get_count(&status, MPI_BYTE, &count);
std::string recv_msg(static_cast<std::size_t>(count),'\0');
void* recv_msg_data = const_cast<char*>(recv_msg.data()); // const_cast needed. Non-const .data() exists in C++11 though...
MPI_Recv(recv_msg_data, count, MPI_BYTE, status.MPI_SOURCE,
status.MPI_TAG, MPI_COMM_WORLD, &status_recv);
msgs[i] = {status.MPI_SOURCE,recv_msg};
}
std::sort(begin(msgs),end(msgs),[](const id_string& x, const id_string& y){ return x.first < y.first; });
// print
if (nb_fail_asserts_glob>0) {
separator_to_stream();
file_line_to_stream(tc->m_file.c_str(), static_cast<int>(tc->m_line), "\n");
if(tc->m_test_suite && tc->m_test_suite[0] != '\0')
s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n";
if(strncmp(tc->m_name, " Scenario:", 11) != 0)
s << Color::Yellow << "TEST CASE: ";
s << Color::None << tc->m_name << "\n\n";
for(const auto& msg : msgs) {
s << msg.second;
}
s << "\n";
}
}
MPI_Waitall(static_cast<int>(requests.size()), requests.data(), MPI_STATUSES_IGNORE);
m_failure_str_queue.clear();
}
ConsoleReporter::test_case_end(st);
}
bool is_mpi_test_case() const {
return tc->m_description != nullptr
&& std::string(tc->m_description) == std::string("MPI_TEST_CASE");
}
void log_assert(const AssertData& rb) override {
if (!is_mpi_test_case()) {
ConsoleReporter::log_assert(rb);
} else {
int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if(!rb.m_failed && !opt.success)
return;
std::lock_guard<std::mutex> lock(mutex);
std::stringstream failure_msg;
failure_msg << Color::Red << "On rank [" << rank << "] : " << Color::None;
failure_msg << file_line_to_string(rb.m_file, rb.m_line, " ");
if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) ==0){
failure_msg << Color::Cyan
<< assertString(rb.m_at)
<< "( " << rb.m_expr << " ) "
<< Color::None
<< (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")
<< " values: "
<< assertString(rb.m_at)
<< "( " << rb.m_decomp.c_str() << " )\n";
}
m_failure_str_queue.push_back({failure_msg.str(), rb.m_line});
}
}
}; // MpiConsoleReporter
// "1" is the priority - used for ordering when multiple reporters/listeners are used
REGISTER_REPORTER("MpiConsoleReporter", 1, MpiConsoleReporter);
REGISTER_REPORTER("MpiFileReporter", 1, MpiFileReporter);
} // anonymous
} // doctest
#endif // DOCTEST_REPORTER_H
@@ -0,0 +1,84 @@
#ifndef DOCTEST_MPI_SUB_COMM_H
#define DOCTEST_MPI_SUB_COMM_H
#include "mpi.h"
#include "doctest/doctest.h"
#include <cassert>
#include <string>
namespace doctest {
inline
int mpi_world_nb_procs() {
int n;
MPI_Comm_size(MPI_COMM_WORLD, &n);
return n;
}
struct mpi_sub_comm {
int nb_procs;
int rank;
MPI_Comm comm;
mpi_sub_comm( mpi_sub_comm const& ) = delete;
mpi_sub_comm& operator=( mpi_sub_comm const& ) = delete;
mpi_sub_comm(int nb_prcs) noexcept
: nb_procs(nb_prcs)
, rank(-1)
, comm(MPI_COMM_NULL)
{
int comm_world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &comm_world_rank);
if (nb_procs>mpi_world_nb_procs()) {
if (comm_world_rank==0) {
MESSAGE(
"Unable to run test: need ", std::to_string(nb_procs), " procs",
" but program launched with only ", std::to_string(doctest::mpi_world_nb_procs()), "."
);
CHECK(nb_procs<=mpi_world_nb_procs());
}
} else {
int color = MPI_UNDEFINED;
if(comm_world_rank < nb_procs){
color = 0;
}
MPI_Comm_split(MPI_COMM_WORLD, color, comm_world_rank, &comm);
if(comm != MPI_COMM_NULL){
MPI_Comm_rank(comm, &rank);
assert(rank==comm_world_rank);
}
}
}
void destroy_comm() {
if(comm != MPI_COMM_NULL){
MPI_Comm_free(&comm);
}
}
mpi_sub_comm(mpi_sub_comm&& x)
: nb_procs(x.nb_procs)
, rank(x.rank)
, comm(x.comm)
{
x.comm = MPI_COMM_NULL;
}
mpi_sub_comm& operator=(mpi_sub_comm&& x) {
destroy_comm();
nb_procs = x.nb_procs;
rank = x.rank;
comm = x.comm;
x.comm = MPI_COMM_NULL;
return *this;
}
~mpi_sub_comm() {
destroy_comm();
}
};
} // doctest
#endif // DOCTEST_SUB_COMM_H
@@ -0,0 +1,29 @@
####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() #######
####### Any changes to this file will be overwritten by the next CMake run ####
####### The input file was cpp-terminalConfig.cmake.in ########
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
macro(set_and_check _var _file)
set(${_var} "${_file}")
if(NOT EXISTS "${_file}")
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
endif()
endmacro()
macro(check_required_components _NAME)
foreach(comp ${${_NAME}_FIND_COMPONENTS})
if(NOT ${_NAME}_${comp}_FOUND)
if(${_NAME}_FIND_REQUIRED_${comp})
set(${_NAME}_FOUND FALSE)
endif()
endif()
endforeach()
endmacro()
####################################################################################
include("${CMAKE_CURRENT_LIST_DIR}/cpp-terminalTargets.cmake")
check_required_components(cpp-terminal)
@@ -0,0 +1,48 @@
# This is a basic version file for the Config-mode of find_package().
# It is used by write_basic_package_version_file() as input file for configure_file()
# to create a version-file which can be installed along a config.cmake file.
#
# The created file sets PACKAGE_VERSION_EXACT if the current version string and
# the requested version string are exactly the same and it sets
# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version.
# The variable CVF_VERSION must be set before calling configure_file().
set(PACKAGE_VERSION "1.0.0")
if (PACKAGE_FIND_VERSION_RANGE)
# Package version must be in the requested version range
if ((PACKAGE_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MIN)
OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_GREATER PACKAGE_FIND_VERSION_MAX)
OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_GREATER_EQUAL PACKAGE_FIND_VERSION_MAX)))
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
endif()
else()
if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
endif()
# if the installed project requested no architecture check, don't perform the check
if("FALSE")
return()
endif()
# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it:
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "")
return()
endif()
# check that the installed version has the same 32/64bit-ness as the one which is currently searching:
if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8")
math(EXPR installedBits "8 * 8")
set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)")
set(PACKAGE_VERSION_UNSUITABLE TRUE)
endif()
@@ -0,0 +1,19 @@
#----------------------------------------------------------------
# Generated CMake target import file for configuration "Debug".
#----------------------------------------------------------------
# Commands may need to know the format version.
set(CMAKE_IMPORT_FILE_VERSION 1)
# Import target "cpp-terminal::cpp-terminal" for configuration "Debug"
set_property(TARGET cpp-terminal::cpp-terminal APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(cpp-terminal::cpp-terminal PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX"
IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/cpp-terminal.lib"
)
list(APPEND _cmake_import_check_targets cpp-terminal::cpp-terminal )
list(APPEND _cmake_import_check_files_for_cpp-terminal::cpp-terminal "${_IMPORT_PREFIX}/lib/cpp-terminal.lib" )
# Commands beyond this point should not need to know the version.
set(CMAKE_IMPORT_FILE_VERSION)
@@ -0,0 +1,19 @@
#----------------------------------------------------------------
# Generated CMake target import file for configuration "Release".
#----------------------------------------------------------------
# Commands may need to know the format version.
set(CMAKE_IMPORT_FILE_VERSION 1)
# Import target "cpp-terminal::cpp-terminal" for configuration "Release"
set_property(TARGET cpp-terminal::cpp-terminal APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(cpp-terminal::cpp-terminal PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX"
IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/cpp-terminal.lib"
)
list(APPEND _cmake_import_check_targets cpp-terminal::cpp-terminal )
list(APPEND _cmake_import_check_files_for_cpp-terminal::cpp-terminal "${_IMPORT_PREFIX}/lib/cpp-terminal.lib" )
# Commands beyond this point should not need to know the version.
set(CMAKE_IMPORT_FILE_VERSION)
@@ -0,0 +1,122 @@
# Generated by CMake
if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8)
message(FATAL_ERROR "CMake >= 2.8.0 required")
endif()
if(CMAKE_VERSION VERSION_LESS "2.8.3")
message(FATAL_ERROR "CMake >= 2.8.3 required")
endif()
cmake_policy(PUSH)
cmake_policy(VERSION 2.8.3...3.23)
#----------------------------------------------------------------
# Generated CMake target import file.
#----------------------------------------------------------------
# Commands may need to know the format version.
set(CMAKE_IMPORT_FILE_VERSION 1)
# Protect against multiple inclusion, which would fail when already imported targets are added once more.
set(_cmake_targets_defined "")
set(_cmake_targets_not_defined "")
set(_cmake_expected_targets "")
foreach(_cmake_expected_target IN ITEMS cpp-terminal::cpp-terminal cpp-terminal::cpp-terminalWarnings cpp-terminal::cpp-terminal-private)
list(APPEND _cmake_expected_targets "${_cmake_expected_target}")
if(TARGET "${_cmake_expected_target}")
list(APPEND _cmake_targets_defined "${_cmake_expected_target}")
else()
list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}")
endif()
endforeach()
unset(_cmake_expected_target)
if(_cmake_targets_defined STREQUAL _cmake_expected_targets)
unset(_cmake_targets_defined)
unset(_cmake_targets_not_defined)
unset(_cmake_expected_targets)
unset(CMAKE_IMPORT_FILE_VERSION)
cmake_policy(POP)
return()
endif()
if(NOT _cmake_targets_defined STREQUAL "")
string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}")
string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}")
message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n")
endif()
unset(_cmake_targets_defined)
unset(_cmake_targets_not_defined)
unset(_cmake_expected_targets)
# Compute the installation prefix relative to this file.
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
if(_IMPORT_PREFIX STREQUAL "/")
set(_IMPORT_PREFIX "")
endif()
# Create imported target cpp-terminal::cpp-terminal
add_library(cpp-terminal::cpp-terminal STATIC IMPORTED)
set_target_properties(cpp-terminal::cpp-terminal PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:cpp-terminal::cpp-terminal-private>"
)
# Create imported target cpp-terminal::cpp-terminalWarnings
add_library(cpp-terminal::cpp-terminalWarnings INTERFACE IMPORTED)
set_target_properties(cpp-terminal::cpp-terminalWarnings PROPERTIES
INTERFACE_COMPILE_OPTIONS "\$<\$<C_COMPILER_ID:MSVC>:/W4;/Wall;/WL;/wd5045;/wd4820;/wd4514>;\$<\$<C_COMPILER_ID:GNU,Clang,AppleClang>:-Wall;-Wextra>;\$<\$<C_COMPILER_ID:GNU>:\$<\$<VERSION_GREATER_EQUAL:\$<C_COMPILER_VERSION>,4.8.0>:-Wpedantic>;\$<\$<VERSION_GREATER_EQUAL:\$<C_COMPILER_VERSION>,4.9.0>:-fdiagnostics-color=auto>;-Wpointer-arith;-Wredundant-decls;-Wundef;-Wwrite-strings>;\$<\$<C_COMPILER_ID:Clang,AppleClang>:-Wno-c++98-compat>;\$<\$<C_COMPILER_ID:Intel,IntelLLVM>:\$<IF:\$<PLATFORM_ID:Windows>,/W5;/Wall,-w3;-Wall;-Wextra-tokens;-Wformat;-Wmain;-Wpointer-arith;-Wreorder;-Wreturn-type;-Wshadow;-Wsign-compare;-Wstrict-aliasing;-Wuninitialized;-Wwrite-strings;-Wno-debug-disables-optimization>>;\$<\$<CXX_COMPILER_ID:MSVC>:/W4;/Wall;/WL;/wd5045;/wd4820;/wd4514>;\$<\$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall;-Wextra>;\$<\$<CXX_COMPILER_ID:GNU>:\$<\$<VERSION_GREATER_EQUAL:\$<C_COMPILER_VERSION>,4.8.0>:-Wpedantic>;\$<\$<VERSION_GREATER_EQUAL:\$<C_COMPILER_VERSION>,4.9.0>:-fdiagnostics-color=auto>;-Wpointer-arith;-Wredundant-decls;-Wundef;-Wwrite-strings>;\$<\$<CXX_COMPILER_ID:Clang,AppleClang>:-Wno-c++98-compat>;\$<\$<OR:\$<COMPILE_LANG_AND_ID:CXX,Clang,AppleClang,GNU>,\$<COMPILE_LANG_AND_ID:OBJCXX,Clang,AppleClang,GNU>>:>;\$<\$<OR:\$<COMPILE_LANG_AND_ID:CXX,GNU>,\$<COMPILE_LANG_AND_ID:OBJCXX,GNU>>:-Wdelete-non-virtual-dtor;\$<\$<VERSION_GREATER_EQUAL:\$<CXX_COMPILER_VERSION>,4.8.0>:-Wpedantic>;\$<\$<VERSION_GREATER_EQUAL:\$<CXX_COMPILER_VERSION>,4.9.0>:-fdiagnostics-color=auto>;-Wnoexcept>;\$<\$<OR:\$<COMPILE_LANG_AND_ID:CXX,Clang,AppleClang>,\$<COMPILE_LANG_AND_ID:OBJCXX,Clang,AppleClang>>:>;\$<\$<CXX_COMPILER_ID:Intel,IntelLLVM>:\$<IF:\$<PLATFORM_ID:Windows>,/W5;/Wall,-w3;-Wall;-Wextra-tokens;-Wformat;-Wmain;-Wpointer-arith;-Wreorder;-Wreturn-type;-Wshadow;-Wsign-compare;-Wstrict-aliasing;-Wuninitialized;-Wwrite-strings;-Wno-debug-disables-optimization>>"
)
# Create imported target cpp-terminal::cpp-terminal-private
add_library(cpp-terminal::cpp-terminal-private INTERFACE IMPORTED)
set_target_properties(cpp-terminal::cpp-terminal-private PROPERTIES
INTERFACE_COMPILE_OPTIONS "\$<\$<CXX_COMPILER_ID:MSVC>:/utf-8>"
INTERFACE_LINK_LIBRARIES "cpp-terminal::cpp-terminalWarnings;Threads::Threads"
)
if(CMAKE_VERSION VERSION_LESS 3.1.0)
message(FATAL_ERROR "This file relies on consumers using CMake 3.1.0 or greater.")
endif()
# Load information for each installed configuration.
file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/cpp-terminalTargets-*.cmake")
foreach(_cmake_config_file IN LISTS _cmake_config_files)
include("${_cmake_config_file}")
endforeach()
unset(_cmake_config_file)
unset(_cmake_config_files)
# Cleanup temporary variables.
set(_IMPORT_PREFIX)
# Loop over all imported files and verify that they actually exist
foreach(_cmake_target IN LISTS _cmake_import_check_targets)
foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}")
if(NOT EXISTS "${_cmake_file}")
message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file
\"${_cmake_file}\"
but this file does not exist. Possible reasons include:
* The file was deleted, renamed, or moved to another location.
* An install or uninstall procedure did not complete successfully.
* The installation package was faulty and contained
\"${CMAKE_CURRENT_LIST_FILE}\"
but not all the files it references.
")
endif()
endforeach()
unset(_cmake_file)
unset("_cmake_import_check_files_for_${_cmake_target}")
endforeach()
unset(_cmake_target)
unset(_cmake_import_check_targets)
# This file does not depend on other imported targets which have
# been exported from the same project but in a separate export set.
# Commands beyond this point should not need to know the version.
set(CMAKE_IMPORT_FILE_VERSION)
cmake_policy(POP)
@@ -0,0 +1,191 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
doctest
-----
This module defines a function to help use the doctest test framework.
The :command:`doctest_discover_tests` discovers tests by asking the compiled test
executable to enumerate its tests. This does not require CMake to be re-run
when tests change. However, it may not work in a cross-compiling environment,
and setting test properties is less convenient.
This command is intended to replace use of :command:`add_test` to register
tests, and will create a separate CTest test for each doctest test case. Note
that this is in some cases less efficient, as common set-up and tear-down logic
cannot be shared by multiple test cases executing in the same instance.
However, it provides more fine-grained pass/fail information to CTest, which is
usually considered as more beneficial. By default, the CTest test name is the
same as the doctest name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
.. command:: doctest_discover_tests
Automatically add tests with CTest by querying the compiled test executable
for available tests::
doctest_discover_tests(target
[TEST_SPEC arg1...]
[EXTRA_ARGS arg1...]
[WORKING_DIRECTORY dir]
[TEST_PREFIX prefix]
[TEST_SUFFIX suffix]
[PROPERTIES name1 value1...]
[ADD_LABELS value]
[TEST_LIST var]
[JUNIT_OUTPUT_DIR dir]
)
``doctest_discover_tests`` sets up a post-build command on the test executable
that generates the list of tests by parsing the output from running the test
with the ``--list-test-cases`` argument. This ensures that the full
list of tests is obtained. Since test discovery occurs at build time, it is
not necessary to re-run CMake when the list of tests changes.
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
in order to function in a cross-compiling environment.
Additionally, setting properties on tests is somewhat less convenient, since
the tests are not available at CMake time. Additional test properties may be
assigned to the set of tests as a whole using the ``PROPERTIES`` option. If
more fine-grained test control is needed, custom content may be provided
through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`
directory property. The set of discovered tests is made accessible to such a
script via the ``<target>_TESTS`` variable.
The options are:
``target``
Specifies the doctest executable, which must be a known CMake executable
target. CMake will substitute the location of the built executable when
running the test.
``TEST_SPEC arg1...``
Specifies test cases, wildcarded test cases, tags and tag expressions to
pass to the doctest executable with the ``--list-test-cases`` argument.
``EXTRA_ARGS arg1...``
Any extra arguments to pass on the command line to each test case.
``WORKING_DIRECTORY dir``
Specifies the directory in which to run the discovered test cases. If this
option is not provided, the current binary directory is used.
``TEST_PREFIX prefix``
Specifies a ``prefix`` to be prepended to the name of each discovered test
case. This can be useful when the same test executable is being used in
multiple calls to ``doctest_discover_tests()`` but with different
``TEST_SPEC`` or ``EXTRA_ARGS``.
``TEST_SUFFIX suffix``
Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of
every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may
be specified.
``PROPERTIES name1 value1...``
Specifies additional properties to be set on all tests discovered by this
invocation of ``doctest_discover_tests``.
``ADD_LABELS value``
Specifies if the test labels should be set automatically.
``TEST_LIST var``
Make the list of tests available in the variable ``var``, rather than the
default ``<target>_TESTS``. This can be useful when the same test
executable is being used in multiple calls to ``doctest_discover_tests()``.
Note that this variable is only available in CTest.
``JUNIT_OUTPUT_DIR dir``
If specified, the parameter is passed along with ``--reporters=junit``
and ``--out=`` to the test executable. The actual file name is the same
as the test target, including prefix and suffix. This should be used
instead of EXTRA_ARGS to avoid race conditions writing the XML result
output when using parallel test execution.
#]=======================================================================]
#------------------------------------------------------------------------------
function(doctest_discover_tests TARGET)
cmake_parse_arguments(
""
""
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;JUNIT_OUTPUT_DIR"
"TEST_SPEC;EXTRA_ARGS;PROPERTIES;ADD_LABELS"
${ARGN}
)
if(NOT _WORKING_DIRECTORY)
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
endif()
if(NOT _TEST_LIST)
set(_TEST_LIST ${TARGET}_TESTS)
endif()
## Generate a unique name based on the extra arguments
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}")
string(SUBSTRING ${args_hash} 0 7 args_hash)
# Define rule to generate test list for aforementioned test executable
set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
get_property(crosscompiling_emulator
TARGET ${TARGET}
PROPERTY CROSSCOMPILING_EMULATOR
)
add_custom_command(
TARGET ${TARGET} POST_BUILD
BYPRODUCTS "${ctest_tests_file}"
COMMAND "${CMAKE_COMMAND}"
-D "TEST_TARGET=${TARGET}"
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
-D "TEST_SPEC=${_TEST_SPEC}"
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
-D "TEST_PROPERTIES=${_PROPERTIES}"
-D "TEST_ADD_LABELS=${_ADD_LABELS}"
-D "TEST_PREFIX=${_TEST_PREFIX}"
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
-D "TEST_LIST=${_TEST_LIST}"
-D "TEST_JUNIT_OUTPUT_DIR=${_JUNIT_OUTPUT_DIR}"
-D "CTEST_FILE=${ctest_tests_file}"
-P "${_DOCTEST_DISCOVER_TESTS_SCRIPT}"
VERBATIM
)
file(WRITE "${ctest_include_file}"
"if(EXISTS \"${ctest_tests_file}\")\n"
" include(\"${ctest_tests_file}\")\n"
"else()\n"
" add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
"endif()\n"
)
if(NOT CMAKE_VERSION VERSION_LESS 3.10)
# Add discovered tests to directory TEST_INCLUDE_FILES
set_property(DIRECTORY
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
)
else()
# Add discovered tests as directory TEST_INCLUDE_FILE if possible
get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
if(NOT ${test_include_file_set})
set_property(DIRECTORY
PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
)
else()
message(FATAL_ERROR
"Cannot set more than one TEST_INCLUDE_FILE"
)
endif()
endif()
endfunction()
###############################################################################
set(_DOCTEST_DISCOVER_TESTS_SCRIPT
${CMAKE_CURRENT_LIST_DIR}/doctestAddTests.cmake
CACHE INTERNAL "The location of the doctestAddTests script"
)
mark_as_advanced(_DOCTEST_DISCOVER_TESTS_SCRIPT)
@@ -0,0 +1,120 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
set(prefix "${TEST_PREFIX}")
set(suffix "${TEST_SUFFIX}")
set(spec ${TEST_SPEC})
set(extra_args ${TEST_EXTRA_ARGS})
set(properties ${TEST_PROPERTIES})
set(add_labels ${TEST_ADD_LABELS})
set(junit_output_dir "${TEST_JUNIT_OUTPUT_DIR}")
set(script)
set(suite)
set(tests)
function(add_command NAME)
set(_args "")
foreach(_arg ${ARGN})
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
else()
set(_args "${_args} ${_arg}")
endif()
endforeach()
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
endfunction()
# Run test executable to get list of available tests
if(NOT EXISTS "${TEST_EXECUTABLE}")
message(FATAL_ERROR
"Specified test executable '${TEST_EXECUTABLE}' does not exist"
)
endif()
if("${spec}" MATCHES .)
set(spec "--test-case=${spec}")
endif()
execute_process(
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-cases
OUTPUT_VARIABLE output
RESULT_VARIABLE result
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
)
if(NOT ${result} EQUAL 0)
message(FATAL_ERROR
"Error running test executable '${TEST_EXECUTABLE}':\n"
" Result: ${result}\n"
" Output: ${output}\n"
)
endif()
string(REPLACE "\n" ";" output "${output}")
# Parse output
foreach(line ${output})
if("${line}" STREQUAL "===============================================================================" OR "${line}" MATCHES [==[^\[doctest\] ]==])
continue()
endif()
set(test ${line})
set(labels "")
if(${add_labels})
# get test suite that test belongs to
execute_process(
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" --test-case=${test} --list-test-suites
OUTPUT_VARIABLE labeloutput
RESULT_VARIABLE labelresult
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
)
if(NOT ${labelresult} EQUAL 0)
message(FATAL_ERROR
"Error running test executable '${TEST_EXECUTABLE}':\n"
" Result: ${labelresult}\n"
" Output: ${labeloutput}\n"
)
endif()
string(REPLACE "\n" ";" labeloutput "${labeloutput}")
foreach(labelline ${labeloutput})
if("${labelline}" STREQUAL "===============================================================================" OR "${labelline}" MATCHES [==[^\[doctest\] ]==])
continue()
endif()
list(APPEND labels ${labelline})
endforeach()
endif()
if(NOT "${junit_output_dir}" STREQUAL "")
# turn testname into a valid filename by replacing all special characters with "-"
string(REGEX REPLACE "[/\\:\"|<>]" "-" test_filename "${test}")
set(TEST_JUNIT_OUTPUT_PARAM "--reporters=junit" "--out=${junit_output_dir}/${prefix}${test_filename}${suffix}.xml")
else()
unset(TEST_JUNIT_OUTPUT_PARAM)
endif()
# use escape commas to handle properly test cases with commas inside the name
string(REPLACE "," "\\," test_name ${test})
# ...and add to script
add_command(add_test
"${prefix}${test}${suffix}"
${TEST_EXECUTOR}
"${TEST_EXECUTABLE}"
"--test-case=${test_name}"
"${TEST_JUNIT_OUTPUT_PARAM}"
${extra_args}
)
add_command(set_tests_properties
"${prefix}${test}${suffix}"
PROPERTIES
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
${properties}
LABELS ${labels}
)
unset(labels)
list(APPEND tests "${prefix}${test}${suffix}")
endforeach()
# Create a list of all discovered tests, which users may use to e.g. set
# properties on the tests
add_command(set ${TEST_LIST} ${tests})
# Write CTest script
file(WRITE "${CTEST_FILE}" "${script}")
@@ -0,0 +1,6 @@
if(NOT TARGET doctest::doctest)
# Provide path for scripts
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
include("${CMAKE_CURRENT_LIST_DIR}/doctestTargets.cmake")
endif()
@@ -0,0 +1,70 @@
# This is a basic version file for the Config-mode of find_package().
# It is used by write_basic_package_version_file() as input file for configure_file()
# to create a version-file which can be installed along a config.cmake file.
#
# The created file sets PACKAGE_VERSION_EXACT if the current version string and
# the requested version string are exactly the same and it sets
# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version,
# but only if the requested major version is the same as the current one.
# The variable CVF_VERSION must be set before calling configure_file().
set(PACKAGE_VERSION "2.4.11")
if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
if("2.4.11" MATCHES "^([0-9]+)\\.")
set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}")
if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0)
string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}")
endif()
else()
set(CVF_VERSION_MAJOR "2.4.11")
endif()
if(PACKAGE_FIND_VERSION_RANGE)
# both endpoints of the range must have the expected major version
math (EXPR CVF_VERSION_MAJOR_NEXT "${CVF_VERSION_MAJOR} + 1")
if (NOT PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR
OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX_MAJOR STREQUAL CVF_VERSION_MAJOR)
OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX VERSION_LESS_EQUAL CVF_VERSION_MAJOR_NEXT)))
set(PACKAGE_VERSION_COMPATIBLE FALSE)
elseif(PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR
AND ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS_EQUAL PACKAGE_FIND_VERSION_MAX)
OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MAX)))
set(PACKAGE_VERSION_COMPATIBLE TRUE)
else()
set(PACKAGE_VERSION_COMPATIBLE FALSE)
endif()
else()
if(PACKAGE_FIND_VERSION_MAJOR STREQUAL CVF_VERSION_MAJOR)
set(PACKAGE_VERSION_COMPATIBLE TRUE)
else()
set(PACKAGE_VERSION_COMPATIBLE FALSE)
endif()
if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
endif()
# if the installed project requested no architecture check, don't perform the check
if("FALSE")
return()
endif()
# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it:
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "" STREQUAL "")
return()
endif()
# check that the installed version has the same 32/64bit-ness as the one which is currently searching:
if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "")
math(EXPR installedBits " * 8")
set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)")
set(PACKAGE_VERSION_UNSUITABLE TRUE)
endif()
@@ -0,0 +1,107 @@
# Generated by CMake
if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8)
message(FATAL_ERROR "CMake >= 2.8.0 required")
endif()
if(CMAKE_VERSION VERSION_LESS "2.8.3")
message(FATAL_ERROR "CMake >= 2.8.3 required")
endif()
cmake_policy(PUSH)
cmake_policy(VERSION 2.8.3...3.23)
#----------------------------------------------------------------
# Generated CMake target import file.
#----------------------------------------------------------------
# Commands may need to know the format version.
set(CMAKE_IMPORT_FILE_VERSION 1)
# Protect against multiple inclusion, which would fail when already imported targets are added once more.
set(_cmake_targets_defined "")
set(_cmake_targets_not_defined "")
set(_cmake_expected_targets "")
foreach(_cmake_expected_target IN ITEMS doctest::doctest)
list(APPEND _cmake_expected_targets "${_cmake_expected_target}")
if(TARGET "${_cmake_expected_target}")
list(APPEND _cmake_targets_defined "${_cmake_expected_target}")
else()
list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}")
endif()
endforeach()
unset(_cmake_expected_target)
if(_cmake_targets_defined STREQUAL _cmake_expected_targets)
unset(_cmake_targets_defined)
unset(_cmake_targets_not_defined)
unset(_cmake_expected_targets)
unset(CMAKE_IMPORT_FILE_VERSION)
cmake_policy(POP)
return()
endif()
if(NOT _cmake_targets_defined STREQUAL "")
string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}")
string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}")
message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n")
endif()
unset(_cmake_targets_defined)
unset(_cmake_targets_not_defined)
unset(_cmake_expected_targets)
# Compute the installation prefix relative to this file.
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
if(_IMPORT_PREFIX STREQUAL "/")
set(_IMPORT_PREFIX "")
endif()
# Create imported target doctest::doctest
add_library(doctest::doctest INTERFACE IMPORTED)
set_target_properties(doctest::doctest PROPERTIES
INTERFACE_COMPILE_FEATURES "cxx_std_11"
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
)
if(CMAKE_VERSION VERSION_LESS 3.0.0)
message(FATAL_ERROR "This file relies on consumers using CMake 3.0.0 or greater.")
endif()
# Load information for each installed configuration.
file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/doctestTargets-*.cmake")
foreach(_cmake_config_file IN LISTS _cmake_config_files)
include("${_cmake_config_file}")
endforeach()
unset(_cmake_config_file)
unset(_cmake_config_files)
# Cleanup temporary variables.
set(_IMPORT_PREFIX)
# Loop over all imported files and verify that they actually exist
foreach(_cmake_target IN LISTS _cmake_import_check_targets)
foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}")
if(NOT EXISTS "${_cmake_file}")
message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file
\"${_cmake_file}\"
but this file does not exist. Possible reasons include:
* The file was deleted, renamed, or moved to another location.
* An install or uninstall procedure did not complete successfully.
* The installation package was faulty and contained
\"${CMAKE_CURRENT_LIST_FILE}\"
but not all the files it references.
")
endif()
endforeach()
unset(_cmake_file)
unset("_cmake_import_check_files_for_${_cmake_target}")
endforeach()
unset(_cmake_target)
unset(_cmake_import_check_targets)
# This file does not depend on other imported targets which have
# been exported from the same project but in a separate export set.
# Commands beyond this point should not need to know the version.
set(CMAKE_IMPORT_FILE_VERSION)
cmake_policy(POP)
Binary file not shown.