/
LogfileLexer.py
191 lines (150 loc) · 7.03 KB
/
LogfileLexer.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
# -*- coding: utf-8 -*-
"""
LogfileLexer - Demo
Highlights whole lines which matches the regex.
Usage:
Load a logfile, modify error and warning regex and
run script to see how it works
Note: By commenting or deleting everything, including,
the <comment_or_delete> tags it is ready to be used
as an additional lexer
"""
from Npp import editor, notepad, LEXER, SCINTILLANOTIFICATION, NOTIFICATION, LANGTYPE
import re
try:
# on first run this will generate an NameError exception
LOGFILE_LEXER().main()
except NameError:
class LOGFILE_LEXER_SINGLETON(type):
''' Ensures that only one log file lexer instance exists and
prevents of getting multiple callbacks
'''
_instance = None
def __call__(cls, *args, **kwargs):
''' The real constructor and first method called when
a new instance should be created.
On first instantiation class variable _instance gets itself assigned,
every subsequent instantiation try returns this object
'''
if cls._instance is None:
cls._instance = super(LOGFILE_LEXER_SINGLETON, cls).__call__(*args, **kwargs)
return cls._instance
class LOGFILE_LEXER(object):
''' A line based lexer implementation.
Whole line gets coloured if part or complete line matches regex
'''
__metaclass__ = LOGFILE_LEXER_SINGLETON
DEFAULT = 0 # the current default style
ERROR_STYLE = 60
WARNING_STYLE = 61
# define the style colors
ERROR_STYLE_FOREGROUND = (224,108,117)
WARNING_STYLE_FOREGROUND = (255,255,128)
# define the regex which return the position of the matches
ERROR_REGEX = '.*error.*'
WARNING_REGEX = '.*warning.*'
def __init__(self):
''' Register needed callbacks on first class instantiation '''
editor.callbackSync(self.styleneeded_callback, [SCINTILLANOTIFICATION.STYLENEEDED])
notepad.callback(self.bufferactivated_callback, [NOTIFICATION.BUFFERACTIVATED])
notepad.callback(self.langchanged_callback, [NOTIFICATION.LANGCHANGED])
def logfile_lexer(self, start_pos, end_pos):
''' Main lexing logic.
Gets called by styleneeded callback
'''
def style_it(match, STYLE):
''' Inform scintilla to do the styling'''
if match[1]-match[0] >= 0:
editor.startStyling(start_pos + match[0], 31)
editor.setStyling(match[1]-match[0], STYLE)
def do_regex(regex):
''' return a list of match positions
Note, is using python regular expression instead of boost::re
'''
return [m.span(0) for m in re.finditer(regex, text, flags=re.I)]
# ensure that position is really the first position for each line
start_pos = editor.positionFromLine(editor.lineFromPosition(start_pos))
# fast but potentially unsafe way to get the text of the line
text = editor.getRangePointer(start_pos, end_pos-start_pos)
# first everything will be styled with default style
style_it((start_pos, end_pos), self.DEFAULT)
# map style_it function to each match returned by do_regex
# ordering is important as it might be that a line matches
# multiple regexes - the last do_regex overwrites previous styling
map(lambda match: style_it(match, self.WARNING_STYLE), do_regex(self.WARNING_REGEX))
map(lambda match: style_it(match, self.ERROR_STYLE), do_regex(self.ERROR_REGEX))
# this needs to stay and to be the last line, to signal scintilla we are done.
editor.startStyling(end_pos,31)
def init_scintilla(self):
''' Initialize configured styles '''
editor.setMarginWidthN(0,38)
editor.setMarginWidthN(1,14)
editor.setMarginWidthN(2,0)
if editor.getLexer() != LEXER.CONTAINER:
editor.setILexer(0)
editor.styleSetFore(self.ERROR_STYLE, self.ERROR_STYLE_FOREGROUND)
editor.styleSetFore(self.WARNING_STYLE, self.WARNING_STYLE_FOREGROUND)
def set_lexer_doc(self,bool_value):
''' Assign the class name as an additional property
to every document which should be handled by this lexer
A value of 1 indicates it should be handled.
'''
editor.setProperty(self.__class__.__name__, 1 if bool_value is True else 0)
def is_lexer_doc(self):
''' Check if the current document is of interest
by reading the class name property.
'''
return True if editor.getPropertyInt(self.__class__.__name__, 0) == 1 else False
def styleneeded_callback(self,args):
''' Called by scintilla to inform the lexer
about the need to style the document.
If document is of interest call main logic (logfile_lexer) function
Ensures that the start position is really the first position per line
'''
if self.is_lexer_doc():
startPos = editor.getEndStyled()
lineNumber = editor.lineFromPosition(startPos)
startPos = editor.positionFromLine(lineNumber)
self.logfile_lexer(startPos, args['position'])
def bufferactivated_callback(self,args):
''' Called by notepad when document switch happens
If document is of interest styles need to be reinitialized
'''
if self.is_lexer_doc():
self.init_scintilla()
def langchanged_callback(self,args):
''' Called by notepad when a built-in or udl language switch happens
If document was previously styled by this lexer it will be reset
and therefore will not be styled by this lexer anymore until
script gets executed on this document again.
'''
if self.is_lexer_doc():
self.set_lexer_doc(False)
def main(self):
''' Main entry point
To prevent issues with other lexers document language will
be set to normal text, then document does get the class name
property assigned, styles do get initialized and main lexing
function does get called on whole document
'''
notepad.setLangType(LANGTYPE.TXT)
self.set_lexer_doc(True)
self.init_scintilla()
self.logfile_lexer(0, editor.getTextLength())
# <comment_or_delete>
# just some demo text not really needed by lexer
notepad.new()
editor.appendText('''
A line with no keywords
followed by a warning line
some
fillings
until
a line with ERROR appears
again
some
text
and a line with error and WARNING
''')
# </comment_or_delete>
LOGFILE_LEXER().main()