/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/*
Copyright 2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt
SPDX-License-Identifier: BSD-2-Clause-FreeBSD
SPDX-FileCopyrightText: 2021 The Libfossil Authors
SPDX-ArtifactOfProjectName: Libfossil
SPDX-FileType: Code
Heavily indebted to the Fossil SCM project (https://fossil-scm.org).
*/
/*
This file holds a custom fsl_dibu implementation which
renders its output to an ncurses PAD
*/
#include "libfossil.h"
#include <ncurses.h>
#include <panel.h>
#include <stdarg.h>
#include <locale.h>
#include "fsl-ncurses.h"
void fnc_screen_init(void){
if(NULL!=stdscr) return;
setlocale(LC_ALL, "")/*needed for ncurses w/ UTF8 chars*/;
initscr();
nonl(); keypad(stdscr,TRUE); cbreak();
if(has_colors()){
start_color();
init_pair(FNColorIndex, COLOR_BLUE, COLOR_BLACK);
init_pair(FNColorChunkSplitter, COLOR_YELLOW, COLOR_BLACK);
init_pair(FNColorInsert, COLOR_GREEN, COLOR_BLACK);
init_pair(FNColorDelete, COLOR_RED, COLOR_BLACK);
init_pair(FNColorNote, COLOR_WHITE, COLOR_RED);
}
curs_set(0);
}
void fnc_screen_shutdown(void){
if(NULL!=stdscr){
endwin();
}
}
/**
Characters used in rendering scrollbars.
*/
static const struct NCScrollbarConfig {
char const * vTop;
char const * vBottom;
char const * hLeft;
char const * hRight;
char const * curPos;
char const * filler;
} NCScrollbarConfig = {
"△",//vTop
"▽",//vBottom
"◁",//hLeft
"▷",//hRight
"▣",//curPos
"░"//filler
};
/**
Renders a vertical scrollbar on window tgt at column x, running
from the given top row to the row (top+barHeight). The scroll
indicator is rendered depending on the final two arguments:
lineCount specifies the number of lines in the scrollable
input and currentLine specifies the top-most line number of the
currently-displayed data. e.g. a lineCount of 100 and currentLine
of 1 puts the indicator 1% of the way down the scrollbar. Likewise,
a lineCount of 100 and currentLine of 90 puts the indicator at the
90% point.
*/
void fsl_nc_scrollbar_v( WINDOW * const tgt, int top, int x,
int barHeight, int lineCount,
int currentLine ){
int y, bottom = top + barHeight - 1,
dotPos = top + (int)((double)currentLine / lineCount * barHeight) + 1;
struct NCScrollbarConfig const * const conf = &NCScrollbarConfig;
unsigned int const attr = A_REVERSE;
wattron(tgt, attr);
mvwprintw(tgt, top, x, "%s", conf->vTop);
for(y = top+1; y<bottom; ++y){
mvwprintw(tgt, y, x, "%s", conf->filler);
}
wattroff(tgt, attr);
mvwprintw(tgt, dotPos>=bottom?bottom-1:dotPos, x,
"%s", conf->curPos);
wattron(tgt, attr);
mvwprintw(tgt, bottom, x, "%s", conf->vBottom);
wattroff(tgt, attr);
}
/**
Works like fsl_nc_scrollbar_v() but renders a horizontal scrollbar
at the given left/top coordinates of tgt.
*/
void fsl_nc_scrollbar_h( WINDOW * const tgt, int top, int left,
int barWidth, int colCount,
int currentCol ){
int x, right = left + barWidth - 1,
dotPos = left + (int)((double)currentCol / colCount * barWidth) + 1;
struct NCScrollbarConfig const * const conf = &NCScrollbarConfig;
unsigned int const attr = A_REVERSE;
wattron(tgt, attr);
mvwprintw(tgt, top, left, "%s", conf->hLeft);
for(x = left+1; x<right; ++x){
mvwprintw(tgt, top, x, "%s", conf->filler);
}
wattroff(tgt, attr);
mvwprintw(tgt, top, dotPos>=x?x-1:dotPos,
"%s", conf->curPos);
wattron(tgt, attr);
mvwprintw(tgt, top, x, "%s", conf->hRight);
wattroff(tgt, attr);
}
static const fsl_dibu_ncu fsl_dibu_ncu_empty = {
{0,0,1,0,0},{0,0},
0/*displayLines*/,
0/*displayWidth*/,
NULL/*pad*/,
{/*cursor*/0,0},
fsl_buffer_empty_m
};
#define DSTATE(VNAME) fsl_dibu_ncu * const VNAME = (fsl_dibu_ncu *)b->pimpl
#if 0
static int fdb__out(fsl_dibu *const b,
char const *z, fsl_size_t n){
return b->opt->out(b->opt->outState, z, n);
}
static int fdb__outf(fsl_dibu * const b,
char const *fmt, ...){
int rc = 0;
if(NULL==stdscr){
va_list va;
assert(b->opt->out);
va_start(va,fmt);
rc = fsl_appendfv(b->opt->out, b->opt->outState, fmt, va);
va_end(va);
}
return rc;
}
#endif
#if 0
static int maxColWidth(fsl_dibu const * const b,
fsl_dibu_ncu const * const sst,
int mwIndex){
static const short minColWidth =
10/*b->opt.columnWidth values smaller than this are treated as
this value*/;
switch(mwIndex){
case FDBCols_NUM1:
case FDBCols_NUM2:
case FDBCols_MOD: return sst->maxWidths[mwIndex];
case FDBCols_TEXT1: case FDBCols_TEXT2: break;
default:
assert(!"internal API misuse: invalid column index.");
break;
}
int const y =
(b->opt->columnWidth>0
&& b->opt->columnWidth<=sst->maxWidths[mwIndex])
? (int)b->opt->columnWidth
: (int)sst->maxWidths[mwIndex];
return minColWidth > y ? minColWidth : y;
}
#endif
static void fdb__dico_update_maxlen(fsl_dibu_ncu * const sst,
int col,
char const * const z,
uint32_t n){
switch(col){
case FDBCols_TEXT1: n+=2; break;
default: break;
}
if(sst->maxWidths[col]<n){
sst->maxWidths[col] = n;
//n = (uint32_t)fsl_strlen_utf8(z, (fsl_int_t)n);
//if(sst->maxWidths[col]<n) sst->maxWidths[col] = n;
}
}
static int fdb__ncu_pline(fsl_dibu * const b,
unsigned ncattr,
char const *fmt,
...){
DSTATE(sst);
va_list args;
va_start(args, fmt);
fsl_buffer_appendfv(fsl_buffer_reuse(&sst->buf), fmt, args);
#if 0
if(sst->buf.used > sst->displayWidth){
/* ^^^ achtung, counted in bytes */
sst->buf.used = sst->displayWidth;
sst->buf.mem[sst->buf.used] = 0;
}
#endif
if(ncattr) wattron(sst->pad, ncattr);
mvwaddstr(sst->pad, sst->cursor.y++, 0,
fsl_buffer_cstr(&sst->buf));
if(ncattr) wattroff(sst->pad, ncattr);
va_end(args);
return 0;
}
static int fdb__ncu_start(fsl_dibu * const b){
int rc = 0;
DSTATE(sst);
if(1==b->passNumber){
if(1==++b->fileCount){
assert(!sst->pad);
*sst = fsl_dibu_ncu_empty;
}
if(0==(FSL_DIFF2_NOINDEX & b->opt->diffFlags)){
sst->displayLines += 2;
if(b->fileCount>1){
++sst->displayLines/*gap before 2nd+ index line*/;
}
}
sst->displayLines += 2;
return rc;
}
unsigned int attr = COLOR_PAIR(FNColorIndex);
if(0==(FSL_DIFF2_NOINDEX & b->opt->diffFlags)){
if(sst->cursor.y){/*gap before 2nd+ index line*/;
fdb__ncu_pline(b, attr,"");
}
fdb__ncu_pline(b, attr,"Index #%d: %s", (int)b->fileCount,b->opt->nameLHS/*RHS?*/);
fdb__ncu_pline(b, attr,"%.*c", (int)sst->displayWidth, '=');
}
fdb__ncu_pline(b, attr,"--- %s", b->opt->nameLHS);
fdb__ncu_pline(b, attr,"+++ %s", b->opt->nameRHS);
return rc;
}
static int fdb__ncu_chunkHeader(fsl_dibu* const b,
uint32_t lnnoLHS, uint32_t linesLHS,
uint32_t lnnoRHS, uint32_t linesRHS ){
if(1==b->passNumber){
DSTATE(sst);
#if 0
if(FSL_DIFF2_LINE_NUMBERS & b->opt->diffFlags){
++sst->displayLines;
}
#endif
++sst->displayLines;
return 0;
}
unsigned int const attr = COLOR_PAIR(FNColorChunkSplitter);
#if 0
if(FSL_DIFF2_LINE_NUMBERS & b->opt->diffFlags){
fdb__ncu_pline(b,attr, "%.40c", '~');
}
#endif
return fdb__ncu_pline(b,attr, "@@ -%" PRIu32 ",%" PRIu32
" +%" PRIu32 ",%" PRIu32 " @@",
lnnoLHS, linesLHS, lnnoRHS, linesRHS);
}
static int fdb__ncu_skip(fsl_dibu * const b, uint32_t n){
if(1==b->passNumber){
b->lnLHS += n;
b->lnRHS += n;
}
return 0;
}
/**
TODO: stuff the line numbers, if enabled, in a buffer in b->pimpl, and have
fdb__ncu_pline() prefix that if it's not empty.
*/
static int fdb__ncu_lineno(fsl_dibu * const b, uint32_t lnL, uint32_t lnR){
int rc = 0;
if(FSL_DIFF2_LINE_NUMBERS & b->opt->diffFlags){
#if 0
rc = lnL
? fdb__outf(b, "%s%6" PRIu32 "%s ",
(lnR ? "" : b->opt->ansiColor.deletion),
lnL,
(lnR ? "" : b->opt->ansiColor.reset))
: fdb__out(b, " ", 7);
if(0==rc){
rc = lnR
? fdb__outf(b, "%s%6" PRIu32 "%s ",
(lnL ? "" : b->opt->ansiColor.insertion),
lnR,
(lnL ? "" : b->opt->ansiColor.reset))
: fdb__out(b, " ", 7);
}
#endif
}
return rc;
}
static int fdb__ncu_common(fsl_dibu * const b, fsl_dline const * pLine){
DSTATE(sst);
++b->lnLHS;
++b->lnRHS;
if(1==b->passNumber){
++sst->displayLines;
fdb__dico_update_maxlen(sst, FDBCols_TEXT1, pLine->z, pLine->n);
//fdb__dico_update_maxlen(sst, FDBCols_TEXT2, pLine->z, pLine->n);
return 0;
}
const int rc = fdb__ncu_lineno(b, b->lnLHS, b->lnRHS);
return rc ? rc : fdb__ncu_pline(b, 0, " %.*s", (int)pLine->n, pLine->z);
}
static int fdb__ncu_insertion(fsl_dibu * const b, fsl_dline const * pLine){
DSTATE(sst);
++b->lnRHS;
if(1==b->passNumber){
++sst->displayLines;
fdb__dico_update_maxlen(sst, FDBCols_TEXT1, pLine->z, pLine->n);
return 0;
}
const int rc = fdb__ncu_lineno(b, 0, b->lnRHS);
return rc ? rc
: fdb__ncu_pline(b, COLOR_PAIR(FNColorInsert)|A_BOLD,
"+%.*s\n",
(int)pLine->n, pLine->z);
return rc;
}
static int fdb__ncu_deletion(fsl_dibu * const b, fsl_dline const * pLine){
DSTATE(sst);
++b->lnLHS;
if(1==b->passNumber){
++sst->displayLines;
fdb__dico_update_maxlen(sst, FDBCols_TEXT1, pLine->z, pLine->n);
return 0;
}
const int rc = fdb__ncu_lineno(b, b->lnLHS, 0);
return rc ? rc
: fdb__ncu_pline(b, COLOR_PAIR(FNColorDelete)|A_BOLD,
"-%.*s\n",
(int)pLine->n, pLine->z);
}
static int fdb__ncu_replacement(fsl_dibu * const b,
fsl_dline const * lineLhs,
fsl_dline const * lineRhs) {
int rc = b->deletion(b, lineLhs);
if(0==rc) rc = b->insertion(b, lineRhs);
return rc;
}
static int fdb__ncu_edit(fsl_dibu * const b,
fsl_dline const * lineLHS,
fsl_dline const * lineRHS){
int rc = b->deletion(b, lineLHS);
if(0==rc) rc = b->insertion(b, lineRHS);
return rc;
}
static int fdb__ncu_finish(fsl_dibu * const b){
int rc = 0;
DSTATE(sst);
if(1==b->passNumber){
sst->lineCount[0] = b->lnLHS;
sst->lineCount[1] = b->lnRHS;
if(sst->pad){
int w, h, maxW;
getmaxyx(sst->pad, h, w);
if(h){/*unused but we need it for the getmaxyx() call!*/}
maxW = (int)sst->displayWidth>=w+2
? (int)sst->displayWidth
: w+2;
if(wresize(sst->pad, sst->displayLines, maxW)){
rc = FSL_RC_OOM;
}
sst->displayWidth = (unsigned)maxW;
}else{
sst->displayWidth = 5/*semi-magic: avoids RHS truncation*/;
for( int i = 0; i<FDBCols_count; ++i ){
sst->displayWidth += sst->maxWidths[i];
}
++sst->displayLines/* "END" marker */;
sst->pad = newpad((int)sst->displayLines, (int)sst->displayWidth);
if(!sst->pad) rc = FSL_RC_OOM;
}
if(FSL_RC_OOM==rc){
rc = fcli_err_set(rc, "Could not (re)allocate PAD for "
"diff display.");
}else{
fsl_buffer_reserve(&sst->buf, sst->displayWidth*2);
}
return rc;
}
return rc;
}
static int fdb__ncu_finally(fsl_dibu * const b){
DSTATE(sst);
return fdb__ncu_pline(b, A_REVERSE, "(END)%.*c",
(int)sst->displayWidth-5, '=');
}
static void fsl__diff_builder_ncu_finalizer(fsl_dibu * const b){
DSTATE(sst);
fsl_buffer_clear(&sst->buf);
if(sst->pad){
delwin(sst->pad);
}
*sst = fsl_dibu_ncu_empty;
*b = fsl_dibu_empty;
fsl_free(b);
}
bool fsl_dibu_is_ncu(fsl_dibu const * const b){
return b && b->typeID==&fsl_dibu_ncu_empty;
}
fsl_dibu_ncu * fsl_dibu_ncu_pimpl(fsl_dibu * const b){
return b->typeID==&fsl_dibu_ncu_empty
? (fsl_dibu_ncu*)b->pimpl : NULL;
}
fsl_dibu * fsl_dibu_ncu_alloc(void){
fsl_dibu * rc =
fsl_dibu_alloc((fsl_size_t)sizeof(fsl_dibu_ncu));
if(rc){
rc->typeID = &fsl_dibu_ncu_empty;
rc->chunkHeader = fdb__ncu_chunkHeader;
rc->start = fdb__ncu_start;
rc->skip = fdb__ncu_skip;
rc->common = fdb__ncu_common;
rc->insertion = fdb__ncu_insertion;
rc->deletion = fdb__ncu_deletion;
rc->replacement = fdb__ncu_replacement;
rc->edit = fdb__ncu_edit;
rc->finish = fdb__ncu_finish;
rc->finally = fdb__ncu_finally;
rc->finalize = fsl__diff_builder_ncu_finalizer;
rc->twoPass = true;
assert(0!=rc->pimpl);
fsl_dibu_ncu * const sst = (fsl_dibu_ncu*)rc->pimpl;
*sst = fsl_dibu_ncu_empty;
assert(0==rc->implFlags);
assert(0==rc->lnLHS);
assert(0==rc->lnRHS);
assert(NULL==rc->opt);
}
return rc;
}
void fsl_dibu_ncu_basic_loop(fsl_dibu_ncu * const nc,
WINDOW * const w){
int pTop = 0, pLeft = 0;
int wW/*window width*/, wH/*window height*/,
wTop/*window Y*/, wLeft/*window X*/,
pW/*pad width*/, pH/*pad height*/,
vH/*viewport height*/, vW/*viewport width*/,
ch/*wgetch() result*/;
int const hScroll = 2/*cols to shift for horizontal scroll*/;
bool showHeader = 1;
if(!nc->pad){
mvwaddstr(stdscr,1,0,"No diff output generated. Tap a key.");
wrefresh(stdscr);
wgetch(stdscr);
return;
}
keypad(w,TRUE);
cbreak();
getyx(w,wTop,wLeft);
getmaxyx(nc->pad,pH,pW);
noecho();
while(1){
getmaxyx(w,wH,wW)/*can change dynamically*/;
//touchwin(w);
bool const scrollV = wH<pH;
bool const scrollH = wW<pW;
vH = wH - scrollV;
vW = wW - wLeft - scrollH;
if(vH>=LINES){
vH = LINES - scrollH;
}
if(vW>=COLS-1){
vW = COLS-wLeft-scrollH;
}
if(pTop + wH - scrollH >= pH) pTop = pH - wH + scrollH;
if(pTop<0) pTop=0;
if(pLeft<0) pLeft=0;
else if(pW - pLeft < vW) pLeft = pW - vW;
if(showHeader){
unsigned int attr = COLOR_PAIR(FNColorNote) | A_BOLD | A_BLINK;
char const * zLabel = "Arrows scroll, 'q'uits, ctrl-l refreshes.";
wattron(w, attr);
mvwprintw(w,1,(vW/2 - fsl_strlen(zLabel)/2), zLabel);
wattroff(w, attr);
showHeader = false;
}
prefresh(nc->pad, pTop, pLeft, wTop, wLeft, vH,vW);
if(scrollV) fsl_nc_scrollbar_v(w, 0, wW-1, wH-1+!scrollH,
pH - vH, pTop);
if(scrollH) fsl_nc_scrollbar_h(w, wH-1, 0, wW-1+!scrollV,
pW - vW, pLeft);
doupdate();
ch = wgetch(w);
if('q'==ch) break;
switch(ch){
case KEY_HOME: pTop = 0; break;
case KEY_END: pTop = pH - vH;
case KEY_DOWN: ++pTop; break;
case KEY_UP: --pTop; break;
case KEY_NPAGE: pTop += vH; break;
case KEY_PPAGE: pTop -= vH; break;
case KEY_LEFT: pLeft -= hScroll; break;
case KEY_RIGHT: pLeft += hScroll; break;
case KEY_ctrl('l'):
wclear(w);
wrefresh(w);
break;
}
}
}