-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathaccessor.py
More file actions
827 lines (759 loc) · 34.1 KB
/
accessor.py
File metadata and controls
827 lines (759 loc) · 34.1 KB
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
#!python
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
# License for the specific language governing rights and limitations
# under the License.
#
# The Original Code is Komodo code.
#
# The Initial Developer of the Original Code is ActiveState Software Inc.
# Portions created by ActiveState Software Inc are Copyright (C) 2000-2007
# ActiveState Software Inc. All Rights Reserved.
#
# Contributor(s):
# ActiveState Software Inc
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
"""The Accessor interface (and implementations) for accessing scintilla
lexer-based styled buffers.
"""
from __future__ import absolute_import
from __future__ import print_function
from __future__ import division
import bisect
import math
import re
import threading
from SilverCity import ScintillaConstants
from codeintel2.common import *
from codeintel2 import util
import six
from six.moves import range
if _xpcom_:
from xpcom import components
from xpcom.client import WeakReference
from xpcom import COMException
class Accessor(object):
"""Virtual base class for a lexed text accessor. This defines an API
with which lexed text data (the text content, styling info, etc.) is
accessed by trigger/completion/etc. handling. Actual instances will
be one of the subclasses.
"""
def char_at_pos(self, pos):
raise VirtualMethodError()
def style_at_pos(self, pos):
raise VirtualMethodError()
def line_and_col_at_pos(self, pos):
raise VirtualMethodError()
def gen_char_and_style_back(self, start, stop):
"""Generate (char, style) tuples backward from start to stop
a la range(start, stop, -1) -- i.e. exclusive at 'stop' index.
For SciMozAccessor this can be implemented more efficiently than
the naive usage of char_at_pos()/style_at_pos().
"""
raise VirtualMethodError()
def gen_char_and_style(self, start, stop):
"""Generate (char, style) tuples forward from start to stop
a la range(start, stop) -- i.e. exclusive at 'stop' index.
For SciMozAccessor this can be implemented more efficiently than
the naive usage of char_at_pos()/style_at_pos().
"""
raise VirtualMethodError()
def match_at_pos(self, pos, s):
"""Return True if the given string matches the text at the given
position.
"""
raise VirtualMethodError()
def line_from_pos(self, pos):
"""Return the 0-based line number for the given position."""
raise VirtualMethodError()
def lines_from_positions(self, positions):
"""Yield the associate 0-based line for each of a number of
positions. This can be much faster than multiple calls to
`line_from_pos` for some accessors.
"""
for pos in positions:
yield self.line_from_pos(pos)
def line_start_pos_from_pos(self, pos):
"""Return the position of the start of the line of the given pos."""
raise VirtualMethodError()
def pos_from_line_and_col(self, line, col):
"""Return the position of the given line and column."""
raise VirtualMethodError()
@property
def text(self):
"""All buffer content (as a unicode string)."""
raise VirtualMethodError()
def text_range(self, start, end):
raise VirtualMethodError()
def length(self):
"""Return the length of the buffer.
Note that whether this returns a *character* pos or a *byte* pos is
left fuzzy so that SilverCity and SciMoz implementations can be
efficient. All that is guaranteed is that the *_at_pos() methods
work as expected.
"""
raise VirtualMethodError()
#def gen_pos_and_char_fwd(self, start_pos):
# """Generate (<pos>, <char>) tuples forward from the starting
# position until the end of the document.
#
# Note that whether <pos> is a *character* pos or a *byte* pos is
# left fuzzy so that SilverCity and SciMoz implementations can be
# efficient.
# """
# raise VirtualMethodError()
def gen_tokens(self):
"""Generator for all styled tokens in the buffer.
Currently this should yield token dict a la SilverCity's
tokenize_by_style().
"""
raise VirtualMethodError()
def contiguous_style_range_from_pos(self, pos):
"""Returns a 2-tuple (start, end) giving the span of the sequence of
characters with the style at position pos."""
raise VirtualMethodError()
class SilverCityAccessor(Accessor):
def __init__(self, lexer, content):
self.lexer = lexer
self.reset_content(content)
def reset_content(self, content):
"""A backdoor specific to this accessor to allow the equivalent of
updating the buffer/file/content.
"""
if isinstance(content, six.text_type):
self.content = content
else:
self.content = content.decode("utf-8")
self.__tokens_cache = None
self.__position_data_cache = None
__tokens_cache = None
@property
def tokens(self):
if self.__tokens_cache is None:
self.__tokens_cache = self.lexer.tokenize_by_style(self.content)
return self.__tokens_cache
def _char_pos_from_byte_pos(self, byte_pos):
line = self.line_from_pos(byte_pos)
byte_offset, char_offset = self.__position_data[line][:2]
try:
while byte_offset < byte_pos:
byte_offset += len(self.content[char_offset].encode("utf-8"))
char_offset += 1
except IndexError:
pass # running past EOF
return char_offset
def char_at_pos(self, byte_pos):
char_pos = self._char_pos_from_byte_pos(byte_pos)
return self.content[char_pos]
def _token_at_pos(self, pos):
#XXX Locality of reference should offer an optimization here.
# Binary search for appropriate token.
lower, upper = 0, len(self.tokens) # [lower-limit, upper-limit)
if upper == 1:
return self.tokens[0]
elif upper == 0:
return {"style": 0, "start_index": 0, "end_index": 0} # blank document
# This being a binary search, we should have a maximum of log2(upper)
# iterations. Enforce that in case we have an issue and hit an infinite
# loop.
for iter_count in range(int(math.ceil(math.log(upper, 2)) + 1)):
idx = ((upper - lower) // 2) + lower
if idx >= len(self.tokens):
return self.tokens[-1]
token = self.tokens[idx]
#print("_token_at_pos %d: token idx=%d text[%d:%d]=%r"\
# % (pos, idx, token["start_index"], token["end_index"],
# token["text"]))
start, end = token["start_index"], token["end_index"]
if pos < token["start_index"]:
upper = idx
elif pos > token["end_index"]:
lower = idx + 1
else:
return token
else:
raise CodeIntelError("style_at_pos binary search sentinel hit: "
"there is likely a logic problem here!")
def style_at_pos(self, pos):
return self._token_at_pos(pos)["style"]
def line_and_col_at_pos(self, pos):
line = self.line_from_pos(pos)
byte_offset, char_offset = self.__position_data[line][:2]
line_char_offset = char_offset
try:
while byte_offset < pos:
byte_offset += len(self.content[char_offset].encode("utf-8"))
char_offset += 1
except IndexError:
char_offset += 1 # EOF
return line, char_offset - line_char_offset
#PERF: If perf is important for this accessor then could do much
# better with smarter use of _token_at_pos() for these two.
def gen_char_and_style_back(self, start, stop):
assert -1 <= stop <= start, "stop: %r, start: %r" % (stop, start)
for pos in range(start, stop, -1):
yield (self.char_at_pos(pos), self.style_at_pos(pos))
def gen_char_and_style(self, start, stop):
assert 0 <= start <= stop, "start: %r, stop: %r" % (start, stop)
for pos in range(start, stop):
yield (self.char_at_pos(pos), self.style_at_pos(pos))
def match_at_pos(self, pos, s):
char_pos = self._char_pos_from_byte_pos(pos)
return self.content[char_pos:char_pos+len(s)] == s
__position_data_cache = None
@property
def __position_data(self):
"""A list holding the cache of line position data. The index is the
line number; the value is a four-tuple of (start pos in bytes,
start pos in chars, line length in bytes, line length in chars).
"""
if self.__position_data_cache is None:
data = []
byte_offset = 0
char_offset = 0
for match in re.finditer(r"\r\n|\r|\n|$", self.content):
char_end = match.end()
char_length = char_end - char_offset
line = self.content[char_offset:char_end]
byte_length = len(line.encode("utf-8"))
data.append((byte_offset, char_offset, byte_length, char_length))
byte_offset += byte_length
char_offset += char_length
assert char_offset == char_end
self.__position_data_cache = data
return self.__position_data_cache
def lines_from_char_positions(self, starts):
"""Yield the 0-based lines given the *character* positions."""
line_starts = [p[1] for p in self.__position_data] # in chars
for char_pos in starts:
# see line_from_pos for the adjustments
yield bisect.bisect_left(line_starts, char_pos + 1) - 1
def line_from_pos(self, byte_pos):
r"""
>>> sa = SilverCityAccessor(lexer,
... #0 1 2 3
... #01234567890 123456789 01234567890 12345
... 'import sys\nif True:\nprint "hi"\n# bye')
>>> sa.line_from_pos(0)
0
>>> sa.line_from_pos(9)
0
>>> sa.line_from_pos(10)
0
>>> sa.line_from_pos(11)
1
>>> sa.line_from_pos(22)
2
>>> sa.line_from_pos(34)
3
>>> sa.line_from_pos(35)
3
"""
# Search for (byte_pos,) in the position data so we will always come
# "before" the line we want (i.e. we have the index of the line itself)
# the +1 is to make sure we get the line after (so we can subtract it)
# this is because for a position not at line start, we get the next line
# instead.
return bisect.bisect_left(self.__position_data, (byte_pos + 1,)) - 1
def line_start_pos_from_pos(self, pos):
return self.__position_data[self.line_from_pos(pos)][0]
def pos_from_line_and_col(self, line, col):
byte_offset, char_offset = self.__position_data[line][:2]
substring = self.content[char_offset:char_offset+col].encode("utf-8")
return byte_offset + len(substring)
@property
def text(self):
return self.content
def text_range(self, start, end):
return self.content[self._char_pos_from_byte_pos(start):
self._char_pos_from_byte_pos(end)]
def length(self):
byte_offset, byte_length = self.__position_data[-1][::2]
return byte_offset + byte_length
def gen_tokens(self):
for token in self.tokens:
yield token
def contiguous_style_range_from_pos(self, pos):
token = self._token_at_pos(pos)
return (token["start_index"], token["end_index"] + 1)
class SciMozAccessor(Accessor):
def __init__(self, scimoz, silvercity_lexer):
self.scimoz = WeakReference(scimoz)
self.silvercity_lexer = silvercity_lexer
def char_at_pos(self, pos):
return self.scimoz().getWCharAt(pos)
def style_at_pos(self, pos):
return self.scimoz().getStyleAt(pos)
def line_and_col_at_pos(self, pos):
scimoz = self.scimoz()
line = scimoz.lineFromPosition(pos)
col = pos - scimoz.positionFromLine(line)
return line, col
# These two are *much* faster than repeatedly calling char_at_pos()
# and style_at_pos().
def gen_char_and_style_back(self, start, stop):
if start > stop:
# For scimoz.getStyledText(), it's (inclusive, exclusive)
styled_text = self.scimoz().getStyledText(stop+1, start+1)
for i in range(len(styled_text)-2, -2, -2):
yield (styled_text[i], ord(styled_text[i+1]))
elif start == stop:
pass
else:
raise AssertionError("start (%r) < stop (%r)" % (start, stop))
def gen_char_and_style(self, start, stop):
if start < stop:
# For scimoz.getStyledText(), it's (inclusive, exclusive)
styled_text = self.scimoz().getStyledText(start, stop)
for i in range(0, len(styled_text), 2):
yield (styled_text[i], ord(styled_text[i+1]))
elif start == stop:
pass
else:
raise AssertionError("start (%r) > stop (%r)" % (start, stop))
#XXX def match_at_pos(self, pos, s):...
def line_from_pos(self, pos):
return self.scimoz().lineFromPosition(pos)
def lines_from_positions(self, positions):
# Note: for a large enough set of positions it might be faster
# to use the algorithm in SilverCityAccessor.
scimoz = self.scimoz()
for pos in positions:
yield scimoz.lineFromPosition(pos)
def line_start_pos_from_pos(self, pos):
scimoz = self.scimoz()
return scimoz.positionFromLine(scimoz.lineFromPosition(pos))
def pos_from_line_and_col(self, line, col):
return self.scimoz().positionFromLine(line) + col
@property
def text(self):
return self.scimoz().text
def text_range(self, start, end):
return self.scimoz().getTextRange(start, end)
def length(self):
return self.scimoz().length
#raise NotImplementedError(
# "Calculating the *character* length of a SciMoz buffer can "
# "be expensive. Are you sure you want to use this method? "
# "Try accessor.gen_pos_and_char_fwd() first.")
def gen_tokens(self):
if self.silvercity_lexer:
#PERF: This is not a great solution but see bug 54217.
acc = SilverCityAccessor(self.silvercity_lexer, self.text)
for token in acc.gen_tokens():
yield token
else:
# Silvercity lexer doesn't exist, use styles straight from SciMoz.
scimoz = self.scimoz()
styled_text = scimoz.getStyledText(0, scimoz.length)
text = styled_text[::2]
styles = styled_text[1::2]
start_index = 0
prev_style = -1
last_i = len(styles) - 1
for i in range(len(styles)):
style = styles[i]
if style != prev_style or i == last_i:
token_text = text[start_index:i]
if token_text:
token = {
'style': ord(prev_style),
'text': token_text,
'start_index': start_index,
'end_index': i-1,
'start_column': 0, # unset
'end_column': 0, # unset
'start_line': 0, # unset
'end_line': 0, # unset
}
yield token
start_index = i
prev_style = style
def contiguous_style_range_from_pos(self, pos):
curr_style = self.style_at_pos(pos)
i = pos - 1
while i >= 0 and self.style_at_pos(i) == curr_style:
i -= 1
start_pos = i + 1
last_pos = self.length()
i = pos + 1
while i < last_pos and self.style_at_pos(i) == curr_style:
i += 1
end_pos = i # Point one past the end
return (start_pos, end_pos)
@property
def udl_family_chunk_ranges(self):
"""Generate a list of continguous UDL-family ranges.
Generates 3-tuples:
(<udl-family>, <start-byte-offset>, <end-byte-offset>)
where
<udl-family> is one of "M", "CSS", "CSL", "SSL", "TPL"
<start-byte-offset> is inclusive
<end-byte-offset> is exclusive (like a Python range)
Note: For non-UDL languages this will return on chunk that is the
whole document and <udl-family> will be None.
"""
# LexUDL will set indicator 18 on the start char (or set of chars)
# beginning a new UDL family section.
scimoz = self.scimoz()
# Note: value must match that in LexUDL.cxx and koILinter.idl.
DECORATOR_UDL_FAMILY_TRANSITION = 18
pos = 0
length = scimoz.length
while pos < length:
start = scimoz.indicatorStart(DECORATOR_UDL_FAMILY_TRANSITION, pos)
end = scimoz.indicatorEnd(DECORATOR_UDL_FAMILY_TRANSITION, start+1)
if start == end == 0: # No indicators.
yield (None, 0, length)
break
start = max(start-1, 0)
#print("range: %d (%r) - %d (%r): %s" % (
# start, scimoz.getWCharAt(start),
# end, scimoz.getWCharAt(end-1),
# self._udl_family_from_style(scimoz.getStyleAt(pos))))
#print(util.indent(repr(scimoz.getTextRange(start, end))))
yield (self._udl_family_from_style(scimoz.getStyleAt(pos)),
start, end)
pos = end + 1
_udl_family_from_start_style = {
ScintillaConstants.SCE_UDL_M_DEFAULT: "M",
ScintillaConstants.SCE_UDL_CSS_DEFAULT: "CSS",
ScintillaConstants.SCE_UDL_CSL_DEFAULT: "CSL",
ScintillaConstants.SCE_UDL_SSL_DEFAULT: "SSL",
ScintillaConstants.SCE_UDL_TPL_DEFAULT: "TPL",
}
_udl_family_start_styles = list(sorted(_udl_family_from_start_style.keys()))
@classmethod
def _udl_family_from_style(cls, style):
"""Determine which UDL family this style is in. Returns one
of M, CSS, CSL, SSL or TPL.
"""
idx = bisect.bisect_right(cls._udl_family_start_styles, style)
start_style = cls._udl_family_start_styles[idx-1]
fam = cls._udl_family_from_start_style[start_style]
return fam
class AccessorCache:
"""Utility class used to cache buffer styling information"""
def __init__(self, accessor, position, fetchsize=20, debug=False):
"""Document accessor cache contructor. Will cache fetchsize style info
pieces starting from position - 1.
@param accessor {Accessor} a form of document accessor
@param position {int} where in the document to start caching from (exclusive)
@param fetchsize {int} how much cache is stored/retrived at a time
"""
self._accessor = accessor
self._cachefetchsize = fetchsize
self._debug = debug
#self._debug = True
self._reset(position)
# Private
def _reset(self, position):
self._pos = position
self._ch = None
self._style = None
# cachePos is used to store where self._pos is inside the _cache
self._cachePos = 0
self._chCache = []
self._styleCache = []
# cacheXXXBufPos is used to store where cache is relative to the buffer
# _cacheFirstBufPos is inclusive
self._cacheFirstBufPos = position
# _cacheLastBufPos is exclusive
self._cacheLastBufPos = position
def _initializeCacheBackwards(self):
self._cachePos -= 1
self._extendCacheBackwards()
def _extendCacheBackwards(self, byAmount=None):
if self._cacheFirstBufPos > 0:
if byAmount is None:
byAmount = self._cachefetchsize
# Generate another n tuples (pos, char, style)
start = max(0, (self._cacheFirstBufPos - byAmount))
# Add more to the start of the cache
extendCount = (self._cacheFirstBufPos - start)
ch_list = []
style_list = []
for ch, style in self._accessor.gen_char_and_style(start, self._cacheFirstBufPos):
ch_list.append(ch)
style_list.append(style)
self._chCache = ch_list + self._chCache
self._styleCache = style_list + self._styleCache
self._cachePos += extendCount
self._cacheFirstBufPos = start
if self._debug:
print("Extended cache by %d, _cachePos: %d, len now: %d" % (
extendCount, self._cachePos, len(self._chCache)))
print("Ch cache now: %r" % (self._chCache))
else:
raise IndexError("No buffer left to examine")
def _initializeCacheForwards(self):
self._cachePos -= 1
self._extendCacheForwards()
def _extendCacheForwards(self, byAmount=None):
buf_length = self._accessor.length()
if self._cacheLastBufPos < buf_length:
if byAmount is None:
byAmount = self._cachefetchsize
# Generate another n tuples (pos, char, style)
end = min(buf_length, (self._cacheLastBufPos + byAmount))
# Add more to the end of the cache
extendCount = end - self._cacheLastBufPos
for ch, style in self._accessor.gen_char_and_style(self._cacheLastBufPos, end):
self._chCache.append(ch)
self._styleCache.append(style)
self._cacheLastBufPos = end
if self._debug:
print("Extended cache by %d, _cachePos: %d, len now: %d" % (
extendCount, self._cachePos, len(self._chCache)))
print("Ch cache now: %r" % (self._chCache))
else:
raise IndexError("No buffer left to examine")
# Public
def dump(self, limit=20):
if len(self._chCache) > 0:
print(" pos: %r, ch: %r, style: %r, cachePos: %r, cache len: %d\n cache: %r" % (self._cachePos + self._cacheFirstBufPos,
self._chCache[self._cachePos],
self._styleCache[self._cachePos],
self._cachePos,
len(self._chCache),
self._chCache))
else:
print("New cache: %r" % (self._chCache[-limit:]))
def setCacheFetchSize(self, size):
self._cachefetchsize = size
def resetToPosition(self, position):
if self._debug:
print("resetToPosition: %d" % (position))
print("self._cacheFirstBufPos: %d" % (self._cacheFirstBufPos))
print("self._cacheLastBufPos: %d" % (self._cacheLastBufPos))
if position >= self._cacheLastBufPos:
if position >= self._cacheLastBufPos + self._cachefetchsize:
# Clear everything
self._reset(position)
return
else:
# Just extend forwards
if self._debug:
print("resetToPosition: extending cache forwards")
self._extendCacheForwards()
elif position < self._cacheFirstBufPos:
if position < self._cacheFirstBufPos - self._cachefetchsize:
# Clear everything
self._reset(position)
return
else:
# Just extend back
if self._debug:
print("resetToPosition: extending cache backwards")
self._extendCacheBackwards()
else:
# It's in the current cache area, we keep that then
pass
self._cachePos = position - self._cacheFirstBufPos
self._ch = self._chCache[self._cachePos]
self._style = self._styleCache[self._cachePos]
self._pos = position
if self._debug:
print("self._cachePos: %d, cacheLen: %d" % (self._cachePos, len(self._chCache)))
print("resetToPosition: p: %r, ch: %r, st: %r" % (self._pos, self._ch, self._style))
#def pushBack(self, numPushed=1):
# """Push back the items that were recetly popped off.
# @returns {int} Number of pushed items
# """
# pushItems = self._popped[-numPushed:]
# pushItems.reverse()
# self._cache += pushItems
# if len(self._popped) > 0:
# self._currentTuple = self._popped[-1]
# else:
# self._currentTuple = (self._currentTuple[0] + numPushed, None, None)
# return len(pushItems)
def getCurrentPosCharStyle(self):
"""Get the current buffer position information.
@returns {tuple} with values (pos, char, style)
"""
return (self._pos, self._ch, self._style)
def getPrevPosCharStyle(self, ignore_styles=None, max_look_back=100):
"""Get the previous buffer position information.
@param ignore_styles {tuple}
@returns {tuple} with values (pos, char, style), these values will
all be None if it exceeds the max_look_back.
@raises IndexError can be raised when nothing left to consume.
"""
count = 0
while count < max_look_back:
count += 1
self._cachePos -= 1
if self._cachePos < 0:
self._extendCacheBackwards()
self._style = self._styleCache[self._cachePos]
if ignore_styles is None or self._style not in ignore_styles:
self._ch = self._chCache[self._cachePos]
break
else:
# Went too far without finding what looking for
return (None, None, None)
self._pos = self._cachePos + self._cacheFirstBufPos
if self._debug:
print("getPrevPosCharStyle:: pos:%d ch:%r style:%d" % (self._pos, self._ch, self._style))
return (self._pos, self._ch, self._style)
def peekPrevPosCharStyle(self, ignore_styles=None, max_look_back=100):
"""Same as getPrevPosCharStyle, but does not move the buffer position.
@param ignore_styles {tuple}
@returns {tuple} with values (pos, char, style), these values will
all be None if it exceeds the max_look_back.
@raises IndexError can be raised when nothing left to consume.
"""
# Store the old values.
old_pos = self._pos
old_ch = self._ch
old_style = self._style
old_cachePos = self._cachePos
old_cacheFirstBufPos = self._cacheFirstBufPos
try:
pos, ch, style = self.getPrevPosCharStyle(ignore_styles, max_look_back)
finally:
# Restore old values.
self._pos = old_pos
self._ch = old_ch
self._style = old_style
# The cache may have gotten extended (which is fine), but in that
# case the old_cachePos is no longer correct, so update it.
cache_extended_by = old_cacheFirstBufPos - self._cacheFirstBufPos
self._cachePos = old_cachePos + cache_extended_by
if self._debug:
print("peekPrevPosCharStyle:: pos:%d ch:%r style:%d" % (pos, ch, style))
return (pos, ch, style)
def getPrecedingPosCharStyle(self, current_style=None, ignore_styles=None,
max_look_back=200):
"""Go back and get the preceding style.
@returns {tuple} with values (pos, char, style)
Returns None for both char and style, when out of characters to look
at and there is still no previous style found.
"""
if current_style is None:
if not self._styleCache:
self._initializeCacheBackwards()
current_style = self._styleCache[self._cachePos]
try:
new_ignore_styles = [current_style]
if ignore_styles is not None:
new_ignore_styles += list(ignore_styles)
return self.getPrevPosCharStyle(new_ignore_styles, max_look_back)
except IndexError:
pass
# Did not find the necessary style
return None, None, None
def getTextBackWithStyle(self, current_style=None, ignore_styles=None,
max_text_len=200):
"""Go back and get the preceding text, which is of a different style.
@returns {tuple} with values (pos, text), pos is position of first text char
"""
old_p = self._pos
new_p, c, style = self.getPrecedingPosCharStyle(current_style,
ignore_styles,
max_look_back=max_text_len)
#print("Return %d:%d" % (new_p, old_p+1))
if style is None: # Ran out of text to look at
new_p = max(0, old_p - max_text_len)
return new_p, self.text_range(new_p, old_p+1)
else:
# We don't eat the new styling info
self._cachePos += 1
return new_p+1, self.text_range(new_p+1, old_p+1)
def getNextPosCharStyle(self, ignore_styles=None, max_look_ahead=100):
"""Get the next buffer position information.
@param ignore_styles {tuple}
@returns {tuple} with values (pos, char, style), these values will
all be None if it exceeds the max_look_ahead.
@raises IndexError can be raised when nothing left to consume.
"""
max_pos = self._cachePos + max_look_ahead
while self._cachePos < max_pos:
self._cachePos += 1
if self._cachePos >= len(self._chCache):
self._extendCacheForwards()
self._style = self._styleCache[self._cachePos]
if ignore_styles is None or self._style not in ignore_styles:
self._ch = self._chCache[self._cachePos]
break
else:
# Went too far without finding what looking for
return (None, None, None)
self._pos = self._cachePos + self._cacheFirstBufPos
if self._debug:
print("getNextPosCharStyle:: pos:%d ch:%r style:%d" % (self._pos, self._ch, self._style))
return (self._pos, self._ch, self._style)
def getSucceedingPosCharStyle(self, current_style=None, ignore_styles=None,
max_look_ahead=200):
"""Go forward and get the next different style.
@returns {tuple} with values (pos, char, style)
Returns None for both char and style, when out of characters to look
at and there is still no previous style found.
"""
if current_style is None:
if not self._styleCache:
self._initializeCacheForwards()
current_style = self._styleCache[self._cachePos]
try:
new_ignore_styles = [current_style]
if ignore_styles is not None:
new_ignore_styles += list(ignore_styles)
return self.getNextPosCharStyle(new_ignore_styles, max_look_ahead)
except IndexError:
pass
# Did not find the necessary style
return None, None, None
def getTextForwardWithStyle(self, current_style=None, ignore_styles=None,
max_text_len=200):
"""Go forward and get the succeeding text, which is of a different style.
@returns {tuple} with values (pos, text), pos is position of last text char.
"""
old_p = self._pos
new_p, c, style = self.getSucceedingPosCharStyle(current_style,
ignore_styles,
max_look_ahead=max_text_len)
if style is None: # Ran out of text to look at
new_p = min(self._accessor.length(), old_p + max_text_len)
return new_p, self.text_range(old_p, new_p)
else:
# We don't eat the new styling info
self._cachePos -= 1
return new_p-1, self.text_range(old_p, new_p)
def text_range(self, start, end):
"""Return text in range buf[start:end]
Note: Start position is inclusive, end position is exclusive.
"""
if start >= self._cacheFirstBufPos and end <= self._cacheLastBufPos:
cstart = start - self._cacheFirstBufPos
cend = end - self._cacheFirstBufPos
if self._debug:
print("text_range:: cstart: %d, cend: %d" % (cstart, cend))
print("text_range:: start: %d, end %d" % (start, end))
print("text_range:: _cacheFirstBufPos: %d, _cacheLastBufPos: %d" % (self._cacheFirstBufPos, self._cacheLastBufPos))
# It's all in the cache
return "".join(self._chCache[cstart:cend])
if self._debug:
print("text_range:: using parent text_range: %r - %r" % (start, end))
return self._accessor.text_range(start, end)