/tortoisehg/hgqt/rejects.py
https://bitbucket.org/tortoisehg/hgtk/ · Python · 252 lines · 215 code · 31 blank · 6 comment · 20 complexity · f59d4ebfda04ab1e7be1cde2922e213d MD5 · raw file
- # rejects.py - TortoiseHg patch reject editor
- #
- # Copyright 2011 Steve Borho <steve@borho.org>
- #
- # This software may be used and distributed according to the terms
- # of the GNU General Public License, incorporated herein by reference.
- import cStringIO
- import os
- from mercurial import hg, util, patch, commands
- from hgext import record
- from tortoisehg.util import hglib
- from tortoisehg.util.patchctx import patchctx
- from tortoisehg.hgqt.i18n import _
- from tortoisehg.hgqt import qtlib, qscilib, lexers
- from PyQt4.QtCore import *
- from PyQt4.QtGui import *
- from PyQt4 import Qsci
- qsci = Qsci.QsciScintilla
- class RejectsDialog(QDialog):
- def __init__(self, path, parent):
- super(RejectsDialog, self).__init__(parent)
- self.setWindowTitle(_('Merge rejected patch chunks into %s') %
- hglib.tounicode(path))
- self.path = path
- self.setLayout(QVBoxLayout())
- editor = qscilib.Scintilla()
- editor.setBraceMatching(qsci.SloppyBraceMatch)
- editor.setMarginLineNumbers(1, True)
- editor.setMarginWidth(1, '000')
- editor.setFolding(qsci.BoxedTreeFoldStyle)
- editor.installEventFilter(qscilib.KeyPressInterceptor(self))
- editor.setContextMenuPolicy(Qt.CustomContextMenu)
- editor.customContextMenuRequested.connect(self.menuRequested)
- self.baseLineColor = editor.markerDefine(qsci.Background, -1)
- editor.setMarkerBackgroundColor(QColor('lightblue'), self.baseLineColor)
- self.layout().addWidget(editor, 3)
- searchbar = qscilib.SearchToolBar(self, hidable=True)
- searchbar.searchRequested.connect(editor.find)
- searchbar.conditionChanged.connect(editor.highlightText)
- searchbar.hide()
- def showsearchbar():
- searchbar.show()
- searchbar.setFocus(Qt.OtherFocusReason)
- QShortcut(QKeySequence.Find, self, showsearchbar)
- self.layout().addWidget(searchbar)
- hbox = QHBoxLayout()
- hbox.setContentsMargins(2, 2, 2, 2)
- self.layout().addLayout(hbox, 1)
- self.chunklist = QListWidget(self)
- self.updating = True
- self.chunklist.currentRowChanged.connect(self.showChunk)
- hbox.addWidget(self.chunklist, 1)
- bvbox = QVBoxLayout()
- bvbox.setContentsMargins(2, 2, 2, 2)
- self.resolved = tb = QToolButton()
- tb.setIcon(qtlib.geticon('success'))
- tb.setToolTip(_('Mark this chunk as resolved, goto next unresolved'))
- tb.pressed.connect(self.resolveCurrentChunk)
- self.unresolved = tb = QToolButton()
- tb.setIcon(qtlib.geticon('warning'))
- tb.setToolTip(_('Mark this chunk as unresolved'))
- tb.pressed.connect(self.unresolveCurrentChunk)
- bvbox.addStretch(1)
- bvbox.addWidget(self.resolved, 0)
- bvbox.addWidget(self.unresolved, 0)
- bvbox.addStretch(1)
- hbox.addLayout(bvbox, 0)
- self.editor = editor
- self.rejectbrowser = RejectBrowser(self)
- hbox.addWidget(self.rejectbrowser, 5)
- BB = QDialogButtonBox
- bb = QDialogButtonBox(BB.Save|BB.Cancel)
- bb.accepted.connect(self.accept)
- bb.rejected.connect(self.reject)
- self.layout().addWidget(bb)
- self.saveButton = bb.button(BB.Save)
- s = QSettings()
- self.restoreGeometry(s.value('rejects/geometry').toByteArray())
- self.editor.loadSettings(s, 'rejects/editor')
- self.rejectbrowser.loadSettings(s, 'rejects/rejbrowse')
- f = QFile(path)
- f.open(QIODevice.ReadOnly)
- editor.read(f)
- editor.setModified(False)
- lexer = lexers.get_lexer(path, f.readData(1024), self)
- editor.setLexer(lexer)
- buf = cStringIO.StringIO()
- try:
- buf.write('diff -r aaaaaaaaaaaa -r bbbbbbbbbbb %s\n' % path)
- buf.write(open(path + '.rej', 'r').read())
- buf.seek(0)
- except IOError, e:
- pass
- try:
- header = record.parsepatch(buf)[0]
- self.chunks = header.hunks
- except (patch.PatchError, IndexError), e:
- self.chunks = []
- for chunk in self.chunks:
- chunk.resolved = False
- self.updateChunkList()
- self.saveButton.setDisabled(len(self.chunks))
- self.resolved.setDisabled(True)
- self.unresolved.setDisabled(True)
- QTimer.singleShot(0, lambda: self.chunklist.setCurrentRow(0))
- def menuRequested(self, point):
- point = self.editor.mapToGlobal(point)
- return self.editor.createStandardContextMenu().exec_(point)
- def updateChunkList(self):
- self.updating = True
- self.chunklist.clear()
- for chunk in self.chunks:
- self.chunklist.addItem('@@ %d %s' % (chunk.fromline,
- chunk.resolved and '(resolved)' or '(unresolved)'))
- self.updating = False
- @pyqtSlot()
- def resolveCurrentChunk(self):
- row = self.chunklist.currentRow()
- chunk = self.chunks[row]
- chunk.resolved = True
- self.updateChunkList()
- for i, chunk in enumerate(self.chunks):
- if not chunk.resolved:
- self.chunklist.setCurrentRow(i)
- return
- else:
- self.chunklist.setCurrentRow(row)
- self.saveButton.setEnabled(True)
- @pyqtSlot()
- def unresolveCurrentChunk(self):
- row = self.chunklist.currentRow()
- chunk = self.chunks[row]
- chunk.resolved = False
- self.updateChunkList()
- self.chunklist.setCurrentRow(row)
- self.saveButton.setEnabled(False)
- @pyqtSlot(int)
- def showChunk(self, row):
- if row == -1 or self.updating:
- return
- buf = cStringIO.StringIO()
- chunk = self.chunks[row]
- chunk.write(buf)
- self.rejectbrowser.showChunk(buf.getvalue().splitlines()[1:])
- self.editor.setCursorPosition(chunk.fromline-1, 0)
- self.editor.ensureLineVisible(chunk.fromline-1)
- self.editor.markerDeleteAll(-1)
- self.editor.markerAdd(chunk.fromline-1, self.baseLineColor)
- self.resolved.setEnabled(not chunk.resolved)
- self.unresolved.setEnabled(chunk.resolved)
- def saveSettings(self):
- s = QSettings()
- s.setValue('rejects/geometry', self.saveGeometry())
- self.editor.saveSettings(s, 'rejects/editor')
- self.rejectbrowser.saveSettings(s, 'rejects/rejbrowse')
- def accept(self):
- f = QFile(self.path)
- f.open(QIODevice.WriteOnly)
- self.editor.write(f)
- self.saveSettings()
- super(RejectsDialog, self).accept()
- def reject(self):
- self.saveSettings()
- super(RejectsDialog, self).reject()
- class RejectBrowser(qscilib.Scintilla):
- 'Display a rejected diff hunk in an easily copy/pasted format'
- def __init__(self, parent):
- super(RejectBrowser, self).__init__(parent)
- self.setFrameStyle(0)
- self.setReadOnly(True)
- self.setUtf8(True)
- self.installEventFilter(qscilib.KeyPressInterceptor(self))
- self.setContextMenuPolicy(Qt.CustomContextMenu)
- self.customContextMenuRequested.connect(self.menuRequested)
- self.setCaretLineVisible(False)
- self.setMarginType(1, qsci.SymbolMargin)
- self.setMarginLineNumbers(1, False)
- self.setMarginWidth(1, QFontMetrics(self.font()).width('XX'))
- self.setMarginSensitivity(1, True)
- self.addedMark = self.markerDefine(qsci.Plus, -1)
- self.removedMark = self.markerDefine(qsci.Minus, -1)
- self.addedColor = self.markerDefine(qsci.Background, -1)
- self.removedColor = self.markerDefine(qsci.Background, -1)
- self.setMarkerBackgroundColor(QColor('lightgreen'), self.addedColor)
- self.setMarkerBackgroundColor(QColor('cyan'), self.removedColor)
- mask = (1 << self.addedMark) | (1 << self.removedMark) | \
- (1 << self.addedColor) | (1 << self.removedColor)
- self.setMarginMarkerMask(1, mask)
- lexer = lexers.get_diff_lexer(self)
- self.setLexer(lexer)
- def menuRequested(self, point):
- point = self.mapToGlobal(point)
- return self.createStandardContextMenu().exec_(point)
- def showChunk(self, lines):
- utext = []
- added = []
- removed = []
- for i, line in enumerate(lines):
- utext.append(hglib.tounicode(line[1:]))
- if line[0] == '+':
- added.append(i)
- elif line[0] == '-':
- removed.append(i)
- self.markerDeleteAll(-1)
- self.setText(u'\n'.join(utext))
- for i in added:
- self.markerAdd(i, self.addedMark)
- self.markerAdd(i, self.addedColor)
- for i in removed:
- self.markerAdd(i, self.removedMark)
- self.markerAdd(i, self.removedColor)
- def run(ui, *pats, **opts):
- if len(pats) != 1:
- qtlib.ErrorMsgBox(_('Filename required'),
- _('You must provide the path to a file'))
- import sys; sys.exit()
- path = pats[0]
- if path.endswith('.rej'):
- path = path[:-4]
- dlg = RejectsDialog(path, None)
- return dlg