alec

Abstraction Layer for Escape Codes
git clone git://git.dimitrijedobrota.com/alec.git
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING

commit b6aefc9b942a0f464448415670b8679298a51782
parent d705309b300eaf8f7c60a6eb6774f21e5634789c
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Tue, 20 May 2025 21:47:04 +0200

Cleanup

Diffstat:
M include/alec/alec.hpp | +++++++++++++++++++++++++++++++++++++++ -------------------------------------------
M include/alec/terminal.hpp | ++++++++++++++++++++++++++++++++++++++++ ------------------------------------------

2 files changed, 102 insertions(+), 111 deletions(-)


diff --git a/ include/alec/alec.hpp b/ include/alec/alec.hpp

@@ -14,26 +14,24 @@ namespace alec


#define ENUM_COLOR \
black, red, green, yellow, blue, magenta, cyan, white, unused_1, def
BASED_DECLARE_ENUM(color, based::bi8, 0, ENUM_COLOR)
BASED_DEFINE_ENUM(color, based::bi8, 0, ENUM_COLOR)
BASED_DECLARE_ENUM(color, based::bu8, 0, ENUM_COLOR)
BASED_DEFINE_ENUM(color, based::bu8, 0, ENUM_COLOR)
#undef ENUM_COLOR

#define ENUM_DECOR \
reset, bold, dim, italic, underline, blink, unused_1, inverse, hide, strike
BASED_DECLARE_ENUM(decor, based::bi8, 0, ENUM_DECOR)
BASED_DEFINE_ENUM(decor, based::bi8, 0, ENUM_DECOR)
BASED_DECLARE_ENUM(decor, based::bu8, 0, ENUM_DECOR)
BASED_DEFINE_ENUM(decor, based::bu8, 0, ENUM_DECOR)
#undef ENUM_DECOR

#define ENUM_MOTION end, begin, whole
BASED_DECLARE_ENUM(motion, based::bi8, 0, ENUM_MOTION)
BASED_DEFINE_ENUM(motion, based::bi8, 0, ENUM_MOTION)
BASED_DECLARE_ENUM(motion, based::bu8, 0, ENUM_MOTION)
BASED_DEFINE_ENUM(motion, based::bu8, 0, ENUM_MOTION)
#undef ENUM_MOTION

namespace detail
{

namespace helper
{
template<std::size_t n>
static constexpr std::size_t size(based::string_literal<n> /*val*/)
{

@@ -79,10 +77,10 @@ static constexpr char* append(char* ptr, T val)


static constexpr std::string make(auto... args)
{
std::string res((helper::size(args) + ... + 2), 0);
std::string res((size(args) + ... + 2), 0);
res[0] = 0x1B, res[1] = '[';
auto* ptr = res.data() + 2; // NOLINT
((ptr = helper::append(ptr, args)), ...);
((ptr = append(ptr, args)), ...);
return res;
}

@@ -91,19 +89,17 @@ struct escape_t

{
static constexpr const auto value = []()
{
std::array<char, (helper::size(args) + ... + 3)> arr = {};
std::array<char, (size(args) + ... + 3)> arr = {};
arr[0] = 0x1B, arr[1] = '[';
auto* ptr = arr.data() + 2;
((ptr = helper::append(ptr, args)), ...);
((ptr = append(ptr, args)), ...);
return arr;
}();
static constexpr auto data = value.data();
};

} // namespace helper

template<auto... args>
static constexpr auto escape = alec::detail::helper::escape_t<args...>::data;
static constexpr auto escape = alec::detail::escape_t<args...>::data;

template<based::string_literal... strs>
static constexpr auto escape_literal = escape<strs...>;

@@ -147,11 +143,11 @@ template<unsigned col>

static constexpr auto cursor_column_v = detail::escape<col, 'G'>;

// Erase functions
template<motion::type motion>
static constexpr auto erase_display_v = detail::escape<motion.value, 'J'>;
template<motion::enum_type motion>
static constexpr auto erase_display_v = detail::escape<motion(), 'J'>;

template<motion::type motion>
static constexpr auto erase_line_v = detail::escape<motion.value, 'K'>;
template<motion::enum_type motion>
static constexpr auto erase_line_v = detail::escape<motion(), 'K'>;

// Scroll up/down
template<unsigned cnt>

@@ -166,13 +162,11 @@ static constexpr auto cursor_position_v = detail::escape<row, ';', col, 'H'>;


// color
// palet colors
template<color::type color>
static constexpr auto foreground_v<color> =
detail::escape<color.value + 30, 'm'>;
template<color::enum_type color>
static constexpr auto foreground_v<color> = detail::escape<color() + 30, 'm'>;

template<color::type color>
static constexpr auto background_v<color> =
detail::escape<color.value + 40, 'm'>;
template<color::enum_type color>
static constexpr auto background_v<color> = detail::escape<color() + 40, 'm'>;

// 256-color palette
template<based::bu8 idx>

@@ -193,11 +187,11 @@ static constexpr auto background_v<red, green, blue> =

detail::escape<48, ';', 2, ';', red, ';', green, ';', blue, 'm'>;

// Set/reset text decorators
template<decor::type decor>
static constexpr auto decor_set_v = detail::escape<decor.value, 'm'>;
template<decor::enum_type decor>
static constexpr auto decor_set_v = detail::escape<decor(), 'm'>;

template<decor::type decor>
static constexpr auto decor_reset_v = detail::escape<decor.value + 20, 'm'>;
template<decor::enum_type decor>
static constexpr auto decor_reset_v = detail::escape<decor() + 20, 'm'>;

// Save/restore cursor position;
static constexpr auto cursor_save_v = detail::escape<'s'>;

@@ -237,90 +231,90 @@ static constexpr auto paste_disable_v = detail::escape_literal<"?2004l">;

// Move cursor up/down/frwd/back
static constexpr auto cursor_up(unsigned cnt)
{
return detail::helper::make(cnt, 'A');
return detail::make(cnt, 'A');
}

static constexpr auto cursor_down(unsigned cnt)
{
return detail::helper::make(cnt, 'B');
return detail::make(cnt, 'B');
}

static constexpr auto cursor_frwd(unsigned cnt)
{
return detail::helper::make(cnt, 'C');
return detail::make(cnt, 'C');
}

static constexpr auto cursor_back(unsigned cnt)
{
return detail::helper::make(cnt, 'D');
return detail::make(cnt, 'D');
}

// Move cursor to the next/prev line
static constexpr auto cursor_line_next(unsigned cnt)
{
return detail::helper::make(cnt, 'E');
return detail::make(cnt, 'E');
}

static constexpr auto cursor_line_prev(unsigned cnt)
{
return detail::helper::make(cnt, 'F');
return detail::make(cnt, 'F');
}

// Set cursor to specific column
static constexpr auto cursor_column(unsigned col)
{
return detail::helper::make(col, 'G');
return detail::make(col, 'G');
}

// Erase functions
static constexpr auto erase_display(motion::type motion)
static constexpr auto erase_display(motion::enum_type motion)
{
return detail::helper::make(motion.value, 'J');
return detail::make(motion(), 'J');
}

static constexpr auto erase_line(motion::type motion)
static constexpr auto erase_line(motion::enum_type motion)
{
return detail::helper::make(motion.value, 'K');
return detail::make(motion(), 'K');
}

// Scroll up/down
static constexpr auto scroll_up(unsigned cnt)
{
return detail::helper::make(cnt, 'S');
return detail::make(cnt, 'S');
}

static constexpr auto scroll_down(unsigned cnt)
{
return detail::helper::make(cnt, 'T');
return detail::make(cnt, 'T');
}

// Set cursor to a specific position
static constexpr auto cursor_position(unsigned row, unsigned col)
{
return detail::helper::make(row, ';', col, 'H');
return detail::make(row, ';', col, 'H');
}

// color
// palet colors
static constexpr auto foreground(color::type color)
static constexpr auto foreground(color::enum_type color)
{
return detail::helper::make(color.value + 30, 'm');
return detail::make(color() + 30, 'm');
}

static constexpr auto background(color::type color)
static constexpr auto background(color::enum_type color)
{
return detail::helper::make(color.value + 40, 'm');
return detail::make(color() + 40, 'm');
}

// 256-color palette
static constexpr auto foreground(based::bu8 idx)
{
return detail::helper::make(38, ';', 5, ';', idx, 'm');
return detail::make(38, ';', 5, ';', idx, 'm');
}

static constexpr auto background(based::bu8 idx)
{
return detail::helper::make(48, ';', 5, ';', idx, 'm');
return detail::make(48, ';', 5, ';', idx, 'm');
}

// RGB colors

@@ -328,36 +322,36 @@ static constexpr auto foreground(

based::bu8 red, based::bu8 green, based::bu8 blue
)
{
return detail::helper::make(38, ';', 2, ';', red, ';', green, ';', blue, 'm');
return detail::make(38, ';', 2, ';', red, ';', green, ';', blue, 'm');
}

static constexpr auto background(
based::bu8 red, based::bu8 green, based::bu8 blue
)
{
return detail::helper::make(48, ';', 2, ';', red, ';', green, ';', blue, 'm');
return detail::make(48, ';', 2, ';', red, ';', green, ';', blue, 'm');
}

// Set/reset text decorators
static constexpr auto decor_set(decor::type decor)
static constexpr auto decor_set(decor::enum_type decor)
{
return detail::helper::make(decor.value, 'm');
return detail::make(decor(), 'm');
}

static constexpr auto decor_reset(decor::type decor)
static constexpr auto decor_reset(decor::enum_type decor)
{
return detail::helper::make(decor.value + 20, 'm');
return detail::make(decor() + 20, 'm');
}

// Set screen modes
static constexpr auto screen_mode_set(unsigned mode)
{
return detail::helper::make('=', mode, 'h');
return detail::make('=', mode, 'h');
}

static constexpr auto screen_mode_reset(unsigned mode)
{
return detail::helper::make('=', mode, 'l');
return detail::make('=', mode, 'l');
}

} // namespace alec

diff --git a/ include/alec/terminal.hpp b/ include/alec/terminal.hpp

@@ -4,6 +4,9 @@

#include <optional>
#include <stdexcept>

#include <based/enum/enum.hpp>
#include <based/enum/enum_flag.hpp>
#include <based/types/types.hpp>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>

@@ -20,18 +23,13 @@ public:

}
};

enum error_code_t // NOLINT
{
FDNTERM,
TERMIOSRD,
TERMIOSWR,
BUFFULL,
CHARRD,
IOCTL,
SCREENSZ
};
#define ENUM_ERROR_CODE \
fdnterm, termiosrd, termioswr, buffull, charrd, ioctl, screensz
BASED_DECLARE_ENUM(error_code_t, based::bi8, 0, ENUM_ERROR_CODE)
BASED_DEFINE_ENUM(error_code_t, based::bi8, 0, ENUM_ERROR_CODE)
#undef ENUM_ERROR_CODE

template<error_code_t e>
template<error_code_t::enum_type e>
class error : public runtime_error
{
public:

@@ -41,26 +39,26 @@ public:

}

private:
static std::string error_get_message(error_code_t error)
static std::string error_get_message(error_code_t::enum_type error)
{
switch (error) {
case error_code_t::FDNTERM:
switch (error()) {
case error_code_t::fdnterm():
return "File descriptor is not associated with a terminal";
case error_code_t::TERMIOSRD:
case error_code_t::termiosrd():
return "Can't read termios";
case error_code_t::TERMIOSWR:
case error_code_t::termioswr():
return "Can't write termios";
case error_code_t::BUFFULL:
case error_code_t::buffull():
return "Buffer is full";
case error_code_t::CHARRD:
case error_code_t::charrd():
return "Can't read character";
case error_code_t::IOCTL:
case error_code_t::ioctl():
return "ioctl error";
case error_code_t::SCREENSZ:
case error_code_t::screensz():
return "Can't determine the screen size";
default:
return "alec error, should not happen...";
}

return "alec error, should not happen...";
}
};

@@ -72,11 +70,11 @@ public:

, m_orig_termios()
{
if (isatty(m_fd) == 0) {
throw error<error_code_t::FDNTERM>();
throw error<error_code_t::fdnterm>();
}

if (tcgetattr(m_fd, &m_orig_termios) == -1) {
throw error<error_code_t::TERMIOSRD>();
throw error<error_code_t::termiosrd>();
}

struct termios raw = m_orig_termios;

@@ -92,7 +90,7 @@ public:


/* put terminal in raw mode after flushing */
if (tcsetattr(m_fd, TCSAFLUSH, &raw) < 0) {
throw error<error_code_t::TERMIOSWR>();
throw error<error_code_t::termioswr>();
}
}

@@ -137,7 +135,7 @@ private:

{
ssize_t scnt = -1;
if ((m_end + 1) % m_buffer.size() == m_start) {
throw error<error_code_t::BUFFULL>();
throw error<error_code_t::buffull>();
}

if (m_start <= m_end) {

@@ -147,7 +145,7 @@ private:

}

if (scnt == -1) {
throw error<error_code_t::CHARRD>();
throw error<error_code_t::charrd>();
}

const auto cnt = static_cast<size_t>(scnt);

@@ -191,61 +189,60 @@ inline std::pair<std::uint16_t, std::uint16_t> get_screen_size()

#elif defined(TIOCGWINSZ)
struct winsize tts = {};
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &tts) == -1) { // NOLINT
throw error<error_code_t::IOCTL>();
throw error<error_code_t::ioctl>();
}
return {tts.ws_col, tts.ws_row};
#endif /* TIOCGSIZE */

throw error<error_code_t::SCREENSZ>();
throw error<error_code_t::screensz>();
}

#define ENUM_EVENT_TYPE none, key, resize, mouse
#define ENUM_EVENT_MOD none, alt, ctrl, shift, motion

class event
{
public:
enum class Type : std::uint8_t
{
NONE = 0,
KEY = 1,
RESIZE = 2,
MOUSE = 3,
};
BASED_DECLARE_ENUM(type, based::bu8, 0, ENUM_EVENT_TYPE)
BASED_DECLARE_ENUM_FLAG(mod, based::bu8, ENUM_EVENT_MOD)

enum class Mod : std::uint8_t
{
ALT = 1,
CTRL = 2,
SHIFT = 4,
MOTION = 8,
};

event(Type type, uint8_t mod_mask, uint8_t key) // NOLINT
event(type::enum_type type, uint8_t mod_mask, uint8_t key) // NOLINT
: m_type(type)
, m_mod_mask(mod_mask)
, m_key(key)
{
}

const auto& type() const { return m_type; }
auto& type() { return m_type; }
[[nodiscard]] const auto& type() const { return m_type; }
[[nodiscard]] auto& type() { return m_type; }

const auto& key() const { return m_key; }
auto& key() { return m_key; }
[[nodiscard]] const auto& key() const { return m_key; }
[[nodiscard]] auto& key() { return m_key; }

const auto& mod_mask() const { return m_mod_mask; }
auto& mod_mask() { return m_mod_mask; }
[[nodiscard]] const auto& mod_mask() const { return m_mod_mask; }
[[nodiscard]] auto& mod_mask() { return m_mod_mask; }

bool is_set(uint8_t mask) const { return mask == (m_mod_mask & mask); }
[[nodiscard]] bool is_set(uint8_t mask) const
{
return mask == (m_mod_mask & mask);
}

private:
Type m_type = Type::NONE;
type::enum_type m_type = type::none;
uint8_t m_mod_mask = 0;
uint8_t m_key = 0;
};

BASED_DEFINE_ENUM_FLAG_CLASS(event, mod, based::bu8, ENUM_EVENT_MOD)
#undef ENUM_EVENT_MOD

BASED_DEFINE_ENUM_CLASS(event, type, based::bu8, 0, ENUM_EVENT_TYPE)
#undef ENUM_EVENT_TYPE

inline event get_event()
{
const auto chr = get_buffer().value().read();
return {chr != 0 ? event::Type::KEY : event::Type::NONE, 0, chr};
return {chr != 0 ? event::type::key : event::type::none, 0, chr};
}

} // namespace alec