├── tests ├── print_end.txt ├── is_binary.pdf ├── print_end.t ├── search_stdin.t ├── option_g.t ├── word_regexp.t ├── bad_path.t ├── is_binary_pdf.t ├── ds_store_ignore.t ├── print_all_files.t ├── ignore_invert.t ├── adjacent_matches.t ├── stupid_fnmatch.t.disabled ├── option_smartcase.t ├── ignore_gitignore.t ├── ignore_pattern_in_subdirectory.t ├── column.t ├── ignore_absolute_search_path_with_glob.t ├── fail │ ├── unicode_case_insensitive.t │ └── unicode_case_insensitive.t.err ├── setup.sh ├── exitcodes.t ├── multiline.t ├── ignore_vcs.t ├── invert_match.t ├── ignore_examine_parent_ignorefiles.t ├── ignore_subdir.t ├── files_with_matches.t ├── ignore_abs_path.t ├── pipecontext.t ├── empty_match.t ├── count.t ├── passthrough.t ├── color.t ├── big │ ├── big_file.t │ └── create_big_file.py ├── filetype.t ├── one_device.t ├── hidden_option.t ├── case_sensitivity.t ├── vimgrep.t ├── line_width.t ├── literal_word_regexp.t ├── max_count.t ├── negated_options.t ├── only_matching.t ├── ignore_extensions.t ├── ignore_backups.t └── list_file_types.t ├── NOTICE ├── src ├── win32 │ └── config.h ├── scandir.h ├── decompress.h ├── log.h ├── lang.h ├── print.h ├── ignore.h ├── search.h ├── log.c ├── scandir.c ├── options.h ├── util.h ├── lang.c ├── main.c ├── decompress.c ├── zfile.c ├── ignore.c ├── print_w32.c └── print.c ├── pgo.sh ├── doc ├── generate_man.sh ├── ag.1.md └── ag.1 ├── .gitignore ├── autogen.sh ├── Makefile.w32 ├── .travis.yml ├── Makefile.am ├── .clang-format ├── format.sh ├── CONTRIBUTING.md ├── the_silver_searcher.spec.in ├── configure.ac ├── ag.bashcomp.sh ├── _the_silver_searcher ├── sanitize.sh ├── README.md ├── LICENSE └── m4 └── ax_pthread.m4 /tests/print_end.txt: -------------------------------------------------------------------------------- 1 | ergneqergneqergneq 2 | ergneq1 -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | The Silver Searcher 2 | Copyright 2011-2016 Geoff Greer 3 | -------------------------------------------------------------------------------- /src/win32/config.h: -------------------------------------------------------------------------------- 1 | #define HAVE_LZMA_H 2 | #define HAVE_PTHREAD_H 3 | -------------------------------------------------------------------------------- /tests/is_binary.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/the_silver_searcher/master/tests/is_binary.pdf -------------------------------------------------------------------------------- /tests/print_end.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ cp $TESTDIR/print_end.txt . 5 | 6 | Print match at the end of a file 7 | 8 | $ ag ergneq1 9 | print_end.txt:2:ergneq1 10 | -------------------------------------------------------------------------------- /tests/search_stdin.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'blah\n' > ./blah.txt 5 | 6 | Feed blah.txt from stdin: 7 | 8 | $ ag 'blah' < ./blah.txt 9 | blah.txt:1:blah 10 | -------------------------------------------------------------------------------- /tests/option_g.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ touch foobar 5 | 6 | Search for lines matching "hello" in test_vimgrep.txt: 7 | 8 | $ ag -g foobar 9 | foobar 10 | $ ag -g baz 11 | [1] 12 | -------------------------------------------------------------------------------- /pgo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | cd "$(dirname "$0")" 5 | 6 | make clean 7 | CFLAGS="$CFLAGS -fprofile-generate" 8 | ./build.sh 9 | ./ag example .. 10 | make clean 11 | CFLAGS="$CFLAGS -fprofile-use" 12 | ./build.sh 13 | -------------------------------------------------------------------------------- /tests/word_regexp.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ echo 'foo' > blah.txt 5 | $ echo 'bar' >> blah.txt 6 | $ echo 'foobar' >> blah.txt 7 | 8 | Word regexp: 9 | 10 | $ ag -w 'foo|bar' ./ 11 | blah.txt:1:foo 12 | blah.txt:2:bar 13 | -------------------------------------------------------------------------------- /tests/bad_path.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | 5 | Complain about nonexistent path: 6 | 7 | $ ag foo doesnt_exist 8 | ERR: Error stat()ing: doesnt_exist 9 | ERR: Error opening directory doesnt_exist: No such file or directory 10 | [1] 11 | -------------------------------------------------------------------------------- /tests/is_binary_pdf.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ cp $TESTDIR/is_binary.pdf . 5 | 6 | PDF files are binary. Do not search them by default: 7 | 8 | $ ag PDF 9 | [1] 10 | 11 | OK, search binary files 12 | 13 | $ ag --search-binary PDF 14 | Binary file is_binary.pdf matches. 15 | -------------------------------------------------------------------------------- /tests/ds_store_ignore.t: -------------------------------------------------------------------------------- 1 | Setup. 2 | $ . $TESTDIR/setup.sh 3 | $ mkdir -p dir0/dir1/dir2 4 | $ printf '*.DS_Store\n' > dir0/.ignore 5 | $ printf 'blah\n' > dir0/dir1/dir2/blah.txt 6 | $ touch dir0/dir1/.DS_Store 7 | 8 | Find blah in blah.txt 9 | 10 | $ ag blah 11 | dir0/dir1/dir2/blah.txt:1:blah 12 | -------------------------------------------------------------------------------- /tests/print_all_files.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'foo\n' > ./foo.txt 5 | $ printf 'bar\n' > ./bar.txt 6 | $ printf 'baz\n' > ./baz.txt 7 | 8 | All files: 9 | 10 | $ ag --print-all-files --group foo | sort 11 | 12 | 13 | 1:foo 14 | bar.txt 15 | baz.txt 16 | foo.txt 17 | -------------------------------------------------------------------------------- /tests/ignore_invert.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'blah1\n' > ./printme.txt 5 | $ printf 'blah2\n' > ./dontprintme.c 6 | $ printf '*\n' > ./.ignore 7 | $ printf '!*.txt\n' >> ./.ignore 8 | 9 | Ignore .gitignore patterns but not .ignore patterns: 10 | 11 | $ ag blah 12 | printme.txt:1:blah1 13 | -------------------------------------------------------------------------------- /doc/generate_man.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ronn is used to turn the markdown into a manpage. 4 | # Get ronn at https://github.com/rtomayko/ronn 5 | # Alternately, since ronn is a Ruby gem, you can just 6 | # `gem install ronn` 7 | 8 | sed -e 's/\\0/\\\\0/' ag.1.md.tmp 9 | ronn -r ag.1.md.tmp 10 | 11 | rm -f ag.1.md.tmp 12 | -------------------------------------------------------------------------------- /tests/adjacent_matches.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ alias ag="$TESTDIR/../ag --noaffinity --workers=1 --parallel --color" 5 | $ printf 'blahfoofooblah\n' > ./fooblah.txt 6 | 7 | Highlights are adjacent: 8 | 9 | $ ag --no-numbers foo 10 | \x1b[1;32mfooblah.txt\x1b[0m\x1b[K:blah\x1b[30;43mfoo\x1b[0m\x1b[K\x1b[30;43mfoo\x1b[0m\x1b[Kblah (esc) 11 | -------------------------------------------------------------------------------- /tests/stupid_fnmatch.t.disabled: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir -p ./a/bomb 5 | $ printf 'whatever\n' > ./a/bomb/foo.yml 6 | $ printf '*b/foo.yml\n' > ./.gitignore 7 | 8 | Ignore foo.yml but not blah.yml: 9 | 10 | $ ag whatever . 11 | 12 | Dont ignore anything (unrestricted search): 13 | 14 | $ ag -u whatever . 15 | a/bomb/foo.yml:1:whatever 16 | -------------------------------------------------------------------------------- /tests/option_smartcase.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'asdf\n' > test.txt 5 | $ printf 'AsDf\n' >> test.txt 6 | 7 | Smart case search: 8 | 9 | $ ag -S asdf -G "test.txt" 10 | test.txt:1:asdf 11 | test.txt:2:AsDf 12 | 13 | Order of options should not matter: 14 | 15 | $ ag asdf -G "test.txt" -S 16 | test.txt:1:asdf 17 | test.txt:2:AsDf 18 | -------------------------------------------------------------------------------- /tests/ignore_gitignore.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ export HOME=$PWD 5 | $ printf '[core]\nexcludesfile = ~/.gitignore.global' >> $HOME/.gitconfig 6 | $ printf 'PATTERN_MARKER\n' > .gitignore.global 7 | 8 | Test that the ignore pattern got picked up: 9 | 10 | $ ag --debug . | grep PATTERN_MARKER 11 | DEBUG: added ignore pattern PATTERN_MARKER to root ignores 12 | 13 | -------------------------------------------------------------------------------- /tests/ignore_pattern_in_subdirectory.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir subdir 5 | $ printf 'first\n' > file1.txt 6 | $ printf 'second\n' > subdir/file2.txt 7 | $ printf '*.txt\n' > .gitignore 8 | 9 | Ignore file based on extension match: 10 | 11 | $ ag first 12 | [1] 13 | 14 | Ignore file in subdirectory based on extension match (#442): 15 | 16 | $ ag second 17 | [1] 18 | -------------------------------------------------------------------------------- /tests/column.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf "blah\nblah2\n" > blah.txt 5 | 6 | Ensure column is correct: 7 | 8 | $ ag --column "blah\nb" 9 | blah.txt:1:1:blah 10 | blah.txt:2:0:blah2 11 | 12 | # Test ackmate output. Not quite right, but at least offsets are in the 13 | # ballpark instead of being 9 quintillion 14 | 15 | $ ag --ackmate "lah\nb" 16 | :blah.txt 17 | 1;blah 18 | 2;1 5:blah2 19 | -------------------------------------------------------------------------------- /tests/ignore_absolute_search_path_with_glob.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir -p parent/multi-part 5 | $ printf 'match1\n' > parent/multi-part/file1.txt 6 | $ printf 'parent/multi-*\n' > .ignore 7 | 8 | # Ignore directory specified by glob: 9 | 10 | # $ ag match . 11 | # [1] 12 | 13 | # Ignore directory specified by glob with absolute search path (#448): 14 | 15 | # $ ag match $(pwd) 16 | # [1] 17 | -------------------------------------------------------------------------------- /tests/fail/unicode_case_insensitive.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/../setup.sh 4 | $ printf "hello=你好\n" > test.txt 5 | $ printf "hello=你好\n" >> test.txt 6 | 7 | Normal search: 8 | 9 | $ $TESTDIR/../../ag --nocolor --workers=1 --parallel 你好 10 | test.txt:1:hello=你好 11 | test.txt:2:hello=你好 12 | 13 | Case-insensitive search: 14 | 15 | $ $TESTDIR/../../ag --nocolor --workers=1 --parallel -i 你好 16 | test.txt:1:hello=你好 17 | test.txt:2:hello=你好 18 | -------------------------------------------------------------------------------- /tests/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # All cram tests should use this. Make sure that "ag" runs the version 4 | # of ag we just built, and make the output really simple. 5 | 6 | # --noaffinity is to stop Travis CI from erroring (it runs in containers so pthread_setaffinity_np fails) 7 | # --workers=1 is to keep all output ordered, to make testing output easier 8 | # shellcheck disable=2139 9 | alias ag="$TESTDIR/../ag --noaffinity --nocolor --workers=1 --parallel" 10 | -------------------------------------------------------------------------------- /tests/exitcodes.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'foo\n' > ./exitcodes_test.txt 5 | $ printf 'bar\n' >> ./exitcodes_test.txt 6 | 7 | Normal matching: 8 | 9 | $ ag foo exitcodes_test.txt 10 | 1:foo 11 | $ ag zoo exitcodes_test.txt 12 | [1] 13 | 14 | Inverted matching: 15 | 16 | $ ag -v foo exitcodes_test.txt 17 | 2:bar 18 | $ ag -v zoo exitcodes_test.txt 19 | 1:foo 20 | 2:bar 21 | $ ag -v "foo|bar" exitcodes_test.txt 22 | [1] 23 | -------------------------------------------------------------------------------- /tests/multiline.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'what\n' > blah.txt 5 | $ printf 'ever\n' >> blah.txt 6 | $ printf 'whatever\n' >> blah.txt 7 | 8 | Multiline: 9 | 10 | $ ag 'wh[^w]+er' . 11 | blah.txt:1:what 12 | blah.txt:2:ever 13 | blah.txt:3:whatever 14 | 15 | No multiline: 16 | 17 | $ ag --nomultiline 'wh[^w]+er' . 18 | blah.txt:3:whatever 19 | 20 | Multiline explicit: 21 | 22 | $ ag '^wh[^w\n]+er$' . 23 | blah.txt:3:whatever 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dSYM 2 | *.gcda 3 | *.o 4 | *.plist 5 | .deps 6 | .dirstamp 7 | .DS_Store 8 | aclocal.m4 9 | ag 10 | ag.exe 11 | autom4te.cache 12 | cachegrind.out.* 13 | callgrind.out.* 14 | clang_output_* 15 | compile 16 | config.guess 17 | config.log 18 | config.status 19 | config.sub 20 | configure 21 | depcomp 22 | gmon.out 23 | install-sh 24 | Makefile 25 | Makefile.in 26 | missing 27 | src/config.h* 28 | stamp-h1 29 | tests/*.err 30 | tests/big/*.err 31 | tests/big/big_file.txt 32 | the_silver_searcher.spec 33 | -------------------------------------------------------------------------------- /src/scandir.h: -------------------------------------------------------------------------------- 1 | #ifndef SCANDIR_H 2 | #define SCANDIR_H 3 | 4 | #include "ignore.h" 5 | 6 | typedef struct { 7 | const ignores *ig; 8 | const char *base_path; 9 | size_t base_path_len; 10 | const char *path_start; 11 | } scandir_baton_t; 12 | 13 | typedef int (*filter_fp)(const char *path, const struct dirent *, void *); 14 | 15 | int ag_scandir(const char *dirname, 16 | struct dirent ***namelist, 17 | filter_fp filter, 18 | void *baton); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /tests/ignore_vcs.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'whatever1\n' > ./always.txt 5 | $ printf 'whatever2\n' > ./git.txt 6 | $ printf 'whatever3\n' > ./text.txt 7 | $ printf 'git.txt\n' > ./.gitignore 8 | $ printf 'text.*\n' > ./.ignore 9 | 10 | Obey .gitignore and .ignore patterns: 11 | 12 | $ ag whatever . 13 | always.txt:1:whatever1 14 | 15 | Ignore .gitignore patterns but not .ignore patterns: 16 | 17 | $ ag -U whatever . | sort 18 | always.txt:1:whatever1 19 | git.txt:1:whatever2 20 | -------------------------------------------------------------------------------- /tests/invert_match.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'valid: 1\n' > ./blah.txt 5 | $ printf 'some_string\n' >> ./blah.txt 6 | $ printf 'valid: 654\n' >> ./blah.txt 7 | $ printf 'some_other_string\n' >> ./blah.txt 8 | $ printf 'valid: 0\n' >> ./blah.txt 9 | $ printf 'valid: 23\n' >> ./blah.txt 10 | $ printf 'valid: 0\n' >> ./blah.txt 11 | 12 | Search for lines not matching "valid: 0" in blah.txt: 13 | 14 | $ ag -v 'valid: ' 15 | blah.txt:2:some_string 16 | blah.txt:4:some_other_string 17 | -------------------------------------------------------------------------------- /tests/ignore_examine_parent_ignorefiles.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir -p subdir 5 | $ printf 'match1\n' > subdir/file1.txt 6 | $ printf 'file1.txt\n' > .ignore 7 | 8 | Ignore directory specified by name: 9 | 10 | $ ag match 11 | [1] 12 | 13 | # Ignore directory specified by name in parent directory when using path (#144): 14 | 15 | # $ ag match subdir 16 | # [1] 17 | 18 | # Ignore directory specified by name in parent directory when using current directory (#144): 19 | 20 | # $ cd subdir 21 | # $ ag match 22 | # [1] 23 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | cd "$(dirname "$0")" 5 | 6 | AC_SEARCH_OPTS="" 7 | # For those of us with pkg-config and other tools in /usr/local 8 | PATH=$PATH:/usr/local/bin 9 | 10 | # This is to make life easier for people who installed pkg-config in /usr/local 11 | # but have autoconf/make/etc in /usr/. AKA most mac users 12 | if [ -d "/usr/local/share/aclocal" ] 13 | then 14 | AC_SEARCH_OPTS="-I /usr/local/share/aclocal" 15 | fi 16 | 17 | # shellcheck disable=2086 18 | aclocal $AC_SEARCH_OPTS 19 | autoconf 20 | autoheader 21 | automake --add-missing 22 | -------------------------------------------------------------------------------- /tests/fail/unicode_case_insensitive.t.err: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/../setup.sh 4 | $ printf "hello=你好\n" > test.txt 5 | $ printf "hello=你好\n" >> test.txt 6 | 7 | Normal search: 8 | 9 | $ $TESTDIR/../../ag --nocolor --workers=1 --parallel 你好 10 | test.txt:1:hello=\xe4\xbd\xa0\xe5\xa5\xbd (esc) 11 | test.txt:2:hello=\xe4\xbd\xa0\xe5\xa5\xbd (esc) 12 | 13 | Case-insensitive search: 14 | 15 | $ $TESTDIR/../../ag --nocolor --workers=1 --parallel -i 你好 16 | test.txt:1:hello=\xe4\xbd\xa0\xe5\xa5\xbd (esc) 17 | test.txt:2:hello=\xe4\xbd\xa0\xe5\xa5\xbd (esc) 18 | -------------------------------------------------------------------------------- /tests/ignore_subdir.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir -p ./a/b/c 5 | $ printf 'whatever1\n' > ./a/b/c/blah.yml 6 | $ printf 'whatever2\n' > ./a/b/foo.yml 7 | $ printf 'a/b/foo.yml\n' > ./.gitignore 8 | # TODO: have this work instead of the above 9 | # $ printf 'a/b/*.yml\n' > ./.gitignore 10 | 11 | Ignore foo.yml but not blah.yml: 12 | 13 | $ ag whatever . 14 | a/b/c/blah.yml:1:whatever1 15 | 16 | Dont ignore anything (unrestricted search): 17 | 18 | $ ag -u whatever . | sort 19 | a/b/c/blah.yml:1:whatever1 20 | a/b/foo.yml:1:whatever2 21 | -------------------------------------------------------------------------------- /tests/files_with_matches.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'foo\n' > ./foo.txt 5 | $ printf 'bar\n' > ./bar.txt 6 | 7 | Files with matches: 8 | 9 | $ ag --files-with-matches foo foo.txt 10 | foo.txt 11 | $ ag --files-with-matches foo foo.txt bar.txt 12 | foo.txt 13 | $ ag --files-with-matches foo bar.txt 14 | [1] 15 | 16 | Files without matches: 17 | 18 | $ ag --files-without-matches bar foo.txt 19 | foo.txt 20 | $ ag --files-without-matches bar foo.txt bar.txt 21 | foo.txt 22 | $ ag --files-without-matches bar bar.txt 23 | [1] 24 | -------------------------------------------------------------------------------- /tests/ignore_abs_path.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir -p ./a/b/c 5 | $ printf 'whatever1\n' > ./a/b/c/blah.yml 6 | $ printf 'whatever2\n' > ./a/b/foo.yml 7 | $ printf '/a/b/foo.yml\n' > ./.ignore 8 | 9 | Ignore foo.yml but not blah.yml: 10 | 11 | $ ag whatever . 12 | a/b/c/blah.yml:1:whatever1 13 | 14 | Dont ignore anything (unrestricted search): 15 | 16 | $ ag -u whatever . | sort 17 | a/b/c/blah.yml:1:whatever1 18 | a/b/foo.yml:1:whatever2 19 | 20 | Ignore foo.yml given an absolute search path [#448]: 21 | 22 | $ ag whatever $(pwd) 23 | /.*/a/b/c/blah.yml:1:whatever1 (re) 24 | -------------------------------------------------------------------------------- /tests/pipecontext.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir pipecontext_dir 5 | $ printf "a\nb\nc\n" > pipecontext_test.txt 6 | $ cd pipecontext_dir 7 | 8 | Do not use parallel flag, which disables stream input: 9 | 10 | $ unalias ag 11 | $ alias ag="$TESTDIR/../ag --nocolor --workers=1" 12 | 13 | B flag on pipe: 14 | 15 | $ cat ../pipecontext_test.txt | ag --numbers -B1 b 16 | 1-a 17 | 2:b 18 | 19 | C flag on pipe: 20 | 21 | $ cat ../pipecontext_test.txt | ag --numbers -C1 b 22 | 1-a 23 | 2:b 24 | 3-c 25 | 26 | Just match last line: 27 | 28 | $ cat ../pipecontext_test.txt | ag --numbers c 29 | 3:c 30 | -------------------------------------------------------------------------------- /tests/empty_match.t: -------------------------------------------------------------------------------- 1 | Setup. 2 | $ . $TESTDIR/setup.sh 3 | $ touch empty.txt 4 | $ printf 'foo\n' > nonempty.txt 5 | 6 | Zero-length match on an empty file should fail silently with return code 1 7 | 8 | $ ag "^" empty.txt 9 | [1] 10 | 11 | A genuine zero-length match should succeed: 12 | $ ag "^" nonempty.txt 13 | 1:foo 14 | 15 | Empty files should be listed with --unrestricted --files-with-matches (-ul) 16 | $ ag -lu --stats | sed '$d' | sort # Remove the last line about timing which will differ 17 | 2 files contained matches 18 | 2 files searched 19 | 2 matches 20 | 4 bytes searched 21 | empty.txt 22 | nonempty.txt 23 | -------------------------------------------------------------------------------- /tests/count.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ unalias ag 5 | $ alias ag="$TESTDIR/../ag --noaffinity --nocolor --workers=1" 6 | $ printf "blah\n" > blah.txt 7 | $ printf "blah2\n" >> blah.txt 8 | $ printf "blah_OTHER\n" > other_file.txt 9 | $ printf "blah_OTHER\n" >> other_file.txt 10 | 11 | Count matches: 12 | 13 | $ ag --count --parallel blah | sort 14 | blah.txt:2 15 | other_file.txt:2 16 | 17 | Count stream matches: 18 | 19 | $ printf 'blah blah blah\n' | ag --count blah 20 | 3 21 | 22 | Count stream matches per line (not very useful since it does not print zero): 23 | 24 | $ cat blah.txt | ag --count blah 25 | 1 26 | 1 27 | -------------------------------------------------------------------------------- /src/decompress.h: -------------------------------------------------------------------------------- 1 | #ifndef DECOMPRESS_H 2 | #define DECOMPRESS_H 3 | 4 | #include 5 | 6 | #include "config.h" 7 | #include "log.h" 8 | #include "options.h" 9 | 10 | typedef enum { 11 | AG_NO_COMPRESSION, 12 | AG_GZIP, 13 | AG_COMPRESS, 14 | AG_ZIP, 15 | AG_XZ, 16 | } ag_compression_type; 17 | 18 | ag_compression_type is_zipped(const void *buf, const int buf_len); 19 | 20 | void *decompress(const ag_compression_type zip_type, const void *buf, const int buf_len, const char *dir_full_path, int *new_buf_len); 21 | 22 | #if HAVE_FOPENCOOKIE 23 | FILE *decompress_open(int fd, const char *mode, ag_compression_type ctype); 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /tests/passthrough.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ unalias ag 5 | $ alias ag="$TESTDIR/../ag --noaffinity --nocolor --workers=1" 6 | $ printf "foo bar\n" > passthrough_test.txt 7 | $ printf "zoo zar\n" >> passthrough_test.txt 8 | $ printf "foo test\n" >> passthrough_test.txt 9 | 10 | No impact on non-stream: 11 | 12 | $ ag --passthrough zoo passthrough_test.txt 13 | zoo zar 14 | 15 | Match stream with --passthrough: 16 | 17 | $ cat passthrough_test.txt | ag --passthrough foo 18 | foo bar 19 | zoo zar 20 | foo test 21 | 22 | Match stream without --passthrough: 23 | 24 | $ cat passthrough_test.txt | ag foo 25 | foo bar 26 | foo test 27 | -------------------------------------------------------------------------------- /tests/color.t: -------------------------------------------------------------------------------- 1 | Setup. Note that we have to turn --color on manually since ag detects that 2 | stdout isn't a tty when running in cram. 3 | 4 | $ . $TESTDIR/setup.sh 5 | $ alias ag="$TESTDIR/../ag --noaffinity --workers=1 --parallel --color" 6 | $ printf 'foo\n' > ./blah.txt 7 | $ printf 'bar\n' >> ./blah.txt 8 | 9 | Matches should contain colors: 10 | 11 | $ ag --no-numbers foo blah.txt 12 | \x1b[30;43mfoo\x1b[0m\x1b[K (esc) 13 | 14 | --nocolor should suppress colors: 15 | 16 | $ ag --nocolor foo blah.txt 17 | 1:foo 18 | 19 | --invert-match should suppress colors: 20 | 21 | $ ag --invert-match foo blah.txt 22 | 2:bar 23 | 24 | -v is the same as --invert-match 25 | 26 | $ ag -v foo blah.txt 27 | 2:bar 28 | -------------------------------------------------------------------------------- /tests/big/big_file.t: -------------------------------------------------------------------------------- 1 | Setup and create really big file: 2 | 3 | $ . $TESTDIR/../setup.sh 4 | $ python3 $TESTDIR/create_big_file.py $TESTDIR/big_file.txt 5 | 6 | Search a big file: 7 | 8 | $ $TESTDIR/../../ag --nocolor --workers=1 --parallel hello $TESTDIR/big_file.txt 9 | 33554432:hello1073741824 10 | 67108864:hello2147483648 11 | 100663296:hello3221225472 12 | 134217728:hello4294967296 13 | 167772160:hello5368709120 14 | 201326592:hello6442450944 15 | 234881024:hello7516192768 16 | 268435456:hello 17 | 18 | Fail to regex search a big file: 19 | $ $TESTDIR/../../ag --nocolor --workers=1 --parallel 'hello.*' $TESTDIR/big_file.txt 20 | ERR: Skipping */big_file.txt: pcre_exec() can't handle files larger than 2147483647 bytes. (glob) 21 | [1] 22 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H 2 | #define LOG_H 3 | 4 | #include 5 | 6 | #include "config.h" 7 | 8 | #ifdef HAVE_PTHREAD_H 9 | #include 10 | #endif 11 | 12 | pthread_mutex_t print_mtx; 13 | 14 | enum log_level { 15 | LOG_LEVEL_DEBUG = 10, 16 | LOG_LEVEL_MSG = 20, 17 | LOG_LEVEL_WARN = 30, 18 | LOG_LEVEL_ERR = 40, 19 | LOG_LEVEL_NONE = 100 20 | }; 21 | 22 | void set_log_level(enum log_level threshold); 23 | 24 | void log_debug(const char *fmt, ...); 25 | void log_msg(const char *fmt, ...); 26 | void log_warn(const char *fmt, ...); 27 | void log_err(const char *fmt, ...); 28 | 29 | void vplog(const unsigned int level, const char *fmt, va_list args); 30 | void plog(const unsigned int level, const char *fmt, ...); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /tests/big/create_big_file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Create an 8GB file of mostly "abcdefghijklmnopqrstuvwxyz01234", 4 | # with a few instances of "hello" 5 | 6 | import sys 7 | 8 | if len(sys.argv) != 2: 9 | print("Usage: %s big_file.txt" % sys.argv[0]) 10 | sys.exit(1) 11 | 12 | big_file = sys.argv[1] 13 | 14 | 15 | def create_big_file(): 16 | with open(big_file, "w") as fd: 17 | for i in range(1, 2**28): 18 | byte = i * 32 19 | if byte % 2**30 == 0: 20 | fd.write("hello%s\n" % byte) 21 | else: 22 | fd.write("abcdefghijklmnopqrstuvwxyz01234\n") 23 | fd.write("hello\n") 24 | 25 | 26 | try: 27 | fd = open(big_file, "r") 28 | except Exception as e: 29 | create_big_file() 30 | -------------------------------------------------------------------------------- /tests/filetype.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ TEST_FILETYPE_EXT1=`ag --list-file-types | grep -E '^[ \t]+\..+' | head -n 1 | awk '{ print $1 }'` 5 | $ TEST_FILETYPE_EXT2=`ag --list-file-types | grep -E '^[ \t]+\..+' | tail -n 1 | awk '{ print $1 }'` 6 | $ TEST_FILETYPE_DIR=filetype_test 7 | $ mkdir $TEST_FILETYPE_DIR 8 | $ printf "This is filetype test1.\n" > $TEST_FILETYPE_DIR/test.$TEST_FILETYPE_EXT1 9 | $ printf "This is filetype test2.\n" > $TEST_FILETYPE_DIR/test.$TEST_FILETYPE_EXT2 10 | 11 | Match only top file type: 12 | 13 | $ TEST_FILETYPE_OPTION=`ag --list-file-types | grep -E '^[ \t]+--.+' | head -n 1 | awk '{ print $1 }'` 14 | $ ag 'This is filetype test' --nofilename $TEST_FILETYPE_OPTION $TEST_FILETYPE_DIR 15 | This is filetype test1. 16 | -------------------------------------------------------------------------------- /tests/one_device.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | > if [ ! -e "/dev/shm" ]; then 5 | > echo "No /dev/shm. Skipping test." 6 | > exit 80 7 | > elif [ "$(stat -c%d /dev/)" = "$(stat -c%d /dev/shm/)" ]; then 8 | > echo "/dev/shm not a different device. Skipping test." 9 | > exit 80 10 | > fi 11 | $ TEST_TMPDIR=`mktemp -d --tmpdir=/dev/shm ag_test.XXX` 12 | $ printf "blah\n" > $TEST_TMPDIR/blah.txt 13 | $ ln -s $TEST_TMPDIR other_device 14 | 15 | Should not descend into /dev/shm symlink when --one-device specified: 16 | 17 | $ ag -f --one-device blah . 18 | [1] 19 | 20 | Files on other devices work the same way as anything else without --one-device: 21 | 22 | $ ag -f blah . 23 | other_device/blah.txt:1:blah 24 | 25 | Cleanup: 26 | $ rm -r $TEST_TMPDIR 27 | -------------------------------------------------------------------------------- /tests/hidden_option.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ mkdir hidden_bug 5 | $ cd hidden_bug 6 | $ printf "test\n" > a.txt 7 | $ git init --quiet 8 | $ if [ ! -d .git/info ] ; then mkdir .git/info ; fi 9 | $ printf "a.txt\n" > .git/info/exclude 10 | 11 | $ ag --ignore-dir .git test 12 | [1] 13 | 14 | $ ag --hidden --ignore-dir .git test 15 | [1] 16 | 17 | $ ag -U --ignore-dir .git test 18 | a.txt:1:test 19 | 20 | $ ag --hidden -U --ignore-dir .git test 21 | a.txt:1:test 22 | 23 | $ mkdir -p ./.hidden 24 | $ printf 'whatever\n' > ./.hidden/a.txt 25 | 26 | $ ag whatever 27 | [1] 28 | 29 | $ ag --hidden whatever 30 | [1] 31 | 32 | $ printf "\n" > .git/info/exclude 33 | 34 | $ ag whatever 35 | [1] 36 | 37 | $ ag --hidden whatever 38 | .hidden/a.txt:1:whatever 39 | -------------------------------------------------------------------------------- /Makefile.w32: -------------------------------------------------------------------------------- 1 | SED=sed 2 | VERSION:=$(shell "$(SED)" -n "s/[^[]*\[\([0-9]\+\.[0-9]\+\.[0-9]\+\)\],/\1/p" configure.ac) 3 | 4 | CC=gcc 5 | RM=/bin/rm 6 | 7 | SRCS = \ 8 | src/decompress.c \ 9 | src/ignore.c \ 10 | src/lang.c \ 11 | src/log.c \ 12 | src/main.c \ 13 | src/options.c \ 14 | src/print.c \ 15 | src/scandir.c \ 16 | src/search.c \ 17 | src/util.c \ 18 | src/print_w32.c 19 | OBJS = $(subst .c,.o,$(SRCS)) 20 | 21 | CFLAGS = -O2 -Isrc/win32 -DPACKAGE_VERSION=\"$(VERSION)\" 22 | LIBS = -lz -lpthread -lpcre -llzma -lshlwapi 23 | TARGET = ag.exe 24 | 25 | all : $(TARGET) 26 | 27 | # depend on configure.ac to account for version changes 28 | $(TARGET) : $(OBJS) configure.ac 29 | $(CC) -o $@ $(OBJS) $(LIBS) 30 | 31 | .c.o : 32 | $(CC) -c $(CFLAGS) -Isrc $< -o $@ 33 | 34 | clean : 35 | $(RM) -f src/*.o $(TARGET) 36 | -------------------------------------------------------------------------------- /tests/case_sensitivity.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'Foo\n' >> ./sample 5 | $ printf 'bar\n' >> ./sample 6 | 7 | Smart case by default: 8 | 9 | $ ag foo sample 10 | 1:Foo 11 | $ ag FOO sample 12 | [1] 13 | $ ag 'f.o' sample 14 | 1:Foo 15 | $ ag Foo sample 16 | 1:Foo 17 | $ ag 'F.o' sample 18 | 1:Foo 19 | 20 | Case sensitive mode: 21 | 22 | $ ag -s foo sample 23 | [1] 24 | $ ag -s FOO sample 25 | [1] 26 | $ ag -s 'f.o' sample 27 | [1] 28 | $ ag -s Foo sample 29 | 1:Foo 30 | $ ag -s 'F.o' sample 31 | 1:Foo 32 | 33 | Case insensitive mode: 34 | 35 | $ ag fOO -i sample 36 | 1:Foo 37 | $ ag fOO --ignore-case sample 38 | 1:Foo 39 | $ ag 'f.o' -i sample 40 | 1:Foo 41 | 42 | Case insensitive file regex 43 | 44 | $ ag -i -g 'Samp.*' 45 | sample 46 | -------------------------------------------------------------------------------- /tests/vimgrep.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf 'Hello, "Hello, world" programs output "Hello, world".\n' > ./test_vimgrep.txt 5 | $ printf '"Hello, world" programs are simple programs.\n' >> ./test_vimgrep.txt 6 | $ printf 'They illustrate the most basic syntax of a programming language\n' >> ./test_vimgrep.txt 7 | $ printf 'In javascript: alert("Hello, world!");\n' >> ./test_vimgrep.txt 8 | 9 | Search for lines matching "hello" in test_vimgrep.txt: 10 | 11 | $ ag --vimgrep hello 12 | test_vimgrep.txt:1:1:Hello, "Hello, world" programs output "Hello, world". 13 | test_vimgrep.txt:1:9:Hello, "Hello, world" programs output "Hello, world". 14 | test_vimgrep.txt:1:40:Hello, "Hello, world" programs output "Hello, world". 15 | test_vimgrep.txt:2:2:"Hello, world" programs are simple programs. 16 | test_vimgrep.txt:4:23:In javascript: alert("Hello, world!"); 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: false 3 | 4 | branches: 5 | only: 6 | - master 7 | 8 | compiler: 9 | - clang 10 | - gcc 11 | 12 | addons: 13 | apt: 14 | sources: 15 | - ubuntu-toolchain-r-test 16 | packages: 17 | - automake 18 | - liblzma-dev 19 | - libpcre3-dev 20 | - pkg-config 21 | - zlib1g-dev 22 | 23 | env: 24 | global: 25 | - LLVM_VERSION=3.8.0 26 | - LLVM_PATH=$HOME/clang+llvm 27 | - CLANG_FORMAT=$LLVM_PATH/bin/clang-format 28 | 29 | before_install: 30 | - wget http://llvm.org/releases/$LLVM_VERSION/clang+llvm-$LLVM_VERSION-x86_64-linux-gnu-ubuntu-14.04.tar.xz -O $LLVM_PATH.tar.xz 31 | - mkdir $LLVM_PATH 32 | - tar xf $LLVM_PATH.tar.xz -C $LLVM_PATH --strip-components=1 33 | - export PATH=$HOME/.local/bin:$PATH 34 | 35 | install: 36 | - pip install --user cram 37 | 38 | script: 39 | - ./build.sh && make test 40 | -------------------------------------------------------------------------------- /tests/line_width.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf "12345678901234567890123456789012345678901234567890\n" >> ./blah.txt 5 | 6 | Truncate to width inside input line length: 7 | 8 | $ ag -W 20 1 < ./blah.txt 9 | blah.txt:1:12345678901234567890 [...] 10 | 11 | Truncate to width inside input line length, long-form: 12 | 13 | $ ag --width 20 1 < ./blah.txt 14 | blah.txt:1:12345678901234567890 [...] 15 | 16 | Truncate to width outside input line length: 17 | 18 | $ ag -W 60 1 < ./blah.txt 19 | blah.txt:1:12345678901234567890123456789012345678901234567890 20 | 21 | Truncate to width one less than input line length: 22 | 23 | $ ag -W 49 1 < ./blah.txt 24 | blah.txt:1:1234567890123456789012345678901234567890123456789 [...] 25 | 26 | Truncate to width exactly input line length: 27 | 28 | $ ag -W 50 1 < ./blah.txt 29 | blah.txt:1:12345678901234567890123456789012345678901234567890 30 | 31 | -------------------------------------------------------------------------------- /tests/literal_word_regexp.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ echo 'blah abc def' > blah1.txt 5 | $ echo 'abc blah def' > blah2.txt 6 | $ echo 'abc def blah' > blah3.txt 7 | $ echo 'abcblah def' > blah4.txt 8 | $ echo 'abc blahdef' >> blah4.txt 9 | $ echo 'blahx blah' > blah5.txt 10 | $ echo 'abcblah blah blah' > blah6.txt 11 | 12 | Match a word of the beginning: 13 | 14 | $ ag -wF --column 'blah' blah1.txt 15 | 1:1:blah abc def 16 | 17 | Match a middle word: 18 | 19 | $ ag -wF --column 'blah' blah2.txt 20 | 1:5:abc blah def 21 | 22 | Match a last word: 23 | 24 | $ ag -wF --column 'blah' blah3.txt 25 | 1:9:abc def blah 26 | 27 | No match: 28 | 29 | $ ag -wF --column 'blah' blah4.txt 30 | [1] 31 | 32 | Match: 33 | 34 | $ ag -wF --column 'blah' blah5.txt 35 | 1:7:blahx blah 36 | 37 | Case of a word repeating the same part: 38 | 39 | $ ag -wF --column 'blah blah' blah6.txt 40 | 1:9:abcblah blah blah 41 | -------------------------------------------------------------------------------- /src/lang.h: -------------------------------------------------------------------------------- 1 | #ifndef LANG_H 2 | #define LANG_H 3 | 4 | #define MAX_EXTENSIONS 12 5 | #define SINGLE_EXT_LEN 20 6 | 7 | typedef struct { 8 | const char *name; 9 | const char *extensions[MAX_EXTENSIONS]; 10 | } lang_spec_t; 11 | 12 | extern lang_spec_t langs[]; 13 | 14 | /** 15 | Return the language count. 16 | */ 17 | size_t get_lang_count(void); 18 | 19 | /** 20 | Convert a NULL-terminated array of language extensions 21 | into a regular expression of the form \.(extension1|extension2...)$ 22 | 23 | Caller is responsible for freeing the returned string. 24 | */ 25 | char *make_lang_regex(char *ext_array, size_t num_exts); 26 | 27 | 28 | /** 29 | Combine multiple file type extensions into one array. 30 | 31 | The combined result is returned through *exts*; 32 | *exts* is one-dimension array, which can contain up to 100 extensions; 33 | The number of extensions that *exts* actually contain is returned. 34 | */ 35 | size_t combine_file_extensions(size_t *extension_index, size_t len, char **exts); 36 | #endif 37 | -------------------------------------------------------------------------------- /tests/max_count.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . $TESTDIR/setup.sh 4 | $ printf "blah\n" > blah.txt 5 | $ printf "blah2\n" >> blah.txt 6 | $ printf "blah2\n" > blah2.txt 7 | $ printf "blah2\n" >> blah2.txt 8 | $ printf "blah2\n" >> blah2.txt 9 | $ printf "blah2\n" >> blah2.txt 10 | $ printf "blah2\n" >> blah2.txt 11 | $ printf "blah2\n" >> blah2.txt 12 | $ printf "blah2\n" >> blah2.txt 13 | $ printf "blah2\n" >> blah2.txt 14 | $ printf "blah2\n" >> blah2.txt 15 | $ printf "blah2\n" >> blah2.txt # 10 lines 16 | 17 | Max match of 1: 18 | 19 | $ ag --max-count 1 blah blah.txt 20 | ERR: Too many matches in blah.txt. Skipping the rest of this file. 21 | 1:blah 22 | 23 | Max match of 10, one file: 24 | 25 | $ ag --count --max-count 10 blah blah2.txt 26 | ERR: Too many matches in blah2.txt. Skipping the rest of this file. 27 | 10 28 | 29 | Max match of 10, multiple files: 30 | 31 | $ ag --count --max-count 10 blah blah.txt blah2.txt 32 | ERR: Too many matches in blah2.txt. Skipping the rest of this file. 33 | blah.txt:2 34 | blah2.txt:10 35 | -------------------------------------------------------------------------------- /tests/negated_options.t: -------------------------------------------------------------------------------- 1 | Setup: 2 | 3 | $ . "${TESTDIR}/setup.sh" 4 | 5 | Should accept both --no-