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