Skip to content

Commit 302c5fb

Browse files
committed
added example scripts received from claudia frank by mail
1 parent 2b6f613 commit 302c5fb

File tree

11 files changed

+1787
-694
lines changed

11 files changed

+1787
-694
lines changed

scripts/Samples/ColumnLexer.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
ColumnLexer - Demo
4+
5+
A column based lexer, odd and even columns will be styled differently.
6+
7+
Usage:
8+
Default column separator is comma, redefine COLUMN_SEPARATOR if needed.
9+
Run script.
10+
11+
Note: By commenting or deleting everything, including,
12+
the <comment_or_delete> tags it is ready to be used
13+
as an additional lexer
14+
15+
"""
16+
17+
from Npp import editor, notepad, LEXER, SCINTILLANOTIFICATION, NOTIFICATION, LANGTYPE
18+
19+
try:
20+
# on first run this will generate an NameError exception
21+
COLUMN_LEXER().main()
22+
except NameError:
23+
24+
class COLUMN_LEXER_SINGLETON(type):
25+
''' Ensures that only one column lexer instance exists and
26+
prevents of getting multiple callbacks
27+
'''
28+
_instance = None
29+
def __call__(cls, *args, **kwargs):
30+
''' The real constructor and first method called when
31+
a new instance should be created.
32+
On first instantiation class variable _instance gets itself assigned,
33+
every subsequent instantiation try returns this object
34+
'''
35+
if cls._instance is None:
36+
cls._instance = super(COLUMN_LEXER_SINGLETON, cls).__call__(*args, **kwargs)
37+
return cls._instance
38+
39+
class COLUMN_LEXER(object):
40+
''' A column based lexer implementation.
41+
Odd and even columns do get different styles assigned.
42+
Only one separator can be defined.
43+
Line-based approach - styling takes place when new line has been added
44+
'''
45+
__metaclass__ = COLUMN_LEXER_SINGLETON
46+
47+
DEFAULT = 0 # don't change it
48+
ODD_COLUMN_STYLE = 50
49+
EVEN_COLUMN_STYLE = 51
50+
COLUMN_SEPARATOR = ','
51+
52+
def __init__(self):
53+
''' Register needed callbacks on first class instantiation '''
54+
editor.callbackSync(self.styleneeded_callback, [SCINTILLANOTIFICATION.STYLENEEDED])
55+
notepad.callback(self.bufferactivated_callback, [NOTIFICATION.BUFFERACTIVATED])
56+
notepad.callback(self.langchanged_callback, [NOTIFICATION.LANGCHANGED])
57+
58+
59+
def column_lexer(self, start_pos, end_pos):
60+
''' Main lexing logic.
61+
Gets called by styleneeded callback
62+
'''
63+
64+
def style_it(start, length, STYLE):
65+
''' Inform scintilla to do the styling'''
66+
if length >= 0:
67+
editor.startStyling(start, 31)
68+
editor.setStyling(length, STYLE)
69+
70+
# first everything will be styled with default style
71+
style_it(start_pos, end_pos-start_pos, self.DEFAULT)
72+
73+
# loop over line indexes from start_pos and end_pos
74+
for line in range(editor.lineFromPosition(start_pos), editor.lineFromPosition(end_pos)):
75+
# get start position for each line
76+
line_start_pos = editor.positionFromLine(line)
77+
# split current line into columns
78+
columns = editor.getLine(line).split(self.COLUMN_SEPARATOR)
79+
if columns > 0:
80+
# iterate over all columns
81+
for i, column in enumerate(columns):
82+
# get the width of the current column
83+
col_length = len(column)
84+
if i % 2 == 0:
85+
# even column
86+
style_it(line_start_pos,col_length,self.EVEN_COLUMN_STYLE)
87+
else:
88+
# odd column
89+
style_it(line_start_pos,col_length, self.ODD_COLUMN_STYLE)
90+
# recalculate start position for next column
91+
line_start_pos += col_length+1
92+
93+
# this needs to stay and to be the last line, to signal scintilla we are done.
94+
editor.startStyling(end_pos,31)
95+
96+
def init_scintilla(self):
97+
''' Initialize configured styles '''
98+
editor.setMarginWidthN(0,38)
99+
editor.setMarginWidthN(1,14)
100+
editor.setMarginWidthN(2,0)
101+
102+
if editor.getLexer() != LEXER.CONTAINER:
103+
editor.setLexer(LEXER.CONTAINER)
104+
105+
editor.styleSetFore(self.ODD_COLUMN_STYLE, (54,125,198))
106+
editor.styleSetFore(self.EVEN_COLUMN_STYLE, (87,166,74))
107+
editor.styleSetBack(self.EVEN_COLUMN_STYLE, (0,50,20))
108+
109+
110+
def set_lexer_doc(self, bool_value):
111+
''' Assign the class name as an additional property
112+
to every document which should be handled by this lexer
113+
A value of 1 indicates it should be handled.
114+
'''
115+
editor.setProperty(self.__class__.__name__, 1 if bool_value is True else 0)
116+
117+
118+
def is_lexer_doc(self):
119+
''' Check if the current document is of interest
120+
by reading the class name property.
121+
'''
122+
return True if editor.getPropertyInt(self.__class__.__name__) == 1 else False
123+
124+
125+
def styleneeded_callback(self,args):
126+
''' Called by scintilla to inform the lexer
127+
about the need to style the document.
128+
If document is of interest call main logic (column_lexer) function
129+
Ensures that the start position is really the first position per line
130+
'''
131+
if self.is_lexer_doc():
132+
startPos = editor.getEndStyled()
133+
lineNumber = editor.lineFromPosition(startPos)
134+
startPos = editor.positionFromLine(lineNumber)
135+
self.column_lexer(startPos, args['position'])
136+
137+
138+
def bufferactivated_callback(self,args):
139+
''' Called by notepad when document switch happens
140+
If document is of interest styles need to be reinitialized
141+
'''
142+
if self.is_lexer_doc():
143+
self.init_scintilla()
144+
145+
146+
def langchanged_callback(self,args):
147+
''' Called by notepad when a built-in or udl language switch happens
148+
If document was previously styled by this lexer it will be reset
149+
and therefore will not be styled by this lexer anymore until
150+
script gets executed on this document again.
151+
'''
152+
if self.is_lexer_doc():
153+
self.set_lexer_doc(False)
154+
155+
156+
def main(self):
157+
''' Main entry point
158+
To prevent issues with other lexers document language will
159+
be set to normal text, then document does get the class name
160+
property assigned, styles do get initialized and main lexing
161+
function does get called on whole document
162+
'''
163+
notepad.setLangType(LANGTYPE.TXT)
164+
self.set_lexer_doc(True)
165+
self.init_scintilla()
166+
self.column_lexer(0, editor.getTextLength())
167+
168+
# <comment_or_delete>
169+
# just some demo text not really needed by lexer
170+
notepad.new()
171+
editor.appendText('''
172+
col1,col2,col3,col4
173+
col1 , col2, col3 , col4
174+
,,col3,col4
175+
col1,column2,col3,colum4
176+
jdjkslddskjfd,jfdksldlskdfkjklsdfs,jfkdslldkjfds,jkfdjskdlskdfj
177+
'''
178+
)
179+
# </comment_or_delete>
180+
181+
COLUMN_LEXER().main()
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
EnhancedPythonLexer
4+
5+
Sometimes a built-in lexer isn't doing what one wants.
6+
To enhance the built-in lexer, indicators can be used, which also automatically
7+
prevent clashes with the styling operations done by the built-in lexer.
8+
The only potential issue might be using an indicator used by npp itself.
9+
10+
Usage:
11+
In the main function
12+
- define the lexer_name to be enhanced and
13+
- create the regex_dict with the regular expressions and
14+
- the associate colours
15+
16+
The regex dict must be in form key_tuple:value_tuple.
17+
18+
The key_tuple needs to have an unique ID followed by a colour tuple,
19+
like (3, (181, 188, 201)).
20+
In this case the same colour can be used with different regexes.
21+
22+
The value_tuple should use an unicode string followed by a number
23+
indicating the (sub-)match,
24+
like (u'\\b(cls|self)\\b',0)
25+
which would colour the main match (the words cls and self) or like
26+
(u'(?:(?:def)\s\w+)\s*\((.*)\):',1)
27+
which would colour the first submatch, which is (.*) and in this case
28+
are basically the parameters of a python function.
29+
30+
Run script.
31+
32+
Note: could be used as an UDL alternative as well.
33+
34+
"""
35+
36+
from Npp import editor, editor1, editor2, notepad, NOTIFICATION, SCINTILLANOTIFICATION, INDICATORSTYLE
37+
38+
try:
39+
EnhancedPythonLexer().main()
40+
except NameError:
41+
42+
# ensure that only a single instance is used to prevent getting
43+
# multiple callbacks executed
44+
class SingletonEnhancedPythonLexer(type):
45+
_instance = None
46+
def __call__(cls, *args, **kwargs):
47+
if cls._instance is None:
48+
cls._instance = super(SingletonEnhancedPythonLexer, cls).__call__(*args, **kwargs)
49+
return cls._instance
50+
51+
52+
# main class
53+
class EnhancedPythonLexer(object):
54+
55+
__metaclass__ = SingletonEnhancedPythonLexer
56+
57+
@staticmethod
58+
def set_indicator_attributes(indicator_number, fore=(0,0,0)):
59+
editor1.indicSetFore(indicator_number, fore)
60+
editor1.indicSetStyle(indicator_number, INDICATORSTYLE.TEXTFORE)
61+
editor2.indicSetFore(indicator_number, fore)
62+
editor2.indicSetStyle(indicator_number, INDICATORSTYLE.TEXTFORE)
63+
return indicator_number
64+
65+
66+
@staticmethod
67+
def paint_it(indicator, pos, length):
68+
current_line = editor.lineFromPosition(pos)
69+
line_start_position = editor.positionFromLine(current_line)
70+
text = editor.getLine(current_line)
71+
found_comment_char = text.find('#')
72+
relative_line_position = pos-line_start_position
73+
if((-1 < found_comment_char < relative_line_position) or
74+
(text.count('"', 0, relative_line_position) % 2 == 1) or
75+
(text.count("'", 0, relative_line_position) % 2 == 1)):
76+
return
77+
else:
78+
editor.setIndicatorCurrent(indicator)
79+
editor.indicatorFillRange(pos,length)
80+
81+
82+
def do_regex(self, regex, indicator, start_position, end_position):
83+
editor.setIndicatorCurrent(indicator)
84+
editor.indicatorClearRange(start_position, end_position-start_position)
85+
editor.research(regex[0],
86+
lambda m: self.paint_it(indicator, m.span(regex[1])[0], m.span(regex[1])[1] - m.span(regex[1])[0]),
87+
0,
88+
start_position,
89+
end_position)
90+
91+
92+
def style(self):
93+
line_number = editor.getFirstVisibleLine()
94+
start_position = editor.positionFromLine(line_number)
95+
end_position = editor.getLineEndPosition(line_number + editor.linesOnScreen())
96+
for indicator, regex in self.regex_dict.items():
97+
self.do_regex(regex, indicator, start_position, end_position)
98+
99+
100+
def __init__(self):
101+
editor.callbackSync(self.on_updateui, [SCINTILLANOTIFICATION.UPDATEUI])
102+
notepad.callback(self.on_langchanged, [NOTIFICATION.LANGCHANGED])
103+
notepad.callback(self.on_bufferactivated, [NOTIFICATION.BUFFERACTIVATED])
104+
self.__is_lexer_doc = False
105+
self.get_lexer_name = lambda: notepad.getLanguageName(notepad.getLangType())
106+
107+
108+
def set_lexer_doc(self,bool_value):
109+
editor.setProperty(self.__class__.__name__, 1 if bool_value is True else -1)
110+
self.__is_lexer_doc = bool_value
111+
112+
113+
def on_bufferactivated(self,args):
114+
if (self.get_lexer_name() == self.lexer_name) and (editor.getPropertyInt(self.__class__.__name__) != -1):
115+
self.__is_lexer_doc = True
116+
else:
117+
self.__is_lexer_doc = False
118+
119+
120+
def on_updateui(self,args):
121+
if self.__is_lexer_doc:
122+
self.style()
123+
124+
125+
def on_langchanged(self,args):
126+
self.set_lexer_doc(True if self.get_lexer_name() == self.lexer_name else False)
127+
128+
129+
# customize, if needed
130+
def main(self):
131+
# basically what is returned by notepad.getLanguageName(notepad.getLangType())
132+
self.lexer_name = 'Python'
133+
134+
self.regex_dict = {
135+
self.set_indicator_attributes(0, (224, 108, 117)) : (u'\\b(cls|self)\\b', 0), # cls and self objects
136+
self.set_indicator_attributes(1, (209, 154, 102)) : (u'(?:(?:def)\s\w+)\s*\((.*)\):', 1), # function parameter
137+
self.set_indicator_attributes(2, (86, 182, 194)) : (u'(\\beditor\\b|\\beditor1\\b|\\beditor2\\b|\\bnotepad\\b|\\bconsole\\b|\\b__init__\\b|\\b__call__\\b|\\b__del__\\b|\\bsuper\\b|\\bobject\\b|\\btype\\b|\\bprint\\b)', 0), # dunder functions and special keywords
138+
self.set_indicator_attributes(3, (181, 188, 201)) : (u'[\(\)\[\]\{\}\=,]', 0), # delimiters - default color
139+
self.set_indicator_attributes(4, (181, 188, 201)) : (u'\(\\b(bcls|self)\\b', 1), # cls and self object within function parameters - default color
140+
self.set_indicator_attributes(5, (181, 188, 201)) : (u'\(\s*\K\*\*|\(\s*\K\*|,\s*\K\*\*|,\s*\K\*', 0), # args and kwargs references within function parameters - default color
141+
self.set_indicator_attributes(6, (79, 175, 239)) : (u'(?(?<=def )(?:)(?=\()|(?(?<!class )\\b\w+?\\b(?=\()|(?:)))', 0), # functions and class instances but not definitions
142+
}
143+
144+
self.set_lexer_doc(True)
145+
self.style()
146+
147+
148+
EnhancedPythonLexer().main()
149+

0 commit comments

Comments
 (0)