'''
Line-based abstraction layer over StyledTextCtrl.
This software is licensed under the GPL (GNU General Public License) version 2
as it appears here: http://www.gnu.org/copyleft/gpl.html
It is also included with this archive as `gpl.txt <gpl.txt>`_.
'''
import sys
KE = "%s does not support %s"
KE2 = "%s does not support strides != 1, your stride was %s"
TE = "%s requires lines that subclass from basestring not %s"
def Property(name, bases, dict):
get = dict.get('get', None)
set = dict.get('set', None)
de1 = dict.get('de1', None) #that's a numeral 1
doc = dict.get('__doc__', None)
return property(get, set, de1, doc)
class LineAbstraction(object):
if 1:
'''
A 0-indexed line abstraction that supports manipulation of the
provided document.
'''
__slots__ = ('stc curline curlinei curlinep selectedlines '
'selectedlinesi targetlines targetlinesi ').split()
def __init__(self, stc):
self.stc = stc
def _mod(self):
self.stc.gotcharacter()
class curline:
if 1:
'''
Manipulates the current line.
'''
__metaclass__ = Property
def get(self):
return self[self.curlinei]
def set(self, value):
self[self.curlinei] = value
self._mod()
def de1(self):
del self[self.curlinei]
self._mod()
class curlinei:
if 1:
'''
Manipulates the current line's index.
'''
__metaclass__ = Property
def get(self):
x = self.stc.GetCurrentPos()
return self.stc.LineFromPosition(x)
def set(self, index):
x,y = self._line_range(index)
self.stc.SetSelection(x,x)
class curlinep:
if 1:
'''
Manipulates the cursor position in the current line.
'''
__metaclass__ = Property
def get(self):
x = self.stc.GetCurrentPos()
y = self.stc.LineFromPosition(x)
y = self.stc.PositionFromLine(y)
curline = self.curline
if type(curline) is not unicode:
return x-y
#the stc gives offsets related to character positions in utf-8
#encoded text
return len(curline.encode('utf-8')[:x-y].decode('utf-8'))
def set(self, posn):
x = self.stc.GetCurrentPos()
y = self.stc.LineFromPosition(x)
y = self.stc.PositionFromLine(y)
curline = self.curline
if posn < 0:
posn %= len(curline)
if posn > len(curline):
posn = len(curline)
#need to convert to utf-8 encoded offsets on unicode platforms
if type(curline) is unicode:
posn = len(curline[:posn].encode('utf-8'))
self.stc.SetSelection(y+posn, y+posn)
class selectedlines:
if 1:
'''
Manipulates the currently selected lines.
Setting requires a sequence of lines, CR and LF endings are
pre-rstripped from theinputlinesinsertedonebetweeneachline import
during setting.
'''
__metaclass__ = Property
def get(self):
return [self[i] for i in xrange(*self.selectedlinesi)]
def set(self, value):
self.selectedlinesi = self.selectedlinesi
p = self.stc.GetSelection()[0]
if value and (value[-1].endswith('\r') or value[-1].endswith('\n')):
#we don't want to use += here!
value = value + ['']
self.stc.ReplaceSelection(self.stc.format.join([i.rstrip('\r\n') for i in value]))
self.stc.SetSelection(p, self.stc.GetSelection()[1])
def de1(self):
self.selectedlinesi = self.selectedlinesi
self.stc.ReplaceSelection('')
self._mod()
class selectedlinesi:
if 1:
'''
Manipulates the indices of the currently selected lines.
'''
__metaclass__ = Property
def get(self):
x,y = self.stc.GetSelection()
x,y = min(x,y), max(x,y)
start = self._line_range(self.stc.LineFromPosition(x))[0]
#we use y-1 because otherwise
#for i in xrange(10):
# lines.selectedlinesi = lines.selectedlinesi
#will continually select more and more lines
end = self._line_range(self.stc.LineFromPosition(y-1))[1]
return self.stc.LineFromPosition(start), self.stc.LineFromPosition(max(end-1, start))+1
def set(self, range):
try:
start, end = range
except:
raise ValueError, "selected line range must be a sequence of integers of length 2"
if end < start:
start, end = end, start
start = self._line_range(start)[0]
end = self._line_range(end)[0]
self.stc.SetSelection(start, end)
self._mod()
class targetlines:
if 1:
'''
Manipulates the currently targeted lines.
Setting requires a sequence of lines, CR and LF endings are
pre-rstripped from theinputlinesinsertedonebetweeneachline import
during setting.
'''
__metaclass__ = Property
def get(self):
return [self[i] for i in xrange(*self.targetlinesi)]
def set(self, value):
self.targetlinesi = self.targetlinesi
if value and (value[-1].endswith('\r') or value[-1].endswith('\n')):
#we don't want to use += here!
value = value + ['']
self.stc.ReplaceTarget(self.stc.format.join([i.rstrip('\r\n') for i in value]))
self._mod()
def de1(self):
self.targetlinesi = self.targetlinesi
self.stc.ReplaceTarget('')
self._mod()
class targetlinesi:
if 1:
'''
Manipulates the indices of the currently targeted lines.
'''
__metaclass__ = Property
def get(self):
x,y = self.stc.GetTargetStart(), self.stc.GetTargetEnd()
x,y = min(x,y), max(x,y)
start = self._line_range(self.stc.LineFromPosition(x))[0]
#we use y-1 because otherwise
#for i in xrange(10):
# lines.selectedlinesi = lines.selectedlinesi
#will continually select more and more lines
end = self._line_range(self.stc.LineFromPosition(y-1))[1]
return self.stc.LineFromPosition(start), self.stc.LineFromPosition(max(end-1, start))+1
def set(self, range):
try:
start, end = range
except:
raise ValueError, "selected line range must be a sequence of integers of length 2"
if end < start:
start, end = end, start
start = self._line_range(start)[0]
end = self._line_range(end)[0]
self.stc.SetTargetStart(start)
self.stc.SetTargetEnd(end)
def get_indent(self, lineno):
'''
Allows read-only access to indent level for particular lines.
'''
line = self[lineno]
ci = len(line) - len(line.lstrip())
# do we care about the fact that this line is blank?
return len(line[:ci].replace('\t', 8))
def __len__(self):
return self.stc.GetLineCount()
def _line_range(self, index):
'''
Utility method for getting the starting and ending position of this
line.
'''
if not isinstance(index, (int, long)):
raise KeyError, KE%(self.__class__, type(index))
x = len(self)
if index < 0:
index %= x
y = self.stc.GetTextLength()
if index >= x:
return y,y
elif x == 1:
return 0,y
elif index == 0:
return 0, self.stc.PositionFromLine(1)
elif index == x-1:
return self.stc.PositionFromLine(index), y
else:
return self.stc.PositionFromLine(index), self.stc.PositionFromLine(index+1)
def __getitem__(self, index):
'''
Gets a line or contiguous sequence of lines.
That is, it supports slices, but step must be equal to 1.
'''
if not isinstance(index, (int, long, slice)):
raise KeyError, KE%(self.__class__, type(index))
if isinstance(index, slice):
if index.step not in (1, None):
raise KeyError, KE2%(self.__class__, index.step)
start = max(index.start, 0)
stop = index.stop
if stop == None:
stop = sys.maxint
stop = min(stop, len(self)-1)
return [self[i] for i in xrange(start, stop)]
return self.stc.GetTextRange(*self._line_range(index))
def __setitem__(self, index, value):
'''
Changes the content of a line.
Your new line must include a line ending if you want to keep this line
separate from thenext. import
Does not support slices.
'''
if not isinstance(value, basestring):
raise TypeError, TE%(self.__class__, type(value))
y,z = self._line_range(index)
self.stc.SetTargetStart(y)
self.stc.SetTargetEnd(z)
self.stc.ReplaceTarget(value)
self._mod()
def __delitem__(self, index):
'''
Deletes a particular line.
'''
self[index] = ''
self._mod()
def __iter__(self):
'''
Yields every line in the file in order (uses a 'current line' index,
so things like:
for i,j in enumerate(lines):
del lines[i]
will actually delete every other line.
'''
i = 0
while i < len(self):
yield self[i]
i += 1
|