├── .clang-format ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .travis.yml ├── ALGORITHM.md ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── contrib ├── fzy-dvtm └── fzy-tmux ├── deps ├── greatest │ ├── greatest.h │ └── package.json └── theft │ ├── .gitignore │ ├── LICENSE │ ├── theft.c │ ├── theft.h │ ├── theft_bloom.c │ ├── theft_bloom.h │ ├── theft_hash.c │ ├── theft_mt.c │ ├── theft_mt.h │ ├── theft_types.h │ └── theft_types_internal.h ├── fzy.1 ├── src ├── bonus.h ├── choices.c ├── choices.h ├── config.def.h ├── fzy.c ├── match.c ├── match.h ├── options.c ├── options.h ├── tty.c ├── tty.h ├── tty_interface.c └── tty_interface.h └── test ├── acceptance ├── Gemfile ├── Gemfile.lock └── acceptance_test.rb ├── fzytest.c ├── test_choices.c ├── test_match.c └── test_properties.c /.clang-format: -------------------------------------------------------------------------------- 1 | IndentWidth: 8 2 | UseTab: Always 3 | BreakBeforeBraces: Attach 4 | IndentCaseLabels: true 5 | AllowShortFunctionsOnASingleLine: false 6 | ColumnLimit: 100 7 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Test ${{ matrix.compiler }} on ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | compiler: [gcc, clang] 11 | os: [ubuntu-latest, macOS-latest] 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Compile and run tests 16 | run: make && make test 17 | env: 18 | CC: ${{ matrix.compiler }} 19 | test_alpine: 20 | name: Test on Alpine Linux 21 | runs-on: ubuntu-latest 22 | container: docker://alpine 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Install build dependencies 26 | run: apk add build-base 27 | - name: Compile and run tests 28 | run: make && make test 29 | multiarch_test: 30 | name: Test on ${{ matrix.arch }} 31 | strategy: 32 | matrix: 33 | arch: [armv7, aarch64, s390x, ppc64le] 34 | runs-on: ubuntu-20.04 35 | steps: 36 | - uses: actions/checkout@v2 37 | - uses: uraimo/run-on-arch-action@v2.0.5 38 | name: Compile and run tests 39 | id: test 40 | with: 41 | arch: ${{ matrix.arch }} 42 | distro: ubuntu20.04 43 | githubToken: ${{ github.token }} 44 | install: | 45 | apt-get update -q -y 46 | apt-get install -y gcc make 47 | rm -rf /var/lib/apt/lists/* 48 | run: make && make test 49 | acceptance_test: 50 | name: Acceptance Tests 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@v2 54 | - name: Set up Ruby 2.6 55 | uses: actions/setup-ruby@v1 56 | with: 57 | ruby-version: 2.6.x 58 | - name: Install dependencies and compile 59 | run: | 60 | gem install bundler 61 | make 62 | cd test/acceptance && bundle install --jobs 4 --retry 3 63 | - name: Run acceptance tests 64 | run: make acceptance 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | fzy 2 | fzytest 3 | *.o 4 | *.d 5 | config.h 6 | test/acceptance/vendor/bundle 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | language: c 4 | 5 | matrix: 6 | include: 7 | - os: linux 8 | arch: amd64 9 | compiler: clang 10 | - os: linux 11 | arch: ppc64le 12 | compiler: clang 13 | - os: osx 14 | arch: amd64 15 | compiler: clang 16 | - os: linux 17 | arch: amd64 18 | compiler: gcc 19 | - os: linux 20 | arch: ppc64le 21 | compiler: gcc 22 | - os: osx 23 | arch: amd64 24 | compiler: gcc 25 | script: make && make check 26 | jobs: 27 | include: 28 | - stage: Acceptance Test 29 | language: ruby 30 | rvm: 2.5.1 31 | script: make acceptance 32 | addons: 33 | apt: 34 | packages: 35 | - tmux 36 | -------------------------------------------------------------------------------- /ALGORITHM.md: -------------------------------------------------------------------------------- 1 | 2 | This document describes the scoring algorithm of fzy as well as the algorithm 3 | of other similar projects. 4 | 5 | # Matching vs Scoring 6 | 7 | I like to split the problem a fuzzy matchers into two subproblems: matching and scoring. 8 | 9 | Matching determines which results are eligible for the list. 10 | All the projects here consider this to be the same problem, matching the 11 | candidate strings against the search string with any number of gaps. 12 | 13 | Scoring determines the order in which the results are sorted. 14 | Since scoring is tasked with finding what the human user intended, there is no 15 | correct solution. As a result there are large variety in scoring strategies. 16 | 17 | # fzy's matching 18 | 19 | Generally, more time is taken in matching rather than scoring, so it is 20 | important that matching be as fast as possible. If this were case sensitive it 21 | would be a simple loop calling strchr, but since it needs to be case 22 | insensitive. 23 | 24 | # fzy's scoring 25 | 26 | fzy treats scoring as a modified [edit 27 | distance](https://en.wikipedia.org/wiki/Edit_distance) problem of calculating 28 | the 29 | [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance). 30 | Edit distance is the measure of how different two strings are in terms of 31 | insertions, deletions, and substitutions. This is the same problems as [DNA 32 | sequence alignment](https://en.wikipedia.org/wiki/Sequence_alignment). Fuzzy 33 | matching is a simpler problem which only accepts insertions, not deletions or 34 | substitutions. 35 | 36 | fzy's scoring is a dynamic programming algorithm similar to 37 | [Wagner–Fischer](https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm) 38 | and 39 | [Needleman–Wunsch](https://en.wikipedia.org/wiki/Needleman%E2%80%93Wunsch_algorithm). 40 | 41 | Dynamic programming requires the observation that the result is based on the 42 | result of subproblems. 43 | 44 | Fzy borrows heavily from concepts in bioinformatics to performs scoring. 45 | 46 | Fzy builds a `n`-by-`m` matrix, where `n` is the length of the search string 47 | and `m` the length of the candidate string. Each position `(i,j)` in the matrix 48 | stores the score for matching the first `i` characters of the search with the 49 | first `j` characters of the candidate. 50 | 51 | Fzy calculates an affine gap penalty, this means simply that we assign a 52 | constant penalty for having a gap and a linear penalty for the length of the 53 | gap. 54 | Inspired by the [Gotoh algorithm 55 | (pdf)](http://www.cs.unibo.it/~dilena/LabBII/Papers/AffineGaps.pdf), fzy 56 | computes a second `D` (for diagonal) matrix in parallel with the score matrix. 57 | The `D` matrix computes the best score which *ends* in a match. This allows 58 | both computation of the penalty for starting a gap and the score for a 59 | consecutive match. 60 | 61 | Using [this 62 | algorithm](https://github.com/jhawthorn/fzy/blob/master/src/match.c#L105) fzy 63 | is able to score based on the optimal match. 64 | 65 | * Gaps (negative score) 66 | * at the start of the match 67 | * at the end of the match 68 | * within the match 69 | * Matches (positive score) 70 | * consecutive 71 | * following a slash 72 | * following a space, underscore, or dash (the start of a word) 73 | * capital letter (the start of a CamelCase word) 74 | * following a dot (often a file extension) 75 | 76 | 77 | 78 | # Other fuzzy finders 79 | 80 | ## TextMate 81 | 82 | TextMate deserves immense credit for popularizing fuzzy finding from inside 83 | text editors. It's influence can be found in the command-t project, various 84 | other editors use command-t for file finding, and the 't' command in the github 85 | web interface. 86 | 87 | * https://github.com/textmate/textmate/blob/master/Frameworks/text/src/ranker.cc 88 | 89 | ## command-t, ctrlp-cmatcher 90 | 91 | Command-t is a plugin first released in 2010 intending to bring TextMate's 92 | "Go to File" feature to vim. 93 | 94 | Anecdotally, this algorithm works very well. The recursive nature makes it a little hard to 95 | 96 | The wy `last_idx` is suspicious. 97 | 98 | * https://github.com/wincent/command-t/blob/master/ruby/command-t/match.c 99 | * https://github.com/JazzCore/ctrlp-cmatcher/blob/master/autoload/fuzzycomt.c 100 | 101 | ## Length of shortest first match: fzf 102 | https://github.com/junegunn/fzf/blob/master/src/algo/algo.go 103 | 104 | Fzy scores based on the size of the greedy shortest match. fzf finds its match 105 | by the first match appearing in the candidate string. It has some cleverness to 106 | find if there is a shorter match contained in that search, but it isn't 107 | guaranteed to find the shortest match in the string. 108 | 109 | Example results for the search "abc" 110 | 111 | * **AXXBXXC**xxabc 112 | * xxxxxxx**AXBXC** 113 | * xxxxxxxxx**ABC** 114 | 115 | ## Length of first match: ctrlp, pick, selecta (`<= 0.0.6`) 116 | 117 | These score based on the length of the first match in the candidate. This is 118 | probably the simplest useful algorithm. This has the advantage that the heavy 119 | lifting can be performed by the regex engine, which is faster than implementing 120 | anything natively in ruby or Vim script. 121 | 122 | ## Length of shortest match: pick 123 | 124 | Pick has a method, `min_match`, to find the absolute shortest match in a string. 125 | This will find better results than the finders, at the expense of speed, as backtracking is required. 126 | 127 | ## selecta (latest master) 128 | https://github.com/garybernhardt/selecta/commit/d874c99dd7f0f94225a95da06fc487b0fa5b9edc 129 | https://github.com/garybernhardt/selecta/issues/80 130 | 131 | Selecta doesn't compare all possible matches, but only the shortest match from the same start location. 132 | This can lead to inconsistent results. 133 | 134 | Example results for the search "abc" 135 | 136 | * x**AXXXXBC** 137 | * x**ABXC**x 138 | * x**ABXC**xbc 139 | 140 | The third result here should have been scored the same as the first, but the 141 | lower scoring but shorter match is what is measured. 142 | 143 | 144 | ## others 145 | 146 | * https://github.com/joshaven/string_score/blob/master/coffee/string_score.coffee (first match + heuristics) 147 | * https://github.com/atom/fuzzaldrin/blob/master/src/scorer.coffee (modified version of string_score) 148 | * https://github.com/jeancroy/fuzzaldrin-plus/blob/master/src/scorer.coffee (Smith Waterman) 149 | 150 | 151 | # Possible fzy Algorithm Improvements 152 | 153 | ## Case sensitivity 154 | 155 | fzy currently treats all searches as case-insensitive. However, scoring prefers 156 | matches on uppercase letters to help find CamelCase candidates. It might be 157 | desirable to support a case sensitive flag or "smart case" searching. 158 | 159 | ## Faster matching 160 | 161 | Matching is currently performed using the standard lib's `strpbrk`, which has a 162 | very simple implementation (at least in glibc). 163 | 164 | Glibc has an extremely clever `strchr` implementation which searches the haystack 165 | string by [word](https://en.wikipedia.org/wiki/Word_(computer_architecture)), a 166 | 4 or 8 byte `long int`, instead of by byte. It tests if a word is likely to 167 | contain either the search char or the null terminator using bit twiddling. 168 | 169 | A similar method could probably be written to perform to find a character in a 170 | string case-insensitively. 171 | 172 | * https://sourceware.org/git/?p=glibc.git;a=blob;f=string/strchr.c;h=f73891d439dcd8a08954fad4d4615acac4e0eb85;hb=HEAD 173 | 174 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0 (2018-09-23) 2 | 3 | Features: 4 | 5 | - Support UTF-8 6 | - Support readline-like editing 7 | - Quit on Esc 8 | - Redraw on terminal resize 9 | - Bracketed paste escapes are ignored 10 | 11 | Performance: 12 | 13 | - Initialize tty interface before reading stdin 14 | 15 | ## 0.9 (2017-04-17) 16 | 17 | Features: 18 | 19 | - Support Ctrl-k and Ctrl-j for movement 20 | 21 | Performance: 22 | 23 | - Use threads to parallelize sorting 24 | - Improve parallelism of searching and scoring 25 | 26 | Internal: 27 | 28 | - Fix test suite on i386 29 | - Replace test suite with greatest 30 | - Add property tests 31 | - Add acceptance tests 32 | 33 | ## 0.8 (2017-01-01) 34 | 35 | Bugfixes: 36 | 37 | - Fix cursor position shifing upwards when input has less than 2 items. 38 | 39 | ## 0.7 (2016-08-03) 40 | 41 | Bugfixes: 42 | 43 | - Fixed a segfault when encountering non-ascii characters 44 | - Fixed building against musl libc 45 | 46 | ## 0.6 (2016-07-26) 47 | 48 | Performance: 49 | 50 | - Use threads to parallelize searching and scoring 51 | - Read all pending input from tty before searching 52 | - Use a lookup table for computing bonuses 53 | 54 | Bugfixes: 55 | 56 | - Fixed command line parsing on ARM 57 | - Fix error when autocompleting and there are no matches 58 | 59 | ## 0.5 (2016-06-11) 60 | 61 | Bugfixes: 62 | 63 | - Made sorting stable on all platforms 64 | 65 | ## 0.4 (May 19, 2016) 66 | 67 | Features: 68 | 69 | - Add `-q`/`--query` for specifying initial query 70 | 71 | Bugfixes: 72 | 73 | - Fixed last line of results not being cleared on exit 74 | - Check errors when opening the TTY device 75 | 76 | ## 0.3 (April 25, 2016) 77 | 78 | Bugfixes: 79 | 80 | - Runs properly in a terminal with -icrnl 81 | 82 | ## 0.2 (October 19, 2014) 83 | 84 | Features: 85 | 86 | - Allow specifying custom prompt 87 | 88 | Performance: 89 | 90 | - Reduce memory usage on large sets 91 | 92 | Bugfixes: 93 | 94 | - Terminal is properly reset on exit 95 | - Fixed make install on OS X 96 | 97 | ## 0.1 (September 20, 2014) 98 | 99 | Initial release 100 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 John Hawthorn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION=1.0 2 | 3 | CPPFLAGS=-DVERSION=\"${VERSION}\" -D_GNU_SOURCE 4 | CFLAGS+=-MD -Wall -Wextra -g -std=c99 -O3 -pedantic -Ideps -Werror=vla 5 | PREFIX?=/usr/local 6 | MANDIR?=$(PREFIX)/share/man 7 | BINDIR?=$(PREFIX)/bin 8 | DEBUGGER?= 9 | 10 | INSTALL=install 11 | INSTALL_PROGRAM=$(INSTALL) 12 | INSTALL_DATA=${INSTALL} -m 644 13 | 14 | LIBS=-lpthread 15 | OBJECTS=src/fzy.o src/match.o src/tty.o src/choices.o src/options.o src/tty_interface.o 16 | THEFTDEPS = deps/theft/theft.o deps/theft/theft_bloom.o deps/theft/theft_mt.o deps/theft/theft_hash.o 17 | TESTOBJECTS=test/fzytest.c test/test_properties.c test/test_choices.c test/test_match.c src/match.o src/choices.o src/options.o $(THEFTDEPS) 18 | 19 | all: fzy 20 | 21 | test/fzytest: $(TESTOBJECTS) 22 | $(CC) $(CFLAGS) $(CCFLAGS) -Isrc -o $@ $(TESTOBJECTS) $(LIBS) 23 | 24 | acceptance: fzy 25 | cd test/acceptance && bundle --quiet && bundle exec ruby acceptance_test.rb 26 | 27 | test: check 28 | check: test/fzytest 29 | $(DEBUGGER) ./test/fzytest 30 | 31 | fzy: $(OBJECTS) 32 | $(CC) $(CFLAGS) $(CCFLAGS) -o $@ $(OBJECTS) $(LIBS) 33 | 34 | %.o: %.c config.h 35 | $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< 36 | 37 | config.h: src/config.def.h 38 | cp src/config.def.h config.h 39 | 40 | install: fzy 41 | mkdir -p $(DESTDIR)$(BINDIR) 42 | cp fzy $(DESTDIR)$(BINDIR)/ 43 | chmod 755 ${DESTDIR}${BINDIR}/fzy 44 | mkdir -p $(DESTDIR)$(MANDIR)/man1 45 | cp fzy.1 $(DESTDIR)$(MANDIR)/man1/ 46 | chmod 644 ${DESTDIR}${MANDIR}/man1/fzy.1 47 | 48 | fmt: 49 | clang-format -i src/*.c src/*.h 50 | 51 | clean: 52 | rm -f fzy test/fzytest src/*.o src/*.d deps/*/*.o 53 | 54 | veryclean: clean 55 | rm -f config.h 56 | 57 | .PHONY: test check all clean veryclean install fmt acceptance 58 | 59 | -include $(OBJECTS:.o=.d) 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![fzy](http://i.hawth.ca/u/fzy-github.svg) 2 | 3 | **fzy** is a fast, simple fuzzy text selector for the terminal with an advanced scoring algorithm. 4 | 5 | [Try it out online!](http://jhawthorn.github.io/fzy-demo) 6 | 7 | ![](http://i.hawth.ca/u/fzy_animated_demo.svg) 8 | 9 |
10 | It's been kind of life-changing. 11 | -@graygilmore 12 |
13 | 14 |
15 | fzy works great btw 16 | -@alexblackie 17 |
18 | 19 | [![Build Status](https://github.com/jhawthorn/fzy/workflows/CI/badge.svg)](https://github.com/jhawthorn/fzy/actions) 20 | 21 | ## Why use this over fzf, pick, selecta, ctrlp, ...? 22 | 23 | fzy is faster and shows better results than other fuzzy finders. 24 | 25 | Most other fuzzy matchers sort based on the length of a match. fzy tries to 26 | find the result the user intended. It does this by favouring matches on 27 | consecutive letters and starts of words. This allows matching using acronyms or 28 | different parts of the path. 29 | 30 | A gory comparison of the sorting used by fuzzy finders can be found in [ALGORITHM.md](ALGORITHM.md) 31 | 32 | fzy is designed to be used both as an editor plugin and on the command line. 33 | Rather than clearing the screen, fzy displays its interface directly below the current cursor position, scrolling the screen if necessary. 34 | 35 | ## Installation 36 | 37 | **macOS** 38 | 39 | Using Homebrew 40 | 41 | brew install fzy 42 | 43 | Using MacPorts 44 | 45 | sudo port install fzy 46 | 47 | **[Arch Linux](https://www.archlinux.org/packages/?sort=&q=fzy&maintainer=&flagged=)/MSYS2**: `pacman -S fzy` 48 | 49 | **[FreeBSD](https://www.freebsd.org/cgi/ports.cgi?query=fzy&stype=all)**: `pkg install fzy` 50 | 51 | **[Gentoo Linux](https://packages.gentoo.org/packages/app-shells/fzy)**: `emerge -av app-shells/fzy` 52 | 53 | **[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` 54 | 55 | **[pkgsrc](http://pkgsrc.se/misc/fzy) (NetBSD and others)**: `pkgin install fzy` 56 | 57 | **[openSUSE](https://software.opensuse.org/package/fzy)**: `zypper in fzy` 58 | 59 | ### From source 60 | 61 | make 62 | sudo make install 63 | 64 | The `PREFIX` environment variable can be used to specify the install location, 65 | the default is `/usr/local`. 66 | 67 | ## Usage 68 | 69 | fzy is a drop in replacement for [selecta](https://github.com/garybernhardt/selecta), and can be used with its [usage examples](https://github.com/garybernhardt/selecta#usage-examples). 70 | 71 | ### Use with Vim 72 | 73 | fzy can be easily integrated with vim. 74 | 75 | ``` vim 76 | function! FzyCommand(choice_command, vim_command) 77 | try 78 | let output = system(a:choice_command . " | fzy ") 79 | catch /Vim:Interrupt/ 80 | " Swallow errors from ^C, allow redraw! below 81 | endtry 82 | redraw! 83 | if v:shell_error == 0 && !empty(output) 84 | exec a:vim_command . ' ' . output 85 | endif 86 | endfunction 87 | 88 | nnoremap e :call FzyCommand("find . -type f", ":e") 89 | nnoremap v :call FzyCommand("find . -type f", ":vs") 90 | nnoremap s :call FzyCommand("find . -type f", ":sp") 91 | ``` 92 | 93 | Any program can be used to filter files presented through fzy. [ag (the silver searcher)](https://github.com/ggreer/the_silver_searcher) can be used to ignore files specified by `.gitignore`. 94 | 95 | ``` vim 96 | nnoremap e :call FzyCommand("ag . --silent -l -g ''", ":e") 97 | nnoremap v :call FzyCommand("ag . --silent -l -g ''", ":vs") 98 | nnoremap s :call FzyCommand("ag . --silent -l -g ''", ":sp") 99 | ``` 100 | 101 | ## Sorting 102 | 103 | fzy attempts to present the best matches first. The following considerations are weighted when sorting: 104 | 105 | It prefers consecutive characters: `file` will match file over filter. 106 | 107 | It prefers matching the beginning of words: `amp` is likely to match app/models/posts.rb. 108 | 109 | It prefers shorter matches: `abce` matches abcdef over abc de. 110 | 111 | It prefers shorter candidates: `test` matches tests over testing. 112 | 113 | ## See Also 114 | 115 | * [fzy.js](https://github.com/jhawthorn/fzy.js) Javascript port 116 | 117 | 118 | -------------------------------------------------------------------------------- /contrib/fzy-dvtm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | _echo() { 4 | printf %s\\n "$*" 5 | } 6 | 7 | fatal() { 8 | _echo "$*" >&2 9 | exit 1 10 | } 11 | 12 | main() { 13 | if [ -z "${DVTM_CMD_FIFO}" ]; then 14 | fatal "No DVTM_CMD_FIFO variable detected in the environment" 15 | fi 16 | 17 | readonly PATH_DIR_TMP=$(mktemp -d) 18 | readonly PATH_FIFO_IN="${PATH_DIR_TMP}/in" 19 | readonly PATH_FIFO_OUT="${PATH_DIR_TMP}/out" 20 | readonly PATH_FIFO_RET="${PATH_DIR_TMP}/ret" 21 | 22 | if [ -z "${PATH_DIR_TMP}" ]; then 23 | fatal "Unable to create a temporary directory" 24 | fi 25 | 26 | args="" 27 | for i in "$@"; do 28 | if [ -z "${args}" ]; then 29 | args="\\'${i}\\'" 30 | else 31 | args="${args} \\'${i}\\'" 32 | fi 33 | done 34 | 35 | mkfifo "${PATH_FIFO_IN}" "${PATH_FIFO_OUT}" 36 | 37 | _echo \ 38 | "create 'fzy ${args} < \\'${PATH_FIFO_IN}\\' > \\'${PATH_FIFO_OUT}\\' 2>&1; echo $? > \\'${PATH_FIFO_RET}\\''" \ 39 | > "${DVTM_CMD_FIFO}" 40 | cat <&0 > "${PATH_FIFO_IN}" & 41 | cat < "${PATH_FIFO_OUT}" 42 | 43 | readonly CODE_RET=$(head -n 1 "${PATH_FIFO_RET}") 44 | 45 | rm -rf "${PATH_DIR_TMP}" 46 | 47 | exit "${CODE_RET}" 48 | } 49 | 50 | main "$@" 51 | -------------------------------------------------------------------------------- /contrib/fzy-tmux: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | _echo() { 4 | printf %s\\n "$*" 5 | } 6 | 7 | fatal() { 8 | _echo "$*" >&2 9 | exit 1 10 | } 11 | 12 | main() { 13 | if [ -z "${TMUX}" ]; then 14 | fatal "No TMUX variable detected in the environment" 15 | fi 16 | 17 | readonly PATH_DIR_TMP=$(mktemp -d) 18 | readonly PATH_FIFO_IN="${PATH_DIR_TMP}/in" 19 | readonly PATH_FIFO_OUT="${PATH_DIR_TMP}/out" 20 | readonly PATH_FIFO_RET="${PATH_DIR_TMP}/ret" 21 | 22 | if [ -z "${PATH_DIR_TMP}" ]; then 23 | fatal "Unable to create a temporary directory" 24 | fi 25 | 26 | mkfifo "${PATH_FIFO_IN}" "${PATH_FIFO_OUT}" "${PATH_FIFO_RET}" 27 | 28 | export TMUX=$(_echo "${TMUX}" | cut -d , -f 1,2) 29 | eval "tmux \ 30 | set-window-option synchronize-panes off \\; \ 31 | set-window-option remain-on-exit off \\; \ 32 | split-window \"fzy $* < '${PATH_FIFO_IN}' > '${PATH_FIFO_OUT}' 2>&1; echo $? > '${PATH_FIFO_RET}'\"" 33 | 34 | cat <&0 > "${PATH_FIFO_IN}" & 35 | cat < "${PATH_FIFO_OUT}" 36 | 37 | readonly CODE_RET=$(head -n 1 "${PATH_FIFO_RET}") 38 | 39 | rm -rf "${PATH_DIR_TMP}" 40 | 41 | exit "${CODE_RET}" 42 | } 43 | 44 | main "$@" 45 | -------------------------------------------------------------------------------- /deps/greatest/greatest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2017 Scott Vokes 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef GREATEST_H 18 | #define GREATEST_H 19 | 20 | #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) 21 | extern "C" { 22 | #endif 23 | 24 | /* 1.3.1 */ 25 | #define GREATEST_VERSION_MAJOR 1 26 | #define GREATEST_VERSION_MINOR 3 27 | #define GREATEST_VERSION_PATCH 1 28 | 29 | /* A unit testing system for C, contained in 1 file. 30 | * It doesn't use dynamic allocation or depend on anything 31 | * beyond ANSI C89. 32 | * 33 | * An up-to-date version can be found at: 34 | * https://github.com/silentbicycle/greatest/ 35 | */ 36 | 37 | 38 | /********************************************************************* 39 | * Minimal test runner template 40 | *********************************************************************/ 41 | #if 0 42 | 43 | #include "greatest.h" 44 | 45 | TEST foo_should_foo(void) { 46 | PASS(); 47 | } 48 | 49 | static void setup_cb(void *data) { 50 | printf("setup callback for each test case\n"); 51 | } 52 | 53 | static void teardown_cb(void *data) { 54 | printf("teardown callback for each test case\n"); 55 | } 56 | 57 | SUITE(suite) { 58 | /* Optional setup/teardown callbacks which will be run before/after 59 | * every test case. If using a test suite, they will be cleared when 60 | * the suite finishes. */ 61 | SET_SETUP(setup_cb, voidp_to_callback_data); 62 | SET_TEARDOWN(teardown_cb, voidp_to_callback_data); 63 | 64 | RUN_TEST(foo_should_foo); 65 | } 66 | 67 | /* Add definitions that need to be in the test runner's main file. */ 68 | GREATEST_MAIN_DEFS(); 69 | 70 | /* Set up, run suite(s) of tests, report pass/fail/skip stats. */ 71 | int run_tests(void) { 72 | GREATEST_INIT(); /* init. greatest internals */ 73 | /* List of suites to run (if any). */ 74 | RUN_SUITE(suite); 75 | 76 | /* Tests can also be run directly, without using test suites. */ 77 | RUN_TEST(foo_should_foo); 78 | 79 | GREATEST_PRINT_REPORT(); /* display results */ 80 | return greatest_all_passed(); 81 | } 82 | 83 | /* main(), for a standalone command-line test runner. 84 | * This replaces run_tests above, and adds command line option 85 | * handling and exiting with a pass/fail status. */ 86 | int main(int argc, char **argv) { 87 | GREATEST_MAIN_BEGIN(); /* init & parse command-line args */ 88 | RUN_SUITE(suite); 89 | GREATEST_MAIN_END(); /* display results */ 90 | } 91 | 92 | #endif 93 | /*********************************************************************/ 94 | 95 | 96 | #include 97 | #include 98 | #include 99 | #include 100 | 101 | /*********** 102 | * Options * 103 | ***********/ 104 | 105 | /* Default column width for non-verbose output. */ 106 | #ifndef GREATEST_DEFAULT_WIDTH 107 | #define GREATEST_DEFAULT_WIDTH 72 108 | #endif 109 | 110 | /* FILE *, for test logging. */ 111 | #ifndef GREATEST_STDOUT 112 | #define GREATEST_STDOUT stdout 113 | #endif 114 | 115 | /* Remove GREATEST_ prefix from most commonly used symbols? */ 116 | #ifndef GREATEST_USE_ABBREVS 117 | #define GREATEST_USE_ABBREVS 1 118 | #endif 119 | 120 | /* Set to 0 to disable all use of setjmp/longjmp. */ 121 | #ifndef GREATEST_USE_LONGJMP 122 | #define GREATEST_USE_LONGJMP 1 123 | #endif 124 | 125 | /* Make it possible to replace fprintf with another 126 | * function with the same interface. */ 127 | #ifndef GREATEST_FPRINTF 128 | #define GREATEST_FPRINTF fprintf 129 | #endif 130 | 131 | #if GREATEST_USE_LONGJMP 132 | #include 133 | #endif 134 | 135 | /* Set to 0 to disable all use of time.h / clock(). */ 136 | #ifndef GREATEST_USE_TIME 137 | #define GREATEST_USE_TIME 1 138 | #endif 139 | 140 | #if GREATEST_USE_TIME 141 | #include 142 | #endif 143 | 144 | /* Floating point type, for ASSERT_IN_RANGE. */ 145 | #ifndef GREATEST_FLOAT 146 | #define GREATEST_FLOAT double 147 | #define GREATEST_FLOAT_FMT "%g" 148 | #endif 149 | 150 | 151 | /********* 152 | * Types * 153 | *********/ 154 | 155 | /* Info for the current running suite. */ 156 | typedef struct greatest_suite_info { 157 | unsigned int tests_run; 158 | unsigned int passed; 159 | unsigned int failed; 160 | unsigned int skipped; 161 | 162 | #if GREATEST_USE_TIME 163 | /* timers, pre/post running suite and individual tests */ 164 | clock_t pre_suite; 165 | clock_t post_suite; 166 | clock_t pre_test; 167 | clock_t post_test; 168 | #endif 169 | } greatest_suite_info; 170 | 171 | /* Type for a suite function. */ 172 | typedef void greatest_suite_cb(void); 173 | 174 | /* Types for setup/teardown callbacks. If non-NULL, these will be run 175 | * and passed the pointer to their additional data. */ 176 | typedef void greatest_setup_cb(void *udata); 177 | typedef void greatest_teardown_cb(void *udata); 178 | 179 | /* Type for an equality comparison between two pointers of the same type. 180 | * Should return non-0 if equal, otherwise 0. 181 | * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ 182 | typedef int greatest_equal_cb(const void *exp, const void *got, void *udata); 183 | 184 | /* Type for a callback that prints a value pointed to by T. 185 | * Return value has the same meaning as printf's. 186 | * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ 187 | typedef int greatest_printf_cb(const void *t, void *udata); 188 | 189 | /* Callbacks for an arbitrary type; needed for type-specific 190 | * comparisons via GREATEST_ASSERT_EQUAL_T[m].*/ 191 | typedef struct greatest_type_info { 192 | greatest_equal_cb *equal; 193 | greatest_printf_cb *print; 194 | } greatest_type_info; 195 | 196 | typedef struct greatest_memory_cmp_env { 197 | const unsigned char *exp; 198 | const unsigned char *got; 199 | size_t size; 200 | } greatest_memory_cmp_env; 201 | 202 | /* Callbacks for string and raw memory types. */ 203 | extern greatest_type_info greatest_type_info_string; 204 | extern greatest_type_info greatest_type_info_memory; 205 | 206 | typedef enum { 207 | GREATEST_FLAG_FIRST_FAIL = 0x01, 208 | GREATEST_FLAG_LIST_ONLY = 0x02 209 | } greatest_flag_t; 210 | 211 | /* Internal state for a PRNG, used to shuffle test order. */ 212 | struct greatest_prng { 213 | unsigned char random_order; /* use random ordering? */ 214 | unsigned char initialized; /* is random ordering initialized? */ 215 | unsigned char pad_0[2]; 216 | unsigned long state; /* PRNG state */ 217 | unsigned long count; /* how many tests, this pass */ 218 | unsigned long count_ceil; /* total number of tests */ 219 | unsigned long count_run; /* total tests run */ 220 | unsigned long mod; /* power-of-2 ceiling of count_ceil */ 221 | unsigned long a; /* LCG multiplier */ 222 | unsigned long c; /* LCG increment */ 223 | }; 224 | 225 | /* Struct containing all test runner state. */ 226 | typedef struct greatest_run_info { 227 | unsigned char flags; 228 | unsigned char verbosity; 229 | unsigned char pad_0[2]; 230 | 231 | unsigned int tests_run; /* total test count */ 232 | 233 | /* currently running test suite */ 234 | greatest_suite_info suite; 235 | 236 | /* overall pass/fail/skip counts */ 237 | unsigned int passed; 238 | unsigned int failed; 239 | unsigned int skipped; 240 | unsigned int assertions; 241 | 242 | /* info to print about the most recent failure */ 243 | unsigned int fail_line; 244 | unsigned int pad_1; 245 | const char *fail_file; 246 | const char *msg; 247 | 248 | /* current setup/teardown hooks and userdata */ 249 | greatest_setup_cb *setup; 250 | void *setup_udata; 251 | greatest_teardown_cb *teardown; 252 | void *teardown_udata; 253 | 254 | /* formatting info for ".....s...F"-style output */ 255 | unsigned int col; 256 | unsigned int width; 257 | 258 | /* only run a specific suite or test */ 259 | const char *suite_filter; 260 | const char *test_filter; 261 | const char *test_exclude; 262 | 263 | struct greatest_prng prng[2]; /* 0: suites, 1: tests */ 264 | 265 | #if GREATEST_USE_TIME 266 | /* overall timers */ 267 | clock_t begin; 268 | clock_t end; 269 | #endif 270 | 271 | #if GREATEST_USE_LONGJMP 272 | int pad_jmp_buf; 273 | jmp_buf jump_dest; 274 | #endif 275 | } greatest_run_info; 276 | 277 | struct greatest_report_t { 278 | /* overall pass/fail/skip counts */ 279 | unsigned int passed; 280 | unsigned int failed; 281 | unsigned int skipped; 282 | unsigned int assertions; 283 | }; 284 | 285 | /* Global var for the current testing context. 286 | * Initialized by GREATEST_MAIN_DEFS(). */ 287 | extern greatest_run_info greatest_info; 288 | 289 | /* Type for ASSERT_ENUM_EQ's ENUM_STR argument. */ 290 | typedef const char *greatest_enum_str_fun(int value); 291 | 292 | /********************** 293 | * Exported functions * 294 | **********************/ 295 | 296 | /* These are used internally by greatest. */ 297 | void greatest_do_pass(const char *name); 298 | void greatest_do_fail(const char *name); 299 | void greatest_do_skip(const char *name); 300 | int greatest_suite_pre(const char *suite_name); 301 | void greatest_suite_post(void); 302 | int greatest_test_pre(const char *name); 303 | void greatest_test_post(const char *name, int res); 304 | void greatest_usage(const char *name); 305 | int greatest_do_assert_equal_t(const void *exp, const void *got, 306 | greatest_type_info *type_info, void *udata); 307 | void greatest_prng_init_first_pass(int id); 308 | int greatest_prng_init_second_pass(int id, unsigned long seed); 309 | void greatest_prng_step(int id); 310 | 311 | /* These are part of the public greatest API. */ 312 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); 313 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); 314 | int greatest_all_passed(void); 315 | void greatest_set_suite_filter(const char *filter); 316 | void greatest_set_test_filter(const char *filter); 317 | void greatest_set_test_exclude(const char *filter); 318 | void greatest_stop_at_first_fail(void); 319 | void greatest_get_report(struct greatest_report_t *report); 320 | unsigned int greatest_get_verbosity(void); 321 | void greatest_set_verbosity(unsigned int verbosity); 322 | void greatest_set_flag(greatest_flag_t flag); 323 | 324 | 325 | /******************** 326 | * Language Support * 327 | ********************/ 328 | 329 | /* If __VA_ARGS__ (C99) is supported, allow parametric testing 330 | * without needing to manually manage the argument struct. */ 331 | #if __STDC_VERSION__ >= 19901L || _MSC_VER >= 1800 332 | #define GREATEST_VA_ARGS 333 | #endif 334 | 335 | 336 | /********** 337 | * Macros * 338 | **********/ 339 | 340 | /* Define a suite. */ 341 | #define GREATEST_SUITE(NAME) void NAME(void); void NAME(void) 342 | 343 | /* Declare a suite, provided by another compilation unit. */ 344 | #define GREATEST_SUITE_EXTERN(NAME) void NAME(void) 345 | 346 | /* Start defining a test function. 347 | * The arguments are not included, to allow parametric testing. */ 348 | #define GREATEST_TEST static enum greatest_test_res 349 | 350 | /* PASS/FAIL/SKIP result from a test. Used internally. */ 351 | typedef enum greatest_test_res { 352 | GREATEST_TEST_RES_PASS = 0, 353 | GREATEST_TEST_RES_FAIL = -1, 354 | GREATEST_TEST_RES_SKIP = 1 355 | } greatest_test_res; 356 | 357 | /* Run a suite. */ 358 | #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME) 359 | 360 | /* Run a test in the current suite. */ 361 | #define GREATEST_RUN_TEST(TEST) \ 362 | do { \ 363 | if (greatest_test_pre(#TEST) == 1) { \ 364 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ 365 | if (res == GREATEST_TEST_RES_PASS) { \ 366 | res = TEST(); \ 367 | } \ 368 | greatest_test_post(#TEST, res); \ 369 | } \ 370 | } while (0) 371 | 372 | /* Ignore a test, don't warn about it being unused. */ 373 | #define GREATEST_IGNORE_TEST(TEST) (void)TEST 374 | 375 | /* Run a test in the current suite with one void * argument, 376 | * which can be a pointer to a struct with multiple arguments. */ 377 | #define GREATEST_RUN_TEST1(TEST, ENV) \ 378 | do { \ 379 | if (greatest_test_pre(#TEST) == 1) { \ 380 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ 381 | if (res == GREATEST_TEST_RES_PASS) { \ 382 | res = TEST(ENV); \ 383 | } \ 384 | greatest_test_post(#TEST, res); \ 385 | } \ 386 | } while (0) 387 | 388 | #ifdef GREATEST_VA_ARGS 389 | #define GREATEST_RUN_TESTp(TEST, ...) \ 390 | do { \ 391 | if (greatest_test_pre(#TEST) == 1) { \ 392 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ 393 | if (res == GREATEST_TEST_RES_PASS) { \ 394 | res = TEST(__VA_ARGS__); \ 395 | } \ 396 | greatest_test_post(#TEST, res); \ 397 | } \ 398 | } while (0) 399 | #endif 400 | 401 | 402 | /* Check if the test runner is in verbose mode. */ 403 | #define GREATEST_IS_VERBOSE() ((greatest_info.verbosity) > 0) 404 | #define GREATEST_LIST_ONLY() \ 405 | (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) 406 | #define GREATEST_FIRST_FAIL() \ 407 | (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) 408 | #define GREATEST_FAILURE_ABORT() \ 409 | (GREATEST_FIRST_FAIL() && \ 410 | (greatest_info.suite.failed > 0 || greatest_info.failed > 0)) 411 | 412 | /* Message-less forms of tests defined below. */ 413 | #define GREATEST_PASS() GREATEST_PASSm(NULL) 414 | #define GREATEST_FAIL() GREATEST_FAILm(NULL) 415 | #define GREATEST_SKIP() GREATEST_SKIPm(NULL) 416 | #define GREATEST_ASSERT(COND) \ 417 | GREATEST_ASSERTm(#COND, COND) 418 | #define GREATEST_ASSERT_OR_LONGJMP(COND) \ 419 | GREATEST_ASSERT_OR_LONGJMPm(#COND, COND) 420 | #define GREATEST_ASSERT_FALSE(COND) \ 421 | GREATEST_ASSERT_FALSEm(#COND, COND) 422 | #define GREATEST_ASSERT_EQ(EXP, GOT) \ 423 | GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) 424 | #define GREATEST_ASSERT_EQ_FMT(EXP, GOT, FMT) \ 425 | GREATEST_ASSERT_EQ_FMTm(#EXP " != " #GOT, EXP, GOT, FMT) 426 | #define GREATEST_ASSERT_IN_RANGE(EXP, GOT, TOL) \ 427 | GREATEST_ASSERT_IN_RANGEm(#EXP " != " #GOT " +/- " #TOL, EXP, GOT, TOL) 428 | #define GREATEST_ASSERT_EQUAL_T(EXP, GOT, TYPE_INFO, UDATA) \ 429 | GREATEST_ASSERT_EQUAL_Tm(#EXP " != " #GOT, EXP, GOT, TYPE_INFO, UDATA) 430 | #define GREATEST_ASSERT_STR_EQ(EXP, GOT) \ 431 | GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT) 432 | #define GREATEST_ASSERT_STRN_EQ(EXP, GOT, SIZE) \ 433 | GREATEST_ASSERT_STRN_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) 434 | #define GREATEST_ASSERT_MEM_EQ(EXP, GOT, SIZE) \ 435 | GREATEST_ASSERT_MEM_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) 436 | #define GREATEST_ASSERT_ENUM_EQ(EXP, GOT, ENUM_STR) \ 437 | GREATEST_ASSERT_ENUM_EQm(#EXP " != " #GOT, EXP, GOT, ENUM_STR) 438 | 439 | /* The following forms take an additional message argument first, 440 | * to be displayed by the test runner. */ 441 | 442 | /* Fail if a condition is not true, with message. */ 443 | #define GREATEST_ASSERTm(MSG, COND) \ 444 | do { \ 445 | greatest_info.assertions++; \ 446 | if (!(COND)) { GREATEST_FAILm(MSG); } \ 447 | } while (0) 448 | 449 | /* Fail if a condition is not true, longjmping out of test. */ 450 | #define GREATEST_ASSERT_OR_LONGJMPm(MSG, COND) \ 451 | do { \ 452 | greatest_info.assertions++; \ 453 | if (!(COND)) { GREATEST_FAIL_WITH_LONGJMPm(MSG); } \ 454 | } while (0) 455 | 456 | /* Fail if a condition is not false, with message. */ 457 | #define GREATEST_ASSERT_FALSEm(MSG, COND) \ 458 | do { \ 459 | greatest_info.assertions++; \ 460 | if ((COND)) { GREATEST_FAILm(MSG); } \ 461 | } while (0) 462 | 463 | /* Fail if EXP != GOT (equality comparison by ==). */ 464 | #define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \ 465 | do { \ 466 | greatest_info.assertions++; \ 467 | if ((EXP) != (GOT)) { GREATEST_FAILm(MSG); } \ 468 | } while (0) 469 | 470 | /* Fail if EXP != GOT (equality comparison by ==). 471 | * Warning: FMT, EXP, and GOT will be evaluated more 472 | * than once on failure. */ 473 | #define GREATEST_ASSERT_EQ_FMTm(MSG, EXP, GOT, FMT) \ 474 | do { \ 475 | greatest_info.assertions++; \ 476 | if ((EXP) != (GOT)) { \ 477 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ 478 | GREATEST_FPRINTF(GREATEST_STDOUT, FMT, EXP); \ 479 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ 480 | GREATEST_FPRINTF(GREATEST_STDOUT, FMT, GOT); \ 481 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 482 | GREATEST_FAILm(MSG); \ 483 | } \ 484 | } while (0) 485 | 486 | /* Fail if EXP is not equal to GOT, printing enum IDs. */ 487 | #define GREATEST_ASSERT_ENUM_EQm(MSG, EXP, GOT, ENUM_STR) \ 488 | do { \ 489 | int greatest_EXP = (int)(EXP); \ 490 | int greatest_GOT = (int)(GOT); \ 491 | greatest_enum_str_fun *greatest_ENUM_STR = ENUM_STR; \ 492 | if (greatest_EXP != greatest_GOT) { \ 493 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: %s", \ 494 | greatest_ENUM_STR(greatest_EXP)); \ 495 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: %s\n", \ 496 | greatest_ENUM_STR(greatest_GOT)); \ 497 | GREATEST_FAILm(MSG); \ 498 | } \ 499 | } while (0) \ 500 | 501 | /* Fail if GOT not in range of EXP +|- TOL. */ 502 | #define GREATEST_ASSERT_IN_RANGEm(MSG, EXP, GOT, TOL) \ 503 | do { \ 504 | GREATEST_FLOAT greatest_EXP = (EXP); \ 505 | GREATEST_FLOAT greatest_GOT = (GOT); \ 506 | GREATEST_FLOAT greatest_TOL = (TOL); \ 507 | greatest_info.assertions++; \ 508 | if ((greatest_EXP > greatest_GOT && \ 509 | greatest_EXP - greatest_GOT > greatest_TOL) || \ 510 | (greatest_EXP < greatest_GOT && \ 511 | greatest_GOT - greatest_EXP > greatest_TOL)) { \ 512 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 513 | "\nExpected: " GREATEST_FLOAT_FMT \ 514 | " +/- " GREATEST_FLOAT_FMT \ 515 | "\n Got: " GREATEST_FLOAT_FMT \ 516 | "\n", \ 517 | greatest_EXP, greatest_TOL, greatest_GOT); \ 518 | GREATEST_FAILm(MSG); \ 519 | } \ 520 | } while (0) 521 | 522 | /* Fail if EXP is not equal to GOT, according to strcmp. */ 523 | #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \ 524 | do { \ 525 | GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ 526 | &greatest_type_info_string, NULL); \ 527 | } while (0) \ 528 | 529 | /* Fail if EXP is not equal to GOT, according to strcmp. */ 530 | #define GREATEST_ASSERT_STRN_EQm(MSG, EXP, GOT, SIZE) \ 531 | do { \ 532 | size_t size = SIZE; \ 533 | GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ 534 | &greatest_type_info_string, &size); \ 535 | } while (0) \ 536 | 537 | /* Fail if EXP is not equal to GOT, according to memcmp. */ 538 | #define GREATEST_ASSERT_MEM_EQm(MSG, EXP, GOT, SIZE) \ 539 | do { \ 540 | greatest_memory_cmp_env env; \ 541 | env.exp = (const unsigned char *)EXP; \ 542 | env.got = (const unsigned char *)GOT; \ 543 | env.size = SIZE; \ 544 | GREATEST_ASSERT_EQUAL_Tm(MSG, env.exp, env.got, \ 545 | &greatest_type_info_memory, &env); \ 546 | } while (0) \ 547 | 548 | /* Fail if EXP is not equal to GOT, according to a comparison 549 | * callback in TYPE_INFO. If they are not equal, optionally use a 550 | * print callback in TYPE_INFO to print them. */ 551 | #define GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, TYPE_INFO, UDATA) \ 552 | do { \ 553 | greatest_type_info *type_info = (TYPE_INFO); \ 554 | greatest_info.assertions++; \ 555 | if (!greatest_do_assert_equal_t(EXP, GOT, \ 556 | type_info, UDATA)) { \ 557 | if (type_info == NULL || type_info->equal == NULL) { \ 558 | GREATEST_FAILm("type_info->equal callback missing!"); \ 559 | } else { \ 560 | GREATEST_FAILm(MSG); \ 561 | } \ 562 | } \ 563 | } while (0) \ 564 | 565 | /* Pass. */ 566 | #define GREATEST_PASSm(MSG) \ 567 | do { \ 568 | greatest_info.msg = MSG; \ 569 | return GREATEST_TEST_RES_PASS; \ 570 | } while (0) 571 | 572 | /* Fail. */ 573 | #define GREATEST_FAILm(MSG) \ 574 | do { \ 575 | greatest_info.fail_file = __FILE__; \ 576 | greatest_info.fail_line = __LINE__; \ 577 | greatest_info.msg = MSG; \ 578 | return GREATEST_TEST_RES_FAIL; \ 579 | } while (0) 580 | 581 | /* Optional GREATEST_FAILm variant that longjmps. */ 582 | #if GREATEST_USE_LONGJMP 583 | #define GREATEST_FAIL_WITH_LONGJMP() GREATEST_FAIL_WITH_LONGJMPm(NULL) 584 | #define GREATEST_FAIL_WITH_LONGJMPm(MSG) \ 585 | do { \ 586 | greatest_info.fail_file = __FILE__; \ 587 | greatest_info.fail_line = __LINE__; \ 588 | greatest_info.msg = MSG; \ 589 | longjmp(greatest_info.jump_dest, GREATEST_TEST_RES_FAIL); \ 590 | } while (0) 591 | #endif 592 | 593 | /* Skip the current test. */ 594 | #define GREATEST_SKIPm(MSG) \ 595 | do { \ 596 | greatest_info.msg = MSG; \ 597 | return GREATEST_TEST_RES_SKIP; \ 598 | } while (0) 599 | 600 | /* Check the result of a subfunction using ASSERT, etc. */ 601 | #define GREATEST_CHECK_CALL(RES) \ 602 | do { \ 603 | enum greatest_test_res greatest_RES = RES; \ 604 | if (greatest_RES != GREATEST_TEST_RES_PASS) { \ 605 | return greatest_RES; \ 606 | } \ 607 | } while (0) \ 608 | 609 | #if GREATEST_USE_TIME 610 | #define GREATEST_SET_TIME(NAME) \ 611 | NAME = clock(); \ 612 | if (NAME == (clock_t) -1) { \ 613 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 614 | "clock error: %s\n", #NAME); \ 615 | exit(EXIT_FAILURE); \ 616 | } 617 | 618 | #define GREATEST_CLOCK_DIFF(C1, C2) \ 619 | GREATEST_FPRINTF(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ 620 | (long unsigned int) (C2) - (long unsigned int)(C1), \ 621 | (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) 622 | #else 623 | #define GREATEST_SET_TIME(UNUSED) 624 | #define GREATEST_CLOCK_DIFF(UNUSED1, UNUSED2) 625 | #endif 626 | 627 | #if GREATEST_USE_LONGJMP 628 | #define GREATEST_SAVE_CONTEXT() \ 629 | /* setjmp returns 0 (GREATEST_TEST_RES_PASS) on first call * \ 630 | * so the test runs, then RES_FAIL from FAIL_WITH_LONGJMP. */ \ 631 | ((enum greatest_test_res)(setjmp(greatest_info.jump_dest))) 632 | #else 633 | #define GREATEST_SAVE_CONTEXT() \ 634 | /*a no-op, since setjmp/longjmp aren't being used */ \ 635 | GREATEST_TEST_RES_PASS 636 | #endif 637 | 638 | /* Run every suite / test function run within BODY in pseudo-random 639 | * order, seeded by SEED. (The top 3 bits of the seed are ignored.) 640 | * 641 | * This should be called like: 642 | * GREATEST_SHUFFLE_TESTS(seed, { 643 | * GREATEST_RUN_TEST(some_test); 644 | * GREATEST_RUN_TEST(some_other_test); 645 | * GREATEST_RUN_TEST(yet_another_test); 646 | * }); 647 | * 648 | * Note that the body of the second argument will be evaluated 649 | * multiple times. */ 650 | #define GREATEST_SHUFFLE_SUITES(SD, BODY) GREATEST_SHUFFLE(0, SD, BODY) 651 | #define GREATEST_SHUFFLE_TESTS(SD, BODY) GREATEST_SHUFFLE(1, SD, BODY) 652 | #define GREATEST_SHUFFLE(ID, SD, BODY) \ 653 | do { \ 654 | struct greatest_prng *prng = &greatest_info.prng[ID]; \ 655 | greatest_prng_init_first_pass(ID); \ 656 | do { \ 657 | prng->count = 0; \ 658 | if (prng->initialized) { greatest_prng_step(ID); } \ 659 | BODY; \ 660 | if (!prng->initialized) { \ 661 | if (!greatest_prng_init_second_pass(ID, SD)) { break; } \ 662 | } else if (prng->count_run == prng->count_ceil) { \ 663 | break; \ 664 | } \ 665 | } while (!GREATEST_FAILURE_ABORT()); \ 666 | prng->count_run = prng->random_order = prng->initialized = 0; \ 667 | } while(0) 668 | 669 | /* Include several function definitions in the main test file. */ 670 | #define GREATEST_MAIN_DEFS() \ 671 | \ 672 | /* Is FILTER a subset of NAME? */ \ 673 | static int greatest_name_match(const char *name, const char *filter, \ 674 | int res_if_none) { \ 675 | size_t offset = 0; \ 676 | size_t filter_len = filter ? strlen(filter) : 0; \ 677 | if (filter_len == 0) { return res_if_none; } /* no filter */ \ 678 | while (name[offset] != '\0') { \ 679 | if (name[offset] == filter[0]) { \ 680 | if (0 == strncmp(&name[offset], filter, filter_len)) { \ 681 | return 1; \ 682 | } \ 683 | } \ 684 | offset++; \ 685 | } \ 686 | \ 687 | return 0; \ 688 | } \ 689 | \ 690 | /* Before running a test, check the name filtering and \ 691 | * test shuffling state, if applicable, and then call setup hooks. */ \ 692 | int greatest_test_pre(const char *name) { \ 693 | struct greatest_run_info *g = &greatest_info; \ 694 | int match = greatest_name_match(name, g->test_filter, 1) && \ 695 | !greatest_name_match(name, g->test_exclude, 0); \ 696 | if (GREATEST_LIST_ONLY()) { /* just listing test names */ \ 697 | if (match) { fprintf(GREATEST_STDOUT, " %s\n", name); } \ 698 | return 0; \ 699 | } \ 700 | if (match && (!GREATEST_FIRST_FAIL() || g->suite.failed == 0)) { \ 701 | struct greatest_prng *p = &g->prng[1]; \ 702 | if (p->random_order) { \ 703 | p->count++; \ 704 | if (!p->initialized || ((p->count - 1) != p->state)) { \ 705 | return 0; /* don't run this test yet */ \ 706 | } \ 707 | } \ 708 | GREATEST_SET_TIME(g->suite.pre_test); \ 709 | if (g->setup) { g->setup(g->setup_udata); } \ 710 | p->count_run++; \ 711 | return 1; /* test should be run */ \ 712 | } else { \ 713 | return 0; /* skipped */ \ 714 | } \ 715 | } \ 716 | \ 717 | void greatest_test_post(const char *name, int res) { \ 718 | GREATEST_SET_TIME(greatest_info.suite.post_test); \ 719 | if (greatest_info.teardown) { \ 720 | void *udata = greatest_info.teardown_udata; \ 721 | greatest_info.teardown(udata); \ 722 | } \ 723 | \ 724 | if (res <= GREATEST_TEST_RES_FAIL) { \ 725 | greatest_do_fail(name); \ 726 | } else if (res >= GREATEST_TEST_RES_SKIP) { \ 727 | greatest_do_skip(name); \ 728 | } else if (res == GREATEST_TEST_RES_PASS) { \ 729 | greatest_do_pass(name); \ 730 | } \ 731 | greatest_info.suite.tests_run++; \ 732 | greatest_info.col++; \ 733 | if (GREATEST_IS_VERBOSE()) { \ 734 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ 735 | greatest_info.suite.post_test); \ 736 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 737 | } else if (greatest_info.col % greatest_info.width == 0) { \ 738 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 739 | greatest_info.col = 0; \ 740 | } \ 741 | fflush(GREATEST_STDOUT); \ 742 | } \ 743 | \ 744 | static void report_suite(void) { \ 745 | if (greatest_info.suite.tests_run > 0) { \ 746 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 747 | "\n%u test%s - %u passed, %u failed, %u skipped", \ 748 | greatest_info.suite.tests_run, \ 749 | greatest_info.suite.tests_run == 1 ? "" : "s", \ 750 | greatest_info.suite.passed, \ 751 | greatest_info.suite.failed, \ 752 | greatest_info.suite.skipped); \ 753 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ 754 | greatest_info.suite.post_suite); \ 755 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 756 | } \ 757 | } \ 758 | \ 759 | static void update_counts_and_reset_suite(void) { \ 760 | greatest_info.setup = NULL; \ 761 | greatest_info.setup_udata = NULL; \ 762 | greatest_info.teardown = NULL; \ 763 | greatest_info.teardown_udata = NULL; \ 764 | greatest_info.passed += greatest_info.suite.passed; \ 765 | greatest_info.failed += greatest_info.suite.failed; \ 766 | greatest_info.skipped += greatest_info.suite.skipped; \ 767 | greatest_info.tests_run += greatest_info.suite.tests_run; \ 768 | memset(&greatest_info.suite, 0, sizeof(greatest_info.suite)); \ 769 | greatest_info.col = 0; \ 770 | } \ 771 | \ 772 | int greatest_suite_pre(const char *suite_name) { \ 773 | struct greatest_prng *p = &greatest_info.prng[0]; \ 774 | if (!greatest_name_match(suite_name, greatest_info.suite_filter, 1) \ 775 | || (GREATEST_FIRST_FAIL() && greatest_info.failed > 0)) { \ 776 | return 0; \ 777 | } \ 778 | if (p->random_order) { \ 779 | p->count++; \ 780 | if (!p->initialized || ((p->count - 1) != p->state)) { \ 781 | return 0; /* don't run this suite yet */ \ 782 | } \ 783 | } \ 784 | p->count_run++; \ 785 | update_counts_and_reset_suite(); \ 786 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ 787 | GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ 788 | return 1; \ 789 | } \ 790 | \ 791 | void greatest_suite_post(void) { \ 792 | GREATEST_SET_TIME(greatest_info.suite.post_suite); \ 793 | report_suite(); \ 794 | } \ 795 | \ 796 | static void greatest_run_suite(greatest_suite_cb *suite_cb, \ 797 | const char *suite_name) { \ 798 | if (greatest_suite_pre(suite_name)) { \ 799 | suite_cb(); \ 800 | greatest_suite_post(); \ 801 | } \ 802 | } \ 803 | \ 804 | void greatest_do_pass(const char *name) { \ 805 | if (GREATEST_IS_VERBOSE()) { \ 806 | GREATEST_FPRINTF(GREATEST_STDOUT, "PASS %s: %s", \ 807 | name, greatest_info.msg ? greatest_info.msg : ""); \ 808 | } else { \ 809 | GREATEST_FPRINTF(GREATEST_STDOUT, "."); \ 810 | } \ 811 | greatest_info.suite.passed++; \ 812 | } \ 813 | \ 814 | void greatest_do_fail(const char *name) { \ 815 | if (GREATEST_IS_VERBOSE()) { \ 816 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 817 | "FAIL %s: %s (%s:%u)", \ 818 | name, greatest_info.msg ? greatest_info.msg : "", \ 819 | greatest_info.fail_file, greatest_info.fail_line); \ 820 | } else { \ 821 | GREATEST_FPRINTF(GREATEST_STDOUT, "F"); \ 822 | greatest_info.col++; \ 823 | /* add linebreak if in line of '.'s */ \ 824 | if (greatest_info.col != 0) { \ 825 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 826 | greatest_info.col = 0; \ 827 | } \ 828 | GREATEST_FPRINTF(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ 829 | name, \ 830 | greatest_info.msg ? greatest_info.msg : "", \ 831 | greatest_info.fail_file, greatest_info.fail_line); \ 832 | } \ 833 | greatest_info.suite.failed++; \ 834 | } \ 835 | \ 836 | void greatest_do_skip(const char *name) { \ 837 | if (GREATEST_IS_VERBOSE()) { \ 838 | GREATEST_FPRINTF(GREATEST_STDOUT, "SKIP %s: %s", \ 839 | name, \ 840 | greatest_info.msg ? \ 841 | greatest_info.msg : "" ); \ 842 | } else { \ 843 | GREATEST_FPRINTF(GREATEST_STDOUT, "s"); \ 844 | } \ 845 | greatest_info.suite.skipped++; \ 846 | } \ 847 | \ 848 | int greatest_do_assert_equal_t(const void *exp, const void *got, \ 849 | greatest_type_info *type_info, void *udata) { \ 850 | int eq = 0; \ 851 | if (type_info == NULL || type_info->equal == NULL) { \ 852 | return 0; \ 853 | } \ 854 | eq = type_info->equal(exp, got, udata); \ 855 | if (!eq) { \ 856 | if (type_info->print != NULL) { \ 857 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ 858 | (void)type_info->print(exp, udata); \ 859 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ 860 | (void)type_info->print(got, udata); \ 861 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 862 | } else { \ 863 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 864 | "GREATEST_ASSERT_EQUAL_T failure at %s:%u\n", \ 865 | greatest_info.fail_file, \ 866 | greatest_info.fail_line); \ 867 | } \ 868 | } \ 869 | return eq; \ 870 | } \ 871 | \ 872 | void greatest_usage(const char *name) { \ 873 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 874 | "Usage: %s [--help] [-hlfv] [-s SUITE] [-t TEST]\n" \ 875 | " -h, --help print this Help\n" \ 876 | " -l List suites and tests, then exit (dry run)\n" \ 877 | " -f Stop runner after first failure\n" \ 878 | " -v Verbose output\n" \ 879 | " -s SUITE only run suites containing string SUITE\n" \ 880 | " -t TEST only run tests containing string TEST\n" \ 881 | " -x EXCLUDE exclude tests containing string EXCLUDE\n", \ 882 | name); \ 883 | } \ 884 | \ 885 | static void greatest_parse_options(int argc, char **argv) { \ 886 | int i = 0; \ 887 | for (i = 1; i < argc; i++) { \ 888 | if (argv[i][0] == '-') { \ 889 | char f = argv[i][1]; \ 890 | if ((f == 's' || f == 't' || f == 'x') && argc <= i + 1) { \ 891 | greatest_usage(argv[0]); exit(EXIT_FAILURE); \ 892 | } \ 893 | switch (f) { \ 894 | case 's': /* suite name filter */ \ 895 | greatest_set_suite_filter(argv[i + 1]); i++; break; \ 896 | case 't': /* test name filter */ \ 897 | greatest_set_test_filter(argv[i + 1]); i++; break; \ 898 | case 'x': /* test name exclusion */ \ 899 | greatest_set_test_exclude(argv[i + 1]); i++; break; \ 900 | case 'f': /* first fail flag */ \ 901 | greatest_stop_at_first_fail(); break; \ 902 | case 'l': /* list only (dry run) */ \ 903 | greatest_info.flags |= GREATEST_FLAG_LIST_ONLY; break; \ 904 | case 'v': /* first fail flag */ \ 905 | greatest_info.verbosity++; break; \ 906 | case 'h': /* help */ \ 907 | greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ 908 | case '-': \ 909 | if (0 == strncmp("--help", argv[i], 6)) { \ 910 | greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ 911 | } else if (0 == strncmp("--", argv[i], 2)) { \ 912 | return; /* ignore following arguments */ \ 913 | } /* fall through */ \ 914 | default: \ 915 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 916 | "Unknown argument '%s'\n", argv[i]); \ 917 | greatest_usage(argv[0]); \ 918 | exit(EXIT_FAILURE); \ 919 | } \ 920 | } \ 921 | } \ 922 | } \ 923 | \ 924 | int greatest_all_passed(void) { return (greatest_info.failed == 0); } \ 925 | \ 926 | void greatest_set_test_filter(const char *filter) { \ 927 | greatest_info.test_filter = filter; \ 928 | } \ 929 | \ 930 | void greatest_set_test_exclude(const char *filter) { \ 931 | greatest_info.test_exclude = filter; \ 932 | } \ 933 | \ 934 | void greatest_set_suite_filter(const char *filter) { \ 935 | greatest_info.suite_filter = filter; \ 936 | } \ 937 | \ 938 | void greatest_stop_at_first_fail(void) { \ 939 | greatest_info.flags |= GREATEST_FLAG_FIRST_FAIL; \ 940 | } \ 941 | \ 942 | void greatest_get_report(struct greatest_report_t *report) { \ 943 | if (report) { \ 944 | report->passed = greatest_info.passed; \ 945 | report->failed = greatest_info.failed; \ 946 | report->skipped = greatest_info.skipped; \ 947 | report->assertions = greatest_info.assertions; \ 948 | } \ 949 | } \ 950 | \ 951 | unsigned int greatest_get_verbosity(void) { \ 952 | return greatest_info.verbosity; \ 953 | } \ 954 | \ 955 | void greatest_set_verbosity(unsigned int verbosity) { \ 956 | greatest_info.verbosity = (unsigned char)verbosity; \ 957 | } \ 958 | \ 959 | void greatest_set_flag(greatest_flag_t flag) { \ 960 | greatest_info.flags |= flag; \ 961 | } \ 962 | \ 963 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ 964 | greatest_info.setup = cb; \ 965 | greatest_info.setup_udata = udata; \ 966 | } \ 967 | \ 968 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, \ 969 | void *udata) { \ 970 | greatest_info.teardown = cb; \ 971 | greatest_info.teardown_udata = udata; \ 972 | } \ 973 | \ 974 | static int greatest_string_equal_cb(const void *exp, const void *got, \ 975 | void *udata) { \ 976 | size_t *size = (size_t *)udata; \ 977 | return (size != NULL \ 978 | ? (0 == strncmp((const char *)exp, (const char *)got, *size)) \ 979 | : (0 == strcmp((const char *)exp, (const char *)got))); \ 980 | } \ 981 | \ 982 | static int greatest_string_printf_cb(const void *t, void *udata) { \ 983 | (void)udata; /* note: does not check \0 termination. */ \ 984 | return GREATEST_FPRINTF(GREATEST_STDOUT, "%s", (const char *)t); \ 985 | } \ 986 | \ 987 | greatest_type_info greatest_type_info_string = { \ 988 | greatest_string_equal_cb, \ 989 | greatest_string_printf_cb, \ 990 | }; \ 991 | \ 992 | static int greatest_memory_equal_cb(const void *exp, const void *got, \ 993 | void *udata) { \ 994 | greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ 995 | return (0 == memcmp(exp, got, env->size)); \ 996 | } \ 997 | \ 998 | /* Hexdump raw memory, with differences highlighted */ \ 999 | static int greatest_memory_printf_cb(const void *t, void *udata) { \ 1000 | greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ 1001 | const unsigned char *buf = (const unsigned char *)t; \ 1002 | unsigned char diff_mark = ' '; \ 1003 | FILE *out = GREATEST_STDOUT; \ 1004 | size_t i, line_i, line_len = 0; \ 1005 | int len = 0; /* format hexdump with differences highlighted */ \ 1006 | for (i = 0; i < env->size; i+= line_len) { \ 1007 | diff_mark = ' '; \ 1008 | line_len = env->size - i; \ 1009 | if (line_len > 16) { line_len = 16; } \ 1010 | for (line_i = i; line_i < i + line_len; line_i++) { \ 1011 | if (env->exp[line_i] != env->got[line_i]) diff_mark = 'X'; \ 1012 | } \ 1013 | len += GREATEST_FPRINTF(out, "\n%04x %c ", \ 1014 | (unsigned int)i, diff_mark); \ 1015 | for (line_i = i; line_i < i + line_len; line_i++) { \ 1016 | int m = env->exp[line_i] == env->got[line_i]; /* match? */ \ 1017 | len += GREATEST_FPRINTF(out, "%02x%c", \ 1018 | buf[line_i], m ? ' ' : '<'); \ 1019 | } \ 1020 | for (line_i = 0; line_i < 16 - line_len; line_i++) { \ 1021 | len += GREATEST_FPRINTF(out, " "); \ 1022 | } \ 1023 | GREATEST_FPRINTF(out, " "); \ 1024 | for (line_i = i; line_i < i + line_len; line_i++) { \ 1025 | unsigned char c = buf[line_i]; \ 1026 | len += GREATEST_FPRINTF(out, "%c", isprint(c) ? c : '.'); \ 1027 | } \ 1028 | } \ 1029 | len += GREATEST_FPRINTF(out, "\n"); \ 1030 | return len; \ 1031 | } \ 1032 | \ 1033 | void greatest_prng_init_first_pass(int id) { \ 1034 | greatest_info.prng[id].random_order = 1; \ 1035 | greatest_info.prng[id].count_run = 0; \ 1036 | } \ 1037 | \ 1038 | int greatest_prng_init_second_pass(int id, unsigned long seed) { \ 1039 | static unsigned long primes[] = { 11, 101, 1009, 10007, \ 1040 | 100003, 1000003, 10000019, 100000007, 1000000007, \ 1041 | 1538461, 1865471, 17471, 2147483647 /* 2**32 - 1 */, }; \ 1042 | struct greatest_prng *prng = &greatest_info.prng[id]; \ 1043 | if (prng->count == 0) { return 0; } \ 1044 | prng->mod = 1; \ 1045 | prng->count_ceil = prng->count; \ 1046 | while (prng->mod < prng->count) { prng->mod <<= 1; } \ 1047 | prng->state = seed & 0x1fffffff; /* only use lower 29 bits... */ \ 1048 | prng->a = (4LU * prng->state) + 1; /* to avoid overflow */ \ 1049 | prng->c = primes[(seed * 16451) % sizeof(primes)/sizeof(primes[0])];\ 1050 | prng->initialized = 1; \ 1051 | return 1; \ 1052 | } \ 1053 | \ 1054 | /* Step the pseudorandom number generator until its state reaches \ 1055 | * another test ID between 0 and the test count. \ 1056 | * This use a linear congruential pseudorandom number generator, \ 1057 | * with the power-of-two ceiling of the test count as the modulus, the \ 1058 | * masked seed as the multiplier, and a prime as the increment. For \ 1059 | * each generated value < the test count, run the corresponding test. \ 1060 | * This will visit all IDs 0 <= X < mod once before repeating, \ 1061 | * with a starting position chosen based on the initial seed. \ 1062 | * For details, see: Knuth, The Art of Computer Programming \ 1063 | * Volume. 2, section 3.2.1. */ \ 1064 | void greatest_prng_step(int id) { \ 1065 | struct greatest_prng *p = &greatest_info.prng[id]; \ 1066 | do { \ 1067 | p->state = ((p->a * p->state) + p->c) & (p->mod - 1); \ 1068 | } while (p->state >= p->count_ceil); \ 1069 | } \ 1070 | \ 1071 | greatest_type_info greatest_type_info_memory = { \ 1072 | greatest_memory_equal_cb, \ 1073 | greatest_memory_printf_cb, \ 1074 | }; \ 1075 | \ 1076 | greatest_run_info greatest_info 1077 | 1078 | /* Init internals. */ 1079 | #define GREATEST_INIT() \ 1080 | do { \ 1081 | /* Suppress unused function warning if features aren't used */ \ 1082 | (void)greatest_run_suite; \ 1083 | (void)greatest_parse_options; \ 1084 | (void)greatest_prng_step; \ 1085 | (void)greatest_prng_init_first_pass; \ 1086 | (void)greatest_prng_init_second_pass; \ 1087 | \ 1088 | memset(&greatest_info, 0, sizeof(greatest_info)); \ 1089 | greatest_info.width = GREATEST_DEFAULT_WIDTH; \ 1090 | GREATEST_SET_TIME(greatest_info.begin); \ 1091 | } while (0) \ 1092 | 1093 | /* Handle command-line arguments, etc. */ 1094 | #define GREATEST_MAIN_BEGIN() \ 1095 | do { \ 1096 | GREATEST_INIT(); \ 1097 | greatest_parse_options(argc, argv); \ 1098 | } while (0) 1099 | 1100 | /* Report passes, failures, skipped tests, the number of 1101 | * assertions, and the overall run time. */ 1102 | #define GREATEST_PRINT_REPORT() \ 1103 | do { \ 1104 | if (!GREATEST_LIST_ONLY()) { \ 1105 | update_counts_and_reset_suite(); \ 1106 | GREATEST_SET_TIME(greatest_info.end); \ 1107 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 1108 | "\nTotal: %u test%s", \ 1109 | greatest_info.tests_run, \ 1110 | greatest_info.tests_run == 1 ? "" : "s"); \ 1111 | GREATEST_CLOCK_DIFF(greatest_info.begin, \ 1112 | greatest_info.end); \ 1113 | GREATEST_FPRINTF(GREATEST_STDOUT, ", %u assertion%s\n", \ 1114 | greatest_info.assertions, \ 1115 | greatest_info.assertions == 1 ? "" : "s"); \ 1116 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 1117 | "Pass: %u, fail: %u, skip: %u.\n", \ 1118 | greatest_info.passed, \ 1119 | greatest_info.failed, greatest_info.skipped); \ 1120 | } \ 1121 | } while (0) 1122 | 1123 | /* Report results, exit with exit status based on results. */ 1124 | #define GREATEST_MAIN_END() \ 1125 | do { \ 1126 | GREATEST_PRINT_REPORT(); \ 1127 | return (greatest_all_passed() ? EXIT_SUCCESS : EXIT_FAILURE); \ 1128 | } while (0) 1129 | 1130 | /* Make abbreviations without the GREATEST_ prefix for the 1131 | * most commonly used symbols. */ 1132 | #if GREATEST_USE_ABBREVS 1133 | #define TEST GREATEST_TEST 1134 | #define SUITE GREATEST_SUITE 1135 | #define SUITE_EXTERN GREATEST_SUITE_EXTERN 1136 | #define RUN_TEST GREATEST_RUN_TEST 1137 | #define RUN_TEST1 GREATEST_RUN_TEST1 1138 | #define RUN_SUITE GREATEST_RUN_SUITE 1139 | #define IGNORE_TEST GREATEST_IGNORE_TEST 1140 | #define ASSERT GREATEST_ASSERT 1141 | #define ASSERTm GREATEST_ASSERTm 1142 | #define ASSERT_FALSE GREATEST_ASSERT_FALSE 1143 | #define ASSERT_EQ GREATEST_ASSERT_EQ 1144 | #define ASSERT_EQ_FMT GREATEST_ASSERT_EQ_FMT 1145 | #define ASSERT_IN_RANGE GREATEST_ASSERT_IN_RANGE 1146 | #define ASSERT_EQUAL_T GREATEST_ASSERT_EQUAL_T 1147 | #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ 1148 | #define ASSERT_STRN_EQ GREATEST_ASSERT_STRN_EQ 1149 | #define ASSERT_MEM_EQ GREATEST_ASSERT_MEM_EQ 1150 | #define ASSERT_ENUM_EQ GREATEST_ASSERT_ENUM_EQ 1151 | #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm 1152 | #define ASSERT_EQm GREATEST_ASSERT_EQm 1153 | #define ASSERT_EQ_FMTm GREATEST_ASSERT_EQ_FMTm 1154 | #define ASSERT_IN_RANGEm GREATEST_ASSERT_IN_RANGEm 1155 | #define ASSERT_EQUAL_Tm GREATEST_ASSERT_EQUAL_Tm 1156 | #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm 1157 | #define ASSERT_STRN_EQm GREATEST_ASSERT_STRN_EQm 1158 | #define ASSERT_MEM_EQm GREATEST_ASSERT_MEM_EQm 1159 | #define ASSERT_ENUM_EQm GREATEST_ASSERT_ENUM_EQm 1160 | #define PASS GREATEST_PASS 1161 | #define FAIL GREATEST_FAIL 1162 | #define SKIP GREATEST_SKIP 1163 | #define PASSm GREATEST_PASSm 1164 | #define FAILm GREATEST_FAILm 1165 | #define SKIPm GREATEST_SKIPm 1166 | #define SET_SETUP GREATEST_SET_SETUP_CB 1167 | #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB 1168 | #define CHECK_CALL GREATEST_CHECK_CALL 1169 | #define SHUFFLE_TESTS GREATEST_SHUFFLE_TESTS 1170 | #define SHUFFLE_SUITES GREATEST_SHUFFLE_SUITES 1171 | 1172 | #ifdef GREATEST_VA_ARGS 1173 | #define RUN_TESTp GREATEST_RUN_TESTp 1174 | #endif 1175 | 1176 | #if GREATEST_USE_LONGJMP 1177 | #define ASSERT_OR_LONGJMP GREATEST_ASSERT_OR_LONGJMP 1178 | #define ASSERT_OR_LONGJMPm GREATEST_ASSERT_OR_LONGJMPm 1179 | #define FAIL_WITH_LONGJMP GREATEST_FAIL_WITH_LONGJMP 1180 | #define FAIL_WITH_LONGJMPm GREATEST_FAIL_WITH_LONGJMPm 1181 | #endif 1182 | 1183 | #endif /* USE_ABBREVS */ 1184 | 1185 | #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) 1186 | } 1187 | #endif 1188 | 1189 | #endif 1190 | -------------------------------------------------------------------------------- /deps/greatest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "greatest", 3 | "version": "v1.2.1", 4 | "repo": "silentbicycle/greatest", 5 | "src": ["greatest.h"], 6 | "description": "A C testing library in 1 file. No dependencies, no dynamic allocation.", 7 | "license": "ISC", 8 | "keywords": ["test", "unit", "testing"] 9 | } 10 | -------------------------------------------------------------------------------- /deps/theft/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | test_theft 3 | libtheft.a 4 | -------------------------------------------------------------------------------- /deps/theft/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Scott Vokes 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /deps/theft/theft.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "theft.h" 4 | #include "theft_types_internal.h" 5 | #include "theft_bloom.h" 6 | #include "theft_mt.h" 7 | 8 | /* Initialize a theft test runner. 9 | * BLOOM_BITS sets the size of the table used for detecting 10 | * combinations of arguments that have already been tested. 11 | * If 0, a default size will be chosen based on trial count. 12 | * (This will only be used if all property types have hash 13 | * callbacks defined.) The bloom filter can also be disabled 14 | * by setting BLOOM_BITS to THEFT_BLOOM_DISABLE. 15 | * 16 | * Returns a NULL if malloc fails or BLOOM_BITS is out of bounds. */ 17 | struct theft *theft_init(uint8_t bloom_bits) { 18 | if ((bloom_bits != 0 && (bloom_bits < THEFT_BLOOM_BITS_MIN)) 19 | || ((bloom_bits > THEFT_BLOOM_BITS_MAX) && 20 | bloom_bits != THEFT_BLOOM_DISABLE)) { 21 | return NULL; 22 | } 23 | 24 | theft *t = malloc(sizeof(*t)); 25 | if (t == NULL) { return NULL; } 26 | memset(t, 0, sizeof(*t)); 27 | 28 | t->mt = theft_mt_init(DEFAULT_THEFT_SEED); 29 | if (t->mt == NULL) { 30 | free(t); 31 | return NULL; 32 | } else { 33 | t->out = stdout; 34 | t->requested_bloom_bits = bloom_bits; 35 | return t; 36 | } 37 | } 38 | 39 | /* (Re-)initialize the random number generator with a specific seed. */ 40 | void theft_set_seed(struct theft *t, uint64_t seed) { 41 | t->seed = seed; 42 | theft_mt_reset(t->mt, seed); 43 | } 44 | 45 | /* Get a random 64-bit integer from the test runner's PRNG. */ 46 | theft_seed theft_random(struct theft *t) { 47 | theft_seed ns = (theft_seed)theft_mt_random(t->mt); 48 | return ns; 49 | } 50 | 51 | /* Get a random double from the test runner's PRNG. */ 52 | double theft_random_double(struct theft *t) { 53 | return theft_mt_random_double(t->mt); 54 | } 55 | 56 | /* Change T's output stream handle to OUT. (Default: stdout.) */ 57 | void theft_set_output_stream(struct theft *t, FILE *out) { t->out = out; } 58 | 59 | /* Check if all argument info structs have all required callbacks. */ 60 | static bool 61 | check_all_args(struct theft_propfun_info *info, bool *all_hashable) { 62 | bool ah = true; 63 | for (int i = 0; i < info->arity; i++) { 64 | struct theft_type_info *ti = info->type_info[i]; 65 | if (ti->alloc == NULL) { return false; } 66 | if (ti->hash == NULL) { ah = false; } 67 | } 68 | *all_hashable = ah; 69 | return true; 70 | } 71 | 72 | static theft_progress_callback_res 73 | default_progress_cb(struct theft_trial_info *info, void *env) { 74 | (void)info; 75 | (void)env; 76 | return THEFT_PROGRESS_CONTINUE; 77 | } 78 | 79 | static void infer_arity(struct theft_propfun_info *info) { 80 | for (int i = 0; i < THEFT_MAX_ARITY; i++) { 81 | if (info->type_info[i] == NULL) { 82 | info->arity = i; 83 | break; 84 | } 85 | } 86 | } 87 | 88 | /* Run a series of randomized trials of a property function. 89 | * 90 | * Configuration is specified in CFG; many fields are optional. 91 | * See the type definition in `theft_types.h`. */ 92 | theft_run_res 93 | theft_run(struct theft *t, struct theft_cfg *cfg) { 94 | if (t == NULL || cfg == NULL) { 95 | return THEFT_RUN_ERROR_BAD_ARGS; 96 | } 97 | 98 | struct theft_propfun_info info; 99 | memset(&info, 0, sizeof(info)); 100 | info.name = cfg->name; 101 | info.fun = cfg->fun; 102 | memcpy(info.type_info, cfg->type_info, sizeof(info.type_info)); 103 | info.always_seed_count = cfg->always_seed_count; 104 | info.always_seeds = cfg->always_seeds; 105 | 106 | if (cfg->seed) { 107 | theft_set_seed(t, cfg->seed); 108 | } else { 109 | theft_set_seed(t, DEFAULT_THEFT_SEED); 110 | } 111 | 112 | if (cfg->trials == 0) { cfg->trials = THEFT_DEF_TRIALS; } 113 | 114 | return theft_run_internal(t, &info, cfg->trials, cfg->progress_cb, 115 | cfg->env, cfg->report); 116 | } 117 | 118 | /* Actually run the trials, with all arguments made explicit. */ 119 | static theft_run_res 120 | theft_run_internal(struct theft *t, struct theft_propfun_info *info, 121 | int trials, theft_progress_cb *cb, void *env, 122 | struct theft_trial_report *r) { 123 | 124 | struct theft_trial_report fake_report; 125 | if (r == NULL) { r = &fake_report; } 126 | memset(r, 0, sizeof(*r)); 127 | 128 | infer_arity(info); 129 | if (info->arity == 0) { 130 | return THEFT_RUN_ERROR_BAD_ARGS; 131 | } 132 | 133 | if (t == NULL || info == NULL || info->fun == NULL 134 | || info->arity == 0) { 135 | return THEFT_RUN_ERROR_BAD_ARGS; 136 | } 137 | 138 | bool all_hashable = false; 139 | if (!check_all_args(info, &all_hashable)) { 140 | return THEFT_RUN_ERROR_MISSING_CALLBACK; 141 | } 142 | 143 | if (cb == NULL) { cb = default_progress_cb; } 144 | 145 | /* If all arguments are hashable, then attempt to use 146 | * a bloom filter to avoid redundant checking. */ 147 | if (all_hashable) { 148 | if (t->requested_bloom_bits == 0) { 149 | t->requested_bloom_bits = theft_bloom_recommendation(trials); 150 | } 151 | if (t->requested_bloom_bits != THEFT_BLOOM_DISABLE) { 152 | t->bloom = theft_bloom_init(t->requested_bloom_bits); 153 | } 154 | } 155 | 156 | theft_seed seed = t->seed; 157 | theft_seed initial_seed = t->seed; 158 | int always_seeds = info->always_seed_count; 159 | if (info->always_seeds == NULL) { always_seeds = 0; } 160 | 161 | void *args[THEFT_MAX_ARITY]; 162 | 163 | theft_progress_callback_res cres = THEFT_PROGRESS_CONTINUE; 164 | 165 | for (int trial = 0; trial < trials; trial++) { 166 | memset(args, 0xFF, sizeof(args)); 167 | if (cres == THEFT_PROGRESS_HALT) { break; } 168 | 169 | /* If any seeds to always run were specified, use those before 170 | * reverting to the specified starting seed. */ 171 | if (trial < always_seeds) { 172 | seed = info->always_seeds[trial]; 173 | } else if ((always_seeds > 0) && (trial == always_seeds)) { 174 | seed = initial_seed; 175 | } 176 | 177 | struct theft_trial_info ti = { 178 | .name = info->name, 179 | .trial = trial, 180 | .seed = seed, 181 | .arity = info->arity, 182 | .args = args 183 | }; 184 | 185 | theft_set_seed(t, seed); 186 | all_gen_res_t gres = gen_all_args(t, info, seed, args, env); 187 | switch (gres) { 188 | case ALL_GEN_SKIP: 189 | /* skip generating these args */ 190 | ti.status = THEFT_TRIAL_SKIP; 191 | r->skip++; 192 | cres = cb(&ti, env); 193 | break; 194 | case ALL_GEN_DUP: 195 | /* skip these args -- probably already tried */ 196 | ti.status = THEFT_TRIAL_DUP; 197 | r->dup++; 198 | cres = cb(&ti, env); 199 | break; 200 | default: 201 | case ALL_GEN_ERROR: 202 | /* Error while generating args */ 203 | ti.status = THEFT_TRIAL_ERROR; 204 | cres = cb(&ti, env); 205 | return THEFT_RUN_ERROR; 206 | case ALL_GEN_OK: 207 | /* (Extracted function to avoid deep nesting here.) */ 208 | if (!run_trial(t, info, args, cb, env, r, &ti, &cres)) { 209 | return THEFT_RUN_ERROR; 210 | } 211 | } 212 | 213 | free_args(info, args, env); 214 | 215 | /* Restore last known seed and generate next. */ 216 | theft_set_seed(t, seed); 217 | seed = theft_random(t); 218 | } 219 | 220 | if (r->fail > 0) { 221 | return THEFT_RUN_FAIL; 222 | } else { 223 | return THEFT_RUN_PASS; 224 | } 225 | } 226 | 227 | /* Now that arguments have been generated, run the trial and update 228 | * counters, call cb with results, etc. */ 229 | static bool run_trial(struct theft *t, struct theft_propfun_info *info, 230 | void **args, theft_progress_cb *cb, void *env, 231 | struct theft_trial_report *r, struct theft_trial_info *ti, 232 | theft_progress_callback_res *cres) { 233 | if (t->bloom) { mark_called(t, info, args, env); } 234 | theft_trial_res tres = call_fun(info, args); 235 | ti->status = tres; 236 | switch (tres) { 237 | case THEFT_TRIAL_PASS: 238 | r->pass++; 239 | *cres = cb(ti, env); 240 | break; 241 | case THEFT_TRIAL_FAIL: 242 | if (!attempt_to_shrink(t, info, args, env)) { 243 | ti->status = THEFT_TRIAL_ERROR; 244 | *cres = cb(ti, env); 245 | return false; 246 | } 247 | r->fail++; 248 | *cres = report_on_failure(t, info, ti, cb, env); 249 | break; 250 | case THEFT_TRIAL_SKIP: 251 | *cres = cb(ti, env); 252 | r->skip++; 253 | break; 254 | case THEFT_TRIAL_DUP: 255 | /* user callback should not return this; fall through */ 256 | case THEFT_TRIAL_ERROR: 257 | *cres = cb(ti, env); 258 | free_args(info, args, env); 259 | return false; 260 | } 261 | return true; 262 | } 263 | 264 | static void free_args(struct theft_propfun_info *info, 265 | void **args, void *env) { 266 | for (int i = 0; i < info->arity; i++) { 267 | theft_free_cb *fcb = info->type_info[i]->free; 268 | if (fcb && args[i] != THEFT_SKIP) { 269 | fcb(args[i], env); 270 | } 271 | } 272 | } 273 | 274 | void theft_free(struct theft *t) { 275 | if (t->bloom) { 276 | theft_bloom_dump(t->bloom); 277 | theft_bloom_free(t->bloom); 278 | t->bloom = NULL; 279 | } 280 | theft_mt_free(t->mt); 281 | free(t); 282 | } 283 | 284 | /* Actually call the property function. Its number of arguments is not 285 | * constrained by the typedef, but will be defined at the call site 286 | * here. (If info->arity is wrong, it will probably crash.) */ 287 | static theft_trial_res 288 | call_fun(struct theft_propfun_info *info, void **args) { 289 | theft_trial_res res = THEFT_TRIAL_ERROR; 290 | switch (info->arity) { 291 | case 1: 292 | res = info->fun(args[0]); 293 | break; 294 | case 2: 295 | res = info->fun(args[0], args[1]); 296 | break; 297 | case 3: 298 | res = info->fun(args[0], args[1], args[2]); 299 | break; 300 | case 4: 301 | res = info->fun(args[0], args[1], args[2], args[3]); 302 | break; 303 | case 5: 304 | res = info->fun(args[0], args[1], args[2], args[3], args[4]); 305 | break; 306 | case 6: 307 | res = info->fun(args[0], args[1], args[2], args[3], args[4], 308 | args[5]); 309 | break; 310 | case 7: 311 | res = info->fun(args[0], args[1], args[2], args[3], args[4], 312 | args[5], args[6]); 313 | break; 314 | case 8: 315 | res = info->fun(args[0], args[1], args[2], args[3], args[4], 316 | args[5], args[6], args[7]); 317 | break; 318 | case 9: 319 | res = info->fun(args[0], args[1], args[2], args[3], args[4], 320 | args[5], args[6], args[7], args[8]); 321 | break; 322 | case 10: 323 | res = info->fun(args[0], args[1], args[2], args[3], args[4], 324 | args[5], args[6], args[7], args[8], args[9]); 325 | break; 326 | /* ... */ 327 | default: 328 | return THEFT_TRIAL_ERROR; 329 | } 330 | return res; 331 | } 332 | 333 | /* Attempt to instantiate arguments, starting with the current seed. */ 334 | static all_gen_res_t 335 | gen_all_args(theft *t, struct theft_propfun_info *info, 336 | theft_seed seed, void *args[THEFT_MAX_ARITY], void *env) { 337 | for (int i = 0; i < info->arity; i++) { 338 | struct theft_type_info *ti = info->type_info[i]; 339 | void *p = ti->alloc(t, seed, env); 340 | if (p == THEFT_SKIP || p == THEFT_ERROR) { 341 | for (int j = 0; j < i; j++) { 342 | ti->free(args[j], env); 343 | } 344 | if (p == THEFT_SKIP) { 345 | return ALL_GEN_SKIP; 346 | } else { 347 | return ALL_GEN_ERROR; 348 | } 349 | } else { 350 | args[i] = p; 351 | } 352 | seed = theft_random(t); 353 | } 354 | 355 | /* check bloom filter */ 356 | if (t->bloom && check_called(t, info, args, env)) { 357 | return ALL_GEN_DUP; 358 | } 359 | 360 | return ALL_GEN_OK; 361 | } 362 | 363 | /* Attempt to simplify all arguments, breadth first. Continue as long as 364 | * progress is made, i.e., until a local minima is reached. */ 365 | static bool 366 | attempt_to_shrink(theft *t, struct theft_propfun_info *info, 367 | void *args[], void *env) { 368 | bool progress = false; 369 | do { 370 | progress = false; 371 | for (int ai = 0; ai < info->arity; ai++) { 372 | struct theft_type_info *ti = info->type_info[ai]; 373 | if (ti->shrink) { 374 | /* attempt to simplify this argument by one step */ 375 | shrink_res rres = attempt_to_shrink_arg(t, info, args, env, ai); 376 | switch (rres) { 377 | case SHRINK_OK: 378 | progress = true; 379 | break; 380 | case SHRINK_DEAD_END: 381 | break; 382 | default: 383 | case SHRINK_ERROR: 384 | return false; 385 | } 386 | } 387 | } 388 | } while (progress); 389 | 390 | return true; 391 | } 392 | 393 | /* Simplify an argument by trying all of its simplification tactics, in 394 | * order, and checking whether the property still fails. If it passes, 395 | * then revert the simplification and try another tactic. 396 | * 397 | * If the bloom filter is being used (i.e., if all arguments have hash 398 | * callbacks defined), then use it to skip over areas of the state 399 | * space that have probably already been tried. */ 400 | static shrink_res 401 | attempt_to_shrink_arg(theft *t, struct theft_propfun_info *info, 402 | void *args[], void *env, int ai) { 403 | struct theft_type_info *ti = info->type_info[ai]; 404 | 405 | for (uint32_t tactic = 0; tactic < THEFT_MAX_TACTICS; tactic++) { 406 | void *cur = args[ai]; 407 | void *nv = ti->shrink(cur, tactic, env); 408 | if (nv == THEFT_NO_MORE_TACTICS) { 409 | return SHRINK_DEAD_END; 410 | } else if (nv == THEFT_ERROR) { 411 | return SHRINK_ERROR; 412 | } else if (nv == THEFT_DEAD_END) { 413 | continue; /* try next tactic */ 414 | } 415 | 416 | args[ai] = nv; 417 | if (t->bloom) { 418 | if (check_called(t, info, args, env)) { 419 | /* probably redundant */ 420 | if (ti->free) { ti->free(nv, env); } 421 | args[ai] = cur; 422 | continue; 423 | } else { 424 | mark_called(t, info, args, env); 425 | } 426 | } 427 | theft_trial_res res = call_fun(info, args); 428 | 429 | switch (res) { 430 | case THEFT_TRIAL_PASS: 431 | case THEFT_TRIAL_SKIP: 432 | /* revert */ 433 | args[ai] = cur; 434 | if (ti->free) { ti->free(nv, env); } 435 | break; 436 | case THEFT_TRIAL_FAIL: 437 | if (ti->free) { ti->free(cur, env); } 438 | return SHRINK_OK; 439 | case THEFT_TRIAL_DUP: /* user callback should not return this */ 440 | case THEFT_TRIAL_ERROR: 441 | return SHRINK_ERROR; 442 | } 443 | } 444 | (void)t; 445 | return SHRINK_DEAD_END; 446 | } 447 | 448 | /* Populate a buffer with hashes of all the arguments. */ 449 | static void get_arg_hash_buffer(theft_hash *buffer, 450 | struct theft_propfun_info *info, void **args, void *env) { 451 | for (int i = 0; i < info->arity; i++) { 452 | buffer[i] = info->type_info[i]->hash(args[i], env); 453 | } 454 | } 455 | 456 | /* Mark the tuple of argument instances as called in the bloom filter. */ 457 | static void mark_called(theft *t, struct theft_propfun_info *info, 458 | void **args, void *env) { 459 | theft_hash buffer[THEFT_MAX_ARITY]; 460 | get_arg_hash_buffer(buffer, info, args, env); 461 | theft_bloom_mark(t->bloom, (uint8_t *)buffer, 462 | info->arity * sizeof(theft_hash)); 463 | } 464 | 465 | /* Check if this combination of argument instances has been called. */ 466 | static bool check_called(theft *t, struct theft_propfun_info *info, 467 | void **args, void *env) { 468 | theft_hash buffer[THEFT_MAX_ARITY]; 469 | get_arg_hash_buffer(buffer, info, args, env); 470 | return theft_bloom_check(t->bloom, (uint8_t *)buffer, 471 | info->arity * sizeof(theft_hash)); 472 | } 473 | 474 | /* Print info about a failure. */ 475 | static theft_progress_callback_res report_on_failure(theft *t, 476 | struct theft_propfun_info *info, 477 | struct theft_trial_info *ti, theft_progress_cb *cb, void *env) { 478 | static theft_progress_callback_res cres; 479 | 480 | int arity = info->arity; 481 | fprintf(t->out, "\n\n -- Counter-Example: %s\n", 482 | info->name ? info-> name : ""); 483 | fprintf(t->out, " Trial %u, Seed 0x%016llx\n", ti->trial, 484 | (uint64_t)ti->seed); 485 | for (int i = 0; i < arity; i++) { 486 | theft_print_cb *print = info->type_info[i]->print; 487 | if (print) { 488 | fprintf(t->out, " Argument %d:\n", i); 489 | print(t->out, ti->args[i], env); 490 | fprintf(t->out, "\n"); 491 | } 492 | } 493 | 494 | cres = cb(ti, env); 495 | return cres; 496 | } 497 | -------------------------------------------------------------------------------- /deps/theft/theft.h: -------------------------------------------------------------------------------- 1 | #ifndef THEFT_H 2 | #define THEFT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /* Version 0.2.0 */ 10 | #define THEFT_VERSION_MAJOR 0 11 | #define THEFT_VERSION_MINOR 2 12 | #define THEFT_VERSION_PATCH 0 13 | 14 | /* A property can have at most this many arguments. */ 15 | #define THEFT_MAX_ARITY 10 16 | 17 | #include "theft_types.h" 18 | 19 | /* Default number of trials to run. */ 20 | #define THEFT_DEF_TRIALS 100 21 | 22 | /* Min and max bits used to determine bloom filter size. 23 | * (A larger value uses more memory, but reduces the odds of an 24 | * untested argument combination being falsely skipped.) */ 25 | #define THEFT_BLOOM_BITS_MIN 13 /* 1 KB */ 26 | #define THEFT_BLOOM_BITS_MAX 33 /* 1 GB */ 27 | 28 | /* Initialize a theft test runner. 29 | * BLOOM_BITS sets the size of the table used for detecting 30 | * combinations of arguments that have already been tested. 31 | * If 0, a default size will be chosen based on trial count. 32 | * (This will only be used if all property types have hash 33 | * callbacks defined.) The bloom filter can also be disabled 34 | * by setting BLOOM_BITS to THEFT_BLOOM_DISABLE. 35 | * 36 | * Returns a NULL if malloc fails or BLOOM_BITS is out of bounds. */ 37 | struct theft *theft_init(uint8_t bloom_bits); 38 | 39 | /* Free a property-test runner. */ 40 | void theft_free(struct theft *t); 41 | 42 | /* (Re-)initialize the random number generator with a specific seed. */ 43 | void theft_set_seed(struct theft *t, uint64_t seed); 44 | 45 | /* Get a random 64-bit integer from the test runner's PRNG. */ 46 | theft_hash theft_random(struct theft *t); 47 | 48 | /* Get a random double from the test runner's PRNG. */ 49 | double theft_random_double(struct theft *t); 50 | 51 | /* Change T's output stream handle to OUT. (Default: stdout.) */ 52 | void theft_set_output_stream(struct theft *t, FILE *out); 53 | 54 | /* Run a series of randomized trials of a property function. 55 | * 56 | * Configuration is specified in CFG; many fields are optional. 57 | * See the type definition in `theft_types.h`. */ 58 | theft_run_res 59 | theft_run(struct theft *t, struct theft_cfg *cfg); 60 | 61 | /* Hash a buffer in one pass. (Wraps the below functions.) */ 62 | theft_hash theft_hash_onepass(uint8_t *data, size_t bytes); 63 | 64 | /* Init/reset a hasher for incremental hashing. 65 | * Returns true, or false if you gave it a NULL pointer. */ 66 | void theft_hash_init(struct theft_hasher *h); 67 | 68 | /* Sink more data into an incremental hash. */ 69 | void theft_hash_sink(struct theft_hasher *h, uint8_t *data, size_t bytes); 70 | 71 | /* Finish hashing and get the result. */ 72 | theft_hash theft_hash_done(struct theft_hasher *h); 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /deps/theft/theft_bloom.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "theft.h" 5 | #include "theft_bloom.h" 6 | 7 | #define DEFAULT_BLOOM_BITS 17 8 | #define DEBUG_BLOOM_FILTER 0 9 | #define LOG2_BITS_PER_BYTE 3 10 | #define HASH_COUNT 4 11 | 12 | static uint8_t get_bits_set_count(uint8_t counts[256], uint8_t byte); 13 | 14 | /* Initialize a bloom filter. */ 15 | struct theft_bloom *theft_bloom_init(uint8_t bit_size2) { 16 | size_t sz = 1 << (bit_size2 - LOG2_BITS_PER_BYTE); 17 | struct theft_bloom *b = malloc(sizeof(struct theft_bloom) + sz); 18 | if (b) { 19 | b->size = sz; 20 | b->bit_count = bit_size2; 21 | memset(b->bits, 0, sz); 22 | } 23 | return b; 24 | } 25 | 26 | /* Hash data and mark it in the bloom filter. */ 27 | void theft_bloom_mark(struct theft_bloom *b, uint8_t *data, size_t data_size) { 28 | uint64_t hash = theft_hash_onepass(data, data_size); 29 | uint8_t bc = b->bit_count; 30 | uint64_t mask = (1 << bc) - 1; 31 | 32 | /* Use HASH_COUNT distinct slices of MASK bits as hashes for the bloom filter. */ 33 | int bit_inc = (64 - bc) / HASH_COUNT; 34 | 35 | for (int i = 0; i < (64 - bc); i += bit_inc) { 36 | uint64_t v = (hash & (mask << i)) >> i; 37 | size_t offset = v / 8; 38 | uint8_t bit = 1 << (v & 0x07); 39 | b->bits[offset] |= bit; 40 | } 41 | } 42 | 43 | /* Check whether the data's hash is in the bloom filter. */ 44 | bool theft_bloom_check(struct theft_bloom *b, uint8_t *data, size_t data_size) { 45 | uint64_t hash = theft_hash_onepass(data, data_size); 46 | uint8_t bc = b->bit_count; 47 | uint64_t mask = (1 << bc) - 1; 48 | 49 | int bit_inc = (64 - bc) / HASH_COUNT; 50 | 51 | for (int i = 0; i < (64 - bc); i += bit_inc) { 52 | uint64_t v = (hash & (mask << i)) >> i; 53 | size_t offset = v / 8; 54 | uint8_t bit = 1 << (v & 0x07); 55 | if (0 == (b->bits[offset] & bit)) { return false; } 56 | } 57 | 58 | return true; 59 | } 60 | 61 | /* Free the bloom filter. */ 62 | void theft_bloom_free(struct theft_bloom *b) { free(b); } 63 | 64 | /* Dump the bloom filter's contents. (Debugging.) */ 65 | void theft_bloom_dump(struct theft_bloom *b) { 66 | uint8_t counts[256]; 67 | memset(counts, 0xFF, sizeof(counts)); 68 | 69 | size_t total = 0; 70 | uint16_t row_total = 0; 71 | 72 | for (size_t i = 0; i < b->size; i++) { 73 | uint8_t count = get_bits_set_count(counts, b->bits[i]); 74 | total += count; 75 | row_total += count; 76 | #if DEBUG_BLOOM_FILTER > 1 77 | char c = (count == 0 ? '.' : '0' + count); 78 | printf("%c", c); 79 | if ((i & 63) == 0 || i == b->size - 1) { 80 | printf(" - %2.1f%%\n", 81 | (100 * row_total) / (64.0 * 8)); 82 | row_total = 0; 83 | } 84 | #endif 85 | } 86 | 87 | #if DEBUG_BLOOM_FILTER 88 | printf(" -- bloom filter: %zd of %zd bits set (%2d%%)\n", 89 | total, 8*b->size, (int)((100.0 * total)/(8.0*b->size))); 90 | #endif 91 | 92 | /* If the total number of bits set is > the number of bytes 93 | * in the table (i.e., > 1/8 full) then warn the user. */ 94 | if (total > b->size) { 95 | fprintf(stderr, "\nWARNING: bloom filter is %zd%% full, " 96 | "larger bloom_bits value recommended.\n", 97 | (size_t)((100 * total) / (8 * b->size))); 98 | } 99 | } 100 | 101 | /* Recommend a bloom filter size for a given number of trials. */ 102 | uint8_t theft_bloom_recommendation(int trials) { 103 | /* With a preferred priority of false positives under 0.1%, 104 | * the required number of bits m in the bloom filter is: 105 | * m = -lg(0.001)/(lg(2)^2) == 14.378 ~= 14, 106 | * so we want an array with 1 << ceil(log2(14*trials)) cells. 107 | * 108 | * Note: The above formula is for the *ideal* number of hash 109 | * functions, but we're using a hardcoded count. It appears to work 110 | * well enough in practice, though, and this can be adjusted later 111 | * without impacting the API. */ 112 | #define TRIAL_MULTIPILER 14 113 | uint8_t res = DEFAULT_BLOOM_BITS; 114 | 115 | const uint8_t min = THEFT_BLOOM_BITS_MIN - LOG2_BITS_PER_BYTE; 116 | const uint8_t max = THEFT_BLOOM_BITS_MAX - LOG2_BITS_PER_BYTE; 117 | 118 | for (uint8_t i = min; i < max; i++) { 119 | int32_t v = (1 << i); 120 | if (v > (TRIAL_MULTIPILER * trials)) { 121 | res = i + LOG2_BITS_PER_BYTE; 122 | break; 123 | } 124 | } 125 | 126 | #if DEBUG_BLOOM_FILTER 127 | size_t sz = 1 << (res - LOG2_BITS_PER_BYTE); 128 | printf("Using %zd bytes for bloom filter: %d trials -> bit_size2 %u\n", 129 | sizeof(struct theft_bloom) + sz, trials, res); 130 | #endif 131 | 132 | return res; 133 | } 134 | 135 | /* Check a byte->bits set table, and lazily populate it. */ 136 | static uint8_t get_bits_set_count(uint8_t counts[256], uint8_t byte) { 137 | uint8_t v = counts[byte]; 138 | if (v != 0xFF) { return v; } 139 | uint8_t t = 0; 140 | for (uint8_t i = 0; i < 8; i++) { 141 | if (byte & (1 << i)) { t++; } 142 | } 143 | counts[byte] = t; 144 | return t; 145 | } 146 | -------------------------------------------------------------------------------- /deps/theft/theft_bloom.h: -------------------------------------------------------------------------------- 1 | #ifndef THEFT_BLOOM_H 2 | #define THEFT_BLOOM_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct theft_bloom { 9 | uint8_t bit_count; 10 | size_t size; 11 | uint8_t bits[]; 12 | }; 13 | 14 | /* Initialize a bloom filter. */ 15 | struct theft_bloom *theft_bloom_init(uint8_t bit_size2); 16 | 17 | /* Hash data and mark it in the bloom filter. */ 18 | void theft_bloom_mark(struct theft_bloom *b, uint8_t *data, size_t data_size); 19 | 20 | /* Check whether the data's hash is in the bloom filter. */ 21 | bool theft_bloom_check(struct theft_bloom *b, uint8_t *data, size_t data_size); 22 | 23 | /* Free the bloom filter. */ 24 | void theft_bloom_free(struct theft_bloom *b); 25 | 26 | /* Dump the bloom filter's contents. (Debugging.) */ 27 | void theft_bloom_dump(struct theft_bloom *b); 28 | 29 | /* Recommend a bloom filter size for a given number of trials. */ 30 | uint8_t theft_bloom_recommendation(int trials); 31 | 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /deps/theft/theft_hash.c: -------------------------------------------------------------------------------- 1 | #include "theft.h" 2 | 3 | /* Fowler/Noll/Vo hash, 64-bit FNV-1a. 4 | * This hashing algorithm is in the public domain. 5 | * For more details, see: http://www.isthe.com/chongo/tech/comp/fnv/. */ 6 | static const uint64_t fnv64_prime = 1099511628211L; 7 | static const uint64_t fnv64_offset_basis = 14695981039346656037UL; 8 | 9 | /* Init a hasher for incremental hashing. */ 10 | void theft_hash_init(struct theft_hasher *h) { 11 | h->accum = fnv64_offset_basis; 12 | } 13 | 14 | /* Sink more data into an incremental hash. */ 15 | void theft_hash_sink(struct theft_hasher *h, uint8_t *data, size_t bytes) { 16 | if (h == NULL || data == NULL) { return; } 17 | uint64_t a = h->accum; 18 | for (size_t i = 0; i < bytes; i++) { 19 | a = (a ^ data[i]) * fnv64_prime; 20 | } 21 | h->accum = a; 22 | } 23 | 24 | /* Finish hashing and get the result. */ 25 | theft_hash theft_hash_done(struct theft_hasher *h) { 26 | theft_hash res = h->accum; 27 | theft_hash_init(h); /* reset */ 28 | return res; 29 | } 30 | 31 | /* Hash a buffer in one pass. (Wraps the above functions.) */ 32 | theft_hash theft_hash_onepass(uint8_t *data, size_t bytes) { 33 | struct theft_hasher h; 34 | theft_hash_init(&h); 35 | theft_hash_sink(&h, data, bytes); 36 | return theft_hash_done(&h); 37 | } 38 | -------------------------------------------------------------------------------- /deps/theft/theft_mt.c: -------------------------------------------------------------------------------- 1 | /* 2 | A C-program for MT19937-64 (2004/9/29 version). 3 | Coded by Takuji Nishimura and Makoto Matsumoto. 4 | 5 | This is a 64-bit version of Mersenne Twister pseudorandom number 6 | generator. 7 | 8 | Before using, initialize the state by using init_genrand64(seed) 9 | or init_by_array64(init_key, key_length). 10 | 11 | Copyright (C) 2004, Makoto Matsumoto and Takuji Nishimura, 12 | All rights reserved. 13 | 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions 16 | are met: 17 | 18 | 1. Redistributions of source code must retain the above copyright 19 | notice, this list of conditions and the following disclaimer. 20 | 21 | 2. Redistributions in binary form must reproduce the above copyright 22 | notice, this list of conditions and the following disclaimer in the 23 | documentation and/or other materials provided with the distribution. 24 | 25 | 3. The names of its contributors may not be used to endorse or promote 26 | products derived from this software without specific prior written 27 | permission. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 30 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 31 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 32 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 33 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 34 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 37 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 38 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 39 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40 | 41 | References: 42 | T. Nishimura, ``Tables of 64-bit Mersenne Twisters'' 43 | ACM Transactions on Modeling and 44 | Computer Simulation 10. (2000) 348--357. 45 | M. Matsumoto and T. Nishimura, 46 | ``Mersenne Twister: a 623-dimensionally equidistributed 47 | uniform pseudorandom number generator'' 48 | ACM Transactions on Modeling and 49 | Computer Simulation 8. (Jan. 1998) 3--30. 50 | 51 | Any feedback is very welcome. 52 | http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html 53 | email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces) 54 | */ 55 | 56 | /* The code has been modified to store internal state in heap/stack 57 | * allocated memory, rather than statically allocated memory, to allow 58 | * multiple instances running in the same address space. */ 59 | 60 | #include 61 | #include 62 | #include "theft_mt.h" 63 | 64 | #define NN THEFT_MT_PARAM_N 65 | #define MM 156 66 | #define MATRIX_A 0xB5026F5AA96619E9ULL 67 | #define UM 0xFFFFFFFF80000000ULL /* Most significant 33 bits */ 68 | #define LM 0x7FFFFFFFULL /* Least significant 31 bits */ 69 | 70 | static uint64_t genrand64_int64(struct theft_mt *r); 71 | 72 | /* Heap-allocate a mersenne twister struct. */ 73 | struct theft_mt *theft_mt_init(uint64_t seed) { 74 | struct theft_mt *mt = malloc(sizeof(struct theft_mt)); 75 | if (mt == NULL) { return NULL; } 76 | theft_mt_reset(mt, seed); 77 | return mt; 78 | } 79 | 80 | /* Free a heap-allocated mersenne twister struct. */ 81 | void theft_mt_free(struct theft_mt *mt) { 82 | free(mt); 83 | } 84 | 85 | /* initializes mt[NN] with a seed */ 86 | void theft_mt_reset(struct theft_mt *mt, uint64_t seed) 87 | { 88 | mt->mt[0] = seed; 89 | uint16_t mti = 0; 90 | for (mti=1; mtimt[mti] = (6364136223846793005ULL * 92 | (mt->mt[mti-1] ^ (mt->mt[mti-1] >> 62)) + mti); 93 | } 94 | mt->mti = mti; 95 | } 96 | 97 | /* Get a 64-bit random number. */ 98 | uint64_t theft_mt_random(struct theft_mt *mt) { 99 | return genrand64_int64(mt); 100 | } 101 | 102 | /* Generate a random number on [0,1]-real-interval. */ 103 | double theft_mt_random_double(struct theft_mt *mt) 104 | { 105 | return (genrand64_int64(mt) >> 11) * (1.0/9007199254740991.0); 106 | } 107 | 108 | /* generates a random number on [0, 2^64-1]-interval */ 109 | static uint64_t genrand64_int64(struct theft_mt *r) 110 | { 111 | int i; 112 | uint64_t x; 113 | static uint64_t mag01[2]={0ULL, MATRIX_A}; 114 | 115 | if (r->mti >= NN) { /* generate NN words at one time */ 116 | 117 | /* if init has not been called, */ 118 | /* a default initial seed is used */ 119 | if (r->mti == NN+1) 120 | theft_mt_reset(r, 5489ULL); 121 | 122 | for (i=0;imt[i]&UM)|(r->mt[i+1]&LM); 124 | r->mt[i] = r->mt[i+MM] ^ (x>>1) ^ mag01[(int)(x&1ULL)]; 125 | } 126 | for (;imt[i]&UM)|(r->mt[i+1]&LM); 128 | r->mt[i] = r->mt[i+(MM-NN)] ^ (x>>1) ^ mag01[(int)(x&1ULL)]; 129 | } 130 | x = (r->mt[NN-1]&UM)|(r->mt[0]&LM); 131 | r->mt[NN-1] = r->mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&1ULL)]; 132 | 133 | r->mti = 0; 134 | } 135 | 136 | x = r->mt[r->mti++]; 137 | 138 | x ^= (x >> 29) & 0x5555555555555555ULL; 139 | x ^= (x << 17) & 0x71D67FFFEDA60000ULL; 140 | x ^= (x << 37) & 0xFFF7EEE000000000ULL; 141 | x ^= (x >> 43); 142 | 143 | return x; 144 | } 145 | -------------------------------------------------------------------------------- /deps/theft/theft_mt.h: -------------------------------------------------------------------------------- 1 | #ifndef THEFT_MT_H 2 | #define THEFT_MT_H 3 | 4 | #include 5 | 6 | /* Wrapper for Mersenne Twister. 7 | * See copyright and license in theft_mt.c, more details at: 8 | * http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html 9 | * 10 | * The code has been modified to store internal state in heap/stack 11 | * allocated memory, rather than statically allocated memory, to allow 12 | * multiple instances running in the same address space. */ 13 | 14 | #define THEFT_MT_PARAM_N 312 15 | 16 | struct theft_mt { 17 | uint64_t mt[THEFT_MT_PARAM_N]; /* the array for the state vector */ 18 | int16_t mti; 19 | }; 20 | 21 | /* Heap-allocate a mersenne twister struct. */ 22 | struct theft_mt *theft_mt_init(uint64_t seed); 23 | 24 | /* Free a heap-allocated mersenne twister struct. */ 25 | void theft_mt_free(struct theft_mt *mt); 26 | 27 | /* Reset a mersenne twister struct, possibly stack-allocated. */ 28 | void theft_mt_reset(struct theft_mt *mt, uint64_t seed); 29 | 30 | /* Get a 64-bit random number. */ 31 | uint64_t theft_mt_random(struct theft_mt *mt); 32 | 33 | /* Generate a random number on [0,1]-real-interval. */ 34 | double theft_mt_random_double(struct theft_mt *mt); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /deps/theft/theft_types.h: -------------------------------------------------------------------------------- 1 | #ifndef THEFT_TYPES_H 2 | #define THEFT_TYPES_H 3 | 4 | /* A pseudo-random number/seed, used to generate instances. */ 5 | typedef uint64_t theft_seed; 6 | 7 | /* A hash of an instance. */ 8 | typedef uint64_t theft_hash; 9 | 10 | /* These are opaque, as far as the API is concerned. */ 11 | struct theft_bloom; /* bloom filter */ 12 | struct theft_mt; /* mersenne twister PRNG */ 13 | 14 | /* Struct for property-testing state. */ 15 | struct theft { 16 | FILE *out; 17 | theft_seed seed; 18 | uint8_t requested_bloom_bits; 19 | struct theft_bloom *bloom; /* bloom filter */ 20 | struct theft_mt *mt; /* random number generator */ 21 | }; 22 | 23 | /* Special sentinel values returned instead of instance pointers. */ 24 | #define THEFT_SKIP ((void *)-1) 25 | #define THEFT_ERROR ((void *)-2) 26 | #define THEFT_DEAD_END ((void *)-1) 27 | #define THEFT_NO_MORE_TACTICS ((void *)-3) 28 | 29 | /* Explicitly disable using the bloom filter. 30 | * Note that if you do this, you must be sure your simplify function 31 | * *always* returns a simpler value, or it will loop forever. */ 32 | #define THEFT_BLOOM_DISABLE ((uint8_t)-1) 33 | 34 | /* Allocate and return an instance of the type, based on a known 35 | * pseudo-random number seed. To get additional seeds, use 36 | * theft_random(t); this stream of numbers will be deterministic, so if 37 | * the alloc callback is constructed appropriately, an identical 38 | * instance can be constructed later from the same initial seed. 39 | * 40 | * Returns a pointer to the instance, THEFT_ERROR, or THEFT_SKIP. */ 41 | typedef void *(theft_alloc_cb)(struct theft *t, theft_seed seed, void *env); 42 | 43 | /* Free an instance. */ 44 | typedef void (theft_free_cb)(void *instance, void *env); 45 | 46 | /* Hash an instance. Used to skip combinations of arguments which 47 | * have probably already been checked. */ 48 | typedef theft_hash (theft_hash_cb)(void *instance, void *env); 49 | 50 | /* Attempt to shrink an instance to a simpler instance. 51 | * 52 | * For a given INSTANCE, there are likely to be multiple ways in which 53 | * it can be simplified. For example, a list of unsigned ints could have 54 | * the first element decremented, divided by 2, or dropped. This 55 | * callback should return a pointer to a freshly allocated, simplified 56 | * instance, or should return THEFT_DEAD_END to indicate that the 57 | * instance cannot be simplified further by this method. 58 | * 59 | * These tactics will be lazily explored breadth-first, to 60 | * try to find simpler versions of arguments that cause the 61 | * property to no longer hold. 62 | * 63 | * If this callback is NULL, it is equivalent to always returning 64 | * THEFT_NO_MORE_TACTICS. */ 65 | typedef void *(theft_shrink_cb)(void *instance, uint32_t tactic, void *env); 66 | 67 | /* Print INSTANCE to output stream F. 68 | * Used for displaying counter-examples. Can be NULL. */ 69 | typedef void (theft_print_cb)(FILE *f, void *instance, void *env); 70 | 71 | /* Result from a single trial. */ 72 | typedef enum { 73 | THEFT_TRIAL_PASS, /* property held */ 74 | THEFT_TRIAL_FAIL, /* property contradicted */ 75 | THEFT_TRIAL_SKIP, /* user requested skip; N/A */ 76 | THEFT_TRIAL_DUP, /* args probably already tried */ 77 | THEFT_TRIAL_ERROR, /* unrecoverable error, halt */ 78 | } theft_trial_res; 79 | 80 | /* A test property function. Arguments must match the types specified by 81 | * theft_cfg.type_info, or the result will be undefined. For example, a 82 | * propfun `prop_foo(A x, B y, C z)` must have a type_info array of 83 | * `{ info_A, info_B, info_C }`. 84 | * 85 | * Should return: 86 | * THEFT_TRIAL_PASS if the property holds, 87 | * THEFT_TRIAL_FAIL if a counter-example is found, 88 | * THEFT_TRIAL_SKIP if the combination of args isn't applicable, 89 | * or THEFT_TRIAL_ERROR if the whole run should be halted. */ 90 | typedef theft_trial_res (theft_propfun)( /* arguments unconstrained */ ); 91 | 92 | /* Callbacks used for testing with random instances of a type. 93 | * For more information, see comments on their typedefs. */ 94 | struct theft_type_info { 95 | /* Required: */ 96 | theft_alloc_cb *alloc; /* gen random instance from seed */ 97 | 98 | /* Optional, but recommended: */ 99 | theft_free_cb *free; /* free instance */ 100 | theft_hash_cb *hash; /* instance -> hash */ 101 | theft_shrink_cb *shrink; /* shrink instance */ 102 | theft_print_cb *print; /* fprintf instance */ 103 | }; 104 | 105 | /* Result from an individual trial. */ 106 | struct theft_trial_info { 107 | const char *name; /* property name */ 108 | int trial; /* N'th trial */ 109 | theft_seed seed; /* Seed used */ 110 | theft_trial_res status; /* Run status */ 111 | uint8_t arity; /* Number of arguments */ 112 | void **args; /* Arguments used */ 113 | }; 114 | 115 | /* Whether to keep running trials after N failures/skips/etc. */ 116 | typedef enum { 117 | THEFT_PROGRESS_CONTINUE, /* keep running trials */ 118 | THEFT_PROGRESS_HALT, /* no need to continue */ 119 | } theft_progress_callback_res; 120 | 121 | /* Handle test results. 122 | * Can be used to halt after too many failures, print '.' after 123 | * every N trials, etc. */ 124 | typedef theft_progress_callback_res 125 | (theft_progress_cb)(struct theft_trial_info *info, void *env); 126 | 127 | /* Result from a trial run. */ 128 | typedef enum { 129 | THEFT_RUN_PASS = 0, /* no failures */ 130 | THEFT_RUN_FAIL = 1, /* 1 or more failures */ 131 | THEFT_RUN_ERROR = 2, /* an error occurred */ 132 | THEFT_RUN_ERROR_BAD_ARGS = -1, /* API misuse */ 133 | /* Missing required callback for 1 or more types */ 134 | THEFT_RUN_ERROR_MISSING_CALLBACK = -2, 135 | } theft_run_res; 136 | 137 | /* Optional report from a trial run; same meanings as theft_trial_res. */ 138 | struct theft_trial_report { 139 | size_t pass; 140 | size_t fail; 141 | size_t skip; 142 | size_t dup; 143 | }; 144 | 145 | /* Configuration struct for a theft test. 146 | * In C99, this struct can be specified as a literal, like this: 147 | * 148 | * struct theft_cfg cfg = { 149 | * .name = "example", 150 | * .fun = prop_fun, 151 | * .type_info = { type_arg_a, type_arg_b }, 152 | * .seed = 0x7he5eed, 153 | * }; 154 | * 155 | * and omitted fields will be set to defaults. 156 | * */ 157 | struct theft_cfg { 158 | /* Property function under test, and info about its arguments. 159 | * The function is called with as many arguments are there 160 | * are values in TYPE_INFO, so it can crash if that is wrong. */ 161 | theft_propfun *fun; 162 | struct theft_type_info *type_info[THEFT_MAX_ARITY]; 163 | 164 | /* -- All fields after this point are optional. -- */ 165 | 166 | /* Property name, displayed in test runner output. */ 167 | const char *name; 168 | 169 | /* Array of seeds to always run, and its length. 170 | * Can be used for regression tests. */ 171 | int always_seed_count; /* number of seeds */ 172 | theft_seed *always_seeds; /* seeds to always run */ 173 | 174 | /* Number of trials to run. Defaults to THEFT_DEF_TRIALS. */ 175 | int trials; 176 | 177 | /* Progress callback, used to display progress in 178 | * slow-running tests, halt early under certain conditions, etc. */ 179 | theft_progress_cb *progress_cb; 180 | 181 | /* Environment pointer. This is completely opaque to theft itself, 182 | * but will be passed along to all callbacks. */ 183 | void *env; 184 | 185 | /* Struct to populate with more detailed test results. */ 186 | struct theft_trial_report *report; 187 | 188 | /* Seed for the random number generator. */ 189 | theft_seed seed; 190 | }; 191 | 192 | /* Internal state for incremental hashing. */ 193 | struct theft_hasher { 194 | theft_hash accum; 195 | }; 196 | 197 | #endif 198 | -------------------------------------------------------------------------------- /deps/theft/theft_types_internal.h: -------------------------------------------------------------------------------- 1 | #ifndef THEFT_TYPES_INTERNAL_H 2 | #define THEFT_TYPES_INTERNAL_H 3 | 4 | typedef struct theft theft; 5 | 6 | #define THEFT_MAX_TACTICS ((uint32_t)-1) 7 | #define DEFAULT_THEFT_SEED 0xa600d16b175eedL 8 | 9 | typedef enum { 10 | ALL_GEN_OK, /* all arguments generated okay */ 11 | ALL_GEN_SKIP, /* skip due to user constraints */ 12 | ALL_GEN_DUP, /* skip probably duplicated trial */ 13 | ALL_GEN_ERROR, /* memory error or other failure */ 14 | } all_gen_res_t; 15 | 16 | typedef enum { 17 | SHRINK_OK, /* simplified argument further */ 18 | SHRINK_DEAD_END, /* at local minima */ 19 | SHRINK_ERROR, /* hard error during shrinking */ 20 | } shrink_res; 21 | 22 | /* Testing context for a specific property function. */ 23 | struct theft_propfun_info { 24 | const char *name; /* property name, can be NULL */ 25 | theft_propfun *fun; /* property function under test */ 26 | 27 | /* Type info for ARITY arguments. */ 28 | uint8_t arity; /* number of arguments */ 29 | struct theft_type_info *type_info[THEFT_MAX_ARITY]; 30 | 31 | /* Optional array of seeds to always run. 32 | * Can be used for regression tests. */ 33 | int always_seed_count; /* number of seeds */ 34 | theft_seed *always_seeds; /* seeds to always run */ 35 | }; 36 | 37 | static theft_trial_res 38 | call_fun(struct theft_propfun_info *info, void **args); 39 | 40 | static bool run_trial(struct theft *t, struct theft_propfun_info *info, 41 | void **args, theft_progress_cb *cb, void *env, 42 | struct theft_trial_report *r, struct theft_trial_info *ti, 43 | theft_progress_callback_res *cres); 44 | 45 | static void mark_called(theft *t, struct theft_propfun_info *info, 46 | void **args, void *env); 47 | 48 | static bool check_called(theft *t, struct theft_propfun_info *info, 49 | void **args, void *env); 50 | 51 | static theft_progress_callback_res report_on_failure(theft *t, 52 | struct theft_propfun_info *info, 53 | struct theft_trial_info *ti, theft_progress_cb *cb, void *env); 54 | 55 | static all_gen_res_t gen_all_args(theft *t, struct theft_propfun_info *info, 56 | theft_seed seed, void *args[THEFT_MAX_ARITY], void *env); 57 | 58 | static void free_args(struct theft_propfun_info *info, 59 | void **args, void *env); 60 | 61 | static theft_run_res theft_run_internal(struct theft *t, 62 | struct theft_propfun_info *info, 63 | int trials, theft_progress_cb *cb, void *env, 64 | struct theft_trial_report *r); 65 | 66 | static bool attempt_to_shrink(theft *t, struct theft_propfun_info *info, 67 | void *args[], void *env); 68 | 69 | static shrink_res 70 | attempt_to_shrink_arg(theft *t, struct theft_propfun_info *info, 71 | void *args[], void *env, int ai); 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /fzy.1: -------------------------------------------------------------------------------- 1 | .TH FZY 1 "2018-09-23" "fzy 1.0" 2 | .SH NAME 3 | fzy \- A fuzzy text selector menu for the terminal. 4 | .SH SYNOPSIS 5 | .B fzy 6 | .IR [OPTION]... 7 | .SH DESCRIPTION 8 | .B fzy is a fuzzy text selector/file finder for the terminal using a search 9 | similar to that of TextMate or CmdT. 10 | 11 | fzy reads a list of newline-separated items from stdin to be displayed as a 12 | menu in the terminal. 13 | Upon pressing ENTER, the currently selected item is printed to stdout. 14 | 15 | Entering text narrows the items using fuzzy matching. Results are sorted using 16 | heuristics for the best match. 17 | 18 | .SH OPTIONS 19 | .TP 20 | .BR \-l ", " \-\-lines =\fILINES\fR 21 | How many lines of items to display. If unspecified, defaults to 10 lines. 22 | . 23 | .TP 24 | .BR \-p ", " \-\-prompt =\fIPROMPT\fR 25 | Input prompt (default: '> ') 26 | . 27 | .TP 28 | .BR \-s ", " \-\-show-scores 29 | Show the scores for each item. 30 | . 31 | .TP 32 | .BR \-t ", " \-\-tty =\fITTY\fR 33 | Use TTY instead of the default tty device (/dev/tty). 34 | . 35 | .TP 36 | .BR \-q ", " \-\-query =\fIQUERY\fR 37 | Use QUERY as the initial search query. 38 | . 39 | .TP 40 | .BR \-e ", " \-\-show-matches =\fIQUERY\fR 41 | Non-interactive mode. Print the matches in sorted order for QUERY to stdout. 42 | . 43 | .TP 44 | .BR \-0 ", " \-\-read-null 45 | Read input delimited by ASCII NUL characters. 46 | . 47 | .TP 48 | .BR \-h ", " \-\-help 49 | Usage help. 50 | . 51 | .TP 52 | .BR \-v ", " \-\-version 53 | Usage help. 54 | . 55 | .SH KEYS 56 | . 57 | .TP 58 | .BR "ENTER" 59 | Print the selected item to stdout and exit 60 | .TP 61 | .BR "Ctrl+c, Ctrl+g, Esc" 62 | Exit with status 1, without making a selection. 63 | .TP 64 | .BR "Up Arrow, Ctrl+p, Ctrl+k" 65 | Select the previous item 66 | .TP 67 | .BR "Down Arrow, Ctrl+n, Ctrl+j" 68 | Select the next item 69 | .TP 70 | Tab 71 | Replace the current search string with the selected item 72 | .TP 73 | .BR "Backspace, Ctrl+h" 74 | Delete the character before the cursor 75 | .TP 76 | .BR Ctrl+w 77 | Delete the word before the cursor 78 | .TP 79 | .BR Ctrl+u 80 | Delete the entire line 81 | . 82 | .SH USAGE EXAMPLES 83 | . 84 | .TP 85 | .BR "ls | fzy" 86 | Present a menu of items in the current directory 87 | .TP 88 | .BR "ls | fzy -l 25" 89 | Same as above, but show 25 lines of items 90 | .TP 91 | .BR "vi $(find -type f | fzy)" 92 | List files under the current directory and open the one selected in vi. 93 | .TP 94 | .BR "cd $(find -type d | fzy)" 95 | Present all directories under current path, and change to the one selected. 96 | .TP 97 | .BR "ps aux | fzy | awk '{ print $2 }' | xargs kill" 98 | List running processes, kill the selected process 99 | .TP 100 | .BR "git checkout $(git branch | cut -c 3- | fzy)" 101 | Same as above, but switching git branches. 102 | .SH AUTHOR 103 | John Hawthorn 104 | -------------------------------------------------------------------------------- /src/bonus.h: -------------------------------------------------------------------------------- 1 | #ifndef BONUS_H 2 | #define BONUS_H BONUS_H 3 | 4 | #include "../config.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #define ASSIGN_LOWER(v) \ 11 | ['a'] = (v), \ 12 | ['b'] = (v), \ 13 | ['c'] = (v), \ 14 | ['d'] = (v), \ 15 | ['e'] = (v), \ 16 | ['f'] = (v), \ 17 | ['g'] = (v), \ 18 | ['h'] = (v), \ 19 | ['i'] = (v), \ 20 | ['j'] = (v), \ 21 | ['k'] = (v), \ 22 | ['l'] = (v), \ 23 | ['m'] = (v), \ 24 | ['n'] = (v), \ 25 | ['o'] = (v), \ 26 | ['p'] = (v), \ 27 | ['q'] = (v), \ 28 | ['r'] = (v), \ 29 | ['s'] = (v), \ 30 | ['t'] = (v), \ 31 | ['u'] = (v), \ 32 | ['v'] = (v), \ 33 | ['w'] = (v), \ 34 | ['x'] = (v), \ 35 | ['y'] = (v), \ 36 | ['z'] = (v) 37 | 38 | #define ASSIGN_UPPER(v) \ 39 | ['A'] = (v), \ 40 | ['B'] = (v), \ 41 | ['C'] = (v), \ 42 | ['D'] = (v), \ 43 | ['E'] = (v), \ 44 | ['F'] = (v), \ 45 | ['G'] = (v), \ 46 | ['H'] = (v), \ 47 | ['I'] = (v), \ 48 | ['J'] = (v), \ 49 | ['K'] = (v), \ 50 | ['L'] = (v), \ 51 | ['M'] = (v), \ 52 | ['N'] = (v), \ 53 | ['O'] = (v), \ 54 | ['P'] = (v), \ 55 | ['Q'] = (v), \ 56 | ['R'] = (v), \ 57 | ['S'] = (v), \ 58 | ['T'] = (v), \ 59 | ['U'] = (v), \ 60 | ['V'] = (v), \ 61 | ['W'] = (v), \ 62 | ['X'] = (v), \ 63 | ['Y'] = (v), \ 64 | ['Z'] = (v) 65 | 66 | #define ASSIGN_DIGIT(v) \ 67 | ['0'] = (v), \ 68 | ['1'] = (v), \ 69 | ['2'] = (v), \ 70 | ['3'] = (v), \ 71 | ['4'] = (v), \ 72 | ['5'] = (v), \ 73 | ['6'] = (v), \ 74 | ['7'] = (v), \ 75 | ['8'] = (v), \ 76 | ['9'] = (v) 77 | 78 | const score_t bonus_states[3][256] = { 79 | { 0 }, 80 | { 81 | ['/'] = SCORE_MATCH_SLASH, 82 | ['-'] = SCORE_MATCH_WORD, 83 | ['_'] = SCORE_MATCH_WORD, 84 | [' '] = SCORE_MATCH_WORD, 85 | ['.'] = SCORE_MATCH_DOT, 86 | }, 87 | { 88 | ['/'] = SCORE_MATCH_SLASH, 89 | ['-'] = SCORE_MATCH_WORD, 90 | ['_'] = SCORE_MATCH_WORD, 91 | [' '] = SCORE_MATCH_WORD, 92 | ['.'] = SCORE_MATCH_DOT, 93 | 94 | /* ['a' ... 'z'] = SCORE_MATCH_CAPITAL, */ 95 | ASSIGN_LOWER(SCORE_MATCH_CAPITAL) 96 | } 97 | }; 98 | 99 | const size_t bonus_index[256] = { 100 | /* ['A' ... 'Z'] = 2 */ 101 | ASSIGN_UPPER(2), 102 | 103 | /* ['a' ... 'z'] = 1 */ 104 | ASSIGN_LOWER(1), 105 | 106 | /* ['0' ... '9'] = 1 */ 107 | ASSIGN_DIGIT(1) 108 | }; 109 | 110 | #define COMPUTE_BONUS(last_ch, ch) (bonus_states[bonus_index[(unsigned char)(ch)]][(unsigned char)(last_ch)]) 111 | 112 | #ifdef __cplusplus 113 | } 114 | #endif 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /src/choices.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "options.h" 9 | #include "choices.h" 10 | #include "match.h" 11 | 12 | /* Initial size of buffer for storing input in memory */ 13 | #define INITIAL_BUFFER_CAPACITY 4096 14 | 15 | /* Initial size of choices array */ 16 | #define INITIAL_CHOICE_CAPACITY 128 17 | 18 | static int cmpchoice(const void *_idx1, const void *_idx2) { 19 | const struct scored_result *a = _idx1; 20 | const struct scored_result *b = _idx2; 21 | 22 | if (a->score == b->score) { 23 | /* To ensure a stable sort, we must also sort by the string 24 | * pointers. We can do this since we know all the strings are 25 | * from a contiguous memory segment (buffer in choices_t). 26 | */ 27 | if (a->str < b->str) { 28 | return -1; 29 | } else { 30 | return 1; 31 | } 32 | } else if (a->score < b->score) { 33 | return 1; 34 | } else { 35 | return -1; 36 | } 37 | } 38 | 39 | static void *safe_realloc(void *buffer, size_t size) { 40 | buffer = realloc(buffer, size); 41 | if (!buffer) { 42 | fprintf(stderr, "Error: Can't allocate memory (%zu bytes)\n", size); 43 | abort(); 44 | } 45 | 46 | return buffer; 47 | } 48 | 49 | void choices_fread(choices_t *c, FILE *file, char input_delimiter) { 50 | /* Save current position for parsing later */ 51 | size_t buffer_start = c->buffer_size; 52 | 53 | /* Resize buffer to at least one byte more capacity than our current 54 | * size. This uses a power of two of INITIAL_BUFFER_CAPACITY. 55 | * This must work even when c->buffer is NULL and c->buffer_size is 0 56 | */ 57 | size_t capacity = INITIAL_BUFFER_CAPACITY; 58 | while (capacity <= c->buffer_size) 59 | capacity *= 2; 60 | c->buffer = safe_realloc(c->buffer, capacity); 61 | 62 | /* Continue reading until we get a "short" read, indicating EOF */ 63 | while ((c->buffer_size += fread(c->buffer + c->buffer_size, 1, capacity - c->buffer_size, file)) == capacity) { 64 | capacity *= 2; 65 | c->buffer = safe_realloc(c->buffer, capacity); 66 | } 67 | c->buffer = safe_realloc(c->buffer, c->buffer_size + 1); 68 | c->buffer[c->buffer_size++] = '\0'; 69 | 70 | /* Truncate buffer to used size, (maybe) freeing some memory for 71 | * future allocations. 72 | */ 73 | 74 | /* Tokenize input and add to choices */ 75 | const char *line_end = c->buffer + c->buffer_size; 76 | char *line = c->buffer + buffer_start; 77 | do { 78 | char *nl = strchr(line, input_delimiter); 79 | if (nl) 80 | *nl++ = '\0'; 81 | 82 | /* Skip empty lines */ 83 | if (*line) 84 | choices_add(c, line); 85 | 86 | line = nl; 87 | } while (line && line < line_end); 88 | } 89 | 90 | static void choices_resize(choices_t *c, size_t new_capacity) { 91 | c->strings = safe_realloc(c->strings, new_capacity * sizeof(const char *)); 92 | c->capacity = new_capacity; 93 | } 94 | 95 | static void choices_reset_search(choices_t *c) { 96 | free(c->results); 97 | c->selection = c->available = 0; 98 | c->results = NULL; 99 | } 100 | 101 | void choices_init(choices_t *c, options_t *options) { 102 | c->strings = NULL; 103 | c->results = NULL; 104 | 105 | c->buffer_size = 0; 106 | c->buffer = NULL; 107 | 108 | c->capacity = c->size = 0; 109 | choices_resize(c, INITIAL_CHOICE_CAPACITY); 110 | 111 | if (options->workers) { 112 | c->worker_count = options->workers; 113 | } else { 114 | c->worker_count = (int)sysconf(_SC_NPROCESSORS_ONLN); 115 | } 116 | 117 | choices_reset_search(c); 118 | } 119 | 120 | void choices_destroy(choices_t *c) { 121 | free(c->buffer); 122 | c->buffer = NULL; 123 | c->buffer_size = 0; 124 | 125 | free(c->strings); 126 | c->strings = NULL; 127 | c->capacity = c->size = 0; 128 | 129 | free(c->results); 130 | c->results = NULL; 131 | c->available = c->selection = 0; 132 | } 133 | 134 | void choices_add(choices_t *c, const char *choice) { 135 | /* Previous search is now invalid */ 136 | choices_reset_search(c); 137 | 138 | if (c->size == c->capacity) { 139 | choices_resize(c, c->capacity * 2); 140 | } 141 | c->strings[c->size++] = choice; 142 | } 143 | 144 | size_t choices_available(choices_t *c) { 145 | return c->available; 146 | } 147 | 148 | #define BATCH_SIZE 512 149 | 150 | struct result_list { 151 | struct scored_result *list; 152 | size_t size; 153 | }; 154 | 155 | struct search_job { 156 | pthread_mutex_t lock; 157 | choices_t *choices; 158 | const char *search; 159 | size_t processed; 160 | struct worker *workers; 161 | }; 162 | 163 | struct worker { 164 | pthread_t thread_id; 165 | struct search_job *job; 166 | unsigned int worker_num; 167 | struct result_list result; 168 | }; 169 | 170 | static void worker_get_next_batch(struct search_job *job, size_t *start, size_t *end) { 171 | pthread_mutex_lock(&job->lock); 172 | 173 | *start = job->processed; 174 | 175 | job->processed += BATCH_SIZE; 176 | if (job->processed > job->choices->size) { 177 | job->processed = job->choices->size; 178 | } 179 | 180 | *end = job->processed; 181 | 182 | pthread_mutex_unlock(&job->lock); 183 | } 184 | 185 | static struct result_list merge2(struct result_list list1, struct result_list list2) { 186 | size_t result_index = 0, index1 = 0, index2 = 0; 187 | 188 | struct result_list result; 189 | result.size = list1.size + list2.size; 190 | result.list = malloc(result.size * sizeof(struct scored_result)); 191 | if (!result.list) { 192 | fprintf(stderr, "Error: Can't allocate memory\n"); 193 | abort(); 194 | } 195 | 196 | while(index1 < list1.size && index2 < list2.size) { 197 | if (cmpchoice(&list1.list[index1], &list2.list[index2]) < 0) { 198 | result.list[result_index++] = list1.list[index1++]; 199 | } else { 200 | result.list[result_index++] = list2.list[index2++]; 201 | } 202 | } 203 | 204 | while(index1 < list1.size) { 205 | result.list[result_index++] = list1.list[index1++]; 206 | } 207 | while(index2 < list2.size) { 208 | result.list[result_index++] = list2.list[index2++]; 209 | } 210 | 211 | free(list1.list); 212 | free(list2.list); 213 | 214 | return result; 215 | } 216 | 217 | static void *choices_search_worker(void *data) { 218 | struct worker *w = (struct worker *)data; 219 | struct search_job *job = w->job; 220 | const choices_t *c = job->choices; 221 | struct result_list *result = &w->result; 222 | 223 | size_t start, end; 224 | 225 | for(;;) { 226 | worker_get_next_batch(job, &start, &end); 227 | 228 | if(start == end) { 229 | break; 230 | } 231 | 232 | for(size_t i = start; i < end; i++) { 233 | if (has_match(job->search, c->strings[i])) { 234 | result->list[result->size].str = c->strings[i]; 235 | result->list[result->size].score = match(job->search, c->strings[i]); 236 | result->size++; 237 | } 238 | } 239 | } 240 | 241 | /* Sort the partial result */ 242 | qsort(result->list, result->size, sizeof(struct scored_result), cmpchoice); 243 | 244 | /* Fan-in, merging results */ 245 | for(unsigned int step = 0;; step++) { 246 | if (w->worker_num % (2 << step)) 247 | break; 248 | 249 | unsigned int next_worker = w->worker_num | (1 << step); 250 | if (next_worker >= c->worker_count) 251 | break; 252 | 253 | if ((errno = pthread_join(job->workers[next_worker].thread_id, NULL))) { 254 | perror("pthread_join"); 255 | exit(EXIT_FAILURE); 256 | } 257 | 258 | w->result = merge2(w->result, job->workers[next_worker].result); 259 | } 260 | 261 | return NULL; 262 | } 263 | 264 | void choices_search(choices_t *c, const char *search) { 265 | choices_reset_search(c); 266 | 267 | struct search_job *job = calloc(1, sizeof(struct search_job)); 268 | job->search = search; 269 | job->choices = c; 270 | if (pthread_mutex_init(&job->lock, NULL) != 0) { 271 | fprintf(stderr, "Error: pthread_mutex_init failed\n"); 272 | abort(); 273 | } 274 | job->workers = calloc(c->worker_count, sizeof(struct worker)); 275 | 276 | struct worker *workers = job->workers; 277 | for (int i = c->worker_count - 1; i >= 0; i--) { 278 | workers[i].job = job; 279 | workers[i].worker_num = i; 280 | workers[i].result.size = 0; 281 | workers[i].result.list = malloc(c->size * sizeof(struct scored_result)); /* FIXME: This is overkill */ 282 | 283 | /* These must be created last-to-first to avoid a race condition when fanning in */ 284 | if ((errno = pthread_create(&workers[i].thread_id, NULL, &choices_search_worker, &workers[i]))) { 285 | perror("pthread_create"); 286 | exit(EXIT_FAILURE); 287 | } 288 | } 289 | 290 | if (pthread_join(workers[0].thread_id, NULL)) { 291 | perror("pthread_join"); 292 | exit(EXIT_FAILURE); 293 | } 294 | 295 | c->results = workers[0].result.list; 296 | c->available = workers[0].result.size; 297 | 298 | free(workers); 299 | pthread_mutex_destroy(&job->lock); 300 | free(job); 301 | } 302 | 303 | const char *choices_get(choices_t *c, size_t n) { 304 | if (n < c->available) { 305 | return c->results[n].str; 306 | } else { 307 | return NULL; 308 | } 309 | } 310 | 311 | score_t choices_getscore(choices_t *c, size_t n) { 312 | return c->results[n].score; 313 | } 314 | 315 | void choices_prev(choices_t *c) { 316 | if (c->available) 317 | c->selection = (c->selection + c->available - 1) % c->available; 318 | } 319 | 320 | void choices_next(choices_t *c) { 321 | if (c->available) 322 | c->selection = (c->selection + 1) % c->available; 323 | } 324 | -------------------------------------------------------------------------------- /src/choices.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOICES_H 2 | #define CHOICES_H CHOICES_H 3 | 4 | #include 5 | 6 | #include "match.h" 7 | #include "options.h" 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | struct scored_result { 14 | score_t score; 15 | const char *str; 16 | }; 17 | 18 | typedef struct { 19 | char *buffer; 20 | size_t buffer_size; 21 | 22 | size_t capacity; 23 | size_t size; 24 | 25 | const char **strings; 26 | struct scored_result *results; 27 | 28 | size_t available; 29 | size_t selection; 30 | 31 | unsigned int worker_count; 32 | } choices_t; 33 | 34 | void choices_init(choices_t *c, options_t *options); 35 | void choices_fread(choices_t *c, FILE *file, char input_delimiter); 36 | void choices_destroy(choices_t *c); 37 | void choices_add(choices_t *c, const char *choice); 38 | size_t choices_available(choices_t *c); 39 | void choices_search(choices_t *c, const char *search); 40 | const char *choices_get(choices_t *c, size_t n); 41 | score_t choices_getscore(choices_t *c, size_t n); 42 | void choices_prev(choices_t *c); 43 | void choices_next(choices_t *c); 44 | 45 | #ifdef __cplusplus 46 | } 47 | #endif 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /src/config.def.h: -------------------------------------------------------------------------------- 1 | #ifdef __cplusplus 2 | extern "C" { 3 | #endif 4 | 5 | #define TTY_COLOR_HIGHLIGHT TTY_COLOR_YELLOW 6 | 7 | #define SCORE_GAP_LEADING -0.005 8 | #define SCORE_GAP_TRAILING -0.005 9 | #define SCORE_GAP_INNER -0.01 10 | #define SCORE_MATCH_CONSECUTIVE 1.0 11 | #define SCORE_MATCH_SLASH 0.9 12 | #define SCORE_MATCH_WORD 0.8 13 | #define SCORE_MATCH_CAPITAL 0.7 14 | #define SCORE_MATCH_DOT 0.6 15 | 16 | /* Time (in ms) to wait for additional bytes of an escape sequence */ 17 | #define KEYTIMEOUT 25 18 | 19 | #define DEFAULT_TTY "/dev/tty" 20 | #define DEFAULT_PROMPT "> " 21 | #define DEFAULT_NUM_LINES 10 22 | #define DEFAULT_WORKERS 0 23 | #define DEFAULT_SHOW_INFO 0 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /src/fzy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "match.h" 9 | #include "tty.h" 10 | #include "choices.h" 11 | #include "options.h" 12 | #include "tty_interface.h" 13 | 14 | #include "../config.h" 15 | 16 | int main(int argc, char *argv[]) { 17 | int ret = 0; 18 | 19 | options_t options; 20 | options_parse(&options, argc, argv); 21 | 22 | choices_t choices; 23 | choices_init(&choices, &options); 24 | 25 | if (options.benchmark) { 26 | if (!options.filter) { 27 | fprintf(stderr, "Must specify -e/--show-matches with --benchmark\n"); 28 | exit(EXIT_FAILURE); 29 | } 30 | choices_fread(&choices, stdin, options.input_delimiter); 31 | for (int i = 0; i < options.benchmark; i++) 32 | choices_search(&choices, options.filter); 33 | } else if (options.filter) { 34 | choices_fread(&choices, stdin, options.input_delimiter); 35 | choices_search(&choices, options.filter); 36 | for (size_t i = 0; i < choices_available(&choices); i++) { 37 | if (options.show_scores) 38 | printf("%f\t", choices_getscore(&choices, i)); 39 | printf("%s\n", choices_get(&choices, i)); 40 | } 41 | } else { 42 | /* interactive */ 43 | 44 | if (isatty(STDIN_FILENO)) 45 | choices_fread(&choices, stdin, options.input_delimiter); 46 | 47 | tty_t tty; 48 | tty_init(&tty, options.tty_filename); 49 | 50 | if (!isatty(STDIN_FILENO)) 51 | choices_fread(&choices, stdin, options.input_delimiter); 52 | 53 | if (options.num_lines > choices.size) 54 | options.num_lines = choices.size; 55 | 56 | int num_lines_adjustment = 1; 57 | if (options.show_info) 58 | num_lines_adjustment++; 59 | 60 | if (options.num_lines + num_lines_adjustment > tty_getheight(&tty)) 61 | options.num_lines = tty_getheight(&tty) - num_lines_adjustment; 62 | 63 | tty_interface_t tty_interface; 64 | tty_interface_init(&tty_interface, &tty, &choices, &options); 65 | ret = tty_interface_run(&tty_interface); 66 | } 67 | 68 | choices_destroy(&choices); 69 | 70 | return ret; 71 | } 72 | -------------------------------------------------------------------------------- /src/match.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "match.h" 10 | #include "bonus.h" 11 | 12 | #include "../config.h" 13 | 14 | char *strcasechr(const char *s, char c) { 15 | const char accept[3] = {c, toupper(c), 0}; 16 | return strpbrk(s, accept); 17 | } 18 | 19 | int has_match(const char *needle, const char *haystack) { 20 | while (*needle) { 21 | char nch = *needle++; 22 | 23 | if (!(haystack = strcasechr(haystack, nch))) { 24 | return 0; 25 | } 26 | haystack++; 27 | } 28 | return 1; 29 | } 30 | 31 | #define SWAP(x, y, T) do { T SWAP = x; x = y; y = SWAP; } while (0) 32 | 33 | #define max(a, b) (((a) > (b)) ? (a) : (b)) 34 | 35 | struct match_struct { 36 | int needle_len; 37 | int haystack_len; 38 | 39 | char lower_needle[MATCH_MAX_LEN]; 40 | char lower_haystack[MATCH_MAX_LEN]; 41 | 42 | score_t match_bonus[MATCH_MAX_LEN]; 43 | }; 44 | 45 | static void precompute_bonus(const char *haystack, score_t *match_bonus) { 46 | /* Which positions are beginning of words */ 47 | char last_ch = '/'; 48 | for (int i = 0; haystack[i]; i++) { 49 | char ch = haystack[i]; 50 | match_bonus[i] = COMPUTE_BONUS(last_ch, ch); 51 | last_ch = ch; 52 | } 53 | } 54 | 55 | static void setup_match_struct(struct match_struct *match, const char *needle, const char *haystack) { 56 | match->needle_len = strlen(needle); 57 | match->haystack_len = strlen(haystack); 58 | 59 | if (match->haystack_len > MATCH_MAX_LEN || match->needle_len > match->haystack_len) { 60 | return; 61 | } 62 | 63 | for (int i = 0; i < match->needle_len; i++) 64 | match->lower_needle[i] = tolower(needle[i]); 65 | 66 | for (int i = 0; i < match->haystack_len; i++) 67 | match->lower_haystack[i] = tolower(haystack[i]); 68 | 69 | precompute_bonus(haystack, match->match_bonus); 70 | } 71 | 72 | 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) { 73 | int n = match->needle_len; 74 | int m = match->haystack_len; 75 | int i = row; 76 | 77 | const char *lower_needle = match->lower_needle; 78 | const char *lower_haystack = match->lower_haystack; 79 | const score_t *match_bonus = match->match_bonus; 80 | 81 | score_t prev_score = SCORE_MIN; 82 | score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER; 83 | 84 | for (int j = 0; j < m; j++) { 85 | if (lower_needle[i] == lower_haystack[j]) { 86 | score_t score = SCORE_MIN; 87 | if (!i) { 88 | score = (j * SCORE_GAP_LEADING) + match_bonus[j]; 89 | } else if (j) { /* i > 0 && j > 0*/ 90 | score = max( 91 | last_M[j - 1] + match_bonus[j], 92 | 93 | /* consecutive match, doesn't stack with match_bonus */ 94 | last_D[j - 1] + SCORE_MATCH_CONSECUTIVE); 95 | } 96 | curr_D[j] = score; 97 | curr_M[j] = prev_score = max(score, prev_score + gap_score); 98 | } else { 99 | curr_D[j] = SCORE_MIN; 100 | curr_M[j] = prev_score = prev_score + gap_score; 101 | } 102 | } 103 | } 104 | 105 | score_t match(const char *needle, const char *haystack) { 106 | if (!*needle) 107 | return SCORE_MIN; 108 | 109 | struct match_struct match; 110 | setup_match_struct(&match, needle, haystack); 111 | 112 | int n = match.needle_len; 113 | int m = match.haystack_len; 114 | 115 | if (m > MATCH_MAX_LEN || n > m) { 116 | /* 117 | * Unreasonably large candidate: return no score 118 | * If it is a valid match it will still be returned, it will 119 | * just be ranked below any reasonably sized candidates 120 | */ 121 | return SCORE_MIN; 122 | } else if (n == m) { 123 | /* Since this method can only be called with a haystack which 124 | * matches needle. If the lengths of the strings are equal the 125 | * strings themselves must also be equal (ignoring case). 126 | */ 127 | return SCORE_MAX; 128 | } 129 | 130 | /* 131 | * D[][] Stores the best score for this position ending with a match. 132 | * M[][] Stores the best possible score at this position. 133 | */ 134 | score_t D[2][MATCH_MAX_LEN], M[2][MATCH_MAX_LEN]; 135 | 136 | score_t *last_D, *last_M; 137 | score_t *curr_D, *curr_M; 138 | 139 | last_D = D[0]; 140 | last_M = M[0]; 141 | curr_D = D[1]; 142 | curr_M = M[1]; 143 | 144 | for (int i = 0; i < n; i++) { 145 | match_row(&match, i, curr_D, curr_M, last_D, last_M); 146 | 147 | SWAP(curr_D, last_D, score_t *); 148 | SWAP(curr_M, last_M, score_t *); 149 | } 150 | 151 | return last_M[m - 1]; 152 | } 153 | 154 | score_t match_positions(const char *needle, const char *haystack, size_t *positions) { 155 | if (!*needle) 156 | return SCORE_MIN; 157 | 158 | struct match_struct match; 159 | setup_match_struct(&match, needle, haystack); 160 | 161 | int n = match.needle_len; 162 | int m = match.haystack_len; 163 | 164 | if (m > MATCH_MAX_LEN || n > m) { 165 | /* 166 | * Unreasonably large candidate: return no score 167 | * If it is a valid match it will still be returned, it will 168 | * just be ranked below any reasonably sized candidates 169 | */ 170 | return SCORE_MIN; 171 | } else if (n == m) { 172 | /* Since this method can only be called with a haystack which 173 | * matches needle. If the lengths of the strings are equal the 174 | * strings themselves must also be equal (ignoring case). 175 | */ 176 | if (positions) 177 | for (int i = 0; i < n; i++) 178 | positions[i] = i; 179 | return SCORE_MAX; 180 | } 181 | 182 | /* 183 | * D[][] Stores the best score for this position ending with a match. 184 | * M[][] Stores the best possible score at this position. 185 | */ 186 | score_t (*D)[MATCH_MAX_LEN], (*M)[MATCH_MAX_LEN]; 187 | M = malloc(sizeof(score_t) * MATCH_MAX_LEN * n); 188 | D = malloc(sizeof(score_t) * MATCH_MAX_LEN * n); 189 | 190 | score_t *last_D = NULL, *last_M = NULL; 191 | score_t *curr_D, *curr_M; 192 | 193 | for (int i = 0; i < n; i++) { 194 | curr_D = &D[i][0]; 195 | curr_M = &M[i][0]; 196 | 197 | match_row(&match, i, curr_D, curr_M, last_D, last_M); 198 | 199 | last_D = curr_D; 200 | last_M = curr_M; 201 | } 202 | 203 | /* backtrace to find the positions of optimal matching */ 204 | if (positions) { 205 | int match_required = 0; 206 | for (int i = n - 1, j = m - 1; i >= 0; i--) { 207 | for (; j >= 0; j--) { 208 | /* 209 | * There may be multiple paths which result in 210 | * the optimal weight. 211 | * 212 | * For simplicity, we will pick the first one 213 | * we encounter, the latest in the candidate 214 | * string. 215 | */ 216 | if (D[i][j] != SCORE_MIN && 217 | (match_required || D[i][j] == M[i][j])) { 218 | /* If this score was determined using 219 | * SCORE_MATCH_CONSECUTIVE, the 220 | * previous character MUST be a match 221 | */ 222 | match_required = 223 | i && j && 224 | M[i][j] == D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE; 225 | positions[i] = j--; 226 | break; 227 | } 228 | } 229 | } 230 | } 231 | 232 | score_t result = M[n - 1][m - 1]; 233 | 234 | free(M); 235 | free(D); 236 | 237 | return result; 238 | } 239 | -------------------------------------------------------------------------------- /src/match.h: -------------------------------------------------------------------------------- 1 | #ifndef MATCH_H 2 | #define MATCH_H MATCH_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef double score_t; 11 | #define SCORE_MAX INFINITY 12 | #define SCORE_MIN -INFINITY 13 | 14 | #define MATCH_MAX_LEN 1024 15 | 16 | int has_match(const char *needle, const char *haystack); 17 | score_t match_positions(const char *needle, const char *haystack, size_t *positions); 18 | score_t match(const char *needle, const char *haystack); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/options.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "options.h" 8 | 9 | #include "../config.h" 10 | 11 | static const char *usage_str = 12 | "" 13 | "Usage: fzy [OPTION]...\n" 14 | " -l, --lines=LINES Specify how many lines of results to show (default 10)\n" 15 | " -p, --prompt=PROMPT Input prompt (default '> ')\n" 16 | " -q, --query=QUERY Use QUERY as the initial search string\n" 17 | " -e, --show-matches=QUERY Output the sorted matches of QUERY\n" 18 | " -t, --tty=TTY Specify file to use as TTY device (default /dev/tty)\n" 19 | " -s, --show-scores Show the scores of each match\n" 20 | " -0, --read-null Read input delimited by ASCII NUL characters\n" 21 | " -j, --workers NUM Use NUM workers for searching. (default is # of CPUs)\n" 22 | " -i, --show-info Show selection info line\n" 23 | " -h, --help Display this help and exit\n" 24 | " -v, --version Output version information and exit\n"; 25 | 26 | static void usage(const char *argv0) { 27 | fprintf(stderr, usage_str, argv0); 28 | } 29 | 30 | static struct option longopts[] = {{"show-matches", required_argument, NULL, 'e'}, 31 | {"query", required_argument, NULL, 'q'}, 32 | {"lines", required_argument, NULL, 'l'}, 33 | {"tty", required_argument, NULL, 't'}, 34 | {"prompt", required_argument, NULL, 'p'}, 35 | {"show-scores", no_argument, NULL, 's'}, 36 | {"read-null", no_argument, NULL, '0'}, 37 | {"version", no_argument, NULL, 'v'}, 38 | {"benchmark", optional_argument, NULL, 'b'}, 39 | {"workers", required_argument, NULL, 'j'}, 40 | {"show-info", no_argument, NULL, 'i'}, 41 | {"help", no_argument, NULL, 'h'}, 42 | {NULL, 0, NULL, 0}}; 43 | 44 | void options_init(options_t *options) { 45 | /* set defaults */ 46 | options->benchmark = 0; 47 | options->filter = NULL; 48 | options->init_search = NULL; 49 | options->show_scores = 0; 50 | options->scrolloff = 1; 51 | options->tty_filename = DEFAULT_TTY; 52 | options->num_lines = DEFAULT_NUM_LINES; 53 | options->prompt = DEFAULT_PROMPT; 54 | options->workers = DEFAULT_WORKERS; 55 | options->input_delimiter = '\n'; 56 | options->show_info = DEFAULT_SHOW_INFO; 57 | } 58 | 59 | void options_parse(options_t *options, int argc, char *argv[]) { 60 | options_init(options); 61 | 62 | int c; 63 | while ((c = getopt_long(argc, argv, "vhs0e:q:l:t:p:j:i", longopts, NULL)) != -1) { 64 | switch (c) { 65 | case 'v': 66 | printf("%s " VERSION " © 2014-2018 John Hawthorn\n", argv[0]); 67 | exit(EXIT_SUCCESS); 68 | case 's': 69 | options->show_scores = 1; 70 | break; 71 | case '0': 72 | options->input_delimiter = '\0'; 73 | break; 74 | case 'q': 75 | options->init_search = optarg; 76 | break; 77 | case 'e': 78 | options->filter = optarg; 79 | break; 80 | case 'b': 81 | if (optarg) { 82 | if (sscanf(optarg, "%d", &options->benchmark) != 1) { 83 | usage(argv[0]); 84 | exit(EXIT_FAILURE); 85 | } 86 | } else { 87 | options->benchmark = 100; 88 | } 89 | break; 90 | case 't': 91 | options->tty_filename = optarg; 92 | break; 93 | case 'p': 94 | options->prompt = optarg; 95 | break; 96 | case 'j': 97 | if (sscanf(optarg, "%u", &options->workers) != 1) { 98 | usage(argv[0]); 99 | exit(EXIT_FAILURE); 100 | } 101 | break; 102 | case 'l': { 103 | int l; 104 | if (!strcmp(optarg, "max")) { 105 | l = INT_MAX; 106 | } else if (sscanf(optarg, "%d", &l) != 1 || l < 3) { 107 | fprintf(stderr, "Invalid format for --lines: %s\n", optarg); 108 | fprintf(stderr, "Must be integer in range 3..\n"); 109 | usage(argv[0]); 110 | exit(EXIT_FAILURE); 111 | } 112 | options->num_lines = l; 113 | } break; 114 | case 'i': 115 | options->show_info = 1; 116 | break; 117 | case 'h': 118 | default: 119 | usage(argv[0]); 120 | exit(EXIT_SUCCESS); 121 | } 122 | } 123 | if (optind != argc) { 124 | usage(argv[0]); 125 | exit(EXIT_FAILURE); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/options.h: -------------------------------------------------------------------------------- 1 | #ifndef OPTIONS_H 2 | #define OPTIONS_H OPTIONS_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | typedef struct { 9 | int benchmark; 10 | const char *filter; 11 | const char *init_search; 12 | const char *tty_filename; 13 | int show_scores; 14 | unsigned int num_lines; 15 | unsigned int scrolloff; 16 | const char *prompt; 17 | unsigned int workers; 18 | char input_delimiter; 19 | int show_info; 20 | } options_t; 21 | 22 | void options_init(options_t *options); 23 | void options_parse(options_t *options, int argc, char *argv[]); 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/tty.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "tty.h" 12 | 13 | #include "../config.h" 14 | 15 | void tty_reset(tty_t *tty) { 16 | tcsetattr(tty->fdin, TCSANOW, &tty->original_termios); 17 | } 18 | 19 | void tty_close(tty_t *tty) { 20 | tty_reset(tty); 21 | fclose(tty->fout); 22 | close(tty->fdin); 23 | } 24 | 25 | static void handle_sigwinch(int sig){ 26 | (void)sig; 27 | } 28 | 29 | void tty_init(tty_t *tty, const char *tty_filename) { 30 | tty->fdin = open(tty_filename, O_RDONLY); 31 | if (tty->fdin < 0) { 32 | perror("Failed to open tty"); 33 | exit(EXIT_FAILURE); 34 | } 35 | 36 | tty->fout = fopen(tty_filename, "w"); 37 | if (!tty->fout) { 38 | perror("Failed to open tty"); 39 | exit(EXIT_FAILURE); 40 | } 41 | 42 | if (setvbuf(tty->fout, NULL, _IOFBF, 4096)) { 43 | perror("setvbuf"); 44 | exit(EXIT_FAILURE); 45 | } 46 | 47 | if (tcgetattr(tty->fdin, &tty->original_termios)) { 48 | perror("tcgetattr"); 49 | exit(EXIT_FAILURE); 50 | } 51 | 52 | struct termios new_termios = tty->original_termios; 53 | 54 | /* 55 | * Disable all of 56 | * ICANON Canonical input (erase and kill processing). 57 | * ECHO Echo. 58 | * ISIG Signals from control characters 59 | * ICRNL Conversion of CR characters into NL 60 | */ 61 | new_termios.c_iflag &= ~(ICRNL); 62 | new_termios.c_lflag &= ~(ICANON | ECHO | ISIG); 63 | 64 | if (tcsetattr(tty->fdin, TCSANOW, &new_termios)) 65 | perror("tcsetattr"); 66 | 67 | tty_getwinsz(tty); 68 | 69 | tty_setnormal(tty); 70 | 71 | signal(SIGWINCH, handle_sigwinch); 72 | } 73 | 74 | void tty_getwinsz(tty_t *tty) { 75 | struct winsize ws; 76 | if (ioctl(fileno(tty->fout), TIOCGWINSZ, &ws) == -1) { 77 | tty->maxwidth = 80; 78 | tty->maxheight = 25; 79 | } else { 80 | tty->maxwidth = ws.ws_col; 81 | tty->maxheight = ws.ws_row; 82 | } 83 | } 84 | 85 | char tty_getchar(tty_t *tty) { 86 | char ch; 87 | int size = read(tty->fdin, &ch, 1); 88 | if (size < 0) { 89 | perror("error reading from tty"); 90 | exit(EXIT_FAILURE); 91 | } else if (size == 0) { 92 | /* EOF */ 93 | exit(EXIT_FAILURE); 94 | } else { 95 | return ch; 96 | } 97 | } 98 | 99 | int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal) { 100 | fd_set readfs; 101 | FD_ZERO(&readfs); 102 | FD_SET(tty->fdin, &readfs); 103 | 104 | struct timespec ts = {timeout / 1000, (timeout % 1000) * 1000000}; 105 | 106 | sigset_t mask; 107 | sigemptyset(&mask); 108 | if (!return_on_signal) 109 | sigaddset(&mask, SIGWINCH); 110 | 111 | int err = pselect( 112 | tty->fdin + 1, 113 | &readfs, 114 | NULL, 115 | NULL, 116 | timeout < 0 ? NULL : &ts, 117 | return_on_signal ? NULL : &mask); 118 | 119 | if (err < 0) { 120 | if (errno == EINTR) { 121 | return 0; 122 | } else { 123 | perror("select"); 124 | exit(EXIT_FAILURE); 125 | } 126 | } else { 127 | return FD_ISSET(tty->fdin, &readfs); 128 | } 129 | } 130 | 131 | static void tty_sgr(tty_t *tty, int code) { 132 | tty_printf(tty, "%c%c%im", 0x1b, '[', code); 133 | } 134 | 135 | void tty_setfg(tty_t *tty, int fg) { 136 | if (tty->fgcolor != fg) { 137 | tty_sgr(tty, 30 + fg); 138 | tty->fgcolor = fg; 139 | } 140 | } 141 | 142 | void tty_setinvert(tty_t *tty) { 143 | tty_sgr(tty, 7); 144 | } 145 | 146 | void tty_setunderline(tty_t *tty) { 147 | tty_sgr(tty, 4); 148 | } 149 | 150 | void tty_setnormal(tty_t *tty) { 151 | tty_sgr(tty, 0); 152 | tty->fgcolor = 9; 153 | } 154 | 155 | void tty_setnowrap(tty_t *tty) { 156 | tty_printf(tty, "%c%c?7l", 0x1b, '['); 157 | } 158 | 159 | void tty_setwrap(tty_t *tty) { 160 | tty_printf(tty, "%c%c?7h", 0x1b, '['); 161 | } 162 | 163 | void tty_newline(tty_t *tty) { 164 | tty_printf(tty, "%c%cK\n", 0x1b, '['); 165 | } 166 | 167 | void tty_clearline(tty_t *tty) { 168 | tty_printf(tty, "%c%cK", 0x1b, '['); 169 | } 170 | 171 | void tty_setcol(tty_t *tty, int col) { 172 | tty_printf(tty, "%c%c%iG", 0x1b, '[', col + 1); 173 | } 174 | 175 | void tty_moveup(tty_t *tty, int i) { 176 | tty_printf(tty, "%c%c%iA", 0x1b, '[', i); 177 | } 178 | 179 | void tty_printf(tty_t *tty, const char *fmt, ...) { 180 | va_list args; 181 | va_start(args, fmt); 182 | vfprintf(tty->fout, fmt, args); 183 | va_end(args); 184 | } 185 | 186 | void tty_putc(tty_t *tty, char c) { 187 | fputc(c, tty->fout); 188 | } 189 | 190 | void tty_flush(tty_t *tty) { 191 | fflush(tty->fout); 192 | } 193 | 194 | size_t tty_getwidth(tty_t *tty) { 195 | return tty->maxwidth; 196 | } 197 | 198 | size_t tty_getheight(tty_t *tty) { 199 | return tty->maxheight; 200 | } 201 | -------------------------------------------------------------------------------- /src/tty.h: -------------------------------------------------------------------------------- 1 | #ifndef TTY_H 2 | #define TTY_H TTY_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | typedef struct { 13 | int fdin; 14 | FILE *fout; 15 | struct termios original_termios; 16 | int fgcolor; 17 | size_t maxwidth; 18 | size_t maxheight; 19 | } tty_t; 20 | 21 | void tty_reset(tty_t *tty); 22 | void tty_close(tty_t *tty); 23 | void tty_init(tty_t *tty, const char *tty_filename); 24 | void tty_getwinsz(tty_t *tty); 25 | char tty_getchar(tty_t *tty); 26 | int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal); 27 | 28 | void tty_setfg(tty_t *tty, int fg); 29 | void tty_setinvert(tty_t *tty); 30 | void tty_setunderline(tty_t *tty); 31 | void tty_setnormal(tty_t *tty); 32 | void tty_setnowrap(tty_t *tty); 33 | void tty_setwrap(tty_t *tty); 34 | 35 | #define TTY_COLOR_BLACK 0 36 | #define TTY_COLOR_RED 1 37 | #define TTY_COLOR_GREEN 2 38 | #define TTY_COLOR_YELLOW 3 39 | #define TTY_COLOR_BLUE 4 40 | #define TTY_COLOR_MAGENTA 5 41 | #define TTY_COLOR_CYAN 6 42 | #define TTY_COLOR_WHITE 7 43 | #define TTY_COLOR_NORMAL 9 44 | 45 | /* tty_newline 46 | * Move cursor to the beginning of the next line, clearing to the end of the 47 | * current line 48 | */ 49 | void tty_newline(tty_t *tty); 50 | 51 | /* tty_clearline 52 | * Clear to the end of the current line without advancing the cursor. 53 | */ 54 | void tty_clearline(tty_t *tty); 55 | 56 | void tty_moveup(tty_t *tty, int i); 57 | void tty_setcol(tty_t *tty, int col); 58 | 59 | void tty_printf(tty_t *tty, const char *fmt, ...); 60 | void tty_putc(tty_t *tty, char c); 61 | void tty_flush(tty_t *tty); 62 | 63 | size_t tty_getwidth(tty_t *tty); 64 | size_t tty_getheight(tty_t *tty); 65 | 66 | #ifdef __cplusplus 67 | } 68 | #endif 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /src/tty_interface.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "match.h" 7 | #include "tty_interface.h" 8 | #include "../config.h" 9 | 10 | static int isprint_unicode(char c) { 11 | return isprint(c) || c & (1 << 7); 12 | } 13 | 14 | static int is_boundary(char c) { 15 | return ~c & (1 << 7) || c & (1 << 6); 16 | } 17 | 18 | static void clear(tty_interface_t *state) { 19 | tty_t *tty = state->tty; 20 | 21 | tty_setcol(tty, 0); 22 | size_t line = 0; 23 | while (line++ < state->options->num_lines + (state->options->show_info ? 1 : 0)) { 24 | tty_newline(tty); 25 | } 26 | tty_clearline(tty); 27 | if (state->options->num_lines > 0) { 28 | tty_moveup(tty, line - 1); 29 | } 30 | tty_flush(tty); 31 | } 32 | 33 | static void draw_match(tty_interface_t *state, const char *choice, int selected) { 34 | tty_t *tty = state->tty; 35 | options_t *options = state->options; 36 | char *search = state->last_search; 37 | 38 | int n = strlen(search); 39 | size_t positions[MATCH_MAX_LEN]; 40 | for (int i = 0; i < n + 1 && i < MATCH_MAX_LEN; i++) 41 | positions[i] = -1; 42 | 43 | score_t score = match_positions(search, choice, &positions[0]); 44 | 45 | if (options->show_scores) { 46 | if (score == SCORE_MIN) { 47 | tty_printf(tty, "( ) "); 48 | } else { 49 | tty_printf(tty, "(%5.2f) ", score); 50 | } 51 | } 52 | 53 | if (selected) 54 | #ifdef TTY_SELECTION_UNDERLINE 55 | tty_setunderline(tty); 56 | #else 57 | tty_setinvert(tty); 58 | #endif 59 | 60 | tty_setnowrap(tty); 61 | for (size_t i = 0, p = 0; choice[i] != '\0'; i++) { 62 | if (positions[p] == i) { 63 | tty_setfg(tty, TTY_COLOR_HIGHLIGHT); 64 | p++; 65 | } else { 66 | tty_setfg(tty, TTY_COLOR_NORMAL); 67 | } 68 | if (choice[i] == '\n') { 69 | tty_putc(tty, ' '); 70 | } else { 71 | tty_printf(tty, "%c", choice[i]); 72 | } 73 | } 74 | tty_setwrap(tty); 75 | tty_setnormal(tty); 76 | } 77 | 78 | static void draw(tty_interface_t *state) { 79 | tty_t *tty = state->tty; 80 | choices_t *choices = state->choices; 81 | options_t *options = state->options; 82 | 83 | unsigned int num_lines = options->num_lines; 84 | size_t start = 0; 85 | size_t current_selection = choices->selection; 86 | if (current_selection + options->scrolloff >= num_lines) { 87 | start = current_selection + options->scrolloff - num_lines + 1; 88 | size_t available = choices_available(choices); 89 | if (start + num_lines >= available && available > 0) { 90 | start = available - num_lines; 91 | } 92 | } 93 | 94 | tty_setcol(tty, 0); 95 | tty_printf(tty, "%s%s", options->prompt, state->search); 96 | tty_clearline(tty); 97 | 98 | if (options->show_info) { 99 | tty_printf(tty, "\n[%lu/%lu]", choices->available, choices->size); 100 | tty_clearline(tty); 101 | } 102 | 103 | for (size_t i = start; i < start + num_lines; i++) { 104 | tty_printf(tty, "\n"); 105 | tty_clearline(tty); 106 | const char *choice = choices_get(choices, i); 107 | if (choice) { 108 | draw_match(state, choice, i == choices->selection); 109 | } 110 | } 111 | 112 | if (num_lines + options->show_info) 113 | tty_moveup(tty, num_lines + options->show_info); 114 | 115 | tty_setcol(tty, 0); 116 | fputs(options->prompt, tty->fout); 117 | for (size_t i = 0; i < state->cursor; i++) 118 | fputc(state->search[i], tty->fout); 119 | tty_flush(tty); 120 | } 121 | 122 | static void update_search(tty_interface_t *state) { 123 | choices_search(state->choices, state->search); 124 | strcpy(state->last_search, state->search); 125 | } 126 | 127 | static void update_state(tty_interface_t *state) { 128 | if (strcmp(state->last_search, state->search)) { 129 | update_search(state); 130 | draw(state); 131 | } 132 | } 133 | 134 | static void action_emit(tty_interface_t *state) { 135 | update_state(state); 136 | 137 | /* Reset the tty as close as possible to the previous state */ 138 | clear(state); 139 | 140 | /* ttyout should be flushed before outputting on stdout */ 141 | tty_close(state->tty); 142 | 143 | const char *selection = choices_get(state->choices, state->choices->selection); 144 | if (selection) { 145 | /* output the selected result */ 146 | printf("%s\n", selection); 147 | } else { 148 | /* No match, output the query instead */ 149 | printf("%s\n", state->search); 150 | } 151 | 152 | state->exit = EXIT_SUCCESS; 153 | } 154 | 155 | static void action_del_char(tty_interface_t *state) { 156 | size_t length = strlen(state->search); 157 | if (state->cursor == 0) { 158 | return; 159 | } 160 | size_t original_cursor = state->cursor; 161 | 162 | do { 163 | state->cursor--; 164 | } while (!is_boundary(state->search[state->cursor]) && state->cursor); 165 | 166 | memmove(&state->search[state->cursor], &state->search[original_cursor], length - original_cursor + 1); 167 | } 168 | 169 | static void action_del_word(tty_interface_t *state) { 170 | size_t original_cursor = state->cursor; 171 | size_t cursor = state->cursor; 172 | 173 | while (cursor && isspace(state->search[cursor - 1])) 174 | cursor--; 175 | 176 | while (cursor && !isspace(state->search[cursor - 1])) 177 | cursor--; 178 | 179 | memmove(&state->search[cursor], &state->search[original_cursor], strlen(state->search) - original_cursor + 1); 180 | state->cursor = cursor; 181 | } 182 | 183 | static void action_del_all(tty_interface_t *state) { 184 | memmove(state->search, &state->search[state->cursor], strlen(state->search) - state->cursor + 1); 185 | state->cursor = 0; 186 | } 187 | 188 | static void action_prev(tty_interface_t *state) { 189 | update_state(state); 190 | choices_prev(state->choices); 191 | } 192 | 193 | static void action_ignore(tty_interface_t *state) { 194 | (void)state; 195 | } 196 | 197 | static void action_next(tty_interface_t *state) { 198 | update_state(state); 199 | choices_next(state->choices); 200 | } 201 | 202 | static void action_left(tty_interface_t *state) { 203 | if (state->cursor > 0) { 204 | state->cursor--; 205 | while (!is_boundary(state->search[state->cursor]) && state->cursor) 206 | state->cursor--; 207 | } 208 | } 209 | 210 | static void action_right(tty_interface_t *state) { 211 | if (state->cursor < strlen(state->search)) { 212 | state->cursor++; 213 | while (!is_boundary(state->search[state->cursor])) 214 | state->cursor++; 215 | } 216 | } 217 | 218 | static void action_beginning(tty_interface_t *state) { 219 | state->cursor = 0; 220 | } 221 | 222 | static void action_end(tty_interface_t *state) { 223 | state->cursor = strlen(state->search); 224 | } 225 | 226 | static void action_pageup(tty_interface_t *state) { 227 | update_state(state); 228 | for (size_t i = 0; i < state->options->num_lines && state->choices->selection > 0; i++) 229 | choices_prev(state->choices); 230 | } 231 | 232 | static void action_pagedown(tty_interface_t *state) { 233 | update_state(state); 234 | for (size_t i = 0; i < state->options->num_lines && state->choices->selection < state->choices->available - 1; i++) 235 | choices_next(state->choices); 236 | } 237 | 238 | static void action_autocomplete(tty_interface_t *state) { 239 | update_state(state); 240 | const char *current_selection = choices_get(state->choices, state->choices->selection); 241 | if (current_selection) { 242 | strncpy(state->search, choices_get(state->choices, state->choices->selection), SEARCH_SIZE_MAX); 243 | state->cursor = strlen(state->search); 244 | } 245 | } 246 | 247 | static void action_exit(tty_interface_t *state) { 248 | clear(state); 249 | tty_close(state->tty); 250 | 251 | state->exit = EXIT_FAILURE; 252 | } 253 | 254 | static void append_search(tty_interface_t *state, char ch) { 255 | char *search = state->search; 256 | size_t search_size = strlen(search); 257 | if (search_size < SEARCH_SIZE_MAX) { 258 | memmove(&search[state->cursor+1], &search[state->cursor], search_size - state->cursor + 1); 259 | search[state->cursor] = ch; 260 | 261 | state->cursor++; 262 | } 263 | } 264 | 265 | void tty_interface_init(tty_interface_t *state, tty_t *tty, choices_t *choices, options_t *options) { 266 | state->tty = tty; 267 | state->choices = choices; 268 | state->options = options; 269 | state->ambiguous_key_pending = 0; 270 | 271 | strcpy(state->input, ""); 272 | strcpy(state->search, ""); 273 | strcpy(state->last_search, ""); 274 | 275 | state->exit = -1; 276 | 277 | if (options->init_search) 278 | strncpy(state->search, options->init_search, SEARCH_SIZE_MAX); 279 | 280 | state->cursor = strlen(state->search); 281 | 282 | update_search(state); 283 | } 284 | 285 | typedef struct { 286 | const char *key; 287 | void (*action)(tty_interface_t *); 288 | } keybinding_t; 289 | 290 | #define KEY_CTRL(key) ((const char[]){((key) - ('@')), '\0'}) 291 | 292 | static const keybinding_t keybindings[] = {{"\x1b", action_exit}, /* ESC */ 293 | {"\x7f", action_del_char}, /* DEL */ 294 | 295 | {KEY_CTRL('H'), action_del_char}, /* Backspace (C-H) */ 296 | {KEY_CTRL('W'), action_del_word}, /* C-W */ 297 | {KEY_CTRL('U'), action_del_all}, /* C-U */ 298 | {KEY_CTRL('I'), action_autocomplete}, /* TAB (C-I ) */ 299 | {KEY_CTRL('C'), action_exit}, /* C-C */ 300 | {KEY_CTRL('D'), action_exit}, /* C-D */ 301 | {KEY_CTRL('G'), action_exit}, /* C-G */ 302 | {KEY_CTRL('M'), action_emit}, /* CR */ 303 | {KEY_CTRL('P'), action_prev}, /* C-P */ 304 | {KEY_CTRL('N'), action_next}, /* C-N */ 305 | {KEY_CTRL('K'), action_prev}, /* C-K */ 306 | {KEY_CTRL('J'), action_next}, /* C-J */ 307 | {KEY_CTRL('A'), action_beginning}, /* C-A */ 308 | {KEY_CTRL('E'), action_end}, /* C-E */ 309 | 310 | {"\x1bOD", action_left}, /* LEFT */ 311 | {"\x1b[D", action_left}, /* LEFT */ 312 | {"\x1bOC", action_right}, /* RIGHT */ 313 | {"\x1b[C", action_right}, /* RIGHT */ 314 | {"\x1b[1~", action_beginning}, /* HOME */ 315 | {"\x1b[H", action_beginning}, /* HOME */ 316 | {"\x1b[4~", action_end}, /* END */ 317 | {"\x1b[F", action_end}, /* END */ 318 | {"\x1b[A", action_prev}, /* UP */ 319 | {"\x1bOA", action_prev}, /* UP */ 320 | {"\x1b[B", action_next}, /* DOWN */ 321 | {"\x1bOB", action_next}, /* DOWN */ 322 | {"\x1b[5~", action_pageup}, 323 | {"\x1b[6~", action_pagedown}, 324 | {"\x1b[200~", action_ignore}, 325 | {"\x1b[201~", action_ignore}, 326 | {NULL, NULL}}; 327 | 328 | #undef KEY_CTRL 329 | 330 | static void handle_input(tty_interface_t *state, const char *s, int handle_ambiguous_key) { 331 | state->ambiguous_key_pending = 0; 332 | 333 | char *input = state->input; 334 | strcat(state->input, s); 335 | 336 | /* Figure out if we have completed a keybinding and whether we're in the 337 | * middle of one (both can happen, because of Esc). */ 338 | int found_keybinding = -1; 339 | int in_middle = 0; 340 | for (int i = 0; keybindings[i].key; i++) { 341 | if (!strcmp(input, keybindings[i].key)) 342 | found_keybinding = i; 343 | else if (!strncmp(input, keybindings[i].key, strlen(state->input))) 344 | in_middle = 1; 345 | } 346 | 347 | /* If we have an unambiguous keybinding, run it. */ 348 | if (found_keybinding != -1 && (!in_middle || handle_ambiguous_key)) { 349 | keybindings[found_keybinding].action(state); 350 | strcpy(input, ""); 351 | return; 352 | } 353 | 354 | /* We could have a complete keybinding, or could be in the middle of one. 355 | * We'll need to wait a few milliseconds to find out. */ 356 | if (found_keybinding != -1 && in_middle) { 357 | state->ambiguous_key_pending = 1; 358 | return; 359 | } 360 | 361 | /* Wait for more if we are in the middle of a keybinding */ 362 | if (in_middle) 363 | return; 364 | 365 | /* No matching keybinding, add to search */ 366 | for (int i = 0; input[i]; i++) 367 | if (isprint_unicode(input[i])) 368 | append_search(state, input[i]); 369 | 370 | /* We have processed the input, so clear it */ 371 | strcpy(input, ""); 372 | } 373 | 374 | int tty_interface_run(tty_interface_t *state) { 375 | draw(state); 376 | 377 | for (;;) { 378 | do { 379 | while(!tty_input_ready(state->tty, -1, 1)) { 380 | /* We received a signal (probably WINCH) */ 381 | draw(state); 382 | } 383 | 384 | char s[2] = {tty_getchar(state->tty), '\0'}; 385 | handle_input(state, s, 0); 386 | 387 | if (state->exit >= 0) 388 | return state->exit; 389 | 390 | draw(state); 391 | } while (tty_input_ready(state->tty, state->ambiguous_key_pending ? KEYTIMEOUT : 0, 0)); 392 | 393 | if (state->ambiguous_key_pending) { 394 | char s[1] = ""; 395 | handle_input(state, s, 1); 396 | 397 | if (state->exit >= 0) 398 | return state->exit; 399 | } 400 | 401 | update_state(state); 402 | } 403 | 404 | return state->exit; 405 | } 406 | -------------------------------------------------------------------------------- /src/tty_interface.h: -------------------------------------------------------------------------------- 1 | #ifndef TTY_INTERFACE_H 2 | #define TTY_INTERFACE_H TTY_INTERFACE_H 3 | 4 | #include "choices.h" 5 | #include "options.h" 6 | #include "tty.h" 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | #define SEARCH_SIZE_MAX 4096 13 | 14 | typedef struct { 15 | tty_t *tty; 16 | choices_t *choices; 17 | options_t *options; 18 | 19 | char search[SEARCH_SIZE_MAX + 1]; 20 | char last_search[SEARCH_SIZE_MAX + 1]; 21 | size_t cursor; 22 | 23 | int ambiguous_key_pending; 24 | char input[32]; /* Pending input buffer */ 25 | 26 | int exit; 27 | } tty_interface_t; 28 | 29 | void tty_interface_init(tty_interface_t *state, tty_t *tty, choices_t *choices, options_t *options); 30 | int tty_interface_run(tty_interface_t *state); 31 | 32 | #ifdef __cplusplus 33 | } 34 | #endif 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /test/acceptance/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'ttytest' 3 | gem 'minitest' 4 | -------------------------------------------------------------------------------- /test/acceptance/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | minitest (5.10.1) 5 | ttytest (0.4.0) 6 | 7 | PLATFORMS 8 | ruby 9 | 10 | DEPENDENCIES 11 | minitest 12 | ttytest 13 | 14 | BUNDLED WITH 15 | 1.13.7 16 | -------------------------------------------------------------------------------- /test/acceptance/acceptance_test.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'minitest' 3 | require 'minitest/autorun' 4 | require 'ttytest' 5 | 6 | class FzyTest < Minitest::Test 7 | FZY_PATH = File.expand_path('../../../fzy', __FILE__) 8 | 9 | LEFT = "\e[D" 10 | RIGHT = "\e[C" 11 | 12 | def test_empty_list 13 | @tty = interactive_fzy(input: %w[], before: "placeholder") 14 | @tty.assert_cursor_position(y: 1, x: 2) 15 | @tty.assert_matches <<~TTY 16 | placeholder 17 | > 18 | TTY 19 | 20 | @tty.send_keys('t') 21 | @tty.assert_cursor_position(y: 1, x: 3) 22 | @tty.assert_matches <<~TTY 23 | placeholder 24 | > t 25 | TTY 26 | 27 | @tty.send_keys('z') 28 | @tty.assert_cursor_position(y: 1, x: 4) 29 | @tty.assert_matches <<~TTY 30 | placeholder 31 | > tz 32 | TTY 33 | 34 | @tty.send_keys("\r") 35 | @tty.assert_cursor_position(y: 2, x: 0) 36 | @tty.assert_matches <<~TTY 37 | placeholder 38 | tz 39 | TTY 40 | end 41 | 42 | def test_one_item 43 | @tty = interactive_fzy(input: %w[test], before: "placeholder") 44 | @tty.assert_matches <<~TTY 45 | placeholder 46 | > 47 | test 48 | TTY 49 | @tty.assert_cursor_position(y: 1, x: 2) 50 | 51 | @tty.send_keys('t') 52 | @tty.assert_cursor_position(y: 1, x: 3) 53 | @tty.assert_matches <<~TTY 54 | placeholder 55 | > t 56 | test 57 | TTY 58 | 59 | @tty.send_keys('z') 60 | @tty.assert_cursor_position(y: 1, x: 4) 61 | @tty.assert_matches <<~TTY 62 | placeholder 63 | > tz 64 | TTY 65 | 66 | @tty.send_keys("\r") 67 | @tty.assert_cursor_position(y: 2, x: 0) 68 | @tty.assert_matches <<~TTY 69 | placeholder 70 | tz 71 | TTY 72 | end 73 | 74 | def test_two_items 75 | @tty = interactive_fzy(input: %w[test foo], before: "placeholder") 76 | @tty.assert_cursor_position(y: 1, x: 2) 77 | @tty.assert_matches <<~TTY 78 | placeholder 79 | > 80 | test 81 | foo 82 | TTY 83 | 84 | @tty.send_keys('t') 85 | @tty.assert_cursor_position(y: 1, x: 3) 86 | @tty.assert_matches <<~TTY 87 | placeholder 88 | > t 89 | test 90 | TTY 91 | 92 | @tty.send_keys('z') 93 | @tty.assert_cursor_position(y: 1, x: 4) 94 | @tty.assert_matches <<~TTY 95 | placeholder 96 | > tz 97 | TTY 98 | 99 | @tty.send_keys("\r") 100 | @tty.assert_matches <<~TTY 101 | placeholder 102 | tz 103 | TTY 104 | @tty.assert_cursor_position(y: 2, x: 0) 105 | end 106 | 107 | def ctrl(key) 108 | ((key.upcase.ord) - ('A'.ord) + 1).chr 109 | end 110 | 111 | def test_editing 112 | @tty = interactive_fzy(input: %w[test foo], before: "placeholder") 113 | @tty.assert_cursor_position(y: 1, x: 2) 114 | @tty.assert_matches <<~TTY 115 | placeholder 116 | > 117 | test 118 | foo 119 | TTY 120 | 121 | @tty.send_keys("foo bar baz") 122 | @tty.assert_cursor_position(y: 1, x: 13) 123 | @tty.assert_matches <<~TTY 124 | placeholder 125 | > foo bar baz 126 | TTY 127 | 128 | @tty.send_keys(ctrl('H')) 129 | @tty.assert_cursor_position(y: 1, x: 12) 130 | @tty.assert_matches <<~TTY 131 | placeholder 132 | > foo bar ba 133 | TTY 134 | 135 | @tty.send_keys(ctrl('W')) 136 | @tty.assert_cursor_position(y: 1, x: 10) 137 | @tty.assert_matches <<~TTY 138 | placeholder 139 | > foo bar 140 | TTY 141 | 142 | @tty.send_keys(ctrl('U')) 143 | @tty.assert_cursor_position(y: 1, x: 2) 144 | @tty.assert_matches <<~TTY 145 | placeholder 146 | > 147 | test 148 | foo 149 | TTY 150 | end 151 | 152 | def test_ctrl_d 153 | @tty = interactive_fzy(input: %w[foo bar]) 154 | @tty.assert_matches ">\nfoo\nbar" 155 | 156 | @tty.send_keys('foo') 157 | @tty.assert_matches "> foo\nfoo" 158 | 159 | @tty.send_keys(ctrl('D')) 160 | @tty.assert_matches '' 161 | @tty.assert_cursor_position(y: 0, x: 0) 162 | end 163 | 164 | def test_ctrl_c 165 | @tty = interactive_fzy(input: %w[foo bar]) 166 | @tty.assert_matches ">\nfoo\nbar" 167 | 168 | @tty.send_keys('foo') 169 | @tty.assert_matches "> foo\nfoo" 170 | 171 | @tty.send_keys(ctrl('C')) 172 | @tty.assert_matches '' 173 | @tty.assert_cursor_position(y: 0, x: 0) 174 | end 175 | 176 | def test_down_arrow 177 | @tty = interactive_fzy(input: %w[foo bar]) 178 | @tty.assert_matches ">\nfoo\nbar" 179 | @tty.send_keys("\e[A\r") 180 | @tty.assert_matches "bar" 181 | 182 | @tty = interactive_fzy(input: %w[foo bar]) 183 | @tty.assert_matches ">\nfoo\nbar" 184 | @tty.send_keys("\eOA\r") 185 | @tty.assert_matches "bar" 186 | end 187 | 188 | def test_up_arrow 189 | @tty = interactive_fzy(input: %w[foo bar]) 190 | @tty.assert_matches ">\nfoo\nbar" 191 | @tty.send_keys("\e[A") # first down 192 | @tty.send_keys("\e[B\r") # and back up 193 | @tty.assert_matches "foo" 194 | 195 | @tty = interactive_fzy(input: %w[foo bar]) 196 | @tty.assert_matches ">\nfoo\nbar" 197 | @tty.send_keys("\eOA") # first down 198 | @tty.send_keys("\e[B\r") # and back up 199 | @tty.assert_matches "foo" 200 | end 201 | 202 | def test_lines 203 | input10 = (1..10).map(&:to_s) 204 | input20 = (1..20).map(&:to_s) 205 | 206 | @tty = interactive_fzy(input: input10) 207 | @tty.assert_matches ">\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10" 208 | 209 | @tty = interactive_fzy(input: input20) 210 | @tty.assert_matches ">\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10" 211 | 212 | @tty = interactive_fzy(input: input10, args: "-l 5") 213 | @tty.assert_matches ">\n1\n2\n3\n4\n5" 214 | 215 | @tty = interactive_fzy(input: input10, args: "--lines=5") 216 | @tty.assert_matches ">\n1\n2\n3\n4\n5" 217 | end 218 | 219 | def test_prompt 220 | @tty = interactive_fzy 221 | @tty.send_keys("foo") 222 | @tty.assert_matches '> foo' 223 | 224 | @tty = interactive_fzy(args: "-p 'C:\\'") 225 | @tty.send_keys("foo") 226 | @tty.assert_matches 'C:\foo' 227 | 228 | @tty = interactive_fzy(args: "--prompt=\"foo bar \"") 229 | @tty.send_keys("baz") 230 | @tty.assert_matches "foo bar baz" 231 | end 232 | 233 | def test_show_scores 234 | expected_score = '( inf)' 235 | @tty = interactive_fzy(input: %w[foo bar], args: "-s") 236 | @tty.send_keys('foo') 237 | @tty.assert_matches "> foo\n#{expected_score} foo" 238 | 239 | @tty = interactive_fzy(input: %w[foo bar], args: "--show-scores") 240 | @tty.send_keys('foo') 241 | @tty.assert_matches "> foo\n#{expected_score} foo" 242 | 243 | expected_score = '( 0.89)' 244 | @tty = interactive_fzy(input: %w[foo bar], args: "-s") 245 | @tty.send_keys('f') 246 | @tty.assert_matches "> f\n#{expected_score} foo" 247 | end 248 | 249 | def test_large_input 250 | @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -l 3}) 251 | @tty.send_keys('34') 252 | @tty.assert_matches "> 34\n34\n340\n341" 253 | 254 | @tty.send_keys('5') 255 | @tty.assert_matches "> 345\n345\n3450\n3451" 256 | 257 | @tty.send_keys('z') 258 | @tty.assert_matches "> 345z" 259 | end 260 | 261 | def test_worker_count 262 | @tty = interactive_fzy(input: %w[foo bar], args: "-j1") 263 | @tty.send_keys('foo') 264 | @tty.assert_matches "> foo\nfoo" 265 | 266 | @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -j1 -l3}) 267 | @tty.send_keys('34') 268 | @tty.assert_matches "> 34\n34\n340\n341" 269 | 270 | @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -j200 -l3}) 271 | @tty.send_keys('34') 272 | @tty.assert_matches "> 34\n34\n340\n341" 273 | end 274 | 275 | def test_initial_query 276 | @tty = interactive_fzy(input: %w[foo bar], args: "-q fo") 277 | @tty.assert_matches "> fo\nfoo" 278 | @tty.send_keys("o") 279 | @tty.assert_matches "> foo\nfoo" 280 | @tty.send_keys("o") 281 | @tty.assert_matches "> fooo" 282 | 283 | @tty = interactive_fzy(input: %w[foo bar], args: "-q asdf") 284 | @tty.assert_matches "> asdf" 285 | end 286 | 287 | def test_non_interactive 288 | @tty = interactive_fzy(input: %w[foo bar], args: "-e foo", before: "before", after: "after") 289 | @tty.assert_matches "before\nfoo\nafter" 290 | end 291 | 292 | def test_moving_text_cursor 293 | @tty = interactive_fzy(input: %w[foo bar]) 294 | @tty.send_keys("br") 295 | @tty.assert_matches "> br\nbar" 296 | @tty.assert_cursor_position(y: 0, x: 4) 297 | 298 | @tty.send_keys(LEFT) 299 | @tty.assert_cursor_position(y: 0, x: 3) 300 | @tty.assert_matches "> br\nbar" 301 | @tty.send_keys("a") 302 | @tty.assert_cursor_position(y: 0, x: 4) 303 | @tty.assert_matches "> bar\nbar" 304 | 305 | @tty.send_keys(ctrl("A")) # Ctrl-A 306 | @tty.assert_cursor_position(y: 0, x: 2) 307 | @tty.assert_matches "> bar\nbar" 308 | @tty.send_keys("foo") 309 | @tty.assert_cursor_position(y: 0, x: 5) 310 | @tty.assert_matches "> foobar" 311 | 312 | @tty.send_keys(ctrl("E")) # Ctrl-E 313 | @tty.assert_cursor_position(y: 0, x: 8) 314 | @tty.assert_matches "> foobar" 315 | @tty.send_keys("baz") # Ctrl-E 316 | @tty.assert_cursor_position(y: 0, x: 11) 317 | @tty.assert_matches "> foobarbaz" 318 | end 319 | 320 | # More info; 321 | # https://github.com/jhawthorn/fzy/issues/42 322 | # https://cirw.in/blog/bracketed-paste 323 | def test_bracketed_paste_characters 324 | @tty = interactive_fzy(input: %w[foo bar]) 325 | @tty.assert_matches ">\nfoo\nbar" 326 | @tty.send_keys("\e[200~foo\e[201~") 327 | @tty.assert_matches "> foo\nfoo" 328 | end 329 | 330 | # https://github.com/jhawthorn/fzy/issues/81 331 | def test_slow_stdin_fast_user 332 | @tty = TTYtest.new_terminal(%{(sleep 0.5; echo aa; echo bc; echo bd) | #{FZY_PATH}}) 333 | 334 | # Before input has all come in, but wait for fzy to at least start 335 | sleep 0.1 336 | 337 | @tty.send_keys("b\r") 338 | @tty.assert_matches "bc" 339 | end 340 | 341 | def test_unicode 342 | @tty = interactive_fzy(input: %w[English Français 日本語]) 343 | @tty.assert_matches <<~TTY 344 | > 345 | English 346 | Français 347 | 日本語 348 | TTY 349 | @tty.assert_cursor_position(y: 0, x: 2) 350 | 351 | @tty.send_keys("ç") 352 | @tty.assert_matches <<~TTY 353 | > ç 354 | Français 355 | TTY 356 | @tty.assert_cursor_position(y: 0, x: 3) 357 | 358 | @tty.send_keys("\r") 359 | @tty.assert_matches "Français" 360 | end 361 | 362 | def test_unicode_backspace 363 | @tty = interactive_fzy 364 | @tty.send_keys "Français" 365 | @tty.assert_matches "> Français" 366 | @tty.assert_cursor_position(y: 0, x: 10) 367 | 368 | @tty.send_keys(ctrl('H') * 3) 369 | @tty.assert_matches "> Franç" 370 | @tty.assert_cursor_position(y: 0, x: 7) 371 | 372 | @tty.send_keys(ctrl('H')) 373 | @tty.assert_matches "> Fran" 374 | @tty.assert_cursor_position(y: 0, x: 6) 375 | 376 | @tty.send_keys('ce') 377 | @tty.assert_matches "> France" 378 | 379 | @tty = interactive_fzy 380 | @tty.send_keys "日本語" 381 | @tty.assert_matches "> 日本語" 382 | @tty.send_keys(ctrl('H')) 383 | @tty.assert_matches "> 日本" 384 | @tty.send_keys(ctrl('H')) 385 | @tty.assert_matches "> 日" 386 | @tty.send_keys(ctrl('H')) 387 | @tty.assert_matches "> " 388 | @tty.assert_cursor_position(y: 0, x: 2) 389 | end 390 | 391 | def test_unicode_delete_word 392 | @tty = interactive_fzy 393 | @tty.send_keys "Je parle Français" 394 | @tty.assert_matches "> Je parle Français" 395 | @tty.assert_cursor_position(y: 0, x: 19) 396 | 397 | @tty.send_keys(ctrl('W')) 398 | @tty.assert_matches "> Je parle" 399 | @tty.assert_cursor_position(y: 0, x: 11) 400 | 401 | @tty = interactive_fzy 402 | @tty.send_keys "日本語" 403 | @tty.assert_matches "> 日本語" 404 | @tty.send_keys(ctrl('W')) 405 | @tty.assert_matches "> " 406 | @tty.assert_cursor_position(y: 0, x: 2) 407 | end 408 | 409 | def test_unicode_cursor_movement 410 | @tty = interactive_fzy 411 | @tty.send_keys "Français" 412 | @tty.assert_cursor_position(y: 0, x: 10) 413 | 414 | @tty.send_keys(LEFT*5) 415 | @tty.assert_cursor_position(y: 0, x: 5) 416 | 417 | @tty.send_keys(RIGHT*3) 418 | @tty.assert_cursor_position(y: 0, x: 8) 419 | 420 | @tty = interactive_fzy 421 | @tty.send_keys "日本語" 422 | @tty.assert_matches "> 日本語" 423 | @tty.assert_cursor_position(y: 0, x: 8) 424 | @tty.send_keys(LEFT) 425 | @tty.assert_cursor_position(y: 0, x: 6) 426 | @tty.send_keys(LEFT) 427 | @tty.assert_cursor_position(y: 0, x: 4) 428 | @tty.send_keys(LEFT) 429 | @tty.assert_cursor_position(y: 0, x: 2) 430 | @tty.send_keys(LEFT) 431 | @tty.assert_cursor_position(y: 0, x: 2) 432 | @tty.send_keys(RIGHT*3) 433 | @tty.assert_cursor_position(y: 0, x: 8) 434 | @tty.send_keys(RIGHT) 435 | @tty.assert_cursor_position(y: 0, x: 8) 436 | end 437 | 438 | def test_long_strings 439 | ascii = "LongStringOfText" * 6 440 | unicode = "LongStringOfText" * 3 441 | 442 | @tty = interactive_fzy(input: [ascii, unicode]) 443 | @tty.assert_matches <<~TTY 444 | > 445 | LongStringOfTextLongStringOfTextLongStringOfTextLongStringOfTextLongStringOfText 446 | LongStringOfTextLongStringOfTextLongStri 447 | TTY 448 | end 449 | 450 | def test_show_info 451 | @tty = interactive_fzy(input: %w[foo bar baz], args: "-i") 452 | @tty.assert_matches ">\n[3/3]\nfoo\nbar\nbaz" 453 | @tty.send_keys("ba") 454 | @tty.assert_matches "> ba\n[2/3]\nbar\nbaz" 455 | @tty.send_keys("q") 456 | @tty.assert_matches "> baq\n[0/3]" 457 | end 458 | 459 | def test_help 460 | @tty = TTYtest.new_terminal(%{#{FZY_PATH} --help}) 461 | @tty.assert_matches < ') 465 | -q, --query=QUERY Use QUERY as the initial search string 466 | -e, --show-matches=QUERY Output the sorted matches of QUERY 467 | -t, --tty=TTY Specify file to use as TTY device (default /dev/tty) 468 | -s, --show-scores Show the scores of each match 469 | -0, --read-null Read input delimited by ASCII NUL characters 470 | -j, --workers NUM Use NUM workers for searching. (default is # of CPUs) 471 | -i, --show-info Show selection info line 472 | -h, --help Display this help and exit 473 | -v, --version Output version information and exit 474 | TTY 475 | end 476 | 477 | private 478 | 479 | def interactive_fzy(input: [], before: nil, after: nil, args: "") 480 | cmd = [] 481 | cmd << %{echo "#{before}"} if before 482 | cmd << %{printf "#{input.join("\\n")}" | #{FZY_PATH} #{args}} 483 | cmd << %{echo "#{after}"} if after 484 | cmd = cmd.join("; ") 485 | TTYtest.new_terminal(cmd) 486 | end 487 | end 488 | -------------------------------------------------------------------------------- /test/fzytest.c: -------------------------------------------------------------------------------- 1 | #include "greatest/greatest.h" 2 | 3 | SUITE(match_suite); 4 | SUITE(choices_suite); 5 | SUITE(properties_suite); 6 | 7 | GREATEST_MAIN_DEFS(); 8 | 9 | int main(int argc, char *argv[]) { 10 | GREATEST_MAIN_BEGIN(); 11 | 12 | RUN_SUITE(match_suite); 13 | RUN_SUITE(choices_suite); 14 | RUN_SUITE(properties_suite); 15 | 16 | GREATEST_MAIN_END(); 17 | } 18 | -------------------------------------------------------------------------------- /test/test_choices.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | 5 | #include "../config.h" 6 | #include "options.h" 7 | #include "choices.h" 8 | 9 | #include "greatest/greatest.h" 10 | 11 | #define ASSERT_SIZE_T_EQ(a,b) ASSERT_EQ_FMT((size_t)(a), (b), "%zu") 12 | 13 | static options_t default_options; 14 | static choices_t choices; 15 | 16 | static void setup(void *udata) { 17 | (void)udata; 18 | 19 | options_init(&default_options); 20 | choices_init(&choices, &default_options); 21 | } 22 | 23 | static void teardown(void *udata) { 24 | (void)udata; 25 | choices_destroy(&choices); 26 | } 27 | 28 | TEST test_choices_empty() { 29 | ASSERT_SIZE_T_EQ(0, choices.size); 30 | ASSERT_SIZE_T_EQ(0, choices.available); 31 | ASSERT_SIZE_T_EQ(0, choices.selection); 32 | 33 | choices_prev(&choices); 34 | ASSERT_SIZE_T_EQ(0, choices.selection); 35 | 36 | choices_next(&choices); 37 | ASSERT_SIZE_T_EQ(0, choices.selection); 38 | 39 | PASS(); 40 | } 41 | 42 | TEST test_choices_1() { 43 | choices_add(&choices, "tags"); 44 | 45 | choices_search(&choices, ""); 46 | ASSERT_SIZE_T_EQ(1, choices.available); 47 | ASSERT_SIZE_T_EQ(0, choices.selection); 48 | 49 | choices_search(&choices, "t"); 50 | ASSERT_SIZE_T_EQ(1, choices.available); 51 | ASSERT_SIZE_T_EQ(0, choices.selection); 52 | 53 | choices_prev(&choices); 54 | ASSERT_SIZE_T_EQ(0, choices.selection); 55 | 56 | choices_next(&choices); 57 | ASSERT_SIZE_T_EQ(0, choices.selection); 58 | 59 | ASSERT(!strcmp(choices_get(&choices, 0), "tags")); 60 | ASSERT_EQ(NULL, choices_get(&choices, 1)); 61 | 62 | PASS(); 63 | } 64 | 65 | TEST test_choices_2() { 66 | choices_add(&choices, "tags"); 67 | choices_add(&choices, "test"); 68 | 69 | /* Empty search */ 70 | choices_search(&choices, ""); 71 | ASSERT_SIZE_T_EQ(0, choices.selection); 72 | ASSERT_SIZE_T_EQ(2, choices.available); 73 | 74 | choices_next(&choices); 75 | ASSERT_SIZE_T_EQ(1, choices.selection); 76 | choices_next(&choices); 77 | ASSERT_SIZE_T_EQ(0, choices.selection); 78 | 79 | choices_prev(&choices); 80 | ASSERT_SIZE_T_EQ(1, choices.selection); 81 | choices_prev(&choices); 82 | ASSERT_SIZE_T_EQ(0, choices.selection); 83 | 84 | /* Filtered search */ 85 | choices_search(&choices, "te"); 86 | ASSERT_SIZE_T_EQ(1, choices.available); 87 | ASSERT_SIZE_T_EQ(0, choices.selection); 88 | ASSERT_STR_EQ("test", choices_get(&choices, 0)); 89 | 90 | choices_next(&choices); 91 | ASSERT_SIZE_T_EQ(0, choices.selection); 92 | 93 | choices_prev(&choices); 94 | ASSERT_SIZE_T_EQ(0, choices.selection); 95 | 96 | /* No results */ 97 | choices_search(&choices, "foobar"); 98 | ASSERT_SIZE_T_EQ(0, choices.available); 99 | ASSERT_SIZE_T_EQ(0, choices.selection); 100 | 101 | /* Different order due to scoring */ 102 | choices_search(&choices, "ts"); 103 | ASSERT_SIZE_T_EQ(2, choices.available); 104 | ASSERT_SIZE_T_EQ(0, choices.selection); 105 | ASSERT_STR_EQ("test", choices_get(&choices, 0)); 106 | ASSERT_STR_EQ("tags", choices_get(&choices, 1)); 107 | 108 | PASS(); 109 | } 110 | 111 | TEST test_choices_without_search() { 112 | /* Before a search is run, it should return no results */ 113 | 114 | ASSERT_SIZE_T_EQ(0, choices.available); 115 | ASSERT_SIZE_T_EQ(0, choices.selection); 116 | ASSERT_SIZE_T_EQ(0, choices.size); 117 | ASSERT_EQ(NULL, choices_get(&choices, 0)); 118 | 119 | choices_add(&choices, "test"); 120 | 121 | ASSERT_SIZE_T_EQ(0, choices.available); 122 | ASSERT_SIZE_T_EQ(0, choices.selection); 123 | ASSERT_SIZE_T_EQ(1, choices.size); 124 | ASSERT_EQ(NULL, choices_get(&choices, 0)); 125 | 126 | PASS(); 127 | } 128 | 129 | /* Regression test for segfault */ 130 | TEST test_choices_unicode() { 131 | choices_add(&choices, "Edmund Husserl - Méditations cartésiennes - Introduction a la phénoménologie.pdf"); 132 | choices_search(&choices, "e"); 133 | 134 | PASS(); 135 | } 136 | 137 | TEST test_choices_large_input() { 138 | const int N = 100000; 139 | char *strings[100000]; 140 | 141 | for(int i = 0; i < N; i++) { 142 | asprintf(&strings[i], "%i", i); 143 | choices_add(&choices, strings[i]); 144 | } 145 | 146 | choices_search(&choices, "12"); 147 | 148 | /* Must match `seq 0 99999 | grep '.*1.*2.*' | wc -l` */ 149 | ASSERT_SIZE_T_EQ(8146, choices.available); 150 | 151 | ASSERT_STR_EQ("12", choices_get(&choices, 0)); 152 | 153 | for(int i = 0; i < N; i++) { 154 | free(strings[i]); 155 | } 156 | 157 | PASS(); 158 | } 159 | 160 | SUITE(choices_suite) { 161 | SET_SETUP(setup, NULL); 162 | SET_TEARDOWN(teardown, NULL); 163 | 164 | RUN_TEST(test_choices_empty); 165 | RUN_TEST(test_choices_1); 166 | RUN_TEST(test_choices_2); 167 | RUN_TEST(test_choices_without_search); 168 | RUN_TEST(test_choices_unicode); 169 | RUN_TEST(test_choices_large_input); 170 | } 171 | -------------------------------------------------------------------------------- /test/test_match.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../config.h" 4 | #include "match.h" 5 | 6 | #include "greatest/greatest.h" 7 | 8 | #define SCORE_TOLERANCE 0.000001 9 | #define ASSERT_SCORE_EQ(a,b) ASSERT_IN_RANGE((a), (b), SCORE_TOLERANCE) 10 | #define ASSERT_SIZE_T_EQ(a,b) ASSERT_EQ_FMT((size_t)(a), (b), "%zu") 11 | 12 | /* has_match(char *needle, char *haystack) */ 13 | TEST exact_match_should_return_true() { 14 | ASSERT(has_match("a", "a")); 15 | PASS(); 16 | } 17 | 18 | TEST partial_match_should_return_true() { 19 | ASSERT(has_match("a", "ab")); 20 | ASSERT(has_match("a", "ba")); 21 | PASS(); 22 | } 23 | 24 | TEST match_with_delimiters_in_between() { 25 | ASSERT(has_match("abc", "a|b|c")); 26 | PASS(); 27 | } 28 | 29 | TEST non_match_should_return_false() { 30 | ASSERT(!has_match("a", "")); 31 | ASSERT(!has_match("a", "b")); 32 | ASSERT(!has_match("ass", "tags")); 33 | PASS(); 34 | } 35 | 36 | TEST empty_query_should_always_match() { 37 | /* match when query is empty */ 38 | ASSERT(has_match("", "")); 39 | ASSERT(has_match("", "a")); 40 | PASS(); 41 | } 42 | 43 | /* match(char *needle, char *haystack) */ 44 | 45 | TEST should_prefer_starts_of_words() { 46 | /* App/Models/Order is better than App/MOdels/zRder */ 47 | ASSERT(match("amor", "app/models/order") > match("amor", "app/models/zrder")); 48 | PASS(); 49 | } 50 | 51 | TEST should_prefer_consecutive_letters() { 52 | /* App/MOdels/foo is better than App/M/fOo */ 53 | ASSERT(match("amo", "app/m/foo") < match("amo", "app/models/foo")); 54 | PASS(); 55 | } 56 | 57 | TEST should_prefer_contiguous_over_letter_following_period() { 58 | /* GEMFIle.Lock < GEMFILe */ 59 | ASSERT(match("gemfil", "Gemfile.lock") < match("gemfil", "Gemfile")); 60 | PASS(); 61 | } 62 | 63 | TEST should_prefer_shorter_matches() { 64 | ASSERT(match("abce", "abcdef") > match("abce", "abc de")); 65 | ASSERT(match("abc", " a b c ") > match("abc", " a b c ")); 66 | ASSERT(match("abc", " a b c ") > match("abc", " a b c ")); 67 | PASS(); 68 | } 69 | 70 | TEST should_prefer_shorter_candidates() { 71 | ASSERT(match("test", "tests") > match("test", "testing")); 72 | PASS(); 73 | } 74 | 75 | TEST should_prefer_start_of_candidate() { 76 | /* Scores first letter highly */ 77 | ASSERT(match("test", "testing") > match("test", "/testing")); 78 | PASS(); 79 | } 80 | 81 | TEST score_exact_match() { 82 | /* Exact match is SCORE_MAX */ 83 | ASSERT_SCORE_EQ(SCORE_MAX, match("abc", "abc")); 84 | ASSERT_SCORE_EQ(SCORE_MAX, match("aBc", "abC")); 85 | PASS(); 86 | } 87 | 88 | TEST score_empty_query() { 89 | /* Empty query always results in SCORE_MIN */ 90 | ASSERT_SCORE_EQ(SCORE_MIN, match("", "")); 91 | ASSERT_SCORE_EQ(SCORE_MIN, match("", "a")); 92 | ASSERT_SCORE_EQ(SCORE_MIN, match("", "bb")); 93 | PASS(); 94 | } 95 | 96 | TEST score_gaps() { 97 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING, match("a", "*a")); 98 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2, match("a", "*ba")); 99 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_GAP_TRAILING, match("a", "**a*")); 100 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_GAP_TRAILING*2, match("a", "**a**")); 101 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_CONSECUTIVE + SCORE_GAP_TRAILING*2, match("aa", "**aa**")); 102 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_GAP_TRAILING + SCORE_GAP_TRAILING, match("aa", "**a*a**")); 103 | PASS(); 104 | } 105 | 106 | TEST score_consecutive() { 107 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_CONSECUTIVE, match("aa", "*aa")); 108 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_CONSECUTIVE*2, match("aaa", "*aaa")); 109 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_MATCH_CONSECUTIVE, match("aaa", "*a*aa")); 110 | PASS(); 111 | } 112 | 113 | TEST score_slash() { 114 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_SLASH, match("a", "/a")); 115 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_SLASH, match("a", "*/a")); 116 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_SLASH + SCORE_MATCH_CONSECUTIVE, match("aa", "a/aa")); 117 | PASS(); 118 | } 119 | 120 | TEST score_capital() { 121 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_CAPITAL, match("a", "bA")); 122 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_CAPITAL, match("a", "baA")); 123 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_CAPITAL + SCORE_MATCH_CONSECUTIVE, match("aa", "baAa")); 124 | PASS(); 125 | } 126 | 127 | TEST score_dot() { 128 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_DOT, match("a", ".a")); 129 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING*3 + SCORE_MATCH_DOT, match("a", "*a.a")); 130 | ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_MATCH_DOT, match("a", "*a.a")); 131 | PASS(); 132 | } 133 | 134 | TEST score_long_string() { 135 | char string[4096]; 136 | memset(string, 'a', sizeof(string) - 1); 137 | string[sizeof(string) - 1] = '\0'; 138 | 139 | ASSERT_SCORE_EQ(SCORE_MIN, match("aa", string)); 140 | ASSERT_SCORE_EQ(SCORE_MIN, match(string, "aa")); 141 | ASSERT_SCORE_EQ(SCORE_MIN, match(string, string)); 142 | 143 | PASS(); 144 | } 145 | 146 | TEST positions_consecutive() { 147 | size_t positions[3]; 148 | match_positions("amo", "app/models/foo", positions); 149 | ASSERT_SIZE_T_EQ(0, positions[0]); 150 | ASSERT_SIZE_T_EQ(4, positions[1]); 151 | ASSERT_SIZE_T_EQ(5, positions[2]); 152 | 153 | PASS(); 154 | } 155 | 156 | TEST positions_start_of_word() { 157 | /* 158 | * We should prefer matching the 'o' in order, since it's the beginning 159 | * of a word. 160 | */ 161 | size_t positions[4]; 162 | match_positions("amor", "app/models/order", positions); 163 | ASSERT_SIZE_T_EQ(0, positions[0]); 164 | ASSERT_SIZE_T_EQ(4, positions[1]); 165 | ASSERT_SIZE_T_EQ(11, positions[2]); 166 | ASSERT_SIZE_T_EQ(12, positions[3]); 167 | 168 | PASS(); 169 | } 170 | 171 | TEST positions_no_bonuses() { 172 | size_t positions[2]; 173 | match_positions("as", "tags", positions); 174 | ASSERT_SIZE_T_EQ(1, positions[0]); 175 | ASSERT_SIZE_T_EQ(3, positions[1]); 176 | 177 | match_positions("as", "examples.txt", positions); 178 | ASSERT_SIZE_T_EQ(2, positions[0]); 179 | ASSERT_SIZE_T_EQ(7, positions[1]); 180 | 181 | PASS(); 182 | } 183 | 184 | TEST positions_multiple_candidates_start_of_words() { 185 | size_t positions[3]; 186 | match_positions("abc", "a/a/b/c/c", positions); 187 | ASSERT_SIZE_T_EQ(2, positions[0]); 188 | ASSERT_SIZE_T_EQ(4, positions[1]); 189 | ASSERT_SIZE_T_EQ(6, positions[2]); 190 | 191 | PASS(); 192 | } 193 | 194 | TEST positions_exact_match() { 195 | size_t positions[3]; 196 | match_positions("foo", "foo", positions); 197 | ASSERT_SIZE_T_EQ(0, positions[0]); 198 | ASSERT_SIZE_T_EQ(1, positions[1]); 199 | ASSERT_SIZE_T_EQ(2, positions[2]); 200 | 201 | PASS(); 202 | } 203 | 204 | SUITE(match_suite) { 205 | RUN_TEST(exact_match_should_return_true); 206 | RUN_TEST(partial_match_should_return_true); 207 | RUN_TEST(empty_query_should_always_match); 208 | RUN_TEST(non_match_should_return_false); 209 | RUN_TEST(match_with_delimiters_in_between); 210 | 211 | RUN_TEST(should_prefer_starts_of_words); 212 | RUN_TEST(should_prefer_consecutive_letters); 213 | RUN_TEST(should_prefer_contiguous_over_letter_following_period); 214 | RUN_TEST(should_prefer_shorter_matches); 215 | RUN_TEST(should_prefer_shorter_candidates); 216 | RUN_TEST(should_prefer_start_of_candidate); 217 | 218 | RUN_TEST(score_exact_match); 219 | RUN_TEST(score_empty_query); 220 | RUN_TEST(score_gaps); 221 | RUN_TEST(score_consecutive); 222 | RUN_TEST(score_slash); 223 | RUN_TEST(score_capital); 224 | RUN_TEST(score_dot); 225 | RUN_TEST(score_long_string); 226 | 227 | RUN_TEST(positions_consecutive); 228 | RUN_TEST(positions_start_of_word); 229 | RUN_TEST(positions_no_bonuses); 230 | RUN_TEST(positions_multiple_candidates_start_of_words); 231 | RUN_TEST(positions_exact_match); 232 | } 233 | -------------------------------------------------------------------------------- /test/test_properties.c: -------------------------------------------------------------------------------- 1 | #define _DEFAULT_SOURCE 2 | #include 3 | 4 | #include "greatest/greatest.h" 5 | #include "theft/theft.h" 6 | 7 | #include "match.h" 8 | 9 | static void *string_alloc_cb(struct theft *t, theft_hash seed, void *env) { 10 | (void)env; 11 | int limit = 128; 12 | 13 | size_t sz = (size_t)(seed % limit) + 1; 14 | char *str = malloc(sz + 1); 15 | if (str == NULL) { 16 | return THEFT_ERROR; 17 | } 18 | 19 | for (size_t i = 0; i < sz; i += sizeof(theft_hash)) { 20 | theft_hash s = theft_random(t); 21 | for (uint8_t b = 0; b < sizeof(theft_hash); b++) { 22 | if (i + b >= sz) { 23 | break; 24 | } 25 | str[i + b] = (uint8_t)(s >> (8 * b)) & 0xff; 26 | } 27 | } 28 | str[sz] = 0; 29 | 30 | return str; 31 | } 32 | 33 | static void string_free_cb(void *instance, void *env) { 34 | free(instance); 35 | (void)env; 36 | } 37 | 38 | static void string_print_cb(FILE *f, void *instance, void *env) { 39 | char *str = (char *)instance; 40 | (void)env; 41 | size_t size = strlen(str); 42 | fprintf(f, "str[%zd]:\n ", size); 43 | uint8_t bytes = 0; 44 | for (size_t i = 0; i < size; i++) { 45 | fprintf(f, "%02x", str[i]); 46 | bytes++; 47 | if (bytes == 16) { 48 | fprintf(f, "\n "); 49 | bytes = 0; 50 | } 51 | } 52 | fprintf(f, "\n"); 53 | } 54 | 55 | static uint64_t string_hash_cb(void *instance, void *env) { 56 | (void)env; 57 | char *str = (char *)instance; 58 | int size = strlen(str); 59 | return theft_hash_onepass((uint8_t *)str, size); 60 | } 61 | 62 | static void *string_shrink_cb(void *instance, uint32_t tactic, void *env) { 63 | (void)env; 64 | char *str = (char *)instance; 65 | int n = strlen(str); 66 | 67 | if (tactic == 0) { /* first half */ 68 | return strndup(str, n / 2); 69 | } else if (tactic == 1) { /* second half */ 70 | return strndup(str + (n / 2), n / 2); 71 | } else { 72 | return THEFT_NO_MORE_TACTICS; 73 | } 74 | } 75 | 76 | static struct theft_type_info string_info = { 77 | .alloc = string_alloc_cb, 78 | .free = string_free_cb, 79 | .print = string_print_cb, 80 | .hash = string_hash_cb, 81 | .shrink = string_shrink_cb, 82 | }; 83 | 84 | static theft_trial_res prop_should_return_results_if_there_is_a_match(char *needle, 85 | char *haystack) { 86 | int match_exists = has_match(needle, haystack); 87 | if (!match_exists) 88 | return THEFT_TRIAL_SKIP; 89 | 90 | score_t score = match(needle, haystack); 91 | 92 | if (needle[0] == '\0') 93 | return THEFT_TRIAL_SKIP; 94 | 95 | if (score == SCORE_MIN) 96 | return THEFT_TRIAL_FAIL; 97 | 98 | return THEFT_TRIAL_PASS; 99 | } 100 | 101 | TEST should_return_results_if_there_is_a_match() { 102 | struct theft *t = theft_init(0); 103 | struct theft_cfg cfg = { 104 | .name = __func__, 105 | .fun = prop_should_return_results_if_there_is_a_match, 106 | .type_info = {&string_info, &string_info}, 107 | .trials = 100000, 108 | }; 109 | 110 | theft_run_res res = theft_run(t, &cfg); 111 | theft_free(t); 112 | GREATEST_ASSERT_EQm("should_return_results_if_there_is_a_match", THEFT_RUN_PASS, res); 113 | PASS(); 114 | } 115 | 116 | static theft_trial_res prop_positions_should_match_characters_in_string(char *needle, 117 | char *haystack) { 118 | int match_exists = has_match(needle, haystack); 119 | if (!match_exists) 120 | return THEFT_TRIAL_SKIP; 121 | 122 | int n = strlen(needle); 123 | size_t *positions = calloc(n, sizeof(size_t)); 124 | if (!positions) 125 | return THEFT_TRIAL_ERROR; 126 | 127 | match_positions(needle, haystack, positions); 128 | 129 | /* Must be increasing */ 130 | for (int i = 1; i < n; i++) { 131 | if (positions[i] <= positions[i - 1]) { 132 | return THEFT_TRIAL_FAIL; 133 | } 134 | } 135 | 136 | /* Matching characters must be in returned positions */ 137 | for (int i = 0; i < n; i++) { 138 | if (toupper(needle[i]) != toupper(haystack[positions[i]])) { 139 | return THEFT_TRIAL_FAIL; 140 | } 141 | } 142 | 143 | free(positions); 144 | return THEFT_TRIAL_PASS; 145 | } 146 | 147 | TEST positions_should_match_characters_in_string() { 148 | struct theft *t = theft_init(0); 149 | struct theft_cfg cfg = { 150 | .name = __func__, 151 | .fun = prop_positions_should_match_characters_in_string, 152 | .type_info = {&string_info, &string_info}, 153 | .trials = 100000, 154 | }; 155 | 156 | theft_run_res res = theft_run(t, &cfg); 157 | theft_free(t); 158 | GREATEST_ASSERT_EQm("should_return_results_if_there_is_a_match", THEFT_RUN_PASS, res); 159 | PASS(); 160 | } 161 | 162 | SUITE(properties_suite) { 163 | RUN_TEST(should_return_results_if_there_is_a_match); 164 | RUN_TEST(positions_should_match_characters_in_string); 165 | } 166 | --------------------------------------------------------------------------------