[PATCH] syntax highlighting for hgweb file revision view

Adam Hupp adam at hupp.org
Mon Nov 5 21:35:55 CST 2007


7 files changed, 152 insertions(+), 4 deletions(-)
mercurial/hgweb/hgweb_mod.py       |   90 ++++++++++++++++++++++++++++++++++--
templates/filerevision.tmpl        |    2 
templates/gitweb/filerevision.tmpl |    2 
templates/gitweb/map               |    1 
templates/header.tmpl              |    1 
templates/map                      |    1 
templates/static/syntax.css        |   59 +++++++++++++++++++++++


# HG changeset patch
# User Adam Hupp <adam at hupp.org>
# Date 1194320010 18000
# Node ID 52815203c50687a81ee673b0a817de683b9cc7af
# Parent  3aa5c45874c60560d75df74adbc964e107c8538a
syntax highlighting for hgweb file revision view

This changeset uses Pygments to add optional syntax highlighting to
the file revision view of hgweb.  If Pygments is not available or
highlighting is disabled it falls back to the plain view.

Two new configuration options are added to the [web] section:

use_pygments: if true, syntax highlighting is enabled (default: True)

pygments_style: the color scheme.  If this is changed the css file in
  templates/static/syntax.css should be regenerated as well
  (default: colorful)

diff -r 3aa5c45874c6 -r 52815203c506 mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py	Sat Oct 20 03:04:34 2007 +0200
+++ b/mercurial/hgweb/hgweb_mod.py	Mon Nov 05 22:33:30 2007 -0500
@@ -8,11 +8,31 @@
 
 import os, mimetypes, re, zlib, mimetools, cStringIO, sys
 import tempfile, urllib, bz2
+
+"""
+this prevents an "'unloaded module' object is not callable" error
+within pygments when using guess_lexer_for_filename
+
+based on info in http://www.selenic.com/mercurial/bts/issue605
+"""
+import mercurial.demandimport
+mercurial.demandimport.disable()
+
 from mercurial.node import *
 from mercurial.i18n import gettext as _
 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
 from mercurial import revlog, templater
 from common import get_mtime, staticfile, style_map, paritygen
+
+try:
+    from pygments import highlight
+    from pygments.util import ClassNotFound
+    from pygments.lexers import \
+        get_lexer_for_filename, guess_lexer_for_filename
+    from pygments.formatters import HtmlFormatter
+    pygments_available = True
+except ImportError:
+    pygments_available = False
 
 def _up(p):
     if p[0] != "/":
@@ -106,6 +126,14 @@ class hgweb(object):
             self.allowpull = self.configbool("web", "allowpull", True)
             self.encoding = self.config("web", "encoding", util._encoding)
 
+            self.pygments_style = self.config("web",
+                                              "pygments_style", "colorful")
+
+            self.use_pygments = pygments_available
+            self.use_pygments &= self.configbool("web",
+                                                 "use_pygments",
+                                                 True)
+            
     def archivelist(self, nodeid):
         allowed = self.configlist("web", "allow_archive")
         for i, spec in self.archive_specs.iteritems():
@@ -383,30 +411,84 @@ class hgweb(object):
                      entries=lambda **x: entries(limit=0, **x),
                      latestentry=lambda **x: entries(limit=1, **x))
 
+        
+    def pygments_format(self, filename, rawtext):
+
+        try:
+            lexer = guess_lexer_for_filename(filename, rawtext)
+        except ClassNotFound:
+            return None
+
+        class StripedHtmlFormatter(HtmlFormatter):
+
+            def __init__(self, *args, **kwargs):
+                self.stripes = kwargs.pop('stripes', 1)
+                super(StripedHtmlFormatter, self).__init__(*args, **kwargs)
+            
+            def wrap(self, source, outfile):
+                yield 0, "<div class='highlight'>"
+                yield 0, "<pre>"
+                parity = paritygen(self.stripes)
+
+                for n, i in source:
+                    if n == 1:
+                        i = "<div class='parity%s'>%s</div>" % \
+                        (parity.next(), i)
+                    yield n, i
+
+                yield 0, "</pre>"
+                yield 0, "</div>"
+
+        formatter = StripedHtmlFormatter(style=self.pygments_style,
+                                         linenos='inline',
+                                         stripes=self.stripecount)
+
+        return highlight(rawtext, lexer, formatter)
+
+
+    
     def filerevision(self, fctx):
         f = fctx.path()
-        text = fctx.data()
+
+        rawtext = fctx.data()
+        text = rawtext
+        
         fl = fctx.filelog()
         n = fctx.filenode()
         parity = paritygen(self.stripecount)
 
         mt = mimetypes.guess_type(f)[0]
-        rawtext = text
+
         if util.binary(text):
             mt = mt or 'application/octet-stream'
             text = "(binary:%s)" % mt
         mt = mt or 'text/plain'
 
-        def lines():
+
+        def lines(text):
             for l, t in enumerate(text.splitlines(1)):
                 yield {"line": t,
                        "linenumber": "% 6d" % (l + 1),
                        "parity": parity.next()}
 
+        # the template to use depends on which is defined
+        text_plain, text_pygments = [],[]
+        if self.use_pygments:
+            pygtext = self.pygments_format(f, text)
+            if pygtext is None:
+                # fallback if no formatter found
+                text_plain = lines(text)
+            else:
+                text_pygments = lines(pygtext)
+        else:
+            text_plain = lines(text)
+
+        
         yield self.t("filerevision",
                      file=f,
                      path=_up(f),
-                     text=lines(),
+                     text=text_plain,
+                     text_pygments=text_pygments,
                      raw=rawtext,
                      mimetype=mt,
                      rev=fctx.rev(),
diff -r 3aa5c45874c6 -r 52815203c506 templates/filerevision.tmpl
--- a/templates/filerevision.tmpl	Sat Oct 20 03:04:34 2007 +0200
+++ b/templates/filerevision.tmpl	Mon Nov 05 22:33:30 2007 -0500
@@ -1,4 +1,5 @@
 #header#
+<link rel="stylesheet" href="#staticurl#syntax.css" type="text/css" />
 <title>#repo|escape#:#file|escape#</title>
 </head>
 <body>
@@ -39,6 +40,7 @@
 
 <pre>
 #text%fileline#
+#text_pygments%fileline_pygments#
 </pre>
 
 #footer#
diff -r 3aa5c45874c6 -r 52815203c506 templates/gitweb/filerevision.tmpl
--- a/templates/gitweb/filerevision.tmpl	Sat Oct 20 03:04:34 2007 +0200
+++ b/templates/gitweb/filerevision.tmpl	Mon Nov 05 22:33:30 2007 -0500
@@ -1,4 +1,5 @@
 #header#
+<link rel="stylesheet" href="{staticurl}syntax.css" type="text/css" />
 <title>{repo|escape}: {file|escape}@{node|short}</title>
 <link rel="alternate" type="application/atom+xml"
    href="{url}atom-log" title="Atom feed for #repo|escape#">
@@ -52,6 +53,7 @@ file |
 
 <div class="page_body">
 #text%fileline#
+#text_pygments%fileline_pygments#
 </div>
 
 #footer#
diff -r 3aa5c45874c6 -r 52815203c506 templates/gitweb/map
--- a/templates/gitweb/map	Sat Oct 20 03:04:34 2007 +0200
+++ b/templates/gitweb/map	Mon Nov 05 22:33:30 2007 -0500
@@ -23,6 +23,7 @@ filediff = filediff.tmpl
 filediff = filediff.tmpl
 filelog = filelog.tmpl
 fileline = '<div style="font-family:monospace" class="parity#parity#"><pre><span class="linenr">   #linenumber#</span> #line|escape#</pre></div>'
+fileline_pygments = '#line#'
 annotateline = '<tr style="font-family:monospace" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
 difflineplus = '<div style="color:#008800;">#line|escape#</div>'
 difflineminus = '<div style="color:#cc0000;">#line|escape#</div>'
diff -r 3aa5c45874c6 -r 52815203c506 templates/header.tmpl
--- a/templates/header.tmpl	Sat Oct 20 03:04:34 2007 +0200
+++ b/templates/header.tmpl	Mon Nov 05 22:33:30 2007 -0500
@@ -6,3 +6,4 @@ Content-type: text/html; charset={encodi
 <link rel="icon" href="#staticurl#hgicon.png" type="image/png">
 <meta name="robots" content="index, nofollow" />
 <link rel="stylesheet" href="#staticurl#style.css" type="text/css" />
+
diff -r 3aa5c45874c6 -r 52815203c506 templates/map
--- a/templates/map	Sat Oct 20 03:04:34 2007 +0200
+++ b/templates/map	Mon Nov 05 22:33:30 2007 -0500
@@ -22,6 +22,7 @@ filediff = filediff.tmpl
 filediff = filediff.tmpl
 filelog = filelog.tmpl
 fileline = '<div class="parity#parity#"><span class="lineno">#linenumber#</span>#line|escape#</div>'
+fileline_pygments = '#line#'
 filelogentry = filelogentry.tmpl
 annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
 difflineplus = '<span class="plusline">#line|escape#</span>'
diff -r 3aa5c45874c6 -r 52815203c506 templates/static/syntax.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/static/syntax.css	Mon Nov 05 22:33:30 2007 -0500
@@ -0,0 +1,59 @@
+.c { color: #808080 } /* Comment */
+.err { color: #F00000; background-color: #F0A0A0 } /* Error */
+.k { color: #008000; font-weight: bold } /* Keyword */
+.o { color: #303030 } /* Operator */
+.cm { color: #808080 } /* Comment.Multiline */
+.cp { color: #507090 } /* Comment.Preproc */
+.c1 { color: #808080 } /* Comment.Single */
+.cs { color: #cc0000; font-weight: bold } /* Comment.Special */
+.gd { color: #A00000 } /* Generic.Deleted */
+.ge { font-style: italic } /* Generic.Emph */
+.gr { color: #FF0000 } /* Generic.Error */
+.gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.gi { color: #00A000 } /* Generic.Inserted */
+.go { color: #808080 } /* Generic.Output */
+.gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
+.gs { font-weight: bold } /* Generic.Strong */
+.gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.gt { color: #0040D0 } /* Generic.Traceback */
+.kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.kp { color: #003080; font-weight: bold } /* Keyword.Pseudo */
+.kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.kt { color: #303090; font-weight: bold } /* Keyword.Type */
+.m { color: #6000E0; font-weight: bold } /* Literal.Number */
+.s { background-color: #fff0f0 } /* Literal.String */
+.na { color: #0000C0 } /* Name.Attribute */
+.nb { color: #007020 } /* Name.Builtin */
+.nc { color: #B00060; font-weight: bold } /* Name.Class */
+.no { color: #003060; font-weight: bold } /* Name.Constant */
+.nd { color: #505050; font-weight: bold } /* Name.Decorator */
+.ni { color: #800000; font-weight: bold } /* Name.Entity */
+.ne { color: #F00000; font-weight: bold } /* Name.Exception */
+.nf { color: #0060B0; font-weight: bold } /* Name.Function */
+.nl { color: #907000; font-weight: bold } /* Name.Label */
+.nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
+.nt { color: #007000 } /* Name.Tag */
+.nv { color: #906030 } /* Name.Variable */
+.ow { color: #000000; font-weight: bold } /* Operator.Word */
+.w { color: #bbbbbb } /* Text.Whitespace */
+.mf { color: #6000E0; font-weight: bold } /* Literal.Number.Float */
+.mh { color: #005080; font-weight: bold } /* Literal.Number.Hex */
+.mi { color: #0000D0; font-weight: bold } /* Literal.Number.Integer */
+.mo { color: #4000E0; font-weight: bold } /* Literal.Number.Oct */
+.sb { background-color: #fff0f0 } /* Literal.String.Backtick */
+.sc { color: #0040D0 } /* Literal.String.Char */
+.sd { color: #D04020 } /* Literal.String.Doc */
+.s2 { background-color: #fff0f0 } /* Literal.String.Double */
+.se { color: #606060; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */
+.sh { background-color: #fff0f0 } /* Literal.String.Heredoc */
+.si { background-color: #e0e0e0 } /* Literal.String.Interpol */
+.sx { color: #D02000; background-color: #fff0f0 } /* Literal.String.Other */
+.sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */
+.s1 { background-color: #fff0f0 } /* Literal.String.Single */
+.ss { color: #A06000 } /* Literal.String.Symbol */
+.bp { color: #007020 } /* Name.Builtin.Pseudo */
+.vc { color: #306090 } /* Name.Variable.Class */
+.vg { color: #d07000; font-weight: bold } /* Name.Variable.Global */
+.vi { color: #3030B0 } /* Name.Variable.Instance */
+.il { color: #0000D0; font-weight: bold } /* Literal.Number.Integer.Long */


More information about the Mercurial-devel mailing list