Staging
v0.5.0
https://foss.heptapod.net/mercurial/hgview
Raw File
Tip revision: 1850a087c1b6cb026470c738b2c7f82c6fada06d authored by Mads Kiilerich on 04 April 2020, 02:27:44 UTC
qt5: renaming of references to hgviewlib/qt5, finishing up the Qt5 port
Tip revision: 1850a08
revision_description.py
# Copyright (c) 2009-2012 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program.  If not, see <http://www.gnu.org/licenses/>.
"""
Qt5 high level widgets for hg repo changelogs and filelogs
"""
from collections import defaultdict

from mercurial import pycompat
from mercurial.node import short as short_hex
from mercurial.error import RepoError

from PyQt5 import QtGui, QtWidgets
from PyQt5.QtCore import pyqtSignal

from hgviewlib.config import HgConfig
from hgviewlib.util import format_desc, xml_escape, tounicode, tohg, tostr
from hgviewlib.util import first_known_precursors, first_known_successors
from hgviewlib.qt5.mixins import ActionsMixin

# Re-Structured Text support
raw2html = lambda x: u'<pre>%s</pre>' % xml_escape(x)
try:
    from docutils.core import publish_string
    import docutils.utils
    def rst2html(text, stylesheet_path=None):
        if stylesheet_path is None:
            stylesheet_path = 'qrc:/resources/description.css'
        try:
            # halt_level allows the parser to raise errors
            # report_level cleans the standard output
            out = publish_string(text, writer_name='html', settings_overrides={
                'halt_level':docutils.utils.Reporter.WARNING_LEVEL,
                'report_level':docutils.utils.Reporter.SEVERE_LEVEL + 1,
                'stylesheet_path': stylesheet_path,
                'embed_stylesheet': False,
            })
        except:
            # docutils is not always reliable (or reliably packaged)
            out = raw2html(text)
        if not isinstance(out, pycompat.unicode):
            # if the docutils call did not fail, we likely got an str ...
            out = tounicode(out)
        return out
except ImportError:
    rst2html = None

TROUBLE_EXPLANATIONS = defaultdict(lambda:u'unknown trouble')
TROUBLE_EXPLANATIONS['unstable']  = "Based on obsolete ancestor"
TROUBLE_EXPLANATIONS['bumped']    = "Hopeless successors of a public changeset"
TROUBLE_EXPLANATIONS['divergent'] = "Another changeset are also a successors "\
                                    "of one of your precursor"
# temporary compat with older evolve version
TROUBLE_EXPLANATIONS['latecomer'] = TROUBLE_EXPLANATIONS['bumped']
TROUBLE_EXPLANATIONS['conflicting'] = TROUBLE_EXPLANATIONS['divergent']
class RevisionDescriptionView(ActionsMixin, QtWidgets.QTextBrowser):
    """
    Display metadata for one revision (rev, author, description, etc.)
    using a TextBrowser.
    """

    parent_revision_selected = pyqtSignal([], [int])
    revision_selected = pyqtSignal([], [int])

    def __init__(self, parent=None):
        super(RevisionDescriptionView, self).__init__(parent)
        self.excluded = ()
        self.descwidth = 60 # number of chars displayed for parent/child descriptions

        self.add_action(
            'rst', self.tr("Fancy description"),
            menu=self.tr("Display"),
            tip=self.tr('Interpret ReST comments'),
            callback=self.refreshDisplay,
            checked=bool(rst2html),
            enabled=bool(rst2html),
            )
        self.anchorClicked.connect(self.on_anchor_clicked)

    def on_anchor_clicked(self, qurl):
        """
        Callback called when a link is clicked in the text browser
        """
        rev = pycompat.unicode(qurl.toString())

        diff = False
        if rev.startswith('diff_'):
            rev = int(rev[5:])
            diff = True
        elif rev.isdigit():
            rev = int(rev)
        else:  # must be some kind of string
            rev = tohg(rev)

        try:
            rev = self.ctx._repo[rev].rev()
        except RepoError:
            QtGui.QDesktopServices.openUrl(qurl)
            self.refreshDisplay()

        if diff:
            self.diffrev = rev
            self.refreshDisplay()
            # TODO: emit a signal to recompute the diff
            if self.diffrev is None:
                self.parent_revision_selected.emit()
            else:
                self.parent_revision_selected[int].emit(self.diffrev)
        else:
            if rev is None:
                self.revision_selected.emit()
            else:
                self.revision_selected[int].emit(rev)

    def setDiffRevision(self, rev):
        if rev != self.diffrev:
            self.diffrev = rev
            self.refreshDisplay()

    def displayRevision(self, ctx):
        self.ctx = ctx
        self.diffrev = ctx.parents()[0].rev()
        if hasattr(self.ctx._repo, "mq"):
            self.mqseries = self.ctx._repo.mq.series[:]
            self.mqunapplied = [x[1] for x in self.ctx._repo.mq.unapplied(self.ctx._repo)]
            mqpatch = set(self.ctx.tags()).intersection(self.mqseries)
            if mqpatch:
                self.mqpatch = mqpatch.pop()
            else:
                self.mqpatch = None
        else:
            self.mqseries = []
            self.mqunapplied = []
            self.mqpatch = None

        self.refreshDisplay()

    def selectNone(self):
        cursor = self.textCursor()
        cursor.clearSelection()
        cursor.setPosition(0)
        self.setTextCursor(cursor)
        self.setExtraSelections([])

    def searchString(self, text):
        self.selectNone()
        if text in self.toPlainText():
            clist = []
            while self.find(text):
                eselect = self.ExtraSelection()
                eselect.cursor = self.textCursor()
                eselect.format.setBackground(QtGui.QColor('#ffffbb'))
                clist.append(eselect)
            self.selectNone()
            self.setExtraSelections(clist)
            def finditer(self, text):
                if text:
                    while True:
                        if self.find(text):
                            yield self.ctx.rev(), None
                        else:
                            break
            return finditer(self, text)

    def refreshDisplay(self):
        ctx = self.ctx
        rev = ctx.rev()
        cfg = HgConfig(ctx._repo.ui)
        buf = u"<table width=100%>\n"
        if self.mqpatch:
            buf += u'<tr bgcolor=%s>' % cfg.getMQFGColor()
            buf += u'<td colspan=4 width=100%><b>Patch queue:</b>&nbsp;'
            for p in self.mqseries:
                if p in self.mqunapplied:
                    p = u"<i>%s</i>" % tounicode(p)
                elif p == self.mqpatch:
                    p = u"<b>%s</b>" % tounicode(p)
                buf += u'&nbsp;%s&nbsp;' % p
            buf += u'</td></tr>\n'

        buf += u'<tr>'
        if rev is None:
            buf += u"<td><b>Working Directory</b></td>\n"
        else:
            buf += u'<td title="Revision"><b>'\
                   u'<span class="rev_number">%s</span>:'\
                   u'<span class="rev_hash">%s</span>'\
                   u'</b></td>\n' % (ctx.rev(), tostr(short_hex(ctx.node())))

        user = tounicode(ctx.user()) if ctx.node() else u''
        buf += '<td title="Author">%s</td>\n' % user
        buf += '<td title="Branch name">%s</td>\n' % tounicode(ctx.branch())
        buf += '<td title="Phase name">%s</td>\n' % tounicode(ctx.phasestr())
        buf += '</tr>'
        buf += "</table>\n"

        buf += "<table width=100%>\n"
        parents = [p for p in ctx.parents() if p]
        for p in parents:
            if p.rev() > -1:
                buf += self._html_ctx_info(p, 'Parent', 'Direct ancestor of this changeset')
        if len(parents) == 2:
            p = parents[0].ancestor(parents[1])
            buf += self._html_ctx_info(p, 'Ancestor', 'Direct ancestor of this changeset')

        for p in ctx.children():
            r = p.rev()
            if r > -1 and r not in self.excluded:
                buf += self._html_ctx_info(p, 'Child', 'Direct descendant of this changeset')
        for prec in first_known_precursors(ctx, self.excluded):
            buf += self._html_ctx_info(prec, 'Precursor',
                'Previous version obsolete by this changeset')
        for suc in first_known_successors(ctx, self.excluded):
            buf += self._html_ctx_info(suc, 'Successors',
                'Updated version that make this changeset obsolete')
        bookmarks = ', '.join(tounicode(bookmark) for bookmark in ctx.bookmarks())
        if bookmarks:
            buf += '<tr><td width=50 class="label"><b>Bookmarks:</b></td>'\
                   '<td colspan=5>&nbsp;'\
                   '<span class="short_desc">%s</span></td></tr>'\
                   '\n' % bookmarks
        troubles = ctx.instabilities()
        if troubles:
            span = u'<span title="%s"  style="color: red;">%s</span>'
            content = u', '.join([span % (TROUBLE_EXPLANATIONS[troub], troub)
                                 for troub in troubles])
            buf += '<tr><td width=50 class="label"><b>Troubles:</b></td>'\
                   '<td colspan=5>&nbsp;'\
                   '<span class="short_desc" >%s</span></td></tr>'\
                   '\n' % ''.join(content)
        buf += u"</table>\n"
        desc = tounicode(ctx.description())
        if self.get_action('rst').isChecked():
            replace = cfg.getFancyReplace()
            if replace:
                desc = replace(desc)
            desc = rst2html(desc, cfg.getDescriptionStylePath())
        else:
            desc = raw2html(desc)
        buf += u'<div class="diff_desc">%s</div>\n' % desc
        self.setHtml(buf)

    def _html_ctx_info(self, ctx, title, tooltip=None):
        isdiffrev = ctx.rev() == self.diffrev
        if not tooltip:
            tooltip = title
        short = tostr(short_hex(ctx.node() if getattr(ctx, 'applied', True) else ctx.node()))
        descr = format_desc(ctx.description(), self.descwidth)
        rev = ctx.rev()
        out = '<tr>'\
              '<td width=60 class="label" title="%(tooltip)s"><b>%(title)s:</b></td>'\
              '<td colspan=5>' % locals()
        if isdiffrev:
            out += '<b>'
        out += '<span class="rev_number">'\
               '<a href="diff_%(rev)s" class="rev_diff" title="display diff from there">%(rev)s</a>'\
               '</span>:'\
               '<a title="go to there" href="%(rev)s" class="rev_hash">%(short)s</a>&nbsp;'\
               '<span class="short_desc"><i>%(descr)s</i></span>' % locals()
        if isdiffrev:
            out += '</b>'
        out += '</td></tr>\n'
        return out
back to top