From cd93fa83ba632588b357b190f47809205b9a5d91 Mon Sep 17 00:00:00 2001 From: Arne Georg Gleditsch Date: Tue, 12 Feb 2008 22:53:01 +0100 Subject: Change line number layout and caching mechanism. --- lib/LXRng/Markup/File.pm | 6 +- lib/LXRng/Web.pm | 140 +++++++++++++++++++++++++++----------- webroot/.static/css/lxrng.css | 34 ++------- webroot/.static/js/lxrng-funcs.js | 79 ++++++++++++++------- 4 files changed, 164 insertions(+), 95 deletions(-) diff --git a/lib/LXRng/Markup/File.pm b/lib/LXRng/Markup/File.pm index 9bd811c..d94952c 100644 --- a/lib/LXRng/Markup/File.pm +++ b/lib/LXRng/Markup/File.pm @@ -49,9 +49,9 @@ sub make_format_newline { $line++; $nl = safe_html($nl); - return qq{$nl
  • }. - qq{}. - qq{}; + return qq{$nl}. + qq{$line}. + qq{}; } } diff --git a/lib/LXRng/Web.pm b/lib/LXRng/Web.pm index 4334981..d7f9be4 100644 --- a/lib/LXRng/Web.pm +++ b/lib/LXRng/Web.pm @@ -34,13 +34,17 @@ use IO::Handle; use Digest::SHA1 qw(sha1_hex); use CGI::Ajax; use File::Temp qw(tempdir tempfile); +use File::Path qw(mkpath); use POSIX qw(waitpid); use constant PDF_LINELEN => 95; use constant PDF_CHARPTS => 6.6; +# Cache must be purged if this is changed. +use constant FRAGMENT_SIZE => 250; + use vars qw($has_gzip_io); -# eval { require PerlIO::gzip; $has_gzip_io = 1; }; +eval { require PerlIO::gzip; $has_gzip_io = 1; }; # Return 1 if gzip compression of html is desired. @@ -77,39 +81,49 @@ sub print_markedup_file { return; } else { - # Grmble. We assume the identifiers to markup are identical - # from one version to another, but if the same revision of a - # file exists both in an indexed and un-indexed release, one - # of them will have its identifiers highlighted and the other - # not. So we can't share a cache slot across releases without - # adding some extra logic here. Bummer. - # TODO: Resolve by caching only accesses to releases that are - # is_indexed. + my $line = 0; + my $focus = 1; + my $fline = $context->param('line'); + + $focus = $fline < 100 if defined($fline); + my $shaid = sha1_hex(join("\0", $node->name, $node->revision, $context->release)); my $cfile; + $shaid =~ s,^(..)(..),$1/$2/,; $cfile = $context->config->{'cache'}.'/'.$shaid if exists $context->config->{'cache'}; - if ($cfile and -e $cfile) { - open(my $cache, '<', $cfile); - - my $focus = $context->param('line') || 0; - $focus = 0 if $context->param('full'); - my $class = $focus ? 'partial' : 'full'; - my $start = $focus > 5 ? " start=".($focus - 5) : ""; - print("
    ");
    -	    while (<$cache>) {
    -		next if $focus and $. < $focus - 5;
    -		print($_);
    -		last if $focus and $. > $focus + 70;
    +	if ($cfile and -d $cfile) {
    +	    print("
    ");
    +	    while (-r "$cfile/$line") {
    +		print("
    "); + if ($focus) { + open(my $cache, '<', "$cfile/$line"); + my $buf; + while (read($cache, $buf, 16384) > 0) { + print($buf); + } + close($cache); + } + else { + print("\n" x FRAGMENT_SIZE); + } + print("
    "); + $line += FRAGMENT_SIZE; + + if (defined($fline)) { + $focus = ($line <= ($fline + 100) + and $line > ($fline - FRAGMENT_SIZE)); + } } - print("
    "); - close($cache); + print("
    \n"); } else { my $cache; - open($cache, '>', $cfile) if $cfile; + mkpath($cfile, 0, 0777); + open($cache, '>', "$cfile/0") if $cfile; my $handle = $node->handle(); LXRng::Lang->init($context); my $lang = LXRng::Lang->new($node); @@ -117,21 +131,39 @@ sub print_markedup_file { @{$lang->parsespec}); my $markup = LXRng::Markup::File->new('context' => $context); my $subst = $lang->markuphandlers($context, $node, $markup); - - # Possible optimization: store cached file also as .gz, - # and pass that on if the client accepts gzip-encoded - # data. Saves us from compressing the cached file each - # time it's needed, but requires a bit of fiddling with - # perlio and the streams to get right. Also messes up - # partial transfers. - print("
      "); + + print("
      ".
      +		  "
      "); while (1) { - my @frags = $markup->markupfile($subst, $parse); + my @frags = map { split(/(?<=\n)/, $_) } + $markup->markupfile($subst, $parse); last unless @frags; - print(@frags); - print($cache @frags) if $cache; + foreach my $f (@frags) { + print($f) if $focus; + print($cache $f) if $cache; + if ($f =~ /\n$/s) { + $line++; + if ($line % FRAGMENT_SIZE == 0) { + print("\n" x FRAGMENT_SIZE) unless $focus; + if (defined($fline)) { + $focus = ($line <= ($fline + 100) + and $line > ($fline - FRAGMENT_SIZE)); + } + print("
      ". + "
      "); + if ($cache) { + close($cache); + open($cache, '>', "$cfile/$line"); + } + } + } + } } - print("
    \n"); + print("\n"); + close($cache) if $cache; } return $shaid; } @@ -161,6 +193,7 @@ sub source { my $pjx = CGI::Ajax->new('pjx_search' => '', 'pjx_load_file' => '', + 'pjx_load_fragment' => '', 'pjx_releases' => ''); $pjx->js_encode_function('escape'); @@ -181,10 +214,17 @@ sub source { my $base = $context->base_url(1); $base =~ s,/*$,/ajax+*/,; + # This is a bit fragile, but only covers a relatively + # esoteric corner case. (CGI::Ajax splits results on + # __pjx__, and there doesn't seem to be any provisions + # for escaping any randomly occurring split markers.) + my $js = $pjx->show_javascript(); + $js =~ s/var splitval.*var data[^;]+/var data = rsp/; + $template->process('main.tt2', {'context' => $context, 'base_url' => $base, - 'javascript' => $pjx->show_javascript(), + 'javascript' => $js, 'is_ajax' => 1}) or die $template->error(); } @@ -443,11 +483,13 @@ sub handle_ajax_request { my $gzip = do_compress_response($query); # $query->no_cache(1); FIXME -- not available with CGI.pm. - print($query->header(-type => 'text/html', - -charset => 'utf-8', - -cache-control => 'no-store, no-cache, must-revalidate', - $gzip ? (-content_encoding => 'gzip') : ())); + my %headers = (-type => 'text/html', + -charset => 'utf-8'); + $headers{'-cache-control'} = 'no-store, no-cache, must-revalidate' + unless $context->param('fname') eq 'pjx_load_fragment'; + $headers{'-content_encoding'} = 'gzip' if $gzip; + print($query->header(%headers)); binmode(\*STDOUT, ":gzip") if $gzip; if ($context->param('fname') eq 'pjx_load_file') { @@ -456,6 +498,22 @@ sub handle_ajax_request { print_markedup_file($context, $template, $node); } + elsif ($context->param('fname') eq 'pjx_load_fragment') { + my $shaid = $context->param('frag'); + return unless $shaid =~ + m|^[0-9a-z]{2}/[0-9a-z]{2}/[0-9a-z]{36}/[0-9]+$|; + return unless exists $context->config->{'cache'}; + my $cfile = $context->config->{'cache'}.'/'.$shaid; + return unless -e $cfile; + open(my $cache, '<', $cfile) or return; + + print($shaid.'|'); + my $buf; + while (read($cache, $buf, 16384) > 0) { + print($buf); + } + close($cache); + } elsif ($context->param('fname') eq 'pjx_search') { if ($context->param('ajax_lookup') =~ /^[+ ](code|ident|file|text|ambig)=(.*)/) diff --git a/webroot/.static/css/lxrng.css b/webroot/.static/css/lxrng.css index f5d84c0..3b6ff2b 100644 --- a/webroot/.static/css/lxrng.css +++ b/webroot/.static/css/lxrng.css @@ -150,6 +150,10 @@ span.close-button { content: attr(id); } */ +pre { + margin-left: 3.8em; +} + a.line { position: absolute; top: auto; @@ -157,39 +161,13 @@ a.line { height: 2ex; width: 3em; text-align: right; + padding-right: 3px; border: solid; border-width: 1px; border-color: #000000; margin-left: 3px; -} - -a.line span { - position: absolute; - top: auto; - height: 2ex; - left: 0px; - background: #6c6c6c; - filter: alpha(opacity=10); - moz-opacity: .10; - opacity: .10; - width: 3em; -} - - -pre#file_contents li { - color: blue; -} - -span.line { - position: absolute; - left: 4em; - color: black; - white-space: pre; -} - -pre { - margin-left: 0.7em; + background: #F0F0F0; } a img { diff --git a/webroot/.static/js/lxrng-funcs.js b/webroot/.static/js/lxrng-funcs.js index 0aa4237..939eefd 100644 --- a/webroot/.static/js/lxrng-funcs.js +++ b/webroot/.static/js/lxrng-funcs.js @@ -190,6 +190,59 @@ function load_file(tree, file, ver, line) { return false; } + +function ajaxify_link_handlers(links) { + var i; + for (i = 0; i < links.length; i++) { + if (links[i].className == 'fref') { + links[i].onclick = ajax_nav; + } + else if (links[i].className == 'line') { + links[i].onclick = ajax_jumpto_line; + } + else if (links[i].className == 'sref' || + links[i].className == 'falt') + { + links[i].onclick = ajax_lookup_anchor; + } + + } +} + +function load_next_pending_fragment() { + var pre = document.getElementById('file_contents'); + if (!pre) + return; + + for (var i = 0; i < pre.childNodes.length; i++) { + if ((pre.childNodes[i].nodeName == 'DIV') && + (pre.childNodes[i].className == 'pending')) + { + pjx_load_fragment(['tree__' + pending_tree, + 'frag__' + pre.childNodes[i].id], + [load_fragment_finalize]); + return; + } + } +} + +function load_fragment_finalize(content) { + var split = content.indexOf('|'); + var div = document.getElementById(content.substr(0, split)); + if (!div) + return; + + div.innerHTML = content.substr(split+1); + div.className = 'done'; + + var links = div.getElementsByTagName('a'); + ajaxify_link_handlers(links); + load_next_pending_fragment(); + +// if (location.hash) +// location.hash = location.hash; +} + function load_file_finalize(content) { var res = document.getElementById('content'); res.innerHTML = 'Done'; @@ -198,7 +251,7 @@ function load_file_finalize(content) { head.innerHTML = '' + pending_tree + ''; var path_walked = ''; var elems = pending_file.split(/\//); - for (var i=0; i