systemSetup

system setup, configuration and dotfiles
git clone https://noulin.net/git/systemSetup.git
Log | Files | Refs | README | LICENSE

commit 4923a9e0ec552eaf668da550c10942a83a027442
parent fd26c3f028b6c4fd57c094ddda96811a134bea51
Author: Remy Noulin <loader2x@gmail.com>
Date:   Wed, 11 Jun 2025 16:59:18 +0200

replace fzf with my version of fzy, add my history system

Use addHistoryFromFile.c to add current bash history to my history system
Run:
h > bashHist.txt
addHistoryFromFile bashHist.txt
rm bashHist.txt

debian/3-home.sh              |  13 --
debian/6-home.sh              |   6 -
debian/updateHomeNoGui.sh     |  16 ++
dotfiles/.bash_aliases        |   3 +-
dotfiles/.bashrc              |   6 +-
dotfiles/addHistoryFromFile.c | 105 +++++++++++
dotfiles/atMyHistory.c        |  41 +++++
dotfiles/fzy/bonus.h          | 108 +++++++++++
dotfiles/fzy/choices.c        | 325 +++++++++++++++++++++++++++++++++
dotfiles/fzy/choices.h        |  41 +++++
dotfiles/fzy/config.def.h     |  19 ++
dotfiles/fzy/config.h         |  20 +++
dotfiles/fzy/fzy.c            |  76 ++++++++
dotfiles/fzy/match.c          | 238 +++++++++++++++++++++++++
dotfiles/fzy/match.h          |  16 ++
dotfiles/fzy/options.c        | 127 +++++++++++++
dotfiles/fzy/options.h        |  21 +++
dotfiles/fzy/tty.c            | 201 +++++++++++++++++++++
dotfiles/fzy/tty.h            |  60 +++++++
dotfiles/fzy/tty_interface.c  | 405 ++++++++++++++++++++++++++++++++++++++++++
dotfiles/fzy/tty_interface.h  |  28 +++
dotfiles/revShowMyHistory.c   |  33 ++++
dotfiles/saveHistory.c        | 175 ++++++++++++++++++
dotfiles/showMyHistory.c      | 105 +++++++++++
24 files changed, 2166 insertions(+), 22 deletions(-)

Diffstat:
Mdebian/3-home.sh | 13-------------
Mdebian/6-home.sh | 6------
Mdebian/updateHomeNoGui.sh | 16++++++++++++++++
Mdotfiles/.bash_aliases | 3+--
Mdotfiles/.bashrc | 6+++++-
Adotfiles/addHistoryFromFile.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adotfiles/atMyHistory.c | 41+++++++++++++++++++++++++++++++++++++++++
Adotfiles/fzy/bonus.h | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adotfiles/fzy/choices.c | 325+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adotfiles/fzy/choices.h | 41+++++++++++++++++++++++++++++++++++++++++
Adotfiles/fzy/config.def.h | 19+++++++++++++++++++
Adotfiles/fzy/config.h | 20++++++++++++++++++++
Adotfiles/fzy/fzy.c | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adotfiles/fzy/match.c | 238+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adotfiles/fzy/match.h | 16++++++++++++++++
Adotfiles/fzy/options.c | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adotfiles/fzy/options.h | 21+++++++++++++++++++++
Adotfiles/fzy/tty.c | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adotfiles/fzy/tty.h | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adotfiles/fzy/tty_interface.c | 405+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adotfiles/fzy/tty_interface.h | 28++++++++++++++++++++++++++++
Adotfiles/revShowMyHistory.c | 33+++++++++++++++++++++++++++++++++
Adotfiles/saveHistory.c | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adotfiles/showMyHistory.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
24 files changed, 2166 insertions(+), 22 deletions(-)

diff --git a/debian/3-home.sh b/debian/3-home.sh @@ -8,19 +8,6 @@ git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim # in vim run :PluginInstall echo 'in vim run :PluginInstall' -# fzf -git clone https://github.com/junegunn/fzf.git -cd fzf -./install --all -cd .. - -# docker for non-root users -echo 'Setting up docker for non-root users' -sudo gpasswd -a $USER docker -# reinitialize environment to load the new group -# starts a new shell - not good -# newgrp - - # setup diff-so-fancy in gitconfig ## commented because it is in .gitconfig git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" diff --git a/debian/6-home.sh b/debian/6-home.sh @@ -8,12 +8,6 @@ git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim # in vim run :PluginInstall echo 'in vim run :PluginInstall' -# fzf -git clone https://github.com/junegunn/fzf.git -cd fzf -./install --all -cd .. - # setup diff-so-fancy in gitconfig git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" git config --global color.ui true diff --git a/debian/updateHomeNoGui.sh b/debian/updateHomeNoGui.sh @@ -43,5 +43,21 @@ mkdir -p ~/.config/ranger/colorschemes cp ../dotfiles/rc.conf ~/.config/ranger/ cp ../dotfiles/default.py ~/.config/ranger/colorschemes/ +# My history +cp ../dotfiles/saveHistory.c ~/bin/ +cp ../dotfiles/showMyHistory.c ~/bin/ +cp ../dotfiles/revShowMyHistory.c ~/bin/ +cp ../dotfiles/atMyHistory.c ~/bin/ +cp ../dotfiles/addHistoryFromFile.c ~/bin/ + +sheepy -c ~/bin/saveHistory.c +sheepy -c ~/bin/showMyHistory.c +sheepy -c ~/bin/revShowMyHistory.c +sheepy -c ~/bin/atMyHistory.c +sheepy -c ~/bin/addHistoryFromFile.c + +sheepy -c ../dotfiles/fzy/fzy.c +cp ../dotfiles/fzy/fzy ~/bin/ + # save current commit in home ./systemSetupCommit.sh diff --git a/dotfiles/.bash_aliases b/dotfiles/.bash_aliases @@ -20,7 +20,7 @@ alias cs='column -s , -t' alias new='~/bin/new.sh' alias mk='source ~/bin/mk.sh' alias r='ranger' -alias h='history | less +G' +alias h='showMyHistory | less +G' alias vd='vimdiff' alias noc="sed 's/\x1b\[[0-9;]*[a-zA-Z]//g'" alias td='tree -d' @@ -142,4 +142,3 @@ alias N='~/bin/N.sh' alias lsb="export LS_COLORS=\"no=00:fi=00:di=01;34:ln=01;35:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:tw=30;42:ow=34;42:st=37;44:ex=01;31:*.tar=01;31:*.tgz=01;31:*.svgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.aac=00;35:*.au=00;35:*.flac=00;35:*.mid=00;35:*.midi=00;35:*.mka=00;35:*.mp3=00;35:*.mpc=00;35:*.ogg=00;35:*.ra=00;35:*.wav=00;35:\"" alias lsd="export LS_COLORS=\"no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.svgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:\"" - diff --git a/dotfiles/.bashrc b/dotfiles/.bashrc @@ -7,7 +7,6 @@ # append to the history file, don't overwrite it shopt -s histappend -export PROMPT_COMMAND='history -a' # don't put duplicate lines in the history. See bash(1) for more options export HISTCONTROL=ignoredups @@ -119,3 +118,8 @@ fi unset LC_PAPER LC_ADDRESS LC_MONETARY LC_NUMERIC LC_TELEPHONE LC_IDENTIFICATION LC_MEASUREMENT LC_TIME LC_NAME . /usr/share/autojump/autojump.sh + +# My history +export PROMPT_COMMAND='rs=$? ; lastCommand=`history 1 | cut -c 26-` ; ~/bin/saveHistory $rs "$lastCommand"' +bind -x '"\C-r": READLINE_LINE=`~/bin/revShowMyHistory | ~/bin/fzy -l 20`' +bind -x '"\C-f": READLINE_LINE=`~/bin/atMyHistory ${READLINE_LINE}`' diff --git a/dotfiles/addHistoryFromFile.c b/dotfiles/addHistoryFromFile.c @@ -0,0 +1,105 @@ +#! /usr/bin/env sheepy +#include "libsheepyObject.h" + +#define HB "~/myhistory.bin" + +// command is equal to +char *filter[] = { + "h", + "fg", + "bg", + "p", + "gu", + "gg", + "gh", + "gs", + "g lg", + "l", + "ls", + "la", + "history", + "E", + "env", + "df", + "dfh", + "du", + "duu", + "exit", + "logout", + "top", + "htop", + "reset", + null +}; +// command starts with +char *start[] = { + "fg ", + "whal ", + "l ", + "ls ", + "la ", + "df ", + "dfh ", + "du ", + "duu ", + "history|", + null +}; + + +int main(int ARGC, char** ARGV) { + + // bashHist.txt + // 1 24-10-24 20:56:35 ssw + + if (!ARGV[1] or not isPath(ARGV[1])) { + logE("Input not found.\n" + "Usage: addHistoryFromFile bashHistoryFile.txt"); + ret 1; + } + + createAllocateSmallArray(h); + readFileG(h, ARGV[1]); + + cleanCharP(hbp) = expandHomeG(HB); + + iter(h, L) { + char *l = ssGet(L); + char *d = l + 7; + char *cmd = l + 25; + *(cmd-1) = 0; + cleanCharP(date) = catS("20", d); + /* logVarG(date); */ + /* logVarG(cmd); */ + + // filter commands + bool skip = no; + forEachS(filter, s) { + if (eqG(cmd, s)) { skip = yes; break;} + } + forEachS(start, s) { + if (startsWithG(cmd, s)) { skip = yes; break;} + } + if (skip) continue; + + // add commands + + cleanAllocateSmallJson(a); + // save history as binary + // a = [path,exit code, date, command] + pushG(a, ""/*path*/); + pushG(a, "0" /*exit code*/); + pushG(a, date); + pushG(a, cmd); + smallBytest *b = serialG(a); + u8 buf[65536]; + u32 len = lenG(b) + 4*2; + memcpy(buf, &len, 4); + memcpy(buf+4, getValO(b), lenG(b)); + memcpy(buf+4+lenG(b), &len, 4); + pError0(appendFile(hbp, buf, len)); + } + + ret 0; +} +// vim: set expandtab ts=2 sw=2: diff --git a/dotfiles/atMyHistory.c b/dotfiles/atMyHistory.c @@ -0,0 +1,41 @@ +#! /usr/bin/env sheepy +#include "libsheepyObject.h" +#define H "~//myhistory.txt" +#define HE "~//myhistoryExit.txt" +#define HP "~//myhistoryPath.txt" +#define HB "~//myhistory.bin" + +int main(int ARGC, char** ARGV) { + initLibsheepy(ARGV[0]); + + if (not ARGV[1]) ret 0; + + u64 index = parseInt(ARGV[1]); + + cleanCharP(hbp) = expandHomeG(HB); + + u8 *buf = null; + size_t hlen = readFile(hbp, (void**)&buf); + + u8 *e = buf; + cleanAllocateSmallJson(a); + + u64 count = 1; + do { + u32 len = *(u32*)e; + if (count == index) { + cleanAllocateSmallBytes(b); + o(b,set, e+4, len-8); + deserialG(a, b); + // a = [path,exit code, date, command] + puts(getG(a, rtChar, 3)); + break; + } + e += len; + inc count; + + } while(e < buf + hlen); + + ret 0; +} +// vim: set expandtab ts=2 sw=2: diff --git a/dotfiles/fzy/bonus.h b/dotfiles/fzy/bonus.h @@ -0,0 +1,108 @@ +#ifndef BONUS_H +#define BONUS_H BONUS_H + +#include "config.h" + +#define ASSIGN_LOWER(v) \ + ['a'] = (v), \ + ['b'] = (v), \ + ['c'] = (v), \ + ['d'] = (v), \ + ['e'] = (v), \ + ['f'] = (v), \ + ['g'] = (v), \ + ['h'] = (v), \ + ['i'] = (v), \ + ['j'] = (v), \ + ['k'] = (v), \ + ['l'] = (v), \ + ['m'] = (v), \ + ['n'] = (v), \ + ['o'] = (v), \ + ['p'] = (v), \ + ['q'] = (v), \ + ['r'] = (v), \ + ['s'] = (v), \ + ['t'] = (v), \ + ['u'] = (v), \ + ['v'] = (v), \ + ['w'] = (v), \ + ['x'] = (v), \ + ['y'] = (v), \ + ['z'] = (v) + +#define ASSIGN_UPPER(v) \ + ['A'] = (v), \ + ['B'] = (v), \ + ['C'] = (v), \ + ['D'] = (v), \ + ['E'] = (v), \ + ['F'] = (v), \ + ['G'] = (v), \ + ['H'] = (v), \ + ['I'] = (v), \ + ['J'] = (v), \ + ['K'] = (v), \ + ['L'] = (v), \ + ['M'] = (v), \ + ['N'] = (v), \ + ['O'] = (v), \ + ['P'] = (v), \ + ['Q'] = (v), \ + ['R'] = (v), \ + ['S'] = (v), \ + ['T'] = (v), \ + ['U'] = (v), \ + ['V'] = (v), \ + ['W'] = (v), \ + ['X'] = (v), \ + ['Y'] = (v), \ + ['Z'] = (v) + +#define ASSIGN_DIGIT(v) \ + ['0'] = (v), \ + ['1'] = (v), \ + ['2'] = (v), \ + ['3'] = (v), \ + ['4'] = (v), \ + ['5'] = (v), \ + ['6'] = (v), \ + ['7'] = (v), \ + ['8'] = (v), \ + ['9'] = (v) + +const score_t bonus_states[3][256] = { + { 0 }, + { + ['/'] = SCORE_MATCH_SLASH, + ['-'] = SCORE_MATCH_WORD, + ['_'] = SCORE_MATCH_WORD, + [' '] = SCORE_MATCH_WORD, + ['.'] = SCORE_MATCH_DOT, + }, + { + ['/'] = SCORE_MATCH_SLASH, + ['-'] = SCORE_MATCH_WORD, + ['_'] = SCORE_MATCH_WORD, + [' '] = SCORE_MATCH_WORD, + ['.'] = SCORE_MATCH_DOT, + + /* ['a' ... 'z'] = SCORE_MATCH_CAPITAL, */ + ASSIGN_LOWER(SCORE_MATCH_CAPITAL) + } +}; + +const size_t bonus_index[256] = { + /* ['A' ... 'Z'] = 2 */ + ASSIGN_UPPER(2), + + /* ['a' ... 'z'] = 1 */ + ASSIGN_LOWER(1), + + /* ['0' ... '9'] = 1 */ + ASSIGN_DIGIT(1) +}; + +#define COMPUTE_BONUS(last_ch, ch) (bonus_states[bonus_index[(unsigned char)(ch)]][(unsigned char)(last_ch)]) + +#endif diff --git a/dotfiles/fzy/choices.c b/dotfiles/fzy/choices.c @@ -0,0 +1,325 @@ +#define _GNU_SOURCE // strcasestr +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <pthread.h> +#include <unistd.h> +#include <errno.h> + +#include "options.h" +#include "choices.h" +#include "match.h" + +/* Initial size of buffer for storing input in memory */ +#define INITIAL_BUFFER_CAPACITY 4096 + +/* Initial size of choices array */ +#define INITIAL_CHOICE_CAPACITY 128 + +static int cmpchoice(const void *_idx1, const void *_idx2) { + const struct scored_result *a = _idx1; + const struct scored_result *b = _idx2; + + if (a->score == b->score) { + /* To ensure a stable sort, we must also sort by the string + * pointers. We can do this since we know all the strings are + * from a contiguous memory segment (buffer in choices_t). + */ + if (a->str < b->str) { + return -1; + } else { + return 1; + } + } else if (a->score < b->score) { + return 1; + } else { + return -1; + } +} + +static void *safe_realloc(void *buffer, size_t size) { + buffer = realloc(buffer, size); + if (!buffer) { + fprintf(stderr, "Error: Can't allocate memory (%zu bytes)\n", size); + abort(); + } + + return buffer; +} + +void choices_fread(choices_t *c, FILE *file, char input_delimiter) { + /* Save current position for parsing later */ + size_t buffer_start = c->buffer_size; + + /* Resize buffer to at least one byte more capacity than our current + * size. This uses a power of two of INITIAL_BUFFER_CAPACITY. + * This must work even when c->buffer is NULL and c->buffer_size is 0 + */ + size_t capacity = INITIAL_BUFFER_CAPACITY; + while (capacity <= c->buffer_size) + capacity *= 2; + c->buffer = safe_realloc(c->buffer, capacity); + + /* Continue reading until we get a "short" read, indicating EOF */ + while ((c->buffer_size += fread(c->buffer + c->buffer_size, 1, capacity - c->buffer_size, file)) == capacity) { + capacity *= 2; + c->buffer = safe_realloc(c->buffer, capacity); + } + c->buffer = safe_realloc(c->buffer, c->buffer_size + 1); + c->buffer[c->buffer_size++] = '\0'; + + /* Truncate buffer to used size, (maybe) freeing some memory for + * future allocations. + */ + + /* Tokenize input and add to choices */ + const char *line_end = c->buffer + c->buffer_size; + char *line = c->buffer + buffer_start; + do { + char *nl = strchr(line, input_delimiter); + if (nl) + *nl++ = '\0'; + + /* Skip empty lines */ + if (*line) + choices_add(c, line); + + line = nl; + } while (line && line < line_end); +} + +static void choices_resize(choices_t *c, size_t new_capacity) { + c->strings = safe_realloc(c->strings, new_capacity * sizeof(const char *)); + c->capacity = new_capacity; +} + +static void choices_reset_search(choices_t *c) { + free(c->results); + c->selection = c->available = 0; + c->results = NULL; +} + +void choices_init(choices_t *c, options_t *options) { + c->strings = NULL; + c->results = NULL; + + c->buffer_size = 0; + c->buffer = NULL; + + c->capacity = c->size = 0; + choices_resize(c, INITIAL_CHOICE_CAPACITY); + + if (options->workers) { + c->worker_count = options->workers; + } else { + c->worker_count = (int)sysconf(_SC_NPROCESSORS_ONLN); + } + + choices_reset_search(c); +} + +void choices_destroy(choices_t *c) { + free(c->buffer); + c->buffer = NULL; + c->buffer_size = 0; + + free(c->strings); + c->strings = NULL; + c->capacity = c->size = 0; + + free(c->results); + c->results = NULL; + c->available = c->selection = 0; +} + +void choices_add(choices_t *c, const char *choice) { + /* Previous search is now invalid */ + choices_reset_search(c); + + if (c->size == c->capacity) { + choices_resize(c, c->capacity * 2); + } + c->strings[c->size++] = choice; +} + +size_t choices_available(choices_t *c) { + return c->available; +} + +#define BATCH_SIZE 512 + +struct result_list { + struct scored_result *list; + size_t size; +}; + +struct search_job { + pthread_mutex_t lock; + choices_t *choices; + const char *search; + size_t processed; + struct worker *workers; +}; + +struct worker { + pthread_t thread_id; + struct search_job *job; + unsigned int worker_num; + struct result_list result; +}; + +static void worker_get_next_batch(struct search_job *job, size_t *start, size_t *end) { + pthread_mutex_lock(&job->lock); + + *start = job->processed; + + job->processed += BATCH_SIZE; + if (job->processed > job->choices->size) { + job->processed = job->choices->size; + } + + *end = job->processed; + + pthread_mutex_unlock(&job->lock); +} + +static struct result_list merge2(struct result_list list1, struct result_list list2) { + size_t result_index = 0, index1 = 0, index2 = 0; + + struct result_list result; + result.size = list1.size + list2.size; + result.list = malloc(result.size * sizeof(struct scored_result)); + if (!result.list) { + fprintf(stderr, "Error: Can't allocate memory\n"); + abort(); + } + + while(index1 < list1.size && index2 < list2.size) { + if (cmpchoice(&list1.list[index1], &list2.list[index2]) < 0) { + result.list[result_index++] = list1.list[index1++]; + } else { + result.list[result_index++] = list2.list[index2++]; + } + } + + while(index1 < list1.size) { + result.list[result_index++] = list1.list[index1++]; + } + while(index2 < list2.size) { + result.list[result_index++] = list2.list[index2++]; + } + + free(list1.list); + free(list2.list); + + return result; +} + +static void *choices_search_worker(void *data) { + struct worker *w = (struct worker *)data; + struct search_job *job = w->job; + const choices_t *c = job->choices; + struct result_list *result = &w->result; + + size_t start, end; + + for(;;) { + worker_get_next_batch(job, &start, &end); + + if(start == end) { + break; + } + + for(size_t i = start; i < end; i++) { + if (has_match(job->search, c->strings[i])) { + result->list[result->size].str = c->strings[i]; + result->list[result->size].score = match(job->search, c->strings[i]); + result->size++; + } + } + } + + /* Sort the partial result */ + qsort(result->list, result->size, sizeof(struct scored_result), cmpchoice); + + /* Fan-in, merging results */ + for(unsigned int step = 0;; step++) { + if (w->worker_num % (2 << step)) + break; + + unsigned int next_worker = w->worker_num | (1 << step); + if (next_worker >= c->worker_count) + break; + + if ((errno = pthread_join(job->workers[next_worker].thread_id, NULL))) { + perror("pthread_join"); + exit(EXIT_FAILURE); + } + + w->result = merge2(w->result, job->workers[next_worker].result); + } + + return NULL; +} + +void choices_search(choices_t *c, const char *search) { + choices_reset_search(c); + + struct scored_result *list = malloc(c->size * sizeof(struct scored_result)); /* FIXME: This is overkill */ + size_t size = 0; + + char *s = strdup(search); + size_t len = strlen(search); + + for (size_t a = 0; a < len ; a++) { + if (s[a] == ' ') s[a] = 0; + } + + for(size_t i = 0 ; i < c->size ; i++) { + char *p = s; + const char *t = c->strings[i]; + size_t lenh = strlen(c->strings[i]); + int foundAllWords = 1; + do { + char *h = strcasestr(t, p); + if (!h) { + foundAllWords = 0; + break; + } + t = h + strlen(p); + p += strlen(p)+1; + } while((t < c->strings[i] + lenh) && (p < s + len)); + + if (foundAllWords) { + // match + list[size].str = c->strings[i]; + list[size].score = 1; + size++; + } + } + + c->results = list; + c->available = size; +} + +const char *choices_get(choices_t *c, size_t n) { + if (n < c->available) { + return c->results[n].str; + } else { + return NULL; + } +} + +score_t choices_getscore(choices_t *c, size_t n) { + return c->results[n].score; +} + +void choices_prev(choices_t *c) { + if (c->available) + c->selection = (c->selection + c->available - 1) % c->available; +} + +void choices_next(choices_t *c) { + if (c->available) + c->selection = (c->selection + 1) % c->available; +} diff --git a/dotfiles/fzy/choices.h b/dotfiles/fzy/choices.h @@ -0,0 +1,41 @@ +#ifndef CHOICES_H +#define CHOICES_H CHOICES_H + +#include <stdio.h> + +#include "match.h" +#include "options.h" + +struct scored_result { + score_t score; + const char *str; +}; + +typedef struct { + char *buffer; + size_t buffer_size; + + size_t capacity; + size_t size; + + const char **strings; + struct scored_result *results; + + size_t available; + size_t selection; + + unsigned int worker_count; +} choices_t; + +void choices_init(choices_t *c, options_t *options); +void choices_fread(choices_t *c, FILE *file, char input_delimiter); +void choices_destroy(choices_t *c); +void choices_add(choices_t *c, const char *choice); +size_t choices_available(choices_t *c); +void choices_search(choices_t *c, const char *search); +const char *choices_get(choices_t *c, size_t n); +score_t choices_getscore(choices_t *c, size_t n); +void choices_prev(choices_t *c); +void choices_next(choices_t *c); + +#endif diff --git a/dotfiles/fzy/config.def.h b/dotfiles/fzy/config.def.h @@ -0,0 +1,19 @@ +#define TTY_COLOR_HIGHLIGHT TTY_COLOR_YELLOW + +#define SCORE_GAP_LEADING -0.005 +#define SCORE_GAP_TRAILING -0.005 +#define SCORE_GAP_INNER -0.01 +#define SCORE_MATCH_CONSECUTIVE 1.0 +#define SCORE_MATCH_SLASH 0.9 +#define SCORE_MATCH_WORD 0.8 +#define SCORE_MATCH_CAPITAL 0.7 +#define SCORE_MATCH_DOT 0.6 + +/* Time (in ms) to wait for additional bytes of an escape sequence */ +#define KEYTIMEOUT 25 + +#define DEFAULT_TTY "/dev/tty" +#define DEFAULT_PROMPT "> " +#define DEFAULT_NUM_LINES 10 +#define DEFAULT_WORKERS 0 +#define DEFAULT_SHOW_INFO 0 diff --git a/dotfiles/fzy/config.h b/dotfiles/fzy/config.h @@ -0,0 +1,20 @@ +#define VERSION "1.0" +#define TTY_COLOR_HIGHLIGHT TTY_COLOR_YELLOW + +#define SCORE_GAP_LEADING -0.005 +#define SCORE_GAP_TRAILING -0.005 +#define SCORE_GAP_INNER -0.01 +#define SCORE_MATCH_CONSECUTIVE 1.0 +#define SCORE_MATCH_SLASH 0.9 +#define SCORE_MATCH_WORD 0.8 +#define SCORE_MATCH_CAPITAL 0.7 +#define SCORE_MATCH_DOT 0.6 + +/* Time (in ms) to wait for additional bytes of an escape sequence */ +#define KEYTIMEOUT 25 + +#define DEFAULT_TTY "/dev/tty" +#define DEFAULT_PROMPT "> " +#define DEFAULT_NUM_LINES 10 +#define DEFAULT_WORKERS 0 +#define DEFAULT_SHOW_INFO 0 diff --git a/dotfiles/fzy/fzy.c b/dotfiles/fzy/fzy.c @@ -0,0 +1,76 @@ +#! /usr/bin/env sheepy +/* or direct path to sheepy: #! /usr/local/bin/sheepy */ + +/* Libsheepy documentation: https://spartatek.se/libsheepy/ */ +#include "libsheepyObject.h" +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <limits.h> +#include <unistd.h> + +#include "match.h" +#include "tty.h" +#include "choices.h" +#include "options.h" +#include "tty_interface.h" + +#include "config.h" + +int main(int argc, char *argv[]) { + int r = 0; + + options_t options; + options_parse(&options, argc, argv); + + choices_t choices; + choices_init(&choices, &options); + + if (options.benchmark) { + if (!options.filter) { + fprintf(stderr, "Must specify -e/--show-matches with --benchmark\n"); + exit(EXIT_FAILURE); + } + choices_fread(&choices, stdin, options.input_delimiter); + for (int i = 0; i < options.benchmark; i++) + choices_search(&choices, options.filter); + } else if (options.filter) { + choices_fread(&choices, stdin, options.input_delimiter); + choices_search(&choices, options.filter); + for (size_t i = 0; i < choices_available(&choices); i++) { + if (options.show_scores) + printf("%f\t", choices_getscore(&choices, i)); + printf("%s\n", choices_get(&choices, i)); + } + } else { + /* interactive */ + + if (isatty(STDIN_FILENO)) + choices_fread(&choices, stdin, options.input_delimiter); + + tty_t tty; + tty_init(&tty, options.tty_filename); + + if (!isatty(STDIN_FILENO)) + choices_fread(&choices, stdin, options.input_delimiter); + + if (options.num_lines > choices.size) + options.num_lines = choices.size; + + int num_lines_adjustment = 1; + if (options.show_info) + num_lines_adjustment++; + + if (options.num_lines + num_lines_adjustment > tty_getheight(&tty)) + options.num_lines = tty_getheight(&tty) - num_lines_adjustment; + + tty_interface_t tty_interface; + tty_interface_init(&tty_interface, &tty, &choices, &options); + r = tty_interface_run(&tty_interface); + } + + choices_destroy(&choices); + + return r; +} diff --git a/dotfiles/fzy/match.c b/dotfiles/fzy/match.c @@ -0,0 +1,238 @@ +#include <ctype.h> +#include <string.h> +#include <strings.h> +#include <stdio.h> +#include <float.h> +#include <math.h> +#include <stdlib.h> + +#include "match.h" +#include "bonus.h" + +#include "config.h" + +char *strcasechr(const char *s, char c) { + const char accept[3] = {c, toupper(c), 0}; + return strpbrk(s, accept); +} + +int has_match(const char *needle, const char *haystack) { + while (*needle) { + char nch = *needle++; + + if (!(haystack = strcasechr(haystack, nch))) { + return 0; + } + haystack++; + } + return 1; +} + +#define SWAP(x, y, T) do { T SWAP = x; x = y; y = SWAP; } while (0) + +#define max(a, b) (((a) > (b)) ? (a) : (b)) + +struct match_struct { + int needle_len; + int haystack_len; + + char lower_needle[MATCH_MAX_LEN]; + char lower_haystack[MATCH_MAX_LEN]; + + score_t match_bonus[MATCH_MAX_LEN]; +}; + +static void precompute_bonus(const char *haystack, score_t *match_bonus) { + /* Which positions are beginning of words */ + char last_ch = '/'; + for (int i = 0; haystack[i]; i++) { + char ch = haystack[i]; + match_bonus[i] = COMPUTE_BONUS(last_ch, ch); + last_ch = ch; + } +} + +static void setup_match_struct(struct match_struct *match, const char *needle, const char *haystack) { + match->needle_len = strlen(needle); + match->haystack_len = strlen(haystack); + + if (match->haystack_len > MATCH_MAX_LEN || match->needle_len > match->haystack_len) { + return; + } + + for (int i = 0; i < match->needle_len; i++) + match->lower_needle[i] = tolower(needle[i]); + + for (int i = 0; i < match->haystack_len; i++) + match->lower_haystack[i] = tolower(haystack[i]); + + precompute_bonus(haystack, match->match_bonus); +} + +static inline void match_row(const struct match_struct *match, int row, score_t *curr_D, score_t *curr_M, const score_t *last_D, const score_t *last_M) { + int n = match->needle_len; + int m = match->haystack_len; + int i = row; + + const char *lower_needle = match->lower_needle; + const char *lower_haystack = match->lower_haystack; + const score_t *match_bonus = match->match_bonus; + + score_t prev_score = SCORE_MIN; + score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER; + + for (int j = 0; j < m; j++) { + if (lower_needle[i] == lower_haystack[j]) { + score_t score = SCORE_MIN; + if (!i) { + score = (j * SCORE_GAP_LEADING) + match_bonus[j]; + } else if (j) { /* i > 0 && j > 0*/ + score = max( + last_M[j - 1] + match_bonus[j], + + /* consecutive match, doesn't stack with match_bonus */ + last_D[j - 1] + SCORE_MATCH_CONSECUTIVE); + } + curr_D[j] = score; + curr_M[j] = prev_score = max(score, prev_score + gap_score); + } else { + curr_D[j] = SCORE_MIN; + curr_M[j] = prev_score = prev_score + gap_score; + } + } +} + +score_t match(const char *needle, const char *haystack) { + if (!*needle) + return SCORE_MIN; + + struct match_struct match; + setup_match_struct(&match, needle, haystack); + + int n = match.needle_len; + int m = match.haystack_len; + + if (m > MATCH_MAX_LEN || n > m) { + /* + * Unreasonably large candidate: return no score + * If it is a valid match it will still be returned, it will + * just be ranked below any reasonably sized candidates + */ + return SCORE_MIN; + } else if (n == m) { + /* Since this method can only be called with a haystack which + * matches needle. If the lengths of the strings are equal the + * strings themselves must also be equal (ignoring case). + */ + return SCORE_MAX; + } + + /* + * D[][] Stores the best score for this position ending with a match. + * M[][] Stores the best possible score at this position. + */ + score_t D[2][MATCH_MAX_LEN], M[2][MATCH_MAX_LEN]; + + score_t *last_D, *last_M; + score_t *curr_D, *curr_M; + + last_D = D[0]; + last_M = M[0]; + curr_D = D[1]; + curr_M = M[1]; + + for (int i = 0; i < n; i++) { + match_row(&match, i, curr_D, curr_M, last_D, last_M); + + SWAP(curr_D, last_D, score_t *); + SWAP(curr_M, last_M, score_t *); + } + + return last_M[m - 1]; +} + +score_t match_positions(const char *needle, const char *haystack, size_t *positions) { + if (!*needle) + return SCORE_MIN; + + struct match_struct match; + setup_match_struct(&match, needle, haystack); + + int n = match.needle_len; + int m = match.haystack_len; + + if (m > MATCH_MAX_LEN || n > m) { + /* + * Unreasonably large candidate: return no score + * If it is a valid match it will still be returned, it will + * just be ranked below any reasonably sized candidates + */ + return SCORE_MIN; + } else if (n == m) { + /* Since this method can only be called with a haystack which + * matches needle. If the lengths of the strings are equal the + * strings themselves must also be equal (ignoring case). + */ + if (positions) + for (int i = 0; i < n; i++) + positions[i] = i; + return SCORE_MAX; + } + + /* + * D[][] Stores the best score for this position ending with a match. + * M[][] Stores the best possible score at this position. + */ + score_t (*D)[MATCH_MAX_LEN], (*M)[MATCH_MAX_LEN]; + M = malloc(sizeof(score_t) * MATCH_MAX_LEN * n); + D = malloc(sizeof(score_t) * MATCH_MAX_LEN * n); + + score_t *last_D, *last_M; + score_t *curr_D, *curr_M; + + for (int i = 0; i < n; i++) { + curr_D = &D[i][0]; + curr_M = &M[i][0]; + + match_row(&match, i, curr_D, curr_M, last_D, last_M); + + last_D = curr_D; + last_M = curr_M; + } + + /* backtrace to find the positions of optimal matching */ + if (positions) { + int match_required = 0; + for (int i = n - 1, j = m - 1; i >= 0; i--) { + for (; j >= 0; j--) { + /* + * There may be multiple paths which result in + * the optimal weight. + * + * For simplicity, we will pick the first one + * we encounter, the latest in the candidate + * string. + */ + if (D[i][j] != SCORE_MIN && + (match_required || D[i][j] == M[i][j])) { + /* If this score was determined using + * SCORE_MATCH_CONSECUTIVE, the + * previous character MUST be a match + */ + match_required = + i && j && + M[i][j] == D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE; + positions[i] = j--; + break; + } + } + } + } + + score_t result = M[n - 1][m - 1]; + + free(M); + free(D); + + return result; +} diff --git a/dotfiles/fzy/match.h b/dotfiles/fzy/match.h @@ -0,0 +1,16 @@ +#ifndef MATCH_H +#define MATCH_H MATCH_H + +#include <math.h> + +typedef double score_t; +#define SCORE_MAX INFINITY +#define SCORE_MIN -INFINITY + +#define MATCH_MAX_LEN 1024 + +int has_match(const char *needle, const char *haystack); +score_t match_positions(const char *needle, const char *haystack, size_t *positions); +score_t match(const char *needle, const char *haystack); + +#endif diff --git a/dotfiles/fzy/options.c b/dotfiles/fzy/options.c @@ -0,0 +1,127 @@ +#include <getopt.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "options.h" + +#include "config.h" + +static const char *usage_str = + "" + "Usage: fzy [OPTION]...\n" + " -l, --lines=LINES Specify how many lines of results to show (default 10)\n" + " -p, --prompt=PROMPT Input prompt (default '> ')\n" + " -q, --query=QUERY Use QUERY as the initial search string\n" + " -e, --show-matches=QUERY Output the sorted matches of QUERY\n" + " -t, --tty=TTY Specify file to use as TTY device (default /dev/tty)\n" + " -s, --show-scores Show the scores of each match\n" + " -0, --read-null Read input delimited by ASCII NUL characters\n" + " -j, --workers NUM Use NUM workers for searching. (default is # of CPUs)\n" + " -i, --show-info Show selection info line\n" + " -h, --help Display this help and exit\n" + " -v, --version Output version information and exit\n"; + +static void usage(const char *argv0) { + fprintf(stderr, usage_str, argv0); +} + +static struct option longopts[] = {{"show-matches", required_argument, NULL, 'e'}, + {"query", required_argument, NULL, 'q'}, + {"lines", required_argument, NULL, 'l'}, + {"tty", required_argument, NULL, 't'}, + {"prompt", required_argument, NULL, 'p'}, + {"show-scores", no_argument, NULL, 's'}, + {"read-null", no_argument, NULL, '0'}, + {"version", no_argument, NULL, 'v'}, + {"benchmark", optional_argument, NULL, 'b'}, + {"workers", required_argument, NULL, 'j'}, + {"show-info", no_argument, NULL, 'i'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0}}; + +void options_init(options_t *options) { + /* set defaults */ + options->benchmark = 0; + options->filter = NULL; + options->init_search = NULL; + options->show_scores = 0; + options->scrolloff = 1; + options->tty_filename = DEFAULT_TTY; + options->num_lines = DEFAULT_NUM_LINES; + options->prompt = DEFAULT_PROMPT; + options->workers = DEFAULT_WORKERS; + options->input_delimiter = '\n'; + options->show_info = DEFAULT_SHOW_INFO; +} + +void options_parse(options_t *options, int argc, char *argv[]) { + options_init(options); + + int c; + while ((c = getopt_long(argc, argv, "vhs0e:q:l:t:p:j:i", longopts, NULL)) != -1) { + switch (c) { + case 'v': + printf("%s " VERSION " © 2014-2018 John Hawthorn\n", argv[0]); + exit(EXIT_SUCCESS); + case 's': + options->show_scores = 1; + break; + case '0': + options->input_delimiter = '\0'; + break; + case 'q': + options->init_search = optarg; + break; + case 'e': + options->filter = optarg; + break; + case 'b': + if (optarg) { + if (sscanf(optarg, "%d", &options->benchmark) != 1) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + } else { + options->benchmark = 100; + } + break; + case 't': + options->tty_filename = optarg; + break; + case 'p': + options->prompt = optarg; + break; + case 'j': + if (sscanf(optarg, "%u", &options->workers) != 1) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + break; + case 'l': { + int l; + if (!strcmp(optarg, "max")) { + l = INT_MAX; + } else if (sscanf(optarg, "%d", &l) != 1 || l < 3) { + fprintf(stderr, "Invalid format for --lines: %s\n", optarg); + fprintf(stderr, "Must be integer in range 3..\n"); + usage(argv[0]); + exit(EXIT_FAILURE); + } + options->num_lines = l; + } break; + case 'i': + options->show_info = 1; + break; + case 'h': + default: + usage(argv[0]); + exit(EXIT_SUCCESS); + } + } + if (optind != argc) { + usage(argv[0]); + exit(EXIT_FAILURE); + } +} diff --git a/dotfiles/fzy/options.h b/dotfiles/fzy/options.h @@ -0,0 +1,21 @@ +#ifndef OPTIONS_H +#define OPTIONS_H OPTIONS_H + +typedef struct { + int benchmark; + const char *filter; + const char *init_search; + const char *tty_filename; + int show_scores; + unsigned int num_lines; + unsigned int scrolloff; + const char *prompt; + unsigned int workers; + char input_delimiter; + int show_info; +} options_t; + +void options_init(options_t *options); +void options_parse(options_t *options, int argc, char *argv[]); + +#endif diff --git a/dotfiles/fzy/tty.c b/dotfiles/fzy/tty.c @@ -0,0 +1,201 @@ +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdarg.h> +#include <termios.h> +#include <sys/ioctl.h> +#include <sys/select.h> +#include <signal.h> +#include <errno.h> + +#include "tty.h" + +#include "config.h" + +void tty_reset(tty_t *tty) { + tcsetattr(tty->fdin, TCSANOW, &tty->original_termios); +} + +void tty_close(tty_t *tty) { + tty_reset(tty); + fclose(tty->fout); + close(tty->fdin); +} + +static void handle_sigwinch(int sig){ + (void)sig; +} + +void tty_init(tty_t *tty, const char *tty_filename) { + tty->fdin = open(tty_filename, O_RDONLY); + if (tty->fdin < 0) { + perror("Failed to open tty"); + exit(EXIT_FAILURE); + } + + tty->fout = fopen(tty_filename, "w"); + if (!tty->fout) { + perror("Failed to open tty"); + exit(EXIT_FAILURE); + } + + if (setvbuf(tty->fout, NULL, _IOFBF, 4096)) { + perror("setvbuf"); + exit(EXIT_FAILURE); + } + + if (tcgetattr(tty->fdin, &tty->original_termios)) { + perror("tcgetattr"); + exit(EXIT_FAILURE); + } + + struct termios new_termios = tty->original_termios; + + /* + * Disable all of + * ICANON Canonical input (erase and kill processing). + * ECHO Echo. + * ISIG Signals from control characters + * ICRNL Conversion of CR characters into NL + */ + new_termios.c_iflag &= ~(ICRNL); + new_termios.c_lflag &= ~(ICANON | ECHO | ISIG); + + if (tcsetattr(tty->fdin, TCSANOW, &new_termios)) + perror("tcsetattr"); + + tty_getwinsz(tty); + + tty_setnormal(tty); + + signal(SIGWINCH, handle_sigwinch); +} + +void tty_getwinsz(tty_t *tty) { + struct winsize ws; + if (ioctl(fileno(tty->fout), TIOCGWINSZ, &ws) == -1) { + tty->maxwidth = 80; + tty->maxheight = 25; + } else { + tty->maxwidth = ws.ws_col; + tty->maxheight = ws.ws_row; + } +} + +char tty_getchar(tty_t *tty) { + char ch; + int size = read(tty->fdin, &ch, 1); + if (size < 0) { + perror("error reading from tty"); + exit(EXIT_FAILURE); + } else if (size == 0) { + /* EOF */ + exit(EXIT_FAILURE); + } else { + return ch; + } +} + +int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal) { + fd_set readfs; + FD_ZERO(&readfs); + FD_SET(tty->fdin, &readfs); + + struct timespec ts = {timeout / 1000, (timeout % 1000) * 1000000}; + + sigset_t mask; + sigemptyset(&mask); + if (!return_on_signal) + sigaddset(&mask, SIGWINCH); + + int err = pselect( + tty->fdin + 1, + &readfs, + NULL, + NULL, + timeout < 0 ? NULL : &ts, + return_on_signal ? NULL : &mask); + + if (err < 0) { + if (errno == EINTR) { + return 0; + } else { + perror("select"); + exit(EXIT_FAILURE); + } + } else { + return FD_ISSET(tty->fdin, &readfs); + } +} + +static void tty_sgr(tty_t *tty, int code) { + tty_printf(tty, "%c%c%im", 0x1b, '[', code); +} + +void tty_setfg(tty_t *tty, int fg) { + if (tty->fgcolor != fg) { + tty_sgr(tty, 30 + fg); + tty->fgcolor = fg; + } +} + +void tty_setinvert(tty_t *tty) { + tty_sgr(tty, 7); +} + +void tty_setunderline(tty_t *tty) { + tty_sgr(tty, 4); +} + +void tty_setnormal(tty_t *tty) { + tty_sgr(tty, 0); + tty->fgcolor = 9; +} + +void tty_setnowrap(tty_t *tty) { + tty_printf(tty, "%c%c?7l", 0x1b, '['); +} + +void tty_setwrap(tty_t *tty) { + tty_printf(tty, "%c%c?7h", 0x1b, '['); +} + +void tty_newline(tty_t *tty) { + tty_printf(tty, "%c%cK\n", 0x1b, '['); +} + +void tty_clearline(tty_t *tty) { + tty_printf(tty, "%c%cK", 0x1b, '['); +} + +void tty_setcol(tty_t *tty, int col) { + tty_printf(tty, "%c%c%iG", 0x1b, '[', col + 1); +} + +void tty_moveup(tty_t *tty, int i) { + tty_printf(tty, "%c%c%iA", 0x1b, '[', i); +} + +void tty_printf(tty_t *tty, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + vfprintf(tty->fout, fmt, args); + va_end(args); +} + +void tty_putc(tty_t *tty, char c) { + fputc(c, tty->fout); +} + +void tty_flush(tty_t *tty) { + fflush(tty->fout); +} + +size_t tty_getwidth(tty_t *tty) { + return tty->maxwidth; +} + +size_t tty_getheight(tty_t *tty) { + return tty->maxheight; +} diff --git a/dotfiles/fzy/tty.h b/dotfiles/fzy/tty.h @@ -0,0 +1,60 @@ +#ifndef TTY_H +#define TTY_H TTY_H + +#include <termios.h> + +typedef struct { + int fdin; + FILE *fout; + struct termios original_termios; + int fgcolor; + size_t maxwidth; + size_t maxheight; +} tty_t; + +void tty_reset(tty_t *tty); +void tty_close(tty_t *tty); +void tty_init(tty_t *tty, const char *tty_filename); +void tty_getwinsz(tty_t *tty); +char tty_getchar(tty_t *tty); +int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal); + +void tty_setfg(tty_t *tty, int fg); +void tty_setinvert(tty_t *tty); +void tty_setunderline(tty_t *tty); +void tty_setnormal(tty_t *tty); +void tty_setnowrap(tty_t *tty); +void tty_setwrap(tty_t *tty); + +#define TTY_COLOR_BLACK 0 +#define TTY_COLOR_RED 1 +#define TTY_COLOR_GREEN 2 +#define TTY_COLOR_YELLOW 3 +#define TTY_COLOR_BLUE 4 +#define TTY_COLOR_MAGENTA 5 +#define TTY_COLOR_CYAN 6 +#define TTY_COLOR_WHITE 7 +#define TTY_COLOR_NORMAL 9 + +/* tty_newline + * Move cursor to the beginning of the next line, clearing to the end of the + * current line + */ +void tty_newline(tty_t *tty); + +/* tty_clearline + * Clear to the end of the current line without advancing the cursor. + */ +void tty_clearline(tty_t *tty); + +void tty_moveup(tty_t *tty, int i); +void tty_setcol(tty_t *tty, int col); + +void tty_printf(tty_t *tty, const char *fmt, ...); +void tty_putc(tty_t *tty, char c); +void tty_flush(tty_t *tty); + +size_t tty_getwidth(tty_t *tty); +size_t tty_getheight(tty_t *tty); + +#endif diff --git a/dotfiles/fzy/tty_interface.c b/dotfiles/fzy/tty_interface.c @@ -0,0 +1,405 @@ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "match.h" +#include "tty_interface.h" +#include "config.h" + +static int isprint_unicode(char c) { + return isprint(c) || c & (1 << 7); +} + +static int is_boundary(char c) { + return ~c & (1 << 7) || c & (1 << 6); +} + +static void clear(tty_interface_t *state) { + tty_t *tty = state->tty; + + tty_setcol(tty, 0); + size_t line = 0; + while (line++ < state->options->num_lines + (state->options->show_info ? 1 : 0)) { + tty_newline(tty); + } + tty_clearline(tty); + if (state->options->num_lines > 0) { + tty_moveup(tty, line - 1); + } + tty_flush(tty); +} + +static void draw_match(tty_interface_t *state, const char *choice, int selected) { + tty_t *tty = state->tty; + options_t *options = state->options; + char *search = state->last_search; + + int n = strlen(search); + size_t positions[MATCH_MAX_LEN]; + for (int i = 0; i < n + 1 && i < MATCH_MAX_LEN; i++) + positions[i] = -1; + + score_t score = match_positions(search, choice, &positions[0]); + + if (options->show_scores) { + if (score == SCORE_MIN) { + tty_printf(tty, "( ) "); + } else { + tty_printf(tty, "(%5.2f) ", score); + } + } + + if (selected) +#ifdef TTY_SELECTION_UNDERLINE + tty_setunderline(tty); +#else + tty_setinvert(tty); +#endif + + tty_setnowrap(tty); + for (size_t i = 0, p = 0; choice[i] != '\0'; i++) { + if (positions[p] == i) { + tty_setfg(tty, TTY_COLOR_HIGHLIGHT); + p++; + } else { + tty_setfg(tty, TTY_COLOR_NORMAL); + } + if (choice[i] == '\n') { + tty_putc(tty, ' '); + } else { + tty_printf(tty, "%c", choice[i]); + } + } + tty_setwrap(tty); + tty_setnormal(tty); +} + +static void draw(tty_interface_t *state) { + tty_t *tty = state->tty; + choices_t *choices = state->choices; + options_t *options = state->options; + + unsigned int num_lines = options->num_lines; + size_t start = 0; + size_t current_selection = choices->selection; + if (current_selection + options->scrolloff >= num_lines) { + start = current_selection + options->scrolloff - num_lines + 1; + size_t available = choices_available(choices); + if (start + num_lines >= available && available > 0) { + start = available - num_lines; + } + } + + tty_setcol(tty, 0); + tty_printf(tty, "%s%s", options->prompt, state->search); + tty_clearline(tty); + + if (options->show_info) { + tty_printf(tty, "\n[%lu/%lu]", choices->available, choices->size); + tty_clearline(tty); + } + + for (size_t i = start; i < start + num_lines; i++) { + tty_printf(tty, "\n"); + tty_clearline(tty); + const char *choice = choices_get(choices, i); + if (choice) { + draw_match(state, choice, i == choices->selection); + } + } + + if (num_lines + options->show_info) + tty_moveup(tty, num_lines + options->show_info); + + tty_setcol(tty, 0); + fputs(options->prompt, tty->fout); + for (size_t i = 0; i < state->cursor; i++) + fputc(state->search[i], tty->fout); + tty_flush(tty); +} + +static void update_search(tty_interface_t *state) { + choices_search(state->choices, state->search); + strcpy(state->last_search, state->search); +} + +static void update_state(tty_interface_t *state) { + if (strcmp(state->last_search, state->search)) { + update_search(state); + draw(state); + } +} + +static void action_emit(tty_interface_t *state) { + update_state(state); + + /* Reset the tty as close as possible to the previous state */ + clear(state); + + /* ttyout should be flushed before outputting on stdout */ + tty_close(state->tty); + + const char *selection = choices_get(state->choices, state->choices->selection); + if (selection) { + /* output the selected result */ + printf("%s\n", selection); + } else { + /* No match, output the query instead */ + printf("%s\n", state->search); + } + + state->exit = EXIT_SUCCESS; +} + +static void action_del_char(tty_interface_t *state) { + size_t length = strlen(state->search); + if (state->cursor == 0) { + return; + } + size_t original_cursor = state->cursor; + + do { + state->cursor--; + } while (!is_boundary(state->search[state->cursor]) && state->cursor); + + memmove(&state->search[state->cursor], &state->search[original_cursor], length - original_cursor + 1); +} + +static void action_del_word(tty_interface_t *state) { + size_t original_cursor = state->cursor; + size_t cursor = state->cursor; + + while (cursor && isspace(state->search[cursor - 1])) + cursor--; + + while (cursor && !isspace(state->search[cursor - 1])) + cursor--; + + memmove(&state->search[cursor], &state->search[original_cursor], strlen(state->search) - original_cursor + 1); + state->cursor = cursor; +} + +static void action_del_all(tty_interface_t *state) { + memmove(state->search, &state->search[state->cursor], strlen(state->search) - state->cursor + 1); + state->cursor = 0; +} + +static void action_prev(tty_interface_t *state) { + update_state(state); + choices_prev(state->choices); +} + +static void action_ignore(tty_interface_t *state) { + (void)state; +} + +static void action_next(tty_interface_t *state) { + update_state(state); + choices_next(state->choices); +} + +static void action_left(tty_interface_t *state) { + if (state->cursor > 0) { + state->cursor--; + while (!is_boundary(state->search[state->cursor]) && state->cursor) + state->cursor--; + } +} + +static void action_right(tty_interface_t *state) { + if (state->cursor < strlen(state->search)) { + state->cursor++; + while (!is_boundary(state->search[state->cursor])) + state->cursor++; + } +} + +static void action_beginning(tty_interface_t *state) { + state->cursor = 0; +} + +static void action_end(tty_interface_t *state) { + state->cursor = strlen(state->search); +} + +static void action_pageup(tty_interface_t *state) { + update_state(state); + for (size_t i = 0; i < state->options->num_lines && state->choices->selection > 0; i++) + choices_prev(state->choices); +} + +static void action_pagedown(tty_interface_t *state) { + update_state(state); + for (size_t i = 0; i < state->options->num_lines && state->choices->selection < state->choices->available - 1; i++) + choices_next(state->choices); +} + +static void action_autocomplete(tty_interface_t *state) { + update_state(state); + const char *current_selection = choices_get(state->choices, state->choices->selection); + if (current_selection) { + strncpy(state->search, choices_get(state->choices, state->choices->selection), SEARCH_SIZE_MAX); + state->cursor = strlen(state->search); + } +} + +static void action_exit(tty_interface_t *state) { + clear(state); + tty_close(state->tty); + + state->exit = EXIT_FAILURE; +} + +static void append_search(tty_interface_t *state, char ch) { + char *search = state->search; + size_t search_size = strlen(search); + if (search_size < SEARCH_SIZE_MAX) { + memmove(&search[state->cursor+1], &search[state->cursor], search_size - state->cursor + 1); + search[state->cursor] = ch; + + state->cursor++; + } +} + +void tty_interface_init(tty_interface_t *state, tty_t *tty, choices_t *choices, options_t *options) { + state->tty = tty; + state->choices = choices; + state->options = options; + state->ambiguous_key_pending = 0; + + strcpy(state->input, ""); + strcpy(state->search, ""); + strcpy(state->last_search, ""); + + state->exit = -1; + + if (options->init_search) + strncpy(state->search, options->init_search, SEARCH_SIZE_MAX); + + state->cursor = strlen(state->search); + + update_search(state); +} + +typedef struct { + const char *key; + void (*action)(tty_interface_t *); +} keybinding_t; + +#define KEY_CTRL(key) ((const char[]){((key) - ('@')), '\0'}) + +static const keybinding_t keybindings[] = {{"\x1b", action_exit}, /* ESC */ + {"\x7f", action_del_char}, /* DEL */ + + {KEY_CTRL('H'), action_del_char}, /* Backspace (C-H) */ + {KEY_CTRL('W'), action_del_word}, /* C-W */ + {KEY_CTRL('U'), action_del_all}, /* C-U */ + {KEY_CTRL('I'), action_autocomplete}, /* TAB (C-I ) */ + {KEY_CTRL('C'), action_exit}, /* C-C */ + {KEY_CTRL('D'), action_exit}, /* C-D */ + {KEY_CTRL('G'), action_exit}, /* C-G */ + {KEY_CTRL('M'), action_emit}, /* CR */ + {KEY_CTRL('P'), action_prev}, /* C-P */ + {KEY_CTRL('N'), action_next}, /* C-N */ + {KEY_CTRL('K'), action_prev}, /* C-K */ + {KEY_CTRL('J'), action_next}, /* C-J */ + {KEY_CTRL('A'), action_beginning}, /* C-A */ + {KEY_CTRL('E'), action_end}, /* C-E */ + + {"\x1bOD", action_left}, /* LEFT */ + {"\x1b[D", action_left}, /* LEFT */ + {"\x1bOC", action_right}, /* RIGHT */ + {"\x1b[C", action_right}, /* RIGHT */ + {"\x1b[1~", action_beginning}, /* HOME */ + {"\x1b[H", action_beginning}, /* HOME */ + {"\x1b[4~", action_end}, /* END */ + {"\x1b[F", action_end}, /* END */ + {"\x1b[A", action_prev}, /* UP */ + {"\x1bOA", action_prev}, /* UP */ + {"\x1b[B", action_next}, /* DOWN */ + {"\x1bOB", action_next}, /* DOWN */ + {"\x1b[5~", action_pageup}, + {"\x1b[6~", action_pagedown}, + {"\x1b[200~", action_ignore}, + {"\x1b[201~", action_ignore}, + {NULL, NULL}}; + +#undef KEY_CTRL + +static void handle_input(tty_interface_t *state, const char *s, int handle_ambiguous_key) { + state->ambiguous_key_pending = 0; + + char *input = state->input; + strcat(state->input, s); + + /* Figure out if we have completed a keybinding and whether we're in the + * middle of one (both can happen, because of Esc). */ + int found_keybinding = -1; + int in_middle = 0; + for (int i = 0; keybindings[i].key; i++) { + if (!strcmp(input, keybindings[i].key)) + found_keybinding = i; + else if (!strncmp(input, keybindings[i].key, strlen(state->input))) + in_middle = 1; + } + + /* If we have an unambiguous keybinding, run it. */ + if (found_keybinding != -1 && (!in_middle || handle_ambiguous_key)) { + keybindings[found_keybinding].action(state); + strcpy(input, ""); + return; + } + + /* We could have a complete keybinding, or could be in the middle of one. + * We'll need to wait a few milliseconds to find out. */ + if (found_keybinding != -1 && in_middle) { + state->ambiguous_key_pending = 1; + return; + } + + /* Wait for more if we are in the middle of a keybinding */ + if (in_middle) + return; + + /* No matching keybinding, add to search */ + for (int i = 0; input[i]; i++) + if (isprint_unicode(input[i])) + append_search(state, input[i]); + + /* We have processed the input, so clear it */ + strcpy(input, ""); +} + +int tty_interface_run(tty_interface_t *state) { + draw(state); + + for (;;) { + do { + while(!tty_input_ready(state->tty, -1, 1)) { + /* We received a signal (probably WINCH) */ + draw(state); + } + + char s[2] = {tty_getchar(state->tty), '\0'}; + handle_input(state, s, 0); + + if (state->exit >= 0) + return state->exit; + + draw(state); + } while (tty_input_ready(state->tty, state->ambiguous_key_pending ? KEYTIMEOUT : 0, 0)); + + if (state->ambiguous_key_pending) { + char s[1] = ""; + handle_input(state, s, 1); + + if (state->exit >= 0) + return state->exit; + } + + update_state(state); + } + + return state->exit; +} diff --git a/dotfiles/fzy/tty_interface.h b/dotfiles/fzy/tty_interface.h @@ -0,0 +1,28 @@ +#ifndef TTY_INTERFACE_H +#define TTY_INTERFACE_H TTY_INTERFACE_H + +#include "choices.h" +#include "options.h" +#include "tty.h" + +#define SEARCH_SIZE_MAX 4096 + +typedef struct { + tty_t *tty; + choices_t *choices; + options_t *options; + + char search[SEARCH_SIZE_MAX + 1]; + char last_search[SEARCH_SIZE_MAX + 1]; + size_t cursor; + + int ambiguous_key_pending; + char input[32]; /* Pending input buffer */ + + int exit; +} tty_interface_t; + +void tty_interface_init(tty_interface_t *state, tty_t *tty, choices_t *choices, options_t *options); +int tty_interface_run(tty_interface_t *state); + +#endif diff --git a/dotfiles/revShowMyHistory.c b/dotfiles/revShowMyHistory.c @@ -0,0 +1,33 @@ +#! /usr/bin/env sheepy +#include "libsheepyObject.h" +#define H "~//myhistory.txt" +#define HE "~//myhistoryExit.txt" +#define HP "~//myhistoryPath.txt" +#define HB "~//myhistory.bin" + +int main(int ARGC, char** ARGV) { + initLibsheepy(ARGV[0]); + + cleanCharP(hbp) = expandHomeG(HB); + + u8 *buf = null; + size_t hlen = readFile(hbp, (void**)&buf); + + + u8 *e = buf + hlen; + cleanAllocateSmallJson(a); + + do { + u32 len = *(u32*)(e - 4); + cleanAllocateSmallBytes(b); + o(b,set, e - len + 4, len-8); + deserialG(a, b); + // a = [path,exit code, date, command] + puts(getG(a, rtChar, 3)); + e -= len; + + } while(e > buf); + + ret 0; +} +// vim: set expandtab ts=2 sw=2: diff --git a/dotfiles/saveHistory.c b/dotfiles/saveHistory.c @@ -0,0 +1,175 @@ +#! /usr/bin/env sheepy +#include "libsheepyObject.h" +// ARG 1: exit code +// ARG 2: last command +// export PROMPT_COMMAND='rs=$? ; lastCommand=`history 1 | cut -c 26-` ; ~/bin/saveHistory $rs "$lastCommand"' + +#define H "~/myhistory.txt" +#define HE "~/myhistoryExit.txt" +#define HP "~/myhistoryPath.txt" +#define HB "~/myhistory.bin" +// command is equal to +char *filter[] = { + "h", + "fg", + "bg", + "p", + "gu", + "gg", + "gh", + "gs", + "g lg", + "l", + "ls", + "la", + "history", + "E", + "env", + "df", + "dfh", + "du", + "duu", + "exit", + "logout", + "top", + "htop", + "reset", + null +}; +// command starts with +char *start[] = { + "fg ", + "whal ", + "l ", + "ls ", + "la ", + "df ", + "dfh ", + "du ", + "duu ", + "history|", + null +}; + + +int main(int ARGC, char** ARGV) { + cleanCharP(newCmd) = trimS(ARGV[2]); + forEachS(filter, s) { + if (eqG(newCmd, s)) ret 0; + } + forEachS(start, s) { + if (startsWithG(ARGV[2], s)) ret 0; + } + initLibsheepy(ARGV[0]); + + cleanCharP(hbp) = expandHomeG(HB); + + // check if last command is duplicate + // check only if history file exists + // don't save duplicate commands + + // export PROMPT_COMMAND='rs=$? ; pwd >> ~/myhistoryPath.txt ; d=`date +"%F %T"` ; printf "%s %s\n" "$rs" "$d" >> ~/myhistoryExit.txt ; history 1 | cut -c 26- >> ~/myhistory.txt' + if (isPath(hbp)) { + // ignore duplicate command + + u8 *buf = null; + size_t hlen = readFile(hbp, (void**)&buf); + + // get last command + cleanAllocateSmallJson(a); + u32 len = *(u32*)(buf + hlen - 4); + cleanAllocateSmallBytes(b); + o(b,set, buf + hlen - len + 4, len-8); + deserialG(a, b); + // a = [path,exit code, date, command] + + // this is a duplicate, stop + cleanCharP(l) = trimS(getG(a, rtChar, 3)); + if (eqG(newCmd, l)) ret 0; + } + + char path[8192] = init0Var; + char *res = bLGetCwd(path, sizeof(path)); + char date[128] = init0Var; + pErrorNULL(bGetCurrentDateYMD(date)); + cleanAllocateSmallJson(a); + // save history as binary + // a = [path,exit code, date, command] + pushG(a, path); + pushG(a, ARGV[1]); + pushG(a, date); + pushG(a, newCmd); + smallBytest *b = serialG(a); + u8 buf[65536]; + u32 len = lenG(b) + 4*2; + memcpy(buf, &len, 4); + memcpy(buf+4, getValO(b), lenG(b)); + memcpy(buf+4+lenG(b), &len, 4); + pError0(appendFile(hbp, buf, len)); + ret 0; +} + +#if 0 +int main(int ARGC, char** ARGV) { + cleanCharP(newCmd) = trimS(ARGV[2]); + forEachS(filter, s) { + if (eqG(newCmd, s)) ret 0; + } + forEachS(start, s) { + if (startsWithG(ARGV[2], s)) ret 0; + } + initLibsheepy(ARGV[0]); + // export PROMPT_COMMAND='rs=$? ; pwd >> ~/myhistoryPath.txt ; d=`date +"%F %T"` ; printf "%s %s\n" "$rs" "$d" >> ~/myhistoryExit.txt ; history 1 | cut -c 26- >> ~/myhistory.txt' + cleanCharP(hp) = expandHomeG(H); + if (isPath(hp)) { + // ignore duplicate command + cleanCharP(h) = readFileS(hp); + size_t len = lenG(h); + char *last = h+len; + u8 status = 0; + range(i, len) { + if (*last == '\n') { + if (status == 1) break; + if (status == 0) { + // end of last command + *last = 0; + inc status; + } + } + dec last; + } + // this is a duplicate, stop + cleanCharP(l) = trimS(last); + if (eqG(newCmd, l)) ret 0; + } + cleanCharP(hep) = expandHomeG(HE); + cleanCharP(hpp) = expandHomeG(HP); + char path[8192] = init0Var; + pError0(appendFileS(hpp, bLGetCwd(path, sizeof(path)))); + pError0(appendFileS(hpp, "\n")); + char date[128] = init0Var; + pErrorNULL(bGetCurrentDateYMD(date)); + cleanCharP(edate) = formatS("%s %s\n", ARGV[1], date); + pError0(appendFileS(hep, edate)); + pError0(appendFileS(hp, newCmd)); + pError0(appendFileS(hp, "\n")); + // doesnt work - system("history 1 | cut -c 26- >> "H); + cleanAllocateSmallJson(a); + // save history as binary + // a = [path,exit code, date, command] + pushG(a, path); + pushG(a, ARGV[1]); + pushG(a, date); + pushG(a, newCmd); + smallBytest *b = serialG(a); + u8 buf[65536]; + u32 len = lenG(b) + 4*2; + memcpy(buf, &len, 4); + memcpy(buf+4, getValO(b), lenG(b)); + memcpy(buf+4+lenG(b), &len, 4); + cleanCharP(hbp) = expandHomeG(HB); + pError0(appendFile(hbp, buf, len)); + ret 0; +} +#endif +// vim: set expandtab ts=2 sw=2: diff --git a/dotfiles/showMyHistory.c b/dotfiles/showMyHistory.c @@ -0,0 +1,105 @@ +#! /usr/bin/env sheepy +#include "libsheepyObject.h" +#define H "~/myhistory.txt" +#define HE "~/myhistoryExit.txt" +#define HP "~/myhistoryPath.txt" +#define HB "~/myhistory.bin" + +#define maxPathLen 50 + +int main(int ARGC, char** ARGV) { + initLibsheepy(ARGV[0]); + + cleanCharP(hbp) = expandHomeG(HB); + + u8 *buf = null; + size_t hlen = readFile(hbp, (void**)&buf); + + // count commands + // search for longest path + u8 *e = buf; + cleanAllocateSmallJson(a); + u64 commandCount = 0; + u16 max = 0; + do { + u32 len = *(u32*)e; + cleanAllocateSmallBytes(b); + o(b,set, e+4, len-8); + deserialG(a, b); + // a = [path,exit code, date, command] + max = maxV(max, lenG(getG(a, rtChar, 0))); + inc commandCount; + e += len; + } while(e < buf + hlen); + + bool ellipsis = (max > maxPathLen) ? yes : no; + if (ellipsis) max = maxPathLen; + + // show history + e = buf; + u64 count = 0; + // compute length of commandCount to align columns + char counts[32] = init0Var; + pErrorNULL(bIntToS(counts,commandCount)); + u32 countLen = strlen(counts); + cleanAllocateSmallBytes(b); + char ellip[256]; + do { + inc count; + u32 len = *(u32*)e; + o(b,set, e+4, len-8); + deserialG(a, b); + // a = [path,exit code, date, command] + u32 exitCode = parseIntG(getG(a, rtChar, 1)); + char *res = bEllipsisStartS(ellip /*dest*/, getG(a, rtChar, 0)/*path string*/, max /*targetLen*/, "]"/*ellipsisString*/); + char *path = ellipsis ? ellip : getG(a, rtChar, 0); + if (exitCode) { + printf("%*d "RED"%3d"RST" %s "YLW"%*s"RST" "RED"%s"RST"\n", countLen, count, exitCode, /*date*/getG(a, rtChar, 2), max, path, /*cmd*/getG(a, rtChar, 3)); + } + else { + printf("%*d "GRN"%3d"RST" %s "YLW"%*s"RST" %s\n", countLen, count, exitCode, /*date*/getG(a, rtChar, 2), max, path, /*cmd*/getG(a, rtChar, 3)); + } + e += len; + } while(e < buf + hlen); + + ret 0; +} + +#if 0 +int main(int ARGC, char** ARGV) { + initLibsheepy(ARGV[0]); + cleanAllocateSmallArray(h); + cleanAllocateSmallArray(he); + cleanAllocateSmallArray(hP); + cleanCharP(hp) = expandHomeG(H); + cleanCharP(hep) = expandHomeG(HE); + cleanCharP(hpp) = expandHomeG(HP); + readFileG(h, hp); + readFileG(he, hep); + readFileG(hP, hpp); + + u16 max = 0; + iter(hP, E) { + castS(e,E); + max = maxV(max, lenG(e)); + } + u32 count = lenG(h); + char counts[32] = init0Var; + pErrorNULL(bIntToS(counts,count)); + count = strlen(counts); + iter(he, e) { + char *s = ssGet(e); + if (not hasG(s, ' ')) continue; + char *date = findS(s, " ") +1; + u32 exitCode = parseIntG(s); + if (exitCode) { + printf("%*d "RED"%3d"RST" %s "YLW"%*s"RST" "RED"%s"RST"\n", count, iterIndexG(he)+1, exitCode, date, max, getG(hP, rtChar, iterIndexG(he)), getG(h, rtChar, iterIndexG(he))); + } + else { + printf("%*d "GRN"%3d"RST" %s "YLW"%*s"RST" %s\n", count, iterIndexG(he)+1, exitCode, date, max, getG(hP, rtChar, iterIndexG(he)), getG(h, rtChar, iterIndexG(he))); + } + } + ret 0; +} +#endif +// vim: set expandtab ts=2 sw=2: