├── .gitignore ├── Commands └── Ack in Project.tmCommand ├── README.textile ├── Support ├── ack-standalone.sh ├── environment.rb ├── lib │ ├── search.rb │ ├── search_dialog.rb │ └── search_results.rb ├── nibs │ └── AckInProjectSearch.nib │ │ ├── classes.nib │ │ ├── info.nib │ │ └── keyedobjects.nib ├── search.css └── search.js └── info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~.nib 3 | *.tm_build_errors 4 | *.pbxuser 5 | *.perspective 6 | -------------------------------------------------------------------------------- /Commands/Ack in Project.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | ${TM_RUBY:-/usr/bin/env ruby} <<"[RUBY]" -rcgi 9 | require ENV['TM_BUNDLE_SUPPORT'] + '/environment.rb' 10 | AckInProject.show_search_dialog do |search_options| 11 | AckInProject.present_search(search_options) 12 | end 13 | [RUBY] 14 | input 15 | none 16 | keyEquivalent 17 | @A 18 | name 19 | Ack in Project 20 | output 21 | showAsHTML 22 | uuid 23 | E1966FE3-4B65-419A-85CA-4447BC63ECAD 24 | 25 | 26 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | Ack Textmate Bundle. 2 | Version 0.1 3 | 4 |

You should probably use AckMate instead...

5 | 6 | Users of Mac OS X 10.5 and above will have a *much* better expierence with "AckMate":http://github.com/protocool/AckMate (also authored by me). It eliminates many of this bundle's shortcomings. 7 | 8 |

Usage

9 | 10 | Type command-shift-a to ack your project in TextMate... 'nuff said. 11 | 12 | You can learn more about ack at http://petdance.com/ack/. 13 | 14 |

Installation

15 | 16 | * Run this: 17 | 18 |
19 | cd ~/Library/Application\ Support/TextMate/Bundles
20 | git clone git://github.com/protocool/ack-tmbundle.git Ack.tmbundle
21 | 
22 | 23 |

Bugs

24 | 25 | You can file tickets for any bugs using LightHouse. 26 | 27 |

Notes

28 | 29 | The ack-standalone.sh script from http://petdance.com/ack/ is included in this bundle. You can use a different version of ack by setting the TM_ACK environment variable to point to your version. 30 | 31 | This bundle has only been tested against this particular version of ack-standalone. 32 | 33 |

Per-project .ackrc

34 | 35 | Ack normally honors the settings in your $HOME/.ackrc file. Additionally, any .ackrc file in your project directory (TM_PROJECT_DIRECTORY) will also be read. 36 | 37 | You can switch-off loading of any .ackrc files in the 'Advanced options' drawer. 38 | 39 |

Background

40 | 41 | The code is based on GrepInProject++ which was itself based on other's code as below: 42 | 43 |
44 | # By Henrik Nyh (http://henrik.nyh.se) 2007-06-26
45 | # Free to modify and redistribute with credit.
46 | 
47 | # Includes some minor modifications by Max (http://max.xaok.org/webtek) 2007-08-01
48 | # Adds search UI plus some minor modifications by Robert Thurnher (http://soup.robert42.com) 2007-08-11
49 | # Further modifications by Trevor Squires (http://somethinglearned.com) 2008-05-21
50 | 
51 | -------------------------------------------------------------------------------- /Support/ack-standalone.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # This standalone version of ack is generated code. 4 | # Please DO NOT EDIT or send patches for it. 5 | # 6 | 7 | use warnings; 8 | use strict; 9 | 10 | our $VERSION = '1.84'; 11 | # Check http://petdance.com/ack/ for updates 12 | 13 | # These are all our globals. 14 | 15 | 16 | MAIN: { 17 | if ( $App::Ack::VERSION ne $main::VERSION ) { 18 | App::Ack::die( "Program/library version mismatch\n\t$0 is $main::VERSION\n\t$INC{'App/Ack.pm'} is $App::Ack::VERSION" ); 19 | } 20 | 21 | # Do preliminary arg checking; 22 | my $env_is_usable = 1; 23 | for ( @ARGV ) { 24 | last if ( $_ eq '--' ); 25 | 26 | # Priorities! Get the --thpppt checking out of the way. 27 | /^--th[pt]+t+$/ && App::Ack::_thpppt($_); 28 | 29 | # See if we want to ignore the environment. (Don't tell Al Gore.) 30 | if ( $_ eq '--noenv' ) { 31 | my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV ); 32 | delete @ENV{@keys}; 33 | $env_is_usable = 0; 34 | } 35 | } 36 | unshift( @ARGV, App::Ack::read_ackrc() ) if $env_is_usable; 37 | App::Ack::load_colors(); 38 | 39 | if ( exists $ENV{ACK_SWITCHES} ) { 40 | App::Ack::warn( 'ACK_SWITCHES is no longer supported. Use ACK_OPTIONS.' ); 41 | } 42 | 43 | if ( !@ARGV ) { 44 | App::Ack::show_help(); 45 | exit 1; 46 | } 47 | 48 | main(); 49 | } 50 | 51 | sub main { 52 | my $opt = App::Ack::get_command_line_options(); 53 | 54 | $| = 1 if $opt->{flush}; # Unbuffer the output if flush mode 55 | 56 | if ( !-t STDIN && !eof(STDIN) ) { 57 | # We're going into filter mode 58 | for ( qw( f g l ) ) { 59 | $opt->{$_} and App::Ack::die( "Can't use -$_ when acting as a filter." ); 60 | } 61 | $opt->{show_filename} = 0; 62 | $opt->{regex} = App::Ack::build_regex( defined $opt->{regex} ? $opt->{regex} : shift @ARGV, $opt ); 63 | if ( my $nargs = @ARGV ) { 64 | my $s = $nargs == 1 ? '' : 's'; 65 | App::Ack::warn( "Ignoring $nargs argument$s on the command-line while acting as a filter." ); 66 | } 67 | App::Ack::search( \*STDIN, 0, '-', $opt ); 68 | exit 0; 69 | } 70 | 71 | my $file_matching = $opt->{f} || $opt->{lines}; 72 | if ( !$file_matching ) { 73 | @ARGV or App::Ack::die( 'No regular expression found.' ); 74 | $opt->{regex} = App::Ack::build_regex( defined $opt->{regex} ? $opt->{regex} : shift @ARGV, $opt ); 75 | } 76 | 77 | # check that all regexes do compile fine 78 | App::Ack::check_regex( $_ ) for ( $opt->{regex}, $opt->{G} ); 79 | 80 | my $what = App::Ack::get_starting_points( \@ARGV, $opt ); 81 | my $iter = App::Ack::get_iterator( $what, $opt ); 82 | App::Ack::filetype_setup(); 83 | 84 | App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager}; 85 | if ( $opt->{f} ) { 86 | App::Ack::print_files( $iter, $opt ); 87 | } 88 | elsif ( $opt->{l} || $opt->{count} ) { 89 | App::Ack::print_files_with_matches( $iter, $opt ); 90 | } 91 | else { 92 | App::Ack::print_matches( $iter, $opt ); 93 | } 94 | close $App::Ack::fh; 95 | exit 0; 96 | } 97 | 98 | =head1 NAME 99 | 100 | ack - grep-like text finder 101 | 102 | =head1 SYNOPSIS 103 | 104 | ack [options] PATTERN [FILE...] 105 | ack -f [options] [DIRECTORY...] 106 | 107 | =head1 DESCRIPTION 108 | 109 | Ack is designed as a replacement for 99% of the uses of F. 110 | 111 | Ack searches the named input FILEs (or standard input if no files are 112 | named, or the file name - is given) for lines containing a match to the 113 | given PATTERN. By default, ack prints the matching lines. 114 | 115 | Ack can also list files that would be searched, without actually searching 116 | them, to let you take advantage of ack's file-type filtering capabilities. 117 | 118 | =head1 FILE SELECTION 119 | 120 | I is intelligent about the files it searches. It knows about 121 | certain file types, based on both the extension on the file and, 122 | in some cases, the contents of the file. These selections can be 123 | made with the B<--type> option. 124 | 125 | With no file selections, I only searches files of types that 126 | it recognizes. If you have a file called F, and I 127 | doesn't know what a .wango file is, I won't search it. 128 | 129 | The B<-a> option tells I to select all files, regardless of 130 | type. 131 | 132 | Some files will never be selected by I, even with B<-a>, 133 | including: 134 | 135 | =over 4 136 | 137 | =item * Backup files: Files ending with F<~>, or F<#*#> 138 | 139 | =item * Coredumps: Files matching F 140 | 141 | =back 142 | 143 | However, I always searches the files given on the command line, 144 | no matter what type. Furthermore, by specifying the B<-u> option all 145 | files will be searched. 146 | 147 | =head1 DIRECTORY SELECTION 148 | 149 | I descends through the directory tree of the starting directories 150 | specified. However, it will ignore the shadow directories used by 151 | many version control systems, and the build directories used by the 152 | Perl MakeMaker system. You may add or remove a directory from this 153 | list with the B<--[no]ignore-dir> option. The option may be repeated 154 | to add/remove multiple directories from the ignore list. 155 | 156 | For a complete list of directories that do not get searched, run 157 | F. 158 | 159 | =head1 WHEN TO USE GREP 160 | 161 | I trumps I as an everyday tool 99% of the time, but don't 162 | throw I away, because there are times you'll still need it. 163 | 164 | E.g., searching through huge files looking for regexes that can be 165 | expressed with I syntax should be quicker with I. 166 | 167 | =head1 OPTIONS 168 | 169 | =over 4 170 | 171 | =item B<-a>, B<--all> 172 | 173 | Operate on all files, regardless of type (but still skip directories 174 | like F, F, etc.) 175 | 176 | =item B<-A I>, B<--after-context=I> 177 | 178 | Print I lines of trailing context after matching lines. 179 | 180 | =item B<-B I>, B<--before-context=I> 181 | 182 | Print I lines of leading context before matching lines. 183 | 184 | =item B<-C [I]>, B<--context[=I]> 185 | 186 | Print I lines (default 2) of context around matching lines. 187 | 188 | =item B<-c>, B<--count> 189 | 190 | Suppress normal output; instead print a count of matching lines for 191 | each input file. If B<-l> is in effect, it will only show the 192 | number of lines for each file that has lines matching. Without 193 | B<-l>, some line counts may be zeroes. 194 | 195 | =item B<--color>, B<--nocolor> 196 | 197 | B<--color> highlights the matching text. B<--nocolor> supresses 198 | the color. This is on by default unless the output is redirected. 199 | 200 | On Windows, this option is off by default unless the 201 | L module is installed or the C 202 | environment variable is used. 203 | 204 | =item B<--env>, B<--noenv> 205 | 206 | B<--noenv> disables all environment processing. No F<.ackrc> is read 207 | and all environment variables are ignored. By default, F considers 208 | F<.ackrc> and settings in the environment. 209 | 210 | =item B<--flush> 211 | 212 | B<--flush> flushes output immediately. This is off by default 213 | unless ack is running interactively (when output goes to a pipe 214 | or file). 215 | 216 | =item B<-f> 217 | 218 | Only print the files that would be searched, without actually doing 219 | any searching. PATTERN must not be specified, or it will be taken as 220 | a path to search. 221 | 222 | =item B<--follow>, B<--nofollow> 223 | 224 | Follow or don't follow symlinks, other than whatever starting files 225 | or directories were specified on the command line. 226 | 227 | This is off by default. 228 | 229 | =item B<-G I> 230 | 231 | Only paths matching I are included in the search. The entire 232 | path and filename are matched against I, and I is a 233 | Perl regular expression, not a shell glob. 234 | 235 | The options B<-i>, B<-w>, B<-v>, and B<-Q> do not apply to this I. 236 | 237 | =item B<-g I> 238 | 239 | Print files where the relative path + filename matches I. This option is 240 | a convenience shortcut for B<-f> B<-G I>. 241 | 242 | The options B<-i>, B<-w>, B<-v>, and B<-Q> do not apply to this I. 243 | 244 | =item B<--group>, B<--nogroup> 245 | 246 | B<--group> groups matches by file name with. This is the default when 247 | used interactively. 248 | 249 | B<--nogroup> prints one result per line, like grep. This is the default 250 | when output is redirected. 251 | 252 | =item B<-H>, B<--with-filename> 253 | 254 | Print the filename for each match. 255 | 256 | =item B<-h>, B<--no-filename> 257 | 258 | Suppress the prefixing of filenames on output when multiple files are 259 | searched. 260 | 261 | =item B<--help> 262 | 263 | Print a short help statement. 264 | 265 | =item B<-i>, B<--ignore-case> 266 | 267 | Ignore case in the search strings. 268 | 269 | This applies only to the PATTERN, not to the regexes given for the B<-g> 270 | and B<-G> options. 271 | 272 | =item B<--[no]ignore-dir=DIRNAME> 273 | 274 | Ignore directory (as CVS, .svn, etc are ignored). May be used multiple times 275 | to ignore multiple directories. For example, mason users may wish to include 276 | B<--ignore-dir=data>. The B<--noignore-dir> option allows users to search 277 | directories which would normally be ignored (perhaps to research the contents 278 | of F<.svn/props> directories). 279 | 280 | =item B<--line=I> 281 | 282 | Only print line I of each file. Multiple lines can be given with multiple 283 | B<--line> options or as a comma separated list (B<--line=3,5,7>). B<--line=4-7> 284 | also works. The lines are always output in ascending order, no matter the 285 | order given on the command line. 286 | 287 | =item B<-l>, B<--files-with-matches> 288 | 289 | Only print the filenames of matching files, instead of the matching text. 290 | 291 | =item B<-L>, B<--files-without-matches> 292 | 293 | Only print the filenames of files that do I match. This is equivalent 294 | to specifying B<-l> and B<-v>. 295 | 296 | =item B<--match I> 297 | 298 | Specify the I explicitly. This is helpful if you don't want to put the 299 | regex as your first argument, e.g. when executing multiple searches over the 300 | same set of files. 301 | 302 | # search for foo and bar in given files 303 | ack file1 t/file* --match foo 304 | ack file1 t/file* --match bar 305 | 306 | =item B<-m=I>, B<--max-count=I> 307 | 308 | Stop reading a file after I matches. 309 | 310 | =item B<--man> 311 | 312 | Print this manual page. 313 | 314 | =item B<-n> 315 | 316 | No descending into subdirectories. 317 | 318 | =item B<-o> 319 | 320 | Show only the part of each line matching PATTERN (turns off text 321 | highlighting) 322 | 323 | =item B<--output=I> 324 | 325 | Output the evaluation of I for each line (turns off text 326 | highlighting) 327 | 328 | =item B<--pager=I> 329 | 330 | Direct ack's output through I. This can also be specified 331 | via the C and C environment variables. 332 | 333 | Using --pager does not suppress grouping and coloring like piping 334 | output on the command-line does. 335 | 336 | =item B<--passthru> 337 | 338 | Prints all lines, whether or not they match the expression. Highlighting 339 | will still work, though, so it can be used to highlight matches while 340 | still seeing the entire file, as in: 341 | 342 | # Watch a log file, and highlight a certain IP address 343 | $ tail -f ~/access.log | ack --passthru 123.45.67.89 344 | 345 | =item B<--print0> 346 | 347 | Only works in conjunction with -f, -g, -l or -c (filename output). The filenames 348 | are output separated with a null byte instead of the usual newline. This is 349 | helpful when dealing with filenames that contain whitespace, e.g. 350 | 351 | # remove all files of type html 352 | ack -f --html --print0 | xargs -0 rm -f 353 | 354 | =item B<-Q>, B<--literal> 355 | 356 | Quote all metacharacters in PATTERN, it is treated as a literal. 357 | 358 | This applies only to the PATTERN, not to the regexes given for the B<-g> 359 | and B<-G> options. 360 | 361 | =item B<--rc=file> 362 | 363 | Specify a path to an alternate F<.ackrc> file. 364 | 365 | =item B<--sort-files> 366 | 367 | Sorts the found files lexically. Use this if you want your file 368 | listings to be deterministic between runs of I. 369 | 370 | =item B<--thpppt> 371 | 372 | Display the all-important Bill The Cat logo. Note that the exact 373 | spelling of B<--thpppppt> is not important. It's checked against 374 | a regular expression. 375 | 376 | =item B<--type=TYPE>, B<--type=noTYPE> 377 | 378 | Specify the types of files to include or exclude from a search. 379 | TYPE is a filetype, like I or I. B<--type=perl> can 380 | also be specified as B<--perl>, and B<--type=noperl> can be done 381 | as B<--noperl>. 382 | 383 | If a file is of both type "foo" and "bar", specifying --foo and 384 | --nobar will exclude the file, because an exclusion takes precedence 385 | over an inclusion. 386 | 387 | Type specifications can be repeated and are ORed together. 388 | 389 | See I for a list of valid types. 390 | 391 | =item B<--type-add I=I<.EXTENSION>[,I<.EXT2>[,...]]> 392 | 393 | Files with the given EXTENSION(s) are recognized as being of (the 394 | existing) type TYPE. See also L. 395 | 396 | 397 | =item B<--type-set I=I<.EXTENSION>[,I<.EXT2>[,...]]> 398 | 399 | Files with the given EXTENSION(s) are recognized as being of type 400 | TYPE. This replaces an existing definition for type TYPE. See also 401 | L. 402 | 403 | =item B<-u, --unrestricted> 404 | 405 | All files and directories (including blib/, core.*, ...) are searched, 406 | nothing is skipped. When both B<-u> and B<--ignore-dir> are used, the 407 | B<--ignore-dir> option has no effect. 408 | 409 | =item B<-v>, B<--invert-match> 410 | 411 | Invert match: select non-matching lines 412 | 413 | This applies only to the PATTERN, not to the regexes given for the B<-g> 414 | and B<-G> options. 415 | 416 | =item B<--version> 417 | 418 | Display version and copyright information. 419 | 420 | =item B<-w>, B<--word-regexp> 421 | 422 | Force PATTERN to match only whole words. The PATTERN is wrapped with 423 | C<\b> metacharacters. 424 | 425 | This applies only to the PATTERN, not to the regexes given for the B<-g> 426 | and B<-G> options. 427 | 428 | =item B<-1> 429 | 430 | Stops after reporting first match of any kind. This is different 431 | from B<--max-count=1> or B<-m1>, where only one match per file is 432 | shown. Also, B<-1> works with B<-f> and B<-g>, where B<-m> does 433 | not. 434 | 435 | =back 436 | 437 | =head1 THE .ackrc FILE 438 | 439 | The F<.ackrc> file contains command-line options that are prepended 440 | to the command line before processing. Multiple options may live 441 | on multiple lines. Lines beginning with a # are ignored. A F<.ackrc> 442 | might look like this: 443 | 444 | # Always sort the files 445 | --sort-files 446 | 447 | # Always color, even if piping to a another program 448 | --color 449 | 450 | # Use "less -r" as my pager 451 | --pager=less -r 452 | 453 | Note that arguments with spaces in them do not need to be quoted, 454 | as they are not interpreted by the shell. Basically, each I 455 | in the F<.ackrc> file is interpreted as one element of C<@ARGV>. 456 | 457 | F looks in your home directory for the F<.ackrc>. You can 458 | specify another location with the F variable, below. 459 | 460 | If B<--noenv> is specified on the command line, the F<.ackrc> file 461 | is ignored. 462 | 463 | =head1 Defining your own types 464 | 465 | ack allows you to define your own types in addition to the predefined 466 | types. This is done with command line options that are best put into 467 | an F<.ackrc> file - then you do not have to define your types over and 468 | over again. In the following examples the options will always be shown 469 | on one command line so that they can be easily copy & pasted. 470 | 471 | I searches for foo in all perl files. I 472 | tells you, that perl files are files ending 473 | in .pl, .pm, .pod or .t. So what if you would like to include .xs 474 | files as well when searching for --perl files? I 475 | does this for you. B<--type-add> appends 476 | additional extensions to an existing type. 477 | 478 | If you want to define a new type, or completely redefine an existing 479 | type, then use B<--type-set>. I defines the type I to include files with 481 | the extensions .e or .eiffel. So to search for all eiffel files 482 | containing the word Bertrand use I. 483 | As usual, you can also write B<--type=eiffel> 484 | instead of B<--eiffel>. Negation also works, so B<--noeiffel> excludes 485 | all eiffel files from a search. Redefining also works: I 486 | and I<.xs> files no longer belong to the type I. 487 | 488 | When defining your own types in the F<.ackrc> file you have to use 489 | the following: 490 | 491 | --type-set=eiffel=.e,.eiffel 492 | 493 | or writing on separate lines 494 | 495 | --type-set 496 | eiffel=.e,.eiffel 497 | 498 | The following does B work in the F<.ackrc> file: 499 | 500 | --type-set eiffel=.e,.eiffel 501 | 502 | 503 | In order to see all currently defined types, use I<--help types>, e.g. 504 | I 505 | 506 | Restrictions: 507 | 508 | =over 4 509 | 510 | =item 511 | 512 | The types 'skipped', 'make', 'binary' and 'text' are considered "builtin" and 513 | cannot be altered. 514 | 515 | =item 516 | 517 | The shebang line recognition of the types 'perl', 'ruby', 'php', 'python', 518 | 'shell' and 'xml' cannot be redefined by I<--type-set>, it is always 519 | active. However, the shebang line is only examined for files where the 520 | extension is not recognised. Therefore it is possible to say 521 | I and 522 | only find your shiny new I<.perl> files (and all files with unrecognized extension 523 | and perl on the shebang line). 524 | 525 | =back 526 | 527 | =head1 ENVIRONMENT VARIABLES 528 | 529 | For commonly-used ack options, environment variables can make life much easier. 530 | These variables are ignored if B<--noenv> is specified on the command line. 531 | 532 | =over 4 533 | 534 | =item ACKRC 535 | 536 | Specifies the location of the F<.ackrc> file. If this file doesn't 537 | exist, F looks in the default location. 538 | 539 | =item ACK_OPTIONS 540 | 541 | This variable specifies default options to be placed in front of 542 | any explicit options on the command line. 543 | 544 | =item ACK_COLOR_FILENAME 545 | 546 | Specifies the color of the filename when it's printed in B<--group> 547 | mode. By default, it's "bold green". 548 | 549 | The recognized attributes are clear, reset, dark, bold, underline, 550 | underscore, blink, reverse, concealed black, red, green, yellow, 551 | blue, magenta, on_black, on_red, on_green, on_yellow, on_blue, 552 | on_magenta, on_cyan, and on_white. Case is not significant. 553 | Underline and underscore are equivalent, as are clear and reset. 554 | The color alone sets the foreground color, and on_color sets the 555 | background color. 556 | 557 | =item ACK_COLOR_MATCH 558 | 559 | Specifies the color of the matching text when printed in B<--color> 560 | mode. By default, it's "black on_yellow". 561 | 562 | See B for the color specifications. 563 | 564 | =item ACK_PAGER 565 | 566 | Specifies a pager program, such as C, C or C, to which 567 | ack will send its output. 568 | 569 | Using C does not suppress grouping and coloring like 570 | piping output on the command-line does, except that on Windows 571 | ack will assume that C does not support color. 572 | 573 | C overrides C if both are specified. 574 | 575 | =item ACK_PAGER_COLOR 576 | 577 | Specifies a pager program that understands ANSI color sequences. 578 | Using C does not suppress grouping and coloring 579 | like piping output on the command-line does. 580 | 581 | If you are not on Windows, you never need to use C. 582 | 583 | =back 584 | 585 | =head1 ACK & OTHER TOOLS 586 | 587 | =head2 Vim integration 588 | 589 | F integrates easily with the Vim text editor. Set this in your 590 | F<.vimrc> to use F instead of F: 591 | 592 | set grepprg=ack\ -a 593 | 594 | That examples uses C<-a> to search through all files, but you may 595 | use other default flags. Now you can search with F and easily 596 | step through the results in Vim: 597 | 598 | :grep Dumper perllib 599 | 600 | =head2 Emacs integration 601 | 602 | Phil Jackson put together an F extension that "provides a 603 | simple compilation mode ... has the ability to guess what files you 604 | want to search for based on the major-mode." 605 | 606 | L 607 | 608 | =head2 TextMate integration 609 | 610 | Pedro Melo is a TextMate user who writes "I spend my day mostly 611 | inside TextMate, and the built-in find-in-project sucks with large 612 | projects. So I hacked a TextMate command that was using find + 613 | grep to use ack. The result is the Search in Project with ack, and 614 | you can find it here: 615 | L" 616 | 617 | =cut 618 | 619 | =head1 DEBUGGING ACK PROBLEMS 620 | 621 | If ack gives you output you're not expecting, start with a few simple steps. 622 | 623 | =head2 Use B<--noenv> 624 | 625 | Your environment variables and F<.ackrc> may be doing things you're 626 | not expecting, or forgotten you specified. Use B<--noenv> to ignore 627 | your environment and F<.ackrc>. 628 | 629 | =head2 Use B<-f> to see what files you're scanning 630 | 631 | The reason I created B<-f> in the first place was as a debugging 632 | tool. If ack is not finding matches you think it should find, run 633 | F to see what files are being checked. 634 | 635 | =head1 AUTHOR 636 | 637 | Andy Lester, C<< >> 638 | 639 | =head1 BUGS 640 | 641 | Please report any bugs or feature requests to the issues list at 642 | Google Code: L 643 | 644 | =head1 ENHANCEMENTS 645 | 646 | All enhancement requests MUST first be posted to the ack-users 647 | mailing list at L. I 648 | will not consider a request without it first getting seen by other 649 | ack users. 650 | 651 | There is a list of enhancements I want to make to F in the ack 652 | issues list at Google Code: L 653 | 654 | Patches are always welcome, but patches with tests get the most 655 | attention. 656 | 657 | =head1 SUPPORT 658 | 659 | Support for and information about F can be found at: 660 | 661 | =over 4 662 | 663 | =item * The ack homepage 664 | 665 | L 666 | 667 | =item * The ack issues list at Google Code 668 | 669 | L 670 | 671 | =item * AnnoCPAN: Annotated CPAN documentation 672 | 673 | L 674 | 675 | =item * CPAN Ratings 676 | 677 | L 678 | 679 | =item * Search CPAN 680 | 681 | L 682 | 683 | =item * Subversion repository 684 | 685 | L 686 | 687 | =back 688 | 689 | =head1 ACKNOWLEDGEMENTS 690 | 691 | How appropriate to have Inowledgements! 692 | 693 | Thanks to everyone who has contributed to ack in any way, including 694 | Pedro Melo, 695 | AJ Schuster, 696 | Phil Jackson, 697 | Michael Schwern, 698 | Jan Dubois, 699 | Christopher J. Madsen, 700 | Matthew Wickline, 701 | David Dyck, 702 | Jason Porritt, 703 | Jjgod Jiang, 704 | Thomas Klausner, 705 | Uri Guttman, 706 | Peter Lewis, 707 | Kevin Riggle, 708 | Ori Avtalion, 709 | Torsten Blix, 710 | Nigel Metheringham, 711 | Gábor Szabó, 712 | Tod Hagan, 713 | Michael Hendricks, 714 | Ævar Arnfjörð Bjarmason, 715 | Piers Cawley, 716 | Stephen Steneker, 717 | Elias Lutfallah, 718 | Mark Leighton Fisher, 719 | Matt Diephouse, 720 | Christian Jaeger, 721 | Bill Sully, 722 | Bill Ricker, 723 | David Golden, 724 | Nilson Santos F. Jr, 725 | Elliot Shank, 726 | Merijn Broeren, 727 | Uwe Voelker, 728 | Rick Scott, 729 | Ask Bjørn Hansen, 730 | Jerry Gay, 731 | Will Coleda, 732 | Mike O'Regan, 733 | Slaven Rezić, 734 | Mark Stosberg, 735 | David Alan Pisoni, 736 | Adriano Ferreira, 737 | James Keenan, 738 | Leland Johnson, 739 | Ricardo Signes 740 | and Pete Krawczyk. 741 | 742 | =head1 COPYRIGHT & LICENSE 743 | 744 | Copyright 2005-2008 Andy Lester, all rights reserved. 745 | 746 | This program is free software; you can redistribute it and/or modify it 747 | under the same terms as Perl itself. 748 | 749 | =cut 750 | package File::Next; 751 | 752 | use strict; 753 | use warnings; 754 | 755 | 756 | our $VERSION = '1.02'; 757 | 758 | 759 | 760 | use File::Spec (); 761 | 762 | 763 | our $name; # name of the current file 764 | our $dir; # dir of the current file 765 | 766 | our %files_defaults; 767 | our %skip_dirs; 768 | 769 | BEGIN { 770 | %files_defaults = ( 771 | file_filter => undef, 772 | descend_filter => undef, 773 | error_handler => sub { CORE::die @_ }, 774 | sort_files => undef, 775 | follow_symlinks => 1, 776 | ); 777 | %skip_dirs = map {($_,1)} (File::Spec->curdir, File::Spec->updir); 778 | } 779 | 780 | 781 | sub files { 782 | my ($parms,@queue) = _setup( \%files_defaults, @_ ); 783 | my $filter = $parms->{file_filter}; 784 | 785 | return sub { 786 | while (@queue) { 787 | my ($dir,$file,$fullpath) = splice( @queue, 0, 3 ); 788 | if ( -f $fullpath ) { 789 | if ( $filter ) { 790 | local $_ = $file; 791 | local $File::Next::dir = $dir; 792 | local $File::Next::name = $fullpath; 793 | next if not $filter->(); 794 | } 795 | return wantarray ? ($dir,$file,$fullpath) : $fullpath; 796 | } 797 | elsif ( -d _ ) { 798 | unshift( @queue, _candidate_files( $parms, $fullpath ) ); 799 | } 800 | } # while 801 | 802 | return; 803 | }; # iterator 804 | } 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] }; 813 | sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] }; 814 | 815 | sub reslash { 816 | my $path = shift; 817 | 818 | my @parts = split( /\//, $path ); 819 | 820 | return $path if @parts < 2; 821 | 822 | return File::Spec->catfile( @parts ); 823 | } 824 | 825 | 826 | 827 | sub _setup { 828 | my $defaults = shift; 829 | my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash 830 | 831 | my %passed_parms = %{$passed_parms}; 832 | 833 | my $parms = {}; 834 | for my $key ( keys %{$defaults} ) { 835 | $parms->{$key} = 836 | exists $passed_parms{$key} 837 | ? delete $passed_parms{$key} 838 | : $defaults->{$key}; 839 | } 840 | 841 | # Any leftover keys are bogus 842 | for my $badkey ( keys %passed_parms ) { 843 | my $sub = (caller(1))[3]; 844 | $parms->{error_handler}->( "Invalid option passed to $sub(): $badkey" ); 845 | } 846 | 847 | # If it's not a code ref, assume standard sort 848 | if ( $parms->{sort_files} && ( ref($parms->{sort_files}) ne 'CODE' ) ) { 849 | $parms->{sort_files} = \&sort_standard; 850 | } 851 | my @queue; 852 | 853 | for ( @_ ) { 854 | my $start = reslash( $_ ); 855 | if (-d $start) { 856 | push @queue, ($start,undef,$start); 857 | } 858 | else { 859 | push @queue, (undef,$start,$start); 860 | } 861 | } 862 | 863 | return ($parms,@queue); 864 | } 865 | 866 | 867 | sub _candidate_files { 868 | my $parms = shift; 869 | my $dir = shift; 870 | 871 | my $dh; 872 | if ( !opendir $dh, $dir ) { 873 | $parms->{error_handler}->( "$dir: $!" ); 874 | return; 875 | } 876 | 877 | my @newfiles; 878 | my $descend_filter = $parms->{descend_filter}; 879 | my $follow_symlinks = $parms->{follow_symlinks}; 880 | my $sort_sub = $parms->{sort_files}; 881 | 882 | while ( defined ( my $file = readdir $dh ) ) { 883 | next if $skip_dirs{$file}; 884 | my $has_stat; 885 | 886 | # Only do directory checking if we have a descend_filter 887 | my $fullpath = File::Spec->catdir( $dir, $file ); 888 | if ( !$follow_symlinks ) { 889 | next if -l $fullpath; 890 | $has_stat = 1; 891 | } 892 | 893 | if ( $descend_filter ) { 894 | if ( $has_stat ? (-d _) : (-d $fullpath) ) { 895 | local $File::Next::dir = $fullpath; 896 | local $_ = $file; 897 | next if not $descend_filter->(); 898 | } 899 | } 900 | if ( $sort_sub ) { 901 | push( @newfiles, [ $dir, $file, $fullpath ] ); 902 | } 903 | else { 904 | push( @newfiles, $dir, $file, $fullpath ); 905 | } 906 | } 907 | closedir $dh; 908 | 909 | if ( $sort_sub ) { 910 | return map { @{$_} } sort $sort_sub @newfiles; 911 | } 912 | 913 | return @newfiles; 914 | } 915 | 916 | 917 | 1; # End of File::Next 918 | package App::Ack; 919 | 920 | use warnings; 921 | use strict; 922 | 923 | 924 | 925 | our $VERSION; 926 | our $COPYRIGHT; 927 | BEGIN { 928 | $VERSION = '1.84'; 929 | $COPYRIGHT = 'Copyright 2005-2008 Andy Lester, all rights reserved.'; 930 | } 931 | 932 | our $fh; 933 | 934 | BEGIN { 935 | $fh = *STDOUT; 936 | } 937 | 938 | 939 | our %types; 940 | our %type_wanted; 941 | our %mappings; 942 | our %ignore_dirs; 943 | 944 | our $dir_sep_chars; 945 | our $is_cygwin; 946 | our $is_windows; 947 | our $to_screen; 948 | 949 | use File::Spec (); 950 | use File::Glob ':glob'; 951 | use Getopt::Long (); 952 | 953 | BEGIN { 954 | %ignore_dirs = ( 955 | '.bzr' => 'Bazaar', 956 | '.cdv' => 'Codeville', 957 | '~.dep' => 'Interface Builder', 958 | '~.dot' => 'Interface Builder', 959 | '~.nib' => 'Interface Builder', 960 | '~.plst' => 'Interface Builder', 961 | '.git' => 'Git', 962 | '.hg' => 'Mercurial', 963 | '.pc' => 'quilt', 964 | '.svn' => 'Subversion', 965 | blib => 'Perl module building', 966 | CVS => 'CVS', 967 | RCS => 'RCS', 968 | SCCS => 'SCCS', 969 | _darcs => 'darcs', 970 | _sgbak => 'Vault/Fortress', 971 | 'autom4te.cache' => 'autoconf', 972 | 'cover_db' => 'Devel::Cover', 973 | _build => 'Module::Build', 974 | ); 975 | 976 | %mappings = ( 977 | actionscript => [qw( as mxml )], 978 | asm => [qw( asm s )], 979 | batch => [qw( bat cmd )], 980 | binary => q{Binary files, as defined by Perl's -B op (default: off)}, 981 | cc => [qw( c h xs )], 982 | cfmx => [qw( cfc cfm cfml )], 983 | cpp => [qw( cpp cc cxx m hpp hh h hxx )], 984 | csharp => [qw( cs )], 985 | css => [qw( css )], 986 | elisp => [qw( el )], 987 | erlang => [qw( erl )], 988 | fortran => [qw( f f77 f90 f95 f03 for ftn fpp )], 989 | haskell => [qw( hs lhs )], 990 | hh => [qw( h )], 991 | html => [qw( htm html shtml xhtml )], 992 | java => [qw( java properties )], 993 | js => [qw( js )], 994 | jsp => [qw( jsp jspx jhtm jhtml )], 995 | lisp => [qw( lisp lsp )], 996 | lua => [qw( lua )], 997 | make => q{Makefiles}, 998 | mason => [qw( mas mhtml mpl mtxt )], 999 | objc => [qw( m h )], 1000 | objcpp => [qw( mm h )], 1001 | ocaml => [qw( ml mli )], 1002 | parrot => [qw( pir pasm pmc ops pod pg tg )], 1003 | perl => [qw( pl pm pod t )], 1004 | php => [qw( php phpt php3 php4 php5 )], 1005 | plone => [qw( pt cpt metadata cpy py )], 1006 | python => [qw( py )], 1007 | ruby => [qw( rb rhtml rjs rxml erb )], 1008 | scheme => [qw( scm )], 1009 | shell => [qw( sh bash csh tcsh ksh zsh )], 1010 | skipped => q{Files, but not directories, normally skipped by ack (default: off)}, 1011 | smalltalk => [qw( st )], 1012 | sql => [qw( sql ctl )], 1013 | tcl => [qw( tcl itcl itk )], 1014 | tex => [qw( tex cls sty )], 1015 | text => q{Text files, as defined by Perl's -T op (default: off)}, 1016 | tt => [qw( tt tt2 ttml )], 1017 | vb => [qw( bas cls frm ctl vb resx )], 1018 | vim => [qw( vim )], 1019 | yaml => [qw( yaml yml )], 1020 | xml => [qw( xml dtd xslt ent )], 1021 | ); 1022 | 1023 | while ( my ($type,$exts) = each %mappings ) { 1024 | if ( ref $exts ) { 1025 | for my $ext ( @{$exts} ) { 1026 | push( @{$types{$ext}}, $type ); 1027 | } 1028 | } 1029 | } 1030 | 1031 | $is_cygwin = ($^O eq 'cygwin'); 1032 | $is_windows = ($^O =~ /MSWin32/); 1033 | $to_screen = -t *STDOUT; 1034 | $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) ); 1035 | } 1036 | 1037 | 1038 | sub read_ackrc { 1039 | my @files = ( $ENV{ACKRC} ); 1040 | my @dirs = 1041 | $is_windows 1042 | ? ( $ENV{HOME}, $ENV{USERPROFILE} ) 1043 | : ( '~', $ENV{HOME} ); 1044 | for my $dir ( grep { defined } @dirs ) { 1045 | for my $file ( '.ackrc', '_ackrc' ) { 1046 | push( @files, bsd_glob( "$dir/$file", GLOB_TILDE ) ); 1047 | } 1048 | } 1049 | for my $filename ( @files ) { 1050 | if ( defined $filename && -e $filename ) { 1051 | open( my $fh, '<', $filename ) or App::Ack::die( "$filename: $!\n" ); 1052 | my @lines = grep { /./ && !/^\s*#/ } <$fh>; 1053 | chomp @lines; 1054 | close $fh or App::Ack::die( "$filename: $!\n" ); 1055 | 1056 | return @lines; 1057 | } 1058 | } 1059 | 1060 | return; 1061 | } 1062 | 1063 | 1064 | sub get_command_line_options { 1065 | my %opt = ( 1066 | pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER}, 1067 | ); 1068 | 1069 | my $getopt_specs = { 1070 | 1 => sub { $opt{1} = $opt{m} = 1 }, 1071 | 'A|after-context=i' => \$opt{after_context}, 1072 | 'B|before-context=i' => \$opt{before_context}, 1073 | 'C|context:i' => sub { shift; my $val = shift; $opt{before_context} = $opt{after_context} = ($val || 2) }, 1074 | 'a|all-types' => \$opt{all}, 1075 | 'break!' => \$opt{break}, 1076 | c => \$opt{count}, 1077 | 'color!' => \$opt{color}, 1078 | count => \$opt{count}, 1079 | 'env!' => sub { }, # ignore this option, it is handled beforehand 1080 | f => \$opt{f}, 1081 | flush => \$opt{flush}, 1082 | 'follow!' => \$opt{follow}, 1083 | 'g=s' => sub { shift; $opt{G} = shift; $opt{f} = 1 }, 1084 | 'G=s' => \$opt{G}, 1085 | 'group!' => sub { shift; $opt{heading} = $opt{break} = shift }, 1086 | 'heading!' => \$opt{heading}, 1087 | 'h|no-filename' => \$opt{h}, 1088 | 'H|with-filename' => \$opt{H}, 1089 | 'i|ignore-case' => \$opt{i}, 1090 | 'lines=s' => sub { shift; my $val = shift; push @{$opt{lines}}, $val }, 1091 | 'l|files-with-matches' => \$opt{l}, 1092 | 'L|files-without-match' => sub { $opt{l} = $opt{v} = 1 }, 1093 | 'm|max-count=i' => \$opt{m}, 1094 | 'match=s' => \$opt{regex}, 1095 | n => \$opt{n}, 1096 | o => sub { $opt{output} = '$&' }, 1097 | 'output=s' => \$opt{output}, 1098 | 'pager=s' => \$opt{pager}, 1099 | 'nopager' => sub { $opt{pager} = undef }, 1100 | 'passthru' => \$opt{passthru}, 1101 | 'print0' => \$opt{print0}, 1102 | 'Q|literal' => \$opt{Q}, 1103 | 'sort-files' => \$opt{sort_files}, 1104 | 'u|unrestricted' => \$opt{u}, 1105 | 'v|invert-match' => \$opt{v}, 1106 | 'w|word-regexp' => \$opt{w}, 1107 | 1108 | 'ignore-dirs=s' => sub { shift; my $dir = remove_dir_sep( shift ); $ignore_dirs{$dir} = '--ignore-dirs' }, 1109 | 'noignore-dirs=s' => sub { shift; my $dir = remove_dir_sep( shift ); delete $ignore_dirs{$dir} }, 1110 | 1111 | 'version' => sub { print_version_statement(); exit 1; }, 1112 | 'help|?:s' => sub { shift; show_help(@_); exit; }, 1113 | 'help-types'=> sub { show_help_types(); exit; }, 1114 | 'man' => sub { require Pod::Usage; Pod::Usage::pod2usage({-verbose => 2}); exit; }, 1115 | 1116 | 'type=s' => sub { 1117 | # Whatever --type=xxx they specify, set it manually in the hash 1118 | my $dummy = shift; 1119 | my $type = shift; 1120 | my $wanted = ($type =~ s/^no//) ? 0 : 1; # must not be undef later 1121 | 1122 | if ( exists $type_wanted{ $type } ) { 1123 | $type_wanted{ $type } = $wanted; 1124 | } 1125 | else { 1126 | App::Ack::die( qq{Unknown --type "$type"} ); 1127 | } 1128 | }, # type sub 1129 | }; 1130 | 1131 | # Stick any default switches at the beginning, so they can be overridden 1132 | # by the command line switches. 1133 | unshift @ARGV, split( ' ', $ENV{ACK_OPTIONS} ) if defined $ENV{ACK_OPTIONS}; 1134 | 1135 | # first pass through options, looking for type definitions 1136 | def_types_from_ARGV(); 1137 | 1138 | for my $i ( filetypes_supported() ) { 1139 | $getopt_specs->{ "$i!" } = \$type_wanted{ $i }; 1140 | } 1141 | 1142 | 1143 | my $parser = Getopt::Long::Parser->new(); 1144 | $parser->configure( 'bundling', 'no_ignore_case', ); 1145 | $parser->getoptions( %{$getopt_specs} ) or 1146 | App::Ack::die( 'See ack --help or ack --man for options.' ); 1147 | 1148 | my %defaults = ( 1149 | all => 0, 1150 | color => $to_screen, 1151 | follow => 0, 1152 | break => $to_screen, 1153 | heading => $to_screen, 1154 | before_context => 0, 1155 | after_context => 0, 1156 | ); 1157 | if ( $is_windows && $defaults{color} && not $ENV{ACK_PAGER_COLOR} ) { 1158 | if ( $ENV{ACK_PAGER} || not eval { require Win32::Console::ANSI } ) { 1159 | $defaults{color} = 0; 1160 | } 1161 | } 1162 | if ( $to_screen && $ENV{ACK_PAGER_COLOR} ) { 1163 | $defaults{color} = 1; 1164 | } 1165 | 1166 | while ( my ($key,$value) = each %defaults ) { 1167 | if ( not defined $opt{$key} ) { 1168 | $opt{$key} = $value; 1169 | } 1170 | } 1171 | 1172 | if ( defined $opt{m} && $opt{m} <= 0 ) { 1173 | App::Ack::die( '-m must be greater than zero' ); 1174 | } 1175 | 1176 | for ( qw( before_context after_context ) ) { 1177 | if ( defined $opt{$_} && $opt{$_} < 0 ) { 1178 | App::Ack::die( "--$_ may not be negative" ); 1179 | } 1180 | } 1181 | 1182 | if ( defined( my $val = $opt{output} ) ) { 1183 | $opt{output} = eval qq[ sub { "$val" } ]; 1184 | } 1185 | if ( defined( my $l = $opt{lines} ) ) { 1186 | # --line=1 --line=5 is equivalent to --line=1,5 1187 | my @lines = split( /,/, join( ',', @{$l} ) ); 1188 | 1189 | # --line=1-3 is equivalent to --line=1,2,3 1190 | @lines = map { 1191 | my @ret; 1192 | if ( /-/ ) { 1193 | my ($from, $to) = split /-/, $_; 1194 | if ( $from > $to ) { 1195 | App::Ack::warn( "ignoring --line=$from-$to" ); 1196 | @ret = (); 1197 | } 1198 | else { 1199 | @ret = ( $from .. $to ); 1200 | } 1201 | } 1202 | else { 1203 | @ret = ( $_ ); 1204 | }; 1205 | @ret 1206 | } @lines; 1207 | 1208 | if ( @lines ) { 1209 | my %uniq; 1210 | @uniq{ @lines } = (); 1211 | $opt{lines} = [ sort { $a <=> $b } keys %uniq ]; # numerical sort and each line occurs only once! 1212 | } 1213 | else { 1214 | # happens if there are only ignored --line directives 1215 | App::Ack::die( 'All --line options are invalid.' ); 1216 | } 1217 | } 1218 | 1219 | return \%opt; 1220 | } 1221 | 1222 | 1223 | sub def_types_from_ARGV { 1224 | my @typedef; 1225 | 1226 | my $parser = Getopt::Long::Parser->new(); 1227 | # pass_through => leave unrecognized command line arguments alone 1228 | # no_auto_abbrev => otherwise -c is expanded and not left alone 1229 | $parser->configure( 'no_ignore_case', 'pass_through', 'no_auto_abbrev' ); 1230 | $parser->getoptions( 1231 | 'type-set=s' => sub { shift; push @typedef, ['c', shift] }, 1232 | 'type-add=s' => sub { shift; push @typedef, ['a', shift] }, 1233 | ) or App::Ack::die( 'See ack --help or ack --man for options.' ); 1234 | 1235 | for my $td (@typedef) { 1236 | my ($type, $ext) = split /=/, $td->[1]; 1237 | 1238 | if ( $td->[0] eq 'c' ) { 1239 | # type-set 1240 | if ( exists $mappings{$type} ) { 1241 | # can't redefine types 'make', 'skipped', 'text' and 'binary' 1242 | App::Ack::die( qq{--type-set: Builtin type "$type" cannot be changed.} ) 1243 | if ref $mappings{$type} ne 'ARRAY'; 1244 | 1245 | delete_type($type); 1246 | } 1247 | } 1248 | else { 1249 | # type-add 1250 | 1251 | # can't append to types 'make', 'skipped', 'text' and 'binary' 1252 | App::Ack::die( qq{--type-add: Builtin type "$type" cannot be changed.} ) 1253 | if exists $mappings{$type} && ref $mappings{$type} ne 'ARRAY'; 1254 | 1255 | App::Ack::warn( qq{--type-add: Type "$type" does not exist, creating with "$ext" ...} ) 1256 | unless exists $mappings{$type}; 1257 | } 1258 | 1259 | my @exts = map { s/^\.//; $_ } split ',', $ext; # %types stores extensions without leading '.' 1260 | 1261 | if ( !exists $mappings{$type} || ref($mappings{$type}) eq 'ARRAY' ) { 1262 | push @{$mappings{$type}}, @exts; 1263 | for my $e ( @exts ) { 1264 | push @{$types{$e}}, $type; 1265 | } 1266 | } 1267 | else { 1268 | App::Ack::die( qq{Cannot append to type "$type".} ); 1269 | } 1270 | } 1271 | 1272 | return; 1273 | } 1274 | 1275 | 1276 | sub delete_type { 1277 | my $type = shift; 1278 | 1279 | App::Ack::die( qq{Internal error: Cannot delete builtin type "$type".} ) 1280 | unless ref $mappings{$type} eq 'ARRAY'; 1281 | 1282 | delete $mappings{$type}; 1283 | delete $type_wanted{$type}; 1284 | for my $ext ( keys %types ) { 1285 | $types{$ext} = [ grep { $_ ne $type } @{$types{$ext}} ]; 1286 | } 1287 | } 1288 | 1289 | 1290 | sub ignoredir_filter { 1291 | return !exists $ignore_dirs{$_}; 1292 | } 1293 | 1294 | 1295 | sub remove_dir_sep { 1296 | my $path = shift; 1297 | $path =~ s/[$dir_sep_chars]$//; 1298 | 1299 | return $path; 1300 | } 1301 | 1302 | 1303 | use constant TEXT => 'text'; 1304 | 1305 | sub filetypes { 1306 | my $filename = shift; 1307 | 1308 | return 'skipped' unless is_searchable( $filename ); 1309 | 1310 | return ('make',TEXT) if $filename =~ m{[$dir_sep_chars]?Makefile$}io; 1311 | 1312 | # If there's an extension, look it up 1313 | if ( $filename =~ m{\.([^\.$dir_sep_chars]+)$}o ) { 1314 | my $ref = $types{lc $1}; 1315 | return (@{$ref},TEXT) if $ref; 1316 | } 1317 | 1318 | # At this point, we can't tell from just the name. Now we have to 1319 | # open it and look inside. 1320 | 1321 | return unless -e $filename; 1322 | # From Elliot Shank: 1323 | # I can't see any reason that -r would fail on these-- the ACLs look 1324 | # fine, and no program has any of them open, so the busted Windows 1325 | # file locking model isn't getting in there. If I comment the if 1326 | # statement out, everything works fine 1327 | # So, for cygwin, don't bother trying to check for readability. 1328 | if ( !$is_cygwin ) { 1329 | if ( !-r $filename ) { 1330 | App::Ack::warn( "$filename: Permission denied" ); 1331 | return; 1332 | } 1333 | } 1334 | 1335 | return 'binary' if -B $filename; 1336 | 1337 | # If there's no extension, or we don't recognize it, check the shebang line 1338 | my $fh; 1339 | if ( !open( $fh, '<', $filename ) ) { 1340 | App::Ack::warn( "$filename: $!" ); 1341 | return; 1342 | } 1343 | my $header = <$fh>; 1344 | close_file( $fh, $filename ) or return; 1345 | 1346 | if ( $header =~ /^#!/ ) { 1347 | return ($1,TEXT) if $header =~ /\b(ruby|p(?:erl|hp|ython))\b/; 1348 | return ('shell',TEXT) if $header =~ /\b(?:ba|c|k|z)?sh\b/; 1349 | } 1350 | else { 1351 | return ('xml',TEXT) if $header =~ /\Q{Q}; 1375 | if ( $opt->{w} ) { 1376 | $str = "\\b$str" if $str =~ /^\w/; 1377 | $str = "$str\\b" if $str =~ /\w$/; 1378 | } 1379 | 1380 | return $str; 1381 | } 1382 | 1383 | 1384 | sub check_regex { 1385 | my $regex = shift; 1386 | 1387 | return unless defined $regex; 1388 | 1389 | eval { qr/$regex/ }; 1390 | if ($@) { 1391 | (my $error = $@) =~ s/ at \S+ line \d+.*//; 1392 | chomp($error); 1393 | App::Ack::die( "Invalid regex '$regex':\n $error" ); 1394 | } 1395 | 1396 | return; 1397 | } 1398 | 1399 | 1400 | 1401 | 1402 | sub warn { 1403 | return CORE::warn( _my_program(), ': ', @_, "\n" ); 1404 | } 1405 | 1406 | 1407 | sub die { 1408 | return CORE::die( _my_program(), ': ', @_, "\n" ); 1409 | } 1410 | 1411 | sub _my_program { 1412 | require File::Basename; 1413 | return File::Basename::basename( $0 ); 1414 | } 1415 | 1416 | 1417 | 1418 | sub filetypes_supported { 1419 | return keys %mappings; 1420 | } 1421 | 1422 | sub _get_thpppt { 1423 | my $y = q{_ /|,\\'!.x',=(www)=, U }; 1424 | $y =~ tr/,x!w/\nOo_/; 1425 | return $y; 1426 | } 1427 | 1428 | sub _thpppt { 1429 | my $y = _get_thpppt(); 1430 | App::Ack::print( "$y ack $_[0]!\n" ); 1431 | exit 0; 1432 | } 1433 | 1434 | sub _key { 1435 | my $str = lc shift; 1436 | $str =~ s/[^a-z]//g; 1437 | 1438 | return $str; 1439 | } 1440 | 1441 | 1442 | sub show_help { 1443 | my $help_arg = shift || 0; 1444 | 1445 | return show_help_types() if $help_arg =~ /^types?/; 1446 | 1447 | my $ignore_dirs = _listify( sort { _key($a) cmp _key($b) } keys %ignore_dirs ); 1448 | 1449 | App::Ack::print( <<"END_OF_HELP" ); 1450 | Usage: ack [OPTION]... PATTERN [FILES] 1451 | 1452 | Search for PATTERN in each source file in the tree from cwd on down. 1453 | If [FILES] is specified, then only those files/directories are checked. 1454 | ack may also search STDIN, but only if no FILES are specified, or if 1455 | one of FILES is "-". 1456 | 1457 | Default switches may be specified in ACK_OPTIONS environment variable or 1458 | an .ackrc file. If you want no dependency on the environment, turn it 1459 | off with --noenv. 1460 | 1461 | Example: ack -i select 1462 | 1463 | Searching: 1464 | -i, --ignore-case Ignore case distinctions in PATTERN 1465 | -v, --invert-match Invert match: select non-matching lines 1466 | -w, --word-regexp Force PATTERN to match only whole words 1467 | -Q, --literal Quote all metacharacters; PATTERN is literal 1468 | 1469 | Search output: 1470 | --line=NUM Only print line(s) NUM of each file 1471 | -l, --files-with-matches 1472 | Only print filenames containing matches 1473 | -L, --files-without-match 1474 | Only print filenames with no match 1475 | -o Show only the part of a line matching PATTERN 1476 | (turns off text highlighting) 1477 | --passthru Print all lines, whether matching or not 1478 | --output=expr Output the evaluation of expr for each line 1479 | (turns off text highlighting) 1480 | --match PATTERN Specify PATTERN explicitly. 1481 | -m, --max-count=NUM Stop searching in each file after NUM matches 1482 | -1 Stop searching after one match of any kind 1483 | -H, --with-filename Print the filename for each match 1484 | -h, --no-filename Suppress the prefixing filename on output 1485 | -c, --count Show number of lines matching per file 1486 | 1487 | -A NUM, --after-context=NUM 1488 | Print NUM lines of trailing context after matching 1489 | lines. 1490 | -B NUM, --before-context=NUM 1491 | Print NUM lines of leading context before matching 1492 | lines. 1493 | -C [NUM], --context[=NUM] 1494 | Print NUM lines (default 2) of output context. 1495 | 1496 | --print0 Print null byte as separator between filenames, 1497 | only works with -f, -g, -l, -L or -c. 1498 | 1499 | File presentation: 1500 | --pager=COMMAND Pipes all ack output through COMMAND. 1501 | Ignored if output is redirected. 1502 | --nopager Do not send output through a pager. Cancels any 1503 | setting in ~/.ackrc, ACK_PAGER or ACK_PAGER_COLOR. 1504 | --[no]heading Print a filename heading above each file's results. 1505 | (default: on when used interactively) 1506 | --[no]break Print a break between results from different files. 1507 | (default: on when used interactively) 1508 | --group Same as --heading --break 1509 | --nogroup Same as --noheading --nobreak 1510 | --[no]color Highlight the matching text (default: on unless 1511 | output is redirected, or on Windows) 1512 | --flush Flush output immediately, even when ack is used 1513 | non-interactively (when output goes to a pipe or 1514 | file). 1515 | 1516 | File finding: 1517 | -f Only print the files found, without searching. 1518 | The PATTERN must not be specified. 1519 | -g REGEX Same as -f, but only print files matching REGEX. 1520 | --sort-files Sort the found files lexically. 1521 | 1522 | File inclusion/exclusion: 1523 | -a, --all-types All file types searched; 1524 | Ignores CVS, .svn and other ignored directories 1525 | -u, --unrestricted All files and directories searched 1526 | --[no]ignore-dir=name Add/Remove directory from the list of ignored dirs 1527 | -n No descending into subdirectories 1528 | -G REGEX Only search files that match REGEX 1529 | 1530 | --perl Include only Perl files. 1531 | --type=perl Include only Perl files. 1532 | --noperl Exclude Perl files. 1533 | --type=noperl Exclude Perl files. 1534 | See "ack --help type" for supported filetypes. 1535 | 1536 | --type-add TYPE=.EXTENSION[,.EXT2[,...]] 1537 | Files with the given EXTENSION(s) are recognized as 1538 | being of (the existing) type TYPE 1539 | --type-set TYPE=.EXTENSION[,.EXT2[,...]] 1540 | Files with the given EXTENSION(s) are recognized as 1541 | being of type TYPE. This replaces an existing 1542 | definition for type TYPE. 1543 | 1544 | --[no]follow Follow symlinks. Default is off. 1545 | 1546 | Directories ignored by default: 1547 | $ignore_dirs 1548 | 1549 | Files not checked for type: 1550 | /~\$/ - Unix backup files 1551 | /#.+#\$/ - Emacs swap files 1552 | /[._].*\\.swp\$/ - Vi(m) swap files 1553 | /core\\.\\d+\$/ - core dumps 1554 | 1555 | Miscellaneous: 1556 | --noenv Ignore environment variables and ~/.ackrc 1557 | --help This help 1558 | --man Man page 1559 | --version Display version & copyright 1560 | --thpppt Bill the Cat 1561 | 1562 | This is version $VERSION of ack. 1563 | END_OF_HELP 1564 | 1565 | return; 1566 | } 1567 | 1568 | 1569 | 1570 | sub show_help_types { 1571 | App::Ack::print( <<'END_OF_HELP' ); 1572 | Usage: ack [OPTION]... PATTERN [FILES] 1573 | 1574 | The following is the list of filetypes supported by ack. You can 1575 | specify a file type with the --type=TYPE format, or the --TYPE 1576 | format. For example, both --type=perl and --perl work. 1577 | 1578 | Note that some extensions may appear in multiple types. For example, 1579 | .pod files are both Perl and Parrot. 1580 | 1581 | END_OF_HELP 1582 | 1583 | my @types = filetypes_supported(); 1584 | my $maxlen = 0; 1585 | for ( @types ) { 1586 | $maxlen = length if $maxlen < length; 1587 | } 1588 | for my $type ( sort @types ) { 1589 | next if $type =~ /^-/; # Stuff to not show 1590 | my $ext_list = $mappings{$type}; 1591 | 1592 | if ( ref $ext_list ) { 1593 | $ext_list = join( ' ', map { ".$_" } @{$ext_list} ); 1594 | } 1595 | App::Ack::print( sprintf( " --[no]%-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) ); 1596 | } 1597 | 1598 | return; 1599 | } 1600 | 1601 | sub _listify { 1602 | my @whats = @_; 1603 | 1604 | return '' if !@whats; 1605 | 1606 | my $end = pop @whats; 1607 | my $str = @whats ? join( ', ', @whats ) . " and $end" : $end; 1608 | 1609 | no warnings 'once'; 1610 | require Text::Wrap; 1611 | $Text::Wrap::columns = 75; 1612 | return Text::Wrap::wrap( '', ' ', $str ); 1613 | } 1614 | 1615 | 1616 | sub get_version_statement { 1617 | my $copyright = get_copyright(); 1618 | return <<"END_OF_VERSION"; 1619 | ack $VERSION 1620 | 1621 | $copyright 1622 | 1623 | This program is free software; you can redistribute it and/or modify it 1624 | under the same terms as Perl itself. 1625 | END_OF_VERSION 1626 | } 1627 | 1628 | 1629 | sub print_version_statement { 1630 | App::Ack::print( get_version_statement() ); 1631 | 1632 | return; 1633 | } 1634 | 1635 | 1636 | sub get_copyright { 1637 | return $COPYRIGHT; 1638 | } 1639 | 1640 | 1641 | sub load_colors { 1642 | eval 'use Term::ANSIColor ()'; 1643 | 1644 | $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow'; 1645 | $ENV{ACK_COLOR_FILENAME} ||= 'bold green'; 1646 | 1647 | return; 1648 | } 1649 | 1650 | 1651 | sub is_interesting { 1652 | return if /^\./; 1653 | 1654 | my $include; 1655 | 1656 | for my $type ( filetypes( $File::Next::name ) ) { 1657 | if ( defined $type_wanted{$type} ) { 1658 | if ( $type_wanted{$type} ) { 1659 | $include = 1; 1660 | } 1661 | else { 1662 | return; 1663 | } 1664 | } 1665 | } 1666 | 1667 | return $include; 1668 | } 1669 | 1670 | 1671 | 1672 | sub open_file { 1673 | my $filename = shift; 1674 | 1675 | my $fh; 1676 | my $could_be_binary; 1677 | 1678 | if ( $filename eq '-' ) { 1679 | $fh = *STDIN; 1680 | $could_be_binary = 0; 1681 | } 1682 | else { 1683 | if ( !open( $fh, '<', $filename ) ) { 1684 | App::Ack::warn( "$filename: $!" ); 1685 | return; 1686 | } 1687 | $could_be_binary = 1; 1688 | } 1689 | 1690 | return ($fh,$could_be_binary); 1691 | } 1692 | 1693 | 1694 | sub close_file { 1695 | if ( close $_[0] ) { 1696 | return 1; 1697 | } 1698 | App::Ack::warn( "$_[1]: $!" ); 1699 | return 0; 1700 | } 1701 | 1702 | 1703 | 1704 | sub needs_line_scan { 1705 | my $fh = shift; 1706 | my $regex = shift; 1707 | my $opt = shift; 1708 | 1709 | return 1 if $opt->{v}; 1710 | 1711 | my $size = -s $fh; 1712 | 1713 | if ( $size > 100_000 ) { 1714 | return 1; 1715 | } 1716 | 1717 | my $buffer; 1718 | my $rc = sysread( $fh, $buffer, $size ); 1719 | return 0 unless $rc && ( $rc == $size ); 1720 | 1721 | return 1722 | $opt->{i} 1723 | ? ( $buffer =~ /$regex/im ) 1724 | : ( $buffer =~ /$regex/m ); 1725 | } 1726 | 1727 | # print subs added in order to make it easy for a third party 1728 | # module (such as App::Wack) to redefine the display methods 1729 | # and show the results in a different way. 1730 | sub print { print {$fh} @_ } 1731 | sub print_first_filename { App::Ack::print( $_[0], "\n" ) } 1732 | sub print_blank_line { App::Ack::print( "\n" ) } 1733 | sub print_separator { App::Ack::print( "--\n" ) } 1734 | sub print_filename { App::Ack::print( $_[0], $_[1] ) } 1735 | sub print_line_no { App::Ack::print( $_[0], $_[1] ) } 1736 | sub print_count { 1737 | my $filename = shift; 1738 | my $nmatches = shift; 1739 | my $ors = shift; 1740 | my $count = shift; 1741 | 1742 | App::Ack::print( $filename ); 1743 | App::Ack::print( ':', $nmatches ) if $count; 1744 | App::Ack::print( $ors ); 1745 | } 1746 | 1747 | sub print_count0 { 1748 | my $filename = shift; 1749 | my $ors = shift; 1750 | 1751 | App::Ack::print( $filename, ':0', $ors ); 1752 | } 1753 | 1754 | 1755 | 1756 | { 1757 | my $filename; 1758 | my $regex; 1759 | my $display_filename; 1760 | 1761 | my $keep_context; 1762 | 1763 | my $last_output_line; # number of the last line that has been output 1764 | my $any_output; # has there been any output for the current file yet 1765 | my $context_overall_output_count; # has there been any output at all 1766 | 1767 | sub search { 1768 | my $fh = shift; 1769 | my $could_be_binary = shift; 1770 | $filename = shift; 1771 | my $opt = shift; 1772 | 1773 | my $v = $opt->{v}; 1774 | my $passthru = $opt->{passthru}; 1775 | my $max = $opt->{m}; 1776 | my $nmatches = 0; 1777 | 1778 | $display_filename = undef; 1779 | 1780 | # for --line processing 1781 | my $has_lines = 0; 1782 | my @lines; 1783 | if ( defined $opt->{lines} ) { 1784 | $has_lines = 1; 1785 | @lines = ( @{$opt->{lines}}, -1 ); 1786 | undef $regex; # Don't match when printing matching line 1787 | } 1788 | else { 1789 | $regex = $opt->{i} ? qr/$opt->{regex}/i : qr/$opt->{regex}/; 1790 | } 1791 | 1792 | 1793 | # for context processing 1794 | $last_output_line = -1; 1795 | $any_output = 0; 1796 | my $before_context = $opt->{before_context}; 1797 | my $after_context = $opt->{after_context}; 1798 | 1799 | $keep_context = ($before_context || $after_context) && !$passthru; 1800 | 1801 | my @before; 1802 | my $before_starts_at_line; 1803 | my $after = 0; # number of lines still to print after a match 1804 | 1805 | my $line; 1806 | while ($line = <$fh>) { 1807 | # XXX Optimize away the case when there are no more @lines to find. 1808 | if ( $has_lines 1809 | ? $. != $lines[0] # $lines[0] should be a scalar 1810 | : $v ? $line =~ m/$regex/ : $line !~ m/$regex/ ) { 1811 | if ( $passthru ) { 1812 | App::Ack::print( $line ); 1813 | next; 1814 | } 1815 | 1816 | if ( $keep_context ) { 1817 | if ( $after ) { 1818 | print_match_or_context( $opt, 0, $., $line ); 1819 | $after--; 1820 | } 1821 | elsif ( $before_context ) { 1822 | if ( @before ) { 1823 | if ( @before >= $before_context ) { 1824 | shift @before; 1825 | ++$before_starts_at_line; 1826 | } 1827 | } 1828 | else { 1829 | $before_starts_at_line = $.; 1830 | } 1831 | push @before, $line; 1832 | } 1833 | last if $max && ( $nmatches >= $max ) && !$after; 1834 | } 1835 | next; 1836 | } # not a match 1837 | 1838 | ++$nmatches; 1839 | 1840 | # print an empty line as a divider before first line in each file (not before the first file) 1841 | if ( !$any_output && $opt->{show_filename} && $opt->{break} && defined( $context_overall_output_count ) ) { 1842 | App::Ack::print_blank_line(); 1843 | } 1844 | 1845 | shift @lines if $has_lines; 1846 | 1847 | if ( $could_be_binary ) { 1848 | if ( -B $filename ) { 1849 | App::Ack::print( "Binary file $filename matches\n" ); 1850 | last; 1851 | } 1852 | $could_be_binary = 0; 1853 | } 1854 | if ( $keep_context ) { 1855 | if ( @before ) { 1856 | print_match_or_context( $opt, 0, $before_starts_at_line, @before ); 1857 | @before = (); 1858 | $before_starts_at_line = 0; 1859 | } 1860 | if ( $max && $nmatches > $max ) { 1861 | --$after; 1862 | } 1863 | else { 1864 | $after = $after_context; 1865 | } 1866 | } 1867 | print_match_or_context( $opt, 1, $., $line ); 1868 | 1869 | last if $max && ( $nmatches >= $max ) && !$after; 1870 | } # while 1871 | 1872 | return $nmatches; 1873 | } # search() 1874 | 1875 | 1876 | 1877 | sub print_match_or_context { 1878 | my $opt = shift; # opts array 1879 | my $is_match = shift; # is there a match on the line? 1880 | my $line_no = shift; 1881 | 1882 | my $color = $opt->{color}; 1883 | my $heading = $opt->{heading}; 1884 | my $show_filename = $opt->{show_filename}; 1885 | 1886 | if ( $show_filename ) { 1887 | if ( not defined $display_filename ) { 1888 | $display_filename = 1889 | $color 1890 | ? Term::ANSIColor::colored( $filename, $ENV{ACK_COLOR_FILENAME} ) 1891 | : $filename; 1892 | if ( $heading && !$any_output ) { 1893 | App::Ack::print_first_filename($display_filename); 1894 | } 1895 | } 1896 | } 1897 | 1898 | my $sep = $is_match ? ':' : '-'; 1899 | my $output_func = $opt->{output}; 1900 | for ( @_ ) { 1901 | if ( $keep_context && !$output_func ) { 1902 | if ( ( $last_output_line != $line_no - 1 ) && 1903 | ( $any_output || ( !$heading && defined( $context_overall_output_count ) ) ) ) { 1904 | App::Ack::print_separator(); 1905 | } 1906 | # to ensure separators between different files when --noheading 1907 | 1908 | $last_output_line = $line_no; 1909 | } 1910 | 1911 | if ( $show_filename ) { 1912 | App::Ack::print_filename($display_filename, $sep) if not $heading; 1913 | App::Ack::print_line_no($line_no, $sep); 1914 | } 1915 | 1916 | if ( $output_func ) { 1917 | while ( /$regex/go ) { 1918 | App::Ack::print( $output_func->() . "\n" ); 1919 | } 1920 | } 1921 | else { 1922 | if ( $color && $is_match && $regex && 1923 | s/$regex/Term::ANSIColor::colored( substr($_, $-[0], $+[0] - $-[0]), $ENV{ACK_COLOR_MATCH} )/eg ) { 1924 | # At the end of the line reset the color and remove newline 1925 | s/[\r\n]*\z/\e[0m\e[K/; 1926 | } 1927 | else { 1928 | # remove any kind of newline at the end of the line 1929 | s/[\r\n]*\z//; 1930 | } 1931 | App::Ack::print($_ . "\n"); 1932 | } 1933 | $any_output = 1; 1934 | ++$context_overall_output_count; 1935 | ++$line_no; 1936 | } 1937 | 1938 | return; 1939 | } # print_match_or_context() 1940 | 1941 | } # scope around search() and print_match_or_context() 1942 | 1943 | 1944 | 1945 | sub search_and_list { 1946 | my $fh = shift; 1947 | my $filename = shift; 1948 | my $opt = shift; 1949 | 1950 | my $nmatches = 0; 1951 | my $count = $opt->{count}; 1952 | my $ors = $opt->{print0} ? "\0" : "\n"; # output record separator 1953 | 1954 | my $regex = $opt->{i} ? qr/$opt->{regex}/i : qr/$opt->{regex}/; 1955 | 1956 | if ( $opt->{v} ) { 1957 | while (<$fh>) { 1958 | if ( /$regex/ ) { 1959 | return 0 unless $count; 1960 | } 1961 | else { 1962 | ++$nmatches; 1963 | } 1964 | } 1965 | } 1966 | else { 1967 | while (<$fh>) { 1968 | if ( /$regex/ ) { 1969 | ++$nmatches; 1970 | last unless $count; 1971 | } 1972 | } 1973 | } 1974 | 1975 | if ( $nmatches ) { 1976 | App::Ack::print_count($filename, $nmatches, $ors, $count); 1977 | } 1978 | elsif ( $count && !$opt->{l} ) { 1979 | App::Ack::print_count0($filename, $ors); 1980 | } 1981 | 1982 | return $nmatches ? 1 : 0; 1983 | } # search_and_list() 1984 | 1985 | 1986 | 1987 | sub filetypes_supported_set { 1988 | return grep { defined $type_wanted{$_} && ($type_wanted{$_} == 1) } filetypes_supported(); 1989 | } 1990 | 1991 | 1992 | 1993 | sub print_files { 1994 | my $iter = shift; 1995 | my $opt = shift; 1996 | 1997 | my $ors = $opt->{print0} ? "\0" : "\n"; 1998 | 1999 | while ( defined ( my $file = $iter->() ) ) { 2000 | App::Ack::print $file, $ors; 2001 | last if $opt->{1}; 2002 | } 2003 | 2004 | return; 2005 | } 2006 | 2007 | 2008 | sub print_files_with_matches { 2009 | my $iter = shift; 2010 | my $opt = shift; 2011 | 2012 | my $nmatches = 0; 2013 | while ( defined ( my $filename = $iter->() ) ) { 2014 | my ($fh) = open_file( $filename ); 2015 | next unless defined $fh; # error while opening file 2016 | $nmatches += search_and_list( $fh, $filename, $opt ); 2017 | close_file( $fh, $filename ); 2018 | last if $nmatches && $opt->{1}; 2019 | } 2020 | 2021 | return; 2022 | } 2023 | 2024 | 2025 | sub print_matches { 2026 | my $iter = shift; 2027 | my $opt = shift; 2028 | 2029 | $opt->{show_filename} = 0 if $opt->{h}; 2030 | $opt->{show_filename} = 1 if $opt->{H}; 2031 | 2032 | my $nmatches = 0; 2033 | while ( defined ( my $filename = $iter->() ) ) { 2034 | my ($fh,$could_be_binary) = open_file( $filename ); 2035 | next unless defined $fh; # error while opening file 2036 | my $needs_line_scan; 2037 | if ( $opt->{regex} && !$opt->{passthru} ) { 2038 | $needs_line_scan = needs_line_scan( $fh, $opt->{regex}, $opt ); 2039 | if ( $needs_line_scan ) { 2040 | seek( $fh, 0, 0 ); 2041 | } 2042 | } 2043 | else { 2044 | $needs_line_scan = 1; 2045 | } 2046 | if ( $needs_line_scan ) { 2047 | $nmatches += search( $fh, $could_be_binary, $filename, $opt ); 2048 | } 2049 | close_file( $fh, $filename ); 2050 | last if $nmatches && $opt->{1}; 2051 | } 2052 | return; 2053 | } 2054 | 2055 | 2056 | sub filetype_setup { 2057 | my $filetypes_supported_set = filetypes_supported_set(); 2058 | # If anyone says --no-whatever, we assume all other types must be on. 2059 | if ( !$filetypes_supported_set ) { 2060 | for my $i ( keys %type_wanted ) { 2061 | $type_wanted{$i} = 1 unless ( defined( $type_wanted{$i} ) || $i eq 'binary' || $i eq 'text' || $i eq 'skipped' ); 2062 | } 2063 | } 2064 | return; 2065 | } 2066 | 2067 | 2068 | EXPAND_FILENAMES_SCOPE: { 2069 | my $filter; 2070 | 2071 | sub expand_filenames { 2072 | my $argv = shift; 2073 | 2074 | my $attr; 2075 | my @files; 2076 | 2077 | foreach my $pattern ( @{$argv} ) { 2078 | my @results = bsd_glob( $pattern ); 2079 | 2080 | if (@results == 0) { 2081 | @results = $pattern; # Glob didn't match, pass it thru unchanged 2082 | } 2083 | elsif ( (@results > 1) or ($results[0] ne $pattern) ) { 2084 | if (not defined $filter) { 2085 | eval 'require Win32::File;'; 2086 | if ($@) { 2087 | $filter = 0; 2088 | } 2089 | else { 2090 | $filter = Win32::File::HIDDEN()|Win32::File::SYSTEM(); 2091 | } 2092 | } # end unless we've tried to load Win32::File 2093 | if ( $filter ) { 2094 | # Filter out hidden and system files: 2095 | @results = grep { not(Win32::File::GetAttributes($_, $attr) and $attr & $filter) } @results; 2096 | App::Ack::warn( "$pattern: Matched only hidden files" ) unless @results; 2097 | } # end if we can filter by file attributes 2098 | } # end elsif this pattern got expanded 2099 | 2100 | push @files, @results; 2101 | } # end foreach pattern 2102 | 2103 | return \@files; 2104 | } # end expand_filenames 2105 | } # EXPAND_FILENAMES_SCOPE 2106 | 2107 | 2108 | 2109 | sub get_starting_points { 2110 | my $argv = shift; 2111 | my $opt = shift; 2112 | 2113 | my @what; 2114 | 2115 | if ( @{$argv} ) { 2116 | @what = @{ $is_windows ? expand_filenames($argv) : $argv }; 2117 | $_ = File::Next::reslash( $_ ) for @what; 2118 | 2119 | # Show filenames unless we've specified one single file 2120 | $opt->{show_filename} = (@what > 1) || (!-f $what[0]); 2121 | } 2122 | else { 2123 | @what = '.'; # Assume current directory 2124 | $opt->{show_filename} = 1; 2125 | } 2126 | 2127 | for my $start_point (@what) { 2128 | App::Ack::warn( "$start_point: No such file or directory" ) unless -e $start_point; 2129 | } 2130 | return \@what; 2131 | } 2132 | 2133 | 2134 | 2135 | sub get_iterator { 2136 | my $what = shift; 2137 | my $opt = shift; 2138 | 2139 | # Starting points are always search, no matter what 2140 | my $is_starting_point = sub { return grep { $_ eq $_[0] } @{$what} }; 2141 | 2142 | my $g_regex = defined $opt->{G} ? qr/$opt->{G}/ : undef; 2143 | my $file_filter 2144 | = $opt->{u} && defined $opt->{G} ? sub { $File::Next::name =~ /$g_regex/ } 2145 | : $opt->{all} && defined $opt->{G} ? sub { $is_starting_point->( $File::Next::name ) || ( $File::Next::name =~ /$g_regex/ && is_searchable( $File::Next::name ) ) } 2146 | : $opt->{u} ? sub {1} 2147 | : $opt->{all} ? sub { $is_starting_point->( $File::Next::name ) || is_searchable( $File::Next::name ) } 2148 | : defined $opt->{G} ? sub { $is_starting_point->( $File::Next::name ) || ( $File::Next::name =~ /$g_regex/ && is_interesting( @_ ) ) } 2149 | : sub { $is_starting_point->( $File::Next::name ) || is_interesting( @_ ) } 2150 | ; 2151 | my $iter = 2152 | File::Next::files( { 2153 | file_filter => $file_filter, 2154 | descend_filter => $opt->{n} 2155 | ? sub {0} 2156 | : $opt->{u} 2157 | ? sub {1} 2158 | : \&ignoredir_filter, 2159 | error_handler => sub { my $msg = shift; App::Ack::warn( $msg ) }, 2160 | sort_files => $opt->{sort_files}, 2161 | follow_symlinks => $opt->{follow}, 2162 | }, @{$what} ); 2163 | return $iter; 2164 | } 2165 | 2166 | 2167 | sub set_up_pager { 2168 | my $command = shift; 2169 | 2170 | return unless $to_screen; 2171 | 2172 | my $pager; 2173 | if ( not open( $pager, '|-', $command ) ) { 2174 | App::Ack::die( qq{Unable to pipe to pager "$command": $!} ); 2175 | } 2176 | $fh = $pager; 2177 | } 2178 | 2179 | 2180 | 1; # End of App::Ack 2181 | -------------------------------------------------------------------------------- /Support/environment.rb: -------------------------------------------------------------------------------- 1 | require 'base64' 2 | 3 | class Module 4 | # lifted from Rails 5 | def delegate(*methods) 6 | options = methods.pop 7 | unless options.is_a?(Hash) && to = options[:to] 8 | raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)." 9 | end 10 | 11 | methods.each do |method| 12 | module_eval(<<-EOS, "(__DELEGATION__)", 1) 13 | def #{method}(*args, &block) 14 | #{to}.__send__(#{method.inspect}, *args, &block) 15 | end 16 | EOS 17 | end 18 | end 19 | end 20 | 21 | module AckInProject 22 | module Environment 23 | class << self 24 | attr_accessor :bundle_support, :ack 25 | 26 | def load_environment 27 | %w{ui exit_codes}.each { |lib| require textmate_lib_file(lib) } 28 | 29 | self.bundle_support = File.expand_path(ENV['TM_BUNDLE_SUPPORT']) 30 | self.ack = (ENV['TM_ACK'] || support_file('ack-standalone.sh')) 31 | end 32 | 33 | def ghetto_include(to_include, target_binding) 34 | [*to_include].each do |lib| 35 | lib_file = textmate_lib_file(lib + '.rb') 36 | eval(IO.read(lib_file), target_binding, lib_file) 37 | end 38 | end 39 | 40 | def textmate_lib_file(called) 41 | "%s/lib/%s" % [ENV['TM_SUPPORT_PATH'], called] 42 | end 43 | 44 | def support_file(*elements) 45 | File.join(bundle_support, *elements) 46 | end 47 | 48 | def lib_file(filename) 49 | support_file('lib', filename) 50 | end 51 | 52 | def nib_file(filename) 53 | support_file('nibs', filename) 54 | end 55 | 56 | def search_directory 57 | @search_directory ||= guess_search_directory() 58 | end 59 | 60 | def file_in_search_directory(file) 61 | File.expand_path(File.join(search_directory, file)) 62 | end 63 | 64 | def searched_in 65 | @searched_in ||= search_directory.gsub(/^#{project_directory}/, File.basename(project_directory)) 66 | end 67 | 68 | def project_directory 69 | ENV['TM_PROJECT_DIRECTORY'] 70 | end 71 | 72 | protected 73 | 74 | def guess_search_directory 75 | directory = ENV['TM_SELECTED_FILE'] || project_directory || 76 | ( ENV['TM_FILEPATH'] && File.dirname(ENV['TM_FILEPATH']) ) 77 | File.file?(directory) ? project_directory : directory 78 | end 79 | end 80 | 81 | delegate :bundle_support, :ack, :support_file, :lib_file, :nib_file, :project_directory, 82 | :search_directory, :file_in_search_directory, :searched_in, :to => '::AckInProject::Environment' 83 | end 84 | 85 | class << self 86 | include Environment 87 | AckInProject::Environment.ghetto_include %w(escape), binding 88 | 89 | def show_search_dialog(&block) 90 | require lib_file('search_dialog') 91 | AckInProject::SearchDialog.new.show(&block) 92 | end 93 | 94 | def present_search(plist) 95 | require lib_file('search_results') 96 | require lib_file('search') 97 | AckInProject::SearchResults.new(plist).show 98 | end 99 | 100 | # sigh... defaults is giving me grief when searches contain quotes 101 | def search_history 102 | unless @search_history 103 | history_command = "defaults read com.macromates.textmate ackHistory 2>/dev/null" 104 | @search_history = OSX::PropertyList::load(Base64.decode64(%x{#{history_command}})) 105 | end 106 | @search_history 107 | rescue 108 | @search_history = [] # oh the humanity! 109 | end 110 | 111 | def update_search_history(search) 112 | search_history.unshift(search) 113 | search_history.uniq! 114 | search_history.compact! 115 | search_history.replace search_history[0..9] 116 | 117 | history = Base64.encode64(search_history.to_plist) 118 | 119 | history_command = %Q|defaults write com.macromates.textmate ackHistory -string #{e_sh history} 2>/dev/null| 120 | %x{#{history_command}} 121 | rescue 122 | end 123 | 124 | def pbfind 125 | @pbfind ||= %x[pbpaste -pboard find] 126 | end 127 | 128 | def update_pbfind(search) 129 | @pbfind = search 130 | IO.popen('pbcopy -pboard find', 'w') {|pbcopy| pbcopy.write @pbfind} 131 | end 132 | 133 | end 134 | end 135 | 136 | AckInProject::Environment.load_environment 137 | -------------------------------------------------------------------------------- /Support/lib/search.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby -rcgi 2 | 3 | class AckInProject::Search 4 | include AckInProject::Environment 5 | AckInProject::Environment.ghetto_include %w(escape), binding 6 | 7 | attr_accessor :plist, :current_file, :lines_matched, :files_matched 8 | 9 | def initialize(plist) 10 | self.plist = plist; 11 | self.lines_matched = self.files_matched = 0 12 | end 13 | 14 | def line_matched 15 | self.lines_matched = lines_matched + 1 16 | end 17 | 18 | def file_matched 19 | self.files_matched = files_matched + 1 20 | end 21 | 22 | def h(string) 23 | CGI.escapeHTML(string) 24 | end 25 | 26 | def section_start(file) 27 | self.current_file = file 28 | reset_stripe 29 | puts %Q|#{current_file}| 30 | puts %Q|| 31 | file_matched() 32 | end 33 | 34 | def section_end 35 | if current_file 36 | puts %Q|  | 37 | puts %Q|| 38 | end 39 | self.current_file = nil 40 | end 41 | 42 | def linked_content(line, content) 43 | href = "txmt://open/?url=file://#{e_url file_in_search_directory(current_file)}&line=#{line}" 44 | %Q|
#{scrub(escape(content))}
| 45 | end 46 | 47 | def content_line(line, content) 48 | puts %Q|#{line}#{linked_content(line, content)}| 49 | line_matched() 50 | end 51 | 52 | def context_line(line, content) 53 | puts %Q|#{line}#{linked_content(line, content)}| 54 | end 55 | 56 | def context_break() 57 | reset_stripe 58 | puts %Q|... | 59 | end 60 | 61 | def stripe 62 | (@stripe = !@stripe) ? '' : ' stripe' 63 | end 64 | 65 | def reset_stripe 66 | @stripe = nil 67 | end 68 | 69 | def escape(content) 70 | CGI.escapeHTML(content) 71 | end 72 | 73 | def scrub(content) 74 | content.gsub(/\e\[\d+m\e\[K$/, ''). 75 | gsub(/\e\[\d+;\d+m/, ''). 76 | gsub(/\e\[\d+m/,'') 77 | end 78 | 79 | def prepare_search 80 | # TODO: bail if the search term is empty 81 | result = plist['result'] 82 | 83 | options = %w(--group --color --flush) 84 | options << '-w' if result['matchWholeWords'] == 1 85 | options << '-i' if result['ignoreCase'] == 1 86 | options << '-Q' if result['literalMatch'] == 1 87 | options << '-C' if result['showContext'] == 1 88 | options << "--#{result['followSymLinks'] == 1 ? '' : 'no'}follow" 89 | options << "--#{result['loadAckRC'] == 1 ? '' : 'no'}env" 90 | 91 | AckInProject.update_search_history result['returnArgument'] 92 | AckInProject.update_pbfind result['returnArgument'] 93 | 94 | %{cd #{e_sh search_directory}; #{e_sh ack} #{options.join(' ')} -- #{e_sh result['returnArgument']}} 95 | end 96 | 97 | def search 98 | # tell ack about potential .ackrc files in the project directory 99 | ENV['ACKRC'] = File.join(project_directory, '.ackrc') 100 | 101 | IO.popen(prepare_search) do |pipe| 102 | pipe.each do |line| 103 | case line 104 | when /^\s*$/ 105 | section_end() 106 | when /^\e\[\d+;\d+m(.*)\e\[0m/ 107 | section_start($1) 108 | when /^(\d+):(.*)$/ 109 | content_line($1, $2) 110 | when /^(\d+)-(.*)$/ 111 | context_line($1, $2) 112 | when /^--$/ 113 | context_break 114 | end 115 | $stdout.flush 116 | end 117 | section_end() 118 | end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /Support/lib/search_dialog.rb: -------------------------------------------------------------------------------- 1 | class AckInProject::SearchDialog 2 | include AckInProject::Environment 3 | AckInProject::Environment.ghetto_include %w(web_preview), binding 4 | 5 | def show(&block) 6 | raise ArgumentError, 'show_search_dialog requires a block' if block.nil? 7 | 8 | verify_project_directory or return 9 | 10 | command = %Q{#{TM_DIALOG} -cm -p #{e_sh params.to_plist} -d #{e_sh defaults.to_plist} #{e_sh nib_file('AckInProjectSearch.nib')}} 11 | plist = OSX::PropertyList::load(%x{#{command}}) 12 | if plist['result'] 13 | block.call(plist) 14 | end 15 | end 16 | 17 | def defaults 18 | %w( 19 | ackMatchWholeWords ackIgnoreCase ackLiteralMatch 20 | ackShowContext ackFollowSymlinks ackLoadAckRC 21 | ).inject({}) do |hsh,v| 22 | hsh[v] = false 23 | hsh 24 | end 25 | end 26 | 27 | def params 28 | history = AckInProject.search_history 29 | { 30 | #'contentHeight' => 168, 31 | 'ackExpression' => AckInProject.pbfind, 32 | 'ackHistory' => history 33 | } 34 | end 35 | 36 | def verify_project_directory 37 | return true if project_directory 38 | 39 | puts <<-HTML 40 | 41 |

Can't determine project directory (TM_PROJECT_DIR)

42 | 43 | HTML 44 | end 45 | end 46 | 47 | 48 | -------------------------------------------------------------------------------- /Support/lib/search_results.rb: -------------------------------------------------------------------------------- 1 | class AckInProject::SearchResults 2 | include AckInProject::Environment 3 | AckInProject::Environment.ghetto_include %w(web_preview escape), binding 4 | 5 | attr_accessor :plist 6 | 7 | def initialize(plist) 8 | self.plist = plist 9 | end 10 | 11 | def show 12 | puts html_head( 13 | :window_title => title, 14 | :page_title => title, 15 | :html_head => header_extra() 16 | ) 17 | puts <<-HTML 18 |

Searching for "#{ h search_string }" in #{ searched_in }

19 |
0 lines matched in 0 files
20 | 21 | 22 | HTML 23 | 24 | AckInProject::Search.new(plist).search 25 | 26 | puts <<-HTML 27 |
28 | 29 | HTML 30 | html_footer 31 | end 32 | 33 | def title 34 | "Ack in Project" 35 | end 36 | 37 | def header_extra 38 | <<-HTML 39 | 40 | 41 | HTML 42 | end 43 | 44 | def search_string 45 | plist['result']['returnArgument'] 46 | end 47 | 48 | def h(string) 49 | CGI.escapeHTML(string) 50 | end 51 | end -------------------------------------------------------------------------------- /Support/nibs/AckInProjectSearch.nib/classes.nib: -------------------------------------------------------------------------------- 1 | { 2 | IBClasses = ( 3 | {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 4 | {ACTIONS = {performButtonClick = id; }; CLASS = NSObject; LANGUAGE = ObjC; } 5 | ); 6 | IBVersion = 1; 7 | } -------------------------------------------------------------------------------- /Support/nibs/AckInProjectSearch.nib/info.nib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IBDocumentLocation 6 | 425 98 703 386 0 0 1680 1028 7 | IBEditorPositions 8 | 9 | 185 10 | 756 617 214 136 0 0 1680 1028 11 | 12 | IBFramework Version 13 | 443.0 14 | IBOpenObjects 15 | 16 | 5 17 | 185 18 | 19 | IBSystem Version 20 | 8S2167 21 | 22 | 23 | -------------------------------------------------------------------------------- /Support/nibs/AckInProjectSearch.nib/keyedobjects.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protocool/ack-tmbundle/c476ccd032ff73327fac258d57af1cc7d28e8b5a/Support/nibs/AckInProjectSearch.nib/keyedobjects.nib -------------------------------------------------------------------------------- /Support/search.css: -------------------------------------------------------------------------------- 1 | table { font-size:0.9em; border-collapse:collapse; border-bottom:1px solid #555; } 2 | h2 { font-size:1.3em; margin-bottom: 0.25em;} 3 | div#counters {margin-bottom: 1.5em;} 4 | tr { background:#FFF; } 5 | tr.stripe { background-color:#F3F3FA; } 6 | th, td { vertical-align:top; text-align:left;} 7 | tbody.matches a { padding:0.25em; text-decoration:none; display:block; font-weight: inherit; color: inherit;} 8 | tbody.matches a:hover { background-color:#C4E2FF; } 9 | th {background-color: rgb(234,234,234); padding: 0.5em;} 10 | tr.context td:first-child, tr.content td:first-child {width: 1em; text-align: right; padding: 0.25em 0.5em; font-family: courier ! important; background-color: rgb(234,234,234);} 11 | tr.break td:first-child { background:#FFF;} 12 | pre { margin:0px; padding: 0px; } 13 | td { vertical-align:top; white-space:nowrap; } 14 | tr.content td, tr.content th { color: #000; font-weight: 500;} 15 | tr.context td, tr.context { color: #666; font-weight: 100;} 16 | td strong { font-weight:600; background:#F6D73A; } 17 | -------------------------------------------------------------------------------- /Support/search.js: -------------------------------------------------------------------------------- 1 | var foundFiles = 0; 2 | var foundLines = 0; 3 | 4 | function f() { 5 | foundFiles += 1; 6 | document.getElementById('filecount').innerText = ('' + foundFiles).concat(' file').concat( (foundFiles == 1 ) ? '' : 's' ); 7 | } 8 | 9 | function l() { 10 | foundLines += 1; 11 | document.getElementById('linecount').innerText = ('' + foundLines).concat(' line').concat( (foundLines == 1 ) ? '' : 's' ); 12 | } 13 | 14 | function searchStarted() { 15 | document.getElementById('teaser').style.display = 'none'; 16 | 17 | } 18 | 19 | function searchCompleted() { 20 | document.getElementById('teaser').style.display = 'block'; 21 | } -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mainMenu 6 | 7 | items 8 | 9 | E1966FE3-4B65-419A-85CA-4447BC63ECAD 10 | 11 | submenus 12 | 13 | 14 | name 15 | Ack In Project 16 | uuid 17 | E3C2D3AD-8AAB-4CE6-87CE-47997F5D950C 18 | 19 | 20 | --------------------------------------------------------------------------------