use strict; use warnings;
$INC{'Encode/ConfigLocal.pm'}=1;
require Encode;
use utf8;

# to read the following docs, you can use "perldoc nicekey.pl"

=head1 NAME

nicekey - map control sequences back to readable key names

=cut

use constant SCRIPT_NAME => 'nicekey';
weechat::register(SCRIPT_NAME, 'Nei <anti.teamidiot.de>', '0.0', 'GPL3', 'nice keys', '', '') || return;
sub SCRIPT_FILE() {
	my $infolistptr = weechat::infolist_get('perl_script', '', SCRIPT_NAME);
	my $filename = weechat::infolist_string($infolistptr, 'filename') if weechat::infolist_next($infolistptr);
	weechat::infolist_free($infolistptr);
	return $filename unless @_;
	my $sep = weechat::info_get('dir_separator', '');
	my @path = split $sep, $filename;
	my $link = readlink $filename;
	my @lpath = split $sep, $link if defined $link;
	(join '/', @path[0..$#path-1]),
	($path[-2] eq 'autoload' ? join '/', @path[0..$#path-2] : ()),
	(@lpath ? join '/', ($lpath[0] ne '' ? @path[0..$#path-1] : ''), @lpath[0..$#lpath-1] : ()),
	(join '/', (split $sep, weechat::info_get('weechat_dir', '')), 'perl')
}
require lib; lib->import(&SCRIPT_FILE(1));
require Nlib;

weechat::hook_command(SCRIPT_NAME, 'list keys in a nicely fashion',
					  (join ' || ', '[-sortcmd] [<context>]',
					   '   label <keysequence> [...]',
					   ' whereis <command>',
					   ' showseq <nice key codes>',
					   'showseq2 <keysequence> [...]',
					   'list|listdefault|listdiff [<context>]'),
					  (join "\n",
					   'without arguments, the list of keys is displayed',
					   "(to bind and unbind keys, you must still use @{[weechat::color('bold')]}/key@{[weechat::color('-bold')]} command)",
					   '',
					   '-sortcmd: sort the list by command names',
					   '   label: shows the label of a given key sequence.',
					   '          use alt+k and press the key you want to see its sequence',
					   ' whereis: give a command and it will show you all key bindings that run this command.',
					   '           * is permitted to search for partial commands',
					   ' showseq: shows the weechat key sequence(s) needed to bind labeled keys',
					   'showseq2: combines label and showseq command',
					   "   list*: runs the @{[weechat::color('bold')]}/key@{[weechat::color('-bold')]} output through /@{[SCRIPT_NAME]}. see @{[weechat::color('bold')]}/help key@{[weechat::color('-bold')]} for more info",
					   '',
					   'sometimes, tags are given which show the terminal key encoding standard where this key sequence is originating. '.
						   'currently, the following tags are used: '.(
							   join ', ',
							   map { "@{[weechat::color('chat_delimiters')]}${$_}[0]:@{[weechat::color('default')]} ${$_}[1]"
								 } [ std => 'definition is derived from vt102/220/410 standard' ],
							   [ meta => 'actually std, but differentiating between Alt and (real) Meta key' ],
							   [ rxvt => 'definition is originating in rxvt terminal emulator family' ],
							   [ screen => 'definition is used by gnu screen' ],
							   [ tmux => 'definition is used by *bsd tmux' ],
							   [ unknown => 'definition is not known to this script. if you know more, please contact the script author. thanks!']),
					   '',
					   'Examples:',
					   "  /@{[SCRIPT_NAME]} label <press alt+k now> <press PgUp key now> <press enter>",
					   '    should output:  PgUp',
					   "  /@{[SCRIPT_NAME]} whereis * delete_next_char",
					   '    might output:   C-d',
					   '                 &  Del',
					   "  /@{[SCRIPT_NAME]} showseq C-c b",
					   "    might output:   ctrl-Cb     @{[weechat::color('chat_delimiters')]} => @{[weechat::color('default')]}/input insert \\x02",
					   "  /@{[SCRIPT_NAME]} showseq2 <press alt+k now> <press alt+↑ key now> <press enter>",
					   "    might output:   meta-meta2-A@{[weechat::color('chat_delimiters')]} => @{[weechat::color('default')]}/buffer -1",
					   "                 &  meta2-1;3A  @{[weechat::color('chat_delimiters')]} => @{[weechat::color('default')]}/buffer -1",
#					   "use @{[weechat::color('bold')]}/@{[SCRIPT_NAME]} help@{[weechat::color('-bold')]} to read the manual"
					  ),
					  (join ' || ', '-sortcmd whereis|default|search|cursor|mouse %-',
					   'label %-',
					   'whereis|default|search|cursor|mouse %-',
					   'showseq %-',
					   'showseq2 %-',
					   (map { "list$_ default|search|cursor|mouse %-"}
					   '', 'diff', 'default'),
					 ), 'nicekey_cmd', '');

my %int_repl = ('meta-' => "\1[", 'meta2-' => "\1[[", 'ctrl-' => "\1");
my $int_repl = join '|', map { quotemeta } sort { length $b <=> length $a } keys %int_repl;
my %int_repl_rev = reverse %int_repl;
my $int_repl_rev = join '|', map { quotemeta } sort { length $b <=> length $a } keys %int_repl_rev;
my $area_re = qr/\@(?:\w|[](.>*)[])*:/;

my %reverse_some_keys;

## rename_key -- translate key to labeled appearance
## $keybinding - hashref with key and key_internal (from key infolist)
## returns label of keybinding and arrayref with annotations
{
	my %arrow = ('A' => '↑', 'B' => '↓', 'C' => '→', 'D' => '←', 'E' => '·');
	my %no_arrow = ('Up' => '↑', 'Down' => '↓', 'Right' => '→', 'Left' => '←', 'Begin' => '·');
	my %no_arrow_disp = reverse %no_arrow;
	$no_arrow{KP5} = $no_arrow{Center} = $no_arrow{Begin};
	my %inspg = (2 => 'Ins', 3 => 'Del', 5 => 'PgUp', 6 => 'PgDn',
				 1 => 'Find', 4 => 'Select',
				 28 => 'Help', 29 => 'Copy', 32 => 'Paste', 34 => 'Cut',
				 (map { 11+$_ => "F$_" } 6..10),
				 (map { 12+$_ => "F$_" } 11..12),
				);
	my %fkeys = ('P' => 'F1', 'Q' => 'F2', 'R' => 'F3', 'S' => 'F4');
	my %homend = ('H' => 'Home', 'F' => 'End');
	my %homend1 = (7 => $homend{H}, 8 => $homend{F},
				   (map { 10+$_ => "F$_" } 1..5),
				  );
	my %homend2 = (1 => $homend{H}, 4 => $homend{F});
	# C-M-S-
	my %csi_mod_map = (2 => 'S-', 3 => 'M-', 4 => 'M-S-', 5 => 'C-', 6 => 'C-S-', 7 => 'C-M-', 8 => 'C-M-S-');
	my %csi_mod_meta = (         9 => 'M-', 10 => 'M-S-',                       13 => 'C-M-', 14 => 'C-M-S-');
	# 11 => 'M-A-', 12 => 'M-A-S-', 15 => 'C-M-A-', 16 => 'C-M-A-S-'
	my %rxvt_mod_map = ('^' => 'C-', '$' #'
							=> 'S-', '@' => 'C-S-');
	## &$apply_tpl -- apply template parameters according to variable hash
	## $r - hashref with key template in key 't'. %X are replaced by $r->{X}
	## returns pair of keyseq and label/tag
	my $apply_tpl = sub {
		my $r = shift;
		my @r = ($r->{t}[0], ref $r->{t}[1] ? [ @{$r->{t}[1]} ] : [ $r->{t}[1] ]);
		push @{$r[1]}, 'meta' if exists $r->{E} && $r->{E}[0] =~ /^\d+$/ && $r->{E}[0] >= 9;
		$r[0] =~ s/%(\w)/${$r}{$1}[0]/g;
		$r[0] =~ s/\\l(.)/\l$1/g;
		for ($r[1][0]) {
			s/\%(\w)/${$r}{$1}[1]/g;
			s/M-C-/C-M-/g;
		}
		$r[1] = $r[1][0] if @{$r[1]} == 1;
		@r
	};

	## &$map_key_add -- adds keys and its values to template replacement hash
	## $rr - template hashref
	## $f - variable name (1 char)
	## $t - hashref with keys/values to iterate through
	## $subr - subroutine ref that is called for each key/value
	## returns list of $subr results applied to all entries in $t
	my $map_key_add = sub {
		my ($rr, $f, $t, $subr) = @_;
		map {
			$rr->{$f} = [ $_ => $t->{$_} ];
			$subr->($rr)
		} keys %$t
	};

	## &$map_hashlist -- call &$map_key_add for all hashrefs in list
	## $rr - template hashref
	## $f - variable name (1 char)
	## @list - list of hashrefs with keys/values to iterate through
	## $subr - subroutine ref that is called for each key/value
	## returns list of $subr results applied to all entries in all hashs of @list
	my $map_hashlist = sub {
		my ($rr, $f, @list) = @_;
		my $subr = pop @list;
		map {
			$map_key_add->($rr, $f, $_, $subr)
		} @list
	};

	## &$apply_tpl_fun -- functor to run &$apply_tpl
	## $_[0] - hashref with template and parameters
	## see &$apply_tpl
	my $apply_tpl_fun = sub {
		$apply_tpl->(+shift)
	};

	## &$variable_e -- calls &$map_hashlist for keys and a list of hashes that are used for the %E template variable
	## $e - arrayref of hashrefs which provide values for %E
	## $_[1] - key template
	## $_[2..$#@] - list of hashrefs which provide key seqs and definitions (%K)
	## returns list of template expansion applied to all %E and %K values
	my $variable_e = sub {
		my $e = shift;
		$map_hashlist->(
			+{ t => +shift },
			K => @_,
			sub {
				$map_hashlist->(
					+shift,
					E => @$e,
					$apply_tpl_fun
				   )
			}
		   )
	};

	my %H = (
		## &$H{def_end} -- apply %K template like meta2-%K => %K
		## $_[0] - key template
		## $_[1..$#@] - list of hashrefs providing %K
		## returns list of template expansion applied to all %K values
		def_end => sub {
			$map_hashlist->(
				+{ t => +shift },
				K => @_,
				$apply_tpl_fun
			   )
		},
		## &$H{csi_end} -- apply %E%K template, %E iterates over csi codes
		## $_[0] - key template
		## $_[1..$#@] - list of hashrefs providing %K
		## returns list of template expansion applied to all %E and %K values
		csi_end => sub {
			$variable_e->(
				[\(%csi_mod_map, %csi_mod_meta)],
				@_
			  )
		},
		## &$H{rxvt_end} -- apply %E%K template, %E iterates over rxvt modifier codes (note: there is no modifier for meta)
		## $_[0] - key template
		## $_[1..$#@] - list of hashrefs providing %K
		## returns list of template expansion applied to all %E and %K values
		rxvt_end => sub {
			$variable_e->(
				[\%rxvt_mod_map],
				@_
			  )
		},
		## &$H{and_meta_pfx} -- add meta-/M- to the list of key mappings
		## @_ - list of sequence => name/tag pairs to which to add a meta variant
		## returns the original hash plus a copy of it with meta- added in front
		and_meta_pfx => sub {
			my %ht = @_;
			map {
				($_ => $ht{$_},
				 "meta-$_" => do {
					 my @r = ref $ht{$_} ? @{$ht{$_}} : $ht{$_};
					 $r[0] = "M-$r[0]";
					 $r[0] =~ s/M-C-/C-M-/g;
					 @r == 1 ? $r[0] : \@r })
			} keys %ht
		},
	   );
	my %some_keys = (
		'meta2-P' => 'Pause',

		'ctrl-@' => 'C-SPC',

		'ctrl-I'        =>   'Tab',
		'meta2-Z'       => 'S-Tab',
		'meta2-27;5;9~' => 'C-Tab',

		$H{and_meta_pfx}(
		'ctrl-M'         =>   'RET',
	   ),
		'meta2-27;5;13~' => 'C-RET',
		'meta2-27;2;13~' => 'S-RET',

		$H{and_meta_pfx}(
		'ctrl-?'      =>     'BS',
		'ctrl-H'      =>   'C-BS',
	   ),

		$H{def_end}([ 'meta2-%K'      =>   '%K'         ], \%arrow, \%homend),
		$H{csi_end}([ 'meta2-1;%E%K'  => '%E%K'         ], \%arrow, \%fkeys, \%homend),

		$H{def_end}([ 'meta-O%K'      =>['C-%K','tmux'] ], \%arrow),
		$H{def_end}([ 'meta-O\l%K'    =>['C-%K','rxvt'] ], \%arrow),
		$H{def_end}([ 'meta2-\l%K'    =>['S-%K','rxvt'] ], \%arrow),
		$H{def_end}([ 'meta-meta2-%K' =>['M-%K','rxvt'] ], \%arrow),

		$H{def_end}([ 'meta-O%K'      =>   '%K'         ], \%fkeys),
		$H{def_end}([ 'meta-meta-O%K' =>['M-%K','tmux'] ], \%fkeys),

		$H{def_end}([ 'meta2-%K~'     =>   '%K'         ], \%inspg),
		$H{csi_end}([ 'meta2-%K;%E~'  => '%E%K'         ], \%inspg),

		$H{and_meta_pfx}(
		$H{rxvt_end}(['meta2-%K%E'    =>['%E%K','rxvt'] ], \%inspg),
	   ),
		$H{def_end}([ 'meta-meta2-%K~'=>['M-%K','rxvt'] ], \%inspg),

		$H{and_meta_pfx}(
		$H{def_end}([ 'meta2-%K~'     =>[  '%K','rxvt'] ], \%homend1),
		$H{rxvt_end}(['meta2-%K%E'    =>['%E%K','rxvt'] ], \%homend1),
	   ),

		$H{def_end}([ 'meta-meta2-%K~'=>['M-%K','screen']],\%homend2),

		'meta2-200~' => 'PasteBegin',
		'meta2-201~' => 'PasteEnd',
	   );
	my %int_some_keys = ("\1[" => 'M-', "\1" => 'C-', "\1[[" => 'meta2-');
	my $no_arrow_disp = '[' . (join '', keys %no_arrow_disp) . ']';
	for (sort { ref $some_keys{$a} && ref $some_keys{$b}
					? $some_keys{$a}[0] cmp $some_keys{$b}[0]
						: ref $some_keys{$b} ? -1
							: ref $some_keys{$a} ? 1
								: $some_keys{$a} cmp $some_keys{$b} } keys %some_keys) {
		push @{$reverse_some_keys{ ref $some_keys{$_} ? $some_keys{$_}[0] : $some_keys{$_} }},
			ref $some_keys{$_} ? [ $_, $some_keys{$_}[1] ] : $_;
		my $o = $_;
		s/($int_repl)/$int_repl{$1}/g;
		$int_some_keys{$_} = $some_keys{$o};
	}
	for (keys %no_arrow) {
		for my $pfx ('', 'C-', 'S-', 'M-', 'C-M-', 'C-S-', 'C-M-S-', 'M-S-') {
			push @{$reverse_some_keys{"$pfx$_"}}, @{$reverse_some_keys{"$pfx$no_arrow{$_}"}}
				if exists $reverse_some_keys{"$pfx$no_arrow{$_}"};
		}
	}
	my $keys_internal = join '|', map { quotemeta } sort { length $b <=> length $a } keys %int_some_keys;

	sub rename_key {
		my $keybinding = shift;
		return wantarray ? ($keybinding->{key}, []) : $keybinding->{key}
			if $keybinding->{key} eq $keybinding->{key_internal};
		$keybinding = $keybinding->{key_internal};
		my $area = $1 if $keybinding =~ s/^($area_re)//;
		my @ann;
		$keybinding =~ s{($keys_internal|\w|\p{Print})}{
			(exists $int_some_keys{$1}
				 ? ref $int_some_keys{$1}
					 ? ((push @ann, $int_some_keys{$1}[1]),
						$int_some_keys{$1}[0])
						 : $int_some_keys{$1}
							 : $1).' '}ge;
		$keybinding =~ s/C- \K(\w)/\L$1/g;
		$keybinding =~ s/M- O (?=\p{Print})/meta-O /g;
		$keybinding =~ s/M-C-/C-M-/g;
		$keybinding =~ s/\w-\K //g;
		$keybinding =~ s/($no_arrow_disp)/$no_arrow_disp{$1}/g
			if weechat::config_string_to_boolean(weechat::config_get_plugin('no_symbols'));
		$keybinding = "$area$keybinding" if defined $area;
		unshift @ann, '|unknown|' if $keybinding =~ /meta(?:2-|-O)/;
		wantarray ? ($keybinding, \@ann) : $keybinding
	}
}

## looks_like_mouse -- check if key sequence looks like mouse key
## $_[0] - key sequence
sub looks_like_mouse {
	$_[0] =~ /^\@.*:/ && $_[0] =~ /[-:](?:wheel(?:up|down)$|button(?:\d|[*]))/;
}

## filter_key_printout -- filter /key output using modifier hook
## () - modifier handler
## $_[2] - plugin;buffer_name;tags
## $_[3] - printed string
sub filter_key_printout {
	Encode::_utf8_on($_[3]);
	return $_[3] unless $_[2] eq 'core;weechat;';
	return $_[3] unless $_[3] =~ s/^  (\p{Print}+)(.*?) => //;
	my $color = $2;
	my %s = ( key_internal => $1, key => $1 );
	$s{key_internal} =~ s/($int_repl)/$int_repl{$1}/g
		unless looks_like_mouse($s{key});
	my ($name, $ann) = rename_key(\%s);
	$ann = $ann->[0] // 'std';
	"  $name$color => $_[3]$color \[$ann]"
}

## int_rename_key -- rename key to internal representation
## $key - key sequence
sub int_rename_key {
	my $key = $_[0];
	$key =~ s/($int_repl)/$int_repl{$1}/g
		unless looks_like_mouse($key);
	rename_key(+{ key_internal => $key, key => $_[0] })
}

## nicekey_label -- show the label of a key given the internal key sequence
## () - forwarded command handler
## $_[1] - buffer
## @args - already split arguments
sub nicekey_label {
	my (undef, undef, @args) = @_;
	my ($name, $ann) = int_rename_key($args[1]);
	$ann = $ann->[0] // 'std';
	weechat::print($_[1], "@{[weechat::color('chat_delimiters')]}\[key]\t$name@{[weechat::color('chat_delimiters')]}:: looks like @{[weechat::color('default')]}$args[1]@{[weechat::color('chat_delimiters')]} \[$ann]");
	return weechat::WEECHAT_RC_OK
}

## nicekey_showseq -- show the internal sequence needed for a labeled key
## () - forwarded command handler
## $_[1] - buffer
## @args - already split arguments
sub nicekey_showseq {
	my (undef, undef, @args) = @_;
	if (@args < 2) {
		weechat::print($_[1], "\tno key labels given");
		return weechat::WEECHAT_RC_OK
	}
	if (lc $args[0] eq 'showseq2') {
		@args = ($args[0], split ' ', scalar int_rename_key($args[1]));
	}
	my @seqs = ['', []];
	my $seq = "@args[1..$#args]";

	# build list of internal key sequences by looping through %reverse_some_keys
	for my $label (@args[1..$#args]) {
		if (exists $reverse_some_keys{$label}) {
			my @new_seqs;
			for my $seq (@{$reverse_some_keys{$label}}) {
				my $s = ref $seq ? $seq->[0] : $seq;
				for (@seqs) {
					my $a = ref $seq ? $seq->[1] : 'std';
					push @new_seqs, ["${$_}[0]$s", [ $a ]]
						if !@{$_->[1]} || $_->[1][0] eq $a;
				}
			}
			@seqs = @new_seqs;
		}
		elsif (($label !~ /^C-[[:upper:]]$/ && $label =~ s/^C-(\p{Print})$/ctrl-\U$1/)
				   || ($label =~ s/^M-(\p{Print})$/meta-$1/)
					   || ($label !~ /^C-M-[[:upper:]]$/ && $label =~ s/^C-M-(\p{Print})$/meta-ctrl-\U$1/)
						   || ($label =~ /^\p{Print}$/)) {
			$_->[0] .= $label for @seqs;				
		}
		else {
			weechat::print($_[1], "\tunknown keycode: $label");
			return weechat::WEECHAT_RC_OK
		}
	}
	weechat::print($_[1], weechat::color('bold').SCRIPT_NAME);
	weechat::print($_[1], "\tkey sequences for $seq:");
	my (%table, %sh_table);

	# first, gather all sequences into %table and %sh_table
	for my $s (@seqs) {
		$s->[0] =~ s/($int_repl)/$int_repl{$1}/g;
		my $ss = quotemeta $s->[0];
		$s->[0] =~ s/($int_repl_rev)/$int_repl_rev{$1}/g;

		for my $ctx (qw(default search cursor)) {
			my @keys = grep { $_->{key_internal} =~ /^(?:$area_re)?$ss/ }
				map {
					my $k = $_;
					Encode::_utf8_on($k->{$_}) for qw(key_internal key command);
					$k }
					Nlib::i2h('key', '', $ctx);
			if (my ($l) = grep { $_->{key_internal} =~ /^(?:$area_re)?$ss$/ }
					@keys) {
				push @{$table{$ctx}}, [ $l->{key}, "@{$s->[1]}", $l->{command} ];
			}
			elsif ($ctx eq 'default') {
				push @{$table{$ctx}}, [ $s->[0], "@{$s->[1]}", '(not bound)' ];
			}
			for my $l (@keys) {
				next if $l->{key_internal} =~ /^(?:$area_re)?$ss$/;
				push @{$sh_table{$ctx}}, [ $l->{key}, rename_key($l), $l->{command} ];
			}
		}
	}

	# print all tables
	for my $ctx (qw(default search cursor)) {
		my $cmd_len = length((sort { length $b->[-1] <=> length $a->[-1] } @{$table{$ctx}//[]}, @{$sh_table{$ctx}//[]}, [''])[0][-1]);
		my $seq_len = ((sort { $b <=> $a } 
							map { $_->[0] =~ /^(?:$area_re)?(.*)/; length $1 }
								@{$table{$ctx}//[]}, @{$sh_table{$ctx}//[]})[0]//0);

		if ($ctx ne 'default' && (@{$table{$ctx}//[]} || @{$sh_table{$ctx}//[]})) {
			weechat::print($_[1], "\t\t");
			weechat::print($_[1], "\tkey sequences for $seq in context \"$ctx\":");
		}

		my $last_area;
		# print perfect matches
		for my $key (@{$table{$ctx}}) {
			my $area = $1 if $key->[0] =~ s/^($area_re)//;
			if (($area//'') ne ($last_area//'')) {
				weechat::print($_[1], "\t $area");
			}
			my $ann = length $key->[1] ? "@{[weechat::color('chat_delimiters')]}\[${$key}[1]]" : '';
			my $p = sprintf "\t  %*s@{[weechat::color('chat_delimiters')]} => @{[weechat::color('default')]}%*s %s", -$seq_len, $key->[0], -$cmd_len, $key->[-1], $ann;
			$p =~ s/\s*$//;
			weechat::print($_[1], $p);
			$last_area = $area;
		}

		# print keys starting with ...
		if (@{$sh_table{$ctx}//[]}) {
			$last_area = undef;
			weechat::print($_[1], "\t key bindings which are shadowed (start with $seq):");
		}

		my $key_len = ((sort { $b <=> $a } 
							map { $_->[1] =~ /^(?:$area_re)?(.*)/; length $1 }
								@{$sh_table{$ctx}//[]})[0]//0);

		for my $skey (@{$sh_table{$ctx}//[]}) {
			my $area = $1 if $skey->[0] =~ s/^($area_re)//;
			if (($area//'') ne ($last_area//'')) {
				weechat::print($_[1], "\t $area");
			}
			my $ann = @{$skey->[2]} ? "@{[weechat::color('chat_delimiters')]}\[@{$skey->[2]}]" : '';
			my $p = sprintf "\t  %*s ( %*s)@{[weechat::color('chat_delimiters')]} => @{[weechat::color('default')]}%*s@{[weechat::color('chat_delimiters')]} %s", -$seq_len, $skey->[0], -$key_len, $skey->[1], -$cmd_len, $skey->[-1], $ann;
			$p =~ s/\s*$//;
			weechat::print($_[1], $p);
			$last_area = $area;
		}
	}

	return weechat::WEECHAT_RC_OK
}

## nicekey_filterlist -- filter /key list through nicekey
## () - forwarded command handler
## $_[1] - buffer
## @args - already split arguments
sub nicekey_filterlist {
	my (undef, undef, @args) = @_;
	Nlib::hook_dynamic(modifier => weechat_print => 'filter_key_printout', '');
	weechat::print('', weechat::color('bold').SCRIPT_NAME);
	weechat::command('', "/key @args");
	Nlib::unhook_dynamic(weechat_print => 'filter_key_printout');
	return weechat::WEECHAT_RC_OK
}

## nicekey_cmd -- show keys in a nice way
## () - command handler
## $_[1] - buffer
## $_[2] - arguments
sub nicekey_cmd {
	Encode::_utf8_on($_[2]);
	my @args = split ' ', $_[2];
	my $sortcmd;
	if (@args && lc $args[0] eq '-sortcmd') {
		$sortcmd = 1;
		shift @args;
	}
	my $cmd_grep = '';
	my $only_ctx;
	if (@args && lc $args[0] eq 'label') {
		return nicekey_label(@_[0..1], @args)
	}
	elsif (@args && lc $args[0] ~~ [qw(showseq showseq2)]) {
		return nicekey_showseq(@_[0..1], @args)
	}
	elsif (@args && lc $args[0] ~~ [qw(list listdefault listdiff)]) {
		return nicekey_filterlist(@_[0..1], @args)
	}
	elsif (@args && lc $args[0] eq 'whereis') {
		$cmd_grep = join '\s+', map { join '', map { $_ eq '*' ? '.*' : quotemeta } split /([*])/, $_ } @args[1..$#args];
	}
	elsif (@args && lc $args[0] ~~ [qw(default cursor search mouse)]) {
		$only_ctx = lc $args[0];
	}
	elsif (@args) {
		weechat::print('', Nlib::fu8on(weechat::prefix('error'))."Error: unknown option for \"@{[SCRIPT_NAME]}\" command: $args[0]");
		return weechat::WEECHAT_RC_OK
	}
	weechat::print('', weechat::color('bold').SCRIPT_NAME);
	my $empty = 1;
	my @listctx = $only_ctx ? $only_ctx : (qw(default search cursor mouse));
	for my $ctx (@listctx) {
		my (@keys, @cmds);
		my (%keys_seen, %cmd_seen);
		my %cmd_to_key;
		for (grep { length $cmd_grep ? $_->{command} =~ /^\s*$cmd_grep\s*$/i : 1 }
				 map {
					 my $k = $_;
					 Encode::_utf8_on($k->{$_}) for qw(key_internal key command);
					 $k }
					 Nlib::i2h('key', '', $ctx)) {
			my ($name, $ann) = rename_key($_);
			push @keys, $name unless $keys_seen{$name}++;
			push @cmds, $_->{command} unless $cmd_seen{$_->{command}}++;
			$ann = $ann->[0] // 'std';
			push @{$cmd_to_key{$_->{command}}{$name}}, $ann;
		}
		if (@keys) {
			weechat::print('', "\t\t") unless $empty;
			$empty = undef;
			weechat::print('', "\t@{[scalar @keys]} key bindings for context \"$ctx\":");
		}
		my $key_len = ((sort { $b <=> $a } 
							map { /^(?:$area_re)?(.*)/; length $1 } @keys)[0]//0);
		my $cmd_len = length((sort { length $b <=> length $a } @cmds)[0]//'');
		@cmds = map { $_->[0] }
			sort { $a->[1] cmp $b->[1] }
				map { my $n = $_;
					  $n =~ s/(\d+)/sprintf "%20d", $1/ge;
					  [ $_, $n ] } @cmds
						  if $sortcmd;
		my $last_area;
		for my $cmd (@cmds) {
			for my $name (grep { $_ ~~ [keys %{$cmd_to_key{$cmd}}] } @keys) {
				my $ann = @{$cmd_to_key{$cmd}{$name}} > 1 || $cmd_to_key{$cmd}{$name}[0] ne 'std' ? "[@{$cmd_to_key{$cmd}{$name}}]" : '';
				my $area = $1 if $name =~ s/^($area_re)//;
				if (($area//'') ne ($last_area//'')) {
					weechat::print('', "\t $area");
				}
				$ann = "@{[weechat::color('chat_delimiters')]}$ann" if length $ann;
				my $p = sprintf "\t  %*s@{[weechat::color('chat_delimiters')]} => @{[weechat::color('default')]}%*s %s", -$key_len, $name, -$cmd_len, $cmd, $ann;
				$p =~ s/\s*$//;
				weechat::print('', $p);
				$last_area = $area;
				delete $cmd_to_key{$cmd}{$name};
			}
			delete $cmd_to_key{$cmd};
		}
	}
	if ($empty) {
		weechat::print('', "\tno keys found.");
	}
	weechat::WEECHAT_RC_OK
}
