-
Notifications
You must be signed in to change notification settings - Fork 40
/
Copy pathabc_styler.py
211 lines (201 loc) · 9.5 KB
/
abc_styler.py
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
# Copyright (C) 2011 Nils Liberg (mail: kotorinl at yahoo.co.uk)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
PY3 = sys.version_info >= (3,0,0)
class ABCStyler:
def __init__(self, styled_text_ctrl):
self.e = styled_text_ctrl
self.STYLE_DEFAULT = 0
self.STYLE_COMMENT_NORMAL = 1
self.STYLE_COMMENT_SPECIAL = 2
self.STYLE_GRACE = 3
self.STYLE_FIELD = 4
self.STYLE_FIELD_VALUE = 5
self.STYLE_FIELD_INDEX = 6
self.STYLE_EMBEDDED_FIELD = 7
self.STYLE_EMBEDDED_FIELD_VALUE = 8
self.STYLE_BAR = 9
self.STYLE_CHORD = 10
self.STYLE_STRING = 11
self.STYLE_ORNAMENT_EXCL = 12
self.STYLE_ORNAMENT_PLUS = 13
self.STYLE_ORNAMENT = 14
self.STYLE_LYRICS = 15
self.fields = 'ABCDEFGHIJKLMmNOPQRrSsTUVWwXYZ'
self.ornaments = 'HIJKLMNOPQRSTUVWhijklmnopqrstuvw~'
# go back to default style if next character is \r\n (to avoid some strange syntax highlighting with lyrics at least on mac version)
self.style_changers = {
self.STYLE_FIELD_VALUE: '\n%',
self.STYLE_COMMENT_NORMAL: '\n',
self.STYLE_COMMENT_SPECIAL: '\n%',
self.STYLE_ORNAMENT_EXCL: '!\n%',
self.STYLE_ORNAMENT_PLUS: '+\n%',
self.STYLE_GRACE: '}\n%',
self.STYLE_CHORD: ']\n%',
self.STYLE_EMBEDDED_FIELD_VALUE: ']\n%',
self.STYLE_STRING: '"\n%',
self.STYLE_DEFAULT: '|[:.!%"{+\n' + self.ornaments
}
self.style_keepers = {
self.STYLE_BAR: '|[]:1234',
self.STYLE_ORNAMENT: self.ornaments,
}
self.style_per_char = {
'!': self.STYLE_ORNAMENT_EXCL,
'+': self.STYLE_ORNAMENT_PLUS,
'{': self.STYLE_GRACE,
}
def OnStyleNeeded(self, event):
STYLE_DEFAULT = self.STYLE_DEFAULT
STYLE_COMMENT_NORMAL = self.STYLE_COMMENT_NORMAL
STYLE_COMMENT_SPECIAL = self.STYLE_COMMENT_SPECIAL
STYLE_GRACE = self.STYLE_GRACE
STYLE_FIELD = self.STYLE_FIELD
STYLE_FIELD_VALUE = self.STYLE_FIELD_VALUE
STYLE_FIELD_INDEX = self.STYLE_FIELD_INDEX
STYLE_EMBEDDED_FIELD = self.STYLE_EMBEDDED_FIELD
STYLE_EMBEDDED_FIELD_VALUE = self.STYLE_EMBEDDED_FIELD_VALUE
STYLE_BAR = self.STYLE_BAR
STYLE_CHORD = self.STYLE_CHORD
STYLE_STRING = self.STYLE_STRING
STYLE_ORNAMENT_EXCL = self.STYLE_ORNAMENT_EXCL
STYLE_ORNAMENT_PLUS = self.STYLE_ORNAMENT_PLUS
STYLE_ORNAMENT = self.STYLE_ORNAMENT
STYLE_LYRICS = self.STYLE_LYRICS
STYLE_CHORD = self.STYLE_CHORD
style_changers = self.style_changers
style_per_char = self.style_per_char
style_keepers = self.style_keepers
fields = self.fields
ornaments = self.ornaments
editor = self.e
get_char_at = editor.GetCharAt
get_text_range = editor.GetTextRangeRaw
set_styling = editor.SetStyleBytes
start = editor.GetEndStyled() # this is the first character that needs styling
end = event.GetPosition() # this is the last character that needs styling
line_start = editor.LineFromPosition(start)
start = editor.PositionFromLine(line_start)
state = STYLE_DEFAULT # editor.GetStyleAt(start-1) # init style
text_length = editor.GetTextLength()
next_state = None
try:
editor.StartStyling(start, 31) # only style the text style bits
except:
editor.StartStyling(start)
i = start
chPrev = chr(get_char_at(i-1))
ch = chr(get_char_at(i))
buffer_pos = start + 1
#FAU: In some case of end of line / end of file might end up with a a styles[count] pointing out of range.
#FAU: Add +1 in buffer_size
#buffer_size = min(100000, end-start)
buffer_size = min(100000, end-start+1)
styles = bytearray(buffer_size)
style_changer = None
style_keeper = None
while True:
count = 0
next_buffer = get_text_range(buffer_pos, min(buffer_pos + buffer_size, text_length))
if PY3:
next_buffer = list(map(chr, next_buffer))
buffer_pos += len(next_buffer)
if buffer_pos >= text_length:
if not PY3:
next_buffer = list(next_buffer)
next_buffer.append('\x00') # add a dummy character so the last actual character gets processed too
for chNext in next_buffer:
if (not style_changer or ch in style_changer) and (not style_keeper or not ch in style_keeper):
style_changer = None
if style_keeper:
style_keeper = None
state = STYLE_DEFAULT
if ch in '\r\n':
state = STYLE_DEFAULT
elif state == STYLE_DEFAULT:
if chPrev in '\n[\x00' and ch in fields and chNext == ':':
if chPrev == '[':
state = STYLE_EMBEDDED_FIELD # field on the [M:3/4] form
elif ch in 'wW':
state = STYLE_LYRICS
style_changer = style_changers[STYLE_FIELD_VALUE]
elif ch == 'X':
state = STYLE_FIELD_INDEX
style_changer = style_changers[STYLE_FIELD_VALUE]
else:
state = STYLE_FIELD
elif ch == '|' or (ch in ':.' and chNext in '|:') or (ch == '[' and chNext in '1234'):
state = STYLE_BAR
style_keeper = style_keepers[state]
elif ch in '!+{':
state = style_per_char[ch]
style_changer = style_changers[state]
elif ch == '%':
if chNext == '%' and chPrev in '\n\x00':
state = STYLE_COMMENT_SPECIAL
else:
state = STYLE_COMMENT_NORMAL
style_changer = style_changers[state]
elif ch == '"':
state = STYLE_STRING
style_changer = style_changers[state]
elif ch in ornaments:
state = STYLE_ORNAMENT
style_keeper = style_keepers[state]
elif chPrev == '[':
state = STYLE_CHORD
style_changer = style_changers[state]
elif state in (STYLE_ORNAMENT_EXCL, STYLE_ORNAMENT_PLUS):
if style_per_char.get(ch) == state:
next_state = STYLE_DEFAULT
elif state == STYLE_GRACE:
if ch == '}':
next_state = STYLE_DEFAULT
elif state == STYLE_COMMENT_SPECIAL and chPrev == '%':
style_changer = style_changers[state]
elif state in (STYLE_FIELD_VALUE, STYLE_LYRICS, STYLE_COMMENT_SPECIAL):
if ch == '%' and chPrev != '\\':
state = STYLE_COMMENT_NORMAL
style_changer = style_changers[state]
elif state in (STYLE_CHORD, STYLE_EMBEDDED_FIELD_VALUE):
if ch == ']':
state = STYLE_DEFAULT
elif state == STYLE_FIELD:
if ch == ':':
next_state = STYLE_FIELD_VALUE
style_changer = style_changers[next_state]
elif state == STYLE_EMBEDDED_FIELD:
if ch == ':':
next_state = STYLE_EMBEDDED_FIELD_VALUE
style_changer = style_changers[next_state]
elif state == STYLE_STRING:
if ch == '"':
if chPrev != '\\':
next_state = STYLE_DEFAULT
else:
style_changer = style_changers[state] # when " is escaped with \ then look for next ""
else:
state = STYLE_DEFAULT
style_changer = style_changers[state]
styles[count] = state
count += 1
if next_state is not None:
state = next_state
next_state = None
chPrev = ch
ch = chNext
set_styling(count, bytes(styles))
if buffer_pos >= end:
break