1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
|
"""
Adapted from a code editor component created
for Enki editor as replacement for QScintilla.
Copyright (C) 2020 Andrei Kopats
Originally licensed under the terms of GNU Lesser General Public License
as published by the Free Software Foundation, version 2.1 of the license.
This is compatible with Orange3's GPL-3.0 license.
"""
from AnyQt.QtGui import QTextCursor
# Lines class.
# list-like object for access text document lines
def _iterateBlocksFrom(block):
while block.isValid():
yield block
block = block.next()
def _atomicModification(func):
"""Decorator
Make document modification atomic
"""
def wrapper(*args, **kwargs):
self = args[0]
with self._qpart: # pylint: disable=protected-access
func(*args, **kwargs)
return wrapper
class Lines:
"""list-like object for access text document lines
"""
def __init__(self, qpart):
self._qpart = qpart
self._doc = qpart.document()
def setDocument(self, document):
self._doc = document
def _toList(self):
"""Convert to Python list
"""
return [block.text() \
for block in _iterateBlocksFrom(self._doc.firstBlock())]
def __str__(self):
"""Serialize
"""
return str(self._toList())
def __len__(self):
"""Get lines count
"""
return self._doc.blockCount()
def _checkAndConvertIndex(self, index):
"""Check integer index, convert from less than zero notation
"""
if index < 0:
index = len(self) + index
if index < 0 or index >= self._doc.blockCount():
raise IndexError('Invalid block index', index)
return index
def __getitem__(self, index):
"""Get item by index
"""
def _getTextByIndex(blockIndex):
return self._doc.findBlockByNumber(blockIndex).text()
if isinstance(index, int):
index = self._checkAndConvertIndex(index)
return _getTextByIndex(index)
elif isinstance(index, slice):
start, stop, step = index.indices(self._doc.blockCount())
return [_getTextByIndex(blockIndex) \
for blockIndex in range(start, stop, step)]
@_atomicModification
def __setitem__(self, index, value):
"""Set item by index
"""
def _setBlockText(blockIndex, text):
cursor = QTextCursor(self._doc.findBlockByNumber(blockIndex))
cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor)
cursor.insertText(text)
if isinstance(index, int):
index = self._checkAndConvertIndex(index)
_setBlockText(index, value)
elif isinstance(index, slice):
# List of indexes is reversed for make sure
# not processed indexes are not shifted during document modification
start, stop, step = index.indices(self._doc.blockCount())
if step > 0:
start, stop, step = stop - 1, start - 1, step * -1
blockIndexes = list(range(start, stop, step))
if len(blockIndexes) != len(value):
raise ValueError('Attempt to replace %d lines with %d lines' %
(len(blockIndexes), len(value)))
for blockIndex, text in zip(blockIndexes, value[::-1]):
_setBlockText(blockIndex, text)
@_atomicModification
def __delitem__(self, index):
"""Delete item by index
"""
def _removeBlock(blockIndex):
block = self._doc.findBlockByNumber(blockIndex)
if block.next().isValid(): # not the last
cursor = QTextCursor(block)
cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor)
elif block.previous().isValid(): # the last, not the first
cursor = QTextCursor(block.previous())
cursor.movePosition(QTextCursor.EndOfBlock)
cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor)
cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor)
else: # only one block
cursor = QTextCursor(block)
cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor)
cursor.removeSelectedText()
if isinstance(index, int):
index = self._checkAndConvertIndex(index)
_removeBlock(index)
elif isinstance(index, slice):
# List of indexes is reversed for make sure
# not processed indexes are not shifted during document modification
start, stop, step = index.indices(self._doc.blockCount())
if step > 0:
start, stop, step = stop - 1, start - 1, step * -1
for blockIndex in range(start, stop, step):
_removeBlock(blockIndex)
class _Iterator:
"""Blocks iterator. Returns text
"""
def __init__(self, block):
self._block = block
def __iter__(self):
return self
def __next__(self):
if self._block.isValid():
self._block, result = self._block.next(), self._block.text()
return result
else:
raise StopIteration()
def __iter__(self):
"""Return iterator object
"""
return self._Iterator(self._doc.firstBlock())
@_atomicModification
def append(self, text):
"""Append line to the end
"""
cursor = QTextCursor(self._doc)
cursor.movePosition(QTextCursor.End)
cursor.insertBlock()
cursor.insertText(text)
@_atomicModification
def insert(self, index, text):
"""Insert line to the document
"""
if index < 0 or index > self._doc.blockCount():
raise IndexError('Invalid block index', index)
if index == 0: # first
cursor = QTextCursor(self._doc.firstBlock())
cursor.insertText(text)
cursor.insertBlock()
elif index != self._doc.blockCount(): # not the last
cursor = QTextCursor(self._doc.findBlockByNumber(index).previous())
cursor.movePosition(QTextCursor.EndOfBlock)
cursor.insertBlock()
cursor.insertText(text)
else: # last append to the end
self.append(text)
|