if (word(2 $loadinfo()) != [pf]) { load -pf $word(1 $loadinfo()); return; }; # tabkey.jm: a full-featured tab key script for epic4 # # written by nsx # # this script features: # # * target history cycling for /msg and /notice # * match possibility cycling (zsh-style completion) # * reverse cycling (ctrl + r) # * shell-like completion for /exec # * script name completion for /load # * the ability to complete words anywhere in the input line # * proper handling of file names that contain spaces # * multi-server support # * completion of aliases, command names, channel names, nicknames, # /set variables, /help topics and file names # # # /set variables this script uses: # # NICK_COMPLETION_CHAR -- the value of this set will be appended each time # nickname completion occurs as the first word of the # input line. a typical value might be ':'. # # TAB_HISTORY_CYCLE_SIZE -- how many nicknames to keep in the /msg history # # ZSH_STYLE_COMPLETION -- turning this on allows you to cycle through completion # possibilities, when there is more than one completion # possibility, similar to how zsh behaves for tab # completion # # # note: this script uses the serial number 110 for its serial hooks # # *** config variables *** @nick_completion_char = []; @tab_history_cycle_size = 10; @zsh_style_completion = [off]; # *** global variables *** @last_input_line = []; @match_index = -1; @match_cycle_list = []; @target_index = 0; @target_cycle_list = []; bind ^I parse_command do_tabkey 1; bind ^R parse_command do_tabkey -1; xdebug +extractw; alias add_target (msg_cmd, target) { if (numwords($myservers()) > 1 && count(/ $target) == 0 && left(1 $target) != [=]) { @:target = [-${servernum()}/${target}]; }; xdebug dword { @target_cycle_list = remw("$msg_cmd $target" $target_cycle_list); if (numwords($target_cycle_list) > (tab_history_cycle_size - 1)) { shift target_cycle_list; }; @target_index = 0; @push(target_cycle_list $msg_cmd $target); }; }; alias current_fragment_start { @:i = 0; @:j = curpos() - 1; @:last_space = 0; @:last_quote = 0; @:quotes = 0; while (i < j) { @:char = mid($i 1 $L); if (char == [ ]) { @:last_space = i; } elsif (char == ["]) { @:last_quote = i; @:quotes++; }; @:i++; }; if (quotes % 2) { if (mid($curpos() 1 $L) == ["] || last_quote > last_space) { return ${last_quote + 1}; } elsif (mid(${curpos() - 1} 1 $L) == ["]) { parsekey backward_character; return ${last_quote + 1}; } else { return ${last_space + 1}; }; } else { if (mid(${curpos() - 1} 1 $L) == [ ]) { return $curpos(); } elsif (last_space) { return ${last_space + 1}; } else { return 0; }; }; }; alias do_match_cycle (direction) { @:frag_start = current_fragment_start(); if (frag_start == -1) { return; }; @:fraglen = curpos() - frag_start; if (mid($curpos() 1 $L) == ["]) { parsekey forward_character; repeat ${fraglen + 1} parsekey backspace; } elsif (mid(${curpos() - 1} 1 $L) == ["]) { repeat ${fraglen + 1} parsekey backspace; } else { repeat $fraglen parsekey backspace; }; if (mid(${curpos() - 1} 1 $L) == ["]) { parsekey backspace; }; @match_index += direction; if (match_index == #match_cycle_list) { @match_index = 0; } elsif (match_index < 0) { @match_index = #match_cycle_list - 1; }; @:new_match = word($match_index $match_cycle_list); if (index("$chr(32)" $new_match) == -1) { xtype -l $new_match; } else { xtype -l "$new_match"; parsekey backward_character; }; @last_input_line = L; }; alias do_msg_cycle (direction) { xdebug dword { if (numwords($target_cycle_list) < 1) { return; }; @target_index -= direction; if (target_index == numwords($target_cycle_list)) { @target_index = 0; } elsif (target_index < 0) { @target_index = numwords($target_cycle_list) - 1; }; @:val = unsplit(" " $qword($target_index $target_cycle_list)); @:msg_cmd = word(0 $val); @:msg_targ = word(1 $val); parsekey reset_line /${msg_cmd} ${msg_targ}$chr(32); @last_input_line = []; }; }; alias do_set (variable, value) { if (value == []) { ^eval @:setval = $variable; if (setval == []) { xecho -b No value for $variable has been set; } else { xecho -b Current value of $variable is $setval; }; return; }; switch ($variable) { (NICK_COMPLETION_CHAR) { if (value == []) { @nick_completion_char = []; xecho -b Value of NICK_COMPLETION_CHAR set to ; } else { @nick_completion_char = value; xecho -b Value of NICK_COMPLETION_CHAR set to $value; }; } (TAB_HISTORY_CYCLE_SIZE) { if (!isnumber($value)) { xecho -b Value of TAB_HISTORY_CYCLE_SIZE must be numeric!; } elsif (value < 1) { xecho -b Value of TAB_HISTORY_CYCLE_SIZE cannot be less than 1; } else { @tab_history_cycle_size = value; xdebug dword { @target_cycle_list = rightw($value $target_cycle_list); }; xecho -b Value of TAB_HISTORY_CYCLE_SIZE set to $value; }; } (ZSH_STYLE_COMPLETION) { if (value == [on]) { @zsh_style_completion = [ON]; xecho -b Value of ZSH_STYLE_COMPLETION set to ON; } elsif (value == [off]) { @zsh_style_completion = [OFF]; xecho -b Value of ZSH_STYLE_COMPLETION set to OFF; } else { xecho -b Value of ZSH_STYLE_COMPLETION must be ON or OFF!; }; } (*) { xecho -b I don't know how to handle "$variable"; } }; }; alias do_tabkey (cycle_direction) { @:input_line = left($curpos() $L); if (L == []) { xdebug dword { @target_index = numwords($target_cycle_list); }; @do_msg_cycle($cycle_direction); return; } elsif (encode($L) == encode($last_input_line)) { if (zsh_style_completion == [ON] && #match_cycle_list > 1) { @do_match_cycle($cycle_direction); }; return; } elsif (leftw(1 $L) == [/msg] || leftw(1 $L) == [/notice]) { if (#L == 2 && mid(${@L - 1} 1 $L) == [ ]) { @do_msg_cycle($cycle_direction); return; }; }; @:frag_start = current_fragment_start(); if (frag_start == -1 || cycle_direction == -1) { return; }; @:fragment = mid($frag_start ${curpos() - frag_start} $L); @:fraglen = strlen($fragment); @:padding = []; switch ($input_line) { (/dcc %) { @:matches = match_dcc($fragment); if (#matches == 1) { @:padding = [ ]; }; } (/dcc send % *) (/exec % *) { @:matches = match_file($fragment); if (#matches == 1 && !isdirectory($matches)) { @:padding = [ ]; }; } (/exec %) { @:matches = match_exec($fragment); if (#matches == 1 && !isdirectory($matches)) { @:padding = [ ]; }; } (/help %) { @:matches = match_help($fragment); if (#matches == 1) { if (!isdirectory(${getset(HELP_PATH)}/${matches})) { @:padding = [ ]; }; }; } (/load *) (/unload *) { @:matches = match_load($fragment); if (#matches == 1 && !isdirectory($matches)) { @:padding = [ ]; }; } (/notify -%) { @:fragment = rest($fragment); @:fraglen--; @:matches = match_notify($fragment); if (#matches == 1) { @:padding = [ ]; }; } (/set -%) { @:fragment = rest($fragment); @:fraglen--; @:matches = match_set($fragment); if (#matches == 1) { @:padding = [ ]; }; } (/set %) { @:matches = match_set($fragment); if (#matches == 1) { @:padding = [ ]; }; } (/%) { @:fragment = rest($fragment); @:fraglen--; @:matches = match_command($fragment); if (#matches == 1) { @:padding = [ ]; }; } (*) { @:matches = []; @push(matches $match_chan($fragment)); @push(matches $match_nick($fragment)); if (#matches == 1) { if (#L == 1 && !ischannel($matches)) { if (nick_completion_char != []) { @:padding = [$nick_completion_char ]; }; } else { @:padding = [ ]; }; }; } }; @last_input_line = L; @match_index = -1; @:match_prefix = prefix($matches); if (@match_prefix <= fraglen && #matches > 1) { xecho -c Possible matches:; xecho -c -- $matches; xecho -c; return; }; if (match_prefix != []) { @:new_fragment = left($fraglen $match_prefix); if (encode($fragment) != encode($new_fragment)) { repeat $fraglen parsekey backspace; xtype -l $new_fragment; }; }; @:completion = rest($fraglen $match_prefix); if (completion == [] && padding == []) { return; }; if (index("$chr(32)" $fragment) == -1) { if (index("$chr(32)" $match_prefix) > -1) { repeat $fraglen parsekey backspace; if (mid($curpos() 1 $L) == ["] && mid(${curpos() - 1} 1 $L) == ["]) { xtype -l $match_prefix; if (padding != []) { parsekey forward_character; }; } else { xtype -l "$match_prefix"; if (padding == []) { parsekey backward_character; }; }; } else { xtype -l $completion; }; } else { xtype -l $completion; if (padding == []) { if (mid($curpos() 1 $L) != ["]) { xtype -l "; parsekey backward_character; }; } else { if (mid($curpos() 1 $L) == ["]) { parsekey forward_character; } else { xtype -l "; }; }; }; if (mid(${curpos()} 1 $L) != padding) { xtype -l $padding; }; @last_input_line = []; }; alias isdirectory (pathname) { @:statret = stat("$pathname"); @:file_type = left(1 $word(2 $statret)); if (file_type & 4) { return 1; } else { return 0; }; }; alias isexe (pathname) { @:statret = stat("$pathname"); @:file_mode = word(2 $statret); @:file_type = left(1 $file_mode); @:permissions = right(3 $file_mode); @:user_perm = left(1 $permissions); @:group_perm = mid(1 1 $permissions); @:other_perm = right(1 $permissions); if (file_type == 1) { if ((user_perm & 1) || (group_perm & 1) || (other_perm & 1)) { return 1; } else { return 0; }; } else { return 0; }; }; alias match_chan (fragment) { @:matches = pattern(${fragment}* $mychannels()); @match_cycle_list = matches; return $matches; }; alias match_command (fragment) { @:matches = []; @:command_matches = []; @push(matches $getcommands(${fragment}*)); @push(matches $aliasctl(alias pmatch ${fragment}*)); fe ($matches) match { @push(command_matches /${match}); }; @match_cycle_list = uniq($command_matches); return $uniq($matches); }; alias match_dcc (fragment) { @:dcc_cmds = [chat close closeall get list raw rename resume send]; @:matches = pattern(${fragment}% $dcc_cmds); @match_cycle_list = matches; return $matches; }; alias match_exec (fragment) { @:matches = []; if (index(/ $fragment) == -1) { @:path_list = PATH; while (path_list != []) { @:pathname = before(: $path_list); if (pathname == []) { @:pathname = path_list; @:path_list = []; } else { @:path_list = after(: $path_list); }; fe ($glob("${pathname}/${fragment}*")) filename { if (isexe($filename)) { @push(matches $after(-1 / $filename)); }; }; }; fe ($glob("${fragment}*")) filename { if (isdirectory($filename)) { @push(matches $filename); }; }; } else { fe ($glob("${fragment}*")) filename { if (isexe($filename) || isdirectory($filename)) { @push(matches $filename); }; }; }; @match_cycle_list = matches = uniq($matches); return $matches; }; alias match_file (fragment) { @:matches = glob("${fragment}*"); @match_cycle_list = matches; return $matches; }; alias match_help (fragment) { @:matches = []; @:help_path = getset(HELP_PATH); @:help_matches = globi("${help_path}/${fragment}*"); fe ($help_matches) match { @push(matches $rest(${@help_path + 1} $match)); }; @match_cycle_list = matches; return $matches; }; alias match_load (fragment) { @:matches = []; if (index(/ $fragment) == -1) { @:path_list = LOAD_PATH; while (path_list != []) { @:pathname = before(: $path_list); if (pathname == []) { @:pathname = path_list; @:path_list = []; } else { @:path_list = after(: $path_list); } fe ($glob("${pathname}/${fragment}*")) filename { @push(matches $after(-1 / $filename)); }; }; }; @push(matches $glob("${fragment}*")); @match_cycle_list = matches = uniq($matches); return $matches; }; alias match_nick (fragment) { @:nick_matches = pattern(${fragment}* $onchannel()); if (nick_matches == []) { @:nick_list = []; fe ($remw($C $mychannels())) chan { @push(nick_list $onchannel($chan)); }; @push(nick_list $notify(on)); @:nick_matches = pattern(${fragment}* $nick_list); }; if (nick_completion_char != [] && #matches > 1 && #L == 1) { fe ($nick_matches) nickname { @push(matches ${nickname}${nick_completion_char}); }; } else { @:matches = nick_matches; }; @:matches = uniq($matches); @match_cycle_list = matches; return $matches; }; alias match_notify (fragment) { @:matches = pattern(${fragment}* $notify()); @match_cycle_list = matches; return $matches; }; alias match_set (fragment) { @:matches = []; @:my_sets = [NICK_COMPLETION_CHAR TAB_HISTORY_CYCLE_SIZE ZSH_STYLE_COMPLETION]; @push(matches $getsets(${fragment}*)); @push(matches $pattern(${fragment}* $my_sets)); @match_cycle_list = matches; return $matches; }; alias match_theme (fragment) { @:matches = []; @:theme_matches = globi("${theme_directory}/${fragment}*.theme"); fe ($theme_matches) match { @push(matches $before(-1 . $rest(${@theme_directory + 1} $match))); }; @match_cycle_list = matches; return $matches; }; alias tclear { @target_cycle_list = []; @target_index = -1; xecho target history cleared ; }; # *** hooks *** on #^action 110 "*" { if (!rmatch($1 #* &* +*) ) { @add_target(msg $0); }; }; on #^dcc_chat 110 "*" { @add_target(msg =$0); }; on #^dcc_connect 110 "% CHAT *" { @add_target(msg =$0); }; on #^general_notice 110 "% *" { @add_target(notice $1); }; on #^msg 110 "*" { @add_target(msg $0); }; on #^send_action 110 "*" { if (!rmatch($0 #* &* +*) ) { @add_target(msg $0); }; }; on #^send_dcc_chat 110 "*" { @add_target(msg =$0); }; on #^send_msg 110 "*" { @add_target(msg $0); }; on #^send_notice 110 "*" { @add_target(notice $0); }; on ?send_to_server "% % NOTICE %:% *" { if (ischannel($3)) { return 0; }; @:refnum = before(: $3); @:target = after(: $3); xquote -server $refnum NOTICE $target $4-; return 1; }; on ?send_to_server "% % PRIVMSG %:% *" { if (ischannel($3)) { return 0; }; @:refnum = before(: $3); @:target = after(: $3); xquote -server $refnum PRIVMSG $target $4-; return 1; }; on ^set "NICK_COMPLETION_CHAR *" { @do_set(NICK_COMPLETION_CHAR $1); }; on ^set "TAB_HISTORY_CYCLE_SIZE *" { @do_set(TAB_HISTORY_CYCLE_SIZE $1); }; on ^set "ZSH_STYLE_COMPLETION *" { @do_set(ZSH_STYLE_COMPLETION $1); };