class _Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() self.qpart.lines = [ 'The quick brown fox', 'jumps over the', 'lazy dog', 'back' ] self.qpart.vimModeIndicationChanged.connect(self._onVimModeChanged) self.qpart.vimModeEnabled = True self.vimMode = 'normal' def tearDown(self): self.qpart.hide() self.qpart.terminate() def _onVimModeChanged(self, color, mode): self.vimMode = mode def click(self, keys): if isinstance(keys, str): for key in keys: if key.isupper() or key in '$%^<>': QTest.keyClick(self.qpart, key, Qt.ShiftModifier) else: QTest.keyClicks(self.qpart, key) else: QTest.keyClick(self.qpart, keys)
def mouseDoubleClickEvent(self, event): """Highlight the current word keeping the selection""" Qutepart.mouseDoubleClickEvent(self, event) if self.selectedText: selectedPos = self.absSelectedPosition self.onHighlight() self.absSelectedPosition = selectedPos
class _Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() self.qpart.lines = ['The quick brown fox', 'jumps over the', 'lazy dog', 'back'] self.qpart.vimModeIndicationChanged.connect(self._onVimModeChanged) self.qpart.vimModeEnabled = True self.vimMode = 'normal' def tearDown(self): self.qpart.hide() self.qpart.terminate() def _onVimModeChanged(self, color, mode): self.vimMode = mode def click(self, keys): if isinstance(keys, str): for key in keys: if key.isupper() or key in '$%^<>': QTest.keyClick(self.qpart, key, Qt.ShiftModifier) else: QTest.keyClicks(self.qpart, key) else: QTest.keyClick(self.qpart, keys)
def setUp(self): self.qpart = Qutepart() self.qpart.lines = [ 'The quick brown fox', 'jumps over the', 'lazy dog', 'back' ] self.qpart.vimModeIndicationChanged.connect(self._onVimModeChanged) self.qpart.vimModeEnabled = True self.vimMode = 'normal'
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): del self.qpart def _verify(self, actual, expected): converted = [] for item in actual: if item.format.background().color() == Qt.green: matched = True elif item.format.background().color() == Qt.red: matched = False else: self.fail("Invalid color") start = item.cursor.selectionStart() end = item.cursor.selectionEnd() converted.append((start, end, matched)) self.assertEqual(converted, expected) def test_1(self): self.qpart.lines = \ [ 'func(param,', ' "text ( param"))'] self.qpart.detectSyntax(language = 'Python') while self.qpart.isHighlightingInProgress(): QTest.qWait(20) firstBlock = self.qpart.document().firstBlock() secondBlock = firstBlock.next() bh = BracketHighlighter() self._verify(bh.extraSelections(self.qpart, firstBlock, 1), []) self._verify(bh.extraSelections(self.qpart, firstBlock, 4), [(4, 5, True), (31, 32, True)]) self._verify(bh.extraSelections(self.qpart, firstBlock, 5), [(4, 5, True), (31, 32, True)]) self._verify(bh.extraSelections(self.qpart, secondBlock, 11), []) self._verify(bh.extraSelections(self.qpart, secondBlock, 19), [(31, 32, True), (4, 5, True)]) self._verify(bh.extraSelections(self.qpart, secondBlock, 20), [(31, 32, True), (4, 5, True)]) self._verify(bh.extraSelections(self.qpart, secondBlock, 21), [(32, 33, False)])
class _BaseTest(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): self.qpart.terminate()
def setupEditor(self): font = QtGui.QFont() font.setFamily('Monaco') # font.setFixedPitch(True) font.setPointSize(12) self.editor = Qutepart() self.editor.setFont(font) self.editor.adjustSize() self.editor.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) # self.highlighter = Highlighter(self.editor.document())
def __init__(self, parent): Qutepart.__init__(self, needMarkArea=False, needLineNumbers=False, needCompleter=False) self.setParent(parent) self.encoding = None self.explicitUserEncoding = None self.mime = None # Search/replace support self.__matchesCache = None self.textChanged.connect(self.__resetMatchCache)
def __init__(self, parent): Qutepart.__init__(self, parent) self.encoding = None self.explicitUserEncoding = None self.mime = None # Remove all the default margins self.delMargin('mark_area') self.delMargin('line_numbers') self.completionEnabled = False # Search/replace support self.__matchesCache = None self.textChanged.connect(self.__resetMatchCache)
def __init__(self, parentObject, filePath, createNew=False): """Create editor and open file. If file is None or createNew is True, empty not saved file is created IO Exceptions are not catched, therefore, must be catched on upper level """ QWidget.__init__(self, parentObject) self._neverSaved = filePath is None or createNew self._filePath = filePath self._externallyRemoved = False self._externallyModified = False # File opening should be implemented in the document classes self._fileWatcher = _FileWatcher(filePath) self._fileWatcher.modified.connect(self._onWatcherFileModified) self._fileWatcher.removed.connect(self._onWatcherFileRemoved) if filePath and self._neverSaved: core.mainWindow().appendMessage( 'New file "%s" is going to be created' % filePath, 5000) self.qutepart = Qutepart(self) self.qutepart.setStyleSheet('QPlainTextEdit {border: 0}') self.qutepart.userWarning.connect( lambda text: core.mainWindow().statusBar().showMessage(text, 5000)) self._applyQpartSettings() core.uiSettingsManager().dialogAccepted.connect( self._applyQpartSettings) layout = QVBoxLayout(self) layout.setMargin(0) layout.addWidget(self.qutepart) self.setFocusProxy(self.qutepart) if not self._neverSaved: originalText = self._readFile(filePath) self.qutepart.text = originalText else: originalText = '' #autodetect eol, if need self._configureEolMode(originalText) self._tryDetectSyntax()
def terminate(self): """Cleans up memory""" Qutepart.terminate(self) self.document().disconnect() self._indenter._qpart = None self._indenter = None if self._highlighter is not None: self._highlighter._syntax = None self._highlighter._document = None self._highlighter._textEdit = None self._solidEdgeLine.deleteLater() self._solidEdgeLine = None self._rectangularSelection._qpart = None self._rectangularSelection = None self._lines._qpart = None self._lines._doc = None self._lines = None
def setUp(self): self.qpart = Qutepart() self.qpart.lines = ['The quick brown fox', 'jumps over the', 'lazy dog', 'back'] self.qpart.vimModeIndicationChanged.connect(self._onVimModeChanged) self.qpart.vimModeEnabled = True self.vimMode = 'normal'
class IndentTest(unittest.TestCase): app = base.papp def setOrigin(self, text): self.qpart.text = '\n'.join(text) def verifyExpected(self, text): lines = self.qpart.text.split('\n') self.assertEquals(map(str, lines), text) def setCursorPosition(self, line, col): self.qpart.cursorPosition = line, col def enter(self): QTest.keyClick(self.qpart, Qt.Key_Enter) def tab(self): QTest.keyClick(self.qpart, Qt.Key_Tab) def type(self, text): QTest.keyClicks(self.qpart, text) def writeCursorPosition(self): line, col = self.qpart.cursorPosition text = '(%d,%d)' % (line, col) self.type(text) def writeln(self): self.qpart.textCursor().insertText('\n') def alignLine(self, index): self.qpart._indenter.autoIndentBlock( self.qpart.document().findBlockByNumber(index), '') def alignAll(self): QTest.keyClick(self.qpart, Qt.Key_A, Qt.ControlModifier) self.qpart.autoIndentLineAction.trigger() def setUp(self): self.qpart = Qutepart() if self.LANGUAGE is not None: self.qpart.detectSyntax(language=self.LANGUAGE) self.qpart.indentWidth = self.INDENT_WIDTH
class IndentTest(unittest.TestCase): app = base.papp def setOrigin(self, text): self.qpart.text = '\n'.join(text) def verifyExpected(self, text): lines = self.qpart.text.split('\n') self.assertEquals(map(str, lines), text) def setCursorPosition(self, line, col): self.qpart.cursorPosition = line, col def enter(self): QTest.keyClick(self.qpart, Qt.Key_Enter) def tab(self): QTest.keyClick(self.qpart, Qt.Key_Tab) def type(self, text): QTest.keyClicks(self.qpart, text) def writeCursorPosition(self): line, col = self.qpart.cursorPosition text = '(%d,%d)' % (line, col) self.type(text) def writeln(self): self.qpart.textCursor().insertText('\n') def alignLine(self, index): self.qpart._indenter.autoIndentBlock(self.qpart.document().findBlockByNumber(index), '') def alignAll(self): QTest.keyClick(self.qpart, Qt.Key_A, Qt.ControlModifier) self.qpart.autoIndentLineAction.trigger() def setUp(self): self.qpart = Qutepart() if self.LANGUAGE is not None: self.qpart.detectSyntax(language = self.LANGUAGE) self.qpart.indentWidth = self.INDENT_WIDTH
class EditorTab(QtGui.QWidget): def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.setupEditor() mainLayout = QtGui.QVBoxLayout() mainLayout.addWidget(self.editor) # mainLayout.addStretch(1) mainLayout.setContentsMargins(0, 0, 0, 0) self.editor.adjustSize() self.setLayout(mainLayout) def setupEditor(self): font = QtGui.QFont() font.setFamily('Monaco') # font.setFixedPitch(True) font.setPointSize(12) self.editor = Qutepart() self.editor.setFont(font) self.editor.adjustSize() self.editor.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) # self.highlighter = Highlighter(self.editor.document())
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): del self.qpart def test_overwrite_edit(self): self.qpart.show() self.qpart.text = 'abcd' QTest.keyClicks(self.qpart, "stu") self.assertEqual(self.qpart.text, 'stuabcd') QTest.keyClick(self.qpart, Qt.Key_Insert) QTest.keyClicks(self.qpart, "xy") self.assertEqual(self.qpart.text, 'stuxycd') QTest.keyClick(self.qpart, Qt.Key_Insert) QTest.keyClicks(self.qpart, "z") self.assertEqual(self.qpart.text, 'stuxyzcd') def test_overwrite_backspace(self): self.qpart.show() self.qpart.text = 'abcd' QTest.keyClick(self.qpart, Qt.Key_Insert) for i in range(3): QTest.keyClick(self.qpart, Qt.Key_Right) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Backspace) self.assertEqual(self.qpart.text, 'a d') @base.in_main_loop def test_overwrite_undo(self): self.qpart.show() self.qpart.text = 'abcd' QTest.keyClick(self.qpart, Qt.Key_Insert) QTest.keyClick(self.qpart, Qt.Key_Right) QTest.keyClick(self.qpart, Qt.Key_X) QTest.keyClick(self.qpart, Qt.Key_X) self.assertEqual(self.qpart.text, 'axxd') # Ctrl+Z doesn't work. Wtf??? self.qpart.document().undo() self.qpart.document().undo() self.assertEqual(self.qpart.text, 'abcd')
def __init__(self, parent): Qutepart.__init__(self, parent) self.encoding = None self.explicitUserEncoding = None self.mime = None # Remove all the default margins self.getMargin('mark_area').setVisible(False) self.blockCountChanged.disconnect(self.getMargin('mark_area').update) self.delMargin('mark_area') self._markArea = None self.getMargin('line_numbers').setVisible(False) self.delMargin('line_numbers') self.completionEnabled = False self._completer.terminate() self.textChanged.disconnect(self._completer._onTextChanged) self.document().modificationChanged.disconnect( self._completer._onModificationChanged) # Search/replace support self.__matchesCache = None self.textChanged.connect(self.__resetMatchCache)
class Test(unittest.TestCase): """Base class for tests """ app = QApplication(sys.argv) # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): del self.qpart def test_overwrite_edit(self): self.qpart.show() self.qpart.text = 'abcd' QTest.keyClicks(self.qpart, "stu") self.assertEqual(self.qpart.text, 'stuabcd') QTest.keyClick(self.qpart, Qt.Key_Insert) QTest.keyClicks(self.qpart, "xy") self.assertEqual(self.qpart.text, 'stuxycd') QTest.keyClick(self.qpart, Qt.Key_Insert) QTest.keyClicks(self.qpart, "z") self.assertEqual(self.qpart.text, 'stuxyzcd') def test_overwrite_backspace(self): self.qpart.show() self.qpart.text = 'abcd' QTest.keyClick(self.qpart, Qt.Key_Insert) for i in range(3): QTest.keyClick(self.qpart, Qt.Key_Right) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Backspace) self.assertEqual(self.qpart.text, 'a d') @base.in_main_loop def test_overwrite_undo(self): self.qpart.show() self.qpart.text = 'abcd' QTest.keyClick(self.qpart, Qt.Key_Insert) QTest.keyClick(self.qpart, Qt.Key_Right) QTest.keyClick(self.qpart, Qt.Key_X) QTest.keyClick(self.qpart, Qt.Key_X) self.assertEqual(self.qpart.text, 'axxd') # Ctrl+Z doesn't work. Wtf??? self.qpart.document().undo() self.qpart.document().undo() self.assertEqual(self.qpart.text, 'abcd')
def __init__( self, parentObject, filePath, createNew=False): """Create editor and open file. If file is None or createNew is True, empty not saved file is created IO Exceptions are not catched, therefore, must be catched on upper level """ QWidget.__init__( self, parentObject) self._neverSaved = filePath is None or createNew self._filePath = filePath self._externallyRemoved = False self._externallyModified = False # File opening should be implemented in the document classes self._fileWatcher = _FileWatcher(filePath) self._fileWatcher.modified.connect(self._onWatcherFileModified) self._fileWatcher.removed.connect(self._onWatcherFileRemoved) if filePath and self._neverSaved: core.mainWindow().appendMessage('New file "%s" is going to be created' % filePath, 5000) self.qutepart = Qutepart(self) self.qutepart.setStyleSheet('QPlainTextEdit {border: 0}') self.qutepart.userWarning.connect(lambda text: core.mainWindow().statusBar().showMessage(text, 5000)) self._applyQpartSettings() core.uiSettingsManager().dialogAccepted.connect(self._applyQpartSettings) layout = QVBoxLayout(self) layout.setMargin(0) layout.addWidget(self.qutepart) self.setFocusProxy(self.qutepart) if not self._neverSaved: originalText = self._readFile(filePath) self.qutepart.text = originalText else: originalText = '' #autodetect eol, if need self._configureEolMode(originalText) self._tryDetectSyntax()
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): self.qpart.terminate() def test_overwrite_edit(self): self.qpart.show() self.qpart.text = 'abcd' QTest.keyClicks(self.qpart, "stu") self.assertEqual(self.qpart.text, 'stuabcd') QTest.keyClick(self.qpart, Qt.Key_Insert) QTest.keyClicks(self.qpart, "xy") self.assertEqual(self.qpart.text, 'stuxycd') QTest.keyClick(self.qpart, Qt.Key_Insert) QTest.keyClicks(self.qpart, "z") self.assertEqual(self.qpart.text, 'stuxyzcd') def test_overwrite_backspace(self): self.qpart.show() self.qpart.text = 'abcd' QTest.keyClick(self.qpart, Qt.Key_Insert) for i in range(3): QTest.keyClick(self.qpart, Qt.Key_Right) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Backspace) self.assertEqual(self.qpart.text, 'a d') @base.in_main_loop def test_overwrite_undo(self): self.qpart.show() self.qpart.text = 'abcd' QTest.keyClick(self.qpart, Qt.Key_Insert) QTest.keyClick(self.qpart, Qt.Key_Right) QTest.keyClick(self.qpart, Qt.Key_X) QTest.keyClick(self.qpart, Qt.Key_X) self.assertEqual(self.qpart.text, 'axxd') # Ctrl+Z doesn't work. Wtf??? self.qpart.document().undo() self.qpart.document().undo() self.assertEqual(self.qpart.text, 'abcd') def test_alt_does_not_type(self): """ By default when Alt+Key is pressed - text is inserted. Qutepart ignores this key pressings """ QTest.keyClick(self.qpart, Qt.Key_A, Qt.AltModifier) self.assertEqual(self.qpart.text, '') QTest.keyClick(self.qpart, Qt.Key_A) self.assertEqual(self.qpart.text, 'a') def test_home1(self): """ Test the operation of the home key. """ self.qpart.show() self.qpart.text = ' xx' # Move to the end of this string. self.qpart.cursorPosition = (100, 100) # Press home the first time. This should move to the beginning of the # indent: line 0, column 4. self.assertEqual(self.qpart.cursorPosition, (0, 4)) def column(self): """ Return the column at which the cursor is located.""" return self.qpart.cursorPosition[1] def test_home2(self): """ Test the operation of the home key. """ self.qpart.show() self.qpart.text = '\n\n ' + 'x'*10000 # Move to the end of this string. self.qpart.cursorPosition = (100, 100) # Press home. We should either move to the line beginning or indent. QTest.keyClick(self.qpart, Qt.Key_Home) # There's no way I can find of determine what the line beginning should # be. So, just press home again if we're not at the indent. if self.column() != 4: # Press home again to move to the beginning of the indent. QTest.keyClick(self.qpart, Qt.Key_Home) # We're at the indent. self.assertEqual(self.column(), 4) # Move to the beginning of the line. QTest.keyClick(self.qpart, Qt.Key_Home) self.assertEqual(self.column(), 0) # Move back to the beginning of the indent. QTest.keyClick(self.qpart, Qt.Key_Home) self.assertEqual(self.column(), 4)
def setUp(self): self._window = QMainWindow() self.qpart = Qutepart() self._window.setCentralWidget(self.qpart) self._window.menuBar().addAction(self.qpart.invokeCompletionAction)
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): del self.qpart def _ws_test(self, text, expectedResult, drawAny=[True, False], drawIncorrect=[True, False], useTab=[True, False], indentWidth=[1, 2, 3, 4, 8]): for drawAnyVal in drawAny: self.qpart.drawAnyWhitespace = drawAnyVal for drawIncorrectVal in drawIncorrect: self.qpart.drawIncorrectIndentation = drawIncorrectVal for useTabVal in useTab: self.qpart.indentUseTabs = useTabVal for indentWidthVal in indentWidth: self.qpart.indentWidth = indentWidthVal try: self._verify(text, expectedResult) except: print "Failed params:\n\tany {}\n\tincorrect {}\n\ttabs {}\n\twidth {}".format( self.qpart.drawAnyWhitespace, self.qpart.drawIncorrectIndentation, self.qpart.indentUseTabs, self.qpart.indentWidth) raise def _verify(self, text, expectedResult): res = self.qpart._chooseVisibleWhitespace(text) for index, value in enumerate(expectedResult): if value == '1': if not res[index]: self.fail("Item {} is not True:\n\t{}".format(index, res)) elif value == '0': if res[index]: self.fail("Item {} is not False:\n\t{}".format(index, res)) else: assert value == ' ' def test_1(self): # Trailing self._ws_test(' m xyz\t ', ' 0 00011', drawIncorrect=[True]) def test_2(self): # Tabs in space mode self._ws_test('\txyz\t', '10001', drawIncorrect=[True], useTab=[False]) def test_3(self): # Spaces in tab mode self._ws_test(' 1 2 3 5', '000001110111110', drawIncorrect=[True], drawAny=[False], indentWidth=[3], useTab=[True]) def test_4(self): # Draw any self._ws_test(' 1 1 2 3 5\t', '100011011101111101', drawAny=[True], indentWidth=[2, 3, 4, 8])
def setUp(self): self.qpart = Qutepart() if self.LANGUAGE is not None: self.qpart.detectSyntax(language=self.LANGUAGE) self.qpart.indentWidth = self.INDENT_WIDTH
class Document(QWidget): """ Base class for documents on workspace, such as opened source file, Qt Designer and Qt Assistant, ... Inherit this class, if you want to create new document type This class may requre redesign, if we need to add support for non-textual or non-unicode editor. DO redesign instead of doing dirty hacks """ documentDataChanged = pyqtSignal() """ documentDataChanged() **Signal** emitted, when document icon or toolTip has changed (i.e. document has been modified externally) """ _EOL_CONVERTOR = {r'\r\n': '\r\n', r'\n': '\n', r'\r': '\r'} def __init__( self, parentObject, filePath, createNew=False): """Create editor and open file. If file is None or createNew is True, empty not saved file is created IO Exceptions are not catched, therefore, must be catched on upper level """ QWidget.__init__( self, parentObject) self._neverSaved = filePath is None or createNew self._filePath = filePath self._externallyRemoved = False self._externallyModified = False # File opening should be implemented in the document classes self._fileWatcher = _FileWatcher(filePath) self._fileWatcher.modified.connect(self._onWatcherFileModified) self._fileWatcher.removed.connect(self._onWatcherFileRemoved) if filePath and self._neverSaved: core.mainWindow().appendMessage('New file "%s" is going to be created' % filePath, 5000) self.qutepart = Qutepart(self) self.qutepart.setStyleSheet('QPlainTextEdit {border: 0}') self.qutepart.userWarning.connect(lambda text: core.mainWindow().statusBar().showMessage(text, 5000)) self._applyQpartSettings() core.uiSettingsManager().dialogAccepted.connect(self._applyQpartSettings) layout = QVBoxLayout(self) layout.setMargin(0) layout.addWidget(self.qutepart) self.setFocusProxy(self.qutepart) if not self._neverSaved: originalText = self._readFile(filePath) self.qutepart.text = originalText else: originalText = '' #autodetect eol, if need self._configureEolMode(originalText) self._tryDetectSyntax() def _tryDetectSyntax(self): if len(self.qutepart.lines) > (100 * 1000) and \ self.qutepart.language() is None: """Qutepart uses too lot of memory when highlighting really big files It may crash the editor, so, do not highlight really big files. But, do not disable highlighting for files, which already was highlighted """ return self.qutepart.detectSyntax(sourceFilePath=self.filePath(), firstLine=self.qutepart.lines[0]) def del_(self): """Explicytly called destructor """ self._fileWatcher.disable() # avoid emit on text change, document shall behave like it is already dead self.qutepart.document().modificationChanged.disconnect() self.qutepart.text = '' # stop background highlighting, free memory def _onWatcherFileModified(self, modified): """File has been modified """ self._externallyModified = modified self.documentDataChanged.emit() def _onWatcherFileRemoved(self, isRemoved): """File has been removed """ self._externallyRemoved = isRemoved self.documentDataChanged.emit() def _readFile(self, filePath): """Read the file contents. Shows QMessageBox for UnicodeDecodeError, but raises IOError, if failed to read file """ with open(filePath, 'rb') as openedFile: # Exception is ok, raise it up self._filePath = os.path.abspath(filePath) # abspath won't fail, if file exists data = openedFile.read() self._fileWatcher.setContents(data) try: text = unicode(data, 'utf8') except UnicodeDecodeError, ex: QMessageBox.critical(None, self.tr("Can not decode file"), filePath + '\n' + unicode(str(ex), 'utf8') + '\nProbably invalid encoding was set. ' + 'You may corrupt your file, if saved it') text = unicode(data, 'utf8', 'replace') # Strip last EOL. It will be restored, when saving if text.endswith('\r\n'): text = text[:-2] elif text.endswith('\r') or text.endswith('\n'): text = text[:-1] return text
def setUp(self): self.app = QApplication(sys.argv) # app crashes, if created more than once self.qpart = Qutepart()
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): self.qpart.terminate() def _ws_test(self, text, expectedResult, drawAny=[True, False], drawIncorrect=[True, False], useTab=[True, False], indentWidth=[1, 2, 3, 4, 8]): for drawAnyVal in drawAny: self.qpart.drawAnyWhitespace = drawAnyVal for drawIncorrectVal in drawIncorrect: self.qpart.drawIncorrectIndentation = drawIncorrectVal for useTabVal in useTab: self.qpart.indentUseTabs = useTabVal for indentWidthVal in indentWidth: self.qpart.indentWidth = indentWidthVal try: self._verify(text, expectedResult) except: print( "Failed params:\n\tany {}\n\tincorrect {}\n\ttabs {}\n\twidth {}" .format(self.qpart.drawAnyWhitespace, self.qpart.drawIncorrectIndentation, self.qpart.indentUseTabs, self.qpart.indentWidth)) raise def _verify(self, text, expectedResult): res = self.qpart._chooseVisibleWhitespace(text) for index, value in enumerate(expectedResult): if value == '1': if not res[index]: self.fail("Item {} is not True:\n\t{}".format(index, res)) elif value == '0': if res[index]: self.fail("Item {} is not False:\n\t{}".format(index, res)) else: assert value == ' ' def test_1(self): # Trailing self._ws_test(' m xyz\t ', ' 0 00011', drawIncorrect=[True]) def test_2(self): # Tabs in space mode self._ws_test('\txyz\t', '10001', drawIncorrect=[True], useTab=[False]) def test_3(self): # Spaces in tab mode self._ws_test(' 1 2 3 5', '000001110111110', drawIncorrect=[True], drawAny=[False], indentWidth=[3], useTab=[True]) def test_4(self): # Draw any self._ws_test(' 1 1 2 3 5\t', '100011011101111101', drawAny=[True], indentWidth=[2, 3, 4, 8])
class Document(QWidget): """ Document is a opened file representation. It contains file management methods and uses `Qutepart <http://qutepart.rtfd.org/>`_ as an editor widget. Qutepart is available as ``qutepart`` attribute. """ documentDataChanged = pyqtSignal() """ documentDataChanged() **Signal** emitted, when document icon or toolTip has changed (i.e. document has been modified externally) """ _EOL_CONVERTOR = {r'\r\n': '\r\n', r'\n': '\n', r'\r': '\r'} def __init__(self, parentObject, filePath, createNew=False): """Create editor and open file. If file is None or createNew is True, empty not saved file is created IO Exceptions are not catched, therefore, must be catched on upper level """ QWidget.__init__(self, parentObject) self._neverSaved = filePath is None or createNew self._filePath = filePath self._externallyRemoved = False self._externallyModified = False # File opening should be implemented in the document classes self._fileWatcher = _FileWatcher(filePath) self._fileWatcher.modified.connect(self._onWatcherFileModified) self._fileWatcher.removed.connect(self._onWatcherFileRemoved) self.qutepart = Qutepart(self) self.qutepart.setStyleSheet('QPlainTextEdit {border: 0}') self.qutepart.userWarning.connect(lambda text: core.mainWindow().statusBar().showMessage(text, 5000)) self._applyQpartSettings() core.uiSettingsManager().dialogAccepted.connect(self._applyQpartSettings) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.qutepart) self.setFocusProxy(self.qutepart) if not self._neverSaved: originalText = self._readFile(filePath) self.qutepart.text = originalText else: originalText = '' # autodetect eol, if need self._configureEolMode(originalText) self._tryDetectSyntax() def _tryDetectSyntax(self): if len(self.qutepart.lines) > (100 * 1000) and \ self.qutepart.language() is None: """Qutepart uses too lot of memory when highlighting really big files It may crash the editor, so, do not highlight really big files. But, do not disable highlighting for files, which already was highlighted """ return self.qutepart.detectSyntax(sourceFilePath=self.filePath(), firstLine=self.qutepart.lines[0]) def terminate(self): """Explicytly called destructor """ self._fileWatcher.term() # avoid emitting signals, document shall behave like it is already dead self.qutepart.document().modificationChanged.disconnect() self.qutepart.cursorPositionChanged.disconnect() # self.qutepart.textChanged.disconnect() self.qutepart.terminate() # stop background highlighting, free memory @pyqtSlot(bool) def _onWatcherFileModified(self, modified): """File has been modified """ self._externallyModified = modified self.documentDataChanged.emit() @pyqtSlot(bool) def _onWatcherFileRemoved(self, isRemoved): """File has been removed """ self._externallyRemoved = isRemoved self.documentDataChanged.emit() def _readFile(self, filePath): """Read the file contents. Shows QMessageBox for UnicodeDecodeError """ with open(filePath, 'rb') as openedFile: # Exception is ok, raise it up self._filePath = os.path.abspath(filePath) # abspath won't fail, if file exists data = openedFile.read() self._fileWatcher.setContents(data) try: text = str(data, 'utf8') except UnicodeDecodeError as ex: QMessageBox.critical(None, self.tr("Can not decode file"), filePath + '\n' + str(ex) + '\nProbably invalid encoding was set. ' + 'You may corrupt your file, if saved it') text = str(data, 'utf8', 'replace') # Strip last EOL. Qutepart adds it when saving file if text.endswith('\r\n'): text = text[:-2] elif text.endswith('\r') or text.endswith('\n'): text = text[:-1] return text def isExternallyModified(self): """Check if document's file has been modified externally. This method does not do any file system access, but only returns cached info """ return self._externallyModified def isExternallyRemoved(self): """Check if document's file has been deleted externally. This method does not do any file system access, but only returns cached info """ return self._externallyRemoved def isNeverSaved(self): """Check if document has been created, but never has been saved on disk """ return self._neverSaved def filePath(self): """Return the document file absolute path. ``None`` if not set (new document)""" return self._filePath def fileName(self): """Document file name without a path. ``None`` if not set (new document)""" if self._filePath: return os.path.basename(self._filePath) else: return None def setFilePath(self, newPath): """Change document file path. Used when saving first time, or on Save As action """ core.workspace().documentClosed.emit(self) self._filePath = newPath self._fileWatcher.setPath(newPath) self._neverSaved = True core.workspace().documentOpened.emit(self) core.workspace().currentDocumentChanged.emit(self, self) def _stripTrailingWhiteSpace(self): lineHasTrailingSpace = ((line and line[-1].isspace()) for line in self.qutepart.lines) if any(lineHasTrailingSpace): with self.qutepart: for lineNo, line in enumerate(self.qutepart.lines): if line and line[-1].isspace(): self.qutepart.lines[lineNo] = line.rstrip() else: pass # Do not enter with statement, because it causes wrong textChanged signal def _saveToFs(self, filePath): """Low level method. Always saves file, even if not modified """ # Create directory dirPath = os.path.dirname(filePath) if not os.path.exists(dirPath): try: os.makedirs(dirPath) except OSError as ex: error = str(ex) QMessageBox.critical(None, self.tr("Cannot save file"), self.tr("Cannot create directory '%s'. Error '%s'." % (dirPath, error))) return text = self.qutepart.textForSaving() # Write file data = text.encode('utf8') self._fileWatcher.disable() try: with open(filePath, 'wb') as openedFile: openedFile.write(data) self._fileWatcher.setContents(data) except IOError as ex: QMessageBox.critical(None, self.tr("Cannot write to file"), str(ex)) return finally: self._fileWatcher.enable() # Update states self._neverSaved = False self._externallyRemoved = False self._externallyModified = False self.qutepart.document().setModified(False) self.documentDataChanged.emit() if self.qutepart.language() is None: self._tryDetectSyntax() def saveFile(self): """Save the file to file system. Show QFileDialog if file name is not known. Return False, if user cancelled QFileDialog, True otherwise """ # Get path if not self._filePath: path, _ = QFileDialog.getSaveFileName(self, self.tr('Save file as...')) if path: self.setFilePath(path) else: return False if core.config()['Qutepart']['StripTrailingWhitespace']: self._stripTrailingWhiteSpace() self._saveToFs(self.filePath()) return True def saveFileAs(self): """Ask for new file name with dialog. Save file """ if self._filePath: default_filename = os.path.basename(self._filePath) else: default_filename = '' path, _ = QFileDialog.getSaveFileName(self, self.tr('Save file as...'), default_filename) if not path: return self.setFilePath(path) self._saveToFs(path) def reload(self): """Reload the file from the disk If child class reimplemented this method, it MUST call method of the parent class for update internal bookkeeping""" text = self._readFile(self.filePath()) pos = self.qutepart.cursorPosition self.qutepart.text = text self._externallyModified = False self._externallyRemoved = False self.qutepart.cursorPosition = pos self.qutepart.centerCursor() def modelToolTip(self): """Tool tip for the opened files model """ toolTip = self.filePath() if toolTip is None: return None if self.qutepart.document().isModified(): toolTip += "<br/><font color='blue'>%s</font>" % self.tr("Locally Modified") if self._externallyModified: toolTip += "<br/><font color='red'>%s</font>" % self.tr("Externally Modified") if self._externallyRemoved: toolTip += "<br/><font color='red'>%s</font>" % self.tr("Externally Deleted") return '<html>' + toolTip + '</html>' def modelIcon(self): """Icon for the opened files model """ if self.isNeverSaved(): # never has been saved icon = "save.png" elif self._externallyRemoved and self.qutepart.document().isModified(): icon = 'modified-externally-deleted.png' elif self._externallyRemoved: icon = "close.png" elif self._externallyModified and self.qutepart.document().isModified(): icon = "modified-externally-modified.png" elif self._externallyModified: icon = "modified-externally.png" elif self.qutepart.document().isModified(): icon = "save.png" else: icon = "transparent.png" return QIcon(":/enkiicons/" + icon) def invokeGoTo(self): """Show GUI dialog, go to line, if user accepted it """ line = self.qutepart.cursorPosition[0] gotoLine, accepted = QInputDialog.getInt(self, self.tr("Go To Line..."), self.tr("Enter the line you want to go to:"), line, 1, len(self.qutepart.lines), 1) if accepted: gotoLine -= 1 self.qutepart.cursorPosition = gotoLine, None self.setFocus() def printFile(self): """Print file """ raise NotImplemented() def _configureEolMode(self, originalText): """Detect end of line mode automatically and apply detected mode """ modes = set() for line in originalText.splitlines(True): if line.endswith('\r\n'): modes.add('\r\n') elif line.endswith('\n'): modes.add('\n') elif line.endswith('\r'): modes.add('\r') if len(modes) == 1: # exactly one detectedMode = modes.pop() else: detectedMode = None default = self._EOL_CONVERTOR[core.config()["Qutepart"]["EOL"]["Mode"]] if len(modes) > 1: message = "%s contains mix of End Of Line symbols. It will be saved with '%s'" % \ (self.filePath(), repr(default)) core.mainWindow().appendMessage(message) self.qutepart.eol = default self.qutepart.document().setModified(True) elif core.config()["Qutepart"]["EOL"]["AutoDetect"]: if detectedMode is not None: self.qutepart.eol = detectedMode else: # empty set, not detected self.qutepart.eol = default else: # no mix, no autodetect. Force EOL if detectedMode is not None and \ detectedMode != default: message = "%s: End Of Line mode is '%s', but file will be saved with '%s'. " \ "EOL autodetection is disabled in the settings" % \ (self.fileName(), repr(detectedMode), repr(default)) core.mainWindow().appendMessage(message) self.qutepart.document().setModified(True) self.qutepart.eol = default @pyqtSlot() def _applyQpartSettings(self): """Apply qutepart settings """ conf = core.config()['Qutepart'] self.qutepart.setFont(QFont(conf['Font']['Family'], conf['Font']['Size'])) self.qutepart.indentUseTabs = conf['Indentation']['UseTabs'] self.qutepart.indentWidth = conf['Indentation']['Width'] if conf['Edge']['Enabled']: self.qutepart.lineLengthEdge = conf['Edge']['Column'] else: self.qutepart.lineLengthEdge = None self.qutepart.lineLengthEdgeColor = QColor(conf['Edge']['Color']) self.qutepart.completionEnabled = conf['AutoCompletion']['Enabled'] self.qutepart.completionThreshold = conf['AutoCompletion']['Threshold'] self.qutepart.setLineWrapMode(QPlainTextEdit.WidgetWidth if conf['Wrap']['Enabled'] else QPlainTextEdit.NoWrap) if conf['Wrap']['Mode'] == 'WrapAtWord': self.qutepart.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere) elif conf['Wrap']['Mode'] == 'WrapAnywhere': self.qutepart.setWordWrapMode(QTextOption.WrapAnywhere) else: assert 'Invalid wrap mode', conf['Wrap']['Mode'] # EOL is managed by _configureEolMode(). But, if autodetect is disabled, we may apply new value here if not conf['EOL']['AutoDetect']: self.qutepart.eol = self._EOL_CONVERTOR[conf['EOL']['Mode']]
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): self.qpart.hide() self.qpart.terminate() def test_real_to_visible(self): self.qpart.text = 'abcdfg' self.assertEqual(0, self.qpart._rectangularSelection._realToVisibleColumn(self.qpart.text, 0)) self.assertEqual(2, self.qpart._rectangularSelection._realToVisibleColumn(self.qpart.text, 2)) self.assertEqual(6, self.qpart._rectangularSelection._realToVisibleColumn(self.qpart.text, 6)) self.qpart.text = '\tab\tcde\t' self.assertEqual(0, self.qpart._rectangularSelection._realToVisibleColumn(self.qpart.text, 0)) self.assertEqual(4, self.qpart._rectangularSelection._realToVisibleColumn(self.qpart.text, 1)) self.assertEqual(5, self.qpart._rectangularSelection._realToVisibleColumn(self.qpart.text, 2)) self.assertEqual(8, self.qpart._rectangularSelection._realToVisibleColumn(self.qpart.text, 4)) self.assertEqual(12, self.qpart._rectangularSelection._realToVisibleColumn(self.qpart.text, 8)) def test_visible_to_real(self): self.qpart.text = 'abcdfg' self.assertEqual(0, self.qpart._rectangularSelection._visibleToRealColumn(self.qpart.text, 0)) self.assertEqual(2, self.qpart._rectangularSelection._visibleToRealColumn(self.qpart.text, 2)) self.assertEqual(6, self.qpart._rectangularSelection._visibleToRealColumn(self.qpart.text, 6)) self.qpart.text = '\tab\tcde\t' self.assertEqual(0, self.qpart._rectangularSelection._visibleToRealColumn(self.qpart.text, 0)) self.assertEqual(1, self.qpart._rectangularSelection._visibleToRealColumn(self.qpart.text, 4)) self.assertEqual(2, self.qpart._rectangularSelection._visibleToRealColumn(self.qpart.text, 5)) self.assertEqual(4, self.qpart._rectangularSelection._visibleToRealColumn(self.qpart.text, 8)) self.assertEqual(8, self.qpart._rectangularSelection._visibleToRealColumn(self.qpart.text, 12)) self.assertEqual(None, self.qpart._rectangularSelection._visibleToRealColumn(self.qpart.text, 13)) def test_basic(self): self.qpart.show() for key in [Qt.Key_Delete, Qt.Key_Backspace]: self.qpart.text = 'abcd\nef\nghkl\nmnop' QTest.keyClick(self.qpart, Qt.Key_Right) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, key) self.assertEqual(self.qpart.text, 'ad\ne\ngl\nmnop') def test_reset_by_move(self): self.qpart.show() self.qpart.text = 'abcd\nef\nghkl\nmnop' QTest.keyClick(self.qpart, Qt.Key_Right) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Left) QTest.keyClick(self.qpart, Qt.Key_Backspace) self.assertEqual(self.qpart.text, 'abcd\nef\ngkl\nmnop') def test_reset_by_edit(self): self.qpart.show() self.qpart.text = 'abcd\nef\nghkl\nmnop' QTest.keyClick(self.qpart, Qt.Key_Right) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClicks(self.qpart, 'x') QTest.keyClick(self.qpart, Qt.Key_Backspace) self.assertEqual(self.qpart.text, 'abcd\nef\nghkl\nmnop') def test_with_tabs(self): self.qpart.show() self.qpart.text = 'abcdefghhhhh\n\tklm\n\t\txyz' self.qpart.cursorPosition = (0, 6) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Delete) # 2 variants, Qt bahavior differs on different systems self.assertTrue(self.qpart.text in ('abcdefhh\n\tkl\n\t\tz', 'abcdefh\n\tkl\n\t\t')) def test_delete(self): self.qpart.show() self.qpart.text = 'this is long\nshort\nthis is long' self.qpart.cursorPosition = (0, 8) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) for i in range(4): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Delete) self.assertEqual(self.qpart.text, 'this is \nshort\nthis is ') def test_copy_paste(self): self.qpart.indentUseTabs = True self.qpart.show() self.qpart.text = 'xx 123 yy\n' + \ 'xx 456 yy\n' + \ 'xx 789 yy\n' + \ '\n' + \ 'asdfghijlmn\n' + \ 'x\t\n' + \ '\n' + \ '\t\t\n' + \ 'end\n' self.qpart.cursorPosition = 0, 3 for i in range(3): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_C, Qt.ControlModifier) self.qpart.cursorPosition = 4, 10 QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'xx 123 yy\nxx 456 yy\nxx 789 yy\n\nasdfghijlm123n\nx\t 456\n\t\t 789\n\t\t\nend\n') def test_copy_paste_utf8(self): self.qpart.show() self.qpart.text = 'фыва' for i in range(3): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_C, Qt.ControlModifier) QTest.keyClick(self.qpart, Qt.Key_Right) QTest.keyClick(self.qpart, Qt.Key_Space) QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'фыва фыв') def test_paste_replace_selection(self): self.qpart.show() self.qpart.text = 'asdf' for i in range(4): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_C, Qt.ControlModifier) QTest.keyClick(self.qpart, Qt.Key_End) QTest.keyClick(self.qpart, Qt.Key_Left, Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'asdasdf') def test_paste_replace_rectangular_selection(self): self.qpart.show() self.qpart.text = 'asdf' for i in range(4): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_C, Qt.ControlModifier) QTest.keyClick(self.qpart, Qt.Key_Left) QTest.keyClick(self.qpart, Qt.Key_Left, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'asasdff') def test_paste_new_lines(self): self.qpart.show() self.qpart.text = 'a\nb\nc\nd' for i in range(4): QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_C, Qt.ControlModifier) self.qpart.text = 'x\ny' self.qpart.cursorPosition = (1, 1) QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'x\nya\n b\n c\n d') def test_cut(self): self.qpart.show() self.qpart.text = 'asdf' for i in range(4): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_X, Qt.ControlModifier) self.assertEqual(self.qpart.text, '') QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'asdf') def test_cut_paste(self): # Cursor must be moved to top-left after cut, and original text is restored after paste self.qpart.show() self.qpart.text = 'abcd\nefgh\nklmn' QTest.keyClick(self.qpart, Qt.Key_Right) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_X, Qt.ControlModifier) self.assertEqual(self.qpart.cursorPosition, (0, 1)) QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'abcd\nefgh\nklmn') def test_warning(self): self.qpart.show() self.qpart.text = 'a\n' * 300 warning = [None] def _saveWarning(text): warning[0] = text self.qpart.userWarning.connect(_saveWarning) base.keySequenceClicks(self.qpart, QKeySequence.SelectEndOfDocument, Qt.AltModifier) self.assertEqual(warning[0], 'Rectangular selection area is too big')
def mouseDoubleClickEvent(self, event): """Disable search highlight on double click""" Qutepart.mouseDoubleClickEvent(self, event)
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): self.qpart.hide() self.qpart.terminate() def test_real_to_visible(self): self.qpart.text = 'abcdfg' self.assertEqual( 0, self.qpart._rectangularSelection._realToVisibleColumn( self.qpart.text, 0)) self.assertEqual( 2, self.qpart._rectangularSelection._realToVisibleColumn( self.qpart.text, 2)) self.assertEqual( 6, self.qpart._rectangularSelection._realToVisibleColumn( self.qpart.text, 6)) self.qpart.text = '\tab\tcde\t' self.assertEqual( 0, self.qpart._rectangularSelection._realToVisibleColumn( self.qpart.text, 0)) self.assertEqual( 4, self.qpart._rectangularSelection._realToVisibleColumn( self.qpart.text, 1)) self.assertEqual( 5, self.qpart._rectangularSelection._realToVisibleColumn( self.qpart.text, 2)) self.assertEqual( 8, self.qpart._rectangularSelection._realToVisibleColumn( self.qpart.text, 4)) self.assertEqual( 12, self.qpart._rectangularSelection._realToVisibleColumn( self.qpart.text, 8)) def test_visible_to_real(self): self.qpart.text = 'abcdfg' self.assertEqual( 0, self.qpart._rectangularSelection._visibleToRealColumn( self.qpart.text, 0)) self.assertEqual( 2, self.qpart._rectangularSelection._visibleToRealColumn( self.qpart.text, 2)) self.assertEqual( 6, self.qpart._rectangularSelection._visibleToRealColumn( self.qpart.text, 6)) self.qpart.text = '\tab\tcde\t' self.assertEqual( 0, self.qpart._rectangularSelection._visibleToRealColumn( self.qpart.text, 0)) self.assertEqual( 1, self.qpart._rectangularSelection._visibleToRealColumn( self.qpart.text, 4)) self.assertEqual( 2, self.qpart._rectangularSelection._visibleToRealColumn( self.qpart.text, 5)) self.assertEqual( 4, self.qpart._rectangularSelection._visibleToRealColumn( self.qpart.text, 8)) self.assertEqual( 8, self.qpart._rectangularSelection._visibleToRealColumn( self.qpart.text, 12)) self.assertEqual( None, self.qpart._rectangularSelection._visibleToRealColumn( self.qpart.text, 13)) def test_basic(self): self.qpart.show() for key in [Qt.Key_Delete, Qt.Key_Backspace]: self.qpart.text = 'abcd\nef\nghkl\nmnop' QTest.keyClick(self.qpart, Qt.Key_Right) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, key) self.assertEqual(self.qpart.text, 'ad\ne\ngl\nmnop') def test_reset_by_move(self): self.qpart.show() self.qpart.text = 'abcd\nef\nghkl\nmnop' QTest.keyClick(self.qpart, Qt.Key_Right) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Left) QTest.keyClick(self.qpart, Qt.Key_Backspace) self.assertEqual(self.qpart.text, 'abcd\nef\ngkl\nmnop') def test_reset_by_edit(self): self.qpart.show() self.qpart.text = 'abcd\nef\nghkl\nmnop' QTest.keyClick(self.qpart, Qt.Key_Right) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClicks(self.qpart, 'x') QTest.keyClick(self.qpart, Qt.Key_Backspace) self.assertEqual(self.qpart.text, 'abcd\nef\nghkl\nmnop') def test_with_tabs(self): self.qpart.show() self.qpart.text = 'abcdefghhhhh\n\tklm\n\t\txyz' self.qpart.cursorPosition = (0, 6) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Delete) # 2 variants, Qt bahavior differs on different systems self.assertTrue(self.qpart.text in ('abcdefhh\n\tkl\n\t\tz', 'abcdefh\n\tkl\n\t\t')) def test_delete(self): self.qpart.show() self.qpart.text = 'this is long\nshort\nthis is long' self.qpart.cursorPosition = (0, 8) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) for i in range(4): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Delete) self.assertEqual(self.qpart.text, 'this is \nshort\nthis is ') def test_copy_paste(self): self.qpart.indentUseTabs = True self.qpart.show() self.qpart.text = 'xx 123 yy\n' + \ 'xx 456 yy\n' + \ 'xx 789 yy\n' + \ '\n' + \ 'asdfghijlmn\n' + \ 'x\t\n' + \ '\n' + \ '\t\t\n' + \ 'end\n' self.qpart.cursorPosition = 0, 3 for i in range(3): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_C, Qt.ControlModifier) self.qpart.cursorPosition = 4, 10 QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual( self.qpart.text, 'xx 123 yy\nxx 456 yy\nxx 789 yy\n\nasdfghijlm123n\nx\t 456\n\t\t 789\n\t\t\nend\n' ) def test_copy_paste_utf8(self): self.qpart.show() self.qpart.text = 'фыва' for i in range(3): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_C, Qt.ControlModifier) QTest.keyClick(self.qpart, Qt.Key_Right) QTest.keyClick(self.qpart, Qt.Key_Space) QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'фыва фыв') def test_paste_replace_selection(self): self.qpart.show() self.qpart.text = 'asdf' for i in range(4): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_C, Qt.ControlModifier) QTest.keyClick(self.qpart, Qt.Key_End) QTest.keyClick(self.qpart, Qt.Key_Left, Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'asdasdf') def test_paste_replace_rectangular_selection(self): self.qpart.show() self.qpart.text = 'asdf' for i in range(4): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_C, Qt.ControlModifier) QTest.keyClick(self.qpart, Qt.Key_Left) QTest.keyClick(self.qpart, Qt.Key_Left, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'asasdff') def test_paste_new_lines(self): self.qpart.show() self.qpart.text = 'a\nb\nc\nd' for i in range(4): QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_C, Qt.ControlModifier) self.qpart.text = 'x\ny' self.qpart.cursorPosition = (1, 1) QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'x\nya\n b\n c\n d') def test_cut(self): self.qpart.show() self.qpart.text = 'asdf' for i in range(4): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_X, Qt.ControlModifier) self.assertEqual(self.qpart.text, '') QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'asdf') def test_cut_paste(self): # Cursor must be moved to top-left after cut, and original text is restored after paste self.qpart.show() self.qpart.text = 'abcd\nefgh\nklmn' QTest.keyClick(self.qpart, Qt.Key_Right) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Right, Qt.AltModifier | Qt.ShiftModifier) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Down, Qt.AltModifier | Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_X, Qt.ControlModifier) self.assertEqual(self.qpart.cursorPosition, (0, 1)) QTest.keyClick(self.qpart, Qt.Key_V, Qt.ControlModifier) self.assertEqual(self.qpart.text, 'abcd\nefgh\nklmn') def test_warning(self): self.qpart.show() self.qpart.text = 'a\n' * 300 warning = [None] def _saveWarning(text): warning[0] = text self.qpart.userWarning.connect(_saveWarning) base.keySequenceClicks(self.qpart, QKeySequence.SelectEndOfDocument, Qt.AltModifier) self.assertEqual(warning[0], 'Rectangular selection area is too big')
class Document(QWidget): """ Document is a opened file representation. It contains file management methods and uses `Qutepart <http://qutepart.rtfd.org/>`_ as an editor widget. Qutepart is available as ``qutepart`` attribute. """ documentDataChanged = pyqtSignal() """ documentDataChanged() **Signal** emitted, when document icon or toolTip has changed (i.e. document has been modified externally) """ _EOL_CONVERTOR = {r'\r\n': '\r\n', r'\n': '\n', r'\r': '\r'} def __init__(self, parentObject, filePath, createNew=False): """Create editor and open file. If file is None or createNew is True, empty not saved file is created IO Exceptions are not catched, therefore, must be catched on upper level """ QWidget.__init__(self, parentObject) self._neverSaved = filePath is None or createNew self._filePath = filePath self._externallyRemoved = False self._externallyModified = False # File opening should be implemented in the document classes self._fileWatcher = _FileWatcher(filePath) self._fileWatcher.modified.connect(self._onWatcherFileModified) self._fileWatcher.removed.connect(self._onWatcherFileRemoved) self.qutepart = Qutepart(self) self.qutepart.setStyleSheet('QPlainTextEdit {border: 0}') self.qutepart.userWarning.connect( lambda text: core.mainWindow().statusBar().showMessage(text, 5000)) self._applyQpartSettings() core.uiSettingsManager().dialogAccepted.connect( self._applyQpartSettings) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.qutepart) self.setFocusProxy(self.qutepart) if not self._neverSaved: originalText = self._readFile(filePath) self.qutepart.text = originalText else: originalText = '' # autodetect eol, if need self._configureEolMode(originalText) self._tryDetectSyntax() QApplication.instance().installEventFilter(self) def _tryDetectSyntax(self): if len(self.qutepart.lines) > (100 * 1000) and \ self.qutepart.language() is None: """Qutepart uses too lot of memory when highlighting really big files It may crash the editor, so, do not highlight really big files. But, do not disable highlighting for files, which already was highlighted """ return self.qutepart.detectSyntax(sourceFilePath=self.filePath(), firstLine=self.qutepart.lines[0]) def terminate(self): """Explicytly called destructor """ self._fileWatcher.term() # avoid emitting signals, document shall behave like it is already dead self.qutepart.document().modificationChanged.disconnect() self.qutepart.cursorPositionChanged.disconnect() # self.qutepart.textChanged.disconnect() self.qutepart.terminate() # stop background highlighting, free memory sip.delete(self) @pyqtSlot(bool) def _onWatcherFileModified(self, modified): """File has been modified """ self._externallyModified = modified self.documentDataChanged.emit() @pyqtSlot(bool) def _onWatcherFileRemoved(self, isRemoved): """File has been removed """ self._externallyRemoved = isRemoved self.documentDataChanged.emit() def _readFile(self, filePath): """Read the file contents. Shows QMessageBox for UnicodeDecodeError """ with open(filePath, 'rb') as openedFile: # Exception is ok, raise it up self._filePath = os.path.abspath( filePath) # abspath won't fail, if file exists data = openedFile.read() self._fileWatcher.setContents(data) try: text = str(data, 'utf8') except UnicodeDecodeError as ex: QMessageBox.critical( None, self.tr("Can not decode file"), filePath + '\n' + str(ex) + '\nProbably invalid encoding was set. ' + 'You may corrupt your file, if saved it') text = str(data, 'utf8', 'replace') # Strip last EOL. Qutepart adds it when saving file if text.endswith('\r\n'): text = text[:-2] elif text.endswith('\r') or text.endswith('\n'): text = text[:-1] return text def isExternallyModified(self): """Check if document's file has been modified externally. This method does not do any file system access, but only returns cached info """ return self._externallyModified def isExternallyRemoved(self): """Check if document's file has been deleted externally. This method does not do any file system access, but only returns cached info """ return self._externallyRemoved def isNeverSaved(self): """Check if document has been created, but never has been saved on disk """ return self._neverSaved def filePath(self): """Return the document file absolute path. ``None`` if not set (new document)""" return self._filePath def fileName(self): """Document file name without a path. ``None`` if not set (new document)""" if self._filePath: return os.path.basename(self._filePath) else: return None def setFilePath(self, newPath): """Change document file path. Used when saving first time, or on Save As action """ core.workspace().documentClosed.emit(self) self._filePath = newPath self._fileWatcher.setPath(newPath) self._neverSaved = True core.workspace().documentOpened.emit(self) core.workspace().currentDocumentChanged.emit(self, self) def _stripTrailingWhiteSpace(self): lineHasTrailingSpace = ((line and line[-1].isspace()) for line in self.qutepart.lines) if any(lineHasTrailingSpace): with self.qutepart: for lineNo, line in enumerate(self.qutepart.lines): if line and line[-1].isspace(): self.qutepart.lines[lineNo] = line.rstrip() else: pass # Do not enter with statement, because it causes wrong textChanged signal def _saveToFs(self, filePath): """Low level method. Always saves file, even if not modified """ # Create directory dirPath = os.path.dirname(filePath) if not os.path.exists(dirPath): try: os.makedirs(dirPath) except OSError as ex: error = str(ex) QMessageBox.critical( None, self.tr("Cannot save file"), self.tr("Cannot create directory '%s'. Error '%s'." % (dirPath, error))) return text = self.qutepart.textForSaving() # Write file data = text.encode('utf8') self._fileWatcher.disable() try: with open(filePath, 'wb') as openedFile: openedFile.write(data) self._fileWatcher.setContents(data) except IOError as ex: QMessageBox.critical(None, self.tr("Cannot write to file"), str(ex)) return finally: self._fileWatcher.enable() # Update states self._neverSaved = False self._externallyRemoved = False self._externallyModified = False self.qutepart.document().setModified(False) self.documentDataChanged.emit() if self.qutepart.language() is None: self._tryDetectSyntax() def saveFile(self): """Save the file to file system. Show QFileDialog if file name is not known. Return False, if user cancelled QFileDialog, True otherwise """ # Get path if not self._filePath: path, _ = QFileDialog.getSaveFileName(self, self.tr('Save file as...')) if path: self.setFilePath(path) else: return False if core.config()['Qutepart']['StripTrailingWhitespace']: self._stripTrailingWhiteSpace() self._saveToFs(self.filePath()) return True def saveFileAs(self): """Ask for new file name with dialog. Save file """ if self._filePath: default_filename = os.path.basename(self._filePath) else: default_filename = '' path, _ = QFileDialog.getSaveFileName(self, self.tr('Save file as...'), default_filename) if not path: return self.setFilePath(path) self._saveToFs(path) def reload(self): """Reload the file from the disk If child class reimplemented this method, it MUST call method of the parent class for update internal bookkeeping""" text = self._readFile(self.filePath()) pos = self.qutepart.cursorPosition self.qutepart.text = text self._externallyModified = False self._externallyRemoved = False self.qutepart.cursorPosition = pos self.qutepart.centerCursor() def modelToolTip(self): """Tool tip for the opened files model """ toolTip = self.filePath() if toolTip is None: return None if self.qutepart.document().isModified(): toolTip += "<br/><font color='blue'>%s</font>" % self.tr( "Locally Modified") if self._externallyModified: toolTip += "<br/><font color='red'>%s</font>" % self.tr( "Externally Modified") if self._externallyRemoved: toolTip += "<br/><font color='red'>%s</font>" % self.tr( "Externally Deleted") return '<html>' + toolTip + '</html>' def modelIcon(self): """Icon for the opened files model """ if self.isNeverSaved(): # never has been saved icon = "save.png" elif self._externallyRemoved and self.qutepart.document().isModified(): icon = 'modified-externally-deleted.png' elif self._externallyRemoved: icon = "close.png" elif self._externallyModified and self.qutepart.document().isModified( ): icon = "modified-externally-modified.png" elif self._externallyModified: icon = "modified-externally.png" elif self.qutepart.document().isModified(): icon = "save.png" else: icon = "transparent.png" return QIcon(":/enkiicons/" + icon) def invokeGoTo(self): """Show GUI dialog, go to line, if user accepted it """ line = self.qutepart.cursorPosition[0] gotoLine, accepted = QInputDialog.getInt( self, self.tr("Go To Line..."), self.tr("Enter the line you want to go to:"), line, 1, len(self.qutepart.lines), 1) if accepted: gotoLine -= 1 self.qutepart.cursorPosition = gotoLine, None self.setFocus() def printFile(self): """Print file """ raise NotImplemented() def _configureEolMode(self, originalText): """Detect end of line mode automatically and apply detected mode """ modes = set() for line in originalText.splitlines(True): if line.endswith('\r\n'): modes.add('\r\n') elif line.endswith('\n'): modes.add('\n') elif line.endswith('\r'): modes.add('\r') if len(modes) == 1: # exactly one detectedMode = modes.pop() else: detectedMode = None default = self._EOL_CONVERTOR[core.config()["Qutepart"]["EOL"]["Mode"]] if len(modes) > 1: message = "%s contains mix of End Of Line symbols. It will be saved with '%s'" % \ (self.filePath(), repr(default)) core.mainWindow().appendMessage(message) self.qutepart.eol = default self.qutepart.document().setModified(True) elif core.config()["Qutepart"]["EOL"]["AutoDetect"]: if detectedMode is not None: self.qutepart.eol = detectedMode else: # empty set, not detected self.qutepart.eol = default else: # no mix, no autodetect. Force EOL if detectedMode is not None and \ detectedMode != default: message = "%s: End Of Line mode is '%s', but file will be saved with '%s'. " \ "EOL autodetection is disabled in the settings" % \ (self.fileName(), repr(detectedMode), repr(default)) core.mainWindow().appendMessage(message) self.qutepart.document().setModified(True) self.qutepart.eol = default @pyqtSlot() def _applyQpartSettings(self): """Apply qutepart settings """ conf = core.config()['Qutepart'] self.qutepart.setFont( QFont(conf['Font']['Family'], conf['Font']['Size'])) self.qutepart.indentUseTabs = conf['Indentation']['UseTabs'] self.qutepart.indentWidth = conf['Indentation']['Width'] if conf['Edge']['Enabled']: self.qutepart.lineLengthEdge = conf['Edge']['Column'] else: self.qutepart.lineLengthEdge = None self.qutepart.lineLengthEdgeColor = QColor(conf['Edge']['Color']) self.qutepart.completionEnabled = conf['AutoCompletion']['Enabled'] self.qutepart.completionThreshold = conf['AutoCompletion']['Threshold'] self.qutepart.setLineWrapMode( QPlainTextEdit. WidgetWidth if conf['Wrap']['Enabled'] else QPlainTextEdit.NoWrap) if conf['Wrap']['Mode'] == 'WrapAtWord': self.qutepart.setWordWrapMode( QTextOption.WrapAtWordBoundaryOrAnywhere) elif conf['Wrap']['Mode'] == 'WrapAnywhere': self.qutepart.setWordWrapMode(QTextOption.WrapAnywhere) else: assert 'Invalid wrap mode', conf['Wrap']['Mode'] # EOL is managed by _configureEolMode(). But, if autodetect is disabled, we may apply new value here if not conf['EOL']['AutoDetect']: self.qutepart.eol = self._EOL_CONVERTOR[conf['EOL']['Mode']] # Whitespace visibility is managed by qpartsettings plugin def eventFilter(self, obj, event): """An event filter that looks for focus in events, closing any floating docks when found.""" # Note: We can't ``def focusInEvent(self, focusEvent)`` since qutepart # is the focus proxy, meaning this won't be called. Hence, the neeed for # an event listener. if (event.type() == QEvent.FocusIn and (obj == self or obj == self.focusProxy())): for dock in core.mainWindow().findChildren(DockWidget): # Close all unpinned docks. The exception: if the Open Files # dock is waiting for the Ctrl button to be released, keep it # open; it will be be closed when Ctrl is released. if not dock.isPinned() and (not getattr( dock, '_waitForCtrlRelease', False)): dock._close() return QWidget.eventFilter(self, obj, event)
class Document(QWidget): """ Base class for documents on workspace, such as opened source file, Qt Designer and Qt Assistant, ... Inherit this class, if you want to create new document type This class may requre redesign, if we need to add support for non-textual or non-unicode editor. DO redesign instead of doing dirty hacks """ documentDataChanged = pyqtSignal() """ documentDataChanged() **Signal** emitted, when document icon or toolTip has changed (i.e. document has been modified externally) """ _EOL_CONVERTOR = {r'\r\n': '\r\n', r'\n': '\n', r'\r': '\r'} def __init__(self, parentObject, filePath, createNew=False): """Create editor and open file. If file is None or createNew is True, empty not saved file is created IO Exceptions are not catched, therefore, must be catched on upper level """ QWidget.__init__(self, parentObject) self._neverSaved = filePath is None or createNew self._filePath = filePath self._externallyRemoved = False self._externallyModified = False # File opening should be implemented in the document classes self._fileWatcher = _FileWatcher(filePath) self._fileWatcher.modified.connect(self._onWatcherFileModified) self._fileWatcher.removed.connect(self._onWatcherFileRemoved) if filePath and self._neverSaved: core.mainWindow().appendMessage( 'New file "%s" is going to be created' % filePath, 5000) self.qutepart = Qutepart(self) self.qutepart.setStyleSheet('QPlainTextEdit {border: 0}') self.qutepart.userWarning.connect( lambda text: core.mainWindow().statusBar().showMessage(text, 5000)) self._applyQpartSettings() core.uiSettingsManager().dialogAccepted.connect( self._applyQpartSettings) layout = QVBoxLayout(self) layout.setMargin(0) layout.addWidget(self.qutepart) self.setFocusProxy(self.qutepart) if not self._neverSaved: originalText = self._readFile(filePath) self.qutepart.text = originalText else: originalText = '' #autodetect eol, if need self._configureEolMode(originalText) self._tryDetectSyntax() def _tryDetectSyntax(self): self.qutepart.detectSyntax(sourceFilePath=self.filePath(), firstLine=self.qutepart.lines[0]) def del_(self): """Explicytly called destructor """ self._fileWatcher.disable() def _onWatcherFileModified(self, modified): """File has been modified """ self._externallyModified = modified self.documentDataChanged.emit() def _onWatcherFileRemoved(self, isRemoved): """File has been removed """ self._externallyRemoved = isRemoved self.documentDataChanged.emit() def _readFile(self, filePath): """Read the file contents. Shows QMessageBox for UnicodeDecodeError, but raises IOError, if failed to read file """ with open(filePath, 'rb') as openedFile: # Exception is ok, raise it up self._filePath = os.path.abspath(filePath) data = openedFile.read() self._fileWatcher.setContents(data) try: text = unicode(data, 'utf8') except UnicodeDecodeError, ex: QMessageBox.critical( None, self.tr("Can not decode file"), filePath + '\n' + unicode(str(ex), 'utf8') + '\nProbably invalid encoding was set. ' + 'You may corrupt your file, if saved it') text = unicode(data, 'utf8', 'replace') # Strip last EOL. It will be restored, when saving if text.endswith('\r\n'): text = text[:-2] elif text.endswith('\r') or text.endswith('\n'): text = text[:-1] return text
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): self.qpart.terminate() def _markedBlocks(self): bookMarksObject = self.qpart._bookmarks return [block.blockNumber() \ for block in iterateBlocksFrom(self.qpart.document().firstBlock()) \ if bookMarksObject.isBlockMarked(block)] @base.in_main_loop def test_set_with_keyboard(self): self.qpart.text = '\n' * 5 QTest.keyClick(self.qpart, Qt.Key_B, Qt.ControlModifier) QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_B, Qt.ControlModifier) self.assertEqual(self._markedBlocks(), [0, 2]) QTest.keyClick(self.qpart, Qt.Key_B, Qt.ControlModifier) self.assertEqual(self._markedBlocks(), [0]) @unittest.skip('Crashes Qt') @base.in_main_loop def test_set_with_mouse(self): self.qpart.text = '\n' * 5 secondBlock = self.qpart.document().findBlockByNumber(2) geometry = self.qpart.blockBoundingGeometry(secondBlock).translated( self.qpart.contentOffset()) QTest.mouseClick(self.qpart._markArea, Qt.LeftButton, Qt.NoModifier, QPoint(0, geometry.bottom() - 1)) self.assertEqual(self._markedBlocks(), [1]) @base.in_main_loop def test_jump(self): self.qpart.text = '\n' * 5 QTest.keyClick(self.qpart, Qt.Key_B, Qt.ControlModifier) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_B, Qt.ControlModifier) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_B, Qt.ControlModifier) self.assertEqual(self._markedBlocks(), [0, 2, 4]) self.qpart.cursorPosition = (0, 0) QTest.keyClick(self.qpart, Qt.Key_PageDown, Qt.AltModifier) self.assertEqual(self.qpart.cursorPosition[0], 2) QTest.keyClick(self.qpart, Qt.Key_PageDown, Qt.AltModifier) self.assertEqual(self.qpart.cursorPosition[0], 4) QTest.keyClick(self.qpart, Qt.Key_PageDown, Qt.AltModifier) self.assertEqual(self.qpart.cursorPosition[0], 4) QTest.keyClick(self.qpart, Qt.Key_PageUp, Qt.AltModifier) self.assertEqual(self.qpart.cursorPosition[0], 2) QTest.keyClick(self.qpart, Qt.Key_PageUp, Qt.AltModifier) self.assertEqual(self.qpart.cursorPosition[0], 0) QTest.keyClick(self.qpart, Qt.Key_PageUp, Qt.AltModifier) self.assertEqual(self.qpart.cursorPosition[0], 0)
def __init__(self, parent, font): Qutepart.__init__(self, parent) self._sizeHintLabel = QLabel("asdf") self._sizeHintLabel.setFont(font)
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): self.qpart.terminate() def test_1(self): # Indent with Tab self.qpart.indentUseTabs = True self.qpart.text = 'ab\ncd' QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_Tab) self.assertEqual(self.qpart.text, 'ab\n\tcd') self.qpart.indentUseTabs = False QTest.keyClick(self.qpart, Qt.Key_Backspace) QTest.keyClick(self.qpart, Qt.Key_Tab) self.assertEqual(self.qpart.text, 'ab\n cd') def test_2(self): # Unindent Tab self.qpart.indentUseTabs = True self.qpart.text = 'ab\n\t\tcd' self.qpart.cursorPosition = (1, 2) self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, 'ab\n\tcd') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, 'ab\ncd') def test_3(self): # Unindent Spaces self.qpart.indentUseTabs = False self.qpart.text = 'ab\n cd' self.qpart.cursorPosition = (1, 6) self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, 'ab\n cd') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, 'ab\ncd') def test_4(self): # (Un)indent multiline with Tab self.qpart.indentUseTabs = False self.qpart.text = ' ab\n cd' self.qpart.selectedPosition = ((0, 2), (1, 3)) QTest.keyClick(self.qpart, Qt.Key_Tab) self.assertEqual(self.qpart.text, ' ab\n cd') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, ' ab\n cd') def test_4b(self): # Indent multiline including line with zero selection self.qpart.indentUseTabs = True self.qpart.text = 'ab\ncd\nef' self.qpart.position = (0, 0) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Tab) self.assertEqual(self.qpart.text, '\tab\ncd\nef') def test_5(self): # (Un)indent multiline with Space self.qpart.indentUseTabs = False self.qpart.text = ' ab\n cd' self.qpart.selectedPosition = ((0, 2), (1, 3)) QTest.keyClick(self.qpart, Qt.Key_Space, Qt.ShiftModifier) self.assertEqual(self.qpart.text, ' ab\n cd') QTest.keyClick(self.qpart, Qt.Key_Backspace, Qt.ShiftModifier) self.assertEqual(self.qpart.text, ' ab\n cd') def test_6(self): # (Unindent Tab/Space mix self.qpart.indentUseTabs = False self.qpart.text = ' \t \tab' self.qpart.cursorPosition = ((0, 8)) self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, ' \t ab') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, ' \tab') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, ' ab') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, 'ab') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, 'ab') def test_7(self): """Smartly indent python""" self.qpart.detectSyntax(language='Python') QTest.keyClicks(self.qpart, "def main():") QTest.keyClick(self.qpart, Qt.Key_Enter) self.assertEqual(self.qpart.cursorPosition, (1, 4)) QTest.keyClicks(self.qpart, "return 7") QTest.keyClick(self.qpart, Qt.Key_Enter) self.assertEqual(self.qpart.cursorPosition, (2, 0))
def setUp(self): self.qpart = Qutepart() if self.LANGUAGE is not None: self.qpart.detectSyntax(language = self.LANGUAGE) self.qpart.indentWidth = self.INDENT_WIDTH
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): self.qpart.terminate() def test_1(self): # Indent with Tab self.qpart.indentUseTabs = True self.qpart.text = 'ab\ncd' QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_Tab) self.assertEqual(self.qpart.text, 'ab\n\tcd') self.qpart.indentUseTabs = False QTest.keyClick(self.qpart, Qt.Key_Backspace) QTest.keyClick(self.qpart, Qt.Key_Tab) self.assertEqual(self.qpart.text, 'ab\n cd') def test_2(self): # Unindent Tab self.qpart.indentUseTabs = True self.qpart.text = 'ab\n\t\tcd' self.qpart.cursorPosition = (1, 2) self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, 'ab\n\tcd') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, 'ab\ncd') def test_3(self): # Unindent Spaces self.qpart.indentUseTabs = False self.qpart.text = 'ab\n cd' self.qpart.cursorPosition = (1, 6) self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, 'ab\n cd') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, 'ab\ncd') def test_4(self): # (Un)indent multiline with Tab self.qpart.indentUseTabs = False self.qpart.text = ' ab\n cd' self.qpart.selectedPosition = ((0, 2), (1, 3)) QTest.keyClick(self.qpart, Qt.Key_Tab) self.assertEqual(self.qpart.text, ' ab\n cd') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, ' ab\n cd') def test_4b(self): # Indent multiline including line with zero selection self.qpart.indentUseTabs = True self.qpart.text = 'ab\ncd\nef' self.qpart.position = (0, 0) QTest.keyClick(self.qpart, Qt.Key_Down, Qt.ShiftModifier) QTest.keyClick(self.qpart, Qt.Key_Tab) self.assertEqual(self.qpart.text, '\tab\ncd\nef') def test_5(self): # (Un)indent multiline with Space self.qpart.indentUseTabs = False self.qpart.text = ' ab\n cd' self.qpart.selectedPosition = ((0, 2), (1, 3)) QTest.keyClick(self.qpart, Qt.Key_Space, Qt.ShiftModifier | Qt.ControlModifier) self.assertEqual(self.qpart.text, ' ab\n cd') QTest.keyClick(self.qpart, Qt.Key_Backspace, Qt.ShiftModifier | Qt.ControlModifier) self.assertEqual(self.qpart.text, ' ab\n cd') def test_6(self): # (Unindent Tab/Space mix self.qpart.indentUseTabs = False self.qpart.text = ' \t \tab' self.qpart.cursorPosition = ((0, 8)) self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, ' \t ab') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, ' \tab') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, ' ab') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, 'ab') self.qpart.decreaseIndentAction.trigger() self.assertEqual(self.qpart.text, 'ab') def test_7(self): """Smartly indent python""" self.qpart.detectSyntax(language='Python') QTest.keyClicks(self.qpart, "def main():") QTest.keyClick(self.qpart, Qt.Key_Enter) self.assertEqual(self.qpart.cursorPosition, (1, 4)) QTest.keyClicks(self.qpart, "return 7") QTest.keyClick(self.qpart, Qt.Key_Enter) self.assertEqual(self.qpart.cursorPosition, (2, 0))
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): del self.qpart def _markedBlocks(self): bookMarksObject = self.qpart._bookmarks return [block.blockNumber() \ for block in iterateBlocksFrom(self.qpart.document().firstBlock()) \ if bookMarksObject.isBlockMarked(block)] @base.in_main_loop def test_set_with_keyboard(self): self.qpart.text = '\n' * 5 QTest.keyClick(self.qpart, Qt.Key_B, Qt.ControlModifier) QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_B, Qt.ControlModifier) self.assertEqual(self._markedBlocks(), [0, 2]) QTest.keyClick(self.qpart, Qt.Key_B, Qt.ControlModifier) self.assertEqual(self._markedBlocks(), [0]) @unittest.skip('Crashes Qt') @base.in_main_loop def test_set_with_mouse(self): self.qpart.text = '\n' * 5 secondBlock = self.qpart.document().findBlockByNumber(2) geometry = self.qpart.blockBoundingGeometry(secondBlock).translated(self.qpart.contentOffset()) QTest.mouseClick(self.qpart._markArea, Qt.LeftButton, Qt.NoModifier, QPoint(0, geometry.bottom() - 1)) self.assertEqual(self._markedBlocks(), [1]) @base.in_main_loop def test_jump(self): self.qpart.text = '\n' * 5 QTest.keyClick(self.qpart, Qt.Key_B, Qt.ControlModifier) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_B, Qt.ControlModifier) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_B, Qt.ControlModifier) self.assertEqual(self._markedBlocks(), [0, 2, 4]) self.qpart.cursorPosition = (0, 0) QTest.keyClick(self.qpart, Qt.Key_PageDown, Qt.AltModifier) self.assertEqual(self.qpart.cursorPosition[0], 2) QTest.keyClick(self.qpart, Qt.Key_PageDown, Qt.AltModifier) self.assertEqual(self.qpart.cursorPosition[0], 4) QTest.keyClick(self.qpart, Qt.Key_PageDown, Qt.AltModifier) self.assertEqual(self.qpart.cursorPosition[0], 4) QTest.keyClick(self.qpart, Qt.Key_PageUp, Qt.AltModifier) self.assertEqual(self.qpart.cursorPosition[0], 2) QTest.keyClick(self.qpart, Qt.Key_PageUp, Qt.AltModifier) self.assertEqual(self.qpart.cursorPosition[0], 0) QTest.keyClick(self.qpart, Qt.Key_PageUp, Qt.AltModifier) self.assertEqual(self.qpart.cursorPosition[0], 0)
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self.qpart = Qutepart() def tearDown(self): self.qpart.terminate() def test_overwrite_edit(self): self.qpart.show() self.qpart.text = 'abcd' QTest.keyClicks(self.qpart, "stu") self.assertEqual(self.qpart.text, 'stuabcd') QTest.keyClick(self.qpart, Qt.Key_Insert) QTest.keyClicks(self.qpart, "xy") self.assertEqual(self.qpart.text, 'stuxycd') QTest.keyClick(self.qpart, Qt.Key_Insert) QTest.keyClicks(self.qpart, "z") self.assertEqual(self.qpart.text, 'stuxyzcd') def test_overwrite_backspace(self): self.qpart.show() self.qpart.text = 'abcd' QTest.keyClick(self.qpart, Qt.Key_Insert) for i in range(3): QTest.keyClick(self.qpart, Qt.Key_Right) for i in range(2): QTest.keyClick(self.qpart, Qt.Key_Backspace) self.assertEqual(self.qpart.text, 'a d') @base.in_main_loop def test_overwrite_undo(self): self.qpart.show() self.qpart.text = 'abcd' QTest.keyClick(self.qpart, Qt.Key_Insert) QTest.keyClick(self.qpart, Qt.Key_Right) QTest.keyClick(self.qpart, Qt.Key_X) QTest.keyClick(self.qpart, Qt.Key_X) self.assertEqual(self.qpart.text, 'axxd') # Ctrl+Z doesn't work. Wtf??? self.qpart.document().undo() self.qpart.document().undo() self.assertEqual(self.qpart.text, 'abcd') def test_alt_does_not_type(self): """ By default when Alt+Key is pressed - text is inserted. Qutepart ignores this key pressings """ QTest.keyClick(self.qpart, Qt.Key_A, Qt.AltModifier) self.assertEqual(self.qpart.text, '') QTest.keyClick(self.qpart, Qt.Key_A) self.assertEqual(self.qpart.text, 'a') def test_home1(self): """ Test the operation of the home key. """ self.qpart.show() self.qpart.text = ' xx' # Move to the end of this string. self.qpart.cursorPosition = (100, 100) # Press home the first time. This should move to the beginning of the # indent: line 0, column 4. self.assertEqual(self.qpart.cursorPosition, (0, 4)) def column(self): """ Return the column at which the cursor is located.""" return self.qpart.cursorPosition[1] def test_home2(self): """ Test the operation of the home key. """ self.qpart.show() self.qpart.text = '\n\n ' + 'x' * 10000 # Move to the end of this string. self.qpart.cursorPosition = (100, 100) # Press home. We should either move to the line beginning or indent. QTest.keyClick(self.qpart, Qt.Key_Home) # There's no way I can find of determine what the line beginning should # be. So, just press home again if we're not at the indent. if self.column() != 4: # Press home again to move to the beginning of the indent. QTest.keyClick(self.qpart, Qt.Key_Home) # We're at the indent. self.assertEqual(self.column(), 4) # Move to the beginning of the line. QTest.keyClick(self.qpart, Qt.Key_Home) self.assertEqual(self.column(), 0) # Move back to the beginning of the indent. QTest.keyClick(self.qpart, Qt.Key_Home) self.assertEqual(self.column(), 4)
def setUp(self): self.qpart = Qutepart()
class Test(unittest.TestCase): """Base class for tests """ app = base.papp # app crashes, if created more than once def setUp(self): self._window = QMainWindow() self.qpart = Qutepart() self._window.setCentralWidget(self.qpart) self._window.menuBar().addAction(self.qpart.invokeCompletionAction) def tearDown(self): self.qpart.terminate() def test_down_selects_first(self): self.qpart.text = 'aaaa\nbbbb\ncccX\ndddd\ncccY' while self.app.hasPendingEvents(): self.app.processEvents() QTest.keyClicks(self.qpart, "ccc") QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_Enter) QTest.keyClick(self.qpart, Qt.Key_Enter) self.assertEqual(self.qpart.text, 'cccX\naaaa\nbbbb\ncccX\ndddd\ncccY') def test_down_selects_second(self): self.qpart.text = 'aaaa\nbbbb\ncccX\ndddd\ncccY' base._processPendingEvents(self.app) QTest.keyClicks(self.qpart, "ccc") QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_Down) QTest.keyClick(self.qpart, Qt.Key_Enter) QTest.keyClick(self.qpart, Qt.Key_Enter) self.assertEqual(self.qpart.text, 'cccY\naaaa\nbbbb\ncccX\ndddd\ncccY') @unittest.skip("Crashes Qt 4.8.3") def test_click_selects_first(self): self.qpart.text = 'aaaa\nbbbb\ncccX\ndddd\ncccY' QTest.keyClicks(self.qpart, "ccc") QTest.mouseClick(self.qpart, Qt.LeftButton) QTest.keyClick(self.qpart, Qt.Key_Enter) self.assertEqual(self.qpart.text, 'cccY\naaaa\nbbbb\ncccX\ndddd\ncccY') def test_tab_completes(self): self.qpart.text = 'aaaaa\naaaaaXXXXX\n' base._processPendingEvents(self.app) self.qpart.cursorPosition = (2, 0) QTest.keyClicks(self.qpart, "aaa") QTest.keyClick(self.qpart, Qt.Key_Tab) self.assertEqual(self.qpart.text, 'aaaaa\naaaaaXXXXX\naaaaa') QTest.keyClick(self.qpart, Qt.Key_Tab) self.assertEqual(self.qpart.text, 'aaaaa\naaaaaXXXXX\naaaaaXXXXX') def test_manual(self): self._window.show() self.qpart.text = 'aaaaa\naaaaaXXXXX\n' base._processPendingEvents(self.app) self.qpart.cursorPosition = (2, 0) QTest.keyClicks(self.qpart, "a") QTest.keyPress(self.qpart, Qt.Key_Space, Qt.ControlModifier, 100) QTest.keyClicks(self.qpart, "a") QTest.keyClick(self.qpart, Qt.Key_Tab) self.assertEqual(self.qpart.text, 'aaaaa\naaaaaXXXXX\naaaaa') @base.in_main_loop def test_too_long_list(self): self._window.show() self.qpart.text = '\n'.join(['asdf' + str(i) \ for i in range(100)]) + '\n' base._processPendingEvents(self.app) self.qpart.cursorPosition = (100, 0) QTest.keyClicks(self.qpart, "asdf") self.assertIsNotNone(self.qpart._completer._widget) self.qpart.text = '\n'.join(['asdf' + str(i) \ for i in range(1000)]) + '\n' base._processPendingEvents(self.app) self.qpart.cursorPosition = (1000, 0) QTest.keyClicks(self.qpart, "asdf") self.assertIsNone(self.qpart._completer._widget) QTest.keyPress(self.qpart, Qt.Key_Space, Qt.ControlModifier, 100) self.assertIsNotNone(self.qpart._completer._widget)
class Document(QWidget): """ Document is a opened file representation. It contains file management methods and uses `Qutepart <http://qutepart.rtfd.org/>`_ as an editor widget. Qutepart is available as ``qutepart`` attribute. """ documentDataChanged = pyqtSignal() """ documentDataChanged() **Signal** emitted, when document icon or toolTip has changed (i.e. document has been modified externally) """ _EOL_CONVERTOR = {r'\r\n': '\r\n', r'\n': '\n', r'\r': '\r'} def __init__(self, parentObject, filePath, createNew=False): """Create editor and open file. If file is None or createNew is True, empty not saved file is created IO Exceptions are not catched, therefore, must be catched on upper level """ QWidget.__init__(self, parentObject) self._neverSaved = filePath is None or createNew self._filePath = filePath self._externallyRemoved = False self._externallyModified = False # File opening should be implemented in the document classes self._fileWatcher = _FileWatcher(filePath) self._fileWatcher.modified.connect(self._onWatcherFileModified) self._fileWatcher.removed.connect(self._onWatcherFileRemoved) self.qutepart = Qutepart(self) self.qutepart.setStyleSheet('QPlainTextEdit {border: 0}') self.qutepart.userWarning.connect( lambda text: core.mainWindow().statusBar().showMessage(text, 5000)) self._applyQpartSettings() core.uiSettingsManager().dialogAccepted.connect( self._applyQpartSettings) layout = QVBoxLayout(self) layout.setMargin(0) layout.addWidget(self.qutepart) self.setFocusProxy(self.qutepart) if not self._neverSaved: originalText = self._readFile(filePath) self.qutepart.text = originalText else: originalText = '' #autodetect eol, if need self._configureEolMode(originalText) self._tryDetectSyntax() def _tryDetectSyntax(self): if len(self.qutepart.lines) > (100 * 1000) and \ self.qutepart.language() is None: """Qutepart uses too lot of memory when highlighting really big files It may crash the editor, so, do not highlight really big files. But, do not disable highlighting for files, which already was highlighted """ return self.qutepart.detectSyntax(sourceFilePath=self.filePath(), firstLine=self.qutepart.lines[0]) def del_(self): """Explicytly called destructor """ self._fileWatcher.disable() # avoid emitting signals, document shall behave like it is already dead self.qutepart.document().modificationChanged.disconnect() self.qutepart.cursorPositionChanged.disconnect() # self.qutepart.textChanged.disconnect() self.qutepart.terminate() # stop background highlighting, free memory def _onWatcherFileModified(self, modified): """File has been modified """ self._externallyModified = modified self.documentDataChanged.emit() def _onWatcherFileRemoved(self, isRemoved): """File has been removed """ self._externallyRemoved = isRemoved self.documentDataChanged.emit() def _readFile(self, filePath): """Read the file contents. Shows QMessageBox for UnicodeDecodeError """ with open(filePath, 'rb') as openedFile: # Exception is ok, raise it up self._filePath = os.path.abspath( filePath) # abspath won't fail, if file exists data = openedFile.read() self._fileWatcher.setContents(data) try: text = unicode(data, 'utf8') except UnicodeDecodeError, ex: QMessageBox.critical( None, self.tr("Can not decode file"), filePath + '\n' + unicode(str(ex), 'utf8') + '\nProbably invalid encoding was set. ' + 'You may corrupt your file, if saved it') text = unicode(data, 'utf8', 'replace') # Strip last EOL. Qutepart adds it when saving file if text.endswith('\r\n'): text = text[:-2] elif text.endswith('\r') or text.endswith('\n'): text = text[:-1] return text