Mercurial > dotfiles.old
comparison irssi/scripts/adv_windowlist.pl @ 148:4e92ca6c779a
add irssi conf
author | zegervdv <zegervdv@me.com> |
---|---|
date | Sat, 18 Oct 2014 10:06:58 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
147:0d420021bd5d | 148:4e92ca6c779a |
---|---|
1 use strict; # use warnings; | |
2 | |
3 # {{{ debug | |
4 | |
5 #BEGIN { | |
6 # open STDERR, '>', '/home/ailin/wlstatwarnings'; | |
7 #}; | |
8 | |
9 # FIXME COULD SOMEONE PLEASE TELL ME HOW TO SHUT UP | |
10 # | |
11 # ... | |
12 # Variable "*" will not stay shared at (eval *) line *. | |
13 # Variable "*" will not stay shared at (eval *) line *. | |
14 # ... | |
15 # Can't locate package Irssi::Nick for @Irssi::Irc::Nick::ISA at (eval *) line *. | |
16 # ... | |
17 # | |
18 # THANKS | |
19 | |
20 # }}} | |
21 | |
22 # if you don't know how to operate folds, type zn | |
23 | |
24 # {{{ header | |
25 | |
26 use Irssi (); # which is the minimum required version of Irssi ? | |
27 use Irssi::TextUI; | |
28 | |
29 use vars qw($VERSION %IRSSI); | |
30 | |
31 $VERSION = '0.6ca'; | |
32 %IRSSI = ( | |
33 original_authors => q(BC-bd, Veli, Timo Sirainen, ). | |
34 q(Wouter Coekaerts, Jean-Yves Lefort), # (decadix) | |
35 original_contact => q([email protected], [email protected], [email protected], ). | |
36 q([email protected], [email protected]), | |
37 authors => q(Nei), | |
38 contact => q(Nei @ [email protected]), | |
39 url => "http://anti.teamidiot.de/", | |
40 name => q(awl), | |
41 description => q(Adds a permanent advanced window list on the right or ). | |
42 q(in a statusbar.), | |
43 description2 => q(Based on chanact.pl which was apparently based on ). | |
44 q(lightbar.c and nicklist.pl with various other ideas ). | |
45 q(from random scripts.), | |
46 license => q(GNU GPLv2 or later), | |
47 ); | |
48 | |
49 # }}} | |
50 | |
51 # {{{ *** D O C U M E N T A T I O N *** | |
52 | |
53 # adapted by Nei | |
54 | |
55 ############### | |
56 # {{{ original comment | |
57 # ########### | |
58 # # Adds new powerful and customizable [Act: ...] item (chanelnames,modes,alias). | |
59 # # Lets you give alias characters to windows so that you can select those with | |
60 # # meta-<char>. | |
61 # # | |
62 # # for irssi 0.8.2 by [email protected] | |
63 # # | |
64 # # inspired by chanlist.pl by '[email protected]' | |
65 # # | |
66 # ######### | |
67 # # {{{ Contributors | |
68 # ######### | |
69 # # | |
70 # # [email protected] /window_alias code | |
71 # # [email protected] chanact_abbreviate_names | |
72 # # [email protected] Extra chanact_show_mode and chanact_chop_status | |
73 # # }}} | |
74 # }}} | |
75 # | |
76 # {{{ FURTHER THANKS TO | |
77 # ############ | |
78 # # buu, fxn, Somni, Khisanth, integral, tybalt89 for much support in any aspect perl | |
79 # # and the channel in general ( #perl @ freenode ) and especially the ir_* functions | |
80 # # | |
81 # # Valentin 'senneth' Batz ( [email protected] ) for the pointer to grep.pl, continuous support | |
82 # # and help in digging up ir_strip_codes | |
83 # # | |
84 # # OnetrixNET technology networks for the debian environment | |
85 # # | |
86 # # Monkey-Pirate.com / Spaceman Spiff for the webspace | |
87 # # | |
88 # }}} | |
89 | |
90 ###### | |
91 # {{{ M A I N P R O B L E M | |
92 ##### | |
93 # | |
94 # It is impossible to place the awl on a statusbar together with other items, | |
95 # because I do not know how to calculate the size that it is going to get | |
96 # granted, and therefore I cannot do the linebreaks properly. | |
97 # This is what is missing to make a nice script out of awl. | |
98 # If you have any ideas, please contact me ASAP :). | |
99 # }}} | |
100 ###### | |
101 | |
102 ###### | |
103 # {{{ UTF-8 PROBLEM | |
104 ##### | |
105 # | |
106 # Please help me find a solution to this: | |
107 # this be your statusbar, it is using up the maximum term size | |
108 # [[1=1]#abc [2=2]#defghi] | |
109 # | |
110 # now consider this example:i | |
111 # "ascii" characters are marked with ., utf-8 characters with * | |
112 # [[1=1]#... [2=2]#...***] | |
113 # | |
114 # you should think that this is how it would be displayed? WRONG! | |
115 # [[1=1]#... [2=2]#...*** ] | |
116 # | |
117 # this is what Irssi does.. I believe my length calculating code to be correct, | |
118 # however, I'd love to be proven wrong (or receive any other fix, too, of | |
119 # course!) | |
120 # }}} | |
121 ###### | |
122 | |
123 ######### | |
124 # {{{ USAGE | |
125 ### | |
126 # | |
127 # copy the script to ~/.irssi/scripts/ | |
128 # | |
129 # In irssi: | |
130 # | |
131 # /script load awl | |
132 # | |
133 # | |
134 # Hint: to get rid of the old [Act:] display | |
135 # /statusbar window remove act | |
136 # | |
137 # to get it back: | |
138 # /statusbar window add -after lag -priority 10 act | |
139 # }}} | |
140 ########## | |
141 # {{{ OPTIONS | |
142 ######## | |
143 # | |
144 # {{{ /set awl_display_nokey <string> | |
145 # /set awl_display_key <string> | |
146 # /set awl_display_nokey_active <string> | |
147 # /set awl_display_key_active <string> | |
148 # * string : Format String for one window. The following $'s are expanded: | |
149 # $C : Name | |
150 # $N : Number of the Window | |
151 # $Q : meta-Keymap | |
152 # $H : Start highlighting | |
153 # $S : Stop highlighting | |
154 # /+++++++++++++++++++++++++++++++++, | |
155 # | **** I M P O R T A N T : **** | | |
156 # | | | |
157 # | don't forget to use $S if you | | |
158 # | used $H before! | | |
159 # | | | |
160 # '+++++++++++++++++++++++++++++++++/ | |
161 # XXX NOTE ON *_active: there is a BUG somewhere in the length | |
162 # XXX calculation. currently it's best to NOT remove $H/$S from those | |
163 # XXX settings if you use it in the non-active settings. | |
164 # }}} | |
165 # {{{ /set awl_separator <string> | |
166 # * string : Charater to use between the channel entries | |
167 # you'll need to escape " " space and "$" like this: | |
168 # "/set awl_separator \ " | |
169 # "/set awl_separator \$" | |
170 # and {}% like this: | |
171 # "/set awl_separator %{" | |
172 # "/set awl_separator %}" | |
173 # "/set awl_separator %%" | |
174 # (reason being, that the separator is used inside a {format }) | |
175 # }}} | |
176 # {{{ /set awl_prefer_name <ON|OFF> | |
177 # * this setting decides whether awl will use the active_name (OFF) or the | |
178 # window name as the name/caption in awl_display_*. | |
179 # That way you can rename windows using /window name myownname. | |
180 # }}} | |
181 # {{{ /set awl_hide_data <num> | |
182 # * num : hide the window if its data_level is below num | |
183 # set it to 0 to basically disable this feature, | |
184 # 1 if you don't want windows without activity to be shown | |
185 # 2 to show only those windows with channel text or hilight | |
186 # 3 to show only windows with hilight | |
187 # }}} | |
188 # {{{ /set awl_maxlines <num> | |
189 # * num : number of lines to use for the window list (0 to disable, negative | |
190 # lock) | |
191 # }}} | |
192 # {{{ /set awl_columns <num> | |
193 # * num : number of columns to use in screen mode (0 for unlimited) | |
194 # }}} | |
195 # {{{ /set awl_block <num> | |
196 # * num : width of a column in screen mode (negative values = block display) | |
197 # /+++++++++++++++++++++++++++++++++, | |
198 # | ****** W A R N I N G ! ****** | | |
199 # | | | |
200 # | If your block display looks | | |
201 # | DISTORTED, you need to add the | | |
202 # | following line to your .theme | | |
203 # | file under | | |
204 # | abstracts = { : | | |
205 # | | | |
206 # | sb_act_none = "%n$*"; | | |
207 # | | | |
208 # '+++++++++++++++++++++++++++++++++/ | |
209 #.02:08:26. < shi> Irssi::current_theme()->get_format <.. can this be used? | |
210 # }}} | |
211 # {{{ /set awl_sbar_maxlength <ON|OFF> | |
212 # * if you enable the maxlength setting, the block width will be used as a | |
213 # maximum length for the non-block statusbar mode too. | |
214 # }}} | |
215 # {{{ /set awl_height_adjust <num> | |
216 # * num : how many lines to leave empty in screen mode | |
217 # }}} | |
218 # {{{ /set awl_sort <-data_level|-last_line|refnum> | |
219 # * you can change the window sort order with this variable | |
220 # -data_level : sort windows with hilight first | |
221 # -last_line : sort windows in order of activity | |
222 # refnum : sort windows by window number | |
223 # }}} | |
224 # {{{ /set awl_placement <top|bottom> | |
225 # /set awl_position <num> | |
226 # * these settings correspond to /statusbar because awl will create | |
227 # statusbars for you | |
228 # (see /help statusbar to learn more) | |
229 # }}} | |
230 # {{{ /set awl_all_disable <ON|OFF> | |
231 # * if you set awl_all_disable to ON, awl will also remove the | |
232 # last statusbar it created if it is empty. | |
233 # As you might guess, this only makes sense with awl_hide_data > 0 ;) | |
234 # }}} | |
235 # {{{ /set awl_automode <sbar|screen|emulate_lightbar> | |
236 # * this setting defines whether the window list is shown in statusbars or | |
237 # whether the screen hack is used (from nicklist.pl) | |
238 # }}} | |
239 # }}} | |
240 ########## | |
241 # {{{ COMMANDS | |
242 ######## | |
243 # {{{ /awl paste <ON|OFF|TOGGLE> | |
244 # * enables or disables the screen hack windowlist. This is useful when you | |
245 # want to mark & copy text that you want to paste somewhere (hence the | |
246 # name). (ON means AWL disabled!) | |
247 # This is nicely bound to a function key for example. | |
248 # }}} | |
249 # {{{ /awl redraw | |
250 # * redraws the screen hack windowlist. There are many occasions where the | |
251 # screen hack windowlist can get destroyed so you can use this command to | |
252 # fix it. | |
253 # }}} | |
254 # }}} | |
255 ### | |
256 # {{{ WISHES | |
257 #### | |
258 # | |
259 # if you fiddle with my mess, provide me with your fixes so I can benefit as well | |
260 # | |
261 # Nei =^.^= ( [email protected] ) | |
262 # }}} | |
263 | |
264 # }}} | |
265 | |
266 # {{{ modules | |
267 | |
268 #use Class::Classless; | |
269 #use Term::Info; | |
270 | |
271 # }}} | |
272 | |
273 # {{{ global variables | |
274 | |
275 my $replaces = '[=]'; # AARGH!!! (chars that are always surrounded by weird | |
276 # colour codes by Irssi) | |
277 | |
278 my $actString = []; # statusbar texts | |
279 my $currentLines = 0; | |
280 my $resetNeeded; # layout/screen has changed, redo everything | |
281 my $needRemake; # "normal" changes | |
282 #my $callcount = 0; | |
283 sub GLOB_QUEUE_TIMER () { 100 } | |
284 my $globTime = undef; # timer to limit remake() calls | |
285 | |
286 | |
287 my $SCREEN_MODE; | |
288 my $DISABLE_SCREEN_TEMP; | |
289 my $currentColumns = 0; | |
290 my $screenResizing; | |
291 my ($screenHeight, $screenWidth); | |
292 my $screenansi = bless { | |
293 NAME => 'Screen::ANSI', | |
294 PARENTS => [], | |
295 METHODS => { | |
296 dcs => sub { "\033P" }, | |
297 st => sub { "\033\\"}, | |
298 } | |
299 }, 'Class::Classless::X'; | |
300 #my $terminfo = new Term::Info 'xterm'; # xterm here, make this modular | |
301 # {{{{{{{{{{{{{{{ | |
302 my $terminfo = bless { # xterm here, make this modular | |
303 NAME => 'Term::Info::xterm', | |
304 PARENTS => [], | |
305 METHODS => { | |
306 # civis=\E[?25l, | |
307 civis => sub { "\033[?25l" }, | |
308 # sc=\E7, | |
309 sc => sub { "\0337" }, | |
310 # cup=\E[%i%p1%d;%p2%dH, | |
311 cup => sub { shift;shift; "\033[" . ($_[0] + 1) . ';' . ($_[1] + 1) . 'H' }, | |
312 # el=\E[K, | |
313 el => sub { "\033[K" }, | |
314 # rc=\E8, | |
315 rc => sub { "\0338" }, | |
316 # cnorm=\E[?25h, | |
317 cnorm => sub { "\033[?25h" }, | |
318 # setab=\E[4%p1%dm, | |
319 setab => sub { shift;shift; "\033[4" . $_[0] . 'm' }, | |
320 # setaf=\E[3%p1%dm, | |
321 setaf => sub { shift;shift; "\033[3" . $_[0] . 'm' }, | |
322 # bold=\E[1m, | |
323 bold => sub { "\033[1m" }, | |
324 # blink=\E[5m, | |
325 blink => sub { "\033[5m" }, | |
326 # rev=\E[7m, | |
327 rev => sub { "\033[7m" }, | |
328 # op=\E[39;49m, | |
329 op => sub { "\033[39;49m" }, | |
330 } | |
331 }, 'Class::Classless::X'; | |
332 # }}}}}}}}}}}}}}} | |
333 | |
334 | |
335 sub setc () { | |
336 $IRSSI{'name'} | |
337 } | |
338 sub set ($) { | |
339 setc . '_' . shift | |
340 } | |
341 | |
342 # }}} | |
343 | |
344 | |
345 # {{{ sbar mode | |
346 | |
347 my %statusbars; # currently active statusbars | |
348 | |
349 # maybe I should just tie the array ? | |
350 sub add_statusbar { | |
351 for (@_) { | |
352 # add subs | |
353 for my $l ($_) { { | |
354 no strict 'refs'; # :P | |
355 *{set$l} = sub { awl($l, @_) }; | |
356 }; } | |
357 Irssi::command('statusbar ' . (set$_) . ' reset'); | |
358 Irssi::command('statusbar ' . (set$_) . ' enable'); | |
359 if (lc Irssi::settings_get_str(set 'placement') eq 'top') { | |
360 Irssi::command('statusbar ' . (set$_) . ' placement top'); | |
361 } | |
362 if ((my $x = int Irssi::settings_get_int(set 'position')) != 0) { | |
363 Irssi::command('statusbar ' . (set$_) . ' position ' . $x); | |
364 } | |
365 Irssi::command('statusbar ' . (set$_) . ' add -priority 100 -alignment left barstart'); | |
366 Irssi::command('statusbar ' . (set$_) . ' add ' . (set$_)); | |
367 Irssi::command('statusbar ' . (set$_) . ' add -priority 100 -alignment right barend'); | |
368 Irssi::command('statusbar ' . (set$_) . ' disable'); | |
369 Irssi::statusbar_item_register(set$_, '$0', set$_); | |
370 $statusbars{$_} = {}; | |
371 } | |
372 } | |
373 | |
374 sub remove_statusbar { | |
375 for (@_) { | |
376 Irssi::command('statusbar ' . (set$_) . ' reset'); | |
377 Irssi::statusbar_item_unregister(set$_); # XXX does this actually work ? | |
378 # DO NOT REMOVE the sub before you have unregistered it :)) | |
379 for my $l ($_) { { | |
380 no strict 'refs'; | |
381 undef &{set$l}; | |
382 }; } | |
383 delete $statusbars{$_}; | |
384 } | |
385 } | |
386 | |
387 sub syncLines { | |
388 my $temp = $currentLines; | |
389 $currentLines = @$actString; | |
390 #Irssi::print("current lines: $temp new lines: $currentLines"); | |
391 my $currMaxLines = Irssi::settings_get_int(set 'maxlines'); | |
392 if ($currMaxLines > 0 and @$actString > $currMaxLines) { | |
393 $currentLines = $currMaxLines; | |
394 } | |
395 elsif ($currMaxLines < 0) { | |
396 $currentLines = abs($currMaxLines); | |
397 } | |
398 return if ($temp == $currentLines); | |
399 if ($currentLines > $temp) { | |
400 for ($temp .. ($currentLines - 1)) { | |
401 add_statusbar($_); | |
402 Irssi::command('statusbar ' . (set$_) . ' enable'); | |
403 } | |
404 } | |
405 else { | |
406 for ($_ = ($temp - 1); $_ >= $currentLines; $_--) { | |
407 Irssi::command('statusbar ' . (set$_) . ' disable'); | |
408 remove_statusbar($_); | |
409 } | |
410 } | |
411 } | |
412 | |
413 # FIXME implement $get_size_only check, and user $item->{min|max-size} ?? | |
414 sub awl { | |
415 my ($line, $item, $get_size_only) = @_; | |
416 | |
417 if ($needRemake) { | |
418 $needRemake = undef; | |
419 remake(); | |
420 } | |
421 | |
422 my $text = $actString->[$line]; # DO NOT set the actual $actString->[$line] to '' here or | |
423 $text = '' unless defined $text; # you'll screw up the statusbar counter ($currentLines) | |
424 $item->default_handler($get_size_only, $text, '', 1); | |
425 } | |
426 | |
427 # remove old statusbars | |
428 my %killBar; | |
429 sub get_old_status { | |
430 my ($textDest, $cont, $cont_stripped) = @_; | |
431 if ($textDest->{'level'} == 524288 and $textDest->{'target'} eq '' | |
432 and !defined($textDest->{'server'}) | |
433 ) { | |
434 my $name = quotemeta(set ''); | |
435 if ($cont_stripped =~ m/^$name(\d+)\s/) { $killBar{$1} = {}; } | |
436 Irssi::signal_stop(); | |
437 } | |
438 } | |
439 sub killOldStatus { | |
440 %killBar = (); | |
441 Irssi::signal_add_first('print text' => 'get_old_status'); | |
442 Irssi::command('statusbar'); | |
443 Irssi::signal_remove('print text' => 'get_old_status'); | |
444 remove_statusbar(keys %killBar); | |
445 } | |
446 #killOldStatus(); | |
447 | |
448 # end sbar mode }}} | |
449 | |
450 | |
451 # {{{ keymaps | |
452 | |
453 my %keymap; | |
454 | |
455 sub get_keymap { | |
456 my ($textDest, undef, $cont_stripped) = @_; | |
457 if ($textDest->{'level'} == 524288 and $textDest->{'target'} eq '' | |
458 and !defined($textDest->{'server'}) | |
459 ) { | |
460 if ($cont_stripped =~ m/((?:meta-)+)(.)\s+change_window (\d+)/) { | |
461 my ($level, $key, $window) = ($1, $2, $3); | |
462 my $numlevel = ($level =~ y/-//) - 1; | |
463 $keymap{$window} = ('-' x $numlevel) . "$key"; | |
464 } | |
465 Irssi::signal_stop(); | |
466 } | |
467 } | |
468 | |
469 sub update_keymap { | |
470 %keymap = (); | |
471 Irssi::signal_remove('command bind' => 'watch_keymap'); | |
472 Irssi::signal_add_first('print text' => 'get_keymap'); | |
473 Irssi::command('bind'); # stolen from grep | |
474 Irssi::signal_remove('print text' => 'get_keymap'); | |
475 Irssi::signal_add('command bind' => 'watch_keymap'); | |
476 Irssi::timeout_add_once(100, 'eventChanged', undef); | |
477 } | |
478 | |
479 # watch keymap changes | |
480 sub watch_keymap { | |
481 Irssi::timeout_add_once(1000, 'update_keymap', undef); | |
482 } | |
483 | |
484 update_keymap(); | |
485 | |
486 # end keymaps }}} | |
487 | |
488 # {{{ format handling | |
489 | |
490 # a bad way do do expansions but who cares | |
491 sub expand { | |
492 my ($string, %format) = @_; | |
493 my ($exp, $repl); | |
494 $string =~ s/\$$exp/$repl/g while (($exp, $repl) = each(%format)); | |
495 return $string; | |
496 } | |
497 | |
498 my %strip_table = ( | |
499 # fe-common::core::formats.c:format_expand_styles | |
500 # delete format_backs format_fores bold_fores other stuff | |
501 (map { $_ => '' } (split //, '04261537' . 'kbgcrmyw' . 'KBGCRMYW' . 'U9_8:|FnN>#[')), | |
502 # escape | |
503 (map { $_ => $_ } (split //, '{}%')), | |
504 ); | |
505 sub ir_strip_codes { # strip %codes | |
506 my $o = shift; | |
507 $o =~ s/(%(.))/exists $strip_table{$2} ? $strip_table{$2} : $1/gex; | |
508 $o | |
509 } | |
510 | |
511 sub ir_parse_special { | |
512 my $o; my $i = shift; | |
513 #if ($_[0]) { # for the future?!? | |
514 # eval { | |
515 # $o = $_[0]->parse_special($i); | |
516 # }; | |
517 # unless ($@) { | |
518 # return $o; | |
519 # } | |
520 #} | |
521 my $win = shift || Irssi::active_win(); | |
522 my $server = Irssi::active_server(); | |
523 if (ref $win and ref $win->{'active'}) { | |
524 $o = $win->{'active'}->parse_special($i); | |
525 } | |
526 elsif (ref $win and ref $win->{'active_server'}) { | |
527 $o = $win->{'active_server'}->parse_special($i); | |
528 } | |
529 elsif (ref $server) { | |
530 $o = $server->parse_special($i); | |
531 } | |
532 else { | |
533 $o = Irssi::parse_special($i); | |
534 } | |
535 $o | |
536 } | |
537 sub ir_parse_special_protected { | |
538 my $o; my $i = shift; | |
539 $i =~ s/ | |
540 ( \\. ) | # skip over escapes (maybe) | |
541 ( \$[^% $\]+ ) # catch special variables | |
542 / | |
543 if ($1) { $1 } | |
544 elsif ($2) { my $i2 = $2; ir_fe(ir_parse_special($i2, @_)) } | |
545 else { $& } | |
546 /gex; | |
547 $i | |
548 } | |
549 | |
550 | |
551 sub sb_ctfe { # Irssi::current_theme->format_expand wrapper | |
552 Irssi::current_theme->format_expand( | |
553 shift, | |
554 ( | |
555 Irssi::EXPAND_FLAG_IGNORE_REPLACES | |
556 | | |
557 ($_[0]?0:Irssi::EXPAND_FLAG_IGNORE_EMPTY) | |
558 ) | |
559 ) | |
560 } | |
561 sub sb_expand { # expand {format }s (and apply parse_special for $vars) | |
562 ir_parse_special( | |
563 sb_ctfe(shift) | |
564 ) | |
565 } | |
566 sub sb_strip { | |
567 ir_strip_codes( | |
568 sb_expand(shift) | |
569 ); # does this get us the actual length of that s*ty bar :P ? | |
570 } | |
571 sub sb_length { | |
572 # unicode cludge, d*mn broken Irssi | |
573 # screw it, this will fail from broken joining anyway (and cause warnings) | |
574 my $term_type = 'term_type'; | |
575 if (Irssi::version > 20040819) { # this is probably wrong, but I don't know | |
576 # when the setting name got changed | |
577 $term_type = 'term_charset'; | |
578 } | |
579 #if (lc Irssi::settings_get_str($term_type) eq '8bit' | |
580 # or Irssi::settings_get_str($term_type) =~ /^iso/i | |
581 #) { | |
582 # length(sb_strip(shift)) | |
583 #} | |
584 #else { | |
585 my $temp = sb_strip(shift); | |
586 # try to get the displayed width | |
587 my $length; | |
588 eval { | |
589 require Text::CharWidth; | |
590 $length = Text::CharWidth::mbswidth($temp); | |
591 }; | |
592 unless ($@) { | |
593 return $length; | |
594 } | |
595 else { | |
596 if (lc Irssi::settings_get_str($term_type) eq 'utf-8') { | |
597 # try to switch on utf8 | |
598 eval { | |
599 no warnings; | |
600 require Encode; | |
601 #$temp = Encode::decode_utf8($temp); # thanks for the hint, but I have my | |
602 # # reasons for _utf8_on | |
603 Encode::_utf8_on($temp); | |
604 }; | |
605 } | |
606 # there is nothing more I can do | |
607 length($temp) | |
608 } | |
609 #} | |
610 } | |
611 | |
612 # !!! G*DD*MN Irssi is adding an additional layer of backslashitis per { } layer | |
613 # !!! AND I still don't know what I need to escape. | |
614 # !!! and NOONE else seems to know or care either. | |
615 # !!! f*ck open source. I mean it. | |
616 # XXX any Irssi::print debug statement leads to SEGFAULT - why ? | |
617 | |
618 # major parts of the idea by buu (#perl @ freenode) | |
619 # thanks to fxn and Somni for debugging | |
620 # while ($_[0] =~ /(.)/g) { | |
621 # my $c = $1; # XXX sooo... goto kills $1 | |
622 # if ($q eq '%') { goto ESC; } | |
623 | |
624 ## <freenode:#perl:tybalt89> s/%(.)|(\{)|(\})|(\\|\$)/$1?$1:$2?($level++,$2):$3?($level>$min_level&&$level--,$3):'\\'x(2**$level-1).$4/ge; # untested... | |
625 sub ir_escape { | |
626 my $min_level = $_[1] || 0; my $level = $min_level; | |
627 my $o = shift; | |
628 $o =~ s/ | |
629 ( %. ) | # $1 | |
630 ( \{ ) | # $2 | |
631 ( \} ) | # $3 | |
632 ( \\ ) | # $4 | |
633 ( \$(?=[^\\]) ) | # $5 | |
634 ( \$ ) # $6 | |
635 / | |
636 if ($1) { $1 } # %. escape | |
637 elsif ($2) { $level++; $2 } # { nesting start | |
638 elsif ($3) { if ($level > $min_level) { $level--; } $3 } # } nesting end | |
639 elsif ($4) { '\\'x(2**$level) } # \ needs \\escaping | |
640 elsif ($5) { '\\'x(2**$level-1) . '$' . '\\'x(2**$level-1) } # and $ needs even more because of "parse_special" | |
641 else { '\\'x(2**$level-1) . '$' } # $ needs \$ escaping | |
642 /gex; | |
643 $o | |
644 } | |
645 #sub ir_escape { | |
646 # my $min_level = $_[1] || 0; my $level = $min_level; | |
647 # my $o = shift; | |
648 # $o =~ s/ | |
649 # ( %. ) | # $1 | |
650 # ( \{ ) | # $2 | |
651 # ( \} ) | # $3 | |
652 # ( \\ | \$ ) # $4 | |
653 # / | |
654 # if ($1) { $1 } # %. escape | |
655 # elsif ($2) { $level++; $2 } # { nesting start | |
656 # elsif ($3) { if ($level > $min_level) { $level--; } $3 } # } nesting end | |
657 # else { '\\'x(2**($level-1)-1) . $4 } # \ or $ needs \\escaping | |
658 # /gex; | |
659 # $o | |
660 #} | |
661 | |
662 sub ir_fe { # try to fix format stuff | |
663 my $x = shift; | |
664 # XXX why do I have to use two/four % here instead of one/two ?? | |
665 # answer: you screwed up in ir_escape | |
666 $x =~ s/([%{}])/%$1/g; | |
667 #$x =~ s/(\\|\$|[ ])/\\$1/g; # XXX HOW CAN I HANDLE THE SPACES CORRECTLY XXX | |
668 $x =~ s/(\\|\$)/\\$1/g; | |
669 #$x =~ s/(\$(?=.))|(\$)/$1?"\\\$\\":"\\\$"/ge; # I think this should be here | |
670 # # (logic), but it doesn't work | |
671 # # that way :P | |
672 #$x =~ s/\\/\\\\/g; # that's right, escape escapes | |
673 $x | |
674 } | |
675 sub ir_ve { # escapes special vars but leave colours alone | |
676 my $x = shift; | |
677 #$x =~ s/([%{}])/%$1/g; | |
678 $x =~ s/(\\|\$|[ ])/\\$1/g; | |
679 $x | |
680 } | |
681 | |
682 my %ansi_table; | |
683 { | |
684 my ($i, $j, $k) = (0, 0, 0); | |
685 %ansi_table = ( | |
686 # fe-common::core::formats.c:format_expand_styles | |
687 # do format_backs | |
688 (map { $_ => $terminfo->setab($i++) } (split //, '01234567' )), | |
689 # do format_fores | |
690 (map { $_ => $terminfo->setaf($j++) } (split //, 'krgybmcw' )), | |
691 # do bold_fores | |
692 (map { $_ => $terminfo->bold() . | |
693 $terminfo->setaf($k++) } (split //, 'KRGYBMCW')), | |
694 # reset | |
695 #(map { $_ => $terminfo->op() } (split //, 'nN')), | |
696 (map { $_ => $terminfo->op() } (split //, 'n')), | |
697 (map { $_ => "\033[0m" } (split //, 'N')), # XXX quick and DIRTY | |
698 # flash/bright | |
699 F => $terminfo->blink(), | |
700 # reverse | |
701 8 => $terminfo->rev(), | |
702 # bold | |
703 (map { $_ => $terminfo->bold() } (split //, '9_')), | |
704 # delete other stuff | |
705 (map { $_ => '' } (split //, ':|>#[')), | |
706 # escape | |
707 (map { $_ => $_ } (split //, '{}%')), | |
708 ) | |
709 } | |
710 sub formats_to_ansi_basic { | |
711 my $o = shift; | |
712 $o =~ s/(%(.))/exists $ansi_table{$2} ? $ansi_table{$2} : $1/gex; | |
713 $o | |
714 } | |
715 | |
716 sub lc1459 ($) { my $x = shift; $x =~ y/A-Z][\^/a-z}{|~/; $x } | |
717 Irssi::settings_add_str(setc, 'banned_channels', ''); | |
718 Irssi::settings_add_bool(setc, 'banned_channels_on', 0); | |
719 my %banned_channels = map { lc1459($_) => undef } | |
720 split ' ', Irssi::settings_get_str('banned_channels'); | |
721 Irssi::settings_add_str(setc, 'fancy_abbrev', 'fancy'); | |
722 | |
723 # }}} | |
724 | |
725 # {{{ main | |
726 | |
727 sub remake () { | |
728 #$callcount++; | |
729 #my $xx = $callcount; Irssi::print("starting remake [ $xx ]"); | |
730 my ($hilight, $number, $display); | |
731 my $separator = '{sb_act_sep ' . Irssi::settings_get_str(set 'separator') . | |
732 '}'; | |
733 my $custSort = Irssi::settings_get_str(set 'sort'); | |
734 my $custSortDir = 1; | |
735 if ($custSort =~ /^[-!](.*)/) { | |
736 $custSortDir = -1; | |
737 $custSort = $1; | |
738 } | |
739 | |
740 my @wins = | |
741 sort { | |
742 ( | |
743 ( (int($a->{$custSort}) <=> int($b->{$custSort})) * $custSortDir ) | |
744 || | |
745 ($a->{'refnum'} <=> $b->{'refnum'}) | |
746 ) | |
747 } Irssi::windows; | |
748 my $block = Irssi::settings_get_int(set 'block'); | |
749 my $columns = $currentColumns; | |
750 my $oldActString = $actString if $SCREEN_MODE; | |
751 $actString = $SCREEN_MODE ? [' A W L'] : []; | |
752 my $line = $SCREEN_MODE ? 1 : 0; | |
753 my $width = $SCREEN_MODE | |
754 ? | |
755 $screenWidth - abs($block)*$columns + 1 | |
756 : | |
757 ([Irssi::windows]->[0]{'width'} - sb_length('{sb x}')); | |
758 my $height = $screenHeight - abs(Irssi::settings_get_int(set | |
759 'height_adjust')); | |
760 my ($numPad, $keyPad) = (0, 0); | |
761 my %abbrevList; | |
762 if ($SCREEN_MODE or Irssi::settings_get_bool(set 'sbar_maxlength') | |
763 or ($block < 0) | |
764 ) { | |
765 %abbrevList = (); | |
766 if (Irssi::settings_get_str('fancy_abbrev') !~ /^(no|off|head)/i) { | |
767 my @nameList = map { ref $_ ? $_->get_active_name : '' } @wins; | |
768 for (my $i = 0; $i < @nameList - 1; ++$i) { | |
769 my ($x, $y) = ($nameList[$i], $nameList[$i + 1]); | |
770 for ($x, $y) { s/^[+#!=]// } | |
771 my $res = Algorithm::LCSS::LCSS($x, $y); | |
772 if (defined $res) { | |
773 #Irssi::print("common pattern $x $y : $res"); | |
774 #Irssi::print("found at $nameList[$i] ".index($nameList[$i], | |
775 # $res)); | |
776 $abbrevList{$nameList[$i]} = int (index($nameList[$i], $res) + | |
777 (length($res) / 2)); | |
778 #Irssi::print("found at ".$nameList[$i+1]." ".index($nameList[$i+1], | |
779 # $res)); | |
780 $abbrevList{$nameList[$i+1]} = int (index($nameList[$i+1], $res) + | |
781 (length($res) / 2)); | |
782 } | |
783 } | |
784 } | |
785 if ($SCREEN_MODE or ($block < 0)) { | |
786 $numPad = length((sort { length($b) <=> length($a) } keys %keymap)[0]); | |
787 $keyPad = length((sort { length($b) <=> length($a) } values %keymap)[0]); | |
788 } | |
789 } | |
790 if ($SCREEN_MODE) { | |
791 print STDERR $screenansi->dcs(). | |
792 $terminfo->civis(). | |
793 $terminfo->sc(). | |
794 $screenansi->st(); | |
795 if (@$oldActString < 1) { | |
796 print STDERR $screenansi->dcs(). | |
797 $terminfo->cup(0, $width). | |
798 $actString->[0]. | |
799 $terminfo->el(). | |
800 $screenansi->st(); | |
801 } | |
802 } | |
803 foreach my $win (@wins) { | |
804 unless ($SCREEN_MODE) { | |
805 $actString->[$line] = '' unless defined $actString->[$line] | |
806 or Irssi::settings_get_bool(set 'all_disable'); | |
807 } | |
808 | |
809 # all stolen from chanact, what does this code do and why do we need it ? | |
810 !ref($win) && next; | |
811 | |
812 my $name = $win->get_active_name; | |
813 $name = '*' if (Irssi::settings_get_bool('banned_channels_on') and exists | |
814 $banned_channels{lc1459($name)}); | |
815 $name = $win->{'name'} if $name ne '*' and $win->{'name'} ne '' | |
816 and Irssi::settings_get_bool(set 'prefer_name'); | |
817 my $active = $win->{'active'}; | |
818 my $colour = $win->{'hilight_color'}; | |
819 if (!defined $colour) { $colour = ''; } | |
820 | |
821 if ($win->{'data_level'} < Irssi::settings_get_int(set 'hide_data')) { | |
822 next; } # for Geert | |
823 if ($win->{'data_level'} == 0) { $hilight = '{sb_act_none '; } | |
824 elsif ($win->{'data_level'} == 1) { $hilight = '{sb_act_text '; } | |
825 elsif ($win->{'data_level'} == 2) { $hilight = '{sb_act_msg '; } | |
826 elsif ($colour ne '') { $hilight = "{sb_act_hilight_color $colour "; } | |
827 elsif ($win->{'data_level'} == 3) { $hilight = '{sb_act_hilight '; } | |
828 else { $hilight = '{sb_act_special '; } | |
829 | |
830 $number = $win->{'refnum'}; | |
831 my @display = ('display_nokey'); | |
832 if (defined $keymap{$number} and $keymap{$number} ne '') { | |
833 unshift @display, map { (my $cpy = $_) =~ s/_no/_/; $cpy } @display; | |
834 } | |
835 if (Irssi::active_win->{'refnum'} == $number) { | |
836 unshift @display, map { my $cpy = $_; $cpy .= '_active'; $cpy } @display; | |
837 } | |
838 #Irssi::print("win $number [@display]: " . join '.', split //, join '<<', map { | |
839 # Irssi::settings_get_str(set $_) } @display); | |
840 $display = (grep { $_ } | |
841 map { Irssi::settings_get_str(set $_) } | |
842 @display)[0]; | |
843 #Irssi::print("win $number : " . join '.', split //, $display); | |
844 | |
845 if ($SCREEN_MODE or Irssi::settings_get_bool(set 'sbar_maxlength') | |
846 or ($block < 0) | |
847 ) { | |
848 my $baseLength = sb_length(ir_escape(ir_ve(ir_parse_special_protected(sb_ctfe( | |
849 '{sb_background}' . expand($display, | |
850 C => ir_fe('x'), | |
851 N => $number . (' 'x($numPad - length($number))), | |
852 Q => ir_fe((' 'x($keyPad - length($keymap{$number}))) . $keymap{$number}), | |
853 H => $hilight, | |
854 S => '}{sb_background}' | |
855 ), 1), $win)))) - 1; | |
856 my $diff = abs($block) - (length($name) + $baseLength); | |
857 if ($diff < 0) { # too long | |
858 if (abs($diff) >= length($name)) { $name = '' } # forget it | |
859 elsif (abs($diff) + 1 >= length($name)) { $name = substr($name, | |
860 0, 1); } | |
861 else { | |
862 my $middle = exists $abbrevList{$name} ? | |
863 (($abbrevList{$name} + (2*(length($name) / 2)))/3) : | |
864 ((Irssi::settings_get_str('fancy_abbrev') =~ /^head/i) ? | |
865 length($name) : | |
866 (length($name) / 2)); | |
867 my $cut = int($middle - (abs($diff) / 2) + .55); | |
868 $cut = 1 if $cut < 1; | |
869 $cut = length($name) - abs($diff) - 1 if $cut > (length($name) - | |
870 abs($diff) - 1); | |
871 $name = substr($name, 0, $cut) . '~' . substr($name, $cut + | |
872 abs($diff) + 1); | |
873 } | |
874 } | |
875 elsif ($SCREEN_MODE or ($block < 0)) { | |
876 $name .= (' ' x $diff); | |
877 } | |
878 } | |
879 | |
880 my $add = ir_ve(ir_parse_special_protected(sb_ctfe('{sb_background}' . expand($display, | |
881 C => ir_fe($name), | |
882 N => $number . (' 'x($numPad - length($number))), | |
883 Q => ir_fe((' 'x($keyPad - length($keymap{$number}))) . $keymap{$number}), | |
884 H => $hilight, | |
885 S => '}{sb_background}' | |
886 ), 1), $win)); | |
887 if ($SCREEN_MODE) { | |
888 $actString->[$line] = $add; | |
889 if ((!defined $oldActString->[$line] | |
890 or $oldActString->[$line] ne $actString->[$line]) | |
891 and | |
892 $line <= ($columns * $height) | |
893 ) { | |
894 print STDERR $screenansi->dcs(). | |
895 $terminfo->cup(($line-1) % $height+1, $width + ( | |
896 abs($block) * int(($line-1) / $height))). | |
897 formats_to_ansi_basic(sb_expand(ir_escape($actString->[$line]))). | |
898 #$terminfo->el(). | |
899 $screenansi->st(); | |
900 } | |
901 $line++; | |
902 } | |
903 else { | |
904 #$temp =~ s/\{\S+?(?:\s(.*?))?\}/$1/g; | |
905 #$temp =~ s/\\\\\\\\/\\/g; # XXX I'm actually guessing here, someone point me | |
906 # # XXX to docs please | |
907 $actString->[$line] = '' unless defined $actString->[$line]; | |
908 | |
909 # XXX how can I check whether the content still fits in the bar? this would | |
910 # XXX allow awlstatus to reside on a statusbar together with other items... | |
911 if (sb_length(ir_escape($actString->[$line] . $add)) >= $width) { | |
912 # XXX doesn't correctly handle utf-8 multibyte ... help !!? | |
913 $actString->[$line] .= ' ' x ($width - sb_length(ir_escape( | |
914 $actString->[$line]))); | |
915 $line++; | |
916 } | |
917 $actString->[$line] .= $add . $separator; | |
918 # XXX if I use these prints, output layout gets screwed up... why ? | |
919 #Irssi::print("line $line: ".$actString->[$line]); | |
920 #Irssi::print("temp $line: ".$temp); | |
921 } | |
922 } | |
923 | |
924 if ($SCREEN_MODE) { | |
925 while ($line <= ($columns * $height)) { | |
926 print STDERR $screenansi->dcs(). | |
927 $terminfo->cup(($line-1) % $height+1, $width + ( | |
928 abs($block) * int(($line-1) / $height))). | |
929 $terminfo->el(). | |
930 $screenansi->st(); | |
931 $line++; | |
932 } | |
933 print STDERR $screenansi->dcs(). | |
934 $terminfo->rc(). | |
935 $terminfo->cnorm(). | |
936 $screenansi->st(); | |
937 } | |
938 else { | |
939 # XXX the Irssi::print statements lead to the MOST WEIRD results | |
940 # e.g.: the loop gets executed TWICE for p > 0 ?!? | |
941 for (my $p = 0; $p < @$actString; $p++) { # wrap each line in {sb }, escape it | |
942 my $x = $actString->[$p]; # properly, etc. | |
943 $x =~ s/\Q$separator\E([ ]*)$/$1/; | |
944 #Irssi::print("[$p]".'current:'.join'.',split//,sb_strip(ir_escape($x,0))); | |
945 #Irssi::print("assumed length before:".sb_length(ir_escape($x,0))); | |
946 $x = "{sb $x}"; | |
947 #Irssi::print("[$p]".'new:'.join'.',split//,sb_expand(ir_escape($x,0))); | |
948 #Irssi::print("[$p]".'new:'.join'.',split//,ir_escape($x,0)); | |
949 #Irssi::print("assumed length after:".sb_length(ir_escape($x,0))); | |
950 $x = ir_escape($x); | |
951 #Irssi::print("[$p]".'REALnew:'.join'.',split//,sb_strip($x)); | |
952 $actString->[$p] = $x; | |
953 # XXX any Irssi::print debug statement leads to SEGFAULT (sometimes) - why ? | |
954 } | |
955 } | |
956 #Irssi::print("remake [ $xx ] finished"); | |
957 } | |
958 | |
959 sub awlHasChanged () { | |
960 $globTime = undef; | |
961 my $temp = ($SCREEN_MODE ? | |
962 "\\\n" . Irssi::settings_get_int(set 'block'). | |
963 Irssi::settings_get_int(set 'height_adjust') | |
964 : "!\n" . Irssi::settings_get_str(set 'placement'). | |
965 Irssi::settings_get_int(set 'position')). | |
966 Irssi::settings_get_str(set 'automode'); | |
967 if ($temp ne $resetNeeded) { wlreset(); return; } | |
968 #Irssi::print("awl has changed, calls to remake so far: $callcount"); | |
969 $needRemake = 1; | |
970 | |
971 #remake(); | |
972 if ( | |
973 ($SCREEN_MODE and !$DISABLE_SCREEN_TEMP) | |
974 or | |
975 ($needRemake and Irssi::settings_get_bool(set 'all_disable')) | |
976 or | |
977 (!Irssi::settings_get_bool(set 'all_disable') and $currentLines < 1) | |
978 ) { | |
979 $needRemake = undef; | |
980 remake(); | |
981 } | |
982 | |
983 unless ($SCREEN_MODE) { | |
984 # XXX Irssi crashes if I try to do this without timer, why ? What's the minimum | |
985 # XXX delay I need to use in the timer ? | |
986 Irssi::timeout_add_once(100, 'syncLines', undef); | |
987 | |
988 for (keys %statusbars) { | |
989 Irssi::statusbar_items_redraw(set$_); | |
990 } | |
991 } | |
992 else { | |
993 Irssi::timeout_add_once(100, 'syncColumns', undef); | |
994 } | |
995 } | |
996 | |
997 sub eventChanged () { # Implement a change queue/blocker -.-) | |
998 if (defined $globTime) { | |
999 Irssi::timeout_remove($globTime); | |
1000 } # delay the update further | |
1001 $globTime = Irssi::timeout_add_once(GLOB_QUEUE_TIMER, 'awlHasChanged', undef); | |
1002 } | |
1003 | |
1004 # }}} | |
1005 | |
1006 | |
1007 # {{{ screen mode | |
1008 | |
1009 sub screenFullRedraw { | |
1010 my ($window) = @_; | |
1011 if (!ref $window or $window->{'refnum'} == Irssi::active_win->{'refnum'}) { | |
1012 $actString = []; | |
1013 eventChanged(); | |
1014 } | |
1015 } | |
1016 | |
1017 sub screenSize { # from nicklist.pl | |
1018 $screenResizing = 1; | |
1019 # fit screen | |
1020 system 'screen -x '.$ENV{'STY'}.' -X fit'; | |
1021 # get size | |
1022 my ($row, $col) = split ' ', `stty size`; | |
1023 # set screen width | |
1024 $screenWidth = $col-1; | |
1025 $screenHeight = $row-1; | |
1026 | |
1027 # on some recent systems, "screen -X fit; screen -X width -w 50" doesn't work, needs a sleep in between the 2 commands | |
1028 # so we wait a second before setting the width | |
1029 Irssi::timeout_add_once(100, sub { | |
1030 my ($new_irssi_width) = @_; | |
1031 $new_irssi_width -= abs(Irssi::settings_get_int(set | |
1032 'block'))*$currentColumns - 1; | |
1033 system 'screen -x '.$ENV{'STY'}.' -X width -w ' . $new_irssi_width; | |
1034 # and then we wait another second for the resizing, and then redraw. | |
1035 Irssi::timeout_add_once(10,sub {$screenResizing = 0; screenFullRedraw()}, []); | |
1036 }, $screenWidth); | |
1037 } | |
1038 | |
1039 sub screenOff { | |
1040 my ($unloadMode) = @_; | |
1041 Irssi::signal_remove('gui print text finished' => 'screenFullRedraw'); | |
1042 Irssi::signal_remove('gui page scrolled' => 'screenFullRedraw'); | |
1043 Irssi::signal_remove('window changed' => 'screenFullRedraw'); | |
1044 Irssi::signal_remove('window changed automatic' => 'screenFullRedraw'); | |
1045 if ($unloadMode) { | |
1046 Irssi::signal_remove('terminal resized' => 'resizeTerm'); | |
1047 } | |
1048 system 'screen -x '.$ENV{'STY'}.' -X fit'; | |
1049 } | |
1050 | |
1051 sub syncColumns { | |
1052 return if (@$actString == 0); | |
1053 my $temp = $currentColumns; | |
1054 #Irssi::print("current columns $temp"); | |
1055 my $height = $screenHeight - abs(Irssi::settings_get_int(set | |
1056 'height_adjust')); | |
1057 $currentColumns = int(($#$actString-1) / $height) + 1; | |
1058 #Irssi::print("objects in actstring:".scalar(@$actString).", screen height:". | |
1059 # $height); | |
1060 my $currMaxColumns = Irssi::settings_get_int(set 'columns'); | |
1061 if ($currMaxColumns > 0 and $currentColumns > $currMaxColumns) { | |
1062 $currentColumns = $currMaxColumns; | |
1063 } | |
1064 elsif ($currMaxColumns < 0) { | |
1065 $currentColumns = abs($currMaxColumns); | |
1066 } | |
1067 return if ($temp == $currentColumns); | |
1068 screenSize(); | |
1069 } | |
1070 | |
1071 #$needRemake = 1; | |
1072 sub resizeTerm () { | |
1073 if ($SCREEN_MODE and !$screenResizing) { | |
1074 $screenResizing = 1; | |
1075 Irssi::timeout_add_once(10, 'screenSize', undef); | |
1076 } | |
1077 Irssi::timeout_add_once(100, 'eventChanged', undef); | |
1078 } | |
1079 | |
1080 # }}} | |
1081 | |
1082 | |
1083 # {{{ settings add | |
1084 | |
1085 Irssi::settings_add_str(setc, set 'display_nokey', '[$N]$H$C$S'); | |
1086 Irssi::settings_add_str(setc, set 'display_key', '[$Q=$N]$H$C$S'); | |
1087 Irssi::settings_add_str(setc, set 'display_nokey_active', ''); | |
1088 Irssi::settings_add_str(setc, set 'display_key_active', ''); | |
1089 Irssi::settings_add_str(setc, set 'separator', "\\ "); | |
1090 Irssi::settings_add_bool(setc, set 'prefer_name', 0); | |
1091 Irssi::settings_add_int(setc, set 'hide_data', 0); | |
1092 Irssi::settings_add_int(setc, set 'maxlines', 9); | |
1093 Irssi::settings_add_int(setc, set 'columns', 1); | |
1094 Irssi::settings_add_int(setc, set 'block', 20); | |
1095 Irssi::settings_add_bool(setc, set 'sbar_maxlength', 0); | |
1096 Irssi::settings_add_int(setc, set 'height_adjust', 2); | |
1097 Irssi::settings_add_str(setc, set 'sort', 'refnum'); | |
1098 Irssi::settings_add_str(setc, set 'placement', 'bottom'); | |
1099 Irssi::settings_add_int(setc, set 'position', 0); | |
1100 Irssi::settings_add_bool(setc, set 'all_disable', 0); | |
1101 Irssi::settings_add_str(setc, set 'automode', 'sbar'); | |
1102 | |
1103 # }}} | |
1104 | |
1105 | |
1106 # {{{ init | |
1107 | |
1108 sub wlreset { | |
1109 $actString = []; | |
1110 $currentLines = 0; # 1; # mhmmmm .. we actually enable one line down there so | |
1111 # let's try this. | |
1112 #update_keymap(); | |
1113 killOldStatus(); | |
1114 # Register statusbar | |
1115 #add_statusbar(0); | |
1116 #Irssi::command('statusbar wl0 enable'); | |
1117 my $was_screen_mode = $SCREEN_MODE; | |
1118 if ($SCREEN_MODE = (Irssi::settings_get_str(set 'automode') =~ /screen/i) | |
1119 and | |
1120 !$was_screen_mode | |
1121 ) { | |
1122 if (!defined $ENV{'STY'}) { | |
1123 Irssi::print('Screen mode can only be used in GNU screen but no '. | |
1124 'screen was found.', MSGLEVEL_CLIENTERROR); | |
1125 $SCREEN_MODE = undef; | |
1126 } | |
1127 else { | |
1128 Irssi::signal_add_last('gui print text finished' => 'screenFullRedraw'); | |
1129 Irssi::signal_add_last('gui page scrolled' => 'screenFullRedraw'); | |
1130 Irssi::signal_add('window changed' => 'screenFullRedraw'); | |
1131 Irssi::signal_add('window changed automatic' => 'screenFullRedraw'); | |
1132 } | |
1133 } | |
1134 elsif ($was_screen_mode and !$SCREEN_MODE) { | |
1135 screenOff(); | |
1136 } | |
1137 $resetNeeded = ($SCREEN_MODE ? | |
1138 "\\\n" . Irssi::settings_get_int(set 'block'). | |
1139 Irssi::settings_get_int(set 'height_adjust') | |
1140 : "!\n" . Irssi::settings_get_str(set 'placement'). | |
1141 Irssi::settings_get_int(set 'position')). | |
1142 Irssi::settings_get_str(set 'automode'); | |
1143 resizeTerm(); | |
1144 } | |
1145 | |
1146 wlreset(); | |
1147 | |
1148 # }}} | |
1149 | |
1150 | |
1151 # {{{ unload/deinit | |
1152 | |
1153 my $Unload; | |
1154 sub unload ($$$) { | |
1155 $Unload = 1; | |
1156 # pretend we didn't do anything ASAP | |
1157 Irssi::timeout_add_once(10, sub { $Unload = undef; }, undef); | |
1158 } | |
1159 # last try to catch a sigsegv | |
1160 Irssi::signal_add_first('gui exit' => sub { $Unload = undef; }); | |
1161 sub UNLOAD { | |
1162 # this might well crash Irssi... try /eval /script unload someotherscript ; | |
1163 # /quit (= SEGFAULT !) | |
1164 if ($Unload) { | |
1165 $actString = ['']; # syncLines(); # XXX Irssi crashes when trying to disable | |
1166 killOldStatus(); # XXX all statusbars ? | |
1167 if ($SCREEN_MODE) { | |
1168 screenOff('unload mode'); | |
1169 } | |
1170 } | |
1171 } | |
1172 | |
1173 # }}} | |
1174 | |
1175 | |
1176 # {{{ signals | |
1177 | |
1178 sub addPrintTextHook { # update on print text | |
1179 return if $_[0]->{'level'} == 262144 and $_[0]->{'target'} eq '' | |
1180 and !defined($_[0]->{'server'}); | |
1181 if (Irssi::settings_get_str(set 'sort') =~ /^[-!]?last_line$/) { | |
1182 Irssi::timeout_add_once(100, 'eventChanged', undef); | |
1183 } | |
1184 } | |
1185 | |
1186 #sub _x { my ($x, $y) = @_; ($x, sub { Irssi::print('-->signal '.$x); eval "$y();"; }) } | |
1187 #sub _x { @_ } | |
1188 Irssi::signal_add_first( | |
1189 'command script unload' => 'unload' | |
1190 ); | |
1191 Irssi::signal_add_last({ | |
1192 'setup changed' => 'eventChanged', | |
1193 'print text' => 'addPrintTextHook', | |
1194 'terminal resized' => 'resizeTerm', | |
1195 'setup reread' => 'wlreset', | |
1196 'window hilight' => 'eventChanged', | |
1197 }); | |
1198 Irssi::signal_add({ | |
1199 'window created' => 'eventChanged', | |
1200 'window destroyed' => 'eventChanged', | |
1201 'window name changed' => 'eventChanged', | |
1202 'window refnum changed' => 'eventChanged', | |
1203 'window changed' => 'eventChanged', | |
1204 'window changed automatic' => 'eventChanged', | |
1205 }); | |
1206 | |
1207 #Irssi::signal_add('nick mode changed', 'chanactHasChanged'); # relicts | |
1208 | |
1209 # }}} | |
1210 | |
1211 # {{{ commands | |
1212 | |
1213 | |
1214 sub runsub { | |
1215 my ($cmd) = @_; | |
1216 sub { | |
1217 my ($data, $server, $item) = @_; | |
1218 Irssi::command_runsub($cmd, $data, $server, $item); | |
1219 }; | |
1220 } | |
1221 Irssi::command_bind( setc() => runsub(setc()) ); | |
1222 Irssi::command_bind( setc() . ' paste' => runsub(setc() . ' paste') ); | |
1223 Irssi::command_bind( | |
1224 setc() . ' paste on' => sub { | |
1225 return unless $SCREEN_MODE; | |
1226 my $was_disabled = $DISABLE_SCREEN_TEMP; | |
1227 $DISABLE_SCREEN_TEMP = 1; | |
1228 Irssi::print('Paste mode is now ON, '.uc(setc()).' is temporarily '. | |
1229 'disabled.'); | |
1230 if (!$was_disabled) { | |
1231 $screenResizing = 1; | |
1232 screenOff(); | |
1233 } | |
1234 } | |
1235 ); | |
1236 Irssi::command_bind( | |
1237 setc() . ' paste off' => sub { | |
1238 return unless $SCREEN_MODE; | |
1239 my $was_disabled = $DISABLE_SCREEN_TEMP; | |
1240 $DISABLE_SCREEN_TEMP = undef; | |
1241 Irssi::print('Paste mode is now OFF, '.uc(setc()).' is enabled.'); | |
1242 if ($was_disabled) { | |
1243 $SCREEN_MODE = undef; | |
1244 $screenResizing = 0; | |
1245 wlreset(); | |
1246 } | |
1247 } | |
1248 ); | |
1249 Irssi::command_bind( | |
1250 setc() . ' paste toggle' => sub { | |
1251 if ($DISABLE_SCREEN_TEMP) { | |
1252 Irssi::command(setc() . ' paste off'); | |
1253 } | |
1254 else { | |
1255 Irssi::command(setc() . ' paste on'); | |
1256 } | |
1257 } | |
1258 ); | |
1259 Irssi::command_bind( | |
1260 setc() . ' redraw' => sub { | |
1261 return unless $SCREEN_MODE; | |
1262 screenFullRedraw(); | |
1263 } | |
1264 ); | |
1265 | |
1266 | |
1267 # }}} | |
1268 | |
1269 # {{{ Algorithm::LCSS module | |
1270 { | |
1271 package Algorithm::Diff; | |
1272 # Skip to first "=head" line for documentation. | |
1273 use strict; | |
1274 | |
1275 use integer; # see below in _replaceNextLargerWith() for mod to make | |
1276 # if you don't use this | |
1277 | |
1278 # McIlroy-Hunt diff algorithm | |
1279 # Adapted from the Smalltalk code of Mario I. Wolczko, <[email protected]> | |
1280 # by Ned Konz, [email protected] | |
1281 # Updates by Tye McQueen, http://perlmonks.org/?node=tye | |
1282 | |
1283 # Create a hash that maps each element of $aCollection to the set of | |
1284 # positions it occupies in $aCollection, restricted to the elements | |
1285 # within the range of indexes specified by $start and $end. | |
1286 # The fourth parameter is a subroutine reference that will be called to | |
1287 # generate a string to use as a key. | |
1288 # Additional parameters, if any, will be passed to this subroutine. | |
1289 # | |
1290 # my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen ); | |
1291 | |
1292 sub _withPositionsOfInInterval | |
1293 { | |
1294 my $aCollection = shift; # array ref | |
1295 my $start = shift; | |
1296 my $end = shift; | |
1297 my $keyGen = shift; | |
1298 my %d; | |
1299 my $index; | |
1300 for ( $index = $start ; $index <= $end ; $index++ ) | |
1301 { | |
1302 my $element = $aCollection->[$index]; | |
1303 my $key = &$keyGen( $element, @_ ); | |
1304 if ( exists( $d{$key} ) ) | |
1305 { | |
1306 unshift ( @{ $d{$key} }, $index ); | |
1307 } | |
1308 else | |
1309 { | |
1310 $d{$key} = [$index]; | |
1311 } | |
1312 } | |
1313 return wantarray ? %d : \%d; | |
1314 } | |
1315 | |
1316 # Find the place at which aValue would normally be inserted into the | |
1317 # array. If that place is already occupied by aValue, do nothing, and | |
1318 # return undef. If the place does not exist (i.e., it is off the end of | |
1319 # the array), add it to the end, otherwise replace the element at that | |
1320 # point with aValue. It is assumed that the array's values are numeric. | |
1321 # This is where the bulk (75%) of the time is spent in this module, so | |
1322 # try to make it fast! | |
1323 | |
1324 sub _replaceNextLargerWith | |
1325 { | |
1326 my ( $array, $aValue, $high ) = @_; | |
1327 $high ||= $#$array; | |
1328 | |
1329 # off the end? | |
1330 if ( $high == -1 || $aValue > $array->[-1] ) | |
1331 { | |
1332 push ( @$array, $aValue ); | |
1333 return $high + 1; | |
1334 } | |
1335 | |
1336 # binary search for insertion point... | |
1337 my $low = 0; | |
1338 my $index; | |
1339 my $found; | |
1340 while ( $low <= $high ) | |
1341 { | |
1342 $index = ( $high + $low ) / 2; | |
1343 | |
1344 # $index = int(( $high + $low ) / 2); # without 'use integer' | |
1345 $found = $array->[$index]; | |
1346 | |
1347 if ( $aValue == $found ) | |
1348 { | |
1349 return undef; | |
1350 } | |
1351 elsif ( $aValue > $found ) | |
1352 { | |
1353 $low = $index + 1; | |
1354 } | |
1355 else | |
1356 { | |
1357 $high = $index - 1; | |
1358 } | |
1359 } | |
1360 | |
1361 # now insertion point is in $low. | |
1362 $array->[$low] = $aValue; # overwrite next larger | |
1363 return $low; | |
1364 } | |
1365 | |
1366 # This method computes the longest common subsequence in $a and $b. | |
1367 | |
1368 # Result is array or ref, whose contents is such that | |
1369 # $a->[ $i ] == $b->[ $result[ $i ] ] | |
1370 # foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined. | |
1371 | |
1372 # An additional argument may be passed; this is a hash or key generating | |
1373 # function that should return a string that uniquely identifies the given | |
1374 # element. It should be the case that if the key is the same, the elements | |
1375 # will compare the same. If this parameter is undef or missing, the key | |
1376 # will be the element as a string. | |
1377 | |
1378 # By default, comparisons will use "eq" and elements will be turned into keys | |
1379 # using the default stringizing operator '""'. | |
1380 | |
1381 # Additional parameters, if any, will be passed to the key generation | |
1382 # routine. | |
1383 | |
1384 sub _longestCommonSubsequence | |
1385 { | |
1386 my $a = shift; # array ref or hash ref | |
1387 my $b = shift; # array ref or hash ref | |
1388 my $counting = shift; # scalar | |
1389 my $keyGen = shift; # code ref | |
1390 my $compare; # code ref | |
1391 | |
1392 if ( ref($a) eq 'HASH' ) | |
1393 { # prepared hash must be in $b | |
1394 my $tmp = $b; | |
1395 $b = $a; | |
1396 $a = $tmp; | |
1397 } | |
1398 | |
1399 # Check for bogus (non-ref) argument values | |
1400 if ( !ref($a) || !ref($b) ) | |
1401 { | |
1402 my @callerInfo = caller(1); | |
1403 die 'error: must pass array or hash references to ' . $callerInfo[3]; | |
1404 } | |
1405 | |
1406 # set up code refs | |
1407 # Note that these are optimized. | |
1408 if ( !defined($keyGen) ) # optimize for strings | |
1409 { | |
1410 $keyGen = sub { $_[0] }; | |
1411 $compare = sub { my ( $a, $b ) = @_; $a eq $b }; | |
1412 } | |
1413 else | |
1414 { | |
1415 $compare = sub { | |
1416 my $a = shift; | |
1417 my $b = shift; | |
1418 &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ ); | |
1419 }; | |
1420 } | |
1421 | |
1422 my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] ); | |
1423 my ( $prunedCount, $bMatches ) = ( 0, {} ); | |
1424 | |
1425 if ( ref($b) eq 'HASH' ) # was $bMatches prepared for us? | |
1426 { | |
1427 $bMatches = $b; | |
1428 } | |
1429 else | |
1430 { | |
1431 my ( $bStart, $bFinish ) = ( 0, $#$b ); | |
1432 | |
1433 # First we prune off any common elements at the beginning | |
1434 while ( $aStart <= $aFinish | |
1435 and $bStart <= $bFinish | |
1436 and &$compare( $a->[$aStart], $b->[$bStart], @_ ) ) | |
1437 { | |
1438 $matchVector->[ $aStart++ ] = $bStart++; | |
1439 $prunedCount++; | |
1440 } | |
1441 | |
1442 # now the end | |
1443 while ( $aStart <= $aFinish | |
1444 and $bStart <= $bFinish | |
1445 and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) ) | |
1446 { | |
1447 $matchVector->[ $aFinish-- ] = $bFinish--; | |
1448 $prunedCount++; | |
1449 } | |
1450 | |
1451 # Now compute the equivalence classes of positions of elements | |
1452 $bMatches = | |
1453 _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ ); | |
1454 } | |
1455 my $thresh = []; | |
1456 my $links = []; | |
1457 | |
1458 my ( $i, $ai, $j, $k ); | |
1459 for ( $i = $aStart ; $i <= $aFinish ; $i++ ) | |
1460 { | |
1461 $ai = &$keyGen( $a->[$i], @_ ); | |
1462 if ( exists( $bMatches->{$ai} ) ) | |
1463 { | |
1464 $k = 0; | |
1465 for $j ( @{ $bMatches->{$ai} } ) | |
1466 { | |
1467 | |
1468 # optimization: most of the time this will be true | |
1469 if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j ) | |
1470 { | |
1471 $thresh->[$k] = $j; | |
1472 } | |
1473 else | |
1474 { | |
1475 $k = _replaceNextLargerWith( $thresh, $j, $k ); | |
1476 } | |
1477 | |
1478 # oddly, it's faster to always test this (CPU cache?). | |
1479 if ( defined($k) ) | |
1480 { | |
1481 $links->[$k] = | |
1482 [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ]; | |
1483 } | |
1484 } | |
1485 } | |
1486 } | |
1487 | |
1488 if (@$thresh) | |
1489 { | |
1490 return $prunedCount + @$thresh if $counting; | |
1491 for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] ) | |
1492 { | |
1493 $matchVector->[ $link->[1] ] = $link->[2]; | |
1494 } | |
1495 } | |
1496 elsif ($counting) | |
1497 { | |
1498 return $prunedCount; | |
1499 } | |
1500 | |
1501 return wantarray ? @$matchVector : $matchVector; | |
1502 } | |
1503 | |
1504 sub traverse_sequences | |
1505 { | |
1506 my $a = shift; # array ref | |
1507 my $b = shift; # array ref | |
1508 my $callbacks = shift || {}; | |
1509 my $keyGen = shift; | |
1510 my $matchCallback = $callbacks->{'MATCH'} || sub { }; | |
1511 my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; | |
1512 my $finishedACallback = $callbacks->{'A_FINISHED'}; | |
1513 my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; | |
1514 my $finishedBCallback = $callbacks->{'B_FINISHED'}; | |
1515 my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); | |
1516 | |
1517 # Process all the lines in @$matchVector | |
1518 my $lastA = $#$a; | |
1519 my $lastB = $#$b; | |
1520 my $bi = 0; | |
1521 my $ai; | |
1522 | |
1523 for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ ) | |
1524 { | |
1525 my $bLine = $matchVector->[$ai]; | |
1526 if ( defined($bLine) ) # matched | |
1527 { | |
1528 &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine; | |
1529 &$matchCallback( $ai, $bi++, @_ ); | |
1530 } | |
1531 else | |
1532 { | |
1533 &$discardACallback( $ai, $bi, @_ ); | |
1534 } | |
1535 } | |
1536 | |
1537 # The last entry (if any) processed was a match. | |
1538 # $ai and $bi point just past the last matching lines in their sequences. | |
1539 | |
1540 while ( $ai <= $lastA or $bi <= $lastB ) | |
1541 { | |
1542 | |
1543 # last A? | |
1544 if ( $ai == $lastA + 1 and $bi <= $lastB ) | |
1545 { | |
1546 if ( defined($finishedACallback) ) | |
1547 { | |
1548 &$finishedACallback( $lastA, @_ ); | |
1549 $finishedACallback = undef; | |
1550 } | |
1551 else | |
1552 { | |
1553 &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB; | |
1554 } | |
1555 } | |
1556 | |
1557 # last B? | |
1558 if ( $bi == $lastB + 1 and $ai <= $lastA ) | |
1559 { | |
1560 if ( defined($finishedBCallback) ) | |
1561 { | |
1562 &$finishedBCallback( $lastB, @_ ); | |
1563 $finishedBCallback = undef; | |
1564 } | |
1565 else | |
1566 { | |
1567 &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA; | |
1568 } | |
1569 } | |
1570 | |
1571 &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA; | |
1572 &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB; | |
1573 } | |
1574 | |
1575 return 1; | |
1576 } | |
1577 | |
1578 sub traverse_balanced | |
1579 { | |
1580 my $a = shift; # array ref | |
1581 my $b = shift; # array ref | |
1582 my $callbacks = shift || {}; | |
1583 my $keyGen = shift; | |
1584 my $matchCallback = $callbacks->{'MATCH'} || sub { }; | |
1585 my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; | |
1586 my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; | |
1587 my $changeCallback = $callbacks->{'CHANGE'}; | |
1588 my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); | |
1589 | |
1590 # Process all the lines in match vector | |
1591 my $lastA = $#$a; | |
1592 my $lastB = $#$b; | |
1593 my $bi = 0; | |
1594 my $ai = 0; | |
1595 my $ma = -1; | |
1596 my $mb; | |
1597 | |
1598 while (1) | |
1599 { | |
1600 | |
1601 # Find next match indices $ma and $mb | |
1602 do { | |
1603 $ma++; | |
1604 } while( | |
1605 $ma <= $#$matchVector | |
1606 && !defined $matchVector->[$ma] | |
1607 ); | |
1608 | |
1609 last if $ma > $#$matchVector; # end of matchVector? | |
1610 $mb = $matchVector->[$ma]; | |
1611 | |
1612 # Proceed with discard a/b or change events until | |
1613 # next match | |
1614 while ( $ai < $ma || $bi < $mb ) | |
1615 { | |
1616 | |
1617 if ( $ai < $ma && $bi < $mb ) | |
1618 { | |
1619 | |
1620 # Change | |
1621 if ( defined $changeCallback ) | |
1622 { | |
1623 &$changeCallback( $ai++, $bi++, @_ ); | |
1624 } | |
1625 else | |
1626 { | |
1627 &$discardACallback( $ai++, $bi, @_ ); | |
1628 &$discardBCallback( $ai, $bi++, @_ ); | |
1629 } | |
1630 } | |
1631 elsif ( $ai < $ma ) | |
1632 { | |
1633 &$discardACallback( $ai++, $bi, @_ ); | |
1634 } | |
1635 else | |
1636 { | |
1637 | |
1638 # $bi < $mb | |
1639 &$discardBCallback( $ai, $bi++, @_ ); | |
1640 } | |
1641 } | |
1642 | |
1643 # Match | |
1644 &$matchCallback( $ai++, $bi++, @_ ); | |
1645 } | |
1646 | |
1647 while ( $ai <= $lastA || $bi <= $lastB ) | |
1648 { | |
1649 if ( $ai <= $lastA && $bi <= $lastB ) | |
1650 { | |
1651 | |
1652 # Change | |
1653 if ( defined $changeCallback ) | |
1654 { | |
1655 &$changeCallback( $ai++, $bi++, @_ ); | |
1656 } | |
1657 else | |
1658 { | |
1659 &$discardACallback( $ai++, $bi, @_ ); | |
1660 &$discardBCallback( $ai, $bi++, @_ ); | |
1661 } | |
1662 } | |
1663 elsif ( $ai <= $lastA ) | |
1664 { | |
1665 &$discardACallback( $ai++, $bi, @_ ); | |
1666 } | |
1667 else | |
1668 { | |
1669 | |
1670 # $bi <= $lastB | |
1671 &$discardBCallback( $ai, $bi++, @_ ); | |
1672 } | |
1673 } | |
1674 | |
1675 return 1; | |
1676 } | |
1677 | |
1678 sub prepare | |
1679 { | |
1680 my $a = shift; # array ref | |
1681 my $keyGen = shift; # code ref | |
1682 | |
1683 # set up code ref | |
1684 $keyGen = sub { $_[0] } unless defined($keyGen); | |
1685 | |
1686 return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ ); | |
1687 } | |
1688 | |
1689 sub LCS | |
1690 { | |
1691 my $a = shift; # array ref | |
1692 my $b = shift; # array ref or hash ref | |
1693 my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ ); | |
1694 my @retval; | |
1695 my $i; | |
1696 for ( $i = 0 ; $i <= $#$matchVector ; $i++ ) | |
1697 { | |
1698 if ( defined( $matchVector->[$i] ) ) | |
1699 { | |
1700 push ( @retval, $a->[$i] ); | |
1701 } | |
1702 } | |
1703 return wantarray ? @retval : \@retval; | |
1704 } | |
1705 | |
1706 sub LCS_length | |
1707 { | |
1708 my $a = shift; # array ref | |
1709 my $b = shift; # array ref or hash ref | |
1710 return _longestCommonSubsequence( $a, $b, 1, @_ ); | |
1711 } | |
1712 | |
1713 sub LCSidx | |
1714 { | |
1715 my $a= shift @_; | |
1716 my $b= shift @_; | |
1717 my $match= _longestCommonSubsequence( $a, $b, 0, @_ ); | |
1718 my @am= grep defined $match->[$_], 0..$#$match; | |
1719 my @bm= @{$match}[@am]; | |
1720 return \@am, \@bm; | |
1721 } | |
1722 | |
1723 sub compact_diff | |
1724 { | |
1725 my $a= shift @_; | |
1726 my $b= shift @_; | |
1727 my( $am, $bm )= LCSidx( $a, $b, @_ ); | |
1728 my @cdiff; | |
1729 my( $ai, $bi )= ( 0, 0 ); | |
1730 push @cdiff, $ai, $bi; | |
1731 while( 1 ) { | |
1732 while( @$am && $ai == $am->[0] && $bi == $bm->[0] ) { | |
1733 shift @$am; | |
1734 shift @$bm; | |
1735 ++$ai, ++$bi; | |
1736 } | |
1737 push @cdiff, $ai, $bi; | |
1738 last if ! @$am; | |
1739 $ai = $am->[0]; | |
1740 $bi = $bm->[0]; | |
1741 push @cdiff, $ai, $bi; | |
1742 } | |
1743 push @cdiff, 0+@$a, 0+@$b | |
1744 if $ai < @$a || $bi < @$b; | |
1745 return wantarray ? @cdiff : \@cdiff; | |
1746 } | |
1747 | |
1748 sub diff | |
1749 { | |
1750 my $a = shift; # array ref | |
1751 my $b = shift; # array ref | |
1752 my $retval = []; | |
1753 my $hunk = []; | |
1754 my $discard = sub { | |
1755 push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ]; | |
1756 }; | |
1757 my $add = sub { | |
1758 push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ]; | |
1759 }; | |
1760 my $match = sub { | |
1761 push @$retval, $hunk | |
1762 if 0 < @$hunk; | |
1763 $hunk = [] | |
1764 }; | |
1765 traverse_sequences( $a, $b, | |
1766 { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ ); | |
1767 &$match(); | |
1768 return wantarray ? @$retval : $retval; | |
1769 } | |
1770 | |
1771 sub sdiff | |
1772 { | |
1773 my $a = shift; # array ref | |
1774 my $b = shift; # array ref | |
1775 my $retval = []; | |
1776 my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) }; | |
1777 my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) }; | |
1778 my $change = sub { | |
1779 push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] ); | |
1780 }; | |
1781 my $match = sub { | |
1782 push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] ); | |
1783 }; | |
1784 traverse_balanced( | |
1785 $a, | |
1786 $b, | |
1787 { | |
1788 MATCH => $match, | |
1789 DISCARD_A => $discard, | |
1790 DISCARD_B => $add, | |
1791 CHANGE => $change, | |
1792 }, | |
1793 @_ | |
1794 ); | |
1795 return wantarray ? @$retval : $retval; | |
1796 } | |
1797 | |
1798 ######################################## | |
1799 my $Root= __PACKAGE__; | |
1800 package Algorithm::Diff::_impl; | |
1801 use strict; | |
1802 | |
1803 sub _Idx() { 0 } # $me->[_Idx]: Ref to array of hunk indices | |
1804 # 1 # $me->[1]: Ref to first sequence | |
1805 # 2 # $me->[2]: Ref to second sequence | |
1806 sub _End() { 3 } # $me->[_End]: Diff between forward and reverse pos | |
1807 sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items | |
1808 sub _Base() { 5 } # $me->[_Base]: Added to range's min and max | |
1809 sub _Pos() { 6 } # $me->[_Pos]: Which hunk is currently selected | |
1810 sub _Off() { 7 } # $me->[_Off]: Offset into _Idx for current position | |
1811 sub _Min() { -2 } # Added to _Off to get min instead of max+1 | |
1812 | |
1813 sub Die | |
1814 { | |
1815 require Carp; | |
1816 Carp::confess( @_ ); | |
1817 } | |
1818 | |
1819 sub _ChkPos | |
1820 { | |
1821 my( $me )= @_; | |
1822 return if $me->[_Pos]; | |
1823 my $meth= ( caller(1) )[3]; | |
1824 Die( "Called $meth on 'reset' object" ); | |
1825 } | |
1826 | |
1827 sub _ChkSeq | |
1828 { | |
1829 my( $me, $seq )= @_; | |
1830 return $seq + $me->[_Off] | |
1831 if 1 == $seq || 2 == $seq; | |
1832 my $meth= ( caller(1) )[3]; | |
1833 Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" ); | |
1834 } | |
1835 | |
1836 sub getObjPkg | |
1837 { | |
1838 my( $us )= @_; | |
1839 return ref $us if ref $us; | |
1840 return $us . "::_obj"; | |
1841 } | |
1842 | |
1843 sub new | |
1844 { | |
1845 my( $us, $seq1, $seq2, $opts ) = @_; | |
1846 my @args; | |
1847 for( $opts->{keyGen} ) { | |
1848 push @args, $_ if $_; | |
1849 } | |
1850 for( $opts->{keyGenArgs} ) { | |
1851 push @args, @$_ if $_; | |
1852 } | |
1853 my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args ); | |
1854 my $same= 1; | |
1855 if( 0 == $cdif->[2] && 0 == $cdif->[3] ) { | |
1856 $same= 0; | |
1857 splice @$cdif, 0, 2; | |
1858 } | |
1859 my @obj= ( $cdif, $seq1, $seq2 ); | |
1860 $obj[_End] = (1+@$cdif)/2; | |
1861 $obj[_Same] = $same; | |
1862 $obj[_Base] = 0; | |
1863 my $me = bless \@obj, $us->getObjPkg(); | |
1864 $me->Reset( 0 ); | |
1865 return $me; | |
1866 } | |
1867 | |
1868 sub Reset | |
1869 { | |
1870 my( $me, $pos )= @_; | |
1871 $pos= int( $pos || 0 ); | |
1872 $pos += $me->[_End] | |
1873 if $pos < 0; | |
1874 $pos= 0 | |
1875 if $pos < 0 || $me->[_End] <= $pos; | |
1876 $me->[_Pos]= $pos || !1; | |
1877 $me->[_Off]= 2*$pos - 1; | |
1878 return $me; | |
1879 } | |
1880 | |
1881 sub Base | |
1882 { | |
1883 my( $me, $base )= @_; | |
1884 my $oldBase= $me->[_Base]; | |
1885 $me->[_Base]= 0+$base if defined $base; | |
1886 return $oldBase; | |
1887 } | |
1888 | |
1889 sub Copy | |
1890 { | |
1891 my( $me, $pos, $base )= @_; | |
1892 my @obj= @$me; | |
1893 my $you= bless \@obj, ref($me); | |
1894 $you->Reset( $pos ) if defined $pos; | |
1895 $you->Base( $base ); | |
1896 return $you; | |
1897 } | |
1898 | |
1899 sub Next { | |
1900 my( $me, $steps )= @_; | |
1901 $steps= 1 if ! defined $steps; | |
1902 if( $steps ) { | |
1903 my $pos= $me->[_Pos]; | |
1904 my $new= $pos + $steps; | |
1905 $new= 0 if $pos && $new < 0; | |
1906 $me->Reset( $new ) | |
1907 } | |
1908 return $me->[_Pos]; | |
1909 } | |
1910 | |
1911 sub Prev { | |
1912 my( $me, $steps )= @_; | |
1913 $steps= 1 if ! defined $steps; | |
1914 my $pos= $me->Next(-$steps); | |
1915 $pos -= $me->[_End] if $pos; | |
1916 return $pos; | |
1917 } | |
1918 | |
1919 sub Diff { | |
1920 my( $me )= @_; | |
1921 $me->_ChkPos(); | |
1922 return 0 if $me->[_Same] == ( 1 & $me->[_Pos] ); | |
1923 my $ret= 0; | |
1924 my $off= $me->[_Off]; | |
1925 for my $seq ( 1, 2 ) { | |
1926 $ret |= $seq | |
1927 if $me->[_Idx][ $off + $seq + _Min ] | |
1928 < $me->[_Idx][ $off + $seq ]; | |
1929 } | |
1930 return $ret; | |
1931 } | |
1932 | |
1933 sub Min { | |
1934 my( $me, $seq, $base )= @_; | |
1935 $me->_ChkPos(); | |
1936 my $off= $me->_ChkSeq($seq); | |
1937 $base= $me->[_Base] if !defined $base; | |
1938 return $base + $me->[_Idx][ $off + _Min ]; | |
1939 } | |
1940 | |
1941 sub Max { | |
1942 my( $me, $seq, $base )= @_; | |
1943 $me->_ChkPos(); | |
1944 my $off= $me->_ChkSeq($seq); | |
1945 $base= $me->[_Base] if !defined $base; | |
1946 return $base + $me->[_Idx][ $off ] -1; | |
1947 } | |
1948 | |
1949 sub Range { | |
1950 my( $me, $seq, $base )= @_; | |
1951 $me->_ChkPos(); | |
1952 my $off = $me->_ChkSeq($seq); | |
1953 if( !wantarray ) { | |
1954 return $me->[_Idx][ $off ] | |
1955 - $me->[_Idx][ $off + _Min ]; | |
1956 } | |
1957 $base= $me->[_Base] if !defined $base; | |
1958 return ( $base + $me->[_Idx][ $off + _Min ] ) | |
1959 .. ( $base + $me->[_Idx][ $off ] - 1 ); | |
1960 } | |
1961 | |
1962 sub Items { | |
1963 my( $me, $seq )= @_; | |
1964 $me->_ChkPos(); | |
1965 my $off = $me->_ChkSeq($seq); | |
1966 if( !wantarray ) { | |
1967 return $me->[_Idx][ $off ] | |
1968 - $me->[_Idx][ $off + _Min ]; | |
1969 } | |
1970 return | |
1971 @{$me->[$seq]}[ | |
1972 $me->[_Idx][ $off + _Min ] | |
1973 .. ( $me->[_Idx][ $off ] - 1 ) | |
1974 ]; | |
1975 } | |
1976 | |
1977 sub Same { | |
1978 my( $me )= @_; | |
1979 $me->_ChkPos(); | |
1980 return wantarray ? () : 0 | |
1981 if $me->[_Same] != ( 1 & $me->[_Pos] ); | |
1982 return $me->Items(1); | |
1983 } | |
1984 | |
1985 my %getName; | |
1986 %getName= ( | |
1987 same => \&Same, | |
1988 diff => \&Diff, | |
1989 base => \&Base, | |
1990 min => \&Min, | |
1991 max => \&Max, | |
1992 range=> \&Range, | |
1993 items=> \&Items, # same thing | |
1994 ); | |
1995 | |
1996 sub Get | |
1997 { | |
1998 my $me= shift @_; | |
1999 $me->_ChkPos(); | |
2000 my @value; | |
2001 for my $arg ( @_ ) { | |
2002 for my $word ( split ' ', $arg ) { | |
2003 my $meth; | |
2004 if( $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/ | |
2005 || not $meth= $getName{ lc $2 } | |
2006 ) { | |
2007 Die( $Root, ", Get: Invalid request ($word)" ); | |
2008 } | |
2009 my( $base, $name, $seq )= ( $1, $2, $3 ); | |
2010 push @value, scalar( | |
2011 4 == length($name) | |
2012 ? $meth->( $me ) | |
2013 : $meth->( $me, $seq, $base ) | |
2014 ); | |
2015 } | |
2016 } | |
2017 if( wantarray ) { | |
2018 return @value; | |
2019 } elsif( 1 == @value ) { | |
2020 return $value[0]; | |
2021 } | |
2022 Die( 0+@value, " values requested from ", | |
2023 $Root, "'s Get in scalar context" ); | |
2024 } | |
2025 | |
2026 | |
2027 my $Obj= getObjPkg($Root); | |
2028 no strict 'refs'; | |
2029 | |
2030 for my $meth ( qw( new getObjPkg ) ) { | |
2031 *{$Root."::".$meth} = \&{$meth}; | |
2032 *{$Obj ."::".$meth} = \&{$meth}; | |
2033 } | |
2034 for my $meth ( qw( | |
2035 Next Prev Reset Copy Base Diff | |
2036 Same Items Range Min Max Get | |
2037 _ChkPos _ChkSeq | |
2038 ) ) { | |
2039 *{$Obj."::".$meth} = \&{$meth}; | |
2040 } | |
2041 | |
2042 }; | |
2043 { | |
2044 package Algorithm::LCSS; | |
2045 | |
2046 use strict; | |
2047 { | |
2048 no strict 'refs'; | |
2049 *traverse_sequences = \&Algorithm::Diff::traverse_sequences; | |
2050 } | |
2051 | |
2052 sub _tokenize { [split //, $_[0]] } | |
2053 | |
2054 sub CSS { | |
2055 my $is_array = ref $_[0] eq 'ARRAY' ? 1 : 0; | |
2056 my ( $seq1, $seq2, @match, $from_match ); | |
2057 my $i = 0; | |
2058 if ( $is_array ) { | |
2059 $seq1 = $_[0]; | |
2060 $seq2 = $_[1]; | |
2061 traverse_sequences( $seq1, $seq2, { | |
2062 MATCH => sub { push @{$match[$i]}, $seq1->[$_[0]]; $from_match = 1 }, | |
2063 DISCARD_A => sub { do{$i++; $from_match = 0} if $from_match }, | |
2064 DISCARD_B => sub { do{$i++; $from_match = 0} if $from_match }, | |
2065 }); | |
2066 } | |
2067 else { | |
2068 $seq1 = _tokenize($_[0]); | |
2069 $seq2 = _tokenize($_[1]); | |
2070 traverse_sequences( $seq1, $seq2, { | |
2071 MATCH => sub { $match[$i] .= $seq1->[$_[0]]; $from_match = 1 }, | |
2072 DISCARD_A => sub { do{$i++; $from_match = 0} if $from_match }, | |
2073 DISCARD_B => sub { do{$i++; $from_match = 0} if $from_match }, | |
2074 }); | |
2075 } | |
2076 return \@match; | |
2077 } | |
2078 | |
2079 sub CSS_Sorted { | |
2080 my $match = CSS(@_); | |
2081 if ( ref $_[0] eq 'ARRAY' ) { | |
2082 @$match = map{$_->[0]}sort{$b->[1]<=>$a->[1]}map{[$_,scalar(@$_)]}@$match | |
2083 } | |
2084 else { | |
2085 @$match = map{$_->[0]}sort{$b->[1]<=>$a->[1]}map{[$_,length($_)]}@$match | |
2086 } | |
2087 return $match; | |
2088 } | |
2089 | |
2090 sub LCSS { | |
2091 my $is_array = ref $_[0] eq 'ARRAY' ? 1 : 0; | |
2092 my $css = CSS(@_); | |
2093 my $index; | |
2094 my $length = 0; | |
2095 if ( $is_array ) { | |
2096 for( my $i = 0; $i < @$css; $i++ ) { | |
2097 next unless @{$css->[$i]}>$length; | |
2098 $index = $i; | |
2099 $length = @{$css->[$i]}; | |
2100 } | |
2101 } | |
2102 else { | |
2103 for( my $i = 0; $i < @$css; $i++ ) { | |
2104 next unless length($css->[$i])>$length; | |
2105 $index = $i; | |
2106 $length = length($css->[$i]); | |
2107 } | |
2108 } | |
2109 return $css->[$index]; | |
2110 } | |
2111 | |
2112 }; | |
2113 # }}} | |
2114 #{{{ Class::Classless module | |
2115 { | |
2116 package Class::Classless; | |
2117 use strict; | |
2118 use vars qw(@ISA); | |
2119 use Carp; | |
2120 | |
2121 @ISA = (); | |
2122 | |
2123 ########################################################################### | |
2124 | |
2125 @Class::Classless::X::ISA = (); | |
2126 | |
2127 ########################################################################### | |
2128 ########################################################################### | |
2129 | |
2130 sub Class::Classless::X::AUTOLOAD { | |
2131 # This's the big dispatcher. | |
2132 | |
2133 my $it = shift @_; | |
2134 my $m = ($Class::Classless::X::AUTOLOAD =~ m/([^:]+)$/s ) | |
2135 ? $1 : $Class::Classless::X::AUTOLOAD; | |
2136 | |
2137 croak "Can't call Class::Classless methods (like $m) without an object" | |
2138 unless ref $it; # sanity, basically. | |
2139 | |
2140 my $prevstate; | |
2141 $prevstate = ${shift @_} | |
2142 if scalar(@_) && defined($_[0]) && | |
2143 ref($_[0]) eq 'Class::Classless::CALLSTATE::SHIMMY' | |
2144 ; # A shim! we were called via $callstate->NEXT | |
2145 | |
2146 my $no_fail = $prevstate ? $prevstate->[3] : undef; | |
2147 my $i = $prevstate ? ($prevstate->[1] + 1) : 0; | |
2148 # where to start scanning | |
2149 my $lineage; | |
2150 | |
2151 # Get the linearization of the ISA tree | |
2152 if($prevstate) { | |
2153 $lineage = $prevstate->[2]; | |
2154 } elsif(defined $it->{'ISA_CACHE'} and ref $it->{'ISA_CACHE'} ){ | |
2155 $lineage = $it->{'ISA_CACHE'}; | |
2156 } else { | |
2157 $lineage = [ &Class::Classless::X::ISA_TREE($it) ]; | |
2158 } | |
2159 | |
2160 # Was: | |
2161 #my @lineage = | |
2162 # $prevstate ? @{$prevstate->[2]} | |
2163 # : &Class::Classless::X::ISA_TREE($it); | |
2164 # # Get the linearization of the ISA tree | |
2165 # # ISA-memoization happens in the ISA_TREE function. | |
2166 | |
2167 for(; $i < @$lineage; ++$i) { | |
2168 | |
2169 if( !defined($no_fail) and exists($lineage->[$i]{'NO_FAIL'}) ) { | |
2170 $no_fail = ($lineage->[$i]{'NO_FAIL'} || 0); | |
2171 # so the first NO_FAIL sets it | |
2172 } | |
2173 | |
2174 if( ref($lineage->[$i]{'METHODS'} || 0) # sanity | |
2175 && exists($lineage->[$i]{'METHODS'}{$m}) | |
2176 ){ | |
2177 # We found what we were after. Now see what to do with it. | |
2178 my $v = $lineage->[$i]{'METHODS'}{$m}; | |
2179 return $v unless defined $v and ref $v; | |
2180 | |
2181 if(ref($v) eq 'CODE') { # normal case, I expect! | |
2182 # Used to have copying of the arglist here. | |
2183 # But it was apparently useless, so I deleted it | |
2184 unshift @_, | |
2185 $it, # $_[0] -- target object | |
2186 # a NEW callstate | |
2187 bless([$m, $i, $lineage, $no_fail, $prevstate ? 1 : 0], | |
2188 'Class::Classless::CALLSTATE' | |
2189 ), # $_[1] -- the callstate | |
2190 ; | |
2191 goto &{ $v }; # yes, magic goto! bimskalabim! | |
2192 } | |
2193 return @$v if ref($v) eq '_deref_array'; | |
2194 return $$v if ref($v) eq '_deref_scalar'; | |
2195 return $v; # fallthru | |
2196 } | |
2197 } | |
2198 | |
2199 if($m eq 'DESTROY') { # mitigate DESTROY-lookup failure at global destruction | |
2200 # should be impossible | |
2201 } else { | |
2202 if($no_fail || 0) { | |
2203 return; | |
2204 } | |
2205 croak "Can't find ", $prevstate ? 'NEXT method' : 'method', | |
2206 " $m in ", $it->{'NAME'} || $it, | |
2207 " or any ancestors\n"; | |
2208 } | |
2209 } | |
2210 | |
2211 ########################################################################### | |
2212 ########################################################################### | |
2213 | |
2214 sub Class::Classless::X::DESTROY { | |
2215 # noop | |
2216 } | |
2217 | |
2218 ########################################################################### | |
2219 sub Class::Classless::X::ISA_TREE { | |
2220 # The linearizer! | |
2221 # Returns the search path for $_[0], starting with $_[0] | |
2222 # Possibly memoized. | |
2223 | |
2224 # I stopped being able to understand this algorithm about five | |
2225 # minutes after I wrote it. | |
2226 use strict; | |
2227 | |
2228 my $set_cache = 0; # flag to set the cache on the way out | |
2229 | |
2230 if(exists($_[0]{'ISA_CACHE'})) { | |
2231 return @{$_[0]{'ISA_CACHE'}} | |
2232 if defined $_[0]{'ISA_CACHE'} | |
2233 and ref $_[0]{'ISA_CACHE'}; | |
2234 | |
2235 # Otherwise, if exists but is not a ref, it's a signal that it should | |
2236 # be replaced at the earliest, with a listref | |
2237 $set_cache = 1; | |
2238 } | |
2239 | |
2240 my $has_mi = 0; # set to 0 on the first node we see with 2 parents! | |
2241 # First, just figure out what's in the tree. | |
2242 my %last_child = ($_[0] => 1); # as if already seen | |
2243 | |
2244 # if $last_child{$x} == $y, that means: | |
2245 # 1) incidentally, we've passed the node $x before. | |
2246 # 2) $x is the last child of $y, | |
2247 # so that means that $y can be pushed to the stack only after | |
2248 # we've pushed $x to the stack. | |
2249 | |
2250 my @tree_nodes; | |
2251 { | |
2252 my $current; | |
2253 my @in_stack = ($_[0]); | |
2254 while(@in_stack) { | |
2255 next unless | |
2256 defined($current = shift @in_stack) | |
2257 && ref($current) # sanity | |
2258 && ref($current->{'PARENTS'} || 0) # sanity | |
2259 ; | |
2260 | |
2261 push @tree_nodes, $current; | |
2262 | |
2263 $has_mi = 1 if @{$current->{'PARENTS'}} > 1; | |
2264 unshift | |
2265 @in_stack, | |
2266 map { | |
2267 if(exists $last_child{$_}) { # seen before! | |
2268 $last_child{$_} = $current; | |
2269 (); # seen -- don't re-explore | |
2270 } else { # first time seen | |
2271 $last_child{$_} = $current; | |
2272 $_; # first time seen -- explore now | |
2273 } | |
2274 } | |
2275 @{$current->{'PARENTS'}} | |
2276 ; | |
2277 } | |
2278 | |
2279 # If there was no MI, then that first scan was sufficient. | |
2280 unless($has_mi) { | |
2281 $_[0]{'ISA_CACHE'} = \@tree_nodes if $set_cache; | |
2282 return @tree_nodes; | |
2283 } | |
2284 | |
2285 # Otherwise, toss this list and rescan, consulting %last_child | |
2286 } | |
2287 | |
2288 # $last_child{$parent} holds the last (or only) child of $parent | |
2289 # in this tree. When walking the tree this time, only that | |
2290 # child is authorized to put its parent on the @in_stack. | |
2291 # And that's the only way a node can get added to @in_stack, | |
2292 # except for $_[0] (the start node) being there at the beginning. | |
2293 | |
2294 # Now, walk again, but this time exploring parents the LAST | |
2295 # time seen in the tree, not the first. | |
2296 | |
2297 my @out; | |
2298 { | |
2299 my $current; | |
2300 my @in_stack = ($_[0]); | |
2301 while(@in_stack) { | |
2302 next unless defined($current = shift @in_stack) && ref($current); | |
2303 push @out, $current; # finally. | |
2304 unshift | |
2305 @in_stack, | |
2306 grep( | |
2307 ( | |
2308 defined($_) # sanity | |
2309 && ref($_) # sanity | |
2310 && $last_child{$_} eq $current, | |
2311 ), | |
2312 # I'm lastborn (or onlyborn) of this parent | |
2313 # so it's OK to explore now | |
2314 @{$current->{'PARENTS'}} | |
2315 ) | |
2316 if ref($current->{'PARENTS'} || 0) # sanity | |
2317 ; | |
2318 } | |
2319 | |
2320 unless(scalar(@out) == scalar(keys(%last_child))) { | |
2321 # the counts should be equal | |
2322 my %good_ones; | |
2323 @good_ones{@out} = (); | |
2324 croak | |
2325 "ISA tree for " . | |
2326 ($_[0]{'NAME'} || $_[0]) . | |
2327 " is apparently cyclic, probably involving the nodes " . | |
2328 nodelist( grep { ref($_) && !exists $good_ones{$_} } | |
2329 values(%last_child) ) | |
2330 . "\n"; | |
2331 } | |
2332 } | |
2333 #print "Contents of out: ", nodelist(@out), "\n"; | |
2334 | |
2335 $_[0]{'ISA_CACHE'} = \@out if $set_cache; | |
2336 return @out; | |
2337 } | |
2338 | |
2339 ########################################################################### | |
2340 | |
2341 sub Class::Classless::X::can { # NOT like UNIVERSAL::can ... | |
2342 # return 1 if $it is capable of the method given -- otherwise 0 | |
2343 my($it, $m) = @_[0,1]; | |
2344 return undef unless ref $it; | |
2345 | |
2346 croak "undef is not a valid method name" unless defined($m); | |
2347 croak "null-string is not a valid method name" unless length($m); | |
2348 | |
2349 foreach my $o (&Class::Classless::X::ISA_TREE($it)) { | |
2350 return 1 | |
2351 if ref($o->{'METHODS'} || 0) # sanity | |
2352 && exists $o->{'METHODS'}{$m}; | |
2353 } | |
2354 | |
2355 return 0; | |
2356 } | |
2357 | |
2358 | |
2359 ########################################################################### | |
2360 | |
2361 sub Class::Classless::X::isa { # Like UNIVERSAL::isa | |
2362 # Returns true for $X->isa($Y) iff $Y is $X or is an ancestor of $X. | |
2363 | |
2364 return unless ref($_[0]) && ref($_[1]); | |
2365 return scalar(grep {$_ eq $_[1]} &Class::Classless::X::ISA_TREE($_[0])); | |
2366 } | |
2367 | |
2368 ########################################################################### | |
2369 | |
2370 sub nodelist { join ', ', map { "" . ($_->{'NAME'} || $_) . ""} @_ } | |
2371 | |
2372 ########################################################################### | |
2373 ########################################################################### | |
2374 ########################################################################### | |
2375 # Methods for the CALLSTATE class. | |
2376 # Basically, CALLSTATE objects represent the state of the dispatcher, | |
2377 # frozen at the moment when the method call was dispatched to the | |
2378 # appropriate sub. | |
2379 # In the grand scheme of things, this needn't be a class -- I could | |
2380 # have just made the callstate data-object be a hash with documented | |
2381 # keys, or a closure that responded to only certain parameters, | |
2382 # etc. But I like it this way. And I like being able to say simply | |
2383 # $cs->NEXT | |
2384 # Yes, these are a bit cryptically written, but it's behoovy for | |
2385 # them to be very very efficient. | |
2386 | |
2387 @Class::Classless::ISA = (); | |
2388 sub Class::Classless::CALLSTATE::found_name { $_[0][0] } | |
2389 # the method name called and found | |
2390 sub Class::Classless::CALLSTATE::found_depth { $_[0][1] } | |
2391 # my depth in the lineage | |
2392 sub Class::Classless::CALLSTATE::lineage { @{$_[0][2]} } | |
2393 # my lineage | |
2394 sub Class::Classless::CALLSTATE::target { $_[0][2][ 0 ] } | |
2395 # the object that's the target -- same as $_[0] for the method called | |
2396 sub Class::Classless::CALLSTATE::home { $_[0][2][ $_[0][1] ] } | |
2397 # the object I was found in | |
2398 sub Class::Classless::CALLSTATE::sub_found { | |
2399 $_[0][2][ $_[0][1] ]{'METHODS'}{ $_[0][0] } | |
2400 } # the routine called | |
2401 | |
2402 sub Class::Classless::CALLSTATE::no_fail { $_[0][3] } | |
2403 sub Class::Classless::CALLSTATE::set_no_fail_true { $_[0][3] = 1 } | |
2404 sub Class::Classless::CALLSTATE::set_fail_false { $_[0][3] = 0 } | |
2405 sub Class::Classless::CALLSTATE::set_fail_undef { $_[0][3] = undef } | |
2406 | |
2407 sub Class::Classless::CALLSTATE::via_next { $_[0][4] } | |
2408 | |
2409 sub Class::Classless::CALLSTATE::NEXT { | |
2410 #croak "NEXT needs at least one argument: \$cs->NEXT('method'...)" | |
2411 # unless @_ > 1; | |
2412 # no longer true. | |
2413 my $cs = shift @_; | |
2414 my $m = shift @_; # which may be (or come out) undef... | |
2415 $m = $cs->[0] unless defined $m; # the method name called and found | |
2416 | |
2417 ($cs->[2][0])->$m( | |
2418 bless( \$cs, 'Class::Classless::CALLSTATE::SHIMMY' ), | |
2419 @_ | |
2420 ); | |
2421 } | |
2422 | |
2423 ########################################################################### | |
2424 }; | |
2425 #}}} | |
2426 | |
2427 ############### | |
2428 ### | |
2429 # | |
2430 # {{{ *** C h a n g e l o g *** | |
2431 # | |
2432 # 0.6ca | |
2433 # - add screen support (from nicklist.pl) | |
2434 # - rename to adv_windowlist.pl (advanced window list) since it isn't just a | |
2435 # window list status bar (wlstat) anymore | |
2436 # - names can now have a max length and window names can be used | |
2437 # - fixed a bug with block display in screen mode and statusbar mode | |
2438 # - added space handling to ir_fe and removed it again | |
2439 # - now handling formats on my own | |
2440 # - added warning about missing sb_act_none abstract leading to | |
2441 # - display*active settings | |
2442 # - added warning about the bug in awl_display_(no)key_active settings | |
2443 # | |
2444 # 0.5d | |
2445 # - add setting to also hide the last statusbar if empty (awl_all_disable) | |
2446 # - reverted to old utf8 code to also calculate broken utf8 length correctly | |
2447 # - simplified dealing with statusbars in wlreset | |
2448 # - added a little tweak for the renamed term_type somewhere after Irssi 0.8.9 | |
2449 # - fixed bug in handling channel #$$ | |
2450 # - typo on line 200 spotted by f0rked | |
2451 # - reset background colour at the beginning of an entry | |
2452 # | |
2453 # 0.4d | |
2454 # - fixed order of disabling statusbars | |
2455 # - several attempts at special chars, without any real success | |
2456 # and much more weird new bugs caused by this | |
2457 # - setting to specify sort order | |
2458 # - reduced timeout values | |
2459 # - added awl_hide_data for Geert Hauwaerts ( [email protected] ) :) | |
2460 # - make it so the dynamic sub is actually deleted | |
2461 # - fix a bug with removing of the last separator | |
2462 # - take into consideration parse_special | |
2463 # | |
2464 # 0.3b | |
2465 # - automatically kill old statusbars | |
2466 # - reset on /reload | |
2467 # - position/placement settings | |
2468 # | |
2469 # 0.2 | |
2470 # - automated retrieval of key bindings (thanks grep.pl authors) | |
2471 # - improved removing of statusbars | |
2472 # - got rid of status chop | |
2473 # | |
2474 # 0.1 | |
2475 # - rewritten to suit my needs | |
2476 # - based on chanact 0.5.5 | |
2477 # }}} | |
2478 # vim: se fdm=marker tw=80 : |