boxen

Create boxes in the terminal
git clone https://noulin.net/git/boxen.git
Log | Files | Refs | README | LICENSE

commit a519945d8fdbb3248dd0a886a257f552dc4866e6
parent de641c02cf3534abc80eef4f44e9924b1dccc208
Author: Remy Noulin <loader2x@gmail.com>
Date:   Wed,  2 Jan 2019 12:04:48 +0100

add utf8 support, hex colors, support for ansi sequences in input

boxen.c     | 203 +++++++++++++++++++++++++++++++++++++++---------------------
boxen.h     |  12 ++--
main.c      |   6 +-
package.yml |   2 +-
4 files changed, 145 insertions(+), 78 deletions(-)

Diffstat:
Mboxen.c | 203++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mboxen.h | 12+++++++-----
Mmain.c | 6+++---
Mpackage.yml | 2+-
4 files changed, 145 insertions(+), 78 deletions(-)

diff --git a/boxen.c b/boxen.c @@ -93,20 +93,22 @@ void initiateBoxen(boxent *self) { } self->f = boxenF; - self->borderColor = ""; - self->borderStyle = singleBorderStyle; - self->dimBorder = ""; - self->padding.top = 0; - self->padding.left = 0; - self->padding.right = 0; - self->padding.bottom = 0; - self->margin.top = 0; - self->margin.left = 0; - self->margin.right = 0; - self->margin.bottom = 0; - self->boxFloat = "left"; - self->backgroundColor = ""; - self->align = "left"; + self->borderColor = ""; + self->borderColorHex = 0; + self->borderStyle = singleBorderStyle; + self->dimBorder = ""; + self->padding.top = 0; + self->padding.left = 0; + self->padding.right = 0; + self->padding.bottom = 0; + self->margin.top = 0; + self->margin.left = 0; + self->margin.right = 0; + self->margin.bottom = 0; + self->boxFloat = "left"; + self->backgroundColor = ""; + self->backgroundColorHex = 0; + self->align = "left"; } void registerMethodsBoxen(boxenFunctionst *f) { @@ -255,10 +257,59 @@ internal const char* helpBoxen(boxent UNUSED *self) { ret "TODO - boxen help"; } +// https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences +// [\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007) +// (?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])) +internal void stripAnsiSequences(char *string) { + size_t i = 0,j = 0; + enum {SEARCH_SEQ, CTRL_CODE}; + u8 status = SEARCH_SEQ; + while(string[i]) { + if (status == SEARCH_SEQ) { + if (string[i] == '\x1b' || string[i] == '\x9b') { + // dont copy sequence + status = CTRL_CODE; + } + else { + // copy other char + string[j++] = string[i]; + } + } + if (status == CTRL_CODE and string[i] == 'm') { + // all libsheepy codes end with 'm' + status = SEARCH_SEQ; + } + i++; + } + string[j] = 0; +} + #define border(bord) self->borderStyle.bord #define oborderColor(color, value)\ if (icEqG(getG(&j, rtChar, "borderColor"), color)) {\ - self->borderColor = value;\ + self->borderColor = value;\ + self->borderColorHex = 0;\ + } +#define oHexColor(color, opt, optHex)\ + if (getG(&j, rtChar, color) && getG(&j, rtChar, color)[0] == '#') {\ + /* hex color */\ + char *c = getG(&j, rtChar, color);\ + /* check if color is 0 */\ + bool is0 = true;\ + size_t i = 1;\ + while(c[i]) if (c[i++] != '0') {is0 = false; break;}\ + \ + self->opt = BLK;\ + if (!is0) {\ + /* convert color string to int */\ + char *s = catS("0x", &c[1]);\ + self->optHex = parseHex(s);\ + free(s);\ + if (!self->optHex) {\ + /* invalid hex number */\ + self->opt = "";\ + }\ + }\ } #define oborderStyle(style, value)\ if (icEqG(getG(&j, rtChar, "borderStyle"), style)) {\ @@ -274,7 +325,8 @@ internal const char* helpBoxen(boxent UNUSED *self) { } #define obackgroundColor(color, value)\ if (icEqG(getG(&j, rtChar, "backgroundColor"), color)) {\ - self->backgroundColor = value;\ + self->backgroundColor = value;\ + self->backgroundColorHex = 0;\ } #define oalign(fl, value)\ if (icEqG(getG(&j, rtChar, "align"), fl)) {\ @@ -286,11 +338,6 @@ internal char *boxBoxen(boxent *self, char *input, char *opts) { char *r = NULL; - // TODO add utf8 support - // TODO - // hex colors - // remove control codes - // options if (opts) { @@ -305,6 +352,7 @@ internal char *boxBoxen(boxent *self, char *input, char *opts) { else oborderColor ( "cyan" , CYN) else oborderColor ( "white" , BLD WHT) else oborderColor ( "gray" , WHT) + else oHexColor("borderColor", borderColor, borderColorHex) elif (isEStringG(&j, "borderColor")) { // invalid value: reset self->borderColor = ""; @@ -360,6 +408,7 @@ internal char *boxBoxen(boxent *self, char *input, char *opts) { else obackgroundColor ( "cyan" , BGCYN) else obackgroundColor ( "white" , BGWHT) // TODO RGB? else obackgroundColor ( "gray" , BGWHT) + else oHexColor("backgroundColor", backgroundColor, backgroundColorHex) elif (isEStringG(&j, "backgroundColor")) { // invalid value: reset self->backgroundColor = ""; @@ -374,23 +423,26 @@ internal char *boxBoxen(boxent *self, char *input, char *opts) { var lines = splitG(input, '\n'); - var widest = widestLine(lines); + var noansi = dupG(lines); + forEachS(noansi, l) { + stripAnsiSequences(l); + } + //var widest = widestLine(lines); + var widest = widestLine(noansi); // align text if (eqG(self->align, "center")) { - forEachCharP(lines, l) { - var algWidth = (widest - lenG(*l)) / 2; - range(i, algWidth) { - prependG(l, " "); - } + enumerateCharP(lines, l, i) { + var algWidth = (widest - lenUTF8(noansi[i])) / 2; + prependNFreeG(l, repeatS(" ", algWidth)); + prependNFreeG(&noansi[i], repeatS(" ", algWidth)); } } elif (eqG(self->align, "right")) { - forEachCharP(lines, l) { - var algWidth = widest - lenG(*l); - range(i, algWidth) { - prependG(l, " "); - } + enumerateCharP(lines, l, i) { + var algWidth = widest - lenUTF8(noansi[i]); + prependNFreeG(l, repeatS(" ", algWidth)); + prependNFreeG(&noansi[i], repeatS(" ", algWidth)); } } // align left - no-op @@ -406,9 +458,7 @@ internal char *boxBoxen(boxent *self, char *input, char *opts) { var contentWidth = widest + self->padding.left + self->padding.right; char *paddingLeft = NULL; - range(i, self->padding.left) { - pushG(&paddingLeft, " "); - } + pushNFreeG(&paddingLeft, repeatS(" ", self->padding.left)); winSizet sz = wsize(); // window cols @@ -417,55 +467,74 @@ internal char *boxBoxen(boxent *self, char *input, char *opts) { // float if (eqG(self->boxFloat, "center")) { var padWidth = maxV((sz.cols - contentWidth) / 2, 0); - range(i, padWidth) { - pushG(&marginLeft, " "); - } + pushNFreeG(&marginLeft, repeatS(" ", padWidth)); } elif (eqG(self->boxFloat, "right")) { var padWidth = maxV(sz.cols - contentWidth - self->margin.right - 2, 0); - range(i, padWidth) { - pushG(&marginLeft, " "); - } + pushNFreeG(&marginLeft, repeatS(" ", padWidth)); } else { // left - range(i, self->margin.left) { - pushG(&marginLeft, " "); - } + pushNFreeG(&marginLeft, repeatS(" ", self->margin.left)); } - // top - range(i, self->margin.top) { - pushG(&r, '\n'); + // colors + char *borderColorS = NULL; + if (!self->borderColor[0]) { + emptyS(borderColorS); + } + elif (self->borderColorHex) { + u32 c = self->borderColorHex; + borderColorS = formatS("\x1b[38;2;%d;%d;%dm", c>>16, (c&0xFF00)>>8, c&0xFF); + } + else { + borderColorS = dupG(self->borderColor); + } + char *backgroundColorS = NULL; + if (!self->backgroundColor[0]) { + emptyS(backgroundColorS); + } + elif (self->backgroundColorHex) { + u32 c = self->backgroundColorHex; + backgroundColorS = formatS("\x1b[48;2;%d;%d;%dm", c>>16, (c&0xFF00)>>8, c&0xFF); + } + else { + backgroundColorS = dupG(self->backgroundColor); } + + // top + pushNFreeG(&r, repeatS("\n", self->margin.top)); pushG(&r, marginLeft); pushG(&r, self->dimBorder); - pushG(&r, self->borderColor); + pushG(&r, borderColorS); pushG(&r, border(topLeft)); - range(i, contentWidth) { - pushG(&r, border(horizontal)); - } + pushNFreeG(&r, repeatS(border(horizontal), contentWidth)); pushG(&r, border(topRight)); pushG(&r, RST"\n"); // middle - forEachS(lines, l) { + enumerateS(lines, l, i) { pushG(&r, marginLeft); pushG(&r, self->dimBorder); - pushG(&r, self->borderColor); + pushG(&r, borderColorS); pushG(&r, border(vertical)); pushG(&r, RST); - pushG(&r, self->backgroundColor); + pushG(&r, backgroundColorS); pushG(&r, paddingLeft); pushG(&r, l); - char *paddingRight = NULL; - range(i, contentWidth - lenG(l) - self->padding.left) { - pushG(&paddingRight, " "); + // restore background color after text + pushG(&r, backgroundColorS); + // paddingRight + i64 idx = i - self->padding.top; + if (idx >= 0 and idx < lenG(noansi)) { + pushNFreeG(&r, repeatS(" ", contentWidth - lenUTF8(noansi[idx]) - self->padding.left)); + } + else { + pushNFreeG(&r, repeatS(" ", contentWidth - lenUTF8(l) - self->padding.left)); } - pushNFreeG(&r, paddingRight); pushG(&r, RST); pushG(&r, self->dimBorder); - pushG(&r, self->borderColor); + pushG(&r, borderColorS); pushG(&r, border(vertical)); pushG(&r, RST); pushG(&r, '\n'); @@ -474,21 +543,17 @@ internal char *boxBoxen(boxent *self, char *input, char *opts) { // bottom pushG(&r, marginLeft); pushG(&r, self->dimBorder); - pushG(&r, self->borderColor); + pushG(&r, borderColorS); pushG(&r, border(bottomLeft)); - range(i, contentWidth) { - pushG(&r, border(horizontal)); - } + pushNFreeG(&r, repeatS(border(horizontal), contentWidth)); pushG(&r, border(bottomRight)); pushG(&r, RST); - range(i, self->margin.bottom) { - pushG(&r, '\n'); - } + pushNFreeG(&r, repeatS("\n", self->margin.bottom)); // free + freeG(noansi); freeG(lines); - free(paddingLeft); - free(marginLeft); + freeManyS(paddingLeft, marginLeft, borderColorS, backgroundColorS); ret r; } @@ -497,7 +562,7 @@ internal size_t widestLine(char **text) { size_t r = 0; if (!text) ret 0; forEachS(text, l) { - r = MAX(r, lenG(l)); + r = MAX(r, lenUTF8(l)); } ret r; } diff --git a/boxen.h b/boxen.h @@ -157,14 +157,16 @@ struct boxen { const char *type; boxenFunctionst *f; - char *borderColor; + char *borderColor; + u32 borderColorHex; borderStyleBoxent borderStyle; - char *dimBorder; + char *dimBorder; struct {u32 top; u32 left; u32 right; u32 bottom;} padding; struct {u32 top; u32 left; u32 right; u32 bottom;} margin; - char *boxFloat; - char *backgroundColor; - char *align; + char *boxFloat; + char *backgroundColor; + u32 backgroundColorHex; + char *align; }; /* boxen */ diff --git a/main.c b/main.c @@ -22,14 +22,14 @@ int main(int ARGC, char** ARGV) { createBoxen(box); logNFree(boxO(&box, "unicorn", NULL)); - logNFree(boxO(&box, "unicorn\nand\nthe sheepy", - "{borderColor: 'Yellow',\n\ + logNFree(boxO(&box, BLD RED"unicornâś”"RST"\nand\nthe sheepy", + "{borderColor: '#FFFF00',\n\ borderStyle: 'double',\n\ dimBorder: true,\n\ padding: {top: 1, left: 2, right: 2, bottom: 2},\n\ margin: {top: 1, left: 1, right: 1, bottom: 1},\n\ float: 'right',\n\ - backgroundColor: 'green',\n\ + backgroundColor: '#0000FF',\n\ align: 'center'}")); // options are optional logNFree(boxO(&box, "unicorn\nand\nthe sheepy", diff --git a/package.yml b/package.yml @@ -1,6 +1,6 @@ --- name: boxen - version: 0.0.1 + version: 0.0.2 description: "Create boxes in the terminal" bin: ./boxen.c #cflags: -DA -ggdb -std=gnu11 -fPIC -pipe