[PATCH 1 of 3 V6] hgweb: code selection without line numbers in file source view

Alexander Plavin me at aplavin.ru
Fri Jul 5 13:20:19 CDT 2013


# HG changeset patch
# User Alexander Plavin <me at aplavin.ru>
# Date 1372933124 -14400
#      Thu Jul 04 14:18:44 2013 +0400
# Node ID dd7a098f0093dc28d025064bba4407acc53a90e5
# Parent  d7b4aa1049d3fbbc2b8d24114afad82586c49db3
hgweb: code selection without line numbers in file source view

All the source lines are put in a <pre> tag, which gives correct display and
copy&paste in both Chromium (WebKit) and FireFox: line numbers are not copied,
all the tabs and spaces are kept. This doesn't change the visual appearance
of the view compared to current hgweb version and doesn't use any JS code.
Also, stripes in this view are now generated clientside with CSS.

Issues:
a) in pre-Webkit versions of Opera line numbers are copied
b) line numbers are (only visually) selected at empty lines and in older
versions of most browsers, which don't support 'user-select' propery

This implementation is chosen because other variants have more important issues:
a) current hgweb implementation: line numbers are selected, tabs and spaces are
lost when copy in FireFox
b) the whole code in one <pre> tag, and all line numbers in another one
with 'float: left': not possible to add stripes, highlighting and line wrapping
c) same as previous, but separate divs for each line and nbsp's or br's
in place of empty lines (to keep display correct): tabs and spaces are lost
when copy in FireFox, not possible to add line wrapping
d) ordered html list for the lines, with nbsp's or br's in place of empty
lines (to keep display correct): tabs and spaces are lost when copy in FireFox,
getting anchor links to lines is possible only with JavaScript, and display is
altered with extra dots after line numbers.

As for browser compatibility, the CSS tricks used are supported in
(according to caniuse.com):
a) line numbers generation with 'content:' property and CSS counters
is supported in IE 8 and later and in all other popular browsers
b) stripes, implemented with 'nth-child' selector supported in
IE 8, FF 3.5, Safari 3.2, Opera 9.5 and later, and all other popular browsers

This patch is based on a demo implementation by
Martin Geisler <martin at geisler.net>.

diff -r d7b4aa1049d3 -r dd7a098f0093 mercurial/templates/paper/filerevision.tmpl
--- a/mercurial/templates/paper/filerevision.tmpl	Sat Jun 29 14:36:51 2013 +0400
+++ b/mercurial/templates/paper/filerevision.tmpl	Thu Jul 04 14:18:44 2013 +0400
@@ -68,7 +68,7 @@
 
 <div class="overflow">
 <div class="sourcefirst"> line source</div>
-{text%fileline}
+<pre class="sourcelines">{text%fileline}</pre>
 <div class="sourcelast"></div>
 </div>
 </div>
diff -r d7b4aa1049d3 -r dd7a098f0093 mercurial/templates/paper/map
--- a/mercurial/templates/paper/map	Sat Jun 29 14:36:51 2013 +0400
+++ b/mercurial/templates/paper/map	Thu Jul 04 14:18:44 2013 +0400
@@ -72,7 +72,7 @@
 filecomparison = filecomparison.tmpl
 filelog = filelog.tmpl
 fileline = '
-  <div class="parity{parity} source"><a href="#{lineid}" id="{lineid}">{linenumber}</a> {line|escape}</div>'
+  <span id="{lineid}">{strip(line|escape, '\r\n')}</span><a href="#{lineid}"></a>'
 filelogentry = filelogentry.tmpl
 
 annotateline = '
diff -r d7b4aa1049d3 -r dd7a098f0093 mercurial/templates/static/style-paper.css
--- a/mercurial/templates/static/style-paper.css	Sat Jun 29 14:36:51 2013 +0400
+++ b/mercurial/templates/static/style-paper.css	Thu Jul 04 14:18:44 2013 +0400
@@ -209,6 +209,44 @@
 .source a { color: #999; font-size: smaller; font-family: monospace;}
 .bottomline { border-bottom: 1px solid #999; }
 
+.sourcelines {
+  font-size: 90%;
+  position: relative;
+}
+
+.sourcelines > span {
+  display: inline-block;
+  width: 100%;
+  padding: 1px 0px;
+  counter-increment: lineno;
+}
+
+.sourcelines > span:before {
+  -moz-user-select: -moz-none;
+  -khtml-user-select: none;
+  -webkit-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  display: inline-block;
+  width: 4em;
+  margin-right: 1em;
+  font-size: smaller;
+  color: #999;
+  text-align: right;
+  content: counter(lineno);
+}
+
+.sourcelines > span:nth-child(4n+1) { background-color: #f0f0f0; }
+.sourcelines > span:nth-child(4n+3) { background-color: white; }
+
+.sourcelines > a {
+    display: inline-block;
+    position: absolute;
+    left: 0px;
+    width: 4em;
+    height: 1em;
+}
+
 .fileline { font-family: monospace; }
 .fileline img { border: 0; }
 
diff -r d7b4aa1049d3 -r dd7a098f0093 tests/test-hgweb-commands.t
--- a/tests/test-hgweb-commands.t	Sat Jun 29 14:36:51 2013 +0400
+++ b/tests/test-hgweb-commands.t	Thu Jul 04 14:18:44 2013 +0400
@@ -668,9 +668,8 @@
   
   <div class="overflow">
   <div class="sourcefirst"> line source</div>
-  
-  <div class="parity0 source"><a href="#l1" id="l1">     1</a> foo
-  </div>
+  <pre class="sourcelines">
+  <span id="l1">foo</span><a href="#l1"></a></pre>
   <div class="sourcelast"></div>
   </div>
   </div>
diff -r d7b4aa1049d3 -r dd7a098f0093 tests/test-highlight.t
--- a/tests/test-highlight.t	Sat Jun 29 14:36:51 2013 +0400
+++ b/tests/test-highlight.t	Thu Jul 04 14:18:44 2013 +0400
@@ -137,39 +137,39 @@
   
   <div class="overflow">
   <div class="sourcefirst"> line source</div>
-  
-  <div class="parity0 source"><a href="#l1" id="l1">     1</a> <span class="c">#!/usr/bin/env python</span></div>
-  <div class="parity1 source"><a href="#l2" id="l2">     2</a> </div>
-  <div class="parity0 source"><a href="#l3" id="l3">     3</a> <span class="sd">"""Fun with generators. Corresponding Haskell implementation:</span></div>
-  <div class="parity1 source"><a href="#l4" id="l4">     4</a> </div>
-  <div class="parity0 source"><a href="#l5" id="l5">     5</a> <span class="sd">primes = 2 : sieve [3, 5..]</span></div>
-  <div class="parity1 source"><a href="#l6" id="l6">     6</a> <span class="sd">    where sieve (p:ns) = p : sieve [n | n <- ns, mod n p /= 0]</span></div>
-  <div class="parity0 source"><a href="#l7" id="l7">     7</a> <span class="sd">"""</span></div>
-  <div class="parity1 source"><a href="#l8" id="l8">     8</a> </div>
-  <div class="parity0 source"><a href="#l9" id="l9">     9</a> <span class="kn">from</span> <span class="nn">itertools</span> <span class="kn">import</span> <span class="n">dropwhile</span><span class="p">,</span> <span class="n">ifilter</span><span class="p">,</span> <span class="n">islice</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">chain</span></div>
-  <div class="parity1 source"><a href="#l10" id="l10">    10</a> </div>
-  <div class="parity0 source"><a href="#l11" id="l11">    11</a> <span class="kn">def</span> <span class="nf">primes</span><span class="p">():</span></div>
-  <div class="parity1 source"><a href="#l12" id="l12">    12</a>     <span class="sd">"""Generate all primes."""</span></div>
-  <div class="parity0 source"><a href="#l13" id="l13">    13</a>     <span class="kn">def</span> <span class="nf">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></div>
-  <div class="parity1 source"><a href="#l14" id="l14">    14</a>         <span class="n">p</span> <span class="o">=</span> <span class="n">ns</span><span class="o">.</span><span class="n">next</span><span class="p">()</span></div>
-  <div class="parity0 source"><a href="#l15" id="l15">    15</a>         <span class="c"># It is important to yield *here* in order to stop the</span></div>
-  <div class="parity1 source"><a href="#l16" id="l16">    16</a>         <span class="c"># infinite recursion.</span></div>
-  <div class="parity0 source"><a href="#l17" id="l17">    17</a>         <span class="kn">yield</span> <span class="n">p</span></div>
-  <div class="parity1 source"><a href="#l18" id="l18">    18</a>         <span class="n">ns</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">%</span> <span class="n">p</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ns</span><span class="p">)</span></div>
-  <div class="parity0 source"><a href="#l19" id="l19">    19</a>         <span class="kn">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></div>
-  <div class="parity1 source"><a href="#l20" id="l20">    20</a>             <span class="kn">yield</span> <span class="n">n</span></div>
-  <div class="parity0 source"><a href="#l21" id="l21">    21</a> </div>
-  <div class="parity1 source"><a href="#l22" id="l22">    22</a>     <span class="n">odds</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">i</span><span class="p">:</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span> <span class="n">count</span><span class="p">())</span></div>
-  <div class="parity0 source"><a href="#l23" id="l23">    23</a>     <span class="kn">return</span> <span class="n">chain</span><span class="p">([</span><span class="mi">2</span><span class="p">],</span> <span class="n">sieve</span><span class="p">(</span><span class="n">dropwhile</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o"><</span> <span class="mi">3</span><span class="p">,</span> <span class="n">odds</span><span class="p">)))</span></div>
-  <div class="parity1 source"><a href="#l24" id="l24">    24</a> </div>
-  <div class="parity0 source"><a href="#l25" id="l25">    25</a> <span class="kn">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span></div>
-  <div class="parity1 source"><a href="#l26" id="l26">    26</a>     <span class="kn">import</span> <span class="nn">sys</span></div>
-  <div class="parity0 source"><a href="#l27" id="l27">    27</a>     <span class="kn">try</span><span class="p">:</span></div>
-  <div class="parity1 source"><a href="#l28" id="l28">    28</a>         <span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span></div>
-  <div class="parity0 source"><a href="#l29" id="l29">    29</a>     <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></div>
-  <div class="parity1 source"><a href="#l30" id="l30">    30</a>         <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></div>
-  <div class="parity0 source"><a href="#l31" id="l31">    31</a>     <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></div>
-  <div class="parity1 source"><a href="#l32" id="l32">    32</a>     <span class="kn">print</span> <span class="s">"The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">)))</span></div>
+  <pre class="sourcelines">
+  <span id="l1"><span class="c">#!/usr/bin/env python</span></span><a href="#l1"></a>
+  <span id="l2"></span><a href="#l2"></a>
+  <span id="l3"><span class="sd">"""Fun with generators. Corresponding Haskell implementation:</span></span><a href="#l3"></a>
+  <span id="l4"></span><a href="#l4"></a>
+  <span id="l5"><span class="sd">primes = 2 : sieve [3, 5..]</span></span><a href="#l5"></a>
+  <span id="l6"><span class="sd">    where sieve (p:ns) = p : sieve [n | n <- ns, mod n p /= 0]</span></span><a href="#l6"></a>
+  <span id="l7"><span class="sd">"""</span></span><a href="#l7"></a>
+  <span id="l8"></span><a href="#l8"></a>
+  <span id="l9"><span class="kn">from</span> <span class="nn">itertools</span> <span class="kn">import</span> <span class="n">dropwhile</span><span class="p">,</span> <span class="n">ifilter</span><span class="p">,</span> <span class="n">islice</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">chain</span></span><a href="#l9"></a>
+  <span id="l10"></span><a href="#l10"></a>
+  <span id="l11"><span class="kn">def</span> <span class="nf">primes</span><span class="p">():</span></span><a href="#l11"></a>
+  <span id="l12">    <span class="sd">"""Generate all primes."""</span></span><a href="#l12"></a>
+  <span id="l13">    <span class="kn">def</span> <span class="nf">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></span><a href="#l13"></a>
+  <span id="l14">        <span class="n">p</span> <span class="o">=</span> <span class="n">ns</span><span class="o">.</span><span class="n">next</span><span class="p">()</span></span><a href="#l14"></a>
+  <span id="l15">        <span class="c"># It is important to yield *here* in order to stop the</span></span><a href="#l15"></a>
+  <span id="l16">        <span class="c"># infinite recursion.</span></span><a href="#l16"></a>
+  <span id="l17">        <span class="kn">yield</span> <span class="n">p</span></span><a href="#l17"></a>
+  <span id="l18">        <span class="n">ns</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">%</span> <span class="n">p</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ns</span><span class="p">)</span></span><a href="#l18"></a>
+  <span id="l19">        <span class="kn">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></span><a href="#l19"></a>
+  <span id="l20">            <span class="kn">yield</span> <span class="n">n</span></span><a href="#l20"></a>
+  <span id="l21"></span><a href="#l21"></a>
+  <span id="l22">    <span class="n">odds</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">i</span><span class="p">:</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span> <span class="n">count</span><span class="p">())</span></span><a href="#l22"></a>
+  <span id="l23">    <span class="kn">return</span> <span class="n">chain</span><span class="p">([</span><span class="mi">2</span><span class="p">],</span> <span class="n">sieve</span><span class="p">(</span><span class="n">dropwhile</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o"><</span> <span class="mi">3</span><span class="p">,</span> <span class="n">odds</span><span class="p">)))</span></span><a href="#l23"></a>
+  <span id="l24"></span><a href="#l24"></a>
+  <span id="l25"><span class="kn">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span></span><a href="#l25"></a>
+  <span id="l26">    <span class="kn">import</span> <span class="nn">sys</span></span><a href="#l26"></a>
+  <span id="l27">    <span class="kn">try</span><span class="p">:</span></span><a href="#l27"></a>
+  <span id="l28">        <span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span></span><a href="#l28"></a>
+  <span id="l29">    <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></span><a href="#l29"></a>
+  <span id="l30">        <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></span><a href="#l30"></a>
+  <span id="l31">    <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></span><a href="#l31"></a>
+  <span id="l32">    <span class="kn">print</span> <span class="s">"The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">)))</span></span><a href="#l32"></a></pre>
   <div class="sourcelast"></div>
   </div>
   </div>
@@ -593,17 +593,14 @@
   $ hgserveget euc-jp eucjp.txt
   % HGENCODING=euc-jp hg serve
   % hgweb filerevision, html
-  <div class="parity0 source"><a href="#l1" id="l1">     1</a> \xb5\xfe</div> (esc)
   % errors encountered
   $ hgserveget utf-8 eucjp.txt
   % HGENCODING=utf-8 hg serve
   % hgweb filerevision, html
-  <div class="parity0 source"><a href="#l1" id="l1">     1</a> \xef\xbf\xbd\xef\xbf\xbd</div> (esc)
   % errors encountered
   $ hgserveget us-ascii eucjp.txt
   % HGENCODING=us-ascii hg serve
   % hgweb filerevision, html
-  <div class="parity0 source"><a href="#l1" id="l1">     1</a> ??</div>
   % errors encountered
 
   $ cd ..


More information about the Mercurial-devel mailing list