diff --git a/.gitignore b/.gitignore
index 7db22fd..52a4942 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,4 @@ fzy
fzytest
*.o
*.d
-config.h
test/acceptance/vendor/bundle
diff --git a/Makefile b/Makefile
index 886ef18..298e784 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=1.0
+VERSION=1.1
CPPFLAGS=-DVERSION=\"${VERSION}\" -D_GNU_SOURCE
CFLAGS+=-MD -Wall -Wextra -g -std=c99 -O3 -pedantic -Ideps -Werror=vla
@@ -31,11 +31,11 @@ check: test/fzytest
fzy: $(OBJECTS)
$(CC) $(CFLAGS) $(CCFLAGS) -o $@ $(OBJECTS) $(LIBS)
-%.o: %.c config.h
- $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+#%.o: %.c config.h
+# $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-config.h: src/config.def.h
- cp src/config.def.h config.h
+#config.h: src/config.def.h
+# cp src/config.def.h config.h
install: fzy
mkdir -p $(DESTDIR)$(BINDIR)
@@ -45,15 +45,20 @@ install: fzy
cp fzy.1 $(DESTDIR)$(MANDIR)/man1/
chmod 644 ${DESTDIR}${MANDIR}/man1/fzy.1
+uninstall:
+ rm -- $(DESTDIR)$(BINDIR)/fzy
+ rm -- $(DESTDIR)$(MANDIR)/fzy.1
+
fmt:
clang-format -i src/*.c src/*.h
clean:
rm -f fzy test/fzytest src/*.o src/*.d deps/*/*.o
-veryclean: clean
- rm -f config.h
+#veryclean: clean
+# rm -f config.h
-.PHONY: test check all clean veryclean install fmt acceptance
+#.PHONY: test check all clean veryclean install fmt acceptance
+.PHONY: test check all clean install fmt acceptance
-include $(OBJECTS:.o=.d)
diff --git a/README.md b/README.md
index d053934..a3f349c 100644
--- a/README.md
+++ b/README.md
@@ -2,22 +2,10 @@
**fzy** is a fast, simple fuzzy text selector for the terminal with an advanced scoring algorithm.
-[Try it out online!](http://jhawthorn.github.io/fzy-demo)
+**Disclaimer**: This fork (originaly intended to make fzy work with [the clifm file manager](https://github.com/leo-arch/clifm)) adds a few new features to the origianl fzy, including basic color support and multi-selection. Consult the manpage for more information.
![](http://i.hawth.ca/u/fzy_animated_demo.svg)
-
-It's been kind of life-changing.
--@graygilmore
-
-
-
-fzy works great btw
--@alexblackie
-
-
- [![Build Status](https://github.com/jhawthorn/fzy/workflows/CI/badge.svg)](https://github.com/jhawthorn/fzy/actions)
-
## Why use this over fzf, pick, selecta, ctrlp, ...?
fzy is faster and shows better results than other fuzzy finders.
@@ -34,32 +22,13 @@ Rather than clearing the screen, fzy displays its interface directly below the c
## Installation
-**macOS**
-
-Using Homebrew
-
- brew install fzy
-
-Using MacPorts
-
- sudo port install fzy
-
-**[Arch Linux](https://www.archlinux.org/packages/?sort=&q=fzy&maintainer=&flagged=)/MSYS2**: `pacman -S fzy`
-
-**[FreeBSD](https://www.freebsd.org/cgi/ports.cgi?query=fzy&stype=all)**: `pkg install fzy`
-
-**[Gentoo Linux](https://packages.gentoo.org/packages/app-shells/fzy)**: `emerge -av app-shells/fzy`
-
-**[Ubuntu](https://packages.ubuntu.com/search?keywords=fzy&searchon=names&suite=bionic§ion=all)/[Debian](https://packages.debian.org/search?keywords=fzy&searchon=names&suite=all§ion=all)**: `apt-get install fzy`
-
-**[pkgsrc](http://pkgsrc.se/misc/fzy) (NetBSD and others)**: `pkgin install fzy`
-
-**[openSUSE](https://software.opensuse.org/package/fzy)**: `zypper in fzy`
-
-### From source
-
- make
- sudo make install
+```sh
+mkdir build && cd build
+git clone https://github.com/leo-arch/fzy
+cd fzy
+make
+sudo make install
+```
The `PREFIX` environment variable can be used to specify the install location,
the default is `/usr/local`.
@@ -98,6 +67,14 @@ nnoremap v :call FzyCommand("ag . --silent -l -g ''", ":vs")
nnoremap s :call FzyCommand("ag . --silent -l -g ''", ":sp")
```
+### Use with [clifm](https://github.com/leo-arch/clifm)
+
+Just run clifm as follows:
+
+```sh
+clifm --fzytab
+```
+
## Sorting
fzy attempts to present the best matches first. The following considerations are weighted when sorting:
@@ -109,9 +86,3 @@ It prefers matching the beginning of words: `amp` is likely to match aabcdef over abc de.
It prefers shorter candidates: `test` matches tests over testing.
-
-## See Also
-
-* [fzy.js](https://github.com/jhawthorn/fzy.js) Javascript port
-
-
diff --git a/fzy.1 b/fzy.1
index 9c34a21..ac7dc65 100644
--- a/fzy.1
+++ b/fzy.1
@@ -1,4 +1,4 @@
-.TH FZY 1 "2018-09-23" "fzy 1.0"
+.TH FZY 1 "Jul 9, 2022" "fzy 1.0"
.SH NAME
fzy \- A fuzzy text selector menu for the terminal.
.SH SYNOPSIS
@@ -25,6 +25,14 @@ How many lines of items to display. If unspecified, defaults to 10 lines.
Input prompt (default: '> ')
.
.TP
+.BR \-P ", " \-\-pad =\fINUM\fR
+Left pad the list of matches NUM places (default: 0)
+.
+.TP
+.BR \-m ", " \-\-multi
+Enable multi-selection
+.
+.TP
.BR \-s ", " \-\-show-scores
Show the scores for each item.
.
@@ -52,32 +60,99 @@ Usage help.
.BR \-v ", " \-\-version
Usage help.
.
+.TP
+.BR \-\-pointer =\fICHAR\fR
+Pointer to highlightled match (default '>')
+.
+.TP
+.BR \-\-marker =\fICHAR\fR
+Multi-select marker (default '*')
+.
+.TP
+.BR \-\-cyclic
+Enable cyclic scrolling
+.
+.TP
+.BR \-\-tab-accetps
+TAB accepts: print selection and exit
+.
+.TP
+.BR \-\-right-accepts
+Right arrow key accepts: print selection and exit
+.
+.TP
+.BR \-\-left-aborts
+Left arrow key aborts: cancel selection and exit
+.
+.TP
+.BR \-\-reverse
+List from top, prompt at bottom
+.
+.TP
+.BR \-\-no\-color
+Run colorless
+.
.SH KEYS
.
.TP
.BR "ENTER"
-Print the selected item to stdout and exit
+Print the selected items to stdout and exit. If \fI\-\-right\-accepts\fR is set, the Right arrow key performs the same function. Equally, if \fI\-\-tab\-accepts\fR is set, the TAB key performs the same function.
+.TP
+.BR "ESC"
+Exit without printing any result. If \fI\-\-left\-aborts\fR is set, the Left arrow key performs the same function.
.TP
.BR "Ctrl+c, Ctrl+g, Esc"
Exit with status 1, without making a selection.
.TP
.BR "Up Arrow, Ctrl+p, Ctrl+k"
-Select the previous item
+Select the previous item.
.TP
.BR "Down Arrow, Ctrl+n, Ctrl+j"
-Select the next item
+Select the next item.
.TP
-Tab
-Replace the current search string with the selected item
+.BR "TAB"
+Replace the current search string with the selected item. If the multi-selection mode (\fI-m, --multi\fR) is enabled, TAB is used to (un)mark the selected entry instead.
.TP
.BR "Backspace, Ctrl+h"
-Delete the character before the cursor
+Delete the character before the cursor.
.TP
.BR Ctrl+w
-Delete the word before the cursor
+Delete the word before the cursor.
.TP
.BR Ctrl+u
-Delete the entire line
+Delete the entire line.
+.
+.SH COLORS
+Interface colors are read from the environment variable \fBFZY_COLORS\fR using a simple pattern: the \fIorder\fR of the color code specifies which \fIinterface element\fR the color must be applied to, while the \fIcontent\fR of this code defines the \fIcolor\fR of this element.
+.sp
+\fBA\fR. The order is this:
+ 1) Prompt
+ 2) Pointer
+ 3) Marker
+ 4) Current entry foreground
+ 5) Current entry background
+.sp
+\fBB\fR. Possible content (available colors):
+ 0 = black
+ 1 = red
+ 2 = green
+ 3 = yellow
+ 4 = blue
+ 5 = magenta
+ 6 = cyan
+ 7 = white
+.sp
+Use a \fBb\fR before the color to make it bold/bright. A dash (\-) means that the color for the interface element in that position must be skipped.
+.sp
+For example, \fBFZF_COLORS="\-b1b2\-4"\fR is to be read as follows:
+.sp
+ \fB\-\fR: no prompt color
+ \fBb1\fR: bold red pointer color
+ \fBb2\fR: bold green marker color
+ \fB\-\fR: no color for the current entry foreground
+ \fB4\fR: blue current entry background
+.sp
+Default colors are: \fBb6b1b2b40\fR
.
.SH USAGE EXAMPLES
.
@@ -85,19 +160,21 @@ Delete the entire line
.BR "ls | fzy"
Present a menu of items in the current directory
.TP
-.BR "ls | fzy -l 25"
+.BR "ls | fzy \-l 25"
Same as above, but show 25 lines of items
.TP
-.BR "vi $(find -type f | fzy)"
+.BR "vi $(find \-type f | fzy)"
List files under the current directory and open the one selected in vi.
.TP
-.BR "cd $(find -type d | fzy)"
+.BR "cd $(find \-type d | fzy)"
Present all directories under current path, and change to the one selected.
.TP
.BR "ps aux | fzy | awk '{ print $2 }' | xargs kill"
List running processes, kill the selected process
.TP
-.BR "git checkout $(git branch | cut -c 3- | fzy)"
+.BR "git checkout $(git branch | cut \-c 3\- | fzy)"
Same as above, but switching git branches.
-.SH AUTHOR
+.SH AUTHORS
John Hawthorn
+
+L. Abramovich
diff --git a/src/bonus.h b/src/bonus.h
index 89cafbe..d0e50d2 100644
--- a/src/bonus.h
+++ b/src/bonus.h
@@ -1,7 +1,7 @@
#ifndef BONUS_H
#define BONUS_H BONUS_H
-#include "../config.h"
+#include "config.h"
#define ASSIGN_LOWER(v) \
['a'] = (v), \
diff --git a/src/choices.c b/src/choices.c
index fe2f80b..6f1b012 100644
--- a/src/choices.c
+++ b/src/choices.c
@@ -15,7 +15,9 @@
/* Initial size of choices array */
#define INITIAL_CHOICE_CAPACITY 128
-static int cmpchoice(const void *_idx1, const void *_idx2) {
+static int
+cmpchoice(const void *_idx1, const void *_idx2)
+{
const struct scored_result *a = _idx1;
const struct scored_result *b = _idx2;
@@ -36,7 +38,9 @@ static int cmpchoice(const void *_idx1, const void *_idx2) {
}
}
-static void *safe_realloc(void *buffer, size_t size) {
+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);
@@ -46,7 +50,9 @@ static void *safe_realloc(void *buffer, size_t size) {
return buffer;
}
-void choices_fread(choices_t *c, FILE *file, char input_delimiter) {
+void
+choices_fread(choices_t *c, FILE *file, char input_delimiter)
+{
/* Save current position for parsing later */
size_t buffer_start = c->buffer_size;
@@ -87,18 +93,24 @@ void choices_fread(choices_t *c, FILE *file, char input_delimiter) {
} while (line && line < line_end);
}
-static void choices_resize(choices_t *c, size_t new_capacity) {
+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) {
+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) {
+void
+choices_init(choices_t *c, options_t *options)
+{
c->strings = NULL;
c->results = NULL;
@@ -108,16 +120,17 @@ void choices_init(choices_t *c, options_t *options) {
c->capacity = c->size = 0;
choices_resize(c, INITIAL_CHOICE_CAPACITY);
- if (options->workers) {
+ if (options->workers)
c->worker_count = options->workers;
- } else {
+ else
c->worker_count = (int)sysconf(_SC_NPROCESSORS_ONLN);
- }
choices_reset_search(c);
}
-void choices_destroy(choices_t *c) {
+void
+choices_destroy(choices_t *c)
+{
free(c->buffer);
c->buffer = NULL;
c->buffer_size = 0;
@@ -131,17 +144,21 @@ void choices_destroy(choices_t *c) {
c->available = c->selection = 0;
}
-void choices_add(choices_t *c, const char *choice) {
+void
+choices_add(choices_t *c, const char *choice)
+{
/* Previous search is now invalid */
choices_reset_search(c);
- if (c->size == c->capacity) {
+ if (c->size == c->capacity)
choices_resize(c, c->capacity * 2);
- }
+
c->strings[c->size++] = choice;
}
-size_t choices_available(choices_t *c) {
+size_t
+choices_available(choices_t *c)
+{
return c->available;
}
@@ -167,22 +184,24 @@ struct worker {
struct result_list result;
};
-static void worker_get_next_batch(struct search_job *job, size_t *start, size_t *end) {
+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) {
+ 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) {
+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;
@@ -194,19 +213,17 @@ static struct result_list merge2(struct result_list list1, struct result_list li
}
while(index1 < list1.size && index2 < list2.size) {
- if (cmpchoice(&list1.list[index1], &list2.list[index2]) < 0) {
+ if (cmpchoice(&list1.list[index1], &list2.list[index2]) < 0)
result.list[result_index++] = list1.list[index1++];
- } else {
+ else
result.list[result_index++] = list2.list[index2++];
- }
}
- while(index1 < list1.size) {
+ while(index1 < list1.size)
result.list[result_index++] = list1.list[index1++];
- }
- while(index2 < list2.size) {
+
+ while(index2 < list2.size)
result.list[result_index++] = list2.list[index2++];
- }
free(list1.list);
free(list2.list);
@@ -214,7 +231,9 @@ static struct result_list merge2(struct result_list list1, struct result_list li
return result;
}
-static void *choices_search_worker(void *data) {
+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;
@@ -225,9 +244,8 @@ static void *choices_search_worker(void *data) {
for(;;) {
worker_get_next_batch(job, &start, &end);
- if(start == end) {
+ if(start == end)
break;
- }
for(size_t i = start; i < end; i++) {
if (has_match(job->search, c->strings[i])) {
@@ -258,20 +276,32 @@ static void *choices_search_worker(void *data) {
w->result = merge2(w->result, job->workers[next_worker].result);
}
- return NULL;
+ return (char *)NULL;
}
-void choices_search(choices_t *c, const char *search) {
+void
+choices_search(choices_t *c, const char *search)
+{
choices_reset_search(c);
struct search_job *job = calloc(1, sizeof(struct search_job));
+ if (!job) {
+ fprintf(stderr, "Error: Can't allocate memory\n");
+ abort();
+ }
+
job->search = search;
job->choices = c;
if (pthread_mutex_init(&job->lock, NULL) != 0) {
fprintf(stderr, "Error: pthread_mutex_init failed\n");
abort();
}
+
job->workers = calloc(c->worker_count, sizeof(struct worker));
+ if (!job->workers) {
+ fprintf(stderr, "Error: Can't allocate memory\n");
+ abort();
+ }
struct worker *workers = job->workers;
for (int i = c->worker_count - 1; i >= 0; i--) {
@@ -300,24 +330,31 @@ void choices_search(choices_t *c, const char *search) {
free(job);
}
-const char *choices_get(choices_t *c, size_t n) {
- if (n < c->available) {
+const char *
+choices_get(choices_t *c, size_t n)
+{
+ if (n < c->available)
return c->results[n].str;
- } else {
- return NULL;
- }
+ else
+ return (char *)NULL;
}
-score_t choices_getscore(choices_t *c, size_t n) {
+score_t
+choices_getscore(choices_t *c, size_t n)
+{
return c->results[n].score;
}
-void choices_prev(choices_t *c) {
+void
+choices_prev(choices_t *c)
+{
if (c->available)
c->selection = (c->selection + c->available - 1) % c->available;
}
-void choices_next(choices_t *c) {
+void
+choices_next(choices_t *c)
+{
if (c->available)
c->selection = (c->selection + 1) % c->available;
}
diff --git a/src/config.def.h b/src/config.def.h
deleted file mode 100644
index fcdcc03..0000000
--- a/src/config.def.h
+++ /dev/null
@@ -1,19 +0,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/src/config.h b/src/config.h
new file mode 100644
index 0000000..f2a9fb5
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1,50 @@
+#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
+#define DEFAULT_DELIMITER '\n'
+#define DEFAULT_BENCHMARK 0
+#define DEFAULT_SCORES 0
+#define DEFAULT_SCROLLOFF 0
+#define DEFAULT_FILTER NULL
+#define DEFAULT_INIT_SEARCH NULL
+#define DEFAULT_MARKER '*'
+#define DEFAULT_POINTER '>'
+#define DEFAULT_PAD 0
+#define DEFAULT_MULTI 0
+#define DEFAULT_CYCLE 0
+#define DEFAULT_TAB_ACCEPTS 0
+#define DEFAULT_RIGHT_ACCEPTS 0
+#define DEFAULT_LEFT_ABORTS 0
+#define DEFAULT_NO_COLOR 0
+#define DEFAULT_REVERSE 0
+
+#define DEFAULT_COLORS "b6b1b2b40"
+#define NC "\x1b[0m" /* Reset attributes */
+
+/* Color indices: colors (from FZY_COLORS env var) will be parsed
+ * exactly in this order. See tty_interface.c */
+#define PROMPT_COLOR 0
+#define POINTER_COLOR 1
+#define MARKER_COLOR 2
+#define SEL_FG_COLOR 3
+#define SEL_BG_COLOR 4
+#define COLOR_ITEMS_NUM 5
+#define MAX_COLOR_LEN 48
+
+#define VERSION "1.1"
diff --git a/src/fzy.c b/src/fzy.c
index 967a1fc..aa974bc 100644
--- a/src/fzy.c
+++ b/src/fzy.c
@@ -11,9 +11,11 @@
#include "options.h"
#include "tty_interface.h"
-#include "../config.h"
+#include "config.h"
-int main(int argc, char *argv[]) {
+int
+main(int argc, char *argv[])
+{
int ret = 0;
options_t options;
diff --git a/src/match.c b/src/match.c
index d618f0a..f1e2337 100644
--- a/src/match.c
+++ b/src/match.c
@@ -9,14 +9,18 @@
#include "match.h"
#include "bonus.h"
-#include "../config.h"
+#include "config.h"
-char *strcasechr(const char *s, char c) {
+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) {
+int
+has_match(const char *needle, const char *haystack)
+{
while (*needle) {
char nch = *needle++;
@@ -42,7 +46,9 @@ struct match_struct {
score_t match_bonus[MATCH_MAX_LEN];
};
-static void precompute_bonus(const char *haystack, score_t *match_bonus) {
+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++) {
@@ -52,13 +58,14 @@ static void precompute_bonus(const char *haystack, score_t *match_bonus) {
}
}
-static void setup_match_struct(struct match_struct *match, const char *needle, const char *haystack) {
+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) {
+ 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]);
@@ -69,7 +76,10 @@ static void setup_match_struct(struct match_struct *match, const char *needle, c
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) {
+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;
@@ -102,7 +112,9 @@ static inline void match_row(const struct match_struct *match, int row, score_t
}
}
-score_t match(const char *needle, const char *haystack) {
+score_t
+match(const char *needle, const char *haystack)
+{
if (!*needle)
return SCORE_MIN;
@@ -151,7 +163,9 @@ score_t match(const char *needle, const char *haystack) {
return last_M[m - 1];
}
-score_t match_positions(const char *needle, const char *haystack, size_t *positions) {
+score_t
+match_positions(const char *needle, const char *haystack, size_t *positions)
+{
if (!*needle)
return SCORE_MIN;
diff --git a/src/options.c b/src/options.c
index e35402f..fe1f07d 100644
--- a/src/options.c
+++ b/src/options.c
@@ -6,13 +6,15 @@
#include "options.h"
-#include "../config.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"
+ " -m, --multi Enable multi-selection\n"
" -p, --prompt=PROMPT Input prompt (default '> ')\n"
+ " -P, --pad=NUM Left pad the list of matches NUM places (default 0)\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"
@@ -20,14 +22,25 @@ static const char *usage_str =
" -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";
+ " -h, --help Display this help and exit\n"
+ " -v, --version Output version information and exit\n"
+ " --pointer Pointer to highlighted match (default '>')\n"
+ " --marker Multi-select marker (default '*')\n"
+ " --cycle Enable cyclic scrolling\n"
+ " --tab-accepts TAB accepts\n"
+ " --right-accepts Right arrow key accepts\n"
+ " --left-aborts Left arrow key aborts\n"
+ " --reverse Display from top, prompt at bottom\n"
+ " --no-color Run colorless\n";
-static void usage(const char *argv0) {
+static void
+usage(const char *argv0)
+{
fprintf(stderr, usage_str, argv0);
}
-static struct option longopts[] = {{"show-matches", required_argument, NULL, 'e'},
+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'},
@@ -39,85 +52,120 @@ static struct option longopts[] = {{"show-matches", required_argument, NULL, 'e'
{"workers", required_argument, NULL, 'j'},
{"show-info", no_argument, NULL, 'i'},
{"help", no_argument, NULL, 'h'},
- {NULL, 0, NULL, 0}};
+ {"pad", required_argument, NULL, 'P'},
+ {"multi", no_argument, NULL, 'm'},
+ {"pointer", required_argument, NULL, 1},
+ {"marker", required_argument, NULL, 2},
+ {"cycle", no_argument, NULL, 3},
+ {"tab-accepts", no_argument, NULL, 4},
+ {"right-accepts", no_argument, NULL, 5},
+ {"left-aborts", no_argument, NULL, 6},
+ {"no-color", no_argument, NULL, 7},
+ {"reverse", no_argument, NULL, 8},
+ {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;
+void
+options_init(options_t *options)
+{
+ /* Set defaults */
+ options->benchmark = DEFAULT_BENCHMARK;
+ options->filter = DEFAULT_FILTER;
+ options->init_search = DEFAULT_INIT_SEARCH;
+ options->show_scores = DEFAULT_SCORES;
+ options->scrolloff = DEFAULT_SCROLLOFF;
options->tty_filename = DEFAULT_TTY;
options->num_lines = DEFAULT_NUM_LINES;
options->prompt = DEFAULT_PROMPT;
options->workers = DEFAULT_WORKERS;
- options->input_delimiter = '\n';
+ options->input_delimiter = DEFAULT_DELIMITER;
options->show_info = DEFAULT_SHOW_INFO;
+ options->pad = DEFAULT_PAD;
+ options->multi = DEFAULT_MULTI;
+ options->pointer = DEFAULT_POINTER;
+ options->marker = DEFAULT_MARKER;
+ options->cycle = DEFAULT_CYCLE;
+ options->tab_accepts = DEFAULT_TAB_ACCEPTS;
+ options->right_accepts = DEFAULT_RIGHT_ACCEPTS;
+ options->left_aborts = DEFAULT_LEFT_ABORTS;
+ options->no_color = DEFAULT_NO_COLOR;
+ options->reverse = DEFAULT_REVERSE;
}
-void options_parse(options_t *options, int argc, char *argv[]) {
+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) {
+ while ((c = getopt_long(argc, argv, "mvhs0e:q:l:t:p: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) {
+ case 'v':
+ printf("%s\n", VERSION);
+ exit(EXIT_SUCCESS);
+ case 's': options->show_scores = 1; break;
+ case '0': options->input_delimiter = '\0'; break;
+ case 'm': options->multi = 1; 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);
}
- 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:
+ } else {
+ options->benchmark = 100;
+ }
+ break;
+ case 't': options->tty_filename = optarg; break;
+ case 'p': options->prompt = optarg; break;
+ case 'P':
+ if (optarg && *optarg && *optarg >= '0' && *optarg <= '9')
+ options->pad = atoi(optarg);
+ break;
+ case 'j':
+ if (sscanf(optarg, "%u", &options->workers) != 1) {
usage(argv[0]);
- exit(EXIT_SUCCESS);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'l': {
+ if (!optarg)
+ break;
+ int l;
+ if (!strcmp(optarg, "max")) {
+ l = INT_MAX;
+// } else if (sscanf(optarg, "%d", &l) != 1 || l < 3) {
+ } else if (sscanf(optarg, "%d", &l) != 1 || l < 2) {
+ fprintf(stderr, "Invalid format for --lines: %s\n", optarg);
+ fprintf(stderr, "Must be integer in range 2..\n");
+// 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 1:
+ if (optarg && *optarg)
+ options->pointer = *optarg;
+ break;
+ case 2:
+ if (optarg && *optarg)
+ options->marker = *optarg;
+ break;
+ case 3: options->cycle = 1; break;
+ case 4: options->tab_accepts = 1; break;
+ case 5: options->right_accepts = 1; break;
+ case 6: options->left_aborts = 1; break;
+ case 7: options->no_color = 1; break;
+ case 8: options->reverse = 1; break;
+
+ case 'h': /* fallthrough */
+ default:
+ usage(argv[0]);
+ exit(EXIT_SUCCESS);
}
}
if (optind != argc) {
diff --git a/src/options.h b/src/options.h
index 4be4cb6..10799c6 100644
--- a/src/options.h
+++ b/src/options.h
@@ -13,6 +13,16 @@ typedef struct {
unsigned int workers;
char input_delimiter;
int show_info;
+ int pad;
+ int multi;
+ char pointer;
+ char marker;
+ int cycle;
+ int tab_accepts;
+ int right_accepts;
+ int left_aborts;
+ int no_color;
+ int reverse;
} options_t;
void options_init(options_t *options);
diff --git a/src/tty.c b/src/tty.c
index 733477e..f48a46a 100644
--- a/src/tty.c
+++ b/src/tty.c
@@ -11,23 +11,31 @@
#include "tty.h"
-#include "../config.h"
+#include "config.h"
-void tty_reset(tty_t *tty) {
+void
+tty_reset(tty_t *tty)
+{
tcsetattr(tty->fdin, TCSANOW, &tty->original_termios);
}
-void tty_close(tty_t *tty) {
+void
+tty_close(tty_t *tty)
+{
tty_reset(tty);
fclose(tty->fout);
close(tty->fdin);
}
-static void handle_sigwinch(int sig){
+static void
+handle_sigwinch(int sig)
+{
(void)sig;
}
-void tty_init(tty_t *tty, const char *tty_filename) {
+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");
@@ -66,13 +74,13 @@ void tty_init(tty_t *tty, const char *tty_filename) {
perror("tcsetattr");
tty_getwinsz(tty);
-
tty_setnormal(tty);
-
signal(SIGWINCH, handle_sigwinch);
}
-void tty_getwinsz(tty_t *tty) {
+void
+tty_getwinsz(tty_t *tty)
+{
struct winsize ws;
if (ioctl(fileno(tty->fout), TIOCGWINSZ, &ws) == -1) {
tty->maxwidth = 80;
@@ -83,7 +91,9 @@ void tty_getwinsz(tty_t *tty) {
}
}
-char tty_getchar(tty_t *tty) {
+char
+tty_getchar(tty_t *tty)
+{
char ch;
int size = read(tty->fdin, &ch, 1);
if (size < 0) {
@@ -97,7 +107,9 @@ char tty_getchar(tty_t *tty) {
}
}
-int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal) {
+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);
@@ -129,73 +141,105 @@ int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal) {
}
}
-static void tty_sgr(tty_t *tty, int code) {
+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) {
+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) {
+void
+tty_setinvert(tty_t *tty)
+{
tty_sgr(tty, 7);
}
-void tty_setunderline(tty_t *tty) {
+void
+tty_setunderline(tty_t *tty)
+{
tty_sgr(tty, 4);
}
-void tty_setnormal(tty_t *tty) {
+void
+tty_setnormal(tty_t *tty)
+{
tty_sgr(tty, 0);
tty->fgcolor = 9;
}
-void tty_setnowrap(tty_t *tty) {
+void
+tty_setnowrap(tty_t *tty)
+{
tty_printf(tty, "%c%c?7l", 0x1b, '[');
}
-void tty_setwrap(tty_t *tty) {
+void
+tty_setwrap(tty_t *tty)
+{
tty_printf(tty, "%c%c?7h", 0x1b, '[');
}
-void tty_newline(tty_t *tty) {
+void
+tty_newline(tty_t *tty)
+{
tty_printf(tty, "%c%cK\n", 0x1b, '[');
}
-void tty_clearline(tty_t *tty) {
+void
+tty_clearline(tty_t *tty)
+{
tty_printf(tty, "%c%cK", 0x1b, '[');
}
-void tty_setcol(tty_t *tty, int col) {
+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) {
+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, ...) {
+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) {
+void
+tty_putc(tty_t *tty, char c)
+{
fputc(c, tty->fout);
}
-void tty_flush(tty_t *tty) {
+void
+tty_flush(tty_t *tty)
+{
fflush(tty->fout);
}
-size_t tty_getwidth(tty_t *tty) {
+size_t
+tty_getwidth(tty_t *tty)
+{
return tty->maxwidth;
}
-size_t tty_getheight(tty_t *tty) {
+size_t
+tty_getheight(tty_t *tty)
+{
return tty->maxheight;
}
diff --git a/src/tty_interface.c b/src/tty_interface.c
index 343dde8..c6e5c8a 100644
--- a/src/tty_interface.c
+++ b/src/tty_interface.c
@@ -5,32 +5,236 @@
#include "match.h"
#include "tty_interface.h"
-#include "../config.h"
+#include "config.h"
+
+#ifndef PATH_MAX
+# ifdef __linux__
+# define PATH_MAX 4096
+# else
+# define PATH_MAX 1024
+# endif /* __linux */
+#endif /* PATH_MAX */
+
+#define _ESC 27
+
+/* Array to store selected/marked entries */
+static char **selections = (char **)NULL;
+/* A buffer big enough to hold decolored entries */
+static char buf[PATH_MAX];
+
+/* SEL_N is the current size of the selections array, while SEL_COUNTER
+ * is the current amount of actually selected entries */
+static size_t seln = 0, sel_counter = 0;
+
+static char colors[COLOR_ITEMS_NUM][MAX_COLOR_LEN];
+/* Parse colors taken from FZY_COLORS environment variable
+ * Colors are parsed in strict order (see config.h)
+ * Colors could be: 0-7 for normal colors, and b0-b7 for bold colors
+ * Specific colors could be skipped using a dash ('-').
+ * Colors are stored in the COLORS array using the same order defined in
+ * config.h
+ * These colors are applied in draw() and draw_match() functions in this file
+ *
+ * For example, "-b1b2-4" is read as follows:
+ * -: no PROMPT color
+ * b1: bold red POINTER color
+ * b2: bold green MARKER color
+ * -: no SELECTED ENTRY FOREGROUND color
+ * 4: blue SELECTED ENTRY BACKGROUND color
+ * */
+static void
+set_colors(void)
+{
+ char *p = getenv("NO_COLOR");
+ if (p)
+ return;
+
+ p = getenv("FZY_COLORS");
+ if (!p || !*p)
+ p = DEFAULT_COLORS;
+
+ size_t i, b = 0, c = 0;
+ for (i = 0; p[i] && c < COLOR_ITEMS_NUM; i++) {
+ if (p[i] == 'b') {
+ b = 1;
+ continue;
+ }
+ if ((p[i] < '0' || p[i] > '7') || p[i] == '-') {
+ *colors[c] = '\0';
+ b = 0;
+ c++;
+ continue;
+ }
+ /* 16 colors: 0-7 normal; b0-b7 bright */
+ snprintf(colors[c], MAX_COLOR_LEN, "\x1b[%s%c%cm",
+ b == 1 ? "1;" : "",
+ c == SEL_BG_COLOR ? '4' : '3',
+ p[i]);
+ b = 0;
+ c++;
+ }
+}
+
+/* Search for the string P in the selections array. If found, return 1,
+ * otherwise zero */
+static int
+is_selected(const char *p)
+{
+ if (!p || !*p || sel_counter == 0)
+ return 0;
+
+ size_t i;
+ for (i = 0; selections[i]; i++) {
+ if (*selections[i] == *p && strcmp(selections[i], p) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Remote the entry NAME from the selections array by setting the first
+ * byte of the corresponding array entry to NUL */
+static void
+deselect_entry(char *name)
+{
+ if (!name || !*name || sel_counter == 0)
+ return;
+
+ size_t i;
+ for (i = 0; selections[i]; i++) {
+ if (*selections[i] != *name || strcmp(selections[i], name) != 0)
+ continue;
+ *selections[i] = '\0';
+ sel_counter--;
+ break;
+ }
+}
+
+static char *
+decolor_name(const char *name)
+{
+ if (!name)
+ return (char *)NULL;
+
+ char *p = buf, *q = buf;
+
+ size_t i, j = 0;
+ size_t name_len = strlen(name);
+ for (i = 0; name[i] && i < name_len; i++) {
+ if (name[i] == _ESC && name[i + 1] == '[') {
+ for (j = i + 1; name[j]; j++) {
+ if (name[j] != 'm')
+ continue;
+ i = j + (name[j + 1] == _ESC ? 0 : 1);
+ break;
+ }
+ }
+
+ if (i == j) /* We have another escape code */
+ continue;
+ *p = name[i];
+ p++;
+ }
+
+ *p = '\0';
+ return *q ? q : (char *)NULL;
+}
+
+/* Save the string P into the selections array */
+static void
+save_selection(const char *p)
+{
+ selections = (char **)realloc(selections, (seln + 2) * sizeof(char *));
+ selections[seln] = (char *)malloc((strlen(p) + 1) * sizeof(char));
+ strcpy(selections[seln], p);
+ seln++;
+ sel_counter++;
+ selections[seln] = (char *)NULL;
+}
+
+/* Select the currently highighted/hovered entry if not already selected.
+ * Otherwise, remove it from the selections list */
+static int
+action_select(tty_interface_t *state)
+{
+ const char *p = choices_get(state->choices, state->choices->selection);
+ if (!p)
+ return EXIT_FAILURE;
+
+ if (is_selected(p) == 1) {
+ deselect_entry((char *)p);
+ return EXIT_FAILURE;
+ }
+
+ save_selection(p);
+ return EXIT_SUCCESS;
+}
+
+/* Print the list of selected/marked entries to STDOUT */
+static void
+print_selections(tty_interface_t *state)
+{
+ if (sel_counter == 0 || state->options->multi == 0)
+ return;
+
+ size_t i;
+ for (i = 0; selections[i]; i++) {
+ if (!*selections[i])
+ continue;
+ char *p = (char *)NULL;
+ if (strchr(selections[i], _ESC))
+ p = decolor_name(selections[i]);
+ printf("%s\n", p ? p : selections[i]);
+ }
+
+}
-static int isprint_unicode(char c) {
+/* Free the selections array */
+static void
+free_selections(tty_interface_t *state)
+{
+ if (state->options->multi == 0 || seln == 0 || !selections)
+ return;
+
+ size_t i;
+ for (i = 0; selections[i]; i++)
+ free(selections[i]);
+ free(selections);
+ selections = (char **)NULL;
+}
+
+static int
+isprint_unicode(char c)
+{
return isprint(c) || c & (1 << 7);
}
-static int is_boundary(char c) {
+static int
+is_boundary(char c)
+{
return ~c & (1 << 7) || c & (1 << 6);
}
-static void clear(tty_interface_t *state) {
+static void
+clear(tty_interface_t *state)
+{
tty_t *tty = state->tty;
- tty_setcol(tty, 0);
+ tty_setcol(tty, state->options->pad);
size_t line = 0;
- while (line++ < state->options->num_lines + (state->options->show_info ? 1 : 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) {
+ 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) {
+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;
@@ -50,12 +254,21 @@ static void draw_match(tty_interface_t *state, const char *choice, int selected)
}
}
- if (selected)
+ if (selected) {
#ifdef TTY_SELECTION_UNDERLINE
tty_setunderline(tty);
#else
- tty_setinvert(tty);
+ /* Let's colorize the selected entry */
+ if (*colors[SEL_FG_COLOR] || *colors[SEL_BG_COLOR]) {
+ if (*colors[SEL_FG_COLOR])
+ tty_printf(tty, "%s", colors[SEL_FG_COLOR]);
+ if (*colors[SEL_BG_COLOR])
+ tty_printf(tty, "%s", colors[SEL_BG_COLOR]);
+ } else {
+ tty_setinvert(tty);
+ }
#endif
+ }
tty_setnowrap(tty);
for (size_t i = 0, p = 0; choice[i] != '\0'; i++) {
@@ -65,17 +278,90 @@ static void draw_match(tty_interface_t *state, const char *choice, int selected)
} else {
tty_setfg(tty, TTY_COLOR_NORMAL);
}
- if (choice[i] == '\n') {
+ if (choice[i] == '\n')
tty_putc(tty, ' ');
- } else {
+ else
tty_printf(tty, "%c", choice[i]);
- }
}
tty_setwrap(tty);
tty_setnormal(tty);
}
-static void draw(tty_interface_t *state) {
+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;
+ }
+ }
+
+ if (options->reverse == 0) {
+ tty_setcol(tty, options->pad);
+ 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++) {
+ if (options->reverse == 0)
+ tty_printf(tty, "\n");
+ tty_clearline(tty);
+ const char *choice = choices_get(choices, i);
+ if (choice) {
+ int multi_sel = options->multi == 1 && is_selected((char *)choice);
+ tty_printf(tty, "%*s%s%c%s%c%s",
+ options->pad, "", colors[POINTER_COLOR],
+ i == choices->selection ? options->pointer : ' ',
+ colors[MARKER_COLOR],
+ multi_sel == 1 ? options->marker : ' ', NC);
+ draw_match(state, choice, i == choices->selection);
+ }
+ if (options->reverse == 1)
+ tty_printf(tty, "\n");
+ }
+
+ if (options->reverse == 0 && num_lines + options->show_info)
+ tty_moveup(tty, num_lines + options->show_info);
+
+ tty_setcol(tty, options->pad);
+ tty_printf(tty, "%s%s%s", colors[PROMPT_COLOR], options->prompt, NC);
+ for (size_t i = 0; i < state->cursor; i++)
+ fputc(state->search[i], tty->fout);
+
+ if (options->reverse == 0) {
+ tty_flush(tty);
+ return;
+ }
+
+ tty_setcol(tty, options->pad);
+ 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);
+ }
+ tty_flush(tty);
+}
+
+/*
+static void
+draw(tty_interface_t *state)
+{
tty_t *tty = state->tty;
choices_t *choices = state->choices;
options_t *options = state->options;
@@ -91,7 +377,10 @@ static void draw(tty_interface_t *state) {
}
}
- tty_setcol(tty, 0);
+ if (options->reverse == 1) // Move to the bottom and print the prompt
+ tty_printf(tty, "\x1b[%dB", num_lines);
+
+ tty_setcol(tty, options->pad);
tty_printf(tty, "%s%s", options->prompt, state->search);
tty_clearline(tty);
@@ -100,40 +389,73 @@ static void draw(tty_interface_t *state) {
tty_clearline(tty);
}
+ if (options->reverse == 1) // Go back to the top to print the files list
+ tty_printf(tty, "\x1b[M\x1b[%dA", num_lines + 1);
+
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) {
+ int multi_sel = options->multi == 1 && is_selected((char *)choice);
+ tty_printf(tty, "%*s%s%c%s%c%s",
+ options->pad, "", colors[POINTER_COLOR],
+ i == choices->selection ? options->pointer : ' ',
+ colors[MARKER_COLOR],
+ multi_sel == 1 ? options->marker : ' ', NC);
draw_match(state, choice, i == choices->selection);
}
}
- if (num_lines + options->show_info)
+ if (options->reverse == 0 && num_lines + options->show_info)
tty_moveup(tty, num_lines + options->show_info);
- tty_setcol(tty, 0);
- fputs(options->prompt, tty->fout);
+ if (options->reverse == 1)
+ tty_printf(tty, "%c", '\n');
+
+ tty_setcol(tty, options->pad);
+ tty_printf(tty, "%s%s%s", colors[PROMPT_COLOR], options->prompt, NC);
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) {
+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) {
+static void
+update_state(tty_interface_t *state)
+{
if (strcmp(state->last_search, state->search)) {
update_search(state);
+ if (state->options->reverse == 1)
+ tty_printf(state->tty, "\x1b[%dA\n", state->options->num_lines + 1);
draw(state);
}
}
-static void action_emit(tty_interface_t *state) {
+static void
+action_emit(tty_interface_t *state)
+{
update_state(state);
+ if (state->options->reverse == 1)
+ tty_printf(state->tty, "\x1b[%dA\x1b[J", state->options->num_lines);
+
+ if (state->options->multi == 1 && seln > 0) {
+ clear(state);
+ tty_close(state->tty);
+
+ print_selections(state);
+ free_selections(state);
+ state->exit = EXIT_SUCCESS;
+ return;
+ }
+
/* Reset the tty as close as possible to the previous state */
clear(state);
@@ -141,32 +463,37 @@ static void action_emit(tty_interface_t *state) {
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 */
+ if (selection) { /* output the selected result */
+ char *p = (char *)NULL;
+ if (strchr(selection, _ESC))
+ p = decolor_name(selection);
+ printf("%s\n", p ? p : 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) {
+static void
+action_del_char(tty_interface_t *state)
+{
+ if (state->cursor == 0)
return;
- }
+ size_t length = strlen(state->search);
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);
+ memmove(&state->search[state->cursor], &state->search[original_cursor],
+ length - original_cursor + 1);
}
-static void action_del_word(tty_interface_t *state) {
+static void
+action_del_word(tty_interface_t *state)
+{
size_t original_cursor = state->cursor;
size_t cursor = state->cursor;
@@ -176,30 +503,64 @@ static void action_del_word(tty_interface_t *state) {
while (cursor && !isspace(state->search[cursor - 1]))
cursor--;
- memmove(&state->search[cursor], &state->search[original_cursor], strlen(state->search) - original_cursor + 1);
+ 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);
+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) {
+static void
+action_prev(tty_interface_t *state)
+{
+ if (state->options->cycle == 0 && state->choices->selection == 0)
+ return;
update_state(state);
choices_prev(state->choices);
}
-static void action_ignore(tty_interface_t *state) {
+static void
+action_ignore(tty_interface_t *state)
+{
(void)state;
}
-static void action_next(tty_interface_t *state) {
+static void
+action_next(tty_interface_t *state)
+{
+ if (state->options->cycle == 0
+ && state->choices->selection + 1 >= state->choices->available)
+ return;
update_state(state);
choices_next(state->choices);
}
-static void action_left(tty_interface_t *state) {
+static void
+action_exit(tty_interface_t *state)
+{
+ if (state->options->reverse == 1)
+ tty_printf(state->tty, "\x1b[%dA\x1b[J", state->options->num_lines);
+
+ clear(state);
+ tty_close(state->tty);
+
+ state->exit = EXIT_FAILURE;
+}
+
+static void
+action_left(tty_interface_t *state)
+{
+ if (state->options->left_aborts == 1) {
+ action_exit(state);
+ return;
+ }
+
if (state->cursor > 0) {
state->cursor--;
while (!is_boundary(state->search[state->cursor]) && state->cursor)
@@ -207,7 +568,14 @@ static void action_left(tty_interface_t *state) {
}
}
-static void action_right(tty_interface_t *state) {
+static void
+action_right(tty_interface_t *state)
+{
+ if (state->options->right_accepts == 1) {
+ action_emit(state);
+ return;
+ }
+
if (state->cursor < strlen(state->search)) {
state->cursor++;
while (!is_boundary(state->search[state->cursor]))
@@ -215,54 +583,78 @@ static void action_right(tty_interface_t *state) {
}
}
-static void action_beginning(tty_interface_t *state) {
+static void
+action_beginning(tty_interface_t *state)
+{
state->cursor = 0;
}
-static void action_end(tty_interface_t *state) {
+static void
+action_end(tty_interface_t *state)
+{
state->cursor = strlen(state->search);
}
-static void action_pageup(tty_interface_t *state) {
+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++)
+ 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) {
+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++)
+ 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) {
+static void
+action_tab(tty_interface_t *state)
+{
+ if (state->options->multi == 1) {
+ action_select(state);
+ action_next(state);
+ return;
+ }
+
+ if (state->options->tab_accepts == 1) {
+ action_emit(state);
+ return;
+ }
+
+ /* Autocomplete */
update_state(state);
- const char *current_selection = choices_get(state->choices, state->choices->selection);
+ 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);
+ 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) {
+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);
+ 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) {
+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;
@@ -289,13 +681,13 @@ typedef struct {
#define KEY_CTRL(key) ((const char[]){((key) - ('@')), '\0'})
-static const keybinding_t keybindings[] = {{"\x1b", action_exit}, /* ESC */
- {"\x7f", action_del_char}, /* DEL */
-
+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('I'), action_tab}, /* TAB (C-I ) */
{KEY_CTRL('C'), action_exit}, /* C-C */
{KEY_CTRL('D'), action_exit}, /* C-D */
{KEY_CTRL('G'), action_exit}, /* C-G */
@@ -327,7 +719,9 @@ static const keybinding_t keybindings[] = {{"\x1b", action_exit}, /* ESC *
#undef KEY_CTRL
-static void handle_input(tty_interface_t *state, const char *s, int handle_ambiguous_key) {
+static void
+handle_input(tty_interface_t *state, const char *s, int handle_ambiguous_key)
+{
state->ambiguous_key_pending = 0;
char *input = state->input;
@@ -362,16 +756,25 @@ static void handle_input(tty_interface_t *state, const char *s, int handle_ambig
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]);
+ /* No matching keybinding, decolorize and add to search */
+ char *p = input, *q = (char *)NULL;
+ if (strchr(input, _ESC) && (q = decolor_name(input)))
+ p = q;
+
+ for (int i = 0; p[i]; i++) {
+ if (isprint_unicode(p[i]))
+ append_search(state, p[i]);
+ }
/* We have processed the input, so clear it */
strcpy(input, "");
}
-int tty_interface_run(tty_interface_t *state) {
+int
+tty_interface_run(tty_interface_t *state)
+{
+ if (state->options->no_color == 0)
+ set_colors();
draw(state);
for (;;) {
@@ -384,18 +787,25 @@ int tty_interface_run(tty_interface_t *state) {
char s[2] = {tty_getchar(state->tty), '\0'};
handle_input(state, s, 0);
- if (state->exit >= 0)
+ if (state->exit >= 0) {
+ free_selections(state);
return state->exit;
+ }
+ if (state->options->reverse == 1)
+ tty_printf(state->tty, "\x1b[%dA\n", state->options->num_lines + 1);
draw(state);
- } while (tty_input_ready(state->tty, state->ambiguous_key_pending ? KEYTIMEOUT : 0, 0));
+ } 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)
+ if (state->exit >= 0) {
+ free_selections(state);
return state->exit;
+ }
}
update_state(state);