use 5.008;
use strict;
use warnings;

package Vim::Complete;
our $VERSION = '1.100880';
# ABSTRACT: Generate auto completion information for vim
use PPI;
use File::Find;
use File::Spec;
use parent qw(Class::Accessor::Complex);
__PACKAGE__->mk_new->mk_scalar_accessors(qw(min_length))
  ->mk_array_accessors(qw(dirs))->mk_hash_accessors(qw(result))
  ->mk_boolean_accessors(qw(verbose));

sub gather {
    my ($self, $filename) = @_;

    # does PPI not like relative filenames?
    $filename = File::Spec->rel2abs($filename);
    my $document = PPI::Document->new($filename);
    unless (UNIVERSAL::isa($document, 'PPI::Document')) {
        warn "couldn't parse $filename\n";
        return;
    }

    # get a hash reference so we can change the hash in place
    my $result = $self->result;
    my $min_length = $self->min_length || 3;
    $result->{$_} = 1
      for grep { /\w{$min_length,}/ }
      map      { $_->namespace }
      @{ $document->find('PPI::Statement::Package') || [] };
    $result->{$_} = 1
      for grep { /\w{$min_length,}/ }
      map { substr($_, 0, 1, ''); $_ }
      map { $_->variables }
      @{ $document->find('PPI::Statement::Variable') || [] };
    $result->{$_} = 1
      for grep { /\w{$min_length,}/ }
      map { $_->name } @{ $document->find('PPI::Statement::Sub') || [] };
}

sub report {
    my $self   = shift;
    my @result = sort $self->result_keys;
    wantarray ? @result : \@result;
}

sub report_to_file {
    my ($self, $filename) = @_;
    die "no filename\n" unless defined $filename;
    open my $fh, '>', $filename or die "can't open $filename for writing: $!\n";
    print $fh "$_\n" for $self->report;
    close $fh or die "can't close $fh: $!\n";
}

sub parse {
    my $self    = shift;
    my $verbose = $self->verbose;
    find(
        sub {
            return unless -f && /\.pm$/;

          # We can see a file more than once if we have nested paths in @INC, so
          # check
            our %seen;
            return if $seen{$File::Find::name}++;
            warn "processing $File::Find::name\n" if $verbose;
            $self->gather($_);
        },
        $self->dirs
    );
    $self;    # for chaining
}
1;


__END__
=pod

=for stopwords dirs

=head1 NAME

Vim::Complete - Generate auto completion information for vim

=head1 VERSION

version 1.100880

=head1 SYNOPSIS

    my (@dirs, $verbose, $min_length);
    my $filename = '...';
    Vim::Complete->new(
        dirs       => \@dirs,
        verbose    => $verbose,
        min_length => $min_length,
    )->parse->report_to_file($filename);

=head1 DESCRIPTION

Vim has a good auto completion mechanism. In insert mode, you can type
Control-n to complete on the current string; you can cycle through the
possible completions by repeatedly typing Control-n. See C<:help complete> in
vim for more information.

By default, vim completes on identifiers it finds in the current buffer,
buffers in other windows, other loaded buffers, unloaded buffers, tags and
included files. That means you still have to type the identifier once so
vim knows about it.

However, you can extend the way vim completes. It can take additional
identifiers from a file. So Vim::Complete takes a list of directories -
usually C<@INC> -, looks at the modules contained therein, parses package
names, variable names and subroutine names and writes them to a file.

Now you need to tell vim where to find the file with the Perl identifiers. Put
this line into your C<.vimrc>:

    set complete+=k~/.vimcomplete

The C<+=k> tells vim to also look into the specified file.

For this to work well, you need to tell vim that colons are part of
identifiers in Perl (for example, C<Foo::Bar> is an identifier. Put this line
in your C<.vimrc>:

    set iskeyword+=:

Included in this distribution is the program C<mk_vim_complete>, which is a
command-line frontend to Vim::Complete.

You can tell Vim::Complete to only use identifiers that are of a certain
minimum length. An identifier that is only one character long (such as C<$x>)
doesn't need to be completed. If you would include two-character identifiers,
you might throw off the auto completion by having to cycle through too many
identifiers. So the default minimum length is 3.

=head1 METHODS

=head2 parse

Assumes that C<dir()>, and optionally C<verbose()> and C<min_length()>, have
been set and starts to look in the directories for files ending in C<.pm>. For
each file it gathers information using C<gather()>.

Returns the Vim::Complete object so method calls can be chained as seen in the
L</SYNOPSIS>.

=head2 report

Takes all the gathered findings and returns the list of identifiers. Returns
an array in list context, or a reference to the array in scalar context.

=head2 report_to_file

Takes as argument a filename. Writes the report generated by C<report()> to
the file.

=head2 gather

Takes a filename of a module, parses the source code and makes a note of the
package names, subroutine names and variable names it sees.

This method is called by C<parse()>; it is unlikely that you want to call it
yourself.

=head2 min_length

The minimum length a package name, variable name or subroutine has to have for
a tag to be made. Defaults to 3.

A basic getter/setter method. If called without an argument, it returns the
value. If called with a single argument, it sets the value.

Examples:

  my $value = $obj->min_length;

  $obj->min_length($value);

There are also the following helper methods for this accessor:

=over 4

=item C<clear_min_length>

=item C<min_length_clear>

Clears the value.

Example:

  $obj->clear_min_length;

=back

=head2 dirs

A list of directories to be searched.

Get or set the array values. If called without arguments, it returns the
array in list context, or a reference to the array in scalar context. If
called with arguments, it expands array references found therein and sets the
values.

Examples:

  my @values    = $obj->dirs;

  my $array_ref = $obj->dirs;

  $obj->dirs(@values);

  $obj->dirs($array_ref);

There are also the following helper methods for this accessor:

=over 4

=item C<push_dirs>

=item C<dirs_push>

Pushes elements onto the end of the array.

Example:

  $obj->push_dirs(@values);

=item C<pop_dirs>

=item C<dirs_pop>

Pops the last element off the array, returning it.

Example:

  my $value = $obj->pop_dirs;

=item C<unshift_dirs>

=item C<dirs_unshift>

Unshifts elements onto the beginning of the array.

Example:

  $obj->unshift_dirs(@values);

=item C<shift_dirs>

=item C<dirs_shift>

Shifts the first element off the array, returning it.

Example:

  my $value = $obj->shift_dirs;

=item C<clear_dirs>

=item C<dirs_clear>

Deletes all elements from the array.

Example:

  $obj->clear_dirs;

=item C<count_dirs>

=item C<dirs_count>

Returns the number of elements in the array.

Example:

  my $count = $obj->count_dirs;

=item C<splice_dirs>

=item C<dirs_splice>

Takes three arguments: An offset, a length and a list.

Removes the elements designated by the offset and the length from the array,
and replaces them with the elements of the list, if any. In list context,
returns the elements removed from the array. In scalar context, returns the
last element removed, or C<undef> if no elements are removed. The array grows
or shrinks as necessary. If the offset is negative then it starts that far
from the end of the array. If the length is omitted, removes everything from
the offset onward. If the length is negative, removes the elements from the
offset onward except for -length elements at the end of the array. If both the
offset and the length are omitted, removes everything. If the offset is past
the end of the array, it issues a warning, and splices at the end of the
array.

Examples:

  $obj->splice_dirs(2, 1, $x, $y);

  $obj->splice_dirs(-1);

  $obj->splice_dirs(0, -1);

=item C<index_dirs>

=item C<dirs_index>

Takes a list of indices and returns the elements indicated by those indices.
If only one index is given, the corresponding array element is returned. If
several indices are given, the result is returned as an array in list context
or as an array reference in scalar context.

Examples:

  my $element   = $obj->index_dirs(3);

  my @elements  = $obj->index_dirs(@indices);

  my $array_ref = $obj->index_dirs(@indices);

=item C<set_dirs>

=item C<dirs_set>

Takes a list of index/value pairs and for each pair it sets the array element
at the indicated index to the indicated value. Returns the number of elements
that have been set.

Example:

  $obj->set_dirs(1 => $x, 5 => $y);

=back

=head2 verbose

A flag that indicates whether verbose diagnostics should be sent to STDERR.

If called without an argument, returns the boolean value (0 or 1). If called
with an argument, it normalizes it to the boolean value. That is, the values
0, undef and the empty string become 0; everything else becomes 1.

Examples:

  $obj->verbose($value);

  my $value = $obj->verbose;

There are also the following helper methods for this accessor:

=over 4

=item C<set_verbose>

=item C<verbose_set>

Sets the boolean value to 1.

Example:

  $obj->set_verbose;

=item C<clear_verbose>

=item C<verbose_clear>

Clears the boolean value by setting it to 0.

Example:

  $obj->clear_verbose;

=back

=head1 INSTALLATION

See perlmodinstall for information and options on installing Perl modules.

=head1 BUGS AND LIMITATIONS

No bugs have been reported.

Please report any bugs or feature requests through the web interface at
L<http://rt.cpan.org/Public/Dist/Display.html?Name=Vim-Complete>.

=head1 AVAILABILITY

The latest version of this module is available from the Comprehensive Perl
Archive Network (CPAN). Visit L<http://www.perl.com/CPAN/> to find a CPAN
site near you, or see
L<http://search.cpan.org/dist/Vim-Complete/>.

The development version lives at
L<http://github.com/hanekomu/Vim-Complete/>.
Instead of sending patches, please fork this project using the standard git
and github infrastructure.

=head1 AUTHOR

  Marcel Gruenauer <marcel@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2007 by Marcel Gruenauer.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut

