Created
May 4, 2022 21:34
-
-
Save dekrain/2b960c791e60228a3b80e98940b06904 to your computer and use it in GitHub Desktop.
Awful little counter management program
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** %pmake **/ | |
/* { "target": "counters", "c++ver": "c++17" } */ | |
/* | |
Entry with _ means unimplemented, + means implemented. | |
Controls: | |
+ ArrowDown = next entry | |
+ ArrowUp = previous entry | |
+ ArrowRight = increase counter | |
+ ArrowLeft = decrease counter | |
+ Q = quit | |
+ S = save to file | |
+ L = load from file | |
+ N = insert before and switch to edit mode | |
+ O = insert before empty | |
+ Enter = switch to edit mode | |
+ R = remove entry (confirm if not empty) | |
+ P = make current entry empty (confirm if not empty) | |
+ PageDown = move entry down | |
+ PageUp = move entry up | |
+ ? = safe mode (no external IO) | |
-BONUS-: | |
+ / = show help (requires source) | |
Edit mode / String input: | |
+ Enter = exit edit mode | |
+ ArrowRight = move forward | |
+ ArrowLeft = move backward | |
+ Home = move to start | |
+ End = move to end | |
+ Backspace = remove previous character | |
+ Delete = remove current character | |
+ [char] = insert character | |
*/ | |
#include <cstdlib> | |
#include <cstdio> | |
#include <cstdint> | |
#include <cctype> | |
#include <cinttypes> | |
#include <cstring> | |
#include <string> | |
#include <vector> | |
#include <charconv> | |
#include <signal.h> | |
#include <termios.h> | |
using zstring = char const*; | |
struct string_buffer : std::string { | |
using std::string::string; | |
void copy(size_t dst, size_t src, size_t count) { | |
std::memmove(data() + dst, data() + src, count * sizeof(value_type)); | |
} | |
void copy(size_t dst, std::string_view view) { | |
std::memcpy(data() + dst, view.data(), view.size() * sizeof(value_type)); | |
} | |
}; | |
static struct termios s_old_mode; | |
void disableRawMode(void) { | |
tcsetattr(STDIN_FILENO, TCSAFLUSH, &s_old_mode); | |
} | |
void enableRawMode(void) { | |
tcgetattr(STDIN_FILENO, &s_old_mode); | |
struct termios raw = s_old_mode; | |
raw.c_lflag &= ~(ECHO | ICANON | ISIG); | |
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); | |
std::atexit(disableRawMode); | |
} | |
/** | |
* Try to read one line from file | |
* @param fin File to read from | |
* @param line String that will contain the result | |
* @returns true on success, false on failure, e.g. end of file | |
*/ | |
bool betterReadLine(std::FILE* fin, std::string& line) { | |
line.clear(); | |
line.reserve(0x20); | |
int ch; | |
for (ch = std::fgetc(fin); ch != '\n' and ch != EOF; ch = std::fgetc(fin)) { | |
line += static_cast<char>(ch); | |
} | |
return ch != EOF; | |
} | |
struct Counter { | |
std::string name; | |
int32_t value; | |
Counter() : value(0) {} | |
Counter(std::string&& name, int32_t init = 0) : name(std::move(name)), value(init) {} | |
Counter(Counter&&) = default; | |
explicit Counter(Counter const&) = default; | |
Counter& operator=(Counter&&) = default; | |
explicit operator bool() const { return !name.empty(); } | |
}; | |
std::vector<Counter> gCounters; | |
uint32_t gCurrentEntry = 0; | |
bool currentActive() { | |
return gCurrentEntry < gCounters.size() and gCounters[gCurrentEntry]; | |
} | |
std::string gLastFilename; | |
bool gSafeMode = false; | |
static char gErrorBuf[0x200]; | |
static char const cErrorOpenFile[] = "Unable to open file"; | |
static char const cErrorSafeMode[] = "Safe mode is enabled"; | |
static char const cErrorNoHelp[] = "No help file (counters.cpp) available"; | |
char const* writeError(int errnum, char const* error, size_t errorLen) { | |
std::memcpy(gErrorBuf, error, errorLen); | |
size_t errPos = errorLen; | |
gErrorBuf[errPos++] = ':'; | |
gErrorBuf[errPos++] = ' '; | |
strerror_r(errnum, gErrorBuf, sizeof gErrorBuf - errPos); | |
return gErrorBuf; | |
} | |
void writeString(std::string_view text, std::FILE* f = stdout) { | |
std::fwrite(text.data(), sizeof(std::string_view::value_type), text.size(), f); | |
} | |
void loadCountersFromFile(zstring fname, void (*error)(zstring text)) { | |
if (gSafeMode) { | |
error(cErrorSafeMode); | |
return; | |
} | |
std::FILE* f = std::fopen(fname, "r"); | |
if (!f) { | |
error(writeError(errno, cErrorOpenFile, sizeof cErrorOpenFile - 1)); | |
return; | |
} | |
std::string line; | |
while (betterReadLine(f, line)) { | |
auto sep = line.find('='); | |
int32_t count = 0; | |
if (sep != std::string::npos) { | |
auto num = sep + 1; | |
while (num < line.size() and std::isblank(line[num])) | |
++num; | |
auto pos = std::from_chars(line.data() + num, line.data() + line.size(), count); | |
if (pos.ec != std::errc()) { | |
std::fputs("Bad number format\n", stderr); | |
std::exit(1); | |
} | |
while (sep > 0 and std::isblank(line[sep-1])) | |
--sep; | |
line.erase(sep); | |
} | |
gCounters.emplace_back(std::move(line), count); | |
} | |
} | |
void saveCountersToFile(zstring fname, void (*error)(zstring text)) { | |
if (gSafeMode) { | |
error(cErrorSafeMode); | |
return; | |
} | |
std::FILE* f = std::fopen(fname, "w"); | |
if (!f) { | |
error(writeError(errno, cErrorOpenFile, sizeof cErrorOpenFile - 1)); | |
return; | |
} | |
for (auto const& counter : gCounters) { | |
if (!counter) { | |
std::fputc('\n', f); | |
continue; | |
} | |
writeString(counter.name, f); | |
std::fprintf(f, " = %" PRId32 "\n", counter.value); | |
} | |
} | |
void moveCursorTo(uint32_t x, uint32_t y) { | |
std::printf("\e[%" PRIu32 ";%" PRIu32 "H", y + 1, x + 1); | |
std::fflush(stdout); | |
} | |
void getCursorPosition(uint32_t& x, uint32_t& y) { | |
std::fputs("\e[6n", stdout); | |
std::fflush(stdout); | |
while (std::fgetc(stdin) != 0x1B) | |
; | |
if (std::fgetc(stdin) != '[') | |
std::exit(2); | |
char buf[16]; | |
char* p = buf; | |
while (p < std::end(buf) and (*p = std::fgetc(stdin)) != 'R') | |
++p; | |
if (p == std::end(buf)) | |
std::exit(2); | |
char* sep = reinterpret_cast<char*>(std::memchr(buf, ';', p - buf)); | |
if (!sep) | |
std::exit(2); | |
auto res = std::from_chars(buf, sep, y); | |
if (res.ec != std::errc() or res.ptr != sep) | |
std::exit(2); | |
--y; | |
res = std::from_chars(sep + 1, p, x); | |
if (res.ec != std::errc() or res.ptr != p) | |
std::exit(2); | |
--x; | |
} | |
void clearLine() { | |
std::fputs("\e[2K", stdout); | |
std::fflush(stdout); | |
} | |
void clearLineForward() { | |
std::fputs("\e[0K", stdout); | |
std::fflush(stdout); | |
} | |
void clearScreen() { | |
std::fputs("\e[H\e[2J", stdout); | |
std::fflush(stdout); | |
} | |
void updateCounter(uint32_t idx) { | |
auto& counter = gCounters[idx]; | |
moveCursorTo(0, idx); | |
clearLine(); | |
if (counter) { | |
std::fwrite(counter.name.data(), 1, counter.name.size(), stdout); | |
std::printf(" = %" PRId32, counter.value); | |
} | |
} | |
void updateEntryCursor() { | |
if (currentActive()) { | |
// Put the cursor on '=' | |
moveCursorTo(gCounters[gCurrentEntry].name.size() + 1, gCurrentEntry); | |
} else { | |
// Put on the start | |
moveCursorTo(0, gCurrentEntry); | |
} | |
} | |
void updateEntries() { | |
for (uint32_t idx = 0, cnt = gCounters.size(); idx != cnt; ++idx) | |
updateCounter(idx); | |
updateEntryCursor(); | |
} | |
void errorTuiStart(char const* text) { | |
moveCursorTo(0, gCounters.size()); | |
std::fputs(text, stdout); | |
} | |
void errorTuiWait() { | |
std::fflush(stdout); | |
std::fgetc(stdin); | |
clearLine(); | |
updateEntryCursor(); | |
} | |
void errorTui(char const* text) { | |
errorTuiStart(text); | |
errorTuiWait(); | |
} | |
bool confirmAction() { | |
moveCursorTo(0, gCounters.size()); | |
std::fputs("Confirm action? [Ny] ", stdout); | |
uint8_t res = 0; | |
do switch (std::fgetc(stdin)) { | |
case 'N': | |
case 'n': | |
case 0x0A: | |
res = 1; | |
break; | |
case 'Y': | |
case 'y': | |
res = 2; | |
break; | |
} while (!res); | |
clearLine(); | |
updateEntryCursor(); | |
return res == 2; | |
} | |
void showHelp() { | |
std::FILE* f = std::fopen("counters.cpp", "r"); | |
if (!f) { | |
errorTui(cErrorNoHelp); | |
return; | |
} | |
std::string line; | |
enum { | |
SEnd, | |
SSearch, | |
SSection, | |
SEntry, | |
} state = SSearch; | |
while (state != SEnd and betterReadLine(f, line)) { | |
if (line.size() and line.back() == '\n') | |
line.pop_back(); | |
lReparse: | |
switch (state) { | |
case SEnd: | |
break; | |
case SSearch: | |
if (line == "/*") { | |
state = SSection; | |
clearScreen(); | |
} | |
break; | |
case SSection: | |
if (line == "*/") { | |
lEnd: | |
state = SEnd; | |
break; | |
} | |
if (!line.empty()) { | |
std::fputs("\e[96m", stdout); | |
std::fwrite(line.data(), 1, line.size(), stdout); | |
if (line.back() == ':') | |
state = SEntry; | |
} | |
lNl: | |
std::fputc('\n', stdout); | |
break; | |
case SEntry: | |
if (line.empty()) { | |
state = SSection; | |
goto lNl; | |
} | |
if (line.front() == '+') { | |
std::fputs("\e[92m", stdout); | |
} else if (line.front() == '_') { | |
std::fputs("\e[91m", stdout); | |
} else { | |
state = SSection; | |
goto lReparse; | |
} | |
std::fputc(line.front(), stdout); | |
size_t pos = 1, sep; | |
while (pos < line.size() and std::isblank(line[pos])) | |
++pos; | |
sep = line.find_first_of(" \t", pos); | |
if (sep != std::string::npos) { | |
std::fputs(" \e[93m", stdout); | |
std::fwrite(line.data() + pos, 1, sep - pos, stdout); | |
while (sep < line.size() and std::isblank(line[sep])) | |
++sep; | |
} else | |
sep = pos; | |
std::fputs(" \e[97m", stdout); | |
std::fwrite(line.data() + sep, 1, line.size() - sep, stdout); | |
goto lNl; | |
} | |
} | |
if (state == SSearch) { | |
errorTui(cErrorNoHelp); | |
return; | |
} | |
std::fgetc(stdin); | |
std::fputs("\e[0m", stdout); | |
clearScreen(); | |
updateEntries(); | |
} | |
std::string inputString(zstring prompt, std::string_view init={}); | |
void insertOne() { | |
gCounters.insert(gCounters.begin() + gCurrentEntry, Counter{}); | |
updateEntries(); | |
} | |
void removeEntry() { | |
if (gCurrentEntry >= gCounters.size()) | |
return; | |
if (currentActive() and !confirmAction()) | |
return; | |
gCounters.erase(gCounters.begin() + gCurrentEntry); | |
// Update view and clear last line | |
updateEntries(); | |
moveCursorTo(0, gCounters.size()); | |
clearLine(); | |
updateEntryCursor(); | |
} | |
void blankEntry() { | |
if (!currentActive() or !confirmAction()) | |
return; | |
gCounters[gCurrentEntry] = {}; | |
updateCounter(gCurrentEntry); | |
} | |
void moveEntry(int32_t dir) { | |
// if (!dir) | |
// return; | |
if (gCurrentEntry >= gCounters.size()) | |
return; | |
int32_t targetEntry = gCurrentEntry + dir; | |
if (targetEntry < 0 or targetEntry >= gCounters.size()) | |
return; | |
std::swap(gCounters[gCurrentEntry], gCounters[targetEntry]); | |
updateCounter(gCurrentEntry); | |
updateCounter(targetEntry); | |
gCurrentEntry = targetEntry; | |
updateEntryCursor(); | |
} | |
void loadFromFile() { | |
gLastFilename = inputString("File name to load: ", gLastFilename); | |
gCounters.clear(); | |
gCurrentEntry = 0; | |
loadCountersFromFile(gLastFilename.c_str(), errorTui); | |
updateEntries(); | |
} | |
void saveToFile() { | |
gLastFilename = inputString("File name to save: ", gLastFilename); | |
saveCountersToFile(gLastFilename.c_str(), errorTui); | |
} | |
enum class SpecialChar : int16_t { | |
ArrowUp = -1, | |
ArrowDown = -2, | |
ArrowRight = -3, | |
ArrowLeft = -4, | |
Backspace = -5, | |
Delete = -6, | |
Enter = -7, | |
Home = -8, | |
End = -9, | |
PageUp = -10, | |
PageDown = -11, | |
}; | |
struct InputChar { | |
int16_t value; | |
constexpr operator int16_t() const && { return value; } | |
constexpr operator int16_t&() & { return value; } | |
constexpr operator int16_t const&() const & { return value; } | |
constexpr InputChar(int16_t v = 0) : value(v) {} | |
constexpr InputChar(char v) : value((int16_t)(uint8_t)v) {} | |
constexpr InputChar(SpecialChar v) : value((int16_t)v) {} | |
}; | |
struct InputEntry { | |
InputChar ch; | |
void (*fun)(); | |
}; | |
extern InputEntry const gModeNormal[]; | |
extern InputEntry const gModeEdit[]; | |
InputEntry const* gMode = gModeNormal; | |
InputChar gInputChar; | |
void handleInput(); | |
void enterEditMode(); | |
InputEntry const gModeNormal[] { | |
{SpecialChar::ArrowUp, [] { if (gCurrentEntry == 0) gCurrentEntry = gCounters.size(); else --gCurrentEntry; updateEntryCursor(); }}, | |
{SpecialChar::ArrowDown, [] { if (gCurrentEntry == gCounters.size()) gCurrentEntry = 0; else ++gCurrentEntry; updateEntryCursor(); }}, | |
{SpecialChar::ArrowRight, [] { if (currentActive()) ++gCounters[gCurrentEntry].value; updateCounter(gCurrentEntry); }}, | |
{SpecialChar::ArrowLeft, [] { if (currentActive()) --gCounters[gCurrentEntry].value; updateCounter(gCurrentEntry); }}, | |
{'q', [] { gMode = nullptr; }}, | |
{'s', saveToFile}, | |
{'l', loadFromFile}, | |
{'n', [] { insertOne(); enterEditMode(); }}, | |
{'o', [] { insertOne(); }}, | |
{SpecialChar::Enter, [] { enterEditMode(); moveCursorTo(0, gCurrentEntry); }}, | |
{'r', [] { removeEntry(); }}, | |
{'p', [] { blankEntry(); }}, | |
{SpecialChar::PageUp, [] { moveEntry(-1); }}, | |
{SpecialChar::PageDown, [] { moveEntry(1); }}, | |
{'?', [] { if (!confirmAction()) return; gSafeMode = true; errorTui("Entered safe mode"); }}, | |
{'/', showHelp}, | |
{} | |
}; | |
struct EditContext { | |
std::string suffix; | |
string_buffer buf; | |
uint32_t pos, len, xpos, ypos; | |
bool (*onInsertCharacter)(char ch); | |
void (*onExit)(std::string&& res); | |
explicit operator bool() const { return len == ~static_cast<uint32_t>(0); } | |
void open(uint32_t x, uint32_t y, std::string_view init, std::string suffix_={}) { | |
suffix = std::move(suffix_); | |
pos = 0; | |
xpos = x; | |
ypos = y; | |
len = init.size(); | |
buf.resize((15 + len) & ~15); | |
buf.copy(0, init); | |
moveCursorTo(xpos, ypos); | |
clearLineForward(); | |
bool moved = false; | |
if (!init.empty()) { | |
writeString(init); | |
moved = true; | |
} | |
if (!suffix.empty()) { | |
writeString(suffix); | |
moved = true; | |
} | |
if (moved) | |
moveCursorTo(xpos, ypos); | |
} | |
void close() { | |
if (!*this) | |
return; | |
pos = xpos = ypos = 0; | |
len = ~static_cast<uint32_t>(0); | |
buf.clear(); | |
suffix.clear(); | |
onInsertCharacter = nullptr; | |
onExit = nullptr; | |
} | |
void exit() { | |
buf.resize(len); | |
onExit(std::move(buf)); | |
close(); | |
} | |
void grow(uint32_t delta) { | |
buf.reserve(len += delta); | |
if (buf.capacity() != buf.size()) | |
buf.resize(buf.capacity()); | |
} | |
} editContext; | |
void enterEditMode() { | |
if (editContext) | |
return; | |
if (gCurrentEntry >= gCounters.size()) { | |
gCurrentEntry = gCounters.size(); | |
gCounters.push_back(Counter{}); | |
} | |
Counter& cnt = gCounters[gCurrentEntry]; | |
std::string suffix; | |
if (cnt) { | |
suffix.append(" = ").append(std::to_string(cnt.value)); | |
} | |
editContext.open(0, gCurrentEntry, cnt.name, std::move(suffix)); | |
editContext.onExit = [] (std::string&& name) { | |
gMode = gModeNormal; | |
if (!gCounters[gCurrentEntry]) | |
gCounters[gCurrentEntry].value = 0; | |
gCounters[gCurrentEntry].name = std::move(name); | |
updateCounter(gCurrentEntry); | |
}; | |
editContext.onInsertCharacter = [] (char ch) { | |
return ch != '='; | |
}; | |
gMode = gModeEdit; | |
} | |
std::string inputString(zstring prompt, std::string_view init) { | |
if (editContext) | |
std::abort(); | |
moveCursorTo(0, gCounters.size()); | |
std::fputs(prompt, stdout); | |
std::fflush(stdout); | |
uint32_t x, y; | |
getCursorPosition(x, y); | |
static std::string res; | |
InputEntry const* prevMode = gMode; | |
res.clear(); | |
editContext.open(x, y, init); | |
editContext.onExit = [] (std::string&& r) { gMode = nullptr; res = std::move(r); }; | |
gMode = gModeEdit; | |
while (gMode) | |
handleInput(); | |
gMode = prevMode; | |
clearLine(); | |
updateEntryCursor(); | |
return std::move(res); | |
} | |
void editCursor() { | |
moveCursorTo(editContext.xpos + editContext.pos, editContext.ypos); | |
} | |
void editPaint() { | |
moveCursorTo(editContext.xpos, editContext.ypos); | |
clearLineForward(); | |
std::fwrite(editContext.buf.data(), sizeof(string_buffer::value_type), editContext.len, stdout); | |
if (!editContext.suffix.empty()) | |
std::fwrite(editContext.suffix.data(), sizeof(std::string::value_type), editContext.suffix.size(), stdout); | |
moveCursorTo(editContext.xpos + editContext.pos, editContext.ypos); | |
} | |
void editRight() { | |
if (editContext.pos >= editContext.len) | |
return; | |
++editContext.pos; | |
editCursor(); | |
} | |
void editLeft() { | |
if (editContext.pos == 0) | |
return; | |
--editContext.pos; | |
editCursor(); | |
} | |
void editHome() { | |
if (editContext.pos == 0) | |
return; | |
editContext.pos = 0; | |
editCursor(); | |
} | |
void editEnd() { | |
if (editContext.pos == editContext.len) | |
return; | |
editContext.pos = editContext.len; | |
editCursor(); | |
} | |
void editRemovePrevious() { | |
if (editContext.pos == 0) | |
return; | |
editContext.buf.copy(editContext.pos - 1, editContext.pos, editContext.len - editContext.pos); | |
--editContext.pos; | |
--editContext.len; | |
editPaint(); | |
} | |
void editRemoveCurrent() { | |
if (editContext.pos == editContext.len) | |
return; | |
editContext.buf.copy(editContext.pos, editContext.pos + 1, editContext.len - editContext.pos - 1); | |
--editContext.len; | |
editPaint(); | |
} | |
void editInsertCharacter() { | |
// Ignore non-text characters | |
if (gInputChar < 0x20 or gInputChar >= 0x7F) | |
return; | |
if (editContext.onInsertCharacter and not editContext.onInsertCharacter(gInputChar)) | |
return; | |
// [012_345] | |
// pos = 3; len = 6 | |
editContext.grow(1); | |
// [012_345%] | |
++editContext.pos; | |
// [0123_45%] | |
// pos = 4; len = 7 | |
editContext.buf.copy(editContext.pos, editContext.pos - 1, editContext.len - editContext.pos); | |
// [0123_345] | |
editContext.buf[editContext.pos - 1] = gInputChar; | |
// [012I_345] | |
// pos = 4 | |
editPaint(); | |
} | |
InputEntry const gModeEdit[] { | |
{SpecialChar::Enter, [] { editContext.exit(); }}, | |
{SpecialChar::ArrowRight, editRight}, | |
{SpecialChar::ArrowLeft, editLeft}, | |
{SpecialChar::Home, editHome}, | |
{SpecialChar::End, editEnd}, | |
{SpecialChar::Backspace, editRemovePrevious}, | |
{SpecialChar::Delete, editRemoveCurrent}, | |
{{}, editInsertCharacter} | |
}; | |
void handleInput(InputChar character) { | |
if (!gMode) | |
return; | |
gInputChar = character; | |
InputEntry const* entry; | |
for (entry = gMode; entry->ch; ++entry) { | |
if (entry->ch != character) | |
continue; | |
if (entry->fun) | |
entry->fun(); | |
return; | |
} | |
if (entry->fun) | |
entry->fun(); | |
} | |
bool isescterm(uint8_t ch) { | |
return ch >= 0x40 and ch <= 0x7E; | |
} | |
void handleInput() { | |
SpecialChar spech {}; | |
int ch = std::fgetc(stdin); | |
if (ch == EOF or ch == 0x03 /* ^C */) { | |
gMode = nullptr; | |
return; | |
} | |
if (ch != 0x1B) { | |
switch (ch) { | |
case 0x7F: | |
return handleInput(SpecialChar::Backspace); | |
case 0x0A: | |
return handleInput(SpecialChar::Enter); | |
default: | |
return handleInput((char)ch); | |
} | |
} | |
char buf[16]; | |
char* p = buf; | |
// Handle '[' as start seperately | |
*p++ = std::fgetc(stdin); | |
while (p != std::end(buf) and !isescterm(*p = std::fgetc(stdin))) | |
++p; | |
if (p != std::end(buf)) | |
ch = *p; | |
else do | |
ch = std::fgetc(stdin); | |
while (!isescterm(ch)); | |
// Check known sequences | |
if (buf[0] == '[' and p - buf >= 1) { | |
switch (buf[1]) { | |
case 'A': | |
spech = SpecialChar::ArrowUp; break; | |
case 'B': | |
spech = SpecialChar::ArrowDown; break; | |
case 'C': | |
spech = SpecialChar::ArrowRight; break; | |
case 'D': | |
spech = SpecialChar::ArrowLeft; break; | |
case 'F': | |
spech = SpecialChar::End; break; | |
case 'H': | |
spech = SpecialChar::Home; break; | |
} | |
if (spech != SpecialChar{}) | |
return handleInput(spech); | |
if (ch == '~') { | |
uint32_t idx = 0; | |
if (p - buf >= 2 and std::isdigit(buf[1])) { | |
idx = buf[1] - '0'; | |
if (p - buf >= 3 and std::isdigit(buf[2])) { | |
idx *= 10; | |
idx += buf[2] - '0'; | |
if (p - buf >= 4) | |
idx = -1; | |
} | |
} | |
switch (idx) { | |
case 1: | |
case 7: | |
spech = SpecialChar::Home; break; | |
case 3: | |
spech = SpecialChar::Delete; break; | |
case 4: | |
case 8: | |
spech = SpecialChar::End; break; | |
case 5: | |
spech = SpecialChar::PageUp; break; | |
case 6: | |
spech = SpecialChar::PageDown; break; | |
} | |
if (spech != SpecialChar{}) | |
return handleInput(spech); | |
} | |
} | |
errorTuiStart("Unknown escape sequence: \""); | |
for (zstring rp = buf; rp <= p and rp != std::end(buf); ++rp) { | |
uint8_t ch = *rp; | |
if (ch >= 0x20 and ch <= 0x7E and ch != '"') | |
std::fputc(ch, stdout); | |
else | |
std::printf("\\x%02" PRIX8, ch); | |
} | |
if (p == std::end(buf)) | |
std::fputs("...", stdout); | |
std::fputc('"', stdout); | |
errorTuiWait(); | |
} | |
void errorExit(char const* text) { | |
std::fputs(text, stderr); | |
std::exit(1); | |
} | |
int main(int argc, char** argv) { | |
enableRawMode(); | |
if (argc >= 2) { | |
gLastFilename = argv[1]; | |
loadCountersFromFile(argv[1], errorExit); | |
} | |
clearScreen(); | |
updateEntries(); | |
while (gMode) | |
handleInput(); | |
moveCursorTo(0, gCounters.size()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment