=== htdocs/css/trac.css
==================================================================
--- htdocs/css/trac.css  (revision 1045)
+++ htdocs/css/trac.css  (local)
@@ -240,6 +240,8 @@
 a.missing:link,a.missing:visited { background: #fafaf0; color: #998 }
 a.missing:hover { color: #000; }
 
+a.external:link,a.external:visited { color: #00b }
+
 #content.wiki { line-height: 140% }
 .wikitoolbar {
  border: solid #d7d7d7;
=== trac/WikiFormatter.py
==================================================================
--- trac/WikiFormatter.py  (revision 1045)
+++ trac/WikiFormatter.py  (local)
@@ -41,10 +41,10 @@
               r"""(?P<strike>~~)""",
               r"""(?P<inlinecode>!?\{\{\{(?P<inline>.*?)\}\}\})""",
               r"""(?P<htmlescapeentity>!?&#\d+;)""",
-              r"""(?P<tickethref>!?#\d+)""",
-              r"""(?P<changesethref>!?\[\d+\])""",
+              r"""(?P<tickethref>!?#(?P<t_intertrac>[a-zA-z]?)\d+)""",
+              r"""(?P<changesethref>!?\[(?P<c_intertrac>[a-zA-z]?)\d+\])""",
               r"""(?P<reporthref>!?\{\d+\})""",
-              r"""(?P<modulehref>!?((?P<modulename>bug|ticket|browser|source|repos|report|changeset|wiki|milestone|search):(?P<moduleargs>(&#34;(.*?)&#34;|'(.*?)')|([^ ]*[^\., \)]))))""",
+              r"""(?P<modulehref>!?((?P<modulename>bug|ticket|browser|source|repos|report|changeset|wiki|milestone|search):(?P<m_intertrac>[a-zA-z]:?)(?P<moduleargs>(&#34;(.*?)&#34;|'(.*?)')|([^ ]*[^\., \)]))))""",
               r"""(?P<wikilink>!?(^|(?<=[^A-Za-z]))[A-Z][a-z]+(?:[A-Z][a-z]*[a-z/])+(?=\Z|\s|,|\.|:|\)))""",
               r"""(?P<fancylink>!?\[(?P<fancyurl>([a-z]+:[^ ]+)) (?P<linkname>.*?)\])"""]
 
@@ -112,30 +112,42 @@
         return match
 
     def _tickethref_formatter(self, match, fullmatch):
-        number = int(match[1:])
-        cursor = self.db.cursor ()
-        cursor.execute('SELECT summary,status FROM ticket WHERE id=%s', number)
-        row = cursor.fetchone ()
-        if not row:
-            return '<a class="missing" href="%s">#%d</a>' % (self._href.ticket(number), number)
+        intertrac = fullmatch.group('t_intertrac')
+        if intertrac:
+            number = int(match[1+len(intertrac):-1])
+            href, title, _class = self._intertrac_link(intertrac)
+            return '<a class="%s" title="%s" href="%s/ticket/%d">#%d</a>' % (_class, title, href, number, number)
         else:
-            summary = util.shorten_line(row[0])
-            if row[1] == 'new':
-                return '<a href="%s" title="NEW : %s">#%d*</a>' % (self._href.ticket(number), summary, number)
-            elif row[1] == 'closed':
-                return '<a href="%s" title="CLOSED : %s"><del>#%d</del></a>' % (self._href.ticket(number), summary, number)
+            number = int(match[1:])
+            cursor = self.db.cursor ()
+            cursor.execute('SELECT summary,status FROM ticket WHERE id=%s', number)
+            row = cursor.fetchone ()
+            if not row:
+                return '<a class="missing" href="%s">#%d</a>' % (self._href.ticket(number), number)
             else:
-                return '<a href="%s" title="%s">#%d</a>' % (self._href.ticket(number), summary, number)
+                summary = util.shorten_line(row[0])
+                if row[1] == 'new':
+                    return '<a href="%s" title="NEW : %s">#%d*</a>' % (self._href.ticket(number), summary, number)
+                elif row[1] == 'closed':
+                    return '<a href="%s" title="CLOSED : %s"><del>#%d</del></a>' % (self._href.ticket(number), summary, number)
+                else:
+                    return '<a href="%s" title="%s">#%d</a>' % (self._href.ticket(number), summary, number)
 
     def _changesethref_formatter(self, match, fullmatch):
-        number = int(match[1:-1])
-        cursor = self.db.cursor ()
-        cursor.execute('SELECT message FROM revision WHERE rev=%d', number)
-        row = cursor.fetchone ()
-        if not row:
-            return '[<a class="missing" href="%s">%d</a>]' % (self._href.changeset(number), number)
+        intertrac = fullmatch.group('c_intertrac')
+        if intertrac:
+            number = int(match[1+len(intertrac):-1])
+            href, title, _class = self._intertrac_link(intertrac)
+            return '[<a class="%s" title="%s" href="%s/changeset/%d">%d</a>]' % (_class, title, href, number, number)
         else:
-            return '[<a title="%s" href="%s">%d</a>]' % (util.shorten_line(row[0]),self._href.changeset(number), number)
+            number = int(match[1:-1])
+            cursor = self.db.cursor ()
+            cursor.execute('SELECT message FROM revision WHERE rev=%d', number)
+            row = cursor.fetchone ()
+            if not row:
+                return '[<a class="missing" href="%s">%d</a>]' % (self._href.changeset(number), number)
+            else:
+                return '[<a title="%s" href="%s">%d</a>]' % (util.shorten_line(row[0]),self._href.changeset(number), number)
 
     def _reporthref_formatter(self, match, fullmatch):
         number = int(match[1:-1])
@@ -147,6 +159,12 @@
             return None, None
         module = text[:sep]
         args = text[sep+1:]
+        sep2 = args.find(':')
+        if sep2 != -1:
+            intertrac = args[:sep2]
+            href, title, _class = self._intertrac_link(intertrac)
+            args = args[sep2+1:]
+            return "%s/%s/%s"% (href,module,args), text, _class, title
 	if module in ['bug', 'ticket']:
 	    cursor = self.db.cursor ()
 	    cursor.execute('SELECT summary,status FROM ticket WHERE id=%s', args)
@@ -195,9 +213,12 @@
             return None, None, 0, None
 
     def _modulehref_formatter(self, match, fullmatch):
-        link, text, missing, title = self._expand_module_link(match)
-        if link and missing:
-            return '<a title="%s" class="missing" href="%s">%s?</a>' % (title,link, text)
+        link, text, _class, title = self._expand_module_link(match)
+        if link and _class:
+            qmark = ''
+            if _class == 1:
+                _class, qmark = 'missing', '?'
+            return '<a title="%s" class="%s" href="%s">%s%s</a>' % (title, _class, link, text, qmark)
         elif link:
             return '<a title="%s" href="%s">%s</a>' % (title or '',link, text)
         else:
@@ -217,15 +238,28 @@
         link = fullmatch.group('fancyurl')
         name = fullmatch.group('linkname')
 
-        module_link, t, missing, title = self._expand_module_link(link)
-        if module_link and missing:
-            return '<a class="missing" href="%s">%s?</a>' % (module_link, name)
+        module_link, t, _class, title = self._expand_module_link(link)
+        if module_link and _class:
+            qmark = ''
+            if _class == 1:
+                _class, qmark = 'missing', '?'
+            return '<a class="%s" href="%s">%s%s</a>' % (_class, module_link, name, qmark)
         elif module_link:
             return '<a href="%s">%s</a>' % (module_link, name)
         else:
             return '<a href="%s">%s</a>' % (link, name)
 
+    def _intertrac_link(self, intertrac):
+        href = self.env.get_config('intertrac', intertrac.upper() + '.href')
+        if href:
+            title = self.env.get_config('intertrac', intertrac.upper() + '.title')
+            _class = 'external'
+        else:
+            title = "Unknown intertrac link '%s'" % intertrac
+            _class = 'missing'
+        return href, title, _class
 
+
 class OneLinerFormatter(CommonFormatter):
     """
     A special version of the wiki formatter that only implement a
@@ -275,7 +309,8 @@
     # RE patterns used by other patterna
     _helper_patterns = ('idepth', 'ldepth', 'hdepth', 'fancyurl',
                         'linkname', 'macroname', 'macroargs', 'inline',
-                        'modulename', 'moduleargs')
+                        'modulename', 'moduleargs',
+                        't_intertrac', 'c_intertrac', 'm_intertrac')
 
     _htmlproc_disallow_rule = re.compile('(?i)<(script|noscript|embed|object|iframe|frame|frameset|link|style|meta|param|doctype)')
 
