use warnings; use strict; =head1 NAME BarnOwl::Completion =head1 DESCRIPTION Hooks for tab-completion support in BarnOwl. =cut package BarnOwl::Completion; use BarnOwl::Completion::Context; use BarnOwl::Editwin qw(save_excursion text_before_point text_after_point point_move replace_region); use List::Util qw(min first); our %completers = (); sub do_complete { my $cmd = shift; my $before = text_before_point(); my $after = text_after_point(); BarnOwl::debug("Completing: $before-|-$after"); my $ctx = BarnOwl::Completion::Context->new($before, $after); my @words = get_completions($ctx); return unless @words; my $prefix = common_prefix(@words); if($prefix) { insert_completion($ctx, $prefix, scalar @words == 1); } if(scalar @words > 1) { show_completions(@words); } else { BarnOwl::message(''); } } sub insert_completion { my $ctx = shift; my $completion = BarnOwl::quote(shift); my $unique = shift; if($unique) { $completion .= " "; } save_excursion { point_move($ctx->word_start - $ctx->point); BarnOwl::Editwin::set_mark(); point_move($ctx->word_end - $ctx->word_start); replace_region($completion); }; if(!length($ctx->words->[$ctx->word])) { point_move(length($completion)); } } sub show_completions { my @words = @_; my $all = join(" ", map {BarnOwl::quote($_)} @words); my $width = BarnOwl::getnumcols(); if (length($all) > $width-1) { $all = substr($all, 0, $width-4) . "..."; } BarnOwl::message($all); } sub common_prefix { my @words = @_; my $len = min(map {length($_)} @words); my $pfx = ''; for my $i (1..$len) { $pfx = substr($words[0], 0, $i); if(first {substr($_, 0, $i) ne $pfx} @words) { $pfx = substr($pfx, 0, $i-1); last; } } return $pfx; } sub get_completions { my $ctx = shift; my @words = (); if($ctx->word == 0) { return complete_command($ctx->words->[0]); } else { my $cmd = $ctx->words->[0]; my $word = $ctx->words->[$ctx->word]; if(exists($completers{$cmd})) { return grep {$_ =~ m{^\Q$word\E}} $completers{$cmd}->($ctx); } return; } } sub complete_command { my $cmd = shift; return grep {$_ =~ m{^\Q$cmd\E}} @BarnOwl::all_commands; } sub register_completer { my $cmd = shift; my $completer = shift; $completers{$cmd} = $completer; } sub load_completers { opendir(my $dh, BarnOwl::get_data_dir() . "/" . "lib/BarnOwl/Complete/") or return; while(my $name = readdir($dh)) { next if $name =~ m{^\.}; next unless $name =~ m{[.]pm$}; $name =~ s{[.]pm$}{}; eval "use BarnOwl::Complete::$name"; if($@) { BarnOwl::error("Loading completion module $name:\n$@\n"); } } } $BarnOwl::Hooks::startup->add("BarnOwl::Completion::load_completers"); 1;