Mercurial and Git clients can push and pull from this alias URL to interact with this repository. You can change to which repository an alias points by going to the Aliases link on the project page.
stable
fileview: Add find next/prev support to diff view
The way this has been done tries to follow the existing implementation for the other file view modes (file and annotate). This means that the nextDiff and prevDiff methods are unchanged.
The main difference between the diff mode and the other modes is that in order to generate the diff markers we use a regular expression that matches the diff "header" ("^@@ -[0-9]+,[0-9]+ \+[0-9]+,[0-9]+ @@$"), rather than callling difflib.SequenceMatcher and parsing the resulting opcodes.
# fileview.py - File diff, content, and annotation display widget## Copyright 2010 Steve Borho <steve@borho.org>## This software may be used and distributed according to the terms of the# GNU General Public License version 2, incorporated herein by reference.import os
import difflib
+import refrom mercurial import error, util
fromtortoisehg.utilimporthglib,patchctx,colormap,thread2fromtortoisehg.hgqt.i18nimport_fromtortoisehg.hgqtimportqscilib,qtlib,blockmatcher,lexersfromtortoisehg.hgqtimportvisdiff,filedatafromPyQt4.QtCoreimport*fromPyQt4.QtGuiimport*fromPyQt4importQsciqsci=Qsci.QsciScintillaDiffMode=1FileMode=2AnnMode=3class HgFileView(QFrame):
"file diff, content, and annotation viewer"
+ diffHeaderRegExp = re.compile("^@@ -[0-9]+,[0-9]+ \+[0-9]+,[0-9]+ @@$")+ linkActivated = pyqtSignal(QString)
fileDisplayed = pyqtSignal(QString, QString)
showMessage = pyqtSignal(QString)
revisionSelected=pyqtSignal(int)shelveToolExited=pyqtSignal()grepRequested=pyqtSignal(unicode,dict)"""Emitted (pattern, opts) when user request to search changelog"""def__init__(self,repo,parent):QFrame.__init__(self,parent)framelayout=QVBoxLayout(self)framelayout.setContentsMargins(0,0,0,0)framelayout.setSpacing(0)l=QHBoxLayout()l.setContentsMargins(0,0,0,0) l.setSpacing(0)
self.repo = repo
+ self._diffs = []+ self.topLayout = QVBoxLayout()
self.labelhbox = hbox = QHBoxLayout()
hbox.setContentsMargins(0,0,0,0)hbox.setSpacing(2)self.topLayout.addLayout(hbox)self.diffToolbar=QToolBar(_('Diff Toolbar'))self.diffToolbar.setIconSize(QSize(16,16))hbox.addWidget(self.diffToolbar)self.filenamelabel=w=QLabel()w.setWordWrap(True)f=w.textInteractionFlags()w.setTextInteractionFlags(f|Qt.TextSelectableByMouse)w.linkActivated.connect(self.linkActivated)hbox.addWidget(w,1)self.extralabel=w=QLabel()w.setWordWrap(True)w.linkActivated.connect(self.linkActivated)self.topLayout.addWidget(w)w.hide()framelayout.addLayout(self.topLayout)framelayout.addLayout(l,1)hbox=QHBoxLayout()hbox.setContentsMargins(0,0,0,0)hbox.setSpacing(0)l.addLayout(hbox)self.blk=blockmatcher.BlockList(self)self.sci=AnnotateView(repo,self)hbox.addWidget(self.blk)hbox.addWidget(self.sci,1)self.sci.showMessage.connect(self.showMessage)self.sci.setAnnotationEnabled(False)self.sci.setContextMenuPolicy(Qt.CustomContextMenu)self.sci.customContextMenuRequested.connect(self.menuRequest)self.blk.linkScrollBar(self.sci.verticalScrollBar())self.blk.setVisible(False)self.sci.setFrameStyle(0)self.sci.setReadOnly(True)self.sci.setUtf8(True)self.sci.installEventFilter(qscilib.KeyPressInterceptor(self))self.sci.setCaretLineVisible(False)# define markers for colorize zones of diffself.markerplus=self.sci.markerDefine(qsci.Background)self.markerminus=self.sci.markerDefine(qsci.Background)self.markertriangle=self.sci.markerDefine(qsci.Background)self.sci.setMarkerBackgroundColor(QColor('#B0FFA0'),self.markerplus)self.sci.setMarkerBackgroundColor(QColor('#A0A0FF'),self.markerminus)self.sci.setMarkerBackgroundColor(QColor('#FFA0A0'),self.markertriangle)# hide margin 0 (markers)self.sci.setMarginType(0,qsci.SymbolMargin)self.sci.setMarginWidth(0,0)self.searchbar=qscilib.SearchToolBar(hidable=True)self.searchbar.hide()self.searchbar.searchRequested.connect(self.find)self.searchbar.conditionChanged.connect(self.highlightText)self.layout().addWidget(self.searchbar)self._ctx=Noneself._filename=Noneself._status=Noneself._mode=Noneself._parent=0self._lostMode=Noneself._lastSearch=u'',Falseself.actionDiffMode=QAction(qtlib.geticon('view-diff'),_('View change as unified diff output'),self)self.actionDiffMode.setCheckable(True)self.actionDiffMode._mode=DiffModeself.actionFileMode=QAction(qtlib.geticon('view-file'),_('View change in context of file'),self)self.actionFileMode.setCheckable(True)self.actionFileMode._mode=FileModeself.actionAnnMode=QAction(qtlib.geticon('view-annotate'),_('annotate with revision numbers'),self)self.actionAnnMode.setCheckable(True)self.actionAnnMode._mode=AnnModeself.modeToggleGroup=QActionGroup(self)self.modeToggleGroup.addAction(self.actionDiffMode)self.modeToggleGroup.addAction(self.actionFileMode)self.modeToggleGroup.addAction(self.actionAnnMode)self.modeToggleGroup.triggered.connect(self.setMode)# Next/Prev diff (in full file mode)self.actionNextDiff=QAction(qtlib.geticon('go-down'),_('Next diff (alt+down)'),self)self.actionNextDiff.setShortcut('Alt+Down')self.actionNextDiff.triggered.connect(self.nextDiff)self.actionPrevDiff=QAction(qtlib.geticon('go-up'),_('Previous diff (alt+up)'),self)self.actionPrevDiff.setShortcut('Alt+Up')self.actionPrevDiff.triggered.connect(self.prevDiff)self.setMode(self.actionDiffMode)self.actionFirstParent=QAction('1',self)self.actionFirstParent.setCheckable(True)self.actionFirstParent.setChecked(True)self.actionFirstParent.setShortcut('CTRL+1')self.actionFirstParent.setToolTip(_('Show changes from first parent'))self.actionSecondParent=QAction('2',self)self.actionSecondParent.setCheckable(True)self.actionSecondParent.setShortcut('CTRL+2')self.actionSecondParent.setToolTip(_('Show changes from second parent'))self.parentToggleGroup=QActionGroup(self)self.parentToggleGroup.addAction(self.actionFirstParent)self.parentToggleGroup.addAction(self.actionSecondParent)self.parentToggleGroup.triggered.connect(self.setParent)self.actionFind=self.searchbar.toggleViewAction()self.actionFind.setIcon(qtlib.geticon('edit-find'))self.actionFind.setToolTip(_('Toggle display of text search bar'))self.actionFind.setShortcut(QKeySequence.Find)self.actionShelf=QAction('Shelve',self)self.actionShelf.setIcon(qtlib.geticon('shelve'))self.actionShelf.setToolTip(_('Open shelve tool'))self.actionShelf.triggered.connect(self.launchShelve)tb=self.diffToolbartb.addAction(self.actionFirstParent)tb.addAction(self.actionSecondParent)tb.addSeparator()tb.addAction(self.actionDiffMode)tb.addAction(self.actionFileMode)tb.addAction(self.actionAnnMode)tb.addSeparator()tb.addAction(self.actionNextDiff)tb.addAction(self.actionPrevDiff)tb.addSeparator()tb.addAction(self.actionFind)tb.addAction(self.actionShelf)self.timer=QTimer()self.timer.setSingleShot(False)self.timer.timeout.connect(self.timerBuildDiffMarkers)deflaunchShelve(self):fromtortoisehg.hgqtimportshelve# TODO: pass self._filenamedlg=shelve.ShelveDialog(self.repo,self)dlg.finished.connect(dlg.deleteLater)dlg.exec_()self.shelveToolExited.emit()defsetFont(self,font):self.sci.setFont(font)defloadSettings(self,qs,prefix):self.sci.loadSettings(qs,prefix)defsaveSettings(self,qs,prefix):self.sci.saveSettings(qs,prefix)defsetRepo(self,repo):self.repo=repoself.sci.repo=repo@pyqtSlot(QAction)defsetMode(self,action):'One of the mode toolbar buttons has been toggled'mode=action._modeself._lostMode=modeifmode!=self._mode:self._mode=modeself.actionNextDiff.setEnabled(False)self.actionPrevDiff.setEnabled(False)self.blk.setVisible(mode!=DiffMode)self.sci.setAnnotationEnabled(mode==AnnMode)self.displayFile(self._filename,self._status)@pyqtSlot(QAction)defsetParent(self,action):ifaction.text()=='1':parent=0else:parent=1ifself._parent!=parent:self._parent=parentself.displayFile(self._filename,self._status)defrestrictModes(self,candiff,canfile,canann):'Disable modes based on content constraints'self.actionDiffMode.setEnabled(candiff)self.actionFileMode.setEnabled(canfile)self.actionAnnMode.setEnabled(canann)# Switch mode if necessarymode=self._modeifnotcandiffandmode==DiffModeandcanfile:mode=FileModeifnotcanfileandmode!=DiffMode:mode=DiffModeifself._lostModeisNone:self._lostMode=self._modeifself._mode!=mode:self.actionNextDiff.setEnabled(False)self.actionPrevDiff.setEnabled(False)self.blk.setVisible(mode!=DiffMode)self.sci.setAnnotationEnabled(mode==AnnMode)self._mode=modeifself._mode==DiffMode:self.actionDiffMode.setChecked(True)elifself._mode==FileMode:self.actionFileMode.setChecked(True)else:self.actionAnnMode.setChecked(True)defsetContext(self,ctx,ctx2=None):self._ctx=ctxself._ctx2=ctx2self.sci.setTabWidth(ctx._repo.tabwidth)self.actionAnnMode.setVisible(ctx.rev()!=None)self.actionShelf.setVisible(ctx.rev()==None)self.actionFirstParent.setVisible(len(ctx.parents())==2)self.actionSecondParent.setVisible(len(ctx.parents())==2)self.actionFirstParent.setEnabled(len(ctx.parents())==2)self.actionSecondParent.setEnabled(len(ctx.parents())==2)defshowLine(self,line):ifline<self.sci.lines():self.sci.setCursorPosition(line,0) @pyqtSlot()
def clearDisplay(self):
self._filename = None
+ self._diffs = [] self.restrictModes(False, False, False)
self.sci.setMarginWidth(1, 0)
self.clearMarkup()
defclearMarkup(self):self.sci.clear()self.blk.clear()# Setting the label to ' ' rather than clear() keeps the label# from disappearing during refresh, and tool layouts bouncingself.filenamelabel.setText(' ')self.extralabel.hide()self.actionNextDiff.setEnabled(False)self.actionPrevDiff.setEnabled(False)defdisplayFile(self,filename=None,status=None):ifisinstance(filename,(unicode,QString)):filename=hglib.fromunicode(filename)status=hglib.fromunicode(status)iffilenameandself._filename==filename:# Get the last visible line to restore it after reloading the editorlastScrollPosition=self.sci.firstVisibleLine()else:# Reset the scroll positions when the file is changedlastScrollPosition=0 self._filename, self._status = filename, status
self.clearMarkup()
+ self._diffs = [] if filename is None:
self.restrictModes(False, False, False)
return
ifself._ctx2:ctx2=self._ctx2elifself._parent==0orlen(self._ctx.parents())==1:ctx2=self._ctx.p1()else:ctx2=self._ctx.p2()fd=filedata.FileData(self._ctx,ctx2,filename,status)iffd.elabel:self.extralabel.setText(fd.elabel)self.extralabel.show()else:self.extralabel.hide()self.filenamelabel.setText(fd.flabel)ifnotfd.isValid():self.sci.setText(fd.error)self.sci.setLexer(None)self.sci.setFont(qtlib.getfont('fontlog').font())self.sci.setMarginWidth(1,0)self.blk.setVisible(False)self.restrictModes(False,False,False)returncandiff=bool(fd.diff)canfile=bool(fd.contentsorfd.ucontents)canann=bool(fd.contents)andtype(self._ctx.rev())isintifnotcandiffornotcanfile:self.restrictModes(candiff,canfile,canann)else:self.actionDiffMode.setEnabled(True)self.actionFileMode.setEnabled(True)self.actionAnnMode.setEnabled(True)ifself._lostMode:self._mode=self._lostModeifself._lostMode==DiffMode:self.actionDiffMode.trigger()elifself._lostMode==FileMode:self.actionFileMode.trigger()elifself._lostMode==AnnMode:self.actionAnnMode.trigger()self._lostMode=Noneself.blk.setVisible(self._mode!=DiffMode)self.sci.setAnnotationEnabled(self._mode==AnnMode)ifself._mode==DiffMode:self.sci.setMarginWidth(1,0)lexer=lexers.get_diff_lexer(self)self.sci.setLexer(lexer)iflexerisNone:self.sci.setFont(qtlib.getfont('fontlog').font())# trim first three lines, for example:# diff -r f6bfc41af6d7 -r c1b18806486d tortoisehg/hgqt/thgrepo.py# --- a/tortoisehg/hgqt/thgrepo.py# +++ b/tortoisehg/hgqt/thgrepo.pyiffd.diff:out=fd.diff.split('\n',3)iflen(out)==4:self.sci.setText(hglib.tounicode(out[3]))else:# there was an error or rename without diffsself.sci.setText(hglib.tounicode(fd.diff))eliffd.ucontents:# subrepo summary and perhaps other dataself.sci.setText(fd.ucontents)self.sci.setLexer(None)self.sci.setFont(qtlib.getfont('fontlog').font())self.sci.setMarginWidth(1,0)self.blk.setVisible(False)returneliffd.contents:lexer=lexers.get_lexer(filename,fd.contents,self)self.sci.setLexer(lexer)iflexerisNone:self.sci.setFont(qtlib.getfont('fontlog').font())self.sci.setText(hglib.tounicode(fd.contents))self.blk.setVisible(True)self.sci._updatemarginwidth()ifself._mode==AnnMode:self.sci._updateannotation(self._ctx,filename)else:return# Recover the last scroll position# Make sure that lastScrollPosition never exceeds the amount of# lines on the editorlastScrollPosition=min(lastScrollPosition,self.sci.lines()-1)self.sci.verticalScrollBar().setValue(lastScrollPosition)self.highlightText(*self._lastSearch)uf=hglib.tounicode(filename)uc=hglib.tounicode(fd.contents)or''self.fileDisplayed.emit(uf,uc)ifself._mode!=DiffMode: self.blk.setVisible(True)
self.blk.syncPageStep()
- if self._mode != DiffMode and fd.contents and fd.olddata:
+ if fd.contents and fd.olddata:
if self.timer.isActive():
self.timer.stop()
self._fd = fd
self.timer.start()
+ self.actionNextDiff.setEnabled(bool(self._diffs))+ self.actionPrevDiff.setEnabled(bool(self._diffs)) #
# These four functions are used by Shift+Cursor actions in revdetails
#defnextLine(self):x,y=self.sci.getCursorPosition()self.sci.setCursorPosition(x+1,y)defprevLine(self):x,y=self.sci.getCursorPosition()self.sci.setCursorPosition(x-1,y)defnextCol(self):x,y=self.sci.getCursorPosition()self.sci.setCursorPosition(x,y+1)defprevCol(self):x,y=self.sci.getCursorPosition()self.sci.setCursorPosition(x,y-1)@pyqtSlot(unicode,bool,bool,bool)deffind(self,exp,icase=True,wrap=False,forward=True):self.sci.find(exp,icase,wrap,forward)@pyqtSlot(unicode,bool)defhighlightText(self,match,icase=False):self._lastSearch=match,icaseself.sci.highlightText(match,icase)defverticalScrollBar(self):returnself.sci.verticalScrollBar()## file mode diff markers #
def timerBuildDiffMarkers(self):
'show modified and added lines in the self.blk margin'
+ # The way the diff markers are generated differs between the DiffMode+ # and the other modes+ # In the DiffMode case, the marker positions are found by looking for+ # lines matching a regular expression representing a diff header, while+ # in all other cases we use the difflib.SequenceMatcher, which returns+ # a set of opcodes that must be parsed+ # In any case, the markers are generated incrementally. This function is+ # run by a timer, which each time that is called processes a bunch of+ # lines (when in DiffMode) or of opcodes (in all other modes).+ # When there are no more lines or opcodes to consume the timer is+ # stopped.+ self.sci.setUpdatesEnabled(False)
self.blk.setUpdatesEnabled(False)
- if self._fd:
- olddata = self._fd.olddata.splitlines()
- newdata = self._fd.contents.splitlines()
- diff = difflib.SequenceMatcher(None, olddata, newdata)
- self._opcodes = diff.get_opcodes()
- self._fd = None
- self._diffs = []
+ if self._mode == DiffMode:+ if self._fd:
+ self._fd = None+ self._diffs = []+ self._linestoprocess = \+ hglib.fromunicode(self.sci.text()).splitlines()+ self._firstlinetoprocess = 0+ self._opcodes = True+ # Process linesPerBlock lines at a time+ linesPerBlock = 100+ # Look for lines matching the "diff header"+ for n, line in enumerate(self._linestoprocess[:linesPerBlock]):+ if self.diffHeaderRegExp.match(line):+ diffLine = self._firstlinetoprocess + n+ self._diffs.append([diffLine, diffLine])+ self.sci.markerAdd(diffLine, self.markerplus)+ self._linestoprocess = self._linestoprocess[linesPerBlock:]+ self._firstlinetoprocess += linesPerBlock+ if not self._linestoprocess:+ self._opcodes = False+ self._firstlinetoprocess = 0+ else:+ if self._fd:+olddata = self._fd.olddata.splitlines()
+newdata = self._fd.contents.splitlines()
+ diff = difflib.SequenceMatcher(None, olddata, newdata)
+self._opcodes = diff.get_opcodes()
+self._fd = None
+ self._diffs = []
- for tag, alo, ahi, blo, bhi in self._opcodes[:30]:
- if tag == 'replace':
- self._diffs.append([blo, bhi])
- self.blk.addBlock('x', blo, bhi)
- for i in range(blo, bhi):
- self.sci.markerAdd(i, self.markertriangle)
- elif tag == 'insert':
- self._diffs.append([blo, bhi])
- self.blk.addBlock('+', blo, bhi)
- for i in range(blo, bhi):
- self.sci.markerAdd(i, self.markerplus)
- elif tag in ('equal', 'delete'):
- pass
- else:
- raise ValueError, 'unknown tag %r' % (tag,)
+ for tag, alo, ahi, blo, bhi in self._opcodes[:30]:
+if tag == 'replace':
+self._diffs.append([blo, bhi])
+self.blk.addBlock('x', blo, bhi)
+for i in range(blo, bhi):
+self.sci.markerAdd(i, self.markertriangle)
+ elif tag == 'insert':
+self._diffs.append([blo, bhi])
+self.blk.addBlock('+', blo, bhi)
+for i in range(blo, bhi):
+self.sci.markerAdd(i, self.markerplus)
+elif tag in ('equal', 'delete'):
+pass
+else:
+ raise ValueError, 'unknown tag %r' % (tag,)
- self._opcodes = self._opcodes[30:]
+ self._opcodes = self._opcodes[30:]
+ if not self._opcodes:
self.actionNextDiff.setEnabled(bool(self._diffs))
self.actionPrevDiff.setEnabled(False)
self.timer.stop()self.sci.setUpdatesEnabled(True) self.blk.setUpdatesEnabled(True)
def nextDiff(self):
- if self._mode == DiffMode or not self._diffs:
+ if not self._diffs:
self.actionNextDiff.setEnabled(False)
self.actionPrevDiff.setEnabled(False)
return
- row, column = self.sci.getCursorPosition()- for i, (lo, hi) in enumerate(self._diffs):- if lo > row:- last = (i == (len(self._diffs)-1))- self.sci.setCursorPosition(lo, 0)- self.sci.verticalScrollBar().setValue(lo)- break else:
- last = True
+ row, column = self.sci.getCursorPosition()+ for i, (lo, hi) in enumerate(self._diffs):+ if lo > row:+ last = (i == (len(self._diffs)-1))+ self.sci.setCursorPosition(lo, 0)+ self.sci.verticalScrollBar().setValue(lo)+ break+ else:+ last = True
self.actionNextDiff.setEnabled(not last)
self.actionPrevDiff.setEnabled(True)
def prevDiff(self):
- if self._mode == DiffMode or not self._diffs:
+ if not self._diffs:
self.actionNextDiff.setEnabled(False)
self.actionPrevDiff.setEnabled(False)
return
- row, column = self.sci.getCursorPosition()- for i, (lo, hi) in enumerate(reversed(self._diffs)):- if hi < row:- first = (i == (len(self._diffs)-1))- self.sci.setCursorPosition(lo, 0)- self.sci.verticalScrollBar().setValue(lo)- break else:
- first = True
+ row, column = self.sci.getCursorPosition()+ for i, (lo, hi) in enumerate(reversed(self._diffs)):+ if hi < row:+ first = (i == (len(self._diffs)-1))+ self.sci.setCursorPosition(lo, 0)+ self.sci.verticalScrollBar().setValue(lo)+ break+ else:+ first = True
self.actionNextDiff.setEnabled(True)
self.actionPrevDiff.setEnabled(not first)
defnDiffs(self):returnlen(self._diffs)defeditSelected(self,path,rev,line):"""Open editor to show the specified file"""path=hglib.fromunicode(path)base=visdiff.snapshot(self.repo,[path],self.repo[rev])[0]files=[os.path.join(base,path)]pattern=hglib.fromunicode(self._lastSearch[0])qtlib.editfiles(self.repo,files,line,pattern,self)@pyqtSlot(QPoint)defmenuRequest(self,point):menu=self.sci.createStandardContextMenu()line=self.sci.lineAt(point)point=self.sci.viewport().mapToGlobal(point)selection=self.sci.selectedText()defsreq(**opts):returnlambda:self.grepRequested.emit(selection,opts)defsann():self.searchbar.search(selection)self.searchbar.show()ifself._mode!=AnnMode:ifselection:menu.addSeparator()forname,funcin[(_('Search in current file'),sann),(_('Search in history'),sreq(all=True))]:defadd(name,func):action=menu.addAction(name)action.triggered.connect(func)add(name,func)returnmenu.exec_(point)ifline<0orline>=len(self.sci._links):returnmenu.exec_(point)fctx,line=self.sci._links[line]ifselection:defsreq(**opts):returnlambda:self.grepRequested.emit(selection,opts)defsann():self.searchbar.search(selection)self.searchbar.show()menu.addSeparator()forname,funcin[(_('Search in original revision'),sreq(rev=fctx.rev())),(_('Search in working revision'),sreq(rev='.')),(_('Search in current annotation'),sann),(_('Search in history'),sreq(all=True))]:defadd(name,func):action=menu.addAction(name)action.triggered.connect(func)add(name,func)defsetSource(path,rev,line):self.revisionSelected.emit(rev)self.setContext(self.repo[rev])self.displayFile(path,None)self.showLine(line)data=[hglib.tounicode(fctx.path()),fctx.rev(),line]defannorig():setSource(*data)defeditorig():self.editSelected(*data)menu.addSeparator()forname,funcin[(_('Annotate originating revision'),annorig),(_('View originating revision'),editorig)]:defadd(name,func):action=menu.addAction(name)action.triggered.connect(func)add(name,func)forpfctxinfctx.parents():pdata=[hglib.tounicode(pfctx.path()),pfctx.changectx().rev(),line]defannparent(data):setSource(*data)defeditparent(data):self.editSelected(*data)forname,funcin[(_('Annotate parent revision %d')%pdata[1],annparent),(_('View parent revision %d')%pdata[1],editparent)]:defadd(name,func):action=menu.addAction(name)action.data=pdataaction.run=lambda:func(action.data)action.triggered.connect(action.run)add(name,func)menu.exec_(point)classAnnotateView(qscilib.Scintilla):'QScintilla widget capable of displaying annotations'showMessage=pyqtSignal(QString)def__init__(self,repo,parent=None):super(AnnotateView,self).__init__(parent)self.setReadOnly(True)self.setMarginLineNumbers(1,True)self.setMarginType(2,qsci.TextMarginRightJustified)self.setMouseTracking(False)self._annotation_enabled=Falseself._links=[]# by lineself._revmarkers={}# by revself._lastrev=Noneself._thread=AnnotateThread(self)self._thread.finished.connect(self.fillModel)self.repo=repoself.repo.configChanged.connect(self.configChanged)self.configChanged()defconfigChanged(self):self.setIndentationWidth(self.repo.tabwidth)self.setTabWidth(self.repo.tabwidth)defkeyPressEvent(self,event):ifevent.key()==Qt.Key_Escape:self._thread.abort()returnreturnsuper(AnnotateView,self).keyPressEvent(event)defmouseMoveEvent(self,event):self._emitRevisionHintAtLine(self.lineAt(event.pos()))super(AnnotateView,self).mouseMoveEvent(event)def_emitRevisionHintAtLine(self,line):ifline<0:returntry:fctx=self._links[line][0]iffctx.rev()!=self._lastrev:s=hglib.get_revision_desc(fctx,self.annfile)self.showMessage.emit(s)self._lastrev=fctx.rev()exceptIndexError:passdef_updateannotation(self,ctx,filename):ifctx.rev()isNone:returnwsub,filename,ctx=hglib.getDeepestSubrepoContainingFile(filename,ctx)ifwsubisNone:# The file was not found in the repo context or its subrepos# This may happen for files that have been removedreturnself.ctx=ctxself.annfile=filenameself._thread.abort()self._thread.start(ctx[filename])@pyqtSlot()deffillModel(self):self._thread.wait()ifself._thread.dataisNone:returnself._links=list(self._thread.data)self._updaterevmargin()self._updatemarkers()self._updatemarginwidth()defclear(self):super(AnnotateView,self).clear()self.clearMarginText()self.markerDeleteAll()defsetAnnotationEnabled(self,enabled):"""Enable / disable annotation"""self._annotation_enabled=enabledself._updatemarginwidth()self.setMouseTracking(enabled)ifnotenabled:self.markerDeleteAll()defisAnnotationEnabled(self):"""True if annotation enabled and available"""returnself._annotation_enableddef_updaterevmargin(self):"""Update the content of margin area showing revisions"""s=self._margin_style# Workaround to set style of the current sci widget.# QsciStyle sends style data only to the first sci widget.# See qscintilla2/Qt4/qscistyle.cppself.SendScintilla(qsci.SCI_STYLESETBACK,s.style(),s.paper())self.SendScintilla(qsci.SCI_STYLESETFONT,s.style(),s.font().family().toAscii().data())self.SendScintilla(qsci.SCI_STYLESETSIZE,s.style(),s.font().pointSize())fori,(fctx,_origline)inenumerate(self._links):self.setMarginText(i,str(fctx.rev()),s)def_updatemarkers(self):"""Update markers which colorizes each line"""self._redefinemarkers()fori,(fctx,_origline)inenumerate(self._links):m=self._revmarkers.get(fctx.rev())ifmisnotNone:self.markerAdd(i,m)def_redefinemarkers(self):"""Redefine line markers according to the current revs"""curdate=self.ctx.date()[0]# make sure to colorize at least 1 yearmindate=curdate-365*24*60*60self._revmarkers.clear()filectxs=iter(fctxforfctx,_origlineinself._links)palette=colormap.makeannotatepalette(filectxs,curdate,maxcolors=32,maxhues=8,maxsaturations=16,mindate=mindate)fori,(color,fctxs)inenumerate(palette.iteritems()):self.markerDefine(qsci.Background,i)self.setMarkerBackgroundColor(QColor(color),i)forfctxinfctxs:self._revmarkers[fctx.rev()]=i@util.propertycachedef_margin_style(self):"""Style for margin area"""s=Qsci.QsciStyle()s.setPaper(QApplication.palette().color(QPalette.Window))s.setFont(self.font())returns@pyqtSlot()def_updatemarginwidth(self):self.setMarginsFont(self.font())deflentext(s):return'M'*(len(str(s))+2)# 2 for marginself.setMarginWidth(1,lentext(self.lines()))ifself.isAnnotationEnabled()andself._links:maxrev=max(fctx.rev()forfctx,_origlineinself._links)self.setMarginWidth(2,lentext(maxrev))else:self.setMarginWidth(2,0)classAnnotateThread(QThread):'Background thread for annotating a file at a revision'def__init__(self,parent=None):super(AnnotateThread,self).__init__(parent)self._threadid=None@pyqtSlot(object)defstart(self,fctx):self._fctx=fctxsuper(AnnotateThread,self).start()self.data=None@pyqtSlot()defabort(self):ifself._threadidisNone:returntry:thread2._async_raise(self._threadid,KeyboardInterrupt)self.wait()exceptValueError:passdefrun(self):assertself.currentThread()!=qApp.thread()self._threadid=self.currentThreadId()try:try:data=[]for(fctx,line),_textinself._fctx.annotate(True,True):data.append((fctx,line))self.data=dataexceptKeyboardInterrupt:passfinally:self._threadid=Nonedelself._fctx
Attach a Trello Card
Add a tag
Your session has expired
You are no longer logged in. Please log in and try your request again.
Filter RSS Feed
This RSS feed URL allows you to see the contents of your current filter using any feed reader.
This link includes a special authentication token. If you share the URL with anyone else, they can see this RSS feed's activity. You can disable these tokens when needed.
Your current filter is unsaved; changing it won't affect this RSS feed.