From 8d05f2637b61c42314e07bb0ab47c4fb5c5c1c0b Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Mon, 22 Aug 2011 20:41:46 -0700 Subject: [PATCH] [emacs] add emacs-nav, PeepOpen, new textmate.el --- emacs | 9 +- emacs.d/emacs-nav/ack | 2778 ++++++++++++++++++++++++++++++ emacs.d/emacs-nav/ack.el | 84 + emacs.d/emacs-nav/ack.elc | Bin 0 -> 3532 bytes emacs.d/emacs-nav/nav.el | 651 +++++++ emacs.d/emacs-nav/nav.elc | Bin 0 -> 20205 bytes emacs.d/vendor/peepopen.el | 96 ++ emacs.d/{ => vendor}/textmate.el | 241 ++- 8 files changed, 3764 insertions(+), 95 deletions(-) create mode 100755 emacs.d/emacs-nav/ack create mode 100644 emacs.d/emacs-nav/ack.el create mode 100644 emacs.d/emacs-nav/ack.elc create mode 100644 emacs.d/emacs-nav/nav.el create mode 100644 emacs.d/emacs-nav/nav.elc create mode 100644 emacs.d/vendor/peepopen.el rename emacs.d/{ => vendor}/textmate.el (62%) diff --git a/emacs b/emacs index ab50a16..9dda10c 100644 --- a/emacs +++ b/emacs @@ -69,14 +69,21 @@ (setq track-eol t) ; When at EOL, C-n and C-p move to EOL on other lines (setq indent-tabs-mode nil) ; never insert tabs +;; nav - awesome filesystem navigation, sort of like TextMate's project drawer +(add-to-load-path "~/config/emacs.d/emacs-nav") +(require 'nav) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; textmate mode +;; peepopen and textmate mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(add-to-list 'load-path "~/.emacs.d/vendor/") (require 'textmate) +(require 'peepopen) (textmate-mode) +;; open files in current frame +(setq ns-pop-up-frames nil) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; minimap diff --git a/emacs.d/emacs-nav/ack b/emacs.d/emacs-nav/ack new file mode 100755 index 0000000..6c8f583 --- /dev/null +++ b/emacs.d/emacs-nav/ack @@ -0,0 +1,2778 @@ +#!/usr/bin/env perl +# +# This file, ack, is generated code. +# Please DO NOT EDIT or send patches for it. +# +# Please take a look at the source from +# http://github.com/petdance/ack +# and submit patches against the individual files +# that build ack. +# + +use warnings; +use strict; + +our $VERSION = '1.94'; +# Check http://betterthangrep.com/ for updates + +# These are all our globals. + + +MAIN: { + if ( $App::Ack::VERSION ne $main::VERSION ) { + App::Ack::die( "Program/library version mismatch\n\t$0 is $main::VERSION\n\t$INC{'App/Ack.pm'} is $App::Ack::VERSION" ); + } + + # Do preliminary arg checking; + my $env_is_usable = 1; + for ( @ARGV ) { + last if ( $_ eq '--' ); + + # Priorities! Get the --thpppt checking out of the way. + /^--th[pt]+t+$/ && App::Ack::_thpppt($_); + + # See if we want to ignore the environment. (Don't tell Al Gore.) + if ( /^--(no)?env$/ ) { + $env_is_usable = defined $1 ? 0 : 1; + } + } + if ( $env_is_usable ) { + unshift( @ARGV, App::Ack::read_ackrc() ); + } + else { + my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV ); + delete @ENV{@keys}; + } + App::Ack::load_colors(); + + if ( exists $ENV{ACK_SWITCHES} ) { + App::Ack::warn( 'ACK_SWITCHES is no longer supported. Use ACK_OPTIONS.' ); + } + + if ( !@ARGV ) { + App::Ack::show_help(); + exit 1; + } + + main(); +} + +sub main { + my $opt = App::Ack::get_command_line_options(); + + $| = 1 if $opt->{flush}; # Unbuffer the output if flush mode + + if ( App::Ack::input_from_pipe() ) { + # We're going into filter mode + for ( qw( f g l ) ) { + $opt->{$_} and App::Ack::die( "Can't use -$_ when acting as a filter." ); + } + $opt->{show_filename} = 0; + $opt->{regex} = App::Ack::build_regex( defined $opt->{regex} ? $opt->{regex} : shift @ARGV, $opt ); + if ( my $nargs = @ARGV ) { + my $s = $nargs == 1 ? '' : 's'; + App::Ack::warn( "Ignoring $nargs argument$s on the command-line while acting as a filter." ); + } + + my $res = App::Ack::Resource::Basic->new( '-' ); + my $nmatches; + if ( $opt->{count} ) { + $nmatches = App::Ack::search_and_list( $res, $opt ); + } + else { + # normal searching + $nmatches = App::Ack::search_resource( $res, $opt ); + } + $res->close(); + App::Ack::exit_from_ack( $nmatches ); + } + + my $file_matching = $opt->{f} || $opt->{lines}; + if ( $file_matching ) { + App::Ack::die( "Can't specify both a regex ($opt->{regex}) and use one of --line, -f or -g." ) if $opt->{regex}; + } + else { + $opt->{regex} = App::Ack::build_regex( defined $opt->{regex} ? $opt->{regex} : shift @ARGV, $opt ); + } + + # check that all regexes do compile fine + App::Ack::check_regex( $_ ) for ( $opt->{regex}, $opt->{G} ); + + my $what = App::Ack::get_starting_points( \@ARGV, $opt ); + my $iter = App::Ack::get_iterator( $what, $opt ); + App::Ack::filetype_setup(); + + my $nmatches = 0; + + App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager}; + if ( $opt->{f} ) { + $nmatches = App::Ack::print_files( $iter, $opt ); + } + elsif ( $opt->{l} || $opt->{count} ) { + $nmatches = App::Ack::print_files_with_matches( $iter, $opt ); + } + else { + $nmatches = App::Ack::print_matches( $iter, $opt ); + } + close $App::Ack::fh; + App::Ack::exit_from_ack( $nmatches ); +} + +=head1 NAME + +ack - grep-like text finder + +=head1 SYNOPSIS + + ack [options] PATTERN [FILE...] + ack -f [options] [DIRECTORY...] + +=head1 DESCRIPTION + +Ack is designed as a replacement for 99% of the uses of F. + +Ack searches the named input FILEs (or standard input if no files are +named, or the file name - is given) for lines containing a match to the +given PATTERN. By default, ack prints the matching lines. + +Ack can also list files that would be searched, without actually searching +them, to let you take advantage of ack's file-type filtering capabilities. + +=head1 FILE SELECTION + +I is intelligent about the files it searches. It knows about +certain file types, based on both the extension on the file and, +in some cases, the contents of the file. These selections can be +made with the B<--type> option. + +With no file selections, I only searches files of types that +it recognizes. If you have a file called F, and I +doesn't know what a .wango file is, I won't search it. + +The B<-a> option tells I to select all files, regardless of +type. + +Some files will never be selected by I, even with B<-a>, +including: + +=over 4 + +=item * Backup files: Files matching F<#*#> or ending with F<~>. + +=item * Coredumps: Files matching F + +=back + +However, I always searches the files given on the command line, +no matter what type. Furthermore, by specifying the B<-u> option all +files will be searched. + +=head1 DIRECTORY SELECTION + +I descends through the directory tree of the starting directories +specified. However, it will ignore the shadow directories used by +many version control systems, and the build directories used by the +Perl MakeMaker system. You may add or remove a directory from this +list with the B<--[no]ignore-dir> option. The option may be repeated +to add/remove multiple directories from the ignore list. + +For a complete list of directories that do not get searched, run +F. + +=head1 WHEN TO USE GREP + +I trumps I as an everyday tool 99% of the time, but don't +throw I away, because there are times you'll still need it. + +E.g., searching through huge files looking for regexes that can be +expressed with I syntax should be quicker with I. + +If your script or parent program uses I C<--quiet> or +C<--silent> or needs exit 2 on IO error, use I. + +=head1 OPTIONS + +=over 4 + +=item B<-a>, B<--all> + +Operate on all files, regardless of type (but still skip directories +like F, F, etc.) + +=item B<-A I>, B<--after-context=I> + +Print I lines of trailing context after matching lines. + +=item B<-B I>, B<--before-context=I> + +Print I lines of leading context before matching lines. + +=item B<-C [I]>, B<--context[=I]> + +Print I lines (default 2) of context around matching lines. + +=item B<-c>, B<--count> + +Suppress normal output; instead print a count of matching lines for +each input file. If B<-l> is in effect, it will only show the +number of lines for each file that has lines matching. Without +B<-l>, some line counts may be zeroes. + +If combined with B<-h> (B<--no-filename>) ack outputs only one total count. + +=item B<--color>, B<--nocolor> + +B<--color> highlights the matching text. B<--nocolor> supresses +the color. This is on by default unless the output is redirected. + +On Windows, this option is off by default unless the +L module is installed or the C +environment variable is used. + +=item B<--color-filename=I> + +Sets the color to be used for filenames. + +=item B<--color-match=I> + +Sets the color to be used for matches. + +=item B<--color-lineno=I> + +Sets the color to be used for line numbers. + +=item B<--column> + +Show the column number of the first match. This is helpful for editors +that can place your cursor at a given position. + +=item B<--env>, B<--noenv> + +B<--noenv> disables all environment processing. No F<.ackrc> is read +and all environment variables are ignored. By default, F considers +F<.ackrc> and settings in the environment. + +=item B<--flush> + +B<--flush> flushes output immediately. This is off by default +unless ack is running interactively (when output goes to a pipe +or file). + +=item B<-f> + +Only print the files that would be searched, without actually doing +any searching. PATTERN must not be specified, or it will be taken as +a path to search. + +=item B<--follow>, B<--nofollow> + +Follow or don't follow symlinks, other than whatever starting files +or directories were specified on the command line. + +This is off by default. + +=item B<-G I> + +Only paths matching I are included in the search. The entire +path and filename are matched against I, and I is a +Perl regular expression, not a shell glob. + +The options B<-i>, B<-w>, B<-v>, and B<-Q> do not apply to this I. + +=item B<-g I> + +Print files where the relative path + filename matches I. This option is +a convenience shortcut for B<-f> B<-G I>. + +The options B<-i>, B<-w>, B<-v>, and B<-Q> do not apply to this I. + +=item B<--group>, B<--nogroup> + +B<--group> groups matches by file name with. This is the default when +used interactively. + +B<--nogroup> prints one result per line, like grep. This is the default +when output is redirected. + +=item B<-H>, B<--with-filename> + +Print the filename for each match. + +=item B<-h>, B<--no-filename> + +Suppress the prefixing of filenames on output when multiple files are +searched. + +=item B<--help> + +Print a short help statement. + +=item B<-i>, B<--ignore-case> + +Ignore case in the search strings. + +This applies only to the PATTERN, not to the regexes given for the B<-g> +and B<-G> options. + +=item B<--[no]ignore-dir=I> + +Ignore directory (as CVS, .svn, etc are ignored). May be used multiple times +to ignore multiple directories. For example, mason users may wish to include +B<--ignore-dir=data>. The B<--noignore-dir> option allows users to search +directories which would normally be ignored (perhaps to research the contents +of F<.svn/props> directories). + +The I must always be a simple directory name. Nested directories like +F are NOT supported. You would need to specify B<--ignore-dir=foo> and +then no files from any foo directory are taken into account by ack unless given +explicitly on the command line. + +=item B<--line=I> + +Only print line I of each file. Multiple lines can be given with multiple +B<--line> options or as a comma separated list (B<--line=3,5,7>). B<--line=4-7> +also works. The lines are always output in ascending order, no matter the +order given on the command line. + +=item B<-l>, B<--files-with-matches> + +Only print the filenames of matching files, instead of the matching text. + +=item B<-L>, B<--files-without-matches> + +Only print the filenames of files that do I match. This is equivalent +to specifying B<-l> and B<-v>. + +=item B<--match I> + +Specify the I explicitly. This is helpful if you don't want to put the +regex as your first argument, e.g. when executing multiple searches over the +same set of files. + + # search for foo and bar in given files + ack file1 t/file* --match foo + ack file1 t/file* --match bar + +=item B<-m=I>, B<--max-count=I> + +Stop reading a file after I matches. + +=item B<--man> + +Print this manual page. + +=item B<-n>, B<--no-recurse> + +No descending into subdirectories. + +=item B<-o> + +Show only the part of each line matching PATTERN (turns off text +highlighting) + +=item B<--output=I> + +Output the evaluation of I for each line (turns off text +highlighting) + +=item B<--pager=I> + +Direct ack's output through I. This can also be specified +via the C and C environment variables. + +Using --pager does not suppress grouping and coloring like piping +output on the command-line does. + +=item B<--passthru> + +Prints all lines, whether or not they match the expression. Highlighting +will still work, though, so it can be used to highlight matches while +still seeing the entire file, as in: + + # Watch a log file, and highlight a certain IP address + $ tail -f ~/access.log | ack --passthru 123.45.67.89 + +=item B<--print0> + +Only works in conjunction with -f, -g, -l or -c (filename output). The filenames +are output separated with a null byte instead of the usual newline. This is +helpful when dealing with filenames that contain whitespace, e.g. + + # remove all files of type html + ack -f --html --print0 | xargs -0 rm -f + +=item B<-Q>, B<--literal> + +Quote all metacharacters in PATTERN, it is treated as a literal. + +This applies only to the PATTERN, not to the regexes given for the B<-g> +and B<-G> options. + +=item B<-r>, B<-R>, B<--recurse> + +Recurse into sub-directories. This is the default and just here for +compatibility with grep. You can also use it for turning B<--no-recurse> off. + +=item B<--smart-case>, B<--no-smart-case> + +Ignore case in the search strings if PATTERN contains no uppercase +characters. This is similar to C in vim. This option is +off by default. + +B<-i> always overrides this option. + +This applies only to the PATTERN, not to the regexes given for the +B<-g> and B<-G> options. + +=item B<--sort-files> + +Sorts the found files lexically. Use this if you want your file +listings to be deterministic between runs of I. + +=item B<--show-types> + +Outputs the filetypes that ack associates with each file. + +Works with B<-f> and B<-g> options. + +=item B<--thpppt> + +Display the all-important Bill The Cat logo. Note that the exact +spelling of B<--thpppppt> is not important. It's checked against +a regular expression. + +=item B<--type=TYPE>, B<--type=noTYPE> + +Specify the types of files to include or exclude from a search. +TYPE is a filetype, like I or I. B<--type=perl> can +also be specified as B<--perl>, and B<--type=noperl> can be done +as B<--noperl>. + +If a file is of both type "foo" and "bar", specifying --foo and +--nobar will exclude the file, because an exclusion takes precedence +over an inclusion. + +Type specifications can be repeated and are ORed together. + +See I for a list of valid types. + +=item B<--type-add I=I<.EXTENSION>[,I<.EXT2>[,...]]> + +Files with the given EXTENSION(s) are recognized as being of (the +existing) type TYPE. See also L. + + +=item B<--type-set I=I<.EXTENSION>[,I<.EXT2>[,...]]> + +Files with the given EXTENSION(s) are recognized as being of type +TYPE. This replaces an existing definition for type TYPE. See also +L. + +=item B<-u>, B<--unrestricted> + +All files and directories (including blib/, core.*, ...) are searched, +nothing is skipped. When both B<-u> and B<--ignore-dir> are used, the +B<--ignore-dir> option has no effect. + +=item B<-v>, B<--invert-match> + +Invert match: select non-matching lines + +This applies only to the PATTERN, not to the regexes given for the B<-g> +and B<-G> options. + +=item B<--version> + +Display version and copyright information. + +=item B<-w>, B<--word-regexp> + +Force PATTERN to match only whole words. The PATTERN is wrapped with +C<\b> metacharacters. + +This applies only to the PATTERN, not to the regexes given for the B<-g> +and B<-G> options. + +=item B<-1> + +Stops after reporting first match of any kind. This is different +from B<--max-count=1> or B<-m1>, where only one match per file is +shown. Also, B<-1> works with B<-f> and B<-g>, where B<-m> does +not. + +=back + +=head1 THE .ackrc FILE + +The F<.ackrc> file contains command-line options that are prepended +to the command line before processing. Multiple options may live +on multiple lines. Lines beginning with a # are ignored. A F<.ackrc> +might look like this: + + # Always sort the files + --sort-files + + # Always color, even if piping to a another program + --color + + # Use "less -r" as my pager + --pager=less -r + +Note that arguments with spaces in them do not need to be quoted, +as they are not interpreted by the shell. Basically, each I +in the F<.ackrc> file is interpreted as one element of C<@ARGV>. + +F looks in your home directory for the F<.ackrc>. You can +specify another location with the F variable, below. + +If B<--noenv> is specified on the command line, the F<.ackrc> file +is ignored. + +=head1 Defining your own types + +ack allows you to define your own types in addition to the predefined +types. This is done with command line options that are best put into +an F<.ackrc> file - then you do not have to define your types over and +over again. In the following examples the options will always be shown +on one command line so that they can be easily copy & pasted. + +I searches for foo in all perl files. I +tells you, that perl files are files ending +in .pl, .pm, .pod or .t. So what if you would like to include .xs +files as well when searching for --perl files? I +does this for you. B<--type-add> appends +additional extensions to an existing type. + +If you want to define a new type, or completely redefine an existing +type, then use B<--type-set>. I defines the type I to include files with +the extensions .e or .eiffel. So to search for all eiffel files +containing the word Bertrand use I. +As usual, you can also write B<--type=eiffel> +instead of B<--eiffel>. Negation also works, so B<--noeiffel> excludes +all eiffel files from a search. Redefining also works: I +and I<.xs> files no longer belong to the type I. + +When defining your own types in the F<.ackrc> file you have to use +the following: + + --type-set=eiffel=.e,.eiffel + +or writing on separate lines + + --type-set + eiffel=.e,.eiffel + +The following does B work in the F<.ackrc> file: + + --type-set eiffel=.e,.eiffel + + +In order to see all currently defined types, use I<--help types>, e.g. +I + +Restrictions: + +=over 4 + +=item + +The types 'skipped', 'make', 'binary' and 'text' are considered "builtin" and +cannot be altered. + +=item + +The shebang line recognition of the types 'perl', 'ruby', 'php', 'python', +'shell' and 'xml' cannot be redefined by I<--type-set>, it is always +active. However, the shebang line is only examined for files where the +extension is not recognised. Therefore it is possible to say +I and +only find your shiny new I<.perl> files (and all files with unrecognized extension +and perl on the shebang line). + +=back + +=head1 ENVIRONMENT VARIABLES + +For commonly-used ack options, environment variables can make life much easier. +These variables are ignored if B<--noenv> is specified on the command line. + +=over 4 + +=item ACKRC + +Specifies the location of the F<.ackrc> file. If this file doesn't +exist, F looks in the default location. + +=item ACK_OPTIONS + +This variable specifies default options to be placed in front of +any explicit options on the command line. + +=item ACK_COLOR_FILENAME + +Specifies the color of the filename when it's printed in B<--group> +mode. By default, it's "bold green". + +The recognized attributes are clear, reset, dark, bold, underline, +underscore, blink, reverse, concealed black, red, green, yellow, +blue, magenta, on_black, on_red, on_green, on_yellow, on_blue, +on_magenta, on_cyan, and on_white. Case is not significant. +Underline and underscore are equivalent, as are clear and reset. +The color alone sets the foreground color, and on_color sets the +background color. + +This option can also be set with B<--color-filename>. + +=item ACK_COLOR_MATCH + +Specifies the color of the matching text when printed in B<--color> +mode. By default, it's "black on_yellow". + +This option can also be set with B<--color-match>. + +See B for the color specifications. + +=item ACK_COLOR_LINENO + +Specifies the color of the line number when printed in B<--color> +mode. By default, it's "bold yellow". + +This option can also be set with B<--color-lineno>. + +See B for the color specifications. + +=item ACK_PAGER + +Specifies a pager program, such as C, C or C, to which +ack will send its output. + +Using C does not suppress grouping and coloring like +piping output on the command-line does, except that on Windows +ack will assume that C does not support color. + +C overrides C if both are specified. + +=item ACK_PAGER_COLOR + +Specifies a pager program that understands ANSI color sequences. +Using C does not suppress grouping and coloring +like piping output on the command-line does. + +If you are not on Windows, you never need to use C. + +=back + +=head1 ACK & OTHER TOOLS + +=head2 Vim integration + +F integrates easily with the Vim text editor. Set this in your +F<.vimrc> to use F instead of F: + + set grepprg=ack\ -a + +That examples uses C<-a> to search through all files, but you may +use other default flags. Now you can search with F and easily +step through the results in Vim: + + :grep Dumper perllib + +=head2 Emacs integration + +Phil Jackson put together an F extension that "provides a +simple compilation mode ... has the ability to guess what files you +want to search for based on the major-mode." + +L + +=head2 TextMate integration + +Pedro Melo is a TextMate user who writes "I spend my day mostly +inside TextMate, and the built-in find-in-project sucks with large +projects. So I hacked a TextMate command that was using find + +grep to use ack. The result is the Search in Project with ack, and +you can find it here: +L" + +=head2 Shell and Return Code + +For greater compatibility with I, I in normal use returns +shell return or exit code of 0 only if something is found and 1 if +no match is found. + +(Shell exit code 1 is C<$?=256> in perl with C or backticks.) + +The I code 2 for errors is not used. + +If C<-f> or C<-g> are specified, then 0 is returned if at least one +file is found. If no files are found, then 1 is returned. + +=cut + +=head1 DEBUGGING ACK PROBLEMS + +If ack gives you output you're not expecting, start with a few simple steps. + +=head2 Use B<--noenv> + +Your environment variables and F<.ackrc> may be doing things you're +not expecting, or forgotten you specified. Use B<--noenv> to ignore +your environment and F<.ackrc>. + +=head2 Use B<-f> to see what files you're scanning + +The reason I created B<-f> in the first place was as a debugging +tool. If ack is not finding matches you think it should find, run +F to see what files are being checked. + +=head1 TIPS + +=head2 Use the F<.ackrc> file. + +The F<.ackrc> is the place to put all your options you use most of +the time but don't want to remember. Put all your --type-add and +--type-set definitions in it. If you like --smart-case, set it +there, too. I also set --sort-files there. + +=head2 Use F<-f> for working with big codesets + +Ack does more than search files. C will create a +list of all the Perl files in a tree, ideal for sending into F. +For example: + + # Change all "this" to "that" in all Perl files in a tree. + ack -f --perl | xargs perl -p -i -e's/this/that/g' + +or if you prefer: + + perl -p -i -e's/this/thatg/' $(ack -f --perl) + +=head2 Use F<-Q> when in doubt about metacharacters + +If you're searching for something with a regular expression +metacharacter, most often a period in a filename or IP address, add +the -Q to avoid false positives without all the backslashing. See +the following example for more... + +=head2 Use ack to watch log files + +Here's one I used the other day to find trouble spots for a website +visitor. The user had a problem loading F, so I +took the access log and scanned it with ack twice. + + ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif + +The first ack finds only the lines in the Apache log for the given +IP. The second finds the match on my troublesome GIF, and shows +the previous five lines from the log in each case. + +=head2 Share your knowledge + +Join the ack-users mailing list. Send me your tips and I may add +them here. + +=head1 FAQ + +=head2 Why isn't ack finding a match in (some file)? + +Probably because it's of a type that ack doesn't recognize. ack's +searching behavior is driven by filetype. B + +Use the C<-f> switch to see a list of files that ack will search +for you. + +If you want ack to search files that it doesn't recognize, use the +C<-a> switch. + +If you want ack to search every file, even ones that it always +ignores like coredumps and backup files, use the C<-u> switch. + +=head2 Why does ack ignore unknown files by default? + +ack is designed by a programmer, for programmers, for searching +large trees of code. Most codebases have a lot files in them which +aren't source files (like compiled object files, source control +metadata, etc), and grep wastes a lot of time searching through all +of those as well and returning matches from those files. + +That's why ack's behavior of not searching things it doesn't recognize +is one of its greatest strengths: the speed you get from only +searching the things that you want to be looking at. + +=head2 Wouldn't it be great if F did search & replace? + +No, ack will always be read-only. Perl has a perfectly good way +to do search & replace in files, using the C<-i>, C<-p> and C<-n> +switches. + +You can certainly use ack to select your files to update. For +example, to change all "foo" to "bar" in all PHP files, you can do +this from the Unix shell: + + $ perl -i -p -e's/foo/bar/g' $(ack -f --php) + +=head2 Can you make ack recognize F<.xyz> files? + +That's an enhancement. Please see the section in the manual about +enhancements. + +=head2 There's already a program/package called ack. + +Yes, I know. + +=head2 Why is it called ack if it's called ack-grep? + +The name of the program is "ack". Some packagers have called it +"ack-grep" when creating packages because there's already a package +out there called "ack" that has nothing to do with this ack. + +I suggest you make a symlink named F that points to F +because one of the crucial benefits of ack is having a name that's +so short and simple to type. + +To do that, run this with F or as root: + + ln -s /usr/bin/ack-grep /usr/bin/ack + +=head2 What does F mean? + +Nothing. I wanted a name that was easy to type and that you could +pronounce as a single syllable. + +=head2 Can I do multi-line regexes? + +No, ack does not support regexes that match multiple lines. Doing +so would require reading in the entire file at a time. + +If you want to see lines near your match, use the C<--A>, C<--B> +and C<--C> switches for displaying context. + +=head1 AUTHOR + +Andy Lester, C<< >> + +=head1 BUGS + +Please report any bugs or feature requests to the issues list at +Github: L + +=head1 ENHANCEMENTS + +All enhancement requests MUST first be posted to the ack-users +mailing list at L. I +will not consider a request without it first getting seen by other +ack users. This includes requests for new filetypes. + +There is a list of enhancements I want to make to F in the ack +issues list at Github: L + +Patches are always welcome, but patches with tests get the most +attention. + +=head1 SUPPORT + +Support for and information about F can be found at: + +=over 4 + +=item * The ack homepage + +L + +=item * The ack issues list at Github + +L + +=item * AnnoCPAN: Annotated CPAN documentation + +L + +=item * CPAN Ratings + +L + +=item * Search CPAN + +L + +=item * Git source repository + +L + +=back + +=head1 ACKNOWLEDGEMENTS + +How appropriate to have Inowledgements! + +Thanks to everyone who has contributed to ack in any way, including +Nick Hooey, +Bo Borgerson, +Mark Szymanski, +Marq Schneider, +Packy Anderson, +JR Boyens, +Dan Sully, +Ryan Niebur, +Kent Fredric, +Mike Morearty, +Ingmar Vanhassel, +Eric Van Dewoestine, +Sitaram Chamarty, +Adam James, +Richard Carlsson, +Pedro Melo, +AJ Schuster, +Phil Jackson, +Michael Schwern, +Jan Dubois, +Christopher J. Madsen, +Matthew Wickline, +David Dyck, +Jason Porritt, +Jjgod Jiang, +Thomas Klausner, +Uri Guttman, +Peter Lewis, +Kevin Riggle, +Ori Avtalion, +Torsten Blix, +Nigel Metheringham, +GEbor SzabE, +Tod Hagan, +Michael Hendricks, +Evar ArnfjErE Bjarmason, +Piers Cawley, +Stephen Steneker, +Elias Lutfallah, +Mark Leighton Fisher, +Matt Diephouse, +Christian Jaeger, +Bill Sully, +Bill Ricker, +David Golden, +Nilson Santos F. Jr, +Elliot Shank, +Merijn Broeren, +Uwe Voelker, +Rick Scott, +Ask BjErn Hansen, +Jerry Gay, +Will Coleda, +Mike O'Regan, +Slaven ReziE<0x107>, +Mark Stosberg, +David Alan Pisoni, +Adriano Ferreira, +James Keenan, +Leland Johnson, +Ricardo Signes +and Pete Krawczyk. + +=head1 COPYRIGHT & LICENSE + +Copyright 2005-2010 Andy Lester. + +This program is free software; you can redistribute it and/or modify +it under the terms of the Artistic License v2.0. + +=cut +package File::Next; + +use strict; +use warnings; + + +our $VERSION = '1.06'; + + + +use File::Spec (); + + +our $name; # name of the current file +our $dir; # dir of the current file + +our %files_defaults; +our %skip_dirs; + +BEGIN { + %files_defaults = ( + file_filter => undef, + descend_filter => undef, + error_handler => sub { CORE::die @_ }, + sort_files => undef, + follow_symlinks => 1, + ); + %skip_dirs = map {($_,1)} (File::Spec->curdir, File::Spec->updir); +} + + +sub files { + ($_[0] eq __PACKAGE__) && die 'File::Next::files must not be invoked as File::Next->files'; + + my ($parms,@queue) = _setup( \%files_defaults, @_ ); + my $filter = $parms->{file_filter}; + + return sub { + while (@queue) { + my ($dir,$file,$fullpath) = splice( @queue, 0, 3 ); + if ( -f $fullpath ) { + if ( $filter ) { + local $_ = $file; + local $File::Next::dir = $dir; + local $File::Next::name = $fullpath; + next if not $filter->(); + } + return wantarray ? ($dir,$file,$fullpath) : $fullpath; + } + elsif ( -d _ ) { + unshift( @queue, _candidate_files( $parms, $fullpath ) ); + } + } # while + + return; + }; # iterator +} + + + + + + + +sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] } +sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] } + +sub reslash { + my $path = shift; + + my @parts = split( /\//, $path ); + + return $path if @parts < 2; + + return File::Spec->catfile( @parts ); +} + + + +sub _setup { + my $defaults = shift; + my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash + + my %passed_parms = %{$passed_parms}; + + my $parms = {}; + for my $key ( keys %{$defaults} ) { + $parms->{$key} = + exists $passed_parms{$key} + ? delete $passed_parms{$key} + : $defaults->{$key}; + } + + # Any leftover keys are bogus + for my $badkey ( keys %passed_parms ) { + my $sub = (caller(1))[3]; + $parms->{error_handler}->( "Invalid option passed to $sub(): $badkey" ); + } + + # If it's not a code ref, assume standard sort + if ( $parms->{sort_files} && ( ref($parms->{sort_files}) ne 'CODE' ) ) { + $parms->{sort_files} = \&sort_standard; + } + my @queue; + + for ( @_ ) { + my $start = reslash( $_ ); + if (-d $start) { + push @queue, ($start,undef,$start); + } + else { + push @queue, (undef,$start,$start); + } + } + + return ($parms,@queue); +} + + +sub _candidate_files { + my $parms = shift; + my $dir = shift; + + my $dh; + if ( !opendir $dh, $dir ) { + $parms->{error_handler}->( "$dir: $!" ); + return; + } + + my @newfiles; + my $descend_filter = $parms->{descend_filter}; + my $follow_symlinks = $parms->{follow_symlinks}; + my $sort_sub = $parms->{sort_files}; + + for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) { + my $has_stat; + + # Only do directory checking if we have a descend_filter + my $fullpath = File::Spec->catdir( $dir, $file ); + if ( !$follow_symlinks ) { + next if -l $fullpath; + $has_stat = 1; + } + + if ( $descend_filter ) { + if ( $has_stat ? (-d _) : (-d $fullpath) ) { + local $File::Next::dir = $fullpath; + local $_ = $file; + next if not $descend_filter->(); + } + } + if ( $sort_sub ) { + push( @newfiles, [ $dir, $file, $fullpath ] ); + } + else { + push( @newfiles, $dir, $file, $fullpath ); + } + } + closedir $dh; + + if ( $sort_sub ) { + return map { @{$_} } sort $sort_sub @newfiles; + } + + return @newfiles; +} + + +1; # End of File::Next +package App::Ack; + +use warnings; +use strict; + + + + +our $VERSION; +our $COPYRIGHT; +BEGIN { + $VERSION = '1.94'; + $COPYRIGHT = 'Copyright 2005-2010 Andy Lester.'; +} + +our $fh; + +BEGIN { + $fh = *STDOUT; +} + + +our %types; +our %type_wanted; +our %mappings; +our %ignore_dirs; + +our $input_from_pipe; +our $output_to_pipe; + +our $dir_sep_chars; +our $is_cygwin; +our $is_windows; + +use File::Spec (); +use File::Glob ':glob'; +use Getopt::Long (); + +BEGIN { + %ignore_dirs = ( + '.bzr' => 'Bazaar', + '.cdv' => 'Codeville', + '~.dep' => 'Interface Builder', + '~.dot' => 'Interface Builder', + '~.nib' => 'Interface Builder', + '~.plst' => 'Interface Builder', + '.git' => 'Git', + '.hg' => 'Mercurial', + '.pc' => 'quilt', + '.svn' => 'Subversion', + _MTN => 'Monotone', + blib => 'Perl module building', + CVS => 'CVS', + RCS => 'RCS', + SCCS => 'SCCS', + _darcs => 'darcs', + _sgbak => 'Vault/Fortress', + 'autom4te.cache' => 'autoconf', + 'cover_db' => 'Devel::Cover', + _build => 'Module::Build', + ); + + %mappings = ( + actionscript => [qw( as mxml )], + ada => [qw( ada adb ads )], + asm => [qw( asm s )], + batch => [qw( bat cmd )], + binary => q{Binary files, as defined by Perl's -B op (default: off)}, + cc => [qw( c h xs )], + cfmx => [qw( cfc cfm cfml )], + clojure => [qw( clj )], + cpp => [qw( cpp cc cxx m hpp hh h hxx )], + csharp => [qw( cs )], + css => [qw( css )], + delphi => [qw( pas int dfm nfm dof dpk dproj groupproj bdsgroup bdsproj )], + elisp => [qw( el )], + erlang => [qw( erl hrl )], + fortran => [qw( f f77 f90 f95 f03 for ftn fpp )], + go => [qw( go )], + haskell => [qw( hs lhs )], + hh => [qw( h )], + html => [qw( htm html shtml xhtml )], + java => [qw( java properties )], + js => [qw( js )], + jsp => [qw( jsp jspx jhtm jhtml )], + lisp => [qw( lisp lsp )], + lua => [qw( lua )], + make => q{Makefiles (including *.mk and *.mak)}, + mason => [qw( mas mhtml mpl mtxt )], + objc => [qw( m h )], + objcpp => [qw( mm h )], + ocaml => [qw( ml mli )], + parrot => [qw( pir pasm pmc ops pod pg tg )], + perl => [qw( pl pm pod t )], + php => [qw( php phpt php3 php4 php5 phtml)], + plone => [qw( pt cpt metadata cpy py )], + python => [qw( py )], + rake => q{Rakefiles}, + ruby => [qw( rb rhtml rjs rxml erb rake spec )], + scala => [qw( scala )], + scheme => [qw( scm ss )], + shell => [qw( sh bash csh tcsh ksh zsh )], + skipped => q{Files, but not directories, normally skipped by ack (default: off)}, + smalltalk => [qw( st )], + sql => [qw( sql ctl )], + tcl => [qw( tcl itcl itk )], + tex => [qw( tex cls sty )], + text => q{Text files, as defined by Perl's -T op (default: off)}, + tt => [qw( tt tt2 ttml )], + vb => [qw( bas cls frm ctl vb resx )], + verilog => [qw( v vh sv )], + vhdl => [qw( vhd vhdl )], + vim => [qw( vim )], + yaml => [qw( yaml yml )], + xml => [qw( xml dtd xsl xslt ent )], + ); + + while ( my ($type,$exts) = each %mappings ) { + if ( ref $exts ) { + for my $ext ( @{$exts} ) { + push( @{$types{$ext}}, $type ); + } + } + } + # add manually Makefile extensions + push @{$types{$_}}, 'make' for qw{ mk mak }; + + # These have to be checked before any filehandle diddling. + $output_to_pipe = not -t *STDOUT; + $input_from_pipe = -p STDIN; + + $is_cygwin = ($^O eq 'cygwin'); + $is_windows = ($^O =~ /MSWin32/); + $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) ); +} + + +sub read_ackrc { + my @files = ( $ENV{ACKRC} ); + my @dirs = + $is_windows + ? ( $ENV{HOME}, $ENV{USERPROFILE} ) + : ( '~', $ENV{HOME} ); + for my $dir ( grep { defined } @dirs ) { + for my $file ( '.ackrc', '_ackrc' ) { + push( @files, bsd_glob( "$dir/$file", GLOB_TILDE ) ); + } + } + for my $filename ( @files ) { + if ( defined $filename && -e $filename ) { + open( my $fh, '<', $filename ) or App::Ack::die( "$filename: $!\n" ); + my @lines = grep { /./ && !/^\s*#/ } <$fh>; + chomp @lines; + close $fh or App::Ack::die( "$filename: $!\n" ); + + # get rid of leading and trailing whitespaces + for ( @lines ) { + s/^\s+//; + s/\s+$//; + } + + return @lines; + } + } + + return; +} + + +sub get_command_line_options { + my %opt = ( + pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER}, + ); + + my $getopt_specs = { + 1 => sub { $opt{1} = $opt{m} = 1 }, + 'A|after-context=i' => \$opt{after_context}, + 'B|before-context=i' => \$opt{before_context}, + 'C|context:i' => sub { shift; my $val = shift; $opt{before_context} = $opt{after_context} = ($val || 2) }, + 'a|all-types' => \$opt{all}, + 'break!' => \$opt{break}, + c => \$opt{count}, + 'color|colour!' => \$opt{color}, + 'color-match=s' => \$ENV{ACK_COLOR_MATCH}, + 'color-filename=s' => \$ENV{ACK_COLOR_FILENAME}, + 'color-lineno=s' => \$ENV{ACK_COLOR_LINENO}, + 'column!' => \$opt{column}, + count => \$opt{count}, + 'env!' => sub { }, # ignore this option, it is handled beforehand + f => \$opt{f}, + flush => \$opt{flush}, + 'follow!' => \$opt{follow}, + 'g=s' => sub { shift; $opt{G} = shift; $opt{f} = 1 }, + 'G=s' => \$opt{G}, + 'group!' => sub { shift; $opt{heading} = $opt{break} = shift }, + 'heading!' => \$opt{heading}, + 'h|no-filename' => \$opt{h}, + 'H|with-filename' => \$opt{H}, + 'i|ignore-case' => \$opt{i}, + 'invert-file-match' => \$opt{invert_file_match}, + 'lines=s' => sub { shift; my $val = shift; push @{$opt{lines}}, $val }, + 'l|files-with-matches' => \$opt{l}, + 'L|files-without-matches' => sub { $opt{l} = $opt{v} = 1 }, + 'm|max-count=i' => \$opt{m}, + 'match=s' => \$opt{regex}, + 'n|no-recurse' => \$opt{n}, + o => sub { $opt{output} = '$&' }, + 'output=s' => \$opt{output}, + 'pager=s' => \$opt{pager}, + 'nopager' => sub { $opt{pager} = undef }, + 'passthru' => \$opt{passthru}, + 'print0' => \$opt{print0}, + 'Q|literal' => \$opt{Q}, + 'r|R|recurse' => sub { $opt{n} = 0 }, + 'show-types' => \$opt{show_types}, + 'smart-case!' => \$opt{smart_case}, + 'sort-files' => \$opt{sort_files}, + 'u|unrestricted' => \$opt{u}, + 'v|invert-match' => \$opt{v}, + 'w|word-regexp' => \$opt{w}, + + 'ignore-dirs=s' => sub { shift; my $dir = remove_dir_sep( shift ); $ignore_dirs{$dir} = '--ignore-dirs' }, + 'noignore-dirs=s' => sub { shift; my $dir = remove_dir_sep( shift ); delete $ignore_dirs{$dir} }, + + 'version' => sub { print_version_statement(); exit; }, + 'help|?:s' => sub { shift; show_help(@_); exit; }, + 'help-types'=> sub { show_help_types(); exit; }, + 'man' => sub { + require Pod::Usage; + Pod::Usage::pod2usage({ + -verbose => 2, + -exitval => 0, + }); + }, + + 'type=s' => sub { + # Whatever --type=xxx they specify, set it manually in the hash + my $dummy = shift; + my $type = shift; + my $wanted = ($type =~ s/^no//) ? 0 : 1; # must not be undef later + + if ( exists $type_wanted{ $type } ) { + $type_wanted{ $type } = $wanted; + } + else { + App::Ack::die( qq{Unknown --type "$type"} ); + } + }, # type sub + }; + + # Stick any default switches at the beginning, so they can be overridden + # by the command line switches. + unshift @ARGV, split( ' ', $ENV{ACK_OPTIONS} ) if defined $ENV{ACK_OPTIONS}; + + # first pass through options, looking for type definitions + def_types_from_ARGV(); + + for my $i ( filetypes_supported() ) { + $getopt_specs->{ "$i!" } = \$type_wanted{ $i }; + } + + + my $parser = Getopt::Long::Parser->new(); + $parser->configure( 'bundling', 'no_ignore_case', ); + $parser->getoptions( %{$getopt_specs} ) or + App::Ack::die( 'See ack --help, ack --help-types or ack --man for options.' ); + + my $to_screen = not output_to_pipe(); + my %defaults = ( + all => 0, + color => $to_screen, + follow => 0, + break => $to_screen, + heading => $to_screen, + before_context => 0, + after_context => 0, + ); + if ( $is_windows && $defaults{color} && not $ENV{ACK_PAGER_COLOR} ) { + if ( $ENV{ACK_PAGER} || not eval { require Win32::Console::ANSI } ) { + $defaults{color} = 0; + } + } + if ( $to_screen && $ENV{ACK_PAGER_COLOR} ) { + $defaults{color} = 1; + } + + while ( my ($key,$value) = each %defaults ) { + if ( not defined $opt{$key} ) { + $opt{$key} = $value; + } + } + + if ( defined $opt{m} && $opt{m} <= 0 ) { + App::Ack::die( '-m must be greater than zero' ); + } + + for ( qw( before_context after_context ) ) { + if ( defined $opt{$_} && $opt{$_} < 0 ) { + App::Ack::die( "--$_ may not be negative" ); + } + } + + if ( defined( my $val = $opt{output} ) ) { + $opt{output} = eval qq[ sub { "$val" } ]; + } + if ( defined( my $l = $opt{lines} ) ) { + # --line=1 --line=5 is equivalent to --line=1,5 + my @lines = split( /,/, join( ',', @{$l} ) ); + + # --line=1-3 is equivalent to --line=1,2,3 + @lines = map { + my @ret; + if ( /-/ ) { + my ($from, $to) = split /-/, $_; + if ( $from > $to ) { + App::Ack::warn( "ignoring --line=$from-$to" ); + @ret = (); + } + else { + @ret = ( $from .. $to ); + } + } + else { + @ret = ( $_ ); + }; + @ret + } @lines; + + if ( @lines ) { + my %uniq; + @uniq{ @lines } = (); + $opt{lines} = [ sort { $a <=> $b } keys %uniq ]; # numerical sort and each line occurs only once! + } + else { + # happens if there are only ignored --line directives + App::Ack::die( 'All --line options are invalid.' ); + } + } + + return \%opt; +} + + +sub def_types_from_ARGV { + my @typedef; + + my $parser = Getopt::Long::Parser->new(); + # pass_through => leave unrecognized command line arguments alone + # no_auto_abbrev => otherwise -c is expanded and not left alone + $parser->configure( 'no_ignore_case', 'pass_through', 'no_auto_abbrev' ); + $parser->getoptions( + 'type-set=s' => sub { shift; push @typedef, ['c', shift] }, + 'type-add=s' => sub { shift; push @typedef, ['a', shift] }, + ) or App::Ack::die( 'See ack --help or ack --man for options.' ); + + for my $td (@typedef) { + my ($type, $ext) = split /=/, $td->[1]; + + if ( $td->[0] eq 'c' ) { + # type-set + if ( exists $mappings{$type} ) { + # can't redefine types 'make', 'skipped', 'text' and 'binary' + App::Ack::die( qq{--type-set: Builtin type "$type" cannot be changed.} ) + if ref $mappings{$type} ne 'ARRAY'; + + delete_type($type); + } + } + else { + # type-add + + # can't append to types 'make', 'skipped', 'text' and 'binary' + App::Ack::die( qq{--type-add: Builtin type "$type" cannot be changed.} ) + if exists $mappings{$type} && ref $mappings{$type} ne 'ARRAY'; + + App::Ack::warn( qq{--type-add: Type "$type" does not exist, creating with "$ext" ...} ) + unless exists $mappings{$type}; + } + + my @exts = split /,/, $ext; + s/^\.// for @exts; + + if ( !exists $mappings{$type} || ref($mappings{$type}) eq 'ARRAY' ) { + push @{$mappings{$type}}, @exts; + for my $e ( @exts ) { + push @{$types{$e}}, $type; + } + } + else { + App::Ack::die( qq{Cannot append to type "$type".} ); + } + } + + return; +} + + +sub delete_type { + my $type = shift; + + App::Ack::die( qq{Internal error: Cannot delete builtin type "$type".} ) + unless ref $mappings{$type} eq 'ARRAY'; + + delete $mappings{$type}; + delete $type_wanted{$type}; + for my $ext ( keys %types ) { + $types{$ext} = [ grep { $_ ne $type } @{$types{$ext}} ]; + } +} + + +sub ignoredir_filter { + return !exists $ignore_dirs{$_} && !exists $ignore_dirs{$File::Next::dir}; +} + + +sub remove_dir_sep { + my $path = shift; + $path =~ s/[$dir_sep_chars]$//; + + return $path; +} + + +use constant TEXT => 'text'; + +sub filetypes { + my $filename = shift; + + my $basename = $filename; + $basename =~ s{.*[$dir_sep_chars]}{}; + + return 'skipped' unless is_searchable( $basename ); + + my $lc_basename = lc $basename; + return ('make',TEXT) if $lc_basename eq 'makefile' || $lc_basename eq 'gnumakefile'; + return ('rake','ruby',TEXT) if $lc_basename eq 'rakefile'; + + # If there's an extension, look it up + if ( $filename =~ m{\.([^\.$dir_sep_chars]+)$}o ) { + my $ref = $types{lc $1}; + return (@{$ref},TEXT) if $ref; + } + + # At this point, we can't tell from just the name. Now we have to + # open it and look inside. + + return unless -e $filename; + # From Elliot Shank: + # I can't see any reason that -r would fail on these-- the ACLs look + # fine, and no program has any of them open, so the busted Windows + # file locking model isn't getting in there. If I comment the if + # statement out, everything works fine + # So, for cygwin, don't bother trying to check for readability. + if ( !$is_cygwin ) { + if ( !-r $filename ) { + App::Ack::warn( "$filename: Permission denied" ); + return; + } + } + + return 'binary' if -B $filename; + + # If there's no extension, or we don't recognize it, check the shebang line + my $fh; + if ( !open( $fh, '<', $filename ) ) { + App::Ack::warn( "$filename: $!" ); + return; + } + my $header = <$fh>; + close $fh; + + if ( $header =~ /^#!/ ) { + return ($1,TEXT) if $header =~ /\b(ruby|p(?:erl|hp|ython))\b/; + return ('shell',TEXT) if $header =~ /\b(?:ba|t?c|k|z)?sh\b/; + } + else { + return ('xml',TEXT) if $header =~ /\Q{Q}; + if ( $opt->{w} ) { + $str = "\\b$str" if $str =~ /^\w/; + $str = "$str\\b" if $str =~ /\w$/; + } + + my $regex_is_lc = $str eq lc $str; + if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) { + $str = "(?i)$str"; + } + + return $str; +} + + +sub check_regex { + my $regex = shift; + + return unless defined $regex; + + eval { qr/$regex/ }; + if ($@) { + (my $error = $@) =~ s/ at \S+ line \d+.*//; + chomp($error); + App::Ack::die( "Invalid regex '$regex':\n $error" ); + } + + return; +} + + + + +sub warn { + return CORE::warn( _my_program(), ': ', @_, "\n" ); +} + + +sub die { + return CORE::die( _my_program(), ': ', @_, "\n" ); +} + +sub _my_program { + require File::Basename; + return File::Basename::basename( $0 ); +} + + + +sub filetypes_supported { + return keys %mappings; +} + +sub _get_thpppt { + my $y = q{_ /|,\\'!.x',=(www)=, U }; + $y =~ tr/,x!w/\nOo_/; + return $y; +} + +sub _thpppt { + my $y = _get_thpppt(); + App::Ack::print( "$y ack $_[0]!\n" ); + exit 0; +} + +sub _key { + my $str = lc shift; + $str =~ s/[^a-z]//g; + + return $str; +} + + +sub show_help { + my $help_arg = shift || 0; + + return show_help_types() if $help_arg =~ /^types?/; + + my $ignore_dirs = _listify( sort { _key($a) cmp _key($b) } keys %ignore_dirs ); + + App::Ack::print( <<"END_OF_HELP" ); +Usage: ack [OPTION]... PATTERN [FILE] + +Search for PATTERN in each source file in the tree from cwd on down. +If [FILES] is specified, then only those files/directories are checked. +ack may also search STDIN, but only if no FILE are specified, or if +one of FILES is "-". + +Default switches may be specified in ACK_OPTIONS environment variable or +an .ackrc file. If you want no dependency on the environment, turn it +off with --noenv. + +Example: ack -i select + +Searching: + -i, --ignore-case Ignore case distinctions in PATTERN + --[no]smart-case Ignore case distinctions in PATTERN, + only if PATTERN contains no upper case + Ignored if -i is specified + -v, --invert-match Invert match: select non-matching lines + -w, --word-regexp Force PATTERN to match only whole words + -Q, --literal Quote all metacharacters; PATTERN is literal + +Search output: + --line=NUM Only print line(s) NUM of each file + -l, --files-with-matches + Only print filenames containing matches + -L, --files-without-matches + Only print filenames with no matches + -o Show only the part of a line matching PATTERN + (turns off text highlighting) + --passthru Print all lines, whether matching or not + --output=expr Output the evaluation of expr for each line + (turns off text highlighting) + --match PATTERN Specify PATTERN explicitly. + -m, --max-count=NUM Stop searching in each file after NUM matches + -1 Stop searching after one match of any kind + -H, --with-filename Print the filename for each match + -h, --no-filename Suppress the prefixing filename on output + -c, --count Show number of lines matching per file + --column Show the column number of the first match + + -A NUM, --after-context=NUM + Print NUM lines of trailing context after matching + lines. + -B NUM, --before-context=NUM + Print NUM lines of leading context before matching + lines. + -C [NUM], --context[=NUM] + Print NUM lines (default 2) of output context. + + --print0 Print null byte as separator between filenames, + only works with -f, -g, -l, -L or -c. + +File presentation: + --pager=COMMAND Pipes all ack output through COMMAND. For example, + --pager="less -R". Ignored if output is redirected. + --nopager Do not send output through a pager. Cancels any + setting in ~/.ackrc, ACK_PAGER or ACK_PAGER_COLOR. + --[no]heading Print a filename heading above each file's results. + (default: on when used interactively) + --[no]break Print a break between results from different files. + (default: on when used interactively) + --group Same as --heading --break + --nogroup Same as --noheading --nobreak + --[no]color Highlight the matching text (default: on unless + output is redirected, or on Windows) + --[no]colour Same as --[no]color + --color-filename=COLOR + --color-match=COLOR + --color-lineno=COLOR Set the color for filenames, matches, and line numbers. + --flush Flush output immediately, even when ack is used + non-interactively (when output goes to a pipe or + file). + +File finding: + -f Only print the files found, without searching. + The PATTERN must not be specified. + -g REGEX Same as -f, but only print files matching REGEX. + --sort-files Sort the found files lexically. + --invert-file-match Print/search handle files that do not match -g/-G. + --show-types Show which types each file has. + +File inclusion/exclusion: + -a, --all-types All file types searched; + Ignores CVS, .svn and other ignored directories + -u, --unrestricted All files and directories searched + --[no]ignore-dir=name Add/Remove directory from the list of ignored dirs + -r, -R, --recurse Recurse into subdirectories (ack's default behavior) + -n, --no-recurse No descending into subdirectories + -G REGEX Only search files that match REGEX + + --perl Include only Perl files. + --type=perl Include only Perl files. + --noperl Exclude Perl files. + --type=noperl Exclude Perl files. + See "ack --help type" for supported filetypes. + + --type-set TYPE=.EXTENSION[,.EXT2[,...]] + Files with the given EXTENSION(s) are recognized as + being of type TYPE. This replaces an existing + definition for type TYPE. + --type-add TYPE=.EXTENSION[,.EXT2[,...]] + Files with the given EXTENSION(s) are recognized as + being of (the existing) type TYPE + + --[no]follow Follow symlinks. Default is off. + + Directories ignored by default: + $ignore_dirs + + Files not checked for type: + /~\$/ - Unix backup files + /#.+#\$/ - Emacs swap files + /[._].*\\.swp\$/ - Vi(m) swap files + /core\\.\\d+\$/ - core dumps + +Miscellaneous: + --noenv Ignore environment variables and ~/.ackrc + --help This help + --man Man page + --version Display version & copyright + --thpppt Bill the Cat + +Exit status is 0 if match, 1 if no match. + +This is version $VERSION of ack. +END_OF_HELP + + return; + } + + + +sub show_help_types { + App::Ack::print( <<'END_OF_HELP' ); +Usage: ack [OPTION]... PATTERN [FILES] + +The following is the list of filetypes supported by ack. You can +specify a file type with the --type=TYPE format, or the --TYPE +format. For example, both --type=perl and --perl work. + +Note that some extensions may appear in multiple types. For example, +.pod files are both Perl and Parrot. + +END_OF_HELP + + my @types = filetypes_supported(); + my $maxlen = 0; + for ( @types ) { + $maxlen = length if $maxlen < length; + } + for my $type ( sort @types ) { + next if $type =~ /^-/; # Stuff to not show + my $ext_list = $mappings{$type}; + + if ( ref $ext_list ) { + $ext_list = join( ' ', map { ".$_" } @{$ext_list} ); + } + App::Ack::print( sprintf( " --[no]%-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) ); + } + + return; +} + +sub _listify { + my @whats = @_; + + return '' if !@whats; + + my $end = pop @whats; + my $str = @whats ? join( ', ', @whats ) . " and $end" : $end; + + no warnings 'once'; + require Text::Wrap; + $Text::Wrap::columns = 75; + return Text::Wrap::wrap( '', ' ', $str ); +} + + +sub get_version_statement { + require Config; + + my $copyright = get_copyright(); + my $this_perl = $Config::Config{perlpath}; + if ($^O ne 'VMS') { + my $ext = $Config::Config{_exe}; + $this_perl .= $ext unless $this_perl =~ m/$ext$/i; + } + my $ver = sprintf( '%vd', $^V ); + + return <<"END_OF_VERSION"; +ack $VERSION +Running under Perl $ver at $this_perl + +$copyright + +This program is free software. You may modify or distribute it +under the terms of the Artistic License v2.0. +END_OF_VERSION +} + + +sub print_version_statement { + App::Ack::print( get_version_statement() ); + + return; +} + + +sub get_copyright { + return $COPYRIGHT; +} + + +sub load_colors { + eval 'use Term::ANSIColor ()'; + + $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow'; + $ENV{ACK_COLOR_FILENAME} ||= 'bold green'; + $ENV{ACK_COLOR_LINENO} ||= 'bold yellow'; + + return; +} + + +sub is_interesting { + return if /^\./; + + my $include; + + for my $type ( filetypes( $File::Next::name ) ) { + if ( defined $type_wanted{$type} ) { + if ( $type_wanted{$type} ) { + $include = 1; + } + else { + return; + } + } + } + + return $include; +} + + + +# print subs added in order to make it easy for a third party +# module (such as App::Wack) to redefine the display methods +# and show the results in a different way. +sub print { print {$fh} @_ } +sub print_first_filename { App::Ack::print( $_[0], "\n" ) } +sub print_blank_line { App::Ack::print( "\n" ) } +sub print_separator { App::Ack::print( "--\n" ) } +sub print_filename { App::Ack::print( $_[0], $_[1] ) } +sub print_line_no { App::Ack::print( $_[0], $_[1] ) } +sub print_column_no { App::Ack::print( $_[0], $_[1] ) } +sub print_count { + my $filename = shift; + my $nmatches = shift; + my $ors = shift; + my $count = shift; + my $show_filename = shift; + + if ($show_filename) { + App::Ack::print( $filename ); + App::Ack::print( ':', $nmatches ) if $count; + } + else { + App::Ack::print( $nmatches ) if $count; + } + App::Ack::print( $ors ); +} + +sub print_count0 { + my $filename = shift; + my $ors = shift; + my $show_filename = shift; + + if ($show_filename) { + App::Ack::print( $filename, ':0', $ors ); + } + else { + App::Ack::print( '0', $ors ); + } +} + + + +{ + my $filename; + my $regex; + my $display_filename; + + my $keep_context; + + my $last_output_line; # number of the last line that has been output + my $any_output; # has there been any output for the current file yet + my $context_overall_output_count; # has there been any output at all + +sub search_resource { + my $res = shift; + my $opt = shift; + + $filename = $res->name(); + + my $v = $opt->{v}; + my $passthru = $opt->{passthru}; + my $max = $opt->{m}; + my $nmatches = 0; + + $display_filename = undef; + + # for --line processing + my $has_lines = 0; + my @lines; + if ( defined $opt->{lines} ) { + $has_lines = 1; + @lines = ( @{$opt->{lines}}, -1 ); + undef $regex; # Don't match when printing matching line + } + else { + $regex = qr/$opt->{regex}/; + } + + # for context processing + $last_output_line = -1; + $any_output = 0; + my $before_context = $opt->{before_context}; + my $after_context = $opt->{after_context}; + + $keep_context = ($before_context || $after_context) && !$passthru; + + my @before; + my $before_starts_at_line; + my $after = 0; # number of lines still to print after a match + + while ( $res->next_text ) { + # XXX Optimize away the case when there are no more @lines to find. + # XXX $has_lines, $passthru and $v never change. Optimize. + if ( $has_lines + ? $. != $lines[0] # $lines[0] should be a scalar + : $v ? m/$regex/ : !m/$regex/ ) { + if ( $passthru ) { + App::Ack::print( $_ ); + next; + } + + if ( $keep_context ) { + if ( $after ) { + print_match_or_context( $opt, 0, $., $-[0], $+[0], $_ ); + $after--; + } + elsif ( $before_context ) { + if ( @before ) { + if ( @before >= $before_context ) { + shift @before; + ++$before_starts_at_line; + } + } + else { + $before_starts_at_line = $.; + } + push @before, $_; + } + last if $max && ( $nmatches >= $max ) && !$after; + } + next; + } # not a match + + ++$nmatches; + + # print an empty line as a divider before first line in each file (not before the first file) + if ( !$any_output && $opt->{show_filename} && $opt->{break} && defined( $context_overall_output_count ) ) { + App::Ack::print_blank_line(); + } + + shift @lines if $has_lines; + + if ( $res->is_binary ) { + App::Ack::print( "Binary file $filename matches\n" ); + last; + } + if ( $keep_context ) { + if ( @before ) { + print_match_or_context( $opt, 0, $before_starts_at_line, $-[0], $+[0], @before ); + @before = (); + $before_starts_at_line = 0; + } + if ( $max && $nmatches > $max ) { + --$after; + } + else { + $after = $after_context; + } + } + print_match_or_context( $opt, 1, $., $-[0], $+[0], $_ ); + + last if $max && ( $nmatches >= $max ) && !$after; + } # while + + return $nmatches; +} # search_resource() + + + +sub print_match_or_context { + my $opt = shift; # opts array + my $is_match = shift; # is there a match on the line? + my $line_no = shift; + my $match_start = shift; + my $match_end = shift; + + my $color = $opt->{color}; + my $heading = $opt->{heading}; + my $show_filename = $opt->{show_filename}; + my $show_column = $opt->{column}; + + if ( $show_filename ) { + if ( not defined $display_filename ) { + $display_filename = + $color + ? Term::ANSIColor::colored( $filename, $ENV{ACK_COLOR_FILENAME} ) + : $filename; + if ( $heading && !$any_output ) { + App::Ack::print_first_filename($display_filename); + } + } + } + + my $sep = $is_match ? ':' : '-'; + my $output_func = $opt->{output}; + for ( @_ ) { + if ( $keep_context && !$output_func ) { + if ( ( $last_output_line != $line_no - 1 ) && + ( $any_output || ( !$heading && defined( $context_overall_output_count ) ) ) ) { + App::Ack::print_separator(); + } + # to ensure separators between different files when --noheading + + $last_output_line = $line_no; + } + + if ( $show_filename ) { + App::Ack::print_filename($display_filename, $sep) if not $heading; + my $display_line_no = + $color + ? Term::ANSIColor::colored( $line_no, $ENV{ACK_COLOR_LINENO} ) + : $line_no; + App::Ack::print_line_no($display_line_no, $sep); + } + + if ( $output_func ) { + while ( /$regex/go ) { + App::Ack::print( $output_func->() . "\n" ); + } + } + else { + if ( $color && $is_match && $regex && + s/$regex/Term::ANSIColor::colored( substr($_, $-[0], $+[0] - $-[0]), $ENV{ACK_COLOR_MATCH} )/eg ) { + # At the end of the line reset the color and remove newline + s/[\r\n]*\z/\e[0m\e[K/; + } + else { + # remove any kind of newline at the end of the line + s/[\r\n]*\z//; + } + if ( $show_column ) { + App::Ack::print_column_no( $match_start+1, $sep ); + } + App::Ack::print($_ . "\n"); + } + $any_output = 1; + ++$context_overall_output_count; + ++$line_no; + } + + return; +} # print_match_or_context() + +} # scope around search_resource() and print_match_or_context() + + +TOTAL_COUNT_SCOPE: { +my $total_count; + +sub get_total_count { + return $total_count; +} + +sub reset_total_count { + $total_count = 0; +} + + +sub search_and_list { + my $res = shift; + my $opt = shift; + + my $nmatches = 0; + my $count = $opt->{count}; + my $ors = $opt->{print0} ? "\0" : "\n"; # output record separator + my $show_filename = $opt->{show_filename}; + + my $regex = qr/$opt->{regex}/; + + if ( $opt->{v} ) { + while ( $res->next_text ) { + if ( /$regex/ ) { + return 0 unless $count; + } + else { + ++$nmatches; + } + } + } + else { + while ( $res->next_text ) { + if ( /$regex/ ) { + ++$nmatches; + last unless $count; + } + } + } + + if ( $opt->{show_total} ) { + $total_count += $nmatches; + } + else { + if ( $nmatches ) { + App::Ack::print_count( $res->name, $nmatches, $ors, $count, $show_filename ); + } + elsif ( $count && !$opt->{l} ) { + App::Ack::print_count0( $res->name, $ors, $show_filename ); + } + } + + return $nmatches ? 1 : 0; +} # search_and_list() + +} # scope around $total_count + + + +sub filetypes_supported_set { + return grep { defined $type_wanted{$_} && ($type_wanted{$_} == 1) } filetypes_supported(); +} + + + +sub print_files { + my $iter = shift; + my $opt = shift; + + my $ors = $opt->{print0} ? "\0" : "\n"; + + my $nmatches = 0; + while ( defined ( my $file = $iter->() ) ) { + App::Ack::print $file, $opt->{show_types} ? " => " . join( ',', filetypes( $file ) ) : (), $ors; + $nmatches++; + last if $opt->{1}; + } + + return $nmatches; +} + + +sub print_files_with_matches { + my $iter = shift; + my $opt = shift; + + # if we have -l and only 1 file given on command line (this means + # show_filename is set to 0), we want to see the filename nevertheless + $opt->{show_filename} = 1 if $opt->{l}; + + $opt->{show_filename} = 0 if $opt->{h}; + $opt->{show_filename} = 1 if $opt->{H}; + + # abuse options to hand in the show_total parameter to search_and_list + $opt->{show_total} = $opt->{count} && !$opt->{show_filename}; + reset_total_count(); + + my $nmatches = 0; + while ( defined ( my $filename = $iter->() ) ) { + my $repo = App::Ack::Repository::Basic->new( $filename ); + my $res; + while ( $res = $repo->next_resource() ) { + $nmatches += search_and_list( $res, $opt ); + $res->close(); + last if $nmatches && $opt->{1}; + } + $repo->close(); + } + + if ( $nmatches && $opt->{show_total} ) { + App::Ack::print_count('', get_total_count(), "\n", 1, 0 ) + } + + return $nmatches; +} + + +sub print_matches { + my $iter = shift; + my $opt = shift; + + $opt->{show_filename} = 0 if $opt->{h}; + $opt->{show_filename} = 1 if $opt->{H}; + + my $nmatches = 0; + while ( defined ( my $filename = $iter->() ) ) { + my $repo; + my $tarballs_work = 0; + if ( $tarballs_work && $filename =~ /\.tar\.gz$/ ) { + App::Ack::die( 'Not working here yet' ); + require App::Ack::Repository::Tar; # XXX Error checking + $repo = App::Ack::Repository::Tar->new( $filename ); + } + else { + $repo = App::Ack::Repository::Basic->new( $filename ); + } + $repo or next; + + while ( my $res = $repo->next_resource() ) { + my $needs_line_scan; + if ( $opt->{regex} && !$opt->{passthru} ) { + $needs_line_scan = $res->needs_line_scan( $opt ); + if ( $needs_line_scan ) { + $res->reset(); + } + } + else { + $needs_line_scan = 1; + } + if ( $needs_line_scan ) { + $nmatches += search_resource( $res, $opt ); + } + $res->close(); + } + last if $nmatches && $opt->{1}; + $repo->close(); + } + return $nmatches; +} + + +sub filetype_setup { + my $filetypes_supported_set = filetypes_supported_set(); + # If anyone says --no-whatever, we assume all other types must be on. + if ( !$filetypes_supported_set ) { + for my $i ( keys %type_wanted ) { + $type_wanted{$i} = 1 unless ( defined( $type_wanted{$i} ) || $i eq 'binary' || $i eq 'text' || $i eq 'skipped' ); + } + } + return; +} + + +EXPAND_FILENAMES_SCOPE: { + my $filter; + + sub expand_filenames { + my $argv = shift; + + my $attr; + my @files; + + foreach my $pattern ( @{$argv} ) { + my @results = bsd_glob( $pattern ); + + if (@results == 0) { + @results = $pattern; # Glob didn't match, pass it thru unchanged + } + elsif ( (@results > 1) or ($results[0] ne $pattern) ) { + if (not defined $filter) { + eval 'require Win32::File;'; + if ($@) { + $filter = 0; + } + else { + $filter = Win32::File::HIDDEN()|Win32::File::SYSTEM(); + } + } # end unless we've tried to load Win32::File + if ( $filter ) { + # Filter out hidden and system files: + @results = grep { not(Win32::File::GetAttributes($_, $attr) and $attr & $filter) } @results; + App::Ack::warn( "$pattern: Matched only hidden files" ) unless @results; + } # end if we can filter by file attributes + } # end elsif this pattern got expanded + + push @files, @results; + } # end foreach pattern + + return \@files; + } # end expand_filenames +} # EXPAND_FILENAMES_SCOPE + + + +sub get_starting_points { + my $argv = shift; + my $opt = shift; + + my @what; + + if ( @{$argv} ) { + @what = @{ $is_windows ? expand_filenames($argv) : $argv }; + $_ = File::Next::reslash( $_ ) for @what; + + # Show filenames unless we've specified one single file + $opt->{show_filename} = (@what > 1) || (!-f $what[0]); + } + else { + @what = '.'; # Assume current directory + $opt->{show_filename} = 1; + } + + for my $start_point (@what) { + App::Ack::warn( "$start_point: No such file or directory" ) unless -e $start_point; + } + return \@what; +} + +sub _match { + my ( $target, $expression, $invert_flag ) = @_; + + if ( $invert_flag ) { + return $target !~ $expression; + } + else { + return $target =~ $expression; + } +} + + +sub get_iterator { + my $what = shift; + my $opt = shift; + + # Starting points are always searched, no matter what + my %starting_point = map { ($_ => 1) } @{$what}; + + my $g_regex = defined $opt->{G} ? qr/$opt->{G}/ : undef; + my $file_filter; + + if ( $g_regex ) { + $file_filter + = $opt->{u} ? sub { _match( $File::Next::name, qr/$g_regex/, $opt->{invert_file_match} ) } # XXX Maybe this should be a 1, no? + : $opt->{all} ? sub { $starting_point{ $File::Next::name } || ( _match( $File::Next::name, qr/$g_regex/, $opt->{invert_file_match} ) && is_searchable( $_ ) ) } + : sub { $starting_point{ $File::Next::name } || ( _match( $File::Next::name, qr/$g_regex/, $opt->{invert_file_match} ) && is_interesting( @ _) ) } + ; + } + else { + $file_filter + = $opt->{u} ? sub {1} + : $opt->{all} ? sub { $starting_point{ $File::Next::name } || is_searchable( $_ ) } + : sub { $starting_point{ $File::Next::name } || is_interesting( @_ ) } + ; + } + + my $descend_filter + = $opt->{n} ? sub {0} + : $opt->{u} ? sub {1} + : \&ignoredir_filter; + + my $iter = + File::Next::files( { + file_filter => $file_filter, + descend_filter => $descend_filter, + error_handler => sub { my $msg = shift; App::Ack::warn( $msg ) }, + sort_files => $opt->{sort_files}, + follow_symlinks => $opt->{follow}, + }, @{$what} ); + return $iter; +} + + +sub set_up_pager { + my $command = shift; + + return if App::Ack::output_to_pipe(); + + my $pager; + if ( not open( $pager, '|-', $command ) ) { + App::Ack::die( qq{Unable to pipe to pager "$command": $!} ); + } + $fh = $pager; + + return; +} + + +sub input_from_pipe { + return $input_from_pipe; +} + + + +sub output_to_pipe { + return $output_to_pipe; +} + + +sub exit_from_ack { + my $nmatches = shift; + + my $rc = $nmatches ? 0 : 1; + exit $rc; +} + + + +1; # End of App::Ack +package App::Ack::Repository; + + +use warnings; +use strict; + +sub FAIL { + require Carp; + Carp::confess( 'Must be overloaded' ); +} + + +sub new { + FAIL(); +} + + +sub next_resource { + FAIL(); +} + + +sub close { + FAIL(); +} + +1; +package App::Ack::Resource; + + +use warnings; +use strict; + +sub FAIL { + require Carp; + Carp::confess( 'Must be overloaded' ); +} + + +sub new { + FAIL(); +} + + +sub name { + FAIL(); +} + + +sub is_binary { + FAIL(); +} + + + +sub needs_line_scan { + FAIL(); +} + + +sub reset { + FAIL(); +} + + +sub next_text { + FAIL(); +} + + +sub close { + FAIL(); +} + +1; +package App::Ack::Plugin::Basic; + + + +package App::Ack::Resource::Basic; + + +use warnings; +use strict; + + +our @ISA = qw( App::Ack::Resource ); + + +sub new { + my $class = shift; + my $filename = shift; + + my $self = bless { + filename => $filename, + fh => undef, + could_be_binary => undef, + opened => undef, + id => undef, + }, $class; + + if ( $self->{filename} eq '-' ) { + $self->{fh} = *STDIN; + $self->{could_be_binary} = 0; + } + else { + if ( !open( $self->{fh}, '<', $self->{filename} ) ) { + App::Ack::warn( "$self->{filename}: $!" ); + return; + } + $self->{could_be_binary} = 1; + } + + return $self; +} + + +sub name { + my $self = shift; + + return $self->{filename}; +} + + +sub is_binary { + my $self = shift; + + if ( $self->{could_be_binary} ) { + return -B $self->{filename}; + } + + return 0; +} + + + +sub needs_line_scan { + my $self = shift; + my $opt = shift; + + return 1 if $opt->{v}; + + my $size = -s $self->{fh}; + if ( $size == 0 ) { + return 0; + } + elsif ( $size > 100_000 ) { + return 1; + } + + my $buffer; + my $rc = sysread( $self->{fh}, $buffer, $size ); + if ( not defined $rc ) { + App::Ack::warn( "$self->{filename}: $!" ); + return 1; + } + return 0 unless $rc && ( $rc == $size ); + + my $regex = $opt->{regex}; + return $buffer =~ /$regex/m; +} + + +sub reset { + my $self = shift; + + seek( $self->{fh}, 0, 0 ) + or App::Ack::warn( "$self->{filename}: $!" ); + + return; +} + + +sub next_text { + if ( defined ($_ = readline $_[0]->{fh}) ) { + $. = ++$_[0]->{line}; + return 1; + } + + return; +} + + +sub close { + my $self = shift; + + if ( not close $self->{fh} ) { + App::Ack::warn( $self->name() . ": $!" ); + } + + return; +} + +package App::Ack::Repository::Basic; + + +our @ISA = qw( App::Ack::Repository ); + + +use warnings; +use strict; + +sub new { + my $class = shift; + my $filename = shift; + + my $self = bless { + filename => $filename, + nexted => 0, + }, $class; + + return $self; +} + + +sub next_resource { + my $self = shift; + + return if $self->{nexted}; + $self->{nexted} = 1; + + return App::Ack::Resource::Basic->new( $self->{filename} ); +} + + +sub close { +} + + + +1; diff --git a/emacs.d/emacs-nav/ack.el b/emacs.d/emacs-nav/ack.el new file mode 100644 index 0000000..2be1290 --- /dev/null +++ b/emacs.d/emacs-nav/ack.el @@ -0,0 +1,84 @@ +;;; ack.el --- Use ack where you might usually use grep. + +;; Copyright (C) 2008 Philip Jackson + +;; Author: Philip Jackson +;; Version: 0.1 + +;; This file is not currently part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 2, or (at +;; your option) any later version. + +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program ; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Commentary: + +;; ack.el provides a simple compilation mode for the perl grep-a-like +;; ack (http://petdance.com/ack/). + +;; If `ack-guess-type' is non-nil and `ack-mode-type-map' has a +;; reasonable value then ack.el will try and guess what you would like +;; in the --type argument for ack. + +;; To install/use put ack.el in your load-path and (require 'ack) in +;; your initialisation file. You can then M-x ack and you're off. + +(require 'compile) + +(defvar ack-guess-type t + "Setting this value to `t' will have `ack' do its best to fill +in the --type argument to the ack command") + +(defvar ack-mode-type-map + '(((c++-mode) . "cpp") + ((c-mode) . "cc") + ((css-mode) . "css") + ((emacs-lisp-mode) . "elisp") + ((fortran-mode) . "fortran") + ((html-mode) . "html") + ((xml-mode nxml-mode) . "xml") + ((java-mode) . "java") + ((lisp-mode) . "lisp") + ((perl-mode cperl-mode yaml-mode) . "perl")) + "alist describing how to fill in the '--type=' argument to ack") + +(defvar ack-command "ack --nocolor --nogroup " + "The command to be run by the ack function.") + +(defun ack-find-type-for-mode () + (catch 'found + (dolist (mode-type ack-mode-type-map) + (when (member major-mode (car mode-type)) + (throw 'found (cdr mode-type)))))) + +(defun ack-build-command () + (let ((type (ack-find-type-for-mode))) + (concat ack-command + (when (and ack-guess-type type) + (concat "--type=" type)) " "))) + +(define-compilation-mode ack-mode "Ack" + "Ack compilation mode." + nil) + +;;;###autoload +(defun ack (command-args) + (interactive + (list (read-shell-command "Run ack (like this): " + (ack-build-command) + 'ack-history + nil))) + (compilation-start command-args 'ack-mode)) + +(provide 'ack) diff --git a/emacs.d/emacs-nav/ack.elc b/emacs.d/emacs-nav/ack.elc new file mode 100644 index 0000000000000000000000000000000000000000..6665295b1d029d1c8d33f82ef3754c85141fbb5e GIT binary patch literal 3532 zcmbtWS#R4$5cZ?3plOqb)=5!xLKl#fmha(E%`PdCaQhSkYjJ01=bN4T*~_0_e6hE;*Ln8r8NJAtMUu*xW*bW0>(fLj5h*tn zh0cpXnnxm~Jfkr=!W!VTfjCHkdft@qCC?A%qu;2pMa9NXK!A-dPUJf zlp@kl^4P$OEGCsxBr=*wN^{WysUD+9iZV@PNqPZBJk971D}U46|Wfm#!0dV)lz2Wn8~;X8!wlz-`Aa9Eaz)cGSR(UNu}%^O}(RUXh5T2(r2B< zA)+7wL@ZTQCNtJ;kzdz{>{}MywUE!dEl|jA11tu5uGpPP-kQ^SUg}b0yQ7L=4i|cv?oN1T$R8>yW%ZZQ3mbF% zUaZCL7ViwXt-)4-LY5YTqTT04G=Z7!^zme4^YLV8Y(5;)TiCR66`LAiULxmF6>2}u zS6Rdd3T{p|4Jk3k?dFv1!ioJ}3@dDHBS+3(_XxX-7hQ}rFdVf%gX9S zi3m07ozt*?PQm$&m(Es68bfW94UI>Jba*h$So=@+_U#XAwf_ri{%gNd&ZZYMtOc;P zjV$^fTRK&iIBAp@eGY3R79OY|G0zUlle#nqti zSeeD{X;|h*C-SN^V$;^OOB$@Qd1Z z-GQ?MQyWYNA1dow2~CRg#_Y&i|ArW&rgU2_GKKBTIPL$hmC0Jhwh}>M?xx6j3~9!J zhJH>X)4fkljH?>Q)@_~6tBfZhV}8_rmesC&)vep7Z8gup`Om_#amMeS8mD7-J)6%~kLA;pg}G1WE^{ z9C7E0Whyq-r`8XR2EvbRN3xk!wiarIVeLDPXEU981cw`?LLmlF z2W%p^l}JvPHf2|MkiSMJo{MchY;S4IcA|{kmX<8*KX{wjgE_u>04F0~Xh}e*HO;j( zhKaoGu9aNY98J`cm4$h1U(o4 literal 0 HcmV?d00001 diff --git a/emacs.d/emacs-nav/nav.el b/emacs.d/emacs-nav/nav.el new file mode 100644 index 0000000..e096936 --- /dev/null +++ b/emacs.d/emacs-nav/nav.el @@ -0,0 +1,651 @@ +;;; nav.el --- Emacs mode for filesystem navigation +;; +;; Copyright 2010 Google Inc. All Rights Reserved. +;; +;; Author: issactrotts@google.com (Issac Trotts) +;; Version: 20110220 +;; + +;;; License: +;; +;; Licensed under the Apache License, Version 2.0 (the "License"); +;; you may not use this file except in compliance with the License. +;; You may obtain a copy of the License at +;; +;; http://www.apache.org/licenses/LICENSE-2.0 +;; +;; Unless required by applicable law or agreed to in writing, software +;; distributed under the License is distributed on an "AS IS" BASIS, +;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +;; See the License for the specific language governing permissions and +;; limitations under the License. + +;;; Commentary: +;; +;; To use this file, put something like the following in your +;; ~/.emacs: +;; +;; (add-to-list 'load-path "/directory/containing/nav/") +;; (require 'nav) +;; +;; Type M-x nav to start navigating. +;; +;; To set options for Nav, type M-x customize, then select +;; Applications, Nav. + +;;; Key Bindings +;; +;; Press ? in the Nav window to display a list of key bindings. +;; + +;;; History: +;; +;; See http://code.google.com/p/emacs-nav/source/list +;; + +;;; Code: + +(condition-case err + (require 'ack) + (error + (message "Could not load ack."))) + +(defgroup nav nil + "A lightweight filesystem navigator." + :group 'applications) + +(defcustom nav-filtered-p t + "*If true, Nav will filter out files and directories such as +hidden files, backups and .elc files. The result depends on +`nav-boring-file-regexps'. +" + :type 'boolean + :group 'nav) + +(defcustom nav-width 25 + "*If non-nil, Nav will change its width to this when it opens files in +other windows. +" + :type 'integer + :group 'nav) + +(defcustom nav-boring-file-regexps + (list "^[.][^.].*$" ; hidden files such as .foo + "^[.]$" ; current directory + "~$" + "[.]elc$" + "[.]pyc$" + "[.]o$" + "[.]bak$" + ;; Stolen from Ack: + "^_MTN$" ; Monotone + "^blib$" ; Perl module building + "^CVS$" ; CVS + "^RCS$" ; RCS + "^SCCS$" ; SCCS + "^_darcs$" ; darcs + "^_sgbak$" ; Vault/Fortress + "^autom4te.cache$" ; autoconf + "^cover_db$" ; Devel::Cover + "^_build$" ; Module::Build + ) + "*Nav ignores filenames that match any regular expression in this list." + :type '(repeat string) + :group 'nav) + + +;; +;; Fontification +;; +(require 'dired) + +(defvar nav-directory-face 'dired-directory + "Face name used for directories.") + +(defvar nav-font-lock-keywords + '(("^.*/$" . nav-directory-face)) + "Regexes and associated faces used by Nav to fontify files and +directories." +) + +(defun nav-make-mode-map () + "Creates and returns a mode map with nav's key bindings." + (let ((keymap (make-sparse-keymap))) + (define-key keymap "a" 'ack) + (define-key keymap "c" 'nav-copy-file-or-dir) + (define-key keymap "C" 'nav-customize) + (define-key keymap "d" 'nav-delete-file-or-dir-on-this-line) + (define-key keymap "e" 'nav-invoke-dired) + (define-key keymap "f" 'nav-find-files) + (define-key keymap "g" 'grep-find) + (define-key keymap "h" 'nav-jump-to-home) + (define-key keymap "j" 'nav-jump-to-dir) + (define-key keymap "m" 'nav-move-file-or-dir) + (define-key keymap "n" 'nav-make-new-directory) + (define-key keymap "o" 'nav-open-file-under-cursor) + (define-key keymap "p" 'nav-pop-dir) + (define-key keymap "P" 'nav-print-current-dir) + (define-key keymap "q" 'delete-window) + (define-key keymap "r" 'nav-refresh) + (define-key keymap "s" 'nav-shell) + (define-key keymap "u" 'nav-go-up-one-dir) + (define-key keymap "w" 'nav-shrink-wrap) + (define-key keymap "W" 'nav-save-window-width) + (define-key keymap "!" 'nav-shell-command) + (define-key keymap "." 'nav-toggle-hidden-files) + (define-key keymap "?" 'nav-help-screen) + (define-key keymap "-" 'nav-shrink-a-bit) + (define-key keymap "_" 'nav-shrink-a-bit) + (define-key keymap "+" 'nav-expand-a-bit) + (define-key keymap "=" 'nav-expand-a-bit) + (define-key keymap " " 'nav-jump-to-name) + (define-key keymap [(control ?x) (control ?f)] 'find-file-other-window) + keymap)) + +(defun nav-shrink-window-horizontally (delta) + "First, compute a new value for the delta to make sure we don't +make the window too small, according to the following equation: + +window-width - delta' = max(window-min-width, window-width - delta) +" + (let ((delta (- (window-width) + (max window-min-width + (- (window-width) delta))))) + (shrink-window-horizontally delta) + (nav-remember-current-width-during-this-session))) + +(defun nav-enlarge-window-horizontally (delta) + (enlarge-window-horizontally delta) + (nav-remember-current-width-during-this-session)) + +(defun nav-remember-current-width-during-this-session () + (customize-set-variable 'nav-width (window-width))) + +(defun nav-shrink-a-bit () + "Decreases the width of the nav window by one character." + (interactive) + (nav-shrink-window-horizontally 1)) + +(defun nav-expand-a-bit () + "Increases the width of the nav window by one character." + (interactive) + (nav-enlarge-window-horizontally 1)) + +(defun nav-quit-help () + "Exits the nav help screen." + (interactive) + (kill-buffer (current-buffer))) + +(defun nav-help-screen () + "Displays the help screen." + (interactive) + (switch-to-buffer-other-window "*nav-help*") + (insert "\ +Nav Key Bindings +================ + +Enter/Return: Go to directory under cursor, or open file under + cursor in other window. + +Tab: Move forward through filenames. +Shift-Tab: Move backward through filenames. + +Space: Press spacebar, then any other letter to jump to filename + that starts with that letter. + +a\t Recursively grep for a Perl regex using Ack (http://betterthangrep.com/). +c\t Copy file or directory under cursor. +C\t Customize Nav settings and bookmarks. +d\t Delete file or directory under cursor (asks to confirm first). +e\t Edit current directory in dired. +f\t Recursively find files whose titles match a Perl regex (using Ack). +g\t Grep recursively from current directory using grep-find +h\t Jump to home (~). +j\t Jump to another directory. +m\t Move or rename file or directory. +n\t Make a new directory. +o\t Open file under cursor in the nav window. +p\t Pop directory stack to go back to the previous directory. +P\t Print full path of current displayed directory. +q\t Quit nav. +r\t Refresh. +s\t Start a shell in an emacs window in the current directory. +u\t Go up to parent directory. +w\t Shrink-wrap Nav's window to fit the longest filename in the current directory. + \t Hit C-x + to roughly undo this by balancing windows. +!\t Run shell command. +.\t Toggle display of hidden files. +?\t Show this help screen. +-\t Narrow Nav window by one character. ++\t Widen Nav window by one character. +") + (nav-goto-line 1) + (view-mode -1) + (toggle-read-only 1)) + +(defvar nav-mode-map + (nav-make-mode-map) + "Mode map for nav mode") + +(defvar nav-dir-stack '()) + +(defvar nav-map-dir-to-line-number (make-hash-table :test 'equal) + "Hash table from dir paths to most recent cursor pos in them.") + +(defvar nav-button-face nil) + +(defconst nav-default-line-num 2 + "Line where cursor starts in directories that have not yet been +visited. A value of 1 would start the cursor off on ../.") + +(defconst nav-shell-buffer-name "*nav-shell*" + "Name of the buffer used for the command line shell spawned by + nav on the 's' key.") + +(defconst nav-buffer-name "*nav*" + "Name of the buffer where nav shows directory contents.") + +(defconst nav-buffer-name-for-find-results "*nav-find*" + "Name of the buffer where nav shows find results.") + +(defun nav-join (sep string-list) + (mapconcat 'identity string-list sep)) + +(defun nav-toggle-hidden-files () + (interactive) + (setq nav-filtered-p (not nav-filtered-p)) + (nav-refresh)) + +(defun nav-filename-matches-some-regexp (filename regexps) + (let ((matches-p nil)) + (dolist (rx regexps) + (if (string-match rx filename) + (setq matches-p t))) + matches-p)) + +;; http://www.emacswiki.org/emacs/ElispCookbook#toc41 +(defun nav-filter (condp lst) + (delq nil + (mapcar (lambda (x) (and (funcall condp x) x)) lst))) + +(defun nav-filter-out-boring-filenames (filenames boring-regexps) + (nav-filter + (lambda (filename) + (not (nav-filename-matches-some-regexp filename boring-regexps))) + filenames)) + +(defun nav-get-line-for-cur-dir () + (gethash (nav-get-working-dir) nav-map-dir-to-line-number)) + +(defun nav-cd (dirname) + "Changes to a different directory and pushes it onto the stack." + (let ((dirname (file-name-as-directory (file-truename dirname)))) + (nav-save-cursor-line) + (setq default-directory dirname) + (nav-show-dir dirname) + (nav-restore-cursor-line))) + +(defun nav-save-cursor-line () + "Updates line number hash table." + (let ((line-num (nav-line-number-at-pos (point)))) + (puthash (nav-get-working-dir) line-num nav-map-dir-to-line-number))) + +(defun nav-goto-line (line) + "Jumps point to the given line." + (goto-char (point-min)) (forward-line (1- line))) + +(defun nav-restore-cursor-line () + "Remembers what line we were on last time we visited this directory." + (let ((line-num (or (nav-get-line-for-cur-dir) + nav-default-line-num))) + (nav-goto-line line-num))) + +(defun nav-open-file (filename) + "Opens a file or directory from Nav." + (interactive "FFilename:") + (if (file-directory-p filename) + (nav-push-dir filename) + (find-file filename))) + +(defun nav-open-file-other-window (filename) + "Opens a file or directory from Nav." + (interactive "FFilename:") + (if (file-directory-p filename) + (nav-push-dir filename) + (find-file-other-window filename))) + +(defun nav-open-file-under-cursor () + "Finds the file under the cursor." + (interactive) + (let ((filename (nav-get-cur-line-str))) + (nav-open-file filename))) + +(defun nav-get-current-window () + "Returns the currently selected window." + (get-buffer-window (current-buffer))) + +(defun nav-get-current-window-width () + "Returns the width of the currently selected window." + (window-width (nav-get-current-window))) + +(defun nav-go-up-one-dir () + "Points Nav to ../." + (interactive) + (nav-push-dir "..")) + +(defun nav-get-shrink-wrap-width () + (let* ((lines (split-string (buffer-string) "\n" t)) + (num-lines (length lines)) + (line-lengths (mapcar 'length lines)) + (desired-width (+ 1 (apply 'max line-lengths))) + (max-width (/ (frame-width) 2)) + (new-width (min desired-width max-width))) + new-width)) + +(defun nav-shrink-wrap () + "Updates the width of the Nav window to fit the longest filename in the +current directory. Updates the global variable nav-width as a side effect." + (interactive) + (nav-set-window-width (nav-get-shrink-wrap-width))) + +(defun nav-push-dir (dirname) + (let ((dirname (file-truename dirname))) + (when (not (string= dirname default-directory)) + (push (file-truename default-directory) nav-dir-stack) + (nav-cd dirname)))) + +(defun nav-pop-dir () + "Goes to the previous directory in Nav's history. +This works like a web browser's back button." + (interactive) + (let ((dir nil)) + (while (and nav-dir-stack + (or (not dir) + (equal dir (file-name-as-directory (file-truename "."))) + (not (file-exists-p dir)))) + (setq dir (pop nav-dir-stack))) + (setq dir (or dir ".")) + (nav-cd dir))) + +(defun nav-get-cur-line-str () + (buffer-substring-no-properties (point-at-bol) + (point-at-eol))) + +(defun nav-non-boring-directory-files (dir) + (nav-filter-out-boring-filenames (directory-files dir) + (if nav-filtered-p + nav-boring-file-regexps + '() + ))) + +(defun nav-dir-suffix (dir) + (replace-regexp-in-string ".*/" "" (directory-file-name dir))) + +(defun nav-line-number-at-pos (p) + (let ((line-num 1)) + (dotimes (i p line-num) + (if (eq ?\n (char-after i)) + (setq line-num (+ line-num 1)))))) + +(defun nav-replace-buffer-contents (new-contents) + (let ((saved-line-number (nav-line-number-at-pos (point))) + ;; Setting inhibit-read-only to t here lets us edit the buffer + ;; in this let-block. + (inhibit-read-only t)) + (erase-buffer) + (insert new-contents) + (nav-make-filenames-clickable) + (nav-goto-line saved-line-number))) + +(defun nav-select-window (window) + (if window + (select-window window) + (message "Attempted to select nil window"))) + +(defun nav-button-action-to-open-file (button) + "Opens a file or directory in response to a button." + (let* ((buffer (overlay-buffer button)) + (window-with-nav (get-buffer-window buffer))) + (nav-select-window window-with-nav) + (if (= 1 (count-windows)) + (split-window-horizontally)) + (nav-open-file-other-window (button-label button)) + + (if nav-width + (let ((other-window (nav-get-current-window))) + (select-window window-with-nav) + (nav-set-window-width nav-width) + (select-window other-window))))) + +(defun nav-button-action-to-open-dir (button) + (let ((buffer (overlay-buffer button))) + (pop-to-buffer buffer) + (nav-push-dir (button-label button)))) + +(defun nav-make-filenames-clickable () + (condition-case err + (save-excursion + (nav-goto-line 1) + (dotimes (i (count-lines 1 (point-max))) + (let* ((start (line-beginning-position)) + (end (line-end-position)) + (filename (buffer-substring-no-properties start end)) + (action (if (file-directory-p filename) + 'nav-button-action-to-open-dir + 'nav-button-action-to-open-file))) + (make-button start end + 'action action + 'follow-link t + 'face nav-button-face + 'help-echo nil)) + (forward-line 1))) + (error + ;; This can happen for versions of emacs that don't have + ;; make-button defined. + 'failed))) + +(defun nav-string< (s1 s2) + "Tells whether S1 comes lexically before S2, ignoring case." + (string< (downcase s1) (downcase s2))) + +(defun nav-show-dir (dir) + (let ((new-contents '())) + (dolist (filename (nav-non-boring-directory-files dir)) + (let ((line (concat filename + (if (file-directory-p filename) + "/" + "") + ))) + (push line new-contents))) + (let* ((new-contents (sort new-contents 'nav-string<)) + (new-contents (nav-join "\n" new-contents))) + (nav-replace-buffer-contents new-contents)) + (setq mode-line-format (nav-make-mode-line "d" dir)) + (force-mode-line-update))) + +(defun nav-set-window-width (n) + (let ((n (max n window-min-width))) + (if (> (window-width) n) + (nav-shrink-window-horizontally (- (window-width) n))) + (if (< (window-width) n) + (nav-enlarge-window-horizontally (- n (window-width)))))) + +(defun nav-save-window-width () + "Saves the width of the current window as the default width for Nav." + (interactive) + (let ((width (window-width (nav-get-current-window)))) + (customize-save-variable 'nav-width width))) + +(defun nav-get-working-dir () + (file-name-as-directory (file-truename default-directory))) + +(defun nav-invoke-dired () + "Invokes dired on the current directory." + (interactive) + (dired (nav-get-working-dir))) + +(defun nav-find-files (pattern) + "Recursively finds files whose names match a Perl regular expression." + (interactive "sPattern: ") + (let* ((pattern (format "%s[^/]*$" pattern)) + (find-command (format "ack -a -l '.' | ack %s" pattern)) + (inhibit-read-only t)) + (erase-buffer) + (call-process-shell-command find-command nil (current-buffer)) + (nav-make-filenames-clickable) + (message "Hit r to bring back Nav directory listing.") + (cond ((string= "" (buffer-string)) + (insert "No matching files found.")) + (t + ;; Enable nav keyboard shortcuts, mainly so hitting enter will open + ;; files. + (use-local-map nav-mode-map)) + ) + (forward-line -1) + )) + +(defun nav-refresh () + "Resizes Nav window to original size, updates its contents." + (interactive) + (nav-show-dir ".") + (nav-restore-cursor-line)) + +(defun nav-jump-to-home () + "Show home directory in Nav." + (interactive) + (nav-push-dir "~")) + +(defun nav-jump-to-name (arg) + (interactive "K") + (nav-goto-line 2) + (let ((nav-search-string (concat "^" arg))) + (search-forward-regexp nav-search-string))) + +(defun nav-jump-to-dir (dirname) + "Shows a specified directory in Nav." + (interactive "fDirectory: ") + (nav-push-dir dirname)) + +(defun nav-make-mode-line (mode dir) + (concat "-(nav)" + (nav-dir-suffix (file-truename dir)) + "/" + " " + (format "[%s]" + (if nav-filtered-p + "filtered" + "unfiltered")) + ) + ) + +(defun nav-delete-file-or-dir (filename) + (nav-save-cursor-line) + (if (and (file-directory-p filename) + (not (file-symlink-p (directory-file-name filename)))) + (when (yes-or-no-p (format "Really delete directory %s ?" filename)) + (delete-directory filename t) + (nav-refresh)) + ;; We first use directory-file-name to strip the trailing slash + ;; if it's a symlink to a directory. + (let ((filename (directory-file-name filename))) + (when (y-or-n-p (format "Really delete file %s ? " filename)) + (delete-file filename) + (nav-refresh)))) + (nav-restore-cursor-line)) + +(defun nav-delete-file-or-dir-on-this-line () + "Deletes a file or directory." + (interactive) + (nav-delete-file-or-dir (nav-get-cur-line-str))) + +(defun nav-copy-file-or-dir (target-name) + "Copies a file or directory." + (interactive "FCopy to: ") + (let ((filename (nav-get-cur-line-str))) + (if (file-directory-p filename) + (copy-directory filename target-name) + (copy-file filename target-name))) + (nav-refresh)) + +(defun nav-customize () + "Starts customization for Nav." + (interactive) + (customize-group "nav")) + +(defun nav-move-file-or-dir (target-name) + "Moves a file or directory." + (interactive "FMove to: ") + (let ((filename (nav-get-cur-line-str))) + (rename-file filename target-name)) + (nav-refresh)) + +(defun nav-append-slashes-to-dir-names (names) + (mapcar (lambda (name) + (if (file-directory-p name) + (concat name "/") + name)) + names)) + +(defun nav-make-new-directory (name) + "Creates a new directory." + (interactive "sMake directory: ") + (make-directory name) + (nav-refresh)) + +(defun nav-shell () + "Starts up a shell on the current nav directory. + +Thanks to claudio.bley for this new, improved version. +http://code.google.com/p/emacs-nav/issues/detail?id=78 +" + (interactive) + (let ((default-directory (nav-get-working-dir))) + (shell nav-shell-buffer-name))) + +(defun nav-shell-command (command) + "Runs a shell command and then refreshes the Nav window." + (interactive "sShell command: ") + (shell-command command) + (nav-refresh)) + +(defun nav-print-current-dir () + "Shows the full path that nav is currently displaying" + (interactive) + (print default-directory)) + +(define-derived-mode nav-mode fundamental-mode + "Nav mode navigates filesystems." + (setq mode-name "Nav") + (use-local-map nav-mode-map) + (setq buffer-read-only t) + (setq truncate-lines t) + (setq font-lock-defaults '(nav-font-lock-keywords)) + (nav-refresh)) + +(defun nav-disable-emacs23-window-splitting () + "Turns off the new feature where Emacs 23 automatically splits windows when +opening files in a large frame." + (interactive) + (setq split-width-threshold most-positive-fixnum) + (setq split-height-threshold most-positive-fixnum)) + +(defun nav-in-place () + "Starts Nav in the current window." + (interactive) + (switch-to-buffer (generate-new-buffer-name nav-buffer-name)) + (nav-mode) + (nav-refresh)) + +;; The next line is for ELPA, the Emacs Lisp Package Archive. +;;;###autoload +(defun nav () + "Opens Nav in a new window to the left of the current one." + (interactive) + (let ((default-directory (nav-get-working-dir))) + (split-window-horizontally) + (nav-in-place) + (nav-set-window-width nav-width))) + +(provide 'nav) + +;;; nav.el ends here diff --git a/emacs.d/emacs-nav/nav.elc b/emacs.d/emacs-nav/nav.elc new file mode 100644 index 0000000000000000000000000000000000000000..54b4f8f9766dd82e05357ad4cd83778c6158a50f GIT binary patch literal 20205 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 Xu2G$vr}*X}w^>10&+ + +;; Licensed under the same terms as Emacs. + +;; Version: 0.1.0 +;; Keywords: textmate osx mac +;; Created: 8 April 2010 +;; Author: Geoffrey Grosenbach +;; +;; Enhancements: Josh Peek http://joshpeek.com/ + +;; This file is NOT part of GNU Emacs. + +;; Licensed under the same terms as Emacs. + +;;; Commentary: + +;; A sensible fuzzy file chooser with a beautiful Mac OS X GUI. +;; +;; This minimal enhancement to textmate-mode calls the external +;; PeepOpen.app when you hit Command-T (or equivalent). + +;; ⌘T - Go to File + +;;; Installation: + +;; This plugin assumes that you've already loaded Chris Wanstrath's +;; textmate.el in your emacs configuration. Load this file afterward. +;; +;; Copy this file to ~/.emacs.d/vendor/peepopen.el (or use the menu +;; item in the PeepOpen application). +;; + +;; You'll also need textmate.el: +;; +;; $ cd ~/.emacs.d/vendor +;; $ git clone git://github.com/defunkt/textmate.el.git + +;; Finally, require both libraries and activate textmate-mode. +;; In most Emacs distributions, you'll do this in ~/.emacs.d/init.el +;; or your personal configuration file. +;; +;; In Aquamacs, this goes in ~/Library/Preferences/Aquamacs Emacs/Preferences.el. + +;; (add-to-list 'load-path "~/.emacs.d/vendor/textmate.el") +;; (require 'textmate) +;; (add-to-list 'load-path "~/.emacs.d/vendor/") +;; (require 'peepopen) +;; (textmate-mode) + +;; For Emacs 23 or Aquamacs, use this to open files in the existing frame: +;; +;; (setq ns-pop-up-frames nil) + +;;;###autoload +(defun peepopen-goto-file-gui () + "Uses external GUI app to quickly jump to a file in the project." + (interactive) + (defun string-join (separator strings) + "Join all STRINGS using SEPARATOR." + (mapconcat 'identity strings separator)) + (let ((root (textmate-project-root))) + (when (null root) + (error + (concat + "Can't find a suitable project root (" + (string-join " " *textmate-project-roots* ) + ")"))) + (shell-command-to-string + (format "open 'peepopen://%s?editor=%s'" + (expand-file-name root) + (invocation-name))))) + +;;;###autoload +(defun peepopen-bind-keys () + (cond ((featurep 'aquamacs) (peepopen-bind-aquamacs-keys)) + ((featurep 'mac-carbon) (peepopen-bind-carbon-keys)) + ((featurep 'ns) (peepopen-bind-ns-keys)))) + +(defun peepopen-bind-aquamacs-keys () + ;; Need `osx-key-mode-map' to override + (define-key osx-key-mode-map (kbd "A-t") 'peepopen-goto-file-gui) + (define-key *textmate-mode-map* (kbd "A-t") 'peepopen-goto-file-gui)) + +(defun peepopen-bind-carbon-keys () + (define-key *textmate-mode-map* [(meta t)] 'peepopen-goto-file-gui)) + +(defun peepopen-bind-ns-keys () + (define-key *textmate-mode-map* [(super t)] 'peepopen-goto-file-gui)) + +;;;###autoload +(add-hook 'textmate-mode-hook 'peepopen-bind-keys) + +(provide 'peepopen) diff --git a/emacs.d/textmate.el b/emacs.d/vendor/textmate.el similarity index 62% rename from emacs.d/textmate.el rename to emacs.d/vendor/textmate.el index 910a399..3f26974 100644 --- a/emacs.d/textmate.el +++ b/emacs.d/vendor/textmate.el @@ -1,4 +1,4 @@ -;; textmate.el --- TextMate minor mode for Emacs +;;; textmate.el --- TextMate minor mode for Emacs ;; Copyright (C) 2008, 2009 Chris Wanstrath @@ -7,7 +7,7 @@ ;; Keywords: textmate osx mac ;; Created: 22 Nov 2008 ;; Author: Chris Wanstrath -;; Version: 1 +;; Version: 2 ;; This file is NOT part of GNU Emacs. @@ -25,6 +25,8 @@ ;; ⌘[ - Shift Left ;; ⌥⌘] - Align Assignments ;; ⌥⌘[ - Indent Line +;; ⌥↑ - Column Up +;; ⌥↓ - Column Down ;; ⌘RET - Insert Newline at Line's End ;; ⌥⌘T - Reset File Cache (for Go to File) @@ -65,11 +67,11 @@ ;;; Minor mode (defvar *textmate-gf-exclude* - "/(\\.|vendor|fixtures|tmp|log|[bB]uild|\\.xcodeproj|\\.nib|\\.framework|\\.app|\\.pbproj|\\.pbxproj|\\.xcode|\\.xcodeproj|\\.bundle|\\.pyc|Frameworks)" + "(/|^)(\\.+[^/]+|vendor|fixtures|tmp|log|classes|build)($|/)|(\\.xcodeproj|\\.nib|\\.framework|\\.app|\\.pbproj|\\.pbxproj|\\.xcode|\\.xcodeproj|\\.bundle|\\.pyc)(/|$)" "Regexp of files to exclude from `textmate-goto-file'.") (defvar *textmate-project-roots* - '(".git" ".hg" "Rakefile" "Makefile" "README" "build.xml") + '(".git" ".hg" "Rakefile" "Makefile" "README" "build.xml" ".emacs-project") "The presence of any file/directory in this list indicates a project root.") (defvar textmate-use-file-cache t @@ -79,9 +81,9 @@ "The library `textmade-goto-symbol' and `textmate-goto-file' should use for completing filenames and symbols (`ido' by default)") -(defvar textmate-find-files-command "find %s -type f" - "The command `textmate-project-root' uses to find files. %s will be replaced -the project root.") +(defvar textmate-find-files-command "find \"%s\" -type f" + "The command `textmate-project-files' uses to find files. %s will be replaced +by the project root.") (defvar *textmate-completing-function-alist* '((ido ido-completing-read) (icicles icicle-completing-read) @@ -97,55 +99,72 @@ the project root.") (defvar *textmate-mode-map* (let ((map (make-sparse-keymap))) (cond ((featurep 'aquamacs) - (define-key map [A-return] 'textmate-next-line) - (define-key map (kbd "A-M-t") 'textmate-clear-cache) - (define-key map (kbd "A-M-]") 'align) - (define-key map (kbd "A-M-[") 'indent-according-to-mode) - (define-key map (kbd "A-]") 'textmate-shift-right) - (define-key map (kbd "A-[") 'textmate-shift-left) - (define-key map (kbd "A-/") 'comment-or-uncomment-region-or-line) - (define-key map (kbd "A-L") 'textmate-select-line) - (define-key map (kbd "A-t") 'textmate-goto-file) - (define-key map (kbd "A-T") 'textmate-goto-symbol)) - ((and (featurep 'mac-carbon) (eq window-system 'mac) mac-key-mode) - (define-key map [(alt meta return)] 'textmate-next-line) - (define-key map [(alt meta t)] 'textmate-clear-cache) - (define-key map [(alt meta \])] 'align) - (define-key map [(alt meta \[)] 'indent-according-to-mode) - (define-key map [(alt \])] 'textmate-shift-right) - (define-key map [(alt \[)] 'textmate-shift-left) - (define-key map [(meta /)] 'comment-or-uncomment-region-or-line) - (define-key map [(alt t)] 'textmate-goto-file) + (define-key map [A-return] 'textmate-next-line) + (define-key map (kbd "A-M-t") 'textmate-clear-cache) + (define-key map (kbd "A-M-]") 'align) + (define-key map (kbd "A-M-[") 'indent-according-to-mode) + (define-key map (kbd "A-]") 'textmate-shift-right) + (define-key map (kbd "A-[") 'textmate-shift-left) + (define-key map (kbd "A-/") 'comment-or-uncomment-region-or-line) + (define-key map (kbd "A-L") 'textmate-select-line) + (define-key map (kbd "A-t") 'textmate-goto-file) + (define-key map (kbd "A-T") 'textmate-goto-symbol) + (define-key map (kbd "M-") 'textmate-column-up) + (define-key map (kbd "M-") 'textmate-column-down) + (define-key map (kbd "M-S-") 'textmate-column-up-with-select) + (define-key map (kbd "M-S-") 'textmate-column-down-with-select)) + ((and (featurep 'mac-carbon) (eq window-system 'mac) mac-key-mode) + (define-key map [(alt meta return)] 'textmate-next-line) + (define-key map [(alt meta t)] 'textmate-clear-cache) + (define-key map [(alt meta \])] 'align) + (define-key map [(alt meta \[)] 'indent-according-to-mode) + (define-key map [(alt \])] 'textmate-shift-right) + (define-key map [(alt \[)] 'textmate-shift-left) + (define-key map [(meta /)] 'comment-or-uncomment-region-or-line) + (define-key map [(alt t)] 'textmate-goto-file) (define-key map [(alt shift l)] 'textmate-select-line) - (define-key map [(alt shift t)] 'textmate-goto-symbol)) - ((featurep 'ns) ;; Emacs.app - (define-key map [(super meta return)] 'textmate-next-line) - (define-key map [(super meta t)] 'textmate-clear-cache) - (define-key map [(super meta \])] 'align) - (define-key map [(super meta \[)] 'indent-according-to-mode) - (define-key map [(super \])] 'textmate-shift-right) - (define-key map [(super \[)] 'textmate-shift-left) - (define-key map [(super /)] 'comment-or-uncomment-region-or-line) -;; (define-key map [(super t)] 'textmate-goto-file) - (define-key map [(super shift l)] 'textmate-select-line) - (define-key map [(super shift t)] 'textmate-goto-symbol)) - (t ;; Any other version - (define-key map [(meta return)] 'textmate-next-line) - (define-key map [(control c)(control t)] 'textmate-clear-cache) - (define-key map [(control c)(control a)] 'align) - (define-key map [(control tab)] 'textmate-shift-right) - (define-key map [(control shift tab)] 'textmate-shift-left) - (define-key map [(control c)(control k)] 'comment-or-uncomment-region-or-line) - (define-key map [(meta t)] 'textmate-goto-file) - (define-key map [(meta shift l)] 'textmate-select-line) - (define-key map [(meta shift t)] 'textmate-goto-symbol))) - map)) + (define-key map [(alt shift t)] 'textmate-goto-symbol) + (define-key map [(alt up)] 'textmate-column-up) + (define-key map [(alt down)] 'textmate-column-down) + (define-key map [(alt shift up)] 'textmate-column-up-with-select) + (define-key map [(alt shift down)] 'textmate-column-down-with-select)) + ((featurep 'ns) ;; Emacs.app + (define-key map [(super meta return)] 'textmate-next-line) + (define-key map [(super meta t)] 'textmate-clear-cache) + (define-key map [(super meta \])] 'align) + (define-key map [(super meta \[)] 'indent-according-to-mode) + (define-key map [(super \])] 'textmate-shift-right) + (define-key map [(super \[)] 'textmate-shift-left) + (define-key map [(super /)] 'comment-or-uncomment-region-or-line) + (define-key map [(super t)] 'textmate-goto-file) + (define-key map [(super shift l)] 'textmate-select-line) + (define-key map [(super shift t)] 'textmate-goto-symbol) + (define-key map [(meta up)] 'textmate-column-up) + (define-key map [(meta down)] 'textmate-column-down) + (define-key map [(meta shift up)] 'textmate-column-up-with-select) + (define-key map [(meta shift down)] 'textmate-column-down-with-select)) + (t ;; Any other version + (define-key map [(meta return)] 'textmate-next-line) + (define-key map [(control c)(control t)] 'textmate-clear-cache) + (define-key map [(control c)(control a)] 'align) + (define-key map [(control tab)] 'textmate-shift-right) + (define-key map [(control shift tab)] 'textmate-shift-left) + (define-key map [(control c)(control k)] 'comment-or-uncomment-region-or-line) + (define-key map [(meta t)] 'textmate-goto-file) + (define-key map [(meta shift l)] 'textmate-select-line) + (define-key map [(meta shift t)] 'textmate-goto-symbol) + (define-key map [(alt up)] 'textmate-column-up) + (define-key map [(alt down)] 'textmate-column-down) + (define-key map [(alt shift up)] 'textmate-column-up-with-select) + (define-key map [(alt shift down)] 'textmate-column-down-with-select))) + map)) (defvar *textmate-project-root* nil "Used internally to cache the project root.") (defvar *textmate-project-files* '() "Used internally to cache the files in a project.") +(defcustom textmate-word-characters "a-zA-Z0-9_" "Word Characters for Column Movement") ;;; Bindings (defun textmate-ido-fix () @@ -268,31 +287,25 @@ Symbols matching the text at point are put first in the completion list." (setq symbol-names (cons symbol (delete symbol symbol-names)))) matching-symbols))))) - (let* ((selected-symbol (ido-completing-read "Symbol? " symbol-names)) + (let* ((selected-symbol (ido-completing-read "Symbol? " (reverse symbol-names))) (position (cdr (assoc selected-symbol name-and-pos)))) (goto-char (if (overlayp position) (overlay-start position) position))))) -;; this is from rails-lib.el in the emacs-rails package -(defun string-join (separator strings) - "Join all STRINGS using SEPARATOR." - (mapconcat 'identity strings separator)) - (defun textmate-goto-file () "Uses your completing read to quickly jump to a file in a project." (interactive) (let ((root (textmate-project-root))) - (when (null root) - (error - (concat - "Can't find a suitable project root (" - (string-join " " *textmate-project-roots* ) - ")"))) - (find-file - (concat + (when (null root) + (error "Can't find any .git directory")) + (find-file + (concat (expand-file-name root) "/" - (textmate-completing-read + (textmate-completing-read "Find file: " - (textmate-cached-project-files root)))))) + (mapcar + (lambda (e) + (replace-regexp-in-string (textmate-project-root) "" e)) + (textmate-cached-project-files (textmate-project-root)))))))) (defun textmate-clear-cache () "Clears the project root and project files cache. Use after adding files." @@ -301,28 +314,9 @@ Symbols matching the text at point are put first in the completion list." (setq *textmate-project-files* nil) (message "textmate-mode cache cleared.")) - -;; FIXME: -;; - use *textmate-gf-exclude* instead of hard-coding ignored paths -;; - format results nicely (like TextMate) -(defun textmate-find-regex-in-project (regex) - "Search the project for a regular expression and quickly jump -to matches in a project. - -This function just finds the project root and calls `RGREP'." - (interactive "sRegex: ") - (let ((root (textmate-project-root))) - (when (null root) - (error - (concat - "Can't find a suitable project root (" - (string-join " " *textmate-project-roots*) - ")"))) - (grep (concat "grep -Hn -E '" regex "' " root "/**/{*.j,*.js,*akefile,*.sh}(N) | grep -vE '/([Bb]uild|Frameworks|.git)/?' | sed -e 's#" root "/##'")))) - ;;; Utilities -(defun textmate-project-files (root) +(defun textmate-find-project-files (root) "Finds all files in a given project." (split-string (shell-command-to-string @@ -334,6 +328,11 @@ This function just finds the project root and calls `RGREP'." *textmate-project-root* "/::'")) "\n" t)) +(defun textmate-project-files (root) + (sort + (textmate-find-project-files root) + '(lambda (a b) (< (length a) (length b))))) + ;; http://snipplr.com/view/18683/stringreplace/ (defun textmate-string-replace (this withthat in) "replace THIS with WITHTHAT' in the string IN" @@ -365,22 +364,22 @@ This function just finds the project root and calls `RGREP'." *textmate-project-root*) (defun root-match(root names) - (member (car names) (directory-files root))) + (member (car names) (directory-files root))) (defun root-matches(root names) - (if (root-match root names) - (root-match root names) - (if (eq (length (cdr names)) 0) - 'nil - (root-matches root (cdr names)) - ))) + (if (root-match root names) + (root-match root names) + (if (eq (length (cdr names)) 0) + 'nil + (root-matches root (cdr names)) + ))) (defun textmate-find-project-root (&optional root) "Determines the current project root by recursively searching for an indicator." (when (null root) (setq root default-directory)) (cond ((root-matches root *textmate-project-roots*) - (expand-file-name root)) + (expand-file-name root)) ((equal (expand-file-name root) "/") nil) (t (textmate-find-project-root (concat (file-name-as-directory root) ".."))))) @@ -400,6 +399,60 @@ A place is considered `tab-width' character columns." (interactive) (textmate-shift-right (* -1 (or arg 1)))) +(defun textmate-go-column (direction arg) + "Move down a column" + (let* ((orig-line (line-number-at-pos)) + (orig-column (current-column)) + (prefix-match-regex (if (<= orig-column 1) "^" (format "^.\\{%d\\}" (- orig-column 1))) ) + (word-regex (concat "[" textmate-word-characters "]")) + (non-word-regex (concat "[^\n" textmate-word-characters "]")) + (matching-regex (concat prefix-match-regex + (cond ((looking-back "^") "") + ((looking-back word-regex) word-regex) + (t non-word-regex)) + (cond ((looking-at "$") "$") + ((looking-at word-regex) word-regex) + (t non-word-regex)))) + (do-search (if (= direction 1) + (lambda () (search-forward-regexp matching-regex nil t)) + (lambda () (search-backward-regexp matching-regex nil t))))) + (forward-char direction) + (funcall do-search) + (backward-char direction) + (move-to-column orig-column) + (if (= (line-number-at-pos) (+ orig-line direction)) ;; did you only move one line? + (progn + (while (= (line-number-at-pos) (+ orig-line direction)) + (setq orig-line (line-number-at-pos)) + (funcall do-search) + (move-to-column orig-column)) + (goto-line orig-line) + (move-to-column orig-column))))) + +(defun textmate-column-up (arg) + "Move up a column, textmate-style" + (interactive "P") + (textmate-go-column -1 arg)) + +(defun textmate-column-down (arg) + "Move down a column, textmate-style" + (interactive "P") + (textmate-go-column 1 arg)) + +(defun textmate-column-up-with-select (arg) + "Move up a column, selecting with shift-select" + (interactive "P") + (unless mark-active (progn (push-mark (point)) + (setq mark-active t transient-mark-mode t))) + (let (deactivate-mark) (textmate-column-up arg))) + +(defun textmate-column-down-with-select (arg) + "Move down a column, selecting with shift-select" + (interactive "P") + (unless mark-active (progn (push-mark (point)) + (setq mark-active t transient-mark-mode t))) + (let (deactivate-mark) (textmate-column-down arg))) + ;;;###autoload (define-minor-mode textmate-mode "TextMate Emulation Minor Mode" :lighter " mate" :global t :keymap *textmate-mode-map* @@ -414,4 +467,4 @@ A place is considered `tab-width' character columns." (funcall (cadr mode) -1))))) (provide 'textmate) -;;; textmate.el ends here \ No newline at end of file +;;; textmate.el ends here