hstr/src/hstr.c

1511 lines
51 KiB
C

/*
hstr.c HSTR shell history completion utility
Copyright (C) 2014-2018 Martin Dvorak <martin.dvorak@mindforger.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#define _GNU_SOURCE
#include "include/hstr.h"
#define SELECTION_CURSOR_IN_PROMPT -1
#define SELECTION_PREFIX_MAX_LNG 512
#define CMDLINE_LNG 2048
#define HOSTNAME_BUFFER 128
#define PG_JUMP_SIZE 10
#define K_CTRL_A 1
#define K_CTRL_E 5
#define K_CTRL_F 6
#define K_CTRL_G 7
#define K_CTRL_H 8
#define K_CTRL_L 12
#define K_CTRL_J 10
#define K_CTRL_K 11
#define K_CTRL_N 14
#define K_CTRL_P 16
#define K_CTRL_R 18
#define K_CTRL_T 20
#define K_CTRL_U 21
#define K_CTRL_W 23
#define K_CTRL_X 24
#define K_CTRL_Z 26
#define K_CTRL_SLASH 31
#define K_ESC 27
#define K_TAB 9
#define K_BACKSPACE 127
#define K_ENTER 13
#define HH_THEME_MONO 0
#define HH_THEME_COLOR 1<<7
#define HH_THEME_LIGHT 1|HH_THEME_COLOR
#define HH_THEME_DARK 2|HH_THEME_COLOR
#define HH_COLOR_NORMAL 1
#define HH_COLOR_HIROW 2
#define HH_COLOR_INFO 2
#define HH_COLOR_PROMPT 3
#define HH_COLOR_DELETE 4
#define HH_COLOR_MATCH 5
#define HH_ENV_VAR_CONFIG "HH_CONFIG"
#define HH_ENV_VAR_PROMPT "HH_PROMPT"
#define HH_CONFIG_THEME_MONOCHROMATIC "monochromatic"
#define HH_CONFIG_THEME_HICOLOR "hicolor"
#define HH_CONFIG_CASE "casesensitive"
#define HH_CONFIG_REGEXP "regexp"
#define HH_CONFIG_SUBSTRING "substring"
#define HH_CONFIG_KEYWORDS "keywords"
#define HH_CONFIG_SORTING "rawhistory"
#define HH_CONFIG_FAVORITES "favorites"
#define HH_CONFIG_NOCONFIRM "noconfirm"
#define HH_CONFIG_VERBOSE_KILL "verbose-kill"
#define HH_CONFIG_PROMPT_BOTTOM "prompt-bottom"
#define HH_CONFIG_BLACKLIST "blacklist"
#define HH_CONFIG_KEEP_PAGE "keep-page"
#define HH_CONFIG_DEBUG "debug"
#define HH_CONFIG_WARN "warning"
#define HH_CONFIG_BIG_KEYS_SKIP "big-keys-skip"
#define HH_CONFIG_BIG_KEYS_FLOOR "big-keys-floor"
#define HH_CONFIG_BIG_KEYS_EXIT "big-keys-exit"
#define HH_CONFIG_DUPLICATES "duplicates"
#define HH_DEBUG_LEVEL_NONE 0
#define HH_DEBUG_LEVEL_WARN 1
#define HH_DEBUG_LEVEL_DEBUG 2
#define HH_VIEW_RANKING 0
#define HH_VIEW_HISTORY 1
#define HH_VIEW_FAVORITES 2
#define HH_MATCH_SUBSTRING 0
#define HH_MATCH_REGEXP 1
#define HH_MATCH_KEYWORDS 2
#define HH_NUM_HISTORY_MATCH 3
#define HH_CASE_INSENSITIVE 0
#define HH_CASE_SENSITIVE 1
#ifdef DEBUG_KEYS
#define LOGKEYS(Y,KEY) mvprintw(Y, 0, "Key: '%3d' / Char: '%c'", KEY, KEY); clrtoeol()
#else
#define LOGKEYS(Y,KEY)
#endif
#ifdef DEBUG_CURPOS
#define LOGCURSOR(Y) mvprintw(Y, 0, "X/Y: %3d / %3d", getcurx(stdscr), getcury(stdscr))
#else
#define LOGCURSOR(Y)
#endif
#ifdef DEBUG_UTF8
#define LOGUTF8(Y,P) mvprintw(Y, 0, "strlen() %zd, mbstowcs() %zd, hstr_strlen() %d",strlen(P),mbstowcs(NULL,P,0),hstr_strlen(P)); clrtoeol()
#else
#define LOGUTF8(Y,P)
#endif
#ifdef DEBUG_SELECTION
#define LOGSELECTION(Y,SCREEN,MODEL) mvprintw(Y, 0, "Selection: screen %3d, model %3d", SCREEN, MODEL); clrtoeol()
#else
#define LOGSELECTION(Y,SCREEN,MODEL)
#endif
static const char* HH_VIEW_LABELS[]={
"ranking",
"history",
"favorites"
};
static const char* HH_MATCH_LABELS[]={
"exact",
"regexp",
"keywords"
};
static const char* HH_CASE_LABELS[]={
"insensitive",
"sensitive"
};
static const char* INSTALL_BASH_STRING=
"\n# add this configuration to ~/.bashrc"
"\nexport HH_CONFIG=hicolor # get more colors"
"\nshopt -s histappend # append new history items to .bash_history"
"\nexport HISTCONTROL=ignorespace # leading space hides commands from history"
"\nexport HISTFILESIZE=10000 # increase history file size (default is 500)"
"\nexport HISTSIZE=${HISTFILESIZE} # increase history size (default is 500)"
// PROMPT_COMMAND considerations:
// history -a ... append NEW entries from memory to .bash_history (i.e. flush to file where HSTR reads commands)
// history -n ... append NEW entries from .bash_history to memory i.e. NOT entire history reload
// history -c ... CLEAR in memory history (keeps .bash_history content)
// history -r ... append ALL entries from .bash_history to memory (useful to sync DIFFERENT Bash sessions)
// Conclusion:
// -a -n ... Fastest and almost-consistent option i.e. there is efficiency/integrity trade-off.
// It works correctly if memory entries are not deleted by HSTR. It doesn't synchronize history
// across different Bash sessions.
// -c -r ... Forces entire .bash_history to be reloaded (handles history deletes, synchronizes different Bash sessions)
"\nexport PROMPT_COMMAND=\"history -a; history -n; ${PROMPT_COMMAND}\" # mem/file sync"
"\n# if this is interactive shell, then bind hh to Ctrl-r (for Vi mode check doc)"
// IMPROVE hh (win10) vs. hstr (cygwin) binary on various platforms must be resolved
#if defined(__MS_WSL__)
// IMPROVE commands are NOT executed on return under win10 > consider hstr_utils changes
"\nfunction hstr_winwsl {"
"\n offset=${READLINE_POINT}"
"\n READLINE_POINT=0"
"\n { READLINE_LINE=$(</dev/tty hstr ${READLINE_LINE:0:offset} 2>&1 1>&$hstrout); } {hstrout}>&1"
"\n READLINE_POINT=${#READLINE_LINE}"
"\n}"
"\nif [[ $- =~ .*i.* ]]; then bind -x '\"\\C-r\": \"hstr_winwsl\"'; fi"
#elif defined(__CYGWIN__)
"\nfunction hstr_cygwin {"
"\n offset=${READLINE_POINT}"
"\n READLINE_POINT=0"
"\n { READLINE_LINE=$(</dev/tty hstr ${READLINE_LINE:0:offset} 2>&1 1>&$hstrout); } {hstrout}>&1"
"\n READLINE_POINT=${#READLINE_LINE}"
"\n}"
"\nif [[ $- =~ .*i.* ]]; then bind -x '\"\\C-r\": \"hstr_cygwin\"'; fi"
#else
"\nif [[ $- =~ .*i.* ]]; then bind '\"\\C-r\": \"\\C-a hh -- \\C-j\"'; fi"
"\n# if this is interactive shell, then bind 'kill last command' to Ctrl-x k"
"\nif [[ $- =~ .*i.* ]]; then bind '\"\\C-xk\": \"\\C-a hh -k \\C-j\"'; fi"
#endif
"\n\n";
static const char* INSTALL_ZSH_STRING=
"\n# add this configuration to ~/.zshrc"
"\nexport HISTFILE=~/.zsh_history # ensure history file visibility"
"\nexport HH_CONFIG=hicolor # get more colors"
#if defined(__MS_WSL__)
// TODO binding to be rewritten for zsh@WSL as it's done for Bash - hstr_winwsl() like function to be implemented to make it work on WSL
"\nbindkey -s \"\\C-r\" \"\\eqhh\\n\" # bind hh to Ctrl-r (for Vi mode check doc)"
#elif defined(__CYGWIN__)
// TODO binding to be rewritten for zsh@Cygwin as it's done for Bash - hstr_winwsl() like function to be implemented to make it work on WSL
"\nbindkey -s \"\\C-r\" \"\\eqhh\\n\" # bind hh to Ctrl-r (for Vi mode check doc)"
#else
"\nbindkey -s \"\\C-r\" \"\\eqhh\\n\" # bind hh to Ctrl-r (for Vi mode check doc)"
#endif
// TODO try variant with args/pars separation
//"\nbindkey -s \"\\C-r\" \"\\eqhh --\\n\" # bind hh to Ctrl-r (for Vi mode check doc)"
// alternate binding options in zsh:
// bindkey -s '^R' '^Ahh ^M'
// bindkey -s "\C-r" "\C-ahh \C-j"
"\n\n";
static const char* HELP_STRING=
"Usage: hh [option] [arg1] [arg2]..."
"\nShell history suggest box:"
"\n"
"\n --favorites -f ... show favorites view"
"\n --kill-last-command -k ... delete last command in history"
"\n --non-interactive -n ... print filtered history and exit"
"\n --show-configuration -s ... show configuration to be added to ~/.bashrc"
"\n --show-zsh-configuration -z ... show Zsh configuration to be added to ~/.zshrc"
"\n --show-blacklist -b ... show commands to skip on history indexation"
"\n --version -V ... show version details"
"\n --help -h ... help"
"\n"
"\nReport bugs to martin.dvorak@mindforger.com"
"\nHome page: https://github.com/dvorka/hstr"
"\n";
// major.minor.revision
static const char* VERSION_STRING=
"hh version \"1.27.0\" (2018-08-13T12:00:00)"
"\n";
// TODO help screen - curses window (tig)
static const char* LABEL_HELP=
"Type to filter, UP/DOWN move, RET/DEL remove, TAB select, C-f add favorite, C-g cancel";
#define GETOPT_NO_ARGUMENT 0
#define GETOPT_REQUIRED_ARGUMENT 1
#define GETOPT_OPTIONAL_ARGUMENT 2
static const struct option long_options[] = {
{"favorites", GETOPT_NO_ARGUMENT, NULL, 'f'},
{"kill-last-command", GETOPT_NO_ARGUMENT, NULL, 'k'},
{"version", GETOPT_NO_ARGUMENT, NULL, 'V'},
{"help", GETOPT_NO_ARGUMENT, NULL, 'h'},
{"non-interactive", GETOPT_NO_ARGUMENT, NULL, 'n'},
{"show-configuration", GETOPT_NO_ARGUMENT, NULL, 's'},
{"show-zsh-configuration", GETOPT_NO_ARGUMENT, NULL, 'z'},
{"show-blacklist", GETOPT_NO_ARGUMENT, NULL, 'b'},
{0, 0, NULL, 0 }
};
typedef struct {
HistoryItems *history;
FavoriteItems *favorites;
char **selection;
unsigned selectionSize;
regmatch_t *selectionRegexpMatch;
int historyMatch; // TODO patternMatching: exact, regexp
int historyView; // TODO view: favorites, ...
int caseSensitive;
bool interactive;
bool unique;
unsigned char theme;
bool keepPage; // do NOT clear page w/ selection on HH exit
bool noConfirm; // do NOT ask for confirmation on history entry delete
bool verboseKill; // write a message on delete of the last command in history
int bigKeys;
int debugLevel;
HstrRegexp regexp;
Blacklist blacklist;
char cmdline[CMDLINE_LNG];
bool promptBottom;
int promptY;
int promptYHelp;
int promptYHistory;
int promptYItemsStart;
int promptYItemsEnd;
int promptItems;
} Hstr;
static Hstr* hstr;
void hstr_init(void)
{
hstr->selection=NULL;
hstr->selectionRegexpMatch=NULL;
hstr->selectionSize=0;
hstr->historyMatch=HH_MATCH_KEYWORDS;
hstr->historyView=HH_VIEW_RANKING;
hstr->caseSensitive=HH_CASE_INSENSITIVE;
hstr->interactive=true;
hstr->unique=true;
hstr->theme=HH_THEME_MONO;
hstr->bigKeys=RADIX_BIG_KEYS_SKIP;
hstr->debugLevel=HH_DEBUG_LEVEL_NONE;
blacklist_init(&hstr->blacklist);
hstr->cmdline[0]=0;
hstr_regexp_init(&hstr->regexp);
}
unsigned recalculate_max_history_items(void)
{
hstr->promptItems = getmaxy(stdscr) - 3;
if(hstr->promptBottom) {
hstr->promptY = getmaxy(stdscr) - 1;
hstr->promptYHelp = hstr->promptY - 1;
hstr->promptYHistory = hstr->promptY - 2;
hstr->promptYItemsStart = 0;
hstr->promptYItemsEnd = hstr->promptItems-1;
} else {
hstr->promptY = 0;
hstr->promptYHelp = 1;
hstr->promptYHistory = 2;
hstr->promptYItemsStart = 3;
hstr->promptYItemsEnd = getmaxy(stdscr);
}
return hstr->promptItems;
}
void hstr_get_env_configuration()
{
char *hstr_config=getenv(HH_ENV_VAR_CONFIG);
if(hstr_config && strlen(hstr_config)>0) {
if(strstr(hstr_config,HH_CONFIG_THEME_MONOCHROMATIC)) {
hstr->theme=HH_THEME_MONO;
} else {
if(strstr(hstr_config,HH_CONFIG_THEME_HICOLOR)) {
hstr->theme=HH_THEME_DARK;
}
}
if(strstr(hstr_config,HH_CONFIG_CASE)) {
hstr->caseSensitive=HH_CASE_SENSITIVE;
}
if(strstr(hstr_config,HH_CONFIG_REGEXP)) {
hstr->historyMatch=HH_MATCH_REGEXP;
} else {
if(strstr(hstr_config,HH_CONFIG_SUBSTRING)) {
hstr->historyMatch=HH_MATCH_SUBSTRING;
} else {
if(strstr(hstr_config, HH_CONFIG_KEYWORDS)) {
hstr->historyMatch=HH_MATCH_KEYWORDS;
}
}
}
if(strstr(hstr_config,HH_CONFIG_SORTING)) {
hstr->historyView=HH_VIEW_HISTORY;
} else {
if(strstr(hstr_config,HH_CONFIG_FAVORITES)) {
hstr->historyView=HH_VIEW_FAVORITES;
}
}
if(strstr(hstr_config,HH_CONFIG_BIG_KEYS_EXIT)) {
hstr->bigKeys=RADIX_BIG_KEYS_EXIT;
} else {
if(strstr(hstr_config,HH_CONFIG_BIG_KEYS_FLOOR)) {
hstr->bigKeys=RADIX_BIG_KEYS_FLOOR;
} else {
hstr->bigKeys=RADIX_BIG_KEYS_SKIP;
}
}
if(strstr(hstr_config,HH_CONFIG_VERBOSE_KILL)) {
hstr->verboseKill=true;
}
if(strstr(hstr_config,HH_CONFIG_BLACKLIST)) {
hstr->blacklist.useFile=true;
}
if(strstr(hstr_config,HH_CONFIG_KEEP_PAGE)) {
hstr->keepPage=true;
}
if(strstr(hstr_config,HH_CONFIG_NOCONFIRM)) {
hstr->noConfirm=true;
}
if(strstr(hstr_config,HH_CONFIG_DEBUG)) {
hstr->debugLevel=HH_DEBUG_LEVEL_DEBUG;
} else {
if(strstr(hstr_config,HH_CONFIG_WARN)) {
hstr->debugLevel=HH_DEBUG_LEVEL_WARN;
}
}
if(strstr(hstr_config,HH_CONFIG_DUPLICATES)) {
hstr->unique=false;
}
if(strstr(hstr_config,HH_CONFIG_PROMPT_BOTTOM)) {
hstr->promptBottom = true;
} else {
hstr->promptBottom = false;
}
recalculate_max_history_items();
}
}
unsigned print_prompt(void)
{
unsigned xoffset = 0, promptLength;
if(hstr->theme & HH_THEME_COLOR) {
color_attr_on(COLOR_PAIR(HH_COLOR_PROMPT));
color_attr_on(A_BOLD);
}
char *prompt = getenv(HH_ENV_VAR_PROMPT);
if(prompt) {
mvprintw(hstr->promptY, xoffset, "%s", prompt);
promptLength=strlen(prompt);
} else {
char *user = getenv(ENV_VAR_USER);
char *hostname=malloc(HOSTNAME_BUFFER);
user=(user?user:"me");
get_hostname(HOSTNAME_BUFFER, hostname);
mvprintw(hstr->promptY, xoffset, "%s@%s$ ", user, hostname);
promptLength=strlen(user)+1+strlen(hostname)+1+1;
free(hostname);
}
if(hstr->theme & HH_THEME_COLOR) {
color_attr_off(A_BOLD);
color_attr_off(COLOR_PAIR(HH_COLOR_PROMPT));
}
refresh();
return promptLength;
}
void add_to_selection(char* line, unsigned int* index)
{
if (hstr->unique) {
unsigned i;
for(i = 0; i < *index; i++) {
if (strcmp(hstr->selection[i], line) == 0) {
return;
}
}
}
hstr->selection[*index]=line;
*index = *index + 1;
}
void print_help_label(void)
{
int cursorX=getcurx(stdscr);
int cursorY=getcury(stdscr);
char screenLine[CMDLINE_LNG];
snprintf(screenLine, getmaxx(stdscr), "%s", LABEL_HELP);
mvprintw(hstr->promptYHelp, 0, "%s", screenLine); clrtoeol();
refresh();
move(cursorY, cursorX);
}
void print_confirm_delete(const char* cmd)
{
char screenLine[CMDLINE_LNG];
snprintf(screenLine, getmaxx(stdscr), "Do you want to delete all occurrences of '%s'? y/n", cmd);
// TODO make this function
if(hstr->theme & HH_THEME_COLOR) {
color_attr_on(COLOR_PAIR(HH_COLOR_DELETE));
color_attr_on(A_BOLD);
}
mvprintw(hstr->promptYHelp, 0, "%s", screenLine);
if(hstr->theme & HH_THEME_COLOR) {
color_attr_off(A_BOLD);
color_attr_on(COLOR_PAIR(HH_COLOR_NORMAL));
}
clrtoeol();
refresh();
}
void print_cmd_deleted_label(const char* cmd, int occurences)
{
char screenLine[CMDLINE_LNG];
snprintf(screenLine, getmaxx(stdscr), "History item '%s' deleted (%d occurrence%s)", cmd, occurences, (occurences==1?"":"s"));
// TODO make this function
if(hstr->theme & HH_THEME_COLOR) {
color_attr_on(COLOR_PAIR(HH_COLOR_DELETE));
color_attr_on(A_BOLD);
}
mvprintw(hstr->promptYHelp, 0, "%s", screenLine);
if(hstr->theme & HH_THEME_COLOR) {
color_attr_off(A_BOLD);
color_attr_on(COLOR_PAIR(HH_COLOR_NORMAL));
}
clrtoeol();
refresh();
}
void print_regexp_error(const char* errorMessage)
{
char screenLine[CMDLINE_LNG];
snprintf(screenLine, getmaxx(stdscr), "%s", errorMessage);
if(hstr->theme & HH_THEME_COLOR) {
color_attr_on(COLOR_PAIR(HH_COLOR_DELETE));
color_attr_on(A_BOLD);
}
mvprintw(hstr->promptYHelp, 0, "%s", screenLine);
if(hstr->theme & HH_THEME_COLOR) {
color_attr_off(A_BOLD);
color_attr_on(COLOR_PAIR(HH_COLOR_NORMAL));
}
clrtoeol();
refresh();
}
void print_cmd_added_favorite_label(const char* cmd)
{
char screenLine[CMDLINE_LNG];
snprintf(screenLine, getmaxx(stdscr), "Command '%s' added to favorites (C-/ to show favorites)", cmd);
if(hstr->theme & HH_THEME_COLOR) {
color_attr_on(COLOR_PAIR(HH_COLOR_INFO));
color_attr_on(A_BOLD);
}
mvprintw(hstr->promptYHelp, 0, screenLine);
if(hstr->theme & HH_THEME_COLOR) {
color_attr_off(A_BOLD);
color_attr_on(COLOR_PAIR(HH_COLOR_NORMAL));
}
clrtoeol();
refresh();
}
void print_history_label(void)
{
unsigned width=getmaxx(stdscr);
char screenLine[CMDLINE_LNG];
#ifdef __APPLE__
snprintf(screenLine, width, "- HISTORY - view:%s (C-w) - match:%s (C-e) - case:%s (C-t) - %d/%d/%d ",
#else
snprintf(screenLine, width, "- HISTORY - view:%s (C-/) - match:%s (C-e) - case:%s (C-t) - %d/%d/%d ",
#endif
HH_VIEW_LABELS[hstr->historyView],
HH_MATCH_LABELS[hstr->historyMatch],
HH_CASE_LABELS[hstr->caseSensitive],
hstr->history->count,
hstr->history->rawCount,
hstr->favorites->count);
width -= strlen(screenLine);
unsigned i;
for(i=0; i<width; i++) {
strcat(screenLine, "-");
}
if(hstr->theme & HH_THEME_COLOR) {
color_attr_on(A_BOLD);
}
color_attr_on(A_REVERSE);
mvprintw(hstr->promptYHistory, 0, "%s", screenLine);
color_attr_off(A_REVERSE);
if(hstr->theme & HH_THEME_COLOR) {
color_attr_off(A_BOLD);
}
refresh();
}
void print_pattern(char* pattern, int y, int x)
{
if(pattern) {
color_attr_on(A_BOLD);
mvprintw(y, x, "%s", pattern);
color_attr_off(A_BOLD);
clrtoeol();
}
}
// TODO don't realloc if size doesn't change
void hstr_realloc_selection(unsigned size)
{
if(hstr->selection) {
if(size) {
hstr->selection
=realloc(hstr->selection, sizeof(char*) * size);
hstr->selectionRegexpMatch
=realloc(hstr->selectionRegexpMatch, sizeof(regmatch_t) * size);
} else {
free(hstr->selection);
free(hstr->selectionRegexpMatch);
hstr->selection=NULL;
hstr->selectionRegexpMatch=NULL;
}
} else {
if(size) {
hstr->selection = malloc(sizeof(char*) * size);
hstr->selectionRegexpMatch = malloc(sizeof(regmatch_t) * size);
}
}
}
unsigned hstr_make_selection(char* prefix, HistoryItems* history, unsigned maxSelectionCount)
{
hstr_realloc_selection(maxSelectionCount);
unsigned i, selectionCount=0;
char **source;
unsigned count;
switch(hstr->historyView) {
case HH_VIEW_HISTORY:
source=history->rawItems;
count=history->rawCount;
break;
case HH_VIEW_FAVORITES:
source=hstr->favorites->items;
count=hstr->favorites->count;
break;
case HH_VIEW_RANKING:
default:
source=history->items;
count=history->count;
break;
}
regmatch_t regexpMatch;
char regexpErrorMessage[CMDLINE_LNG];
bool regexpCompilationError=false;
bool keywordsAllMatch;
char *keywordsSavePtr=NULL;
char *keywordsToken=NULL;
char *keywordsParsedLine=NULL;
char *keywordsPointerToDelete=NULL;
for(i=0; i<count && selectionCount<maxSelectionCount; i++) {
if(source[i]) {
if(!prefix || !strlen(prefix)) {
add_to_selection(source[i], &selectionCount);
} else {
switch(hstr->historyMatch) {
case HH_MATCH_SUBSTRING:
switch(hstr->caseSensitive) {
case HH_CASE_SENSITIVE:
if(source[i]==strstr(source[i], prefix)) {
add_to_selection(source[i], &selectionCount);
}
break;
case HH_CASE_INSENSITIVE:
if(source[i]==strcasestr(source[i], prefix)) {
add_to_selection(source[i], &selectionCount);
}
break;
}
break;
case HH_MATCH_REGEXP:
if(hstr_regexp_match(&(hstr->regexp), prefix, source[i], &regexpMatch, regexpErrorMessage, CMDLINE_LNG)) {
hstr->selection[selectionCount]=source[i];
hstr->selectionRegexpMatch[selectionCount].rm_so=regexpMatch.rm_so;
hstr->selectionRegexpMatch[selectionCount].rm_eo=regexpMatch.rm_eo;
selectionCount++;
} else {
if(!regexpCompilationError) {
// TODO fix broken messages - getting just escape sequences
// print_regexp_error(regexpErrorMessage);
regexpCompilationError=true;
}
}
break;
case HH_MATCH_KEYWORDS:
keywordsParsedLine = strdup(prefix);
keywordsAllMatch = true;
keywordsPointerToDelete = keywordsParsedLine;
while(true) {
keywordsToken = strtok_r(keywordsParsedLine, " ", &keywordsSavePtr);
if (keywordsToken == NULL) {
break;
}
keywordsParsedLine = NULL;
switch(hstr->caseSensitive) {
case HH_CASE_SENSITIVE:
if(strstr(source[i], keywordsToken) == NULL) {
keywordsAllMatch = false;
}
break;
case HH_CASE_INSENSITIVE:
if(strcasestr(source[i], keywordsToken) == NULL) {
keywordsAllMatch = false;
}
break;
}
}
if(keywordsAllMatch) {
add_to_selection(source[i], &selectionCount);
}
free(keywordsPointerToDelete);
break;
}
}
}
}
if(prefix && selectionCount<maxSelectionCount) {
char *substring;
for(i=0; i<count && selectionCount<maxSelectionCount; i++) {
switch(hstr->historyMatch) {
case HH_MATCH_SUBSTRING:
switch(hstr->caseSensitive) {
case HH_CASE_SENSITIVE:
substring = strstr(source[i], prefix);
if (substring != NULL && substring!=source[i]) {
add_to_selection(source[i], &selectionCount);
}
break;
case HH_CASE_INSENSITIVE:
substring = strcasestr(source[i], prefix);
if (substring != NULL && substring!=source[i]) {
add_to_selection(source[i], &selectionCount);
}
break;
}
break;
case HH_MATCH_REGEXP:
// all regexps matched previously - user decides whether match ^ or infix
break;
case HH_MATCH_KEYWORDS:
// TODO MD consider adding lines that didn't matched all keywords, but some of them
// (ordered by number of matched keywords)
break;
}
}
}
hstr->selectionSize=selectionCount;
return selectionCount;
}
void print_selection_row(char* text, int y, int width, char* pattern)
{
char screenLine[CMDLINE_LNG];
char buffer[CMDLINE_LNG];
hstr_strelide(buffer, text, width>2?width-2:0);
snprintf(screenLine, width, " %s", buffer);
mvprintw(y, 0, "%s", screenLine); clrtoeol();
if(pattern && strlen(pattern)) {
color_attr_on(A_BOLD);
if(hstr->theme & HH_THEME_COLOR) {
color_attr_on(COLOR_PAIR(HH_COLOR_MATCH));
}
char* p=NULL;
char* pp=NULL;
char* keywordsSavePtr=NULL;
char* keywordsToken=NULL;
char* keywordsParsedLine=NULL;
char* keywordsPointerToDelete=NULL;
switch(hstr->historyMatch) {
case HH_MATCH_SUBSTRING:
switch(hstr->caseSensitive) {
case HH_CASE_INSENSITIVE:
p=strcasestr(screenLine, pattern);
if(p) {
snprintf(buffer, strlen(pattern)+1, "%s", p);
mvprintw(y, p-screenLine, "%s", buffer);
}
break;
case HH_CASE_SENSITIVE:
p=strstr(screenLine, pattern);
if(p) {
mvprintw(y, p-screenLine, "%s", pattern);
}
break;
}
break;
case HH_MATCH_REGEXP:
p=strstr(screenLine, pattern);
if(p) {
mvprintw(y, p-screenLine, "%s", pattern);
}
break;
case HH_MATCH_KEYWORDS:
keywordsParsedLine = strdup(pattern);
keywordsPointerToDelete = keywordsParsedLine;
while (true) {
keywordsToken = strtok_r(keywordsParsedLine, " ", &keywordsSavePtr);
keywordsParsedLine = NULL;
if (keywordsToken == NULL) {
break;
}
switch(hstr->caseSensitive) {
case HH_CASE_SENSITIVE:
p=strstr(screenLine, keywordsToken);
if(p) {
mvprintw(y, p-screenLine, "%s", keywordsToken);
}
break;
case HH_CASE_INSENSITIVE:
p=strcasestr(screenLine, keywordsToken);
if(p) {
pp=strdup(p);
pp[strlen(keywordsToken)]=0;
mvprintw(y, p-screenLine, "%s", pp);
free(pp);
}
break;
}
}
free(keywordsPointerToDelete);
break;
}
if(hstr->theme & HH_THEME_COLOR) {
color_attr_on(COLOR_PAIR(HH_COLOR_NORMAL));
}
color_attr_off(A_BOLD);
}
}
void hstr_print_highlighted_selection_row(char* text, int y, int width)
{
color_attr_on(A_BOLD);
if(hstr->theme & HH_THEME_COLOR) {
color_attr_on(COLOR_PAIR(HH_COLOR_HIROW));
} else {
color_attr_on(A_REVERSE);
}
char buffer[CMDLINE_LNG];
hstr_strelide(buffer, text, width>2?width-2:0);
char screenLine[CMDLINE_LNG];
snprintf(screenLine, getmaxx(stdscr)+1, "%s%-*.*s ",
(terminal_has_colors()?" ":">"),
getmaxx(stdscr)-2, getmaxx(stdscr)-2, buffer);
mvprintw(y, 0, "%s", screenLine);
if(hstr->theme & HH_THEME_COLOR) {
color_attr_on(COLOR_PAIR(HH_COLOR_NORMAL));
} else {
color_attr_off(A_REVERSE);
}
color_attr_off(A_BOLD);
}
char* hstr_print_selection(unsigned maxHistoryItems, char* pattern)
{
char* result=NULL;
unsigned selectionCount=hstr_make_selection(pattern, hstr->history, maxHistoryItems);
if (selectionCount > 0) {
result=hstr->selection[0];
}
unsigned height=recalculate_max_history_items();
unsigned width=getmaxx(stdscr);
unsigned i;
int y;
move(hstr->promptYItemsStart, 0);
clrtobot();
if(hstr->promptBottom) {
print_help_label();
print_history_label();
print_pattern(pattern, hstr->promptY, print_prompt());
y=hstr->promptYItemsEnd;
} else {
y=hstr->promptYItemsStart;
}
int start, count;
char screenLine[CMDLINE_LNG];
for(i=0; i<height; ++i) {
if(i<hstr->selectionSize) {
// TODO make this function
if(pattern && strlen(pattern)) {
if(hstr->historyMatch==HH_MATCH_REGEXP) {
start=hstr->selectionRegexpMatch[i].rm_so;
count=hstr->selectionRegexpMatch[i].rm_eo-start;
if(count>CMDLINE_LNG) {
count=CMDLINE_LNG-1;
}
strncpy(screenLine,
hstr->selection[i]+start,
count);
screenLine[count]=0;
} else {
strcpy(screenLine, pattern);
}
print_selection_row(hstr->selection[i], y, width, screenLine);
} else {
print_selection_row(hstr->selection[i], y, width, pattern);
}
} else {
mvprintw(y, 0, " ");
}
if(hstr->promptBottom) {
y--;
} else {
y++;
}
}
refresh();
return result;
}
void highlight_selection(int selectionCursorPosition, int previousSelectionCursorPosition, char* pattern)
{
if(previousSelectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
// TODO make this function
char buffer[CMDLINE_LNG];
if(pattern && strlen(pattern) && hstr->historyMatch==HH_MATCH_REGEXP) {
int start=hstr->selectionRegexpMatch[previousSelectionCursorPosition].rm_so;
int end=hstr->selectionRegexpMatch[previousSelectionCursorPosition].rm_eo-start;
strncpy(buffer,
hstr->selection[previousSelectionCursorPosition]+start,
end);
buffer[end]=0;
} else {
strcpy(buffer, pattern);
}
int text, y;
if(hstr->promptBottom) {
text=hstr->promptItems-previousSelectionCursorPosition-1;
y=hstr->promptYItemsStart+previousSelectionCursorPosition;
} else {
text=previousSelectionCursorPosition;
y=hstr->promptYItemsStart+previousSelectionCursorPosition;
}
print_selection_row(
hstr->selection[text],
y,
getmaxx(stdscr),
buffer);
}
if(selectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
int text, y;
if(hstr->promptBottom) {
text=hstr->promptItems-selectionCursorPosition-1;
y=hstr->promptYItemsStart+selectionCursorPosition;
} else {
text=selectionCursorPosition;
y=hstr->promptYItemsStart+selectionCursorPosition;
}
hstr_print_highlighted_selection_row(hstr->selection[text], y, getmaxx(stdscr));
}
}
void hstr_on_exit(void)
{
history_mgmt_flush();
free_prioritized_history();
}
void signal_callback_handler_ctrl_c(int signum)
{
if(signum==SIGINT) {
hstr_curses_stop(false);
hstr_on_exit();
exit(signum);
}
}
int remove_from_history_model(char* almostDead)
{
if(hstr->historyView==HH_VIEW_FAVORITES) {
return favorites_remove(hstr->favorites, almostDead);
} else {
// raw & ranked history is pruned first as its items point to system history lines
int systemOccurences=0, rawOccurences=history_mgmt_remove_from_raw(almostDead, hstr->history);
history_mgmt_remove_from_ranked(almostDead, hstr->history);
if(rawOccurences) {
systemOccurences=history_mgmt_remove_from_system_history(almostDead);
}
if(systemOccurences!=rawOccurences && hstr->debugLevel>HH_DEBUG_LEVEL_NONE) {
fprintf(stderr, "WARNING: system and raw items deletion mismatch %d / %d\n", systemOccurences, rawOccurences);
}
return systemOccurences;
}
}
void hstr_next_view(void)
{
hstr->historyView++;
hstr->historyView=hstr->historyView%3;
}
void stdout_history_and_return(void)
{
unsigned selectionCount=hstr_make_selection(hstr->cmdline, hstr->history, hstr->history->rawCount);
if (selectionCount > 0) {
unsigned i;
for(i=0; i<selectionCount; i++) {
printf("%s\n",hstr->selection[i]);
}
}
}
// IMPROVE hstr doesn't have to be passed as parameter - it's global static
char* getResultFromSelection(int selectionCursorPosition, Hstr* hstr, char* result) {
if (hstr->promptBottom) {
result=hstr->selection[hstr->promptYItemsEnd-selectionCursorPosition];
} else {
result=hstr->selection[selectionCursorPosition];
}
return result;
}
void loop_to_select(void)
{
signal(SIGINT, signal_callback_handler_ctrl_c);
hstr_curses_start();
// TODO move the code below to hstr_curses
color_init_pair(HH_COLOR_NORMAL, -1, -1);
if(hstr->theme & HH_THEME_COLOR) {
color_init_pair(HH_COLOR_HIROW, COLOR_WHITE, COLOR_GREEN);
color_init_pair(HH_COLOR_PROMPT, COLOR_BLUE, -1);
color_init_pair(HH_COLOR_DELETE, COLOR_WHITE, COLOR_RED);
color_init_pair(HH_COLOR_MATCH, COLOR_RED, -1);
}
color_attr_on(COLOR_PAIR(HH_COLOR_NORMAL));
// TODO why do I print non-filtered selection when on command line there is a pattern?
hstr_print_selection(recalculate_max_history_items(), NULL);
color_attr_off(COLOR_PAIR(HH_COLOR_NORMAL));
if(!hstr->promptBottom) {
print_help_label();
print_history_label();
}
bool done=FALSE, skip=TRUE, executeResult=FALSE, lowercase=TRUE;
bool printDefaultLabel=TRUE, fixCommand=FALSE, editCommand=FALSE;
unsigned basex=print_prompt();
int x=basex, c, cc, cursorX=0, cursorY=0, maxHistoryItems, deletedOccurences;
int width=getmaxx(stdscr);
int selectionCursorPosition=SELECTION_CURSOR_IN_PROMPT;
int previousSelectionCursorPosition=SELECTION_CURSOR_IN_PROMPT;
char *result="", *msg, *almostDead;
char pattern[SELECTION_PREFIX_MAX_LNG];
pattern[0]=0;
// TODO this is too late! > don't render twice
// TODO overflow
strcpy(pattern, hstr->cmdline);
while (!done) {
maxHistoryItems=recalculate_max_history_items();
if(!skip) {
c = wgetch(stdscr);
} else {
if(strlen(pattern)) {
color_attr_on(A_BOLD);
mvprintw(hstr->promptY, basex, "%s", pattern);
color_attr_off(A_BOLD);
cursorX=getcurx(stdscr);
cursorY=getcury(stdscr);
result=hstr_print_selection(maxHistoryItems, pattern);
move(cursorY, cursorX);
}
skip=FALSE;
continue;
}
if(printDefaultLabel) {
print_help_label();
printDefaultLabel=FALSE;
}
if(c == K_CTRL_R) {
c = (hstr->promptBottom ? K_CTRL_P : K_CTRL_N);
}
switch (c) {
case KEY_HOME:
// avoids printing of wild chars in search prompt
break;
case KEY_END:
// avoids printing of wild chars in search prompt
break;
case KEY_DC: // DEL
if(selectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
almostDead=getResultFromSelection(selectionCursorPosition, hstr, result);
msg=malloc(strlen(almostDead)+1);
strcpy(msg, almostDead);
if(!hstr->noConfirm) {
print_confirm_delete(msg);
cc = wgetch(stdscr);
}
if(hstr->noConfirm || cc == 'y') {
deletedOccurences=remove_from_history_model(msg);
result=hstr_print_selection(maxHistoryItems, pattern);
print_cmd_deleted_label(msg, deletedOccurences);
} else {
print_help_label();
}
free(msg);
move(hstr->promptY, basex+strlen(pattern));
printDefaultLabel=TRUE;
print_history_label();
if(hstr->selectionSize == 0) {
// just update the cursor, there are no elements to select
move(hstr->promptY, basex+strlen(pattern));
break;
}
if(hstr->promptBottom) {
if(selectionCursorPosition <= (int)(hstr->promptYItemsEnd-hstr->selectionSize+1)) {
selectionCursorPosition=hstr->promptYItemsEnd-hstr->selectionSize+1;
}
} else {
if(selectionCursorPosition >= (int)hstr->selectionSize) {
selectionCursorPosition = hstr->selectionSize - 1;
}
}
highlight_selection(selectionCursorPosition, SELECTION_CURSOR_IN_PROMPT, pattern);
move(hstr->promptY, basex+strlen(pattern));
}
break;
case K_CTRL_E:
hstr->historyMatch++;
hstr->historyMatch=hstr->historyMatch%HH_NUM_HISTORY_MATCH;
// TODO make this a function
result=hstr_print_selection(maxHistoryItems, pattern);
print_history_label();
selectionCursorPosition=SELECTION_CURSOR_IN_PROMPT;
if(strlen(pattern)<(width-basex-1)) {
print_pattern(pattern, hstr->promptY, basex);
cursorX=getcurx(stdscr);
cursorY=getcury(stdscr);
}
break;
case K_CTRL_T:
hstr->caseSensitive=!hstr->caseSensitive;
hstr->regexp.caseSensitive=hstr->caseSensitive;
result=hstr_print_selection(maxHistoryItems, pattern);
print_history_label();
selectionCursorPosition=SELECTION_CURSOR_IN_PROMPT;
if(strlen(pattern)<(width-basex-1)) {
print_pattern(pattern, hstr->promptY, basex);
cursorX=getcurx(stdscr);
cursorY=getcury(stdscr);
}
break;
#ifdef __APPLE__
// reserved for view rotation on macOS
case K_CTRL_W:
#endif
case K_CTRL_SLASH:
hstr_next_view();
result=hstr_print_selection(maxHistoryItems, pattern);
print_history_label();
// TODO function
selectionCursorPosition=SELECTION_CURSOR_IN_PROMPT;
if(strlen(pattern)<(width-basex-1)) {
print_pattern(pattern, hstr->promptY, basex);
cursorX=getcurx(stdscr);
cursorY=getcury(stdscr);
}
break;
case K_CTRL_F:
if(selectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
result=getResultFromSelection(selectionCursorPosition, hstr, result);
if(hstr->historyView==HH_VIEW_FAVORITES) {
favorites_choose(hstr->favorites, result);
} else {
favorites_add(hstr->favorites, result);
}
hstr_print_selection(maxHistoryItems, pattern);
selectionCursorPosition=SELECTION_CURSOR_IN_PROMPT;
if(hstr->historyView!=HH_VIEW_FAVORITES) {
print_cmd_added_favorite_label(result);
printDefaultLabel=TRUE;
}
// TODO code review
if(strlen(pattern)<(width-basex-1)) {
print_pattern(pattern, hstr->promptY, basex);
cursorX=getcurx(stdscr);
cursorY=getcury(stdscr);
}
}
break;
case KEY_RESIZE:
print_history_label();
maxHistoryItems=recalculate_max_history_items();
result=hstr_print_selection(maxHistoryItems, pattern);
print_history_label();
selectionCursorPosition=SELECTION_CURSOR_IN_PROMPT;
move(hstr->promptY, basex+strlen(pattern));
break;
#ifndef __APPLE__
case K_CTRL_W: // TODO supposed to delete just one word backward
#endif
case K_CTRL_U:
pattern[0]=0;
print_pattern(pattern, hstr->promptY, basex);
break;
case K_CTRL_L:
toggle_case(pattern, lowercase);
lowercase=!lowercase;
print_pattern(pattern, hstr->promptY, basex);
selectionCursorPosition=SELECTION_CURSOR_IN_PROMPT;
break;
case K_CTRL_H:
case K_BACKSPACE:
case KEY_BACKSPACE:
if(hstr_strlen(pattern)>0) {
hstr_chop(pattern);
x--;
print_pattern(pattern, hstr->promptY, basex);
}
// TODO why I make selection if it's done in print_selection?
if(strlen(pattern)>0) {
hstr_make_selection(pattern, hstr->history, maxHistoryItems);
} else {
hstr_make_selection(NULL, hstr->history, maxHistoryItems);
}
result=hstr_print_selection(maxHistoryItems, pattern);
move(hstr->promptY, basex+hstr_strlen(pattern));
break;
case KEY_UP:
case K_CTRL_K:
case K_CTRL_P:
previousSelectionCursorPosition=selectionCursorPosition;
if(selectionCursorPosition>0) {
if(hstr->promptBottom) {
if(selectionCursorPosition <= (int)(hstr->promptYItemsEnd-hstr->selectionSize+1)) {
selectionCursorPosition=hstr->promptYItemsEnd;
} else {
selectionCursorPosition--;
}
} else {
selectionCursorPosition--;
}
} else {
if(hstr->promptBottom) {
selectionCursorPosition=hstr->promptYItemsEnd;
} else {
selectionCursorPosition=hstr->selectionSize-1;
}
}
highlight_selection(selectionCursorPosition, previousSelectionCursorPosition, pattern);
move(hstr->promptY, basex+strlen(pattern));
break;
case KEY_PPAGE:
previousSelectionCursorPosition=selectionCursorPosition;
if(selectionCursorPosition>=PG_JUMP_SIZE) {
selectionCursorPosition=selectionCursorPosition-PG_JUMP_SIZE;
} else {
selectionCursorPosition=0;
}
highlight_selection(selectionCursorPosition, previousSelectionCursorPosition, pattern);
move(hstr->promptY, basex+strlen(pattern));
break;
case KEY_DOWN:
case K_CTRL_J:
case K_CTRL_N:
if(selectionCursorPosition==SELECTION_CURSOR_IN_PROMPT) {
if(hstr->promptBottom) {
selectionCursorPosition=hstr->promptYItemsEnd-hstr->selectionSize+1;
} else {
selectionCursorPosition=previousSelectionCursorPosition=0;
}
} else {
previousSelectionCursorPosition=selectionCursorPosition;
if(hstr->promptBottom) {
if(selectionCursorPosition<hstr->promptYItemsEnd) {
selectionCursorPosition++;
} else {
selectionCursorPosition=hstr->promptYItemsEnd-hstr->selectionSize+1;
}
} else {
if((selectionCursorPosition+1) < (int)hstr->selectionSize) {
selectionCursorPosition++;
} else {
selectionCursorPosition=0;
}
}
}
if(hstr->selectionSize) {
highlight_selection(selectionCursorPosition, previousSelectionCursorPosition, pattern);
}
move(hstr->promptY, basex+strlen(pattern));
break;
case KEY_NPAGE:
if(selectionCursorPosition==SELECTION_CURSOR_IN_PROMPT) {
selectionCursorPosition=previousSelectionCursorPosition=0;
} else {
previousSelectionCursorPosition=selectionCursorPosition;
if((selectionCursorPosition+PG_JUMP_SIZE) < (int)hstr->selectionSize) {
selectionCursorPosition = selectionCursorPosition+PG_JUMP_SIZE;
} else {
selectionCursorPosition=hstr->selectionSize-1;
}
}
if(hstr->selectionSize) {
highlight_selection(selectionCursorPosition, previousSelectionCursorPosition, pattern);
}
move(hstr->promptY, basex+strlen(pattern));
break;
case K_ENTER:
case KEY_ENTER:
executeResult=TRUE;
if(selectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
result=getResultFromSelection(selectionCursorPosition, hstr, result);
if(hstr->historyView==HH_VIEW_FAVORITES) {
favorites_choose(hstr->favorites,result);
}
}
else {
if (hstr->selectionSize > 0) {
result=hstr->selection[0];
}
}
done=TRUE;
break;
case KEY_LEFT:
fixCommand=TRUE;
executeResult=TRUE;
if(selectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
result=getResultFromSelection(selectionCursorPosition, hstr, result);
if(hstr->historyView==HH_VIEW_FAVORITES) {
favorites_choose(hstr->favorites,result);
}
} else {
result=pattern;
}
done=TRUE;
break;
case K_TAB:
case KEY_RIGHT:
editCommand=TRUE;
if(selectionCursorPosition!=SELECTION_CURSOR_IN_PROMPT) {
result=getResultFromSelection(selectionCursorPosition, hstr, result);
if(hstr->historyView==HH_VIEW_FAVORITES) {
favorites_choose(hstr->favorites,result);
}
} else {
result=pattern;
}
done=TRUE;
break;
case K_CTRL_G:
case K_ESC:
result=NULL;
history_clear_dirty();
done=TRUE;
break;
case K_CTRL_X:
result=NULL;
done=TRUE;
break;
default:
LOGKEYS(Y_OFFSET_HELP, c);
LOGCURSOR(Y_OFFSET_HELP);
LOGUTF8(Y_OFFSET_HELP,pattern);
LOGSELECTION(Y_OFFSET_HELP,getmaxy(stdscr),hstr->selectionSize);
if(c>K_CTRL_Z) {
selectionCursorPosition=SELECTION_CURSOR_IN_PROMPT;
if(strlen(pattern)<(width-basex-1)) {
strcat(pattern, (char*)(&c));
print_pattern(pattern, hstr->promptY, basex);
cursorX=getcurx(stdscr);
cursorY=getcury(stdscr);
}
result = hstr_print_selection(maxHistoryItems, pattern);
move(cursorY, cursorX);
refresh();
}
break;
}
}
hstr_curses_stop(hstr->keepPage);
if(result!=NULL) {
if(fixCommand) {
fill_terminal_input("fc \"", FALSE);
}
fill_terminal_input(result, editCommand);
if(fixCommand) {
fill_terminal_input("\"", FALSE);
}
if(executeResult) {
fill_terminal_input("\n", FALSE);
}
}
}
// TODO protection from line overflow (snprinf)
void hstr_assemble_cmdline_pattern(int argc, char* argv[], int startIndex)
{
if(argc>0) {
int i;
for(i=startIndex; i<argc; i++) {
if((strlen(hstr->cmdline)+strlen(argv[i])*2)>CMDLINE_LNG) break;
if(strstr(argv[i], " ")) {
strcat(hstr->cmdline, "\"");
}
strcat(hstr->cmdline, argv[i]);
if(strstr(argv[i], " ")) {
strcat(hstr->cmdline, "\"");
}
if((i+1<argc)) {
strcat(hstr->cmdline, " ");
}
}
}
}
// TODO make favorites loading lazy (load on the first opening of favorites view)
void hstr_init_favorites(void)
{
hstr->favorites=malloc(sizeof(FavoriteItems));
favorites_init(hstr->favorites);
favorites_get(hstr->favorites);
}
void hstr_interactive(void)
{
hstr->history=get_prioritized_history(hstr->bigKeys, hstr->blacklist.set);
if(hstr->history) {
history_mgmt_open();
if(hstr->interactive) {
loop_to_select();
} else {
stdout_history_and_return();
}
hstr_on_exit();
} else {
printf("No history - nothing to suggest...\n");
}
}
void hstr_getopt(int argc, char **argv)
{
int option_index = 0;
int option = getopt_long(argc, argv, "fkVhnszb", long_options, &option_index);
if(option != -1) {
switch(option) {
case 'f':
hstr->historyView=HH_VIEW_FAVORITES;
break;
case 'n':
hstr->interactive=false;
break;
case 'k':
if(history_mgmt_remove_last_history_entry(hstr->verboseKill)) {
exit(EXIT_SUCCESS);
} else {
exit(EXIT_FAILURE);
}
case 'b':
blacklist_load(&hstr->blacklist);
blacklist_dump(&hstr->blacklist);
exit(EXIT_SUCCESS);
case 'V':
printf("%s", VERSION_STRING);
exit(EXIT_SUCCESS);
case 'h':
printf("%s", HELP_STRING);
exit(EXIT_SUCCESS);
case 'z':
printf("%s", INSTALL_ZSH_STRING);
exit(EXIT_SUCCESS);
case 's':
// ZSH_VERSION is not exported by Zsh > detected by parent process name
if(isZshParentShell()) {
printf("%s", INSTALL_ZSH_STRING);
} else {
printf("%s", INSTALL_BASH_STRING);
}
exit(EXIT_SUCCESS);
case '?':
default:
printf("%s", HELP_STRING);
exit(EXIT_SUCCESS);
}
}
if(optind < argc) {
hstr_assemble_cmdline_pattern(argc, argv, optind);
}
}
int hstr_main(int argc, char* argv[])
{
setlocale(LC_ALL, "");
hstr=malloc(sizeof(Hstr));
hstr_init();
hstr_get_env_configuration();
hstr_getopt(argc, argv);
hstr_init_favorites();
blacklist_load(&hstr->blacklist);
hstr_interactive();
favorites_destroy(hstr->favorites);
free(hstr);
return EXIT_SUCCESS;
}