5800 - move `browse_trace` to `tools/` dir
This commit is contained in:
parent
9e45cae061
commit
f821c0e28b
|
@ -77,7 +77,7 @@ messages.
|
|||
42
|
||||
```
|
||||
|
||||
Emulated runs can generate a trace that permits [time-travel debugging](https://github.com/akkartik/mu/blob/master/browse_trace/Readme.md).
|
||||
Emulated runs can generate a trace that permits [time-travel debugging](https://github.com/akkartik/mu/blob/master/tools/browse_trace.readme.md).
|
||||
|
||||
```sh
|
||||
$ ./subx --debug translate init.linux examples/factorial.subx -o examples/factorial
|
||||
|
@ -87,7 +87,7 @@ Emulated runs can generate a trace that permits [time-travel debugging](https://
|
|||
$ ./subx --debug --trace run examples/factorial
|
||||
saving trace to 'last_run'
|
||||
|
||||
$ ./browse_trace/browse_trace last_run # text-mode debugger UI
|
||||
$ tools/browse_trace last_run # text-mode debugger UI
|
||||
```
|
||||
|
||||
You can write tests for your programs. The entire stack is thoroughly covered
|
||||
|
@ -542,7 +542,7 @@ rudimentary but hopefully still workable toolkit:
|
|||
layer. It makes the trace a lot more verbose and a lot less dense, necessitating
|
||||
a lot more scrolling around, so I keep it turned off most of the time.
|
||||
|
||||
* If the trace seems overwhelming, try [browsing it](https://github.com/akkartik/mu/blob/master/browse_trace/Readme.md)
|
||||
* If the trace seems overwhelming, try [browsing it](https://github.com/akkartik/mu/blob/master/tools/browse_trace.readme.md)
|
||||
in the 'time-travel debugger'.
|
||||
|
||||
Hopefully these hints are enough to get you started. The main thing to
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
cd `dirname $0`
|
||||
./build
|
||||
cd -
|
||||
`dirname $0`/browse_trace_bin $*
|
4
clean
4
clean
|
@ -6,7 +6,7 @@ rm -rf subx.cc subx_bin* *_list
|
|||
rm -rf .until
|
||||
test $# -gt 0 && exit 0 # convenience: 'clean top-level' to leave subsidiary tools alone
|
||||
rm -rf tools/enumerate tangle/tangle tangle/*_list */*.dSYM
|
||||
rm -rf browse_trace/browse_trace_bin browse_trace/*_list
|
||||
rm -rf tools/treeshake tools/linkify tools/*.dSYM
|
||||
rm -rf tools/browse_trace_bin tools/treeshake tools/linkify tools/*.dSYM
|
||||
rm -rf tools/termbox/*.o tools/termbox/libtermbox.a
|
||||
rm -rf tmp_linux mu_linux.iso outfs initrd.fat mu_soso.iso
|
||||
( cd kernel.soso && make clean; )
|
||||
|
|
|
@ -12,6 +12,8 @@ These are built automatically.
|
|||
|
||||
These are built lazily.
|
||||
|
||||
* `browse_trace`: debugging tool. See `browse_trace.readme.md` for details.
|
||||
|
||||
* `linkify`: inserts hyperlinks from variables to definitions in Mu's html
|
||||
sources. Hacky; just see the number of tests. Invoked by `update_html`.
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
test "$CXX" || export CXX=c++
|
||||
test "$CC" || export CC=cc
|
||||
test "$CFLAGS" || export CFLAGS="-g -O2"
|
||||
export CFLAGS="$CFLAGS -Wall -Wextra -ftrapv -fno-strict-aliasing"
|
||||
|
||||
# build if doesn't exist
|
||||
[ ! -f `dirname $0`/browse_trace_bin ] && (
|
||||
cd `dirname $0`
|
||||
[ ! -f termbox/libtermbox.a ] && (
|
||||
cd termbox
|
||||
$CC $CFLAGS -c utf8.c
|
||||
$CC $CFLAGS -c termbox.c
|
||||
ar rcs libtermbox.a *.o
|
||||
)
|
||||
$CXX $CFLAGS browse_trace.cc termbox/libtermbox.a -o browse_trace_bin
|
||||
)
|
||||
|
||||
`dirname $0`/browse_trace_bin $*
|
|
@ -52,12 +52,6 @@ struct trace_stream {
|
|||
enum search_direction { FORWARD, BACKWARD };
|
||||
// End Types
|
||||
|
||||
// Function prototypes are auto-generated in the 'build*' scripts; define your
|
||||
// functions in any order. Just be sure to declare each function header all on
|
||||
// one line, ending with the '{'. Our auto-generation scripts are too minimal
|
||||
// and simple-minded to handle anything else.
|
||||
#include "function_list" // by convention, files ending with '_list' are auto-generated
|
||||
|
||||
// from http://stackoverflow.com/questions/152643/idiomatic-c-for-reading-from-a-const-map
|
||||
template<typename T> typename T::mapped_type& get(T& map, typename T::key_type const& key) {
|
||||
typename T::iterator iter(map.find(key));
|
||||
|
@ -101,6 +95,280 @@ bool has_data(istream& in) {
|
|||
return in && !in.eof();
|
||||
}
|
||||
|
||||
void skip_whitespace_but_not_newline(istream& in) {
|
||||
while (true) {
|
||||
if (!has_data(in)) break;
|
||||
else if (in.peek() == '\n') break;
|
||||
else if (isspace(in.peek())) in.get();
|
||||
else break;
|
||||
}
|
||||
}
|
||||
|
||||
void load_trace(const char* filename) {
|
||||
ifstream tin(filename);
|
||||
if (!tin) {
|
||||
cerr << "no such file: " << filename << '\n';
|
||||
exit(1);
|
||||
}
|
||||
Trace_stream = new trace_stream;
|
||||
while (has_data(tin)) {
|
||||
tin >> std::noskipws;
|
||||
skip_whitespace_but_not_newline(tin);
|
||||
if (!isdigit(tin.peek())) {
|
||||
string dummy;
|
||||
getline(tin, dummy);
|
||||
continue;
|
||||
}
|
||||
tin >> std::skipws;
|
||||
int depth;
|
||||
tin >> depth;
|
||||
string label;
|
||||
tin >> label;
|
||||
if (*--label.end() == ':') label.erase(--label.end());
|
||||
string line;
|
||||
getline(tin, line);
|
||||
Trace_stream->past_lines.push_back(trace_line(line, label, depth));
|
||||
}
|
||||
cerr << "lines read: " << Trace_stream->past_lines.size() << '\n';
|
||||
}
|
||||
|
||||
// update Trace_indices for each screen_row on the basis of Top_of_screen and Visible
|
||||
void refresh_screen_rows() {
|
||||
int screen_row = 0, index = 0;
|
||||
Trace_index.clear();
|
||||
for (screen_row = 0, index = Top_of_screen; screen_row < tb_height() && index < SIZE(Trace_stream->past_lines); ++screen_row, ++index) {
|
||||
// skip lines without depth for now
|
||||
while (!contains_key(Visible, index)) {
|
||||
++index;
|
||||
if (index >= SIZE(Trace_stream->past_lines)) goto done;
|
||||
}
|
||||
assert(index < SIZE(Trace_stream->past_lines));
|
||||
put(Trace_index, screen_row, index);
|
||||
}
|
||||
done:;
|
||||
}
|
||||
|
||||
void clear_line(int screen_row) {
|
||||
tb_set_cursor(0, screen_row);
|
||||
for (int col = 0; col < tb_width(); ++col)
|
||||
tb_print(' ', TB_WHITE, TB_BLACK);
|
||||
tb_set_cursor(0, screen_row);
|
||||
}
|
||||
|
||||
int read_key() {
|
||||
tb_event event;
|
||||
do {
|
||||
tb_poll_event(&event);
|
||||
} while (event.type != TB_EVENT_KEY);
|
||||
return event.key ? event.key : event.ch;
|
||||
}
|
||||
|
||||
int lines_hidden(int screen_row) {
|
||||
assert(contains_key(Trace_index, screen_row));
|
||||
if (!contains_key(Trace_index, screen_row+1))
|
||||
return SIZE(Trace_stream->past_lines) - get(Trace_index, screen_row);
|
||||
else
|
||||
return get(Trace_index, screen_row+1) - get(Trace_index, screen_row);
|
||||
}
|
||||
|
||||
bool in_range(const vector<pair<size_t, size_t> >& highlight_ranges, size_t idx) {
|
||||
for (int i = 0; i < SIZE(highlight_ranges); ++i) {
|
||||
if (idx >= highlight_ranges.at(i).first && idx < highlight_ranges.at(i).second)
|
||||
return true;
|
||||
if (idx < highlight_ranges.at(i).second) break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
vector<pair<size_t, size_t> > find_all_occurrences(const string& s, const string& pat) {
|
||||
vector<pair<size_t, size_t> > result;
|
||||
if (pat.empty()) return result;
|
||||
size_t idx = 0;
|
||||
while (true) {
|
||||
size_t next_idx = s.find(pat, idx);
|
||||
if (next_idx == string::npos) break;
|
||||
result.push_back(pair<size_t, size_t>(next_idx, next_idx+SIZE(pat)));
|
||||
idx = next_idx+SIZE(pat);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void render_line(int screen_row, const string& s, bool cursor_line) {
|
||||
int col = 0;
|
||||
int color = TB_WHITE;
|
||||
int background_color = cursor_line ? /*subtle grey*/240 : TB_BLACK;
|
||||
vector<pair<size_t, size_t> > highlight_ranges = find_all_occurrences(s, Current_search_pattern);
|
||||
tb_set_cursor(0, screen_row);
|
||||
for (col = 0; col < tb_width() && col+Left_of_screen < SIZE(s); ++col) {
|
||||
char c = s.at(col+Left_of_screen); // todo: unicode
|
||||
if (c == '\n') c = ';'; // replace newlines with semi-colons
|
||||
// escapes. hack: can't start a line with them.
|
||||
if (c == '\1') { color = /*red*/1; continue; }
|
||||
if (c == '\2') { color = TB_WHITE; continue; }
|
||||
if (in_range(highlight_ranges, col+Left_of_screen))
|
||||
tb_print(c, TB_BLACK, /*yellow*/11);
|
||||
else
|
||||
tb_print(c, color, background_color);
|
||||
}
|
||||
for (; col < tb_width(); ++col)
|
||||
tb_print(' ', TB_WHITE, background_color);
|
||||
}
|
||||
|
||||
void search_next(const string& pat) {
|
||||
for (int trace_index = get(Trace_index, Display_row)+1; trace_index < SIZE(Trace_stream->past_lines); ++trace_index) {
|
||||
if (!contains_key(Visible, trace_index)) continue;
|
||||
const trace_line& line = Trace_stream->past_lines.at(trace_index);
|
||||
if (line.label.find(pat) == string::npos && line.contents.find(pat) == string::npos) continue;
|
||||
Top_of_screen = trace_index;
|
||||
Display_row = 0;
|
||||
refresh_screen_rows();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void search_previous(const string& pat) {
|
||||
for (int trace_index = get(Trace_index, Display_row)-1; trace_index >= 0; --trace_index) {
|
||||
if (!contains_key(Visible, trace_index)) continue;
|
||||
const trace_line& line = Trace_stream->past_lines.at(trace_index);
|
||||
if (line.label.find(pat) == string::npos && line.contents.find(pat) == string::npos) continue;
|
||||
Top_of_screen = trace_index;
|
||||
Display_row = 0;
|
||||
refresh_screen_rows();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void search(const string& pat, search_direction dir) {
|
||||
if (dir == FORWARD) search_next(pat);
|
||||
else search_previous(pat);
|
||||
}
|
||||
|
||||
search_direction opposite(search_direction dir) {
|
||||
if (dir == FORWARD) return BACKWARD;
|
||||
else return FORWARD;
|
||||
}
|
||||
|
||||
bool start_search_editor(search_direction dir) {
|
||||
const int bottom_screen_line = tb_height()-1;
|
||||
// run a little editor just in the last line of the screen
|
||||
clear_line(bottom_screen_line);
|
||||
int col = 0; // screen column of cursor on bottom line. also used to update pattern.
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
tb_print('/', TB_WHITE, TB_BLACK);
|
||||
++col;
|
||||
string pattern;
|
||||
while (true) {
|
||||
int key = read_key();
|
||||
if (key == TB_KEY_ENTER) {
|
||||
if (!pattern.empty()) {
|
||||
Current_search_pattern = pattern;
|
||||
Current_search_direction = dir;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (key == TB_KEY_ESC || key == TB_KEY_CTRL_C) {
|
||||
return false;
|
||||
}
|
||||
else if (key == TB_KEY_ARROW_LEFT) {
|
||||
if (col > /*slash*/1) {
|
||||
--col;
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
}
|
||||
else if (key == TB_KEY_ARROW_RIGHT) {
|
||||
if (col-/*slash*/1 < SIZE(pattern)) {
|
||||
++col;
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
}
|
||||
else if (key == TB_KEY_HOME || key == TB_KEY_CTRL_A) {
|
||||
col = /*skip slash*/1;
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
else if (key == TB_KEY_END || key == TB_KEY_CTRL_E) {
|
||||
col = SIZE(pattern)+/*skip slash*/1;
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
else if (key == TB_KEY_BACKSPACE || key == TB_KEY_BACKSPACE2) {
|
||||
if (col > /*slash*/1) {
|
||||
assert(col <= SIZE(pattern)+1);
|
||||
--col;
|
||||
// update pattern
|
||||
pattern.erase(col-/*slash*/1, /*len*/1);
|
||||
// update screen
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
for (int x = col; x < SIZE(pattern)+/*skip slash*/1; ++x)
|
||||
tb_print(pattern.at(x-/*slash*/1), TB_WHITE, TB_BLACK);
|
||||
tb_print(' ', TB_WHITE, TB_BLACK);
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
}
|
||||
else if (key == TB_KEY_CTRL_K) {
|
||||
int old_pattern_size = SIZE(pattern);
|
||||
pattern.erase(col-/*slash*/1, SIZE(pattern) - (col-/*slash*/1));
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
for (int x = col; x < old_pattern_size+/*slash*/1; ++x)
|
||||
tb_print(' ', TB_WHITE, TB_BLACK);
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
else if (key == TB_KEY_CTRL_U) {
|
||||
int old_pattern_size = SIZE(pattern);
|
||||
pattern.erase(0, col-/*slash*/1);
|
||||
col = /*skip slash*/1;
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
for (int x = /*slash*/1; x < SIZE(pattern)+/*skip slash*/1; ++x)
|
||||
tb_print(pattern.at(x-/*slash*/1), TB_WHITE, TB_BLACK);
|
||||
for (int x = SIZE(pattern)+/*slash*/1; x < old_pattern_size+/*skip slash*/1; ++x)
|
||||
tb_print(' ', TB_WHITE, TB_BLACK);
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
else if (key < 128) { // ascii only
|
||||
// update pattern
|
||||
char c = static_cast<char>(key);
|
||||
assert(col-1 >= 0);
|
||||
assert(col-1 <= SIZE(pattern));
|
||||
pattern.insert(col-/*slash*/1, /*num*/1, c);
|
||||
// update screen
|
||||
for (int x = col; x < SIZE(pattern)+/*skip slash*/1; ++x)
|
||||
tb_print(pattern.at(x-/*slash*/1), TB_WHITE, TB_BLACK);
|
||||
++col;
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void render() {
|
||||
int screen_row = 0;
|
||||
for (screen_row = 0; screen_row < tb_height(); ++screen_row) {
|
||||
if (!contains_key(Trace_index, screen_row)) break;
|
||||
trace_line& curr_line = Trace_stream->past_lines.at(get(Trace_index, screen_row));
|
||||
ostringstream out;
|
||||
if (screen_row < tb_height()-1) {
|
||||
int delta = lines_hidden(screen_row);
|
||||
// home-brew escape sequence for red
|
||||
if (delta > 1) {
|
||||
if (delta > 999) out << static_cast<char>(1);
|
||||
out << std::setw(6) << delta << "| ";
|
||||
if (delta > 999) out << static_cast<char>(2);
|
||||
}
|
||||
else {
|
||||
out << " ";
|
||||
}
|
||||
}
|
||||
else {
|
||||
out << " ";
|
||||
}
|
||||
out << std::setw(4) << curr_line.depth << ' ' << curr_line.label << ": " << curr_line.contents;
|
||||
render_line(screen_row, out.str(), screen_row == Display_row);
|
||||
}
|
||||
// clear rest of screen
|
||||
Last_printed_row = screen_row-1;
|
||||
for (; screen_row < tb_height(); ++screen_row)
|
||||
render_line(screen_row, "~", /*cursor_line?*/false);
|
||||
// move cursor back to display row at the end
|
||||
tb_set_cursor(0, Display_row);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc != 2) {
|
||||
cerr << "Usage: browse_trace <trace file>\n";
|
||||
|
@ -277,277 +545,3 @@ int main(int argc, char* argv[]) {
|
|||
tb_shutdown();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool start_search_editor(search_direction dir) {
|
||||
const int bottom_screen_line = tb_height()-1;
|
||||
// run a little editor just in the last line of the screen
|
||||
clear_line(bottom_screen_line);
|
||||
int col = 0; // screen column of cursor on bottom line. also used to update pattern.
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
tb_print('/', TB_WHITE, TB_BLACK);
|
||||
++col;
|
||||
string pattern;
|
||||
while (true) {
|
||||
int key = read_key();
|
||||
if (key == TB_KEY_ENTER) {
|
||||
if (!pattern.empty()) {
|
||||
Current_search_pattern = pattern;
|
||||
Current_search_direction = dir;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (key == TB_KEY_ESC || key == TB_KEY_CTRL_C) {
|
||||
return false;
|
||||
}
|
||||
else if (key == TB_KEY_ARROW_LEFT) {
|
||||
if (col > /*slash*/1) {
|
||||
--col;
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
}
|
||||
else if (key == TB_KEY_ARROW_RIGHT) {
|
||||
if (col-/*slash*/1 < SIZE(pattern)) {
|
||||
++col;
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
}
|
||||
else if (key == TB_KEY_HOME || key == TB_KEY_CTRL_A) {
|
||||
col = /*skip slash*/1;
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
else if (key == TB_KEY_END || key == TB_KEY_CTRL_E) {
|
||||
col = SIZE(pattern)+/*skip slash*/1;
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
else if (key == TB_KEY_BACKSPACE || key == TB_KEY_BACKSPACE2) {
|
||||
if (col > /*slash*/1) {
|
||||
assert(col <= SIZE(pattern)+1);
|
||||
--col;
|
||||
// update pattern
|
||||
pattern.erase(col-/*slash*/1, /*len*/1);
|
||||
// update screen
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
for (int x = col; x < SIZE(pattern)+/*skip slash*/1; ++x)
|
||||
tb_print(pattern.at(x-/*slash*/1), TB_WHITE, TB_BLACK);
|
||||
tb_print(' ', TB_WHITE, TB_BLACK);
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
}
|
||||
else if (key == TB_KEY_CTRL_K) {
|
||||
int old_pattern_size = SIZE(pattern);
|
||||
pattern.erase(col-/*slash*/1, SIZE(pattern) - (col-/*slash*/1));
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
for (int x = col; x < old_pattern_size+/*slash*/1; ++x)
|
||||
tb_print(' ', TB_WHITE, TB_BLACK);
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
else if (key == TB_KEY_CTRL_U) {
|
||||
int old_pattern_size = SIZE(pattern);
|
||||
pattern.erase(0, col-/*slash*/1);
|
||||
col = /*skip slash*/1;
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
for (int x = /*slash*/1; x < SIZE(pattern)+/*skip slash*/1; ++x)
|
||||
tb_print(pattern.at(x-/*slash*/1), TB_WHITE, TB_BLACK);
|
||||
for (int x = SIZE(pattern)+/*slash*/1; x < old_pattern_size+/*skip slash*/1; ++x)
|
||||
tb_print(' ', TB_WHITE, TB_BLACK);
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
else if (key < 128) { // ascii only
|
||||
// update pattern
|
||||
char c = static_cast<char>(key);
|
||||
assert(col-1 >= 0);
|
||||
assert(col-1 <= SIZE(pattern));
|
||||
pattern.insert(col-/*slash*/1, /*num*/1, c);
|
||||
// update screen
|
||||
for (int x = col; x < SIZE(pattern)+/*skip slash*/1; ++x)
|
||||
tb_print(pattern.at(x-/*slash*/1), TB_WHITE, TB_BLACK);
|
||||
++col;
|
||||
tb_set_cursor(col, bottom_screen_line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void search(const string& pat, search_direction dir) {
|
||||
if (dir == FORWARD) search_next(pat);
|
||||
else search_previous(pat);
|
||||
}
|
||||
|
||||
search_direction opposite(search_direction dir) {
|
||||
if (dir == FORWARD) return BACKWARD;
|
||||
else return FORWARD;
|
||||
}
|
||||
|
||||
void search_next(const string& pat) {
|
||||
for (int trace_index = get(Trace_index, Display_row)+1; trace_index < SIZE(Trace_stream->past_lines); ++trace_index) {
|
||||
if (!contains_key(Visible, trace_index)) continue;
|
||||
const trace_line& line = Trace_stream->past_lines.at(trace_index);
|
||||
if (line.label.find(pat) == string::npos && line.contents.find(pat) == string::npos) continue;
|
||||
Top_of_screen = trace_index;
|
||||
Display_row = 0;
|
||||
refresh_screen_rows();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void search_previous(const string& pat) {
|
||||
for (int trace_index = get(Trace_index, Display_row)-1; trace_index >= 0; --trace_index) {
|
||||
if (!contains_key(Visible, trace_index)) continue;
|
||||
const trace_line& line = Trace_stream->past_lines.at(trace_index);
|
||||
if (line.label.find(pat) == string::npos && line.contents.find(pat) == string::npos) continue;
|
||||
Top_of_screen = trace_index;
|
||||
Display_row = 0;
|
||||
refresh_screen_rows();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void clear_line(int screen_row) {
|
||||
tb_set_cursor(0, screen_row);
|
||||
for (int col = 0; col < tb_width(); ++col)
|
||||
tb_print(' ', TB_WHITE, TB_BLACK);
|
||||
tb_set_cursor(0, screen_row);
|
||||
}
|
||||
|
||||
// update Trace_indices for each screen_row on the basis of Top_of_screen and Visible
|
||||
void refresh_screen_rows() {
|
||||
int screen_row = 0, index = 0;
|
||||
Trace_index.clear();
|
||||
for (screen_row = 0, index = Top_of_screen; screen_row < tb_height() && index < SIZE(Trace_stream->past_lines); ++screen_row, ++index) {
|
||||
// skip lines without depth for now
|
||||
while (!contains_key(Visible, index)) {
|
||||
++index;
|
||||
if (index >= SIZE(Trace_stream->past_lines)) goto done;
|
||||
}
|
||||
assert(index < SIZE(Trace_stream->past_lines));
|
||||
put(Trace_index, screen_row, index);
|
||||
}
|
||||
done:;
|
||||
}
|
||||
|
||||
void render() {
|
||||
int screen_row = 0;
|
||||
for (screen_row = 0; screen_row < tb_height(); ++screen_row) {
|
||||
if (!contains_key(Trace_index, screen_row)) break;
|
||||
trace_line& curr_line = Trace_stream->past_lines.at(get(Trace_index, screen_row));
|
||||
ostringstream out;
|
||||
if (screen_row < tb_height()-1) {
|
||||
int delta = lines_hidden(screen_row);
|
||||
// home-brew escape sequence for red
|
||||
if (delta > 1) {
|
||||
if (delta > 999) out << static_cast<char>(1);
|
||||
out << std::setw(6) << delta << "| ";
|
||||
if (delta > 999) out << static_cast<char>(2);
|
||||
}
|
||||
else {
|
||||
out << " ";
|
||||
}
|
||||
}
|
||||
else {
|
||||
out << " ";
|
||||
}
|
||||
out << std::setw(4) << curr_line.depth << ' ' << curr_line.label << ": " << curr_line.contents;
|
||||
render_line(screen_row, out.str(), screen_row == Display_row);
|
||||
}
|
||||
// clear rest of screen
|
||||
Last_printed_row = screen_row-1;
|
||||
for (; screen_row < tb_height(); ++screen_row)
|
||||
render_line(screen_row, "~", /*cursor_line?*/false);
|
||||
// move cursor back to display row at the end
|
||||
tb_set_cursor(0, Display_row);
|
||||
}
|
||||
|
||||
int lines_hidden(int screen_row) {
|
||||
assert(contains_key(Trace_index, screen_row));
|
||||
if (!contains_key(Trace_index, screen_row+1))
|
||||
return SIZE(Trace_stream->past_lines) - get(Trace_index, screen_row);
|
||||
else
|
||||
return get(Trace_index, screen_row+1) - get(Trace_index, screen_row);
|
||||
}
|
||||
|
||||
void render_line(int screen_row, const string& s, bool cursor_line) {
|
||||
int col = 0;
|
||||
int color = TB_WHITE;
|
||||
int background_color = cursor_line ? /*subtle grey*/240 : TB_BLACK;
|
||||
vector<pair<size_t, size_t> > highlight_ranges = find_all_occurrences(s, Current_search_pattern);
|
||||
tb_set_cursor(0, screen_row);
|
||||
for (col = 0; col < tb_width() && col+Left_of_screen < SIZE(s); ++col) {
|
||||
char c = s.at(col+Left_of_screen); // todo: unicode
|
||||
if (c == '\n') c = ';'; // replace newlines with semi-colons
|
||||
// escapes. hack: can't start a line with them.
|
||||
if (c == '\1') { color = /*red*/1; continue; }
|
||||
if (c == '\2') { color = TB_WHITE; continue; }
|
||||
if (in_range(highlight_ranges, col+Left_of_screen))
|
||||
tb_print(c, TB_BLACK, /*yellow*/11);
|
||||
else
|
||||
tb_print(c, color, background_color);
|
||||
}
|
||||
for (; col < tb_width(); ++col)
|
||||
tb_print(' ', TB_WHITE, background_color);
|
||||
}
|
||||
|
||||
vector<pair<size_t, size_t> > find_all_occurrences(const string& s, const string& pat) {
|
||||
vector<pair<size_t, size_t> > result;
|
||||
if (pat.empty()) return result;
|
||||
size_t idx = 0;
|
||||
while (true) {
|
||||
size_t next_idx = s.find(pat, idx);
|
||||
if (next_idx == string::npos) break;
|
||||
result.push_back(pair<size_t, size_t>(next_idx, next_idx+SIZE(pat)));
|
||||
idx = next_idx+SIZE(pat);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool in_range(const vector<pair<size_t, size_t> >& highlight_ranges, size_t idx) {
|
||||
for (int i = 0; i < SIZE(highlight_ranges); ++i) {
|
||||
if (idx >= highlight_ranges.at(i).first && idx < highlight_ranges.at(i).second)
|
||||
return true;
|
||||
if (idx < highlight_ranges.at(i).second) break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void load_trace(const char* filename) {
|
||||
ifstream tin(filename);
|
||||
if (!tin) {
|
||||
cerr << "no such file: " << filename << '\n';
|
||||
exit(1);
|
||||
}
|
||||
Trace_stream = new trace_stream;
|
||||
while (has_data(tin)) {
|
||||
tin >> std::noskipws;
|
||||
skip_whitespace_but_not_newline(tin);
|
||||
if (!isdigit(tin.peek())) {
|
||||
string dummy;
|
||||
getline(tin, dummy);
|
||||
continue;
|
||||
}
|
||||
tin >> std::skipws;
|
||||
int depth;
|
||||
tin >> depth;
|
||||
string label;
|
||||
tin >> label;
|
||||
if (*--label.end() == ':') label.erase(--label.end());
|
||||
string line;
|
||||
getline(tin, line);
|
||||
Trace_stream->past_lines.push_back(trace_line(line, label, depth));
|
||||
}
|
||||
cerr << "lines read: " << Trace_stream->past_lines.size() << '\n';
|
||||
}
|
||||
|
||||
int read_key() {
|
||||
tb_event event;
|
||||
do {
|
||||
tb_poll_event(&event);
|
||||
} while (event.type != TB_EVENT_KEY);
|
||||
return event.key ? event.key : event.ch;
|
||||
}
|
||||
|
||||
void skip_whitespace_but_not_newline(istream& in) {
|
||||
while (true) {
|
||||
if (!has_data(in)) break;
|
||||
else if (in.peek() == '\n') break;
|
||||
else if (isspace(in.peek())) in.get();
|
||||
else break;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ To try it out, first create an example trace (from the top-level `mu/`
|
|||
directory):
|
||||
|
||||
```shell
|
||||
./mu --trace nqueens.mu
|
||||
./subx --trace run apps/factorial
|
||||
```
|
||||
|
||||
This command will save a trace of its execution in a file called `last_run`.
|
||||
|
@ -14,7 +14,7 @@ and a single-word 'label', followed by a colon and whitespace.
|
|||
Now browse this trace:
|
||||
|
||||
```shell
|
||||
./browse_trace/browse_trace last_run
|
||||
tools/browse_trace last_run
|
||||
```
|
||||
|
||||
You should now find yourself in a UI showing a subsequence of lines from the
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (C) 2010-2013 nsf <no.smile.face@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,2 @@
|
|||
Fork of https://github.com/nsf/termbox as of 2015-06-05
|
||||
git hash 252bef01264a2aeef22ebe5bae0b5893a58947f3
|
|
@ -0,0 +1,79 @@
|
|||
struct bytebuffer {
|
||||
char *buf;
|
||||
int len;
|
||||
int cap;
|
||||
};
|
||||
|
||||
static void bytebuffer_reserve(struct bytebuffer *b, int cap) {
|
||||
if (b->cap >= cap) {
|
||||
return;
|
||||
}
|
||||
|
||||
// prefer doubling capacity
|
||||
if (b->cap * 2 >= cap) {
|
||||
cap = b->cap * 2;
|
||||
}
|
||||
|
||||
char *newbuf = malloc(cap);
|
||||
if (b->len > 0) {
|
||||
// copy what was there, b->len > 0 assumes b->buf != null
|
||||
memcpy(newbuf, b->buf, b->len);
|
||||
}
|
||||
if (b->buf) {
|
||||
// in case there was an allocated buffer, free it
|
||||
free(b->buf);
|
||||
}
|
||||
b->buf = newbuf;
|
||||
b->cap = cap;
|
||||
}
|
||||
|
||||
static void bytebuffer_init(struct bytebuffer *b, int cap) {
|
||||
b->cap = 0;
|
||||
b->len = 0;
|
||||
b->buf = 0;
|
||||
|
||||
if (cap > 0) {
|
||||
b->cap = cap;
|
||||
b->buf = malloc(cap); // just assume malloc works always
|
||||
}
|
||||
}
|
||||
|
||||
static void bytebuffer_free(struct bytebuffer *b) {
|
||||
if (b->buf)
|
||||
free(b->buf);
|
||||
}
|
||||
|
||||
static void bytebuffer_clear(struct bytebuffer *b) {
|
||||
b->len = 0;
|
||||
}
|
||||
|
||||
static void bytebuffer_append(struct bytebuffer *b, const char *data, int len) {
|
||||
bytebuffer_reserve(b, b->len + len);
|
||||
memcpy(b->buf + b->len, data, len);
|
||||
b->len += len;
|
||||
}
|
||||
|
||||
static void bytebuffer_puts(struct bytebuffer *b, const char *str) {
|
||||
bytebuffer_append(b, str, strlen(str));
|
||||
}
|
||||
|
||||
static void bytebuffer_resize(struct bytebuffer *b, int len) {
|
||||
bytebuffer_reserve(b, len);
|
||||
b->len = len;
|
||||
}
|
||||
|
||||
static void bytebuffer_flush(struct bytebuffer *b, int fd) {
|
||||
int yyy = write(fd, b->buf, b->len);
|
||||
(void) yyy;
|
||||
bytebuffer_clear(b);
|
||||
}
|
||||
|
||||
static void bytebuffer_truncate(struct bytebuffer *b, int n) {
|
||||
if (n <= 0)
|
||||
return;
|
||||
if (n > b->len)
|
||||
n = b->len;
|
||||
const int nmove = b->len - n;
|
||||
memmove(b->buf, b->buf+n, nmove);
|
||||
b->len -= n;
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
// if s1 starts with s2 returns true, else false
|
||||
// len is the length of s1
|
||||
// s2 should be null-terminated
|
||||
static bool starts_with(const char *s1, int len, const char *s2)
|
||||
{
|
||||
int n = 0;
|
||||
while (*s2 && n < len) {
|
||||
if (*s1++ != *s2++)
|
||||
return false;
|
||||
n++;
|
||||
}
|
||||
return *s2 == 0;
|
||||
}
|
||||
|
||||
#define FOO(...) { \
|
||||
FILE* f = fopen("log", "a+"); \
|
||||
fprintf(f, __VA_ARGS__); \
|
||||
fclose(f); \
|
||||
}
|
||||
|
||||
// convert escape sequence to event, and return consumed bytes on success (failure == 0)
|
||||
static int parse_escape_seq(struct tb_event *event, const char *buf, int len)
|
||||
{
|
||||
static int parse_attempts = 0;
|
||||
static const int MAX_PARSE_ATTEMPTS = 2;
|
||||
|
||||
//? int x = 0;
|
||||
//? FOO("-- %d\n", len);
|
||||
//? for (x = 0; x < len; ++x) {
|
||||
//? FOO("%d\n", (unsigned char)buf[x]);
|
||||
//? }
|
||||
if (len >= 6 && starts_with(buf, len, "\033[M")) {
|
||||
|
||||
switch (buf[3] & 3) {
|
||||
case 0:
|
||||
if (buf[3] == 0x60)
|
||||
event->key = TB_KEY_MOUSE_WHEEL_UP;
|
||||
else
|
||||
event->key = TB_KEY_MOUSE_LEFT;
|
||||
break;
|
||||
case 1:
|
||||
if (buf[3] == 0x61)
|
||||
event->key = TB_KEY_MOUSE_WHEEL_DOWN;
|
||||
else
|
||||
event->key = TB_KEY_MOUSE_MIDDLE;
|
||||
break;
|
||||
case 2:
|
||||
event->key = TB_KEY_MOUSE_RIGHT;
|
||||
break;
|
||||
case 3:
|
||||
event->key = TB_KEY_MOUSE_RELEASE;
|
||||
break;
|
||||
default:
|
||||
parse_attempts = 0;
|
||||
return -6;
|
||||
}
|
||||
event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default
|
||||
|
||||
// the coord is 1,1 for upper left
|
||||
event->x = (uint8_t)buf[4] - 1 - 32;
|
||||
event->y = (uint8_t)buf[5] - 1 - 32;
|
||||
|
||||
parse_attempts = 0;
|
||||
return 6;
|
||||
}
|
||||
|
||||
// it's pretty simple here, find 'starts_with' match and return
|
||||
// success, else return failure
|
||||
int i;
|
||||
for (i = 0; keys[i]; i++) {
|
||||
if (starts_with(buf, len, keys[i])) {
|
||||
event->ch = 0;
|
||||
event->key = 0xFFFF-i;
|
||||
parse_attempts = 0;
|
||||
return strlen(keys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (starts_with(buf, len, "\033[200~")) {
|
||||
event->ch = 0;
|
||||
event->key = TB_KEY_START_PASTE;
|
||||
parse_attempts = 0;
|
||||
return strlen("\033[200~");
|
||||
}
|
||||
if (starts_with(buf, len, "\033[201~")) {
|
||||
event->ch = 0;
|
||||
event->key = TB_KEY_END_PASTE;
|
||||
parse_attempts = 0;
|
||||
return strlen("\033[201~");
|
||||
}
|
||||
if (starts_with(buf, len, "\033[1;5A")) {
|
||||
event->ch = 0;
|
||||
event->key = TB_KEY_CTRL_ARROW_UP;
|
||||
parse_attempts = 0;
|
||||
return strlen("\033[1;5A");
|
||||
}
|
||||
if (starts_with(buf, len, "\033[1;5B")) {
|
||||
event->ch = 0;
|
||||
event->key = TB_KEY_CTRL_ARROW_DOWN;
|
||||
parse_attempts = 0;
|
||||
return strlen("\033[1;5B");
|
||||
}
|
||||
if (starts_with(buf, len, "\033[1;5C")) {
|
||||
event->ch = 0;
|
||||
event->key = TB_KEY_CTRL_ARROW_RIGHT;
|
||||
parse_attempts = 0;
|
||||
return strlen("\033[1;5C");
|
||||
}
|
||||
if (starts_with(buf, len, "\033[1;5D")) {
|
||||
event->ch = 0;
|
||||
event->key = TB_KEY_CTRL_ARROW_LEFT;
|
||||
parse_attempts = 0;
|
||||
return strlen("\033[1;5D");
|
||||
}
|
||||
if (starts_with(buf, len, "\033[Z")) {
|
||||
event->ch = 0;
|
||||
event->key = TB_KEY_SHIFT_TAB;
|
||||
parse_attempts = 0;
|
||||
return strlen("\033[Z");
|
||||
}
|
||||
|
||||
// no escape sequence recognized? wait a bit in case our buffer is incomplete
|
||||
++parse_attempts;
|
||||
if (parse_attempts < MAX_PARSE_ATTEMPTS) return 0;
|
||||
// still nothing? give up and consume just the esc
|
||||
event->ch = 0;
|
||||
event->key = TB_KEY_ESC;
|
||||
parse_attempts = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool extract_event(struct tb_event *event, struct bytebuffer *inbuf)
|
||||
{
|
||||
const char *buf = inbuf->buf;
|
||||
const int len = inbuf->len;
|
||||
if (len == 0)
|
||||
return false;
|
||||
|
||||
//? int x = 0;
|
||||
//? FOO("== %d\n", len);
|
||||
//? for (x = 0; x < len; ++x) {
|
||||
//? FOO("%x\n", (unsigned char)buf[x]);
|
||||
//? }
|
||||
if (buf[0] == '\033') {
|
||||
int n = parse_escape_seq(event, buf, len);
|
||||
if (n == 0) return false;
|
||||
//? FOO("parsed: %u %u %u %u\n", n, (unsigned int)event->type, (unsigned int)event->key, event->ch);
|
||||
bool success = true;
|
||||
if (n < 0) {
|
||||
success = false;
|
||||
n = -n;
|
||||
}
|
||||
bytebuffer_truncate(inbuf, n);
|
||||
return success;
|
||||
}
|
||||
|
||||
// if we're here, this is not an escape sequence and not an alt sequence
|
||||
// so, it's a FUNCTIONAL KEY or a UNICODE character
|
||||
|
||||
// first of all check if it's a functional key
|
||||
if ((unsigned char)buf[0] <= TB_KEY_SPACE ||
|
||||
(unsigned char)buf[0] == TB_KEY_BACKSPACE2)
|
||||
{
|
||||
// fill event, pop buffer, return success */
|
||||
event->ch = 0;
|
||||
event->key = (uint16_t)buf[0];
|
||||
bytebuffer_truncate(inbuf, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// feh... we got utf8 here
|
||||
|
||||
// check if there is all bytes
|
||||
if (len >= tb_utf8_char_length(buf[0])) {
|
||||
/* everything ok, fill event, pop buffer, return success */
|
||||
tb_utf8_char_to_unicode(&event->ch, buf);
|
||||
event->key = 0;
|
||||
bytebuffer_truncate(inbuf, tb_utf8_char_length(buf[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
// event isn't recognized, perhaps there is not enough bytes in utf8
|
||||
// sequence
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
enum {
|
||||
T_ENTER_CA,
|
||||
T_EXIT_CA,
|
||||
T_SHOW_CURSOR,
|
||||
T_HIDE_CURSOR,
|
||||
T_CLEAR_SCREEN,
|
||||
T_SGR0,
|
||||
T_UNDERLINE,
|
||||
T_BOLD,
|
||||
T_BLINK,
|
||||
T_REVERSE,
|
||||
T_ENTER_KEYPAD,
|
||||
T_EXIT_KEYPAD,
|
||||
T_ENTER_MOUSE,
|
||||
T_EXIT_MOUSE,
|
||||
T_ENTER_BRACKETED_PASTE,
|
||||
T_EXIT_BRACKETED_PASTE,
|
||||
T_FUNCS_NUM,
|
||||
};
|
||||
|
||||
#define EUNSUPPORTED_TERM -1
|
||||
|
||||
// rxvt-256color
|
||||
static const char *rxvt_256color_keys[] = {
|
||||
"\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C", 0
|
||||
};
|
||||
static const char *rxvt_256color_funcs[] = {
|
||||
"\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033=", "\033>", "\033[?1000h", "\033[?1000l", "\033[?2004h", "\033[?2004l",
|
||||
};
|
||||
|
||||
// Eterm
|
||||
static const char *eterm_keys[] = {
|
||||
"\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C", 0
|
||||
};
|
||||
static const char *eterm_funcs[] = {
|
||||
"\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "", "", "",
|
||||
};
|
||||
|
||||
// screen
|
||||
static const char *screen_keys[] = {
|
||||
"\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~", "\033OA", "\033OB", "\033OD", "\033OC", 0
|
||||
};
|
||||
static const char *screen_funcs[] = {
|
||||
"\033[?1049h", "\033[?1049l", "\033[34h\033[?25h", "\033[?25l", "\033[H\033[J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033[?1h\033=", "\033[?1l\033>", "\033[?1000h", "\033[?1000l", "\033[?2004h", "\033[?2004l",
|
||||
};
|
||||
|
||||
// rxvt-unicode
|
||||
static const char *rxvt_unicode_keys[] = {
|
||||
"\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C", 0
|
||||
};
|
||||
static const char *rxvt_unicode_funcs[] = {
|
||||
"\033[?1049h", "\033[r\033[?1049l", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m\033(B", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033=", "\033>", "\033[?1000h", "\033[?1000l", "\033[?2004h", "\033[?2004l",
|
||||
};
|
||||
|
||||
// linux
|
||||
static const char *linux_keys[] = {
|
||||
"\033[[A", "\033[[B", "\033[[C", "\033[[D", "\033[[E", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C", 0
|
||||
};
|
||||
static const char *linux_funcs[] = {
|
||||
"", "", "\033[?25h\033[?0c", "\033[?25l\033[?1c", "\033[H\033[J", "\033[0;10m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "", "", "",
|
||||
};
|
||||
|
||||
// xterm
|
||||
static const char *xterm_keys[] = {
|
||||
"\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033OH", "\033OF", "\033[5~", "\033[6~", "\033OA", "\033OB", "\033OD", "\033OC", 0
|
||||
};
|
||||
static const char *xterm_funcs[] = {
|
||||
"\033[?1049h", "\033[?1049l", "\033[?12l\033[?25h", "\033[?25l", "\033[H\033[2J", "\033(B\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033[?1h\033=", "\033[?1l\033>", "\033[?1000h", "\033[?1000l", "\033[?2004h", "\033[?2004l",
|
||||
};
|
||||
|
||||
static struct term {
|
||||
const char *name;
|
||||
const char **keys;
|
||||
const char **funcs;
|
||||
} terms[] = {
|
||||
{"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs},
|
||||
{"Eterm", eterm_keys, eterm_funcs},
|
||||
{"screen", screen_keys, screen_funcs},
|
||||
{"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs},
|
||||
{"linux", linux_keys, linux_funcs},
|
||||
{"xterm", xterm_keys, xterm_funcs},
|
||||
{0, 0, 0},
|
||||
};
|
||||
|
||||
static bool init_from_terminfo = false;
|
||||
static const char **keys;
|
||||
static const char **funcs;
|
||||
|
||||
static int try_compatible(const char *term, const char *name,
|
||||
const char **tkeys, const char **tfuncs)
|
||||
{
|
||||
if (strstr(term, name)) {
|
||||
keys = tkeys;
|
||||
funcs = tfuncs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return EUNSUPPORTED_TERM;
|
||||
}
|
||||
|
||||
static int init_term_builtin(void)
|
||||
{
|
||||
int i;
|
||||
const char *term = getenv("TERM");
|
||||
|
||||
if (term) {
|
||||
for (i = 0; terms[i].name; i++) {
|
||||
if (!strcmp(terms[i].name, term)) {
|
||||
keys = terms[i].keys;
|
||||
funcs = terms[i].funcs;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* let's do some heuristic, maybe it's a compatible terminal */
|
||||
if (try_compatible(term, "xterm", xterm_keys, xterm_funcs) == 0)
|
||||
return 0;
|
||||
if (try_compatible(term, "rxvt", rxvt_unicode_keys, rxvt_unicode_funcs) == 0)
|
||||
return 0;
|
||||
if (try_compatible(term, "linux", linux_keys, linux_funcs) == 0)
|
||||
return 0;
|
||||
if (try_compatible(term, "Eterm", eterm_keys, eterm_funcs) == 0)
|
||||
return 0;
|
||||
if (try_compatible(term, "screen", screen_keys, screen_funcs) == 0)
|
||||
return 0;
|
||||
/* let's assume that 'cygwin' is xterm compatible */
|
||||
if (try_compatible(term, "cygwin", xterm_keys, xterm_funcs) == 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return EUNSUPPORTED_TERM;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// terminfo
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
static char *read_file(const char *file) {
|
||||
FILE *f = fopen(file, "rb");
|
||||
if (!f)
|
||||
return 0;
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fileno(f), &st) != 0) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *data = malloc(st.st_size);
|
||||
if (!data) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fread(data, 1, st.st_size, f) != (size_t)st.st_size) {
|
||||
fclose(f);
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return data;
|
||||
}
|
||||
|
||||
static char *terminfo_try_path(const char *path, const char *term) {
|
||||
char tmp[4096];
|
||||
// snprintf guarantee for older compilers
|
||||
assert(sizeof(tmp) > sizeof(path)+sizeof("/x/")+sizeof(term)+1);
|
||||
snprintf(tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term);
|
||||
char *data = read_file(tmp);
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// fallback to darwin specific dirs structure
|
||||
// snprintf guarantee above still applies
|
||||
snprintf(tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term);
|
||||
return read_file(tmp);
|
||||
}
|
||||
|
||||
void string_copy(char* dest, const char* src, int dest_capacity) {
|
||||
strncpy(dest, src, dest_capacity);
|
||||
dest[dest_capacity-1] = '\0';
|
||||
}
|
||||
|
||||
void string_append(char* dest, const char* src, int dest_capacity) {
|
||||
strncat(dest, src, dest_capacity);
|
||||
dest[dest_capacity-1] = '\0';
|
||||
}
|
||||
|
||||
static char *load_terminfo(void) {
|
||||
char tmp[4096];
|
||||
const char *term = getenv("TERM");
|
||||
if (!term) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// if TERMINFO is set, no other directory should be searched
|
||||
const char *terminfo = getenv("TERMINFO");
|
||||
if (terminfo) {
|
||||
return terminfo_try_path(terminfo, term);
|
||||
}
|
||||
|
||||
// next, consider ~/.terminfo
|
||||
const char *home = getenv("HOME");
|
||||
if (home) {
|
||||
// snprintf guarantee for older compilers
|
||||
assert(sizeof(tmp) > sizeof(home)+sizeof("/.terminfo")+1);
|
||||
string_copy(tmp, home, sizeof(tmp));
|
||||
string_append(tmp, "/.terminfo", sizeof(tmp));
|
||||
char *data = terminfo_try_path(tmp, term);
|
||||
if (data)
|
||||
return data;
|
||||
}
|
||||
|
||||
// next, TERMINFO_DIRS
|
||||
const char *dirs = getenv("TERMINFO_DIRS");
|
||||
if (dirs) {
|
||||
// snprintf guarantee for older compilers
|
||||
assert(sizeof(tmp) > sizeof(dirs));
|
||||
strncpy(tmp, dirs, sizeof(tmp));
|
||||
char *dir = strtok(tmp, ":");
|
||||
while (dir) {
|
||||
const char *cdir = dir;
|
||||
if (strcmp(cdir, "") == 0) {
|
||||
cdir = "/usr/share/terminfo";
|
||||
}
|
||||
char *data = terminfo_try_path(cdir, term);
|
||||
if (data)
|
||||
return data;
|
||||
dir = strtok(0, ":");
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to /usr/share/terminfo
|
||||
return terminfo_try_path("/usr/share/terminfo", term);
|
||||
}
|
||||
|
||||
#define TI_MAGIC 0432
|
||||
#define TI_HEADER_LENGTH 12
|
||||
#define TB_KEYS_NUM 22
|
||||
|
||||
static const char *terminfo_copy_string(char *data, int str, int table) {
|
||||
const int16_t off = *(int16_t*)(data + str);
|
||||
const char *src = data + table + off;
|
||||
int len = strlen(src);
|
||||
char *dst = malloc(len+1);
|
||||
string_copy(dst, src, len+1);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static const int16_t ti_funcs[] = {
|
||||
28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88,
|
||||
};
|
||||
|
||||
static const int16_t ti_keys[] = {
|
||||
66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69,
|
||||
70, 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61,
|
||||
79, 83,
|
||||
};
|
||||
|
||||
static int init_term(void) {
|
||||
int i;
|
||||
char *data = load_terminfo();
|
||||
if (!data) {
|
||||
init_from_terminfo = false;
|
||||
return init_term_builtin();
|
||||
}
|
||||
|
||||
int16_t *header = (int16_t*)data;
|
||||
if ((header[1] + header[2]) % 2) {
|
||||
// old quirk to align everything on word boundaries
|
||||
header[2] += 1;
|
||||
}
|
||||
|
||||
const int str_offset = TI_HEADER_LENGTH +
|
||||
header[1] + header[2] + 2 * header[3];
|
||||
const int table_offset = str_offset + 2 * header[4];
|
||||
|
||||
keys = malloc(sizeof(const char*) * (TB_KEYS_NUM+1));
|
||||
for (i = 0; i < TB_KEYS_NUM; i++) {
|
||||
keys[i] = terminfo_copy_string(data,
|
||||
str_offset + 2 * ti_keys[i], table_offset);
|
||||
}
|
||||
keys[TB_KEYS_NUM] = 0;
|
||||
|
||||
funcs = malloc(sizeof(const char*) * T_FUNCS_NUM);
|
||||
// the last four entries are reserved for mouse, bracketed paste. because the table offset is
|
||||
// not there, the two entries have to fill in manually
|
||||
for (i = 0; i < T_FUNCS_NUM-4; i++) {
|
||||
funcs[i] = terminfo_copy_string(data,
|
||||
str_offset + 2 * ti_funcs[i], table_offset);
|
||||
}
|
||||
|
||||
funcs[T_FUNCS_NUM-4] = "\033[?1000h";
|
||||
funcs[T_FUNCS_NUM-3] = "\033[?1000l";
|
||||
funcs[T_FUNCS_NUM-2] = "\033[?2004h";
|
||||
funcs[T_FUNCS_NUM-1] = "\033[?2004l";
|
||||
|
||||
init_from_terminfo = true;
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void shutdown_term(void) {
|
||||
if (init_from_terminfo) {
|
||||
int i;
|
||||
for (i = 0; i < TB_KEYS_NUM; i++) {
|
||||
free((void*)keys[i]);
|
||||
}
|
||||
// the last four entries are reserved for mouse, bracketed paste. because the table offset
|
||||
// is not there, the two entries have to fill in manually and do not
|
||||
// need to be freed.
|
||||
for (i = 0; i < T_FUNCS_NUM-4; i++) {
|
||||
free((void*)funcs[i]);
|
||||
}
|
||||
free(keys);
|
||||
free(funcs);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,397 @@
|
|||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <wchar.h>
|
||||
/* hack: we can't define _XOPEN_SOURCE because that causes OpenBSD to not
|
||||
* include SIGWINCH. But then this prototype is not included on Linux,
|
||||
* triggering a warning. */
|
||||
extern int wcwidth (wchar_t);
|
||||
|
||||
#include "termbox.h"
|
||||
|
||||
#include "bytebuffer.inl"
|
||||
#include "output.inl"
|
||||
#include "input.inl"
|
||||
|
||||
#define LAST_COORD_INIT -1
|
||||
|
||||
static struct termios orig_tios;
|
||||
|
||||
static struct bytebuffer output_buffer;
|
||||
static struct bytebuffer input_buffer;
|
||||
|
||||
static int termw = -1;
|
||||
static int termh = -1;
|
||||
|
||||
static int inout;
|
||||
static int winch_fds[2];
|
||||
|
||||
static int cursor_x = 0;
|
||||
static int cursor_y = 0;
|
||||
|
||||
static uint16_t background = TB_BLACK;
|
||||
static uint16_t foreground = TB_WHITE;
|
||||
|
||||
static void update_size(void);
|
||||
static void update_term_size(void);
|
||||
static void send_attr(uint16_t fg, uint16_t bg);
|
||||
static void send_clear(void);
|
||||
static void sigwinch_handler(int xxx);
|
||||
static int wait_fill_event(struct tb_event *event, struct timeval *timeout);
|
||||
|
||||
/* may happen in a different thread */
|
||||
static volatile int buffer_size_change_request;
|
||||
|
||||
/* -------------------------------------------------------- */
|
||||
|
||||
int tb_init(void)
|
||||
{
|
||||
inout = open("/dev/tty", O_RDWR);
|
||||
if (inout == -1) {
|
||||
return TB_EFAILED_TO_OPEN_TTY;
|
||||
}
|
||||
|
||||
if (init_term() < 0) {
|
||||
close(inout);
|
||||
return TB_EUNSUPPORTED_TERMINAL;
|
||||
}
|
||||
|
||||
if (pipe(winch_fds) < 0) {
|
||||
close(inout);
|
||||
return TB_EPIPE_TRAP_ERROR;
|
||||
}
|
||||
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = sigwinch_handler;
|
||||
sa.sa_flags = 0;
|
||||
sigaction(SIGWINCH, &sa, 0);
|
||||
|
||||
tcgetattr(inout, &orig_tios);
|
||||
|
||||
struct termios tios;
|
||||
memcpy(&tios, &orig_tios, sizeof(tios));
|
||||
|
||||
tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
|
||||
| INLCR | IGNCR | ICRNL | IXON);
|
||||
tios.c_oflag &= ~OPOST;
|
||||
tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||
tios.c_cflag &= ~(CSIZE | PARENB);
|
||||
tios.c_cflag |= CS8;
|
||||
tios.c_cc[VMIN] = 0;
|
||||
tios.c_cc[VTIME] = 0;
|
||||
tcsetattr(inout, TCSAFLUSH, &tios);
|
||||
|
||||
bytebuffer_init(&input_buffer, 128);
|
||||
bytebuffer_init(&output_buffer, 32 * 1024);
|
||||
|
||||
bytebuffer_puts(&output_buffer, funcs[T_ENTER_KEYPAD]);
|
||||
bytebuffer_puts(&output_buffer, funcs[T_ENTER_MOUSE]);
|
||||
bytebuffer_puts(&output_buffer, funcs[T_ENTER_BRACKETED_PASTE]);
|
||||
bytebuffer_flush(&output_buffer, inout);
|
||||
|
||||
update_term_size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tb_shutdown(void)
|
||||
{
|
||||
if (termw == -1) return;
|
||||
|
||||
bytebuffer_puts(&output_buffer, funcs[T_SGR0]);
|
||||
bytebuffer_puts(&output_buffer, funcs[T_EXIT_KEYPAD]);
|
||||
bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]);
|
||||
bytebuffer_puts(&output_buffer, funcs[T_EXIT_BRACKETED_PASTE]);
|
||||
bytebuffer_flush(&output_buffer, inout);
|
||||
tcsetattr(inout, TCSAFLUSH, &orig_tios);
|
||||
|
||||
shutdown_term();
|
||||
close(inout);
|
||||
close(winch_fds[0]);
|
||||
close(winch_fds[1]);
|
||||
|
||||
bytebuffer_free(&output_buffer);
|
||||
bytebuffer_free(&input_buffer);
|
||||
termw = termh = -1;
|
||||
}
|
||||
|
||||
int tb_is_active(void)
|
||||
{
|
||||
return termw != -1;
|
||||
}
|
||||
|
||||
void tb_print(uint32_t ch, uint16_t fg, uint16_t bg)
|
||||
{
|
||||
assert(termw != -1);
|
||||
send_attr(fg, bg);
|
||||
if (ch == 0) {
|
||||
// replace 0 with whitespace
|
||||
bytebuffer_puts(&output_buffer, " ");
|
||||
}
|
||||
else {
|
||||
char buf[7];
|
||||
int bw = tb_utf8_unicode_to_char(buf, ch);
|
||||
buf[bw] = '\0';
|
||||
bytebuffer_puts(&output_buffer, buf);
|
||||
}
|
||||
bytebuffer_flush(&output_buffer, inout);
|
||||
}
|
||||
|
||||
int tb_poll_event(struct tb_event *event)
|
||||
{
|
||||
assert(termw != -1);
|
||||
return wait_fill_event(event, 0);
|
||||
}
|
||||
|
||||
int tb_peek_event(struct tb_event *event, int timeout)
|
||||
{
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout / 1000;
|
||||
tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
|
||||
assert(termw != -1);
|
||||
return wait_fill_event(event, &tv);
|
||||
}
|
||||
|
||||
int tb_width(void)
|
||||
{
|
||||
assert(termw != -1);
|
||||
return termw;
|
||||
}
|
||||
|
||||
int tb_height(void)
|
||||
{
|
||||
assert(termw != -1);
|
||||
return termh;
|
||||
}
|
||||
|
||||
void tb_clear(void)
|
||||
{
|
||||
assert(termw != -1);
|
||||
if (buffer_size_change_request) {
|
||||
update_size();
|
||||
buffer_size_change_request = 0;
|
||||
}
|
||||
send_clear();
|
||||
}
|
||||
|
||||
void tb_set_clear_attributes(uint16_t fg, uint16_t bg)
|
||||
{
|
||||
assert(termw != -1);
|
||||
foreground = fg;
|
||||
background = bg;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------- */
|
||||
|
||||
static int convertnum(uint32_t num, char* buf) {
|
||||
int i, l = 0;
|
||||
int ch;
|
||||
do {
|
||||
buf[l++] = '0' + (num % 10);
|
||||
num /= 10;
|
||||
} while (num);
|
||||
for(i = 0; i < l / 2; i++) {
|
||||
ch = buf[i];
|
||||
buf[i] = buf[l - 1 - i];
|
||||
buf[l - 1 - i] = ch;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
#define WRITE_LITERAL(X) bytebuffer_append(&output_buffer, (X), sizeof(X)-1)
|
||||
#define WRITE_INT(X) bytebuffer_append(&output_buffer, buf, convertnum((X), buf))
|
||||
|
||||
void tb_set_cursor(int x, int y) {
|
||||
char buf[32];
|
||||
WRITE_LITERAL("\033[");
|
||||
WRITE_INT(y+1);
|
||||
WRITE_LITERAL(";");
|
||||
WRITE_INT(x+1);
|
||||
WRITE_LITERAL("H");
|
||||
bytebuffer_flush(&output_buffer, inout);
|
||||
}
|
||||
|
||||
static void get_term_size(int *w, int *h)
|
||||
{
|
||||
struct winsize sz;
|
||||
memset(&sz, 0, sizeof(sz));
|
||||
|
||||
ioctl(inout, TIOCGWINSZ, &sz);
|
||||
|
||||
if (w) *w = sz.ws_col;
|
||||
if (h) *h = sz.ws_row;
|
||||
}
|
||||
|
||||
static void update_term_size(void)
|
||||
{
|
||||
struct winsize sz;
|
||||
memset(&sz, 0, sizeof(sz));
|
||||
|
||||
ioctl(inout, TIOCGWINSZ, &sz);
|
||||
|
||||
termw = sz.ws_col;
|
||||
termh = sz.ws_row;
|
||||
}
|
||||
|
||||
static void send_attr(uint16_t fg, uint16_t bg)
|
||||
{
|
||||
#define LAST_ATTR_INIT 0xFFFF
|
||||
static uint16_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT;
|
||||
if (fg != lastfg || bg != lastbg) {
|
||||
bytebuffer_puts(&output_buffer, funcs[T_SGR0]);
|
||||
|
||||
uint16_t fgcol = fg & 0xFF;
|
||||
uint16_t bgcol = bg & 0xFF;
|
||||
|
||||
if (fg & TB_BOLD)
|
||||
bytebuffer_puts(&output_buffer, funcs[T_BOLD]);
|
||||
if (bg & TB_BOLD)
|
||||
bytebuffer_puts(&output_buffer, funcs[T_BLINK]);
|
||||
if (fg & TB_UNDERLINE)
|
||||
bytebuffer_puts(&output_buffer, funcs[T_UNDERLINE]);
|
||||
if ((fg & TB_REVERSE) || (bg & TB_REVERSE))
|
||||
bytebuffer_puts(&output_buffer, funcs[T_REVERSE]);
|
||||
char buf[32];
|
||||
WRITE_LITERAL("\033[38;5;");
|
||||
WRITE_INT(fgcol);
|
||||
WRITE_LITERAL("m");
|
||||
WRITE_LITERAL("\033[48;5;");
|
||||
WRITE_INT(bgcol);
|
||||
WRITE_LITERAL("m");
|
||||
bytebuffer_flush(&output_buffer, inout);
|
||||
lastfg = fg;
|
||||
lastbg = bg;
|
||||
}
|
||||
}
|
||||
|
||||
const char* to_unicode(uint32_t c)
|
||||
{
|
||||
static char buf[7];
|
||||
int bw = tb_utf8_unicode_to_char(buf, c);
|
||||
buf[bw] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void send_clear(void)
|
||||
{
|
||||
send_attr(foreground, background);
|
||||
bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]);
|
||||
tb_set_cursor(cursor_x, cursor_y);
|
||||
bytebuffer_flush(&output_buffer, inout);
|
||||
}
|
||||
|
||||
static void sigwinch_handler(int xxx)
|
||||
{
|
||||
(void) xxx;
|
||||
const int zzz = 1;
|
||||
int yyy = write(winch_fds[1], &zzz, sizeof(int));
|
||||
(void) yyy;
|
||||
}
|
||||
|
||||
static void update_size(void)
|
||||
{
|
||||
update_term_size();
|
||||
send_clear();
|
||||
}
|
||||
|
||||
static int read_up_to(int n) {
|
||||
assert(n > 0);
|
||||
const int prevlen = input_buffer.len;
|
||||
bytebuffer_resize(&input_buffer, prevlen + n);
|
||||
|
||||
int read_n = 0;
|
||||
while (read_n <= n) {
|
||||
ssize_t r = 0;
|
||||
if (read_n < n) {
|
||||
r = read(inout, input_buffer.buf + prevlen + read_n, n - read_n);
|
||||
}
|
||||
#ifdef __CYGWIN__
|
||||
// While linux man for tty says when VMIN == 0 && VTIME == 0, read
|
||||
// should return 0 when there is nothing to read, cygwin's read returns
|
||||
// -1. Not sure why and if it's correct to ignore it, but let's pretend
|
||||
// it's zero.
|
||||
if (r < 0) r = 0;
|
||||
#endif
|
||||
if (r < 0) {
|
||||
// EAGAIN / EWOULDBLOCK shouldn't occur here
|
||||
assert(errno != EAGAIN && errno != EWOULDBLOCK);
|
||||
return -1;
|
||||
} else if (r > 0) {
|
||||
read_n += r;
|
||||
} else {
|
||||
bytebuffer_resize(&input_buffer, prevlen + read_n);
|
||||
return read_n;
|
||||
}
|
||||
}
|
||||
assert(!"unreachable");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tb_event_ready(void)
|
||||
{
|
||||
return input_buffer.len > 0;
|
||||
}
|
||||
|
||||
static int wait_fill_event(struct tb_event *event, struct timeval *timeout)
|
||||
{
|
||||
// ;-)
|
||||
#define ENOUGH_DATA_FOR_PARSING 64
|
||||
fd_set events;
|
||||
memset(event, 0, sizeof(struct tb_event));
|
||||
|
||||
// try to extract event from input buffer, return on success
|
||||
event->type = TB_EVENT_KEY;
|
||||
if (extract_event(event, &input_buffer))
|
||||
return event->type;
|
||||
|
||||
// it looks like input buffer is incomplete, let's try the short path,
|
||||
// but first make sure there is enough space
|
||||
int n = read_up_to(ENOUGH_DATA_FOR_PARSING);
|
||||
if (n < 0)
|
||||
return -1;
|
||||
if (n > 0 && extract_event(event, &input_buffer))
|
||||
return event->type;
|
||||
|
||||
// n == 0, or not enough data, let's go to select
|
||||
while (1) {
|
||||
FD_ZERO(&events);
|
||||
FD_SET(inout, &events);
|
||||
FD_SET(winch_fds[0], &events);
|
||||
int maxfd = (winch_fds[0] > inout) ? winch_fds[0] : inout;
|
||||
int result = select(maxfd+1, &events, 0, 0, timeout);
|
||||
if (!result)
|
||||
return 0;
|
||||
|
||||
if (FD_ISSET(inout, &events)) {
|
||||
event->type = TB_EVENT_KEY;
|
||||
n = read_up_to(ENOUGH_DATA_FOR_PARSING);
|
||||
if (n < 0)
|
||||
return -1;
|
||||
|
||||
if (n == 0)
|
||||
continue;
|
||||
|
||||
if (extract_event(event, &input_buffer))
|
||||
return event->type;
|
||||
}
|
||||
if (FD_ISSET(winch_fds[0], &events)) {
|
||||
event->type = TB_EVENT_RESIZE;
|
||||
int zzz = 0;
|
||||
int yyy = read(winch_fds[0], &zzz, sizeof(int));
|
||||
(void) yyy;
|
||||
buffer_size_change_request = 1;
|
||||
get_term_size(&event->w, &event->h);
|
||||
return TB_EVENT_RESIZE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*** 1. Controlling the screen. */
|
||||
|
||||
/* Names for some foreground/background colors. */
|
||||
#define TB_BLACK 232
|
||||
#define TB_WHITE 255
|
||||
|
||||
/* Some attributes of screen cells that can be combined with colors using
|
||||
* bitwise-OR. */
|
||||
#define TB_BOLD 0x0100
|
||||
#define TB_UNDERLINE 0x0200
|
||||
#define TB_REVERSE 0x0400
|
||||
|
||||
/* Initialize screen and keyboard. */
|
||||
int tb_init(void);
|
||||
/* Possible error codes returned by tb_init() */
|
||||
#define TB_EUNSUPPORTED_TERMINAL -1
|
||||
#define TB_EFAILED_TO_OPEN_TTY -2
|
||||
/* Termbox uses unix pipes in order to deliver a message from a signal handler
|
||||
* (SIGWINCH) to the main event reading loop. */
|
||||
#define TB_EPIPE_TRAP_ERROR -3
|
||||
|
||||
/* Restore terminal mode. */
|
||||
void tb_shutdown(void);
|
||||
|
||||
int tb_is_active(void);
|
||||
|
||||
/* Size of the screen. Return negative values before tb_init() or after
|
||||
* tb_shutdown() */
|
||||
int tb_width(void);
|
||||
int tb_height(void);
|
||||
|
||||
/* Clear the screen using either TB_DEFAULT or the color/attributes set by
|
||||
* tb_set_clear_attributes(). */
|
||||
void tb_clear(void);
|
||||
void tb_set_clear_attributes(uint16_t fg, uint16_t bg);
|
||||
|
||||
/* Move the cursor. Upper-left character is (0, 0). */
|
||||
void tb_set_cursor(int cx, int cy);
|
||||
|
||||
/* Modify the screen at the cursor. */
|
||||
void tb_print(uint32_t ch, uint16_t fg, uint16_t bg);
|
||||
|
||||
/*** 2. Controlling keyboard events. */
|
||||
|
||||
struct tb_event {
|
||||
uint8_t type;
|
||||
/* fields for type TB_EVENT_KEY. At most one of 'key' and 'ch' will be set at
|
||||
* any time. */
|
||||
uint16_t key;
|
||||
uint32_t ch;
|
||||
/* fields for type TB_EVENT_RESIZE */
|
||||
int32_t w;
|
||||
int32_t h;
|
||||
/* fields for type TB_EVENT_MOUSE */
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
};
|
||||
|
||||
/* Possible values for tb_event.type. */
|
||||
#define TB_EVENT_KEY 1
|
||||
#define TB_EVENT_RESIZE 2
|
||||
#define TB_EVENT_MOUSE 3
|
||||
|
||||
/* Possible values for tb_event.key. */
|
||||
#define TB_KEY_F1 (0xFFFF-0)
|
||||
#define TB_KEY_F2 (0xFFFF-1)
|
||||
#define TB_KEY_F3 (0xFFFF-2)
|
||||
#define TB_KEY_F4 (0xFFFF-3)
|
||||
#define TB_KEY_F5 (0xFFFF-4)
|
||||
#define TB_KEY_F6 (0xFFFF-5)
|
||||
#define TB_KEY_F7 (0xFFFF-6)
|
||||
#define TB_KEY_F8 (0xFFFF-7)
|
||||
#define TB_KEY_F9 (0xFFFF-8)
|
||||
#define TB_KEY_F10 (0xFFFF-9)
|
||||
#define TB_KEY_F11 (0xFFFF-10)
|
||||
#define TB_KEY_F12 (0xFFFF-11)
|
||||
#define TB_KEY_INSERT (0xFFFF-12)
|
||||
#define TB_KEY_DELETE (0xFFFF-13)
|
||||
#define TB_KEY_HOME (0xFFFF-14)
|
||||
#define TB_KEY_END (0xFFFF-15)
|
||||
#define TB_KEY_PGUP (0xFFFF-16)
|
||||
#define TB_KEY_PGDN (0xFFFF-17)
|
||||
#define TB_KEY_ARROW_UP (0xFFFF-18)
|
||||
#define TB_KEY_ARROW_DOWN (0xFFFF-19)
|
||||
#define TB_KEY_ARROW_LEFT (0xFFFF-20)
|
||||
#define TB_KEY_ARROW_RIGHT (0xFFFF-21)
|
||||
#define TB_KEY_MOUSE_LEFT (0xFFFF-22)
|
||||
#define TB_KEY_MOUSE_RIGHT (0xFFFF-23)
|
||||
#define TB_KEY_MOUSE_MIDDLE (0xFFFF-24)
|
||||
#define TB_KEY_MOUSE_RELEASE (0xFFFF-25)
|
||||
#define TB_KEY_MOUSE_WHEEL_UP (0xFFFF-26)
|
||||
#define TB_KEY_MOUSE_WHEEL_DOWN (0xFFFF-27)
|
||||
#define TB_KEY_START_PASTE (0xFFFF-28)
|
||||
#define TB_KEY_END_PASTE (0xFFFF-29)
|
||||
#define TB_KEY_CTRL_ARROW_UP (0xFFFF-30)
|
||||
#define TB_KEY_CTRL_ARROW_DOWN (0xFFFF-31)
|
||||
#define TB_KEY_CTRL_ARROW_LEFT (0xFFFF-32)
|
||||
#define TB_KEY_CTRL_ARROW_RIGHT (0xFFFF-33)
|
||||
#define TB_KEY_SHIFT_TAB (0xFFFF-34)
|
||||
|
||||
/* Names for some of the possible values for tb_event.ch. */
|
||||
/* These are all ASCII code points below SPACE character and a BACKSPACE key. */
|
||||
#define TB_KEY_CTRL_TILDE 0x00
|
||||
#define TB_KEY_CTRL_2 0x00 /* clash with 'CTRL_TILDE' */
|
||||
#define TB_KEY_CTRL_A 0x01
|
||||
#define TB_KEY_CTRL_B 0x02
|
||||
#define TB_KEY_CTRL_C 0x03
|
||||
#define TB_KEY_CTRL_D 0x04
|
||||
#define TB_KEY_CTRL_E 0x05
|
||||
#define TB_KEY_CTRL_F 0x06
|
||||
#define TB_KEY_CTRL_G 0x07
|
||||
#define TB_KEY_BACKSPACE 0x08
|
||||
#define TB_KEY_CTRL_H 0x08 /* clash with 'CTRL_BACKSPACE' */
|
||||
#define TB_KEY_TAB 0x09
|
||||
#define TB_KEY_CTRL_I 0x09 /* clash with 'TAB' */
|
||||
#define TB_KEY_CTRL_J 0x0A
|
||||
#define TB_KEY_CTRL_K 0x0B
|
||||
#define TB_KEY_CTRL_L 0x0C
|
||||
#define TB_KEY_ENTER 0x0D
|
||||
#define TB_KEY_CTRL_M 0x0D /* clash with 'ENTER' */
|
||||
#define TB_KEY_CTRL_N 0x0E
|
||||
#define TB_KEY_CTRL_O 0x0F
|
||||
#define TB_KEY_CTRL_P 0x10
|
||||
#define TB_KEY_CTRL_Q 0x11
|
||||
#define TB_KEY_CTRL_R 0x12
|
||||
#define TB_KEY_CTRL_S 0x13
|
||||
#define TB_KEY_CTRL_T 0x14
|
||||
#define TB_KEY_CTRL_U 0x15
|
||||
#define TB_KEY_CTRL_V 0x16
|
||||
#define TB_KEY_CTRL_W 0x17
|
||||
#define TB_KEY_CTRL_X 0x18
|
||||
#define TB_KEY_CTRL_Y 0x19
|
||||
#define TB_KEY_CTRL_Z 0x1A
|
||||
#define TB_KEY_ESC 0x1B
|
||||
#define TB_KEY_CTRL_LSQ_BRACKET 0x1B /* clash with 'ESC' */
|
||||
#define TB_KEY_CTRL_3 0x1B /* clash with 'ESC' */
|
||||
#define TB_KEY_CTRL_4 0x1C
|
||||
#define TB_KEY_CTRL_BACKSLASH 0x1C /* clash with 'CTRL_4' */
|
||||
#define TB_KEY_CTRL_5 0x1D
|
||||
#define TB_KEY_CTRL_RSQ_BRACKET 0x1D /* clash with 'CTRL_5' */
|
||||
#define TB_KEY_CTRL_6 0x1E
|
||||
#define TB_KEY_CTRL_7 0x1F
|
||||
#define TB_KEY_CTRL_SLASH 0x1F /* clash with 'CTRL_7' */
|
||||
#define TB_KEY_CTRL_UNDERSCORE 0x1F /* clash with 'CTRL_7' */
|
||||
#define TB_KEY_SPACE 0x20
|
||||
#define TB_KEY_BACKSPACE2 0x7F
|
||||
#define TB_KEY_CTRL_8 0x7F /* clash with 'DELETE' */
|
||||
/* These are non-existing ones.
|
||||
*
|
||||
* #define TB_KEY_CTRL_1 clash with '1'
|
||||
* #define TB_KEY_CTRL_9 clash with '9'
|
||||
* #define TB_KEY_CTRL_0 clash with '0'
|
||||
*/
|
||||
/* Some aliases */
|
||||
#define TB_KEY_NEWLINE TB_KEY_CTRL_J
|
||||
#define TB_KEY_CARRIAGE_RETURN TB_KEY_CTRL_M
|
||||
|
||||
/* Wait for an event up to 'timeout' milliseconds and fill the 'event'
|
||||
* structure with it, when the event is available. Returns the type of the
|
||||
* event (one of TB_EVENT_* constants) or -1 if there was an error or 0 in case
|
||||
* there were no event during 'timeout' period.
|
||||
*/
|
||||
int tb_peek_event(struct tb_event *event, int timeout);
|
||||
|
||||
/* Wait for an event forever and fill the 'event' structure with it, when the
|
||||
* event is available. Returns the type of the event (one of TB_EVENT_*
|
||||
* constants) or -1 if there was an error.
|
||||
*/
|
||||
int tb_poll_event(struct tb_event *event);
|
||||
|
||||
int tb_event_ready(void);
|
||||
|
||||
/*** 3. Utility utf8 functions. */
|
||||
#define TB_EOF -1
|
||||
int tb_utf8_char_length(char c);
|
||||
int tb_utf8_char_to_unicode(uint32_t *out, const char *c);
|
||||
int tb_utf8_unicode_to_char(char *out, uint32_t c);
|
||||
const char* to_unicode(uint32_t c);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,79 @@
|
|||
#include "termbox.h"
|
||||
|
||||
static const unsigned char utf8_length[256] = {
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
||||
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
|
||||
};
|
||||
|
||||
static const unsigned char utf8_mask[6] = {
|
||||
0x7F,
|
||||
0x1F,
|
||||
0x0F,
|
||||
0x07,
|
||||
0x03,
|
||||
0x01
|
||||
};
|
||||
|
||||
int tb_utf8_char_length(char c)
|
||||
{
|
||||
return utf8_length[(unsigned char)c];
|
||||
}
|
||||
|
||||
int tb_utf8_char_to_unicode(uint32_t *out, const char *c)
|
||||
{
|
||||
if (*c == 0)
|
||||
return TB_EOF;
|
||||
|
||||
int i;
|
||||
unsigned char len = tb_utf8_char_length(*c);
|
||||
unsigned char mask = utf8_mask[len-1];
|
||||
uint32_t result = c[0] & mask;
|
||||
for (i = 1; i < len; ++i) {
|
||||
result <<= 6;
|
||||
result |= c[i] & 0x3f;
|
||||
}
|
||||
|
||||
*out = result;
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
int tb_utf8_unicode_to_char(char *out, uint32_t c)
|
||||
{
|
||||
int len = 0;
|
||||
int first;
|
||||
int i;
|
||||
|
||||
if (c < 0x80) {
|
||||
first = 0;
|
||||
len = 1;
|
||||
} else if (c < 0x800) {
|
||||
first = 0xc0;
|
||||
len = 2;
|
||||
} else if (c < 0x10000) {
|
||||
first = 0xe0;
|
||||
len = 3;
|
||||
} else if (c < 0x200000) {
|
||||
first = 0xf0;
|
||||
len = 4;
|
||||
} else if (c < 0x4000000) {
|
||||
first = 0xf8;
|
||||
len = 5;
|
||||
} else {
|
||||
first = 0xfc;
|
||||
len = 6;
|
||||
}
|
||||
|
||||
for (i = len - 1; i > 0; --i) {
|
||||
out[i] = (c & 0x3f) | 0x80;
|
||||
c >>= 6;
|
||||
}
|
||||
out[0] = c | first;
|
||||
|
||||
return len;
|
||||
}
|
Loading…
Reference in New Issue