over-env/render.cpp

316 lines
6.3 KiB
C++

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <unistr.h>
#define ATTR_NONE 0
#define ATTR_BOLD 1
#define ATTR_UNDER 4
#define ATTR_BLINK 5
#define SYM_GIT "\ue0a0"
#define SYM_LN "\ue0a1"
#define SYM_LOCK "\ue0a2"
#define SYM_RIGHTBLK "\ue0b0"
#define SYM_RIGHT "\ue0b1"
#define SYM_LEFTBLK "\ue0b2"
#define SYM_LEFT "\ue0b3"
#define PATH_MAX 4096
#ifndef uint
#define uint unsigned int
#endif
/* -------------------------------------------------- */
void term_size(uint *columns, uint *rows) {
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
if (columns) *columns = w.ws_col;
if (rows) *rows = w.ws_row;
}
/* -------------------------------------------------- */
class Config {
public:
Config(int argc, char **argv) {
if (argc != 3) {
fprintf(stderr, "Usage: %s red_threshold yellow_threshold\n", argv[0]);
this->failed = true;
return;
}
this->red_thresh = strtof(argv[1], NULL);
this->yellow_thresh = strtof(argv[2], NULL);
term_size(&(this->term_width), NULL);
this->failed = false;
}
uint term_width;
float red_thresh;
float yellow_thresh;
bool failed;
};
class Segment {
public:
Segment(const char *str, bool printable=true) {
this->raw_length = strlen(str);
this->raw_str = new char[(this->raw_length + 1) * sizeof(char)]; // +1 for null
strcpy(this->raw_str, str);
if (printable) {
this->printable_length = u8_mbsnlen((const uint8_t*)str, this->raw_length);
} else {
this->printable_length = 0;
}
}
~Segment(void) {
delete[] this->raw_str;
}
char *raw_str;
size_t raw_length;
size_t printable_length;
};
class Output {
public:
Output(void) {
this->printable_length = 0;
this->raw_length = 0;
this->ptr = 0;
this->max = 256; // a reasonable amount?
this->segments = new Segment*[this->max];
this->set_colors(7, 0);
}
~Output(void) {
for (uint i = 0; i < this->ptr; i++) {
delete this->segments[i];
}
delete[] this->segments;
}
void output(FILE *stream) {
for (uint i = 0; i < this->ptr; i++) {
fputs(this->segments[i]->raw_str, stream);
}
}
void set_colors(uint fg, uint bg, const char *transition=NULL) {
if (transition) {
this->push(" ");
}
this->_push_color(bg, true);
if (transition) {
this->_push_color(this->col_bg);
this->push(transition);
this->push(" ");
}
this->_push_color(fg);
this->col_fg = fg;
this->col_bg = bg;
}
void _push_color(uint color=ATTR_NONE, bool bg=false) {
if (color == ATTR_NONE) {
this->set_attr();
} else {
char tmp[16];
snprintf(tmp, 16, "\e[%d;5;%dm", (bg? 48:38), color);
this->push(new Segment(tmp, false));
}
}
void set_attr(uint attr=ATTR_NONE) {
char tmp[10];
snprintf(tmp, 10, "\e[%dm", attr);
this->push(new Segment(tmp, false));
}
void push(Segment *segment) {
if (this->ptr + 1 == this->max) {
fprintf(stderr, "Output.segments full, this will probably fail.\n");
}
this->segments[this->ptr++] = segment;
this->printable_length += segment->printable_length;
this->raw_length += segment->raw_length;
}
void push(const char *str) {
this->push(new Segment(str));
}
protected:
Segment **segments;
size_t ptr, max;
size_t raw_length;
size_t printable_length;
uint col_fg, col_bg;
};
void render_screen(Output *output) {
char *tmp = getenv("WINDOW");
if (tmp) {
output->push(":");
output->push(tmp);
}
}
void render_path(Output *output) {
char buf[PATH_MAX];
getcwd(buf, PATH_MAX);
// tokenize path TODO replace homes with ~
char *ptr = buf;
char *ptr_end;
char *tokens[100]; // a sensible default?
char token[4096];
uint token_count = 0;
uint token_length;
while (*ptr) { // until NULL terminator
ptr = strchrnul(ptr, '/') + 1;
ptr_end = strchrnul(ptr, '/');
token_length = ptr_end - ptr;
memcpy(token, ptr, token_length);
token[token_length] = 0;
if (token_length) {
tokens[token_count] = new char[(token_length + 1) * sizeof(char)];
memcpy(tokens[token_count], token, token_length);
tokens[token_count][token_length] = 0;
token_count++;
}
}
// render path with increasing background brightness
uint fg = 202;
uint bg = 233;
for (uint i = 0; i < token_count; i++) {
if (bg == 241) fg = 232;
if (bg < 255) {
output->set_colors(fg, bg++, SYM_RIGHTBLK);
} else {
output->push(" ");
output->push(SYM_RIGHT);
output->push(" ");
}
output->push(tokens[i]);
delete[] tokens[i];
}
// if cwd is not writable, show a warning
if (access(".", W_OK) != 0) {
output->set_colors(232, 88, SYM_RIGHTBLK);
output->push(SYM_LOCK);
}
}
void render_git(Output *output) {
FILE *pipe;
char branch_name[1024];
pipe = popen("git rev-parse --abbrev-ref HEAD 2> /dev/null", "r");
if (pipe) {
if (fgets(branch_name, 1024, pipe)) {
branch_name[strlen(branch_name) - 1] = 0; // kill the newline
// rebuild index
system("git update-index -q --ignore-submodules --refresh");
// so that I can check if there are unstaged changes
if (system("git diff-files --quiet --ignore-submodules")) {
output->set_colors(232, 88, SYM_RIGHTBLK);
} else {
output->set_colors(232, 46, SYM_RIGHTBLK);
}
output->push(SYM_GIT);
output->push(" ");
output->push(branch_name);
}
pclose(pipe);
} else {
fprintf(stderr, "--- failed to run git\n");
}
}
/* -------------------------------------------------- */
int main(int argc, char **argv) {
Config config(argc, argv);
if (config.failed) return 1;
/* --------------------------------------------------
* output init
*/
Output top_left;
Output top_path;
Output top_right;
/* --------------------------------------------------
* user@host[:screen]
*/
char buf[PATH_MAX];
if (geteuid() == 0) {
top_left.set_colors(232, 124); // red bg
} else {
top_left.set_colors(232, 202); // aware orange bg
}
top_left.push(" ");
top_left.push(getlogin());
top_left.set_colors(202, 232, SYM_RIGHTBLK);
gethostname(buf, 256);
top_left.push(buf);
render_screen(&top_left);
render_path(&top_left);
render_git(&top_left);
/* --------------------------------------------------
* end
*/
top_left.set_colors(7, 0, SYM_RIGHTBLK);
/* --------------------------------------------------
* finally, output the prompt
*/
top_left.output(stdout);
return 0;
}