5800 - move `browse_trace` to `tools/` dir

This commit is contained in:
Kartik Agaram 2019-12-07 16:36:40 -08:00
parent 9e45cae061
commit f821c0e28b
15 changed files with 1575 additions and 294 deletions

View File

@ -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

View File

@ -1,7 +0,0 @@
#!/bin/sh
set -e
cd `dirname $0`
./build
cd -
`dirname $0`/browse_trace_bin $*

4
clean
View File

@ -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; )

View File

@ -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`.

21
tools/browse_trace Executable file
View File

@ -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 $*

View File

@ -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;
}
}

View File

@ -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

19
tools/termbox/COPYING Normal file
View File

@ -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.

2
tools/termbox/Readme Normal file
View File

@ -0,0 +1,2 @@
Fork of https://github.com/nsf/termbox as of 2015-06-05
git hash 252bef01264a2aeef22ebe5bae0b5893a58947f3

View File

@ -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;
}

185
tools/termbox/input.inl Normal file
View File

@ -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;
}

320
tools/termbox/output.inl Normal file
View File

@ -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);
}
}

397
tools/termbox/termbox.c Normal file
View File

@ -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;
}
}
}

190
tools/termbox/termbox.h Normal file
View File

@ -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

79
tools/termbox/utf8.c Normal file
View File

@ -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;
}