src/eric7/QScintilla/Lexers/SubstyledLexer.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2003 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the lexer mixin class.
8 """
9
10 import copy
11
12 from PyQt6.QtGui import QColor
13
14 from .Lexer import Lexer
15
16 import Preferences
17
18
19 class SubstyledLexer(Lexer):
20 """
21 Class to implement the sub-styled lexer mixin class.
22 """
23 def __init__(self):
24 """
25 Constructor
26 """
27 super().__init__()
28
29 self.baseStyles = []
30 # list of style numbers, that support sub-styling
31 self.defaultSubStyles = {}
32 # dictionary with sub-styling data
33 # main key: base style number, value : dict with
34 # key: sub-style number, value: dict with
35 # 'Description': string containing a short description
36 # 'Words': string of whitespace separated words to be styled
37 # 'Style': dictionary with styling data (only difference to
38 # the base style is required
39 # 'fore': foreground color (int containing RGB values)
40 # 'paper': background color (int containing RGB values)
41 # 'eolfill': fill to eol (bool)
42 # 'font_family': font family (str)
43 # 'font_size: point size (int)
44 # 'font_bold: bold font (bool)
45 # 'font_italic: italic font (bool)
46 # 'font_underline: underlined font (bool)
47
48 self.__subStyles = {}
49 self.__subStylesInitialized = False
50
51 def loadAllDefaultSubStyles(self):
52 """
53 Public method to load the default sub-style definitions.
54 """
55 self.__subStyles = copy.deepcopy(self.defaultSubStyles)
56
57 self.__subStylesInitialized = True
58
59 def loadDefaultSubStyles(self, style):
60 """
61 Public method to load the default sub-styles for a given base style.
62
63 @param style style number
64 @type int
65 """
66 if style in self.defaultSubStyles:
67 self.__subStyles[style] = copy.deepcopy(
68 self.defaultSubStyles[style])
69
70 def loadSubstyles(self):
71 """
72 Public method to load the sub-styles from the settings file.
73 """
74 settings = Preferences.getSettings()
75
76 # Step 1: check if sub-styles were defined and saved
77 subStylesDefined = False
78 for baseStyle in self.baseStyles:
79 key = "Scintilla/{0}/style{1}/SubStyleLength".format(
80 self.language(), baseStyle)
81 subStylesDefined |= settings.contains(key)
82 # Step 2.1: load default sub-styles, if none were defined
83 if not subStylesDefined:
84 self.loadAllDefaultSubStyles()
85
86 # Step 2.2: load from settings file
87 else:
88 self.__subStyles = {}
89 for baseStyle in self.baseStyles:
90 key = "Scintilla/{0}/style{1}/SubStyleLength".format(
91 self.language(), baseStyle)
92 if settings.contains(key):
93 subStyleLength = int(settings.value(key))
94 if subStyleLength:
95 self.__subStyles[baseStyle] = {}
96 for subStyle in range(subStyleLength):
97 substyleKey = (
98 "Scintilla/{0}/style{1}/substyle{2}/"
99 ).format(self.language(), baseStyle, subStyle)
100 if settings.contains(substyleKey + "Description"):
101 subStyleData = {}
102 subStyleData["Description"] = settings.value(
103 substyleKey + "Description", "")
104 subStyleData["Words"] = settings.value(
105 substyleKey + "Words", "")
106 style = {}
107
108 key = substyleKey + "fore"
109 if settings.contains(key):
110 style["fore"] = int(settings.value(key))
111 key = substyleKey + "paper"
112 if settings.contains(key):
113 style["paper"] = int(settings.value(key))
114 key = substyleKey + "eolfill"
115 if settings.contains(key):
116 style["eolfill"] = Preferences.toBool(
117 settings.value(key))
118 key = substyleKey + "font_family"
119 if settings.contains(key):
120 style["font_family"] = settings.value(key)
121 key = substyleKey + "font_size"
122 if settings.contains(key):
123 style["font_size"] = (
124 int(settings.value(key))
125 )
126 key = substyleKey + "font_bold"
127 if settings.contains(key):
128 style["font_bold"] = Preferences.toBool(
129 settings.value(key))
130 key = substyleKey + "font_italic"
131 if settings.contains(key):
132 style["font_italic"] = Preferences.toBool(
133 settings.value(key))
134 key = substyleKey + "font_underline"
135 if settings.contains(key):
136 style["font_underline"] = (
137 Preferences.toBool(settings.value(key))
138 )
139
140 subStyleData["Style"] = style
141
142 self.__subStyles[baseStyle][subStyle] = (
143 subStyleData
144 )
145
146 else:
147 # initialize with default
148 self.__subStyles[baseStyle][subStyle] = (
149 copy.deepcopy(self.defaultSubStyles
150 [baseStyle][subStyle])
151 )
152
153 self.__subStylesInitialized = True
154
155 def readSubstyles(self, editor):
156 """
157 Public method to load the sub-styles and configure the editor.
158
159 @param editor reference to the editor object
160 @type QsciScintilla
161 """
162 subStyleBasesLength = editor.SendScintilla(
163 editor.SCI_GETSUBSTYLEBASES, 0, None)
164 if not subStyleBasesLength:
165 # lexer does not support sub-styling
166 return
167
168 self.loadSubstyles()
169
170 # free existing sub-styles first
171 editor.SendScintilla(editor.SCI_FREESUBSTYLES)
172 subStyleBases = b"\00" * (subStyleBasesLength + 1)
173 editor.SendScintilla(editor.SCI_GETSUBSTYLEBASES, 0, subStyleBases)
174 distanceToSecondary = editor.SendScintilla(
175 editor.SCI_DISTANCETOSECONDARYSTYLES)
176
177 subStyleBases = [b for b in bytearray(subStyleBases[:-1])]
178 if distanceToSecondary:
179 subStyleBases.extend(b + distanceToSecondary
180 for b in subStyleBases[:])
181 for baseStyleNo in subStyleBases:
182 if baseStyleNo in self.__subStyles:
183 subStylesData = self.__subStyles[baseStyleNo]
184 subStyleLength = len(subStylesData)
185 subStyleStart = editor.SendScintilla(
186 editor.SCI_ALLOCATESUBSTYLES, baseStyleNo, subStyleLength)
187 if subStyleStart < 0:
188 continue
189
190 for subStyleIndex, subStyleKey in enumerate(
191 sorted(subStylesData.keys())
192 ):
193 styleNo = subStyleStart + subStyleIndex
194 subStyle = subStylesData[subStyleKey]
195 # set the words
196 editor.SendScintilla(
197 editor.SCI_SETIDENTIFIERS,
198 styleNo,
199 subStyle["Words"].encode())
200
201 # set the style
202 style = subStyle["Style"]
203 color = (
204 QColor(
205 style["fore"] >> 16 & 0xff,
206 style["fore"] >> 8 & 0xff,
207 style["fore"] & 0xff,)
208 if "fore" in style else
209 self.color(baseStyleNo)
210 )
211 self.setColor(color, styleNo)
212
213 color = (
214 QColor(
215 style["paper"] >> 16 & 0xff,
216 style["paper"] >> 8 & 0xff,
217 style["paper"] & 0xff,)
218 if "paper" in style else
219 self.paper(baseStyleNo)
220 )
221 self.setPaper(color, styleNo)
222
223 eolFill = (
224 style["eolfill"]
225 if "eolfill" in style else
226 self.eolFill(baseStyleNo)
227 )
228 self.setEolFill(eolFill, styleNo)
229
230 font = self.font(baseStyleNo)
231 if "font_family" in style:
232 font.setFamily(style["font_family"])
233 if "font_size" in style:
234 font.setPointSize(style["font_size"])
235 if "font_bold" in style:
236 font.setBold(style["font_bold"])
237 if "font_italic" in style:
238 font.setItalic(style["font_italic"])
239 if "font_underline" in style:
240 font.setUnderline(style["font_underline"])
241 self.setFont(font, styleNo)
242
243 def writeSubstyles(self):
244 """
245 Public method to save the sub-styles.
246 """
247 if not self.__subStylesInitialized:
248 return
249
250 settings = Preferences.getSettings()
251
252 # Step 1: remove all sub-style definitions first
253 for baseStyle in self.baseStyles:
254 key = "Scintilla/{0}/style{1}/SubStyleLength".format(
255 self.language(), baseStyle)
256 if settings.contains(key):
257 subStyleLength = int(settings.value(key))
258 if subStyleLength:
259 for subStyle in range(subStyleLength):
260 substyleKey = (
261 "Scintilla/{0}/style{1}/substyle{2}/"
262 ).format(self.language(), baseStyle, subStyle)
263 settings.remove(substyleKey)
264
265 # Step 2: save the defined sub-styles
266 for baseStyle in self.baseStyles:
267 key = "Scintilla/{0}/style{1}/SubStyleLength".format(
268 self.language(), baseStyle)
269 settings.setValue(key, len(self.__subStyles[baseStyle]))
270 for subStyleIndex, subStyle in enumerate(
271 sorted(self.__subStyles[baseStyle].keys())
272 ):
273 substyleKey = "Scintilla/{0}/style{1}/substyle{2}/".format(
274 self.language(), baseStyle, subStyleIndex)
275 subStyleData = self.__subStyles[baseStyle][subStyle]
276
277 if (
278 not subStyleData["Description"] and
279 not subStyleData["Words"]
280 ):
281 # invalid or incomplete sub-style definition
282 continue
283
284 settings.setValue(substyleKey + "Description",
285 subStyleData["Description"])
286 settings.setValue(substyleKey + "Words",
287 subStyleData["Words"])
288
289 style = subStyleData["Style"]
290 if "fore" in style:
291 color = style["fore"]
292 else:
293 col = self.color(baseStyle)
294 color = col.red() << 16 | col.green() << 8 | col.blue()
295 settings.setValue(substyleKey + "fore", color)
296 if "paper" in style:
297 color = style["paper"]
298 else:
299 col = self.paper(baseStyle)
300 color = col.red() << 16 | col.green() << 8 | col.blue()
301 settings.setValue(substyleKey + "paper", color)
302 eolfill = style.get("eolfill", self.eolFill(baseStyle))
303 settings.setValue(substyleKey + "eolfill", eolfill)
304 font = self.font(baseStyle)
305 family = style.get("font_family", font.family())
306 settings.setValue(substyleKey + "font_family", family)
307 size = style.get("font_size", font.pointSize())
308 settings.setValue(substyleKey + "font_size", size)
309 bold = style.get("font_bold", font.bold())
310 settings.setValue(substyleKey + "font_bold", bold)
311 italic = style.get("font_italic", font.italic())
312 settings.setValue(substyleKey + "font_italic", italic)
313 underline = (
314 style["font_underline"]
315 if "font_underline" in style else
316 font.underline()
317 )
318 settings.setValue(substyleKey + "font_underline", underline)
319
320 def hasSubstyles(self):
321 """
322 Public method to indicate the support of sub-styles.
323
324 @return flag indicating sub-styling support
325 @rtype bool
326 """
327 return True
328
329 def getBaseStyles(self):
330 """
331 Public method to get the list of supported base styles.
332
333 @return list of base styles
334 @rtype list of int
335 """
336 return self.baseStyles[:]
337
338 def substylesCount(self, style):
339 """
340 Public method to get the number of defined sub-styles.
341
342 @param style base style number
343 @type int
344 @return number of defined sub-styles
345 @rtype int
346 """
347 count = (len(self.__subStyles[style]) if style in self.__subStyles
348 else 0)
349
350 return count
351
352 def setSubstyleDescription(self, description, style, substyle):
353 """
354 Public method to set the description for a sub-style.
355
356 @param description description to be set
357 @type str
358 @param style base style number
359 @type int
360 @param substyle sub-style number
361 @type int
362 """
363 if style in self.__subStyles and substyle in self.__subStyles[style]:
364 self.__subStyles[style][substyle]["Description"] = (
365 description.strip()
366 )
367
368 def substyleDescription(self, style, substyle):
369 """
370 Public method to get the description of a sub-style.
371
372 @param style base style number
373 @type int
374 @param substyle sub-style number
375 @type int
376 @return sub-style description
377 @rtype str
378 """
379 desc = (
380 self.__subStyles[style][substyle]["Description"].strip()
381 if (style in self.__subStyles and
382 substyle in self.__subStyles[style]) else
383 ""
384 )
385
386 return desc
387
388 def setSubstyleWords(self, words, style, substyle):
389 """
390 Public method to set the words for a sub-style.
391
392 @param words words to be set separated by white-space
393 @type str
394 @param style base style number
395 @type int
396 @param substyle sub-style number
397 @type int
398 """
399 if style in self.__subStyles and substyle in self.__subStyles[style]:
400 self.__subStyles[style][substyle]["Words"] = words.strip()
401
402 def substyleWords(self, style, substyle):
403 """
404 Public method to get the words of a sub-style.
405
406 @param style base style number
407 @type int
408 @param substyle sub-style number
409 @type int
410 @return white-space separated word list
411 @rtype str
412 """
413 words = (
414 self.__subStyles[style][substyle]["Words"].strip()
415 if (style in self.__subStyles and
416 substyle in self.__subStyles[style]) else
417 ""
418 )
419
420 return words
421
422 def setSubstyleColor(self, color, style, substyle):
423 """
424 Public method to set the foreground color of a sub-style.
425
426 @param color foreground color to be set
427 @type QColor
428 @param style base style number
429 @type int
430 @param substyle sub-style number
431 @type int
432 """
433 if style in self.__subStyles and substyle in self.__subStyles[style]:
434 self.__subStyles[style][substyle]["Style"]["fore"] = (
435 color.red() << 16 | color.green() << 8 | color.blue()
436 )
437
438 def substyleColor(self, style, substyle):
439 """
440 Public method to get the sub-style foreground color.
441
442 @param style base style number
443 @type int
444 @param substyle sub-style number
445 @type int
446 @return foreground color
447 @rtype QColor
448 """
449 color = self.color(style)
450
451 if style in self.__subStyles and substyle in self.__subStyles[style]:
452 styleData = self.__subStyles[style][substyle]["Style"]
453 if "fore" in styleData:
454 color = QColor(
455 styleData["fore"] >> 16 & 0xff,
456 styleData["fore"] >> 8 & 0xff,
457 styleData["fore"] & 0xff,
458 )
459
460 return color
461
462 def setSubstylePaper(self, color, style, substyle):
463 """
464 Public method to set the background color of a sub-style.
465
466 @param color background color to be set
467 @type QColor
468 @param style base style number
469 @type int
470 @param substyle sub-style number
471 @type int
472 """
473 if style in self.__subStyles and substyle in self.__subStyles[style]:
474 self.__subStyles[style][substyle]["Style"]["paper"] = (
475 color.red() << 16 | color.green() << 8 | color.blue()
476 )
477
478 def substylePaper(self, style, substyle):
479 """
480 Public method to get the sub-style background color.
481
482 @param style base style number
483 @type int
484 @param substyle sub-style number
485 @type int
486 @return background color
487 @rtype QColor
488 """
489 color = self.paper(style)
490
491 if style in self.__subStyles and substyle in self.__subStyles[style]:
492 styleData = self.__subStyles[style][substyle]["Style"]
493 if "paper" in styleData:
494 color = QColor(
495 styleData["paper"] >> 16 & 0xff,
496 styleData["paper"] >> 8 & 0xff,
497 styleData["paper"] & 0xff,
498 )
499
500 return color
501
502 def setSubstyleEolFill(self, eolFill, style, substyle):
503 """
504 Public method to set the eolfill flag of a sub-style.
505
506 @param eolFill eolfill flag to be set
507 @type bool
508 @param style base style number
509 @type int
510 @param substyle sub-style number
511 @type int
512 """
513 if style in self.__subStyles and substyle in self.__subStyles[style]:
514 self.__subStyles[style][substyle]["Style"]["eolfill"] = (
515 eolFill
516 )
517
518 def substyleEolFill(self, style, substyle):
519 """
520 Public method to get the eolfill flag.
521
522 @param style base style number
523 @type int
524 @param substyle sub-style number
525 @type int
526 @return eolfill flag
527 @rtype bool
528 """
529 eolFill = self.eolFill(style)
530
531 if style in self.__subStyles and substyle in self.__subStyles[style]:
532 styleData = self.__subStyles[style][substyle]["Style"]
533 eolFill = styleData.get("eolfill", self.eolFill(style))
534
535 return eolFill
536
537 def setSubstyleFont(self, font, style, substyle):
538 """
539 Public method to set the font of a sub-style.
540
541 @param font font to be set
542 @type QFont
543 @param style base style number
544 @type int
545 @param substyle sub-style number
546 @type int
547 """
548 if style in self.__subStyles and substyle in self.__subStyles[style]:
549 self.__subStyles[style][substyle]["Style"]["font_family"] = (
550 font.family()
551 )
552 self.__subStyles[style][substyle]["Style"]["font_size"] = (
553 font.pointSize()
554 )
555 self.__subStyles[style][substyle]["Style"]["font_bold"] = (
556 font.bold()
557 )
558 self.__subStyles[style][substyle]["Style"]["font_italic"] = (
559 font.italic()
560 )
561 self.__subStyles[style][substyle]["Style"]["font_underline"] = (
562 font.underline()
563 )
564
565 def substyleFont(self, style, substyle):
566 """
567 Public method to get the sub-style font.
568
569 @param style base style number
570 @type int
571 @param substyle sub-style number
572 @type int
573 @return font
574 @rtype QFont
575 """
576 font = self.font(style)
577
578 if style in self.__subStyles and substyle in self.__subStyles[style]:
579 styleData = self.__subStyles[style][substyle]["Style"]
580 if "font_family" in styleData:
581 font.setFamily(styleData["font_family"])
582 if "font_size" in styleData:
583 font.setPointSize(styleData["font_size"])
584 if "font_bold" in styleData:
585 font.setBold(styleData["font_bold"])
586 if "font_italic" in styleData:
587 font.setItalic(styleData["font_italic"])
588 if "font_underline" in styleData:
589 font.setUnderline(styleData["font_underline"])
590
591 return font
592
593 def substyleDefaultDescription(self, style, substyle):
594 """
595 Public method to get the default description of a sub-style.
596
597 @param style base style number
598 @type int
599 @param substyle sub-style number
600 @type int
601 @return sub-style default description
602 @rtype str
603 """
604 description = ""
605
606 if (
607 style in self.defaultSubStyles and
608 substyle in self.defaultSubStyles[style]
609 ):
610 substyleData = self.defaultSubStyles[style][substyle]
611 description = substyleData["Description"].strip()
612
613 return description
614
615 def substyleDefaultWords(self, style, substyle):
616 """
617 Public method to get the default words of a sub-style.
618
619 @param style base style number
620 @type int
621 @param substyle sub-style number
622 @type int
623 @return white-space separated default word list
624 @rtype str
625 """
626 words = ""
627
628 if (
629 style in self.defaultSubStyles and
630 substyle in self.defaultSubStyles[style]
631 ):
632 substyleData = self.defaultSubStyles[style][substyle]
633 words = substyleData["Words"].strip()
634
635 return words
636
637 def substyleDefaultColor(self, style, substyle):
638 """
639 Public method to get the sub-style default foreground color.
640
641 @param style base style number
642 @type int
643 @param substyle sub-style number
644 @type int
645 @return default foreground color
646 @rtype QColor
647 """
648 color = self.defaultColor(style)
649
650 if (
651 style in self.defaultSubStyles and
652 substyle in self.defaultSubStyles[style]
653 ):
654 styleData = self.defaultSubStyles[style][substyle]["Style"]
655 if "fore" in styleData:
656 color = QColor(
657 styleData["fore"] >> 16 & 0xff,
658 styleData["fore"] >> 8 & 0xff,
659 styleData["fore"] & 0xff,
660 )
661
662 return color
663
664 def substyleDefaultPaper(self, style, substyle):
665 """
666 Public method to get the sub-style default background color.
667
668 @param style base style number
669 @type int
670 @param substyle sub-style number
671 @type int
672 @return default background color
673 @rtype QColor
674 """
675 color = self.defaultPaper(style)
676
677 if (
678 style in self.defaultSubStyles and
679 substyle in self.defaultSubStyles[style]
680 ):
681 styleData = self.defaultSubStyles[style][substyle]["Style"]
682 if "paper" in styleData:
683 color = QColor(
684 styleData["paper"] >> 16 & 0xff,
685 styleData["paper"] >> 8 & 0xff,
686 styleData["paper"] & 0xff,
687 )
688
689 return color
690
691 def substyleDefaultEolFill(self, style, substyle):
692 """
693 Public method to get the default eolfill flag.
694
695 @param style base style number
696 @type int
697 @param substyle sub-style number
698 @type int
699 @return default eolfill flag
700 @rtype bool
701 """
702 eolFill = self.defaultEolFill(style)
703
704 if (
705 style in self.defaultSubStyles and
706 substyle in self.defaultSubStyles[style]
707 ):
708 styleData = self.defaultSubStyles[style][substyle]["Style"]
709 eolFill = styleData.get("eolfill", self.defaultEolFill(style))
710
711 return eolFill
712
713 def substyleDefaultFont(self, style, substyle):
714 """
715 Public method to get the default sub-style font.
716
717 @param style base style number
718 @type int
719 @param substyle sub-style number
720 @type int
721 @return default font
722 @rtype QFont
723 """
724 font = self.defaultFont(style)
725
726 if (
727 style in self.defaultSubStyles and
728 substyle in self.defaultSubStyles[style]
729 ):
730 styleData = self.defaultSubStyles[style][substyle]["Style"]
731 if "font_family" in styleData:
732 font.setFamily(styleData["font_family"])
733 if "font_size" in styleData:
734 font.setPointSize(styleData["font_size"])
735 if "font_bold" in styleData:
736 font.setBold(styleData["font_bold"])
737 if "font_italic" in styleData:
738 font.setItalic(styleData["font_italic"])
739 if "font_underline" in styleData:
740 font.setUnderline(styleData["font_underline"])
741
742 return font
743
744 def addSubstyle(self, style):
745 """
746 Public method to add an empty sub-style to a given base style.
747
748 @param style base style number
749 @type int
750 @return allocated sub-style number or -1 to indicate an error
751 @rtype int
752 """
753 if style in self.__subStyles:
754 lastSubStyle = sorted(self.__subStyles[style].keys())[-1]
755 subStyle = lastSubStyle + 1
756 self.__subStyles[style][subStyle] = {
757 "Description": "",
758 "Words": "",
759 "Style": {},
760 }
761 else:
762 subStyle = -1
763
764 return subStyle
765
766 def delSubstyle(self, style, substyle):
767 """
768 Public method to delete a given sub-style definition.
769
770 @param style base style number
771 @type int
772 @param substyle sub-style number
773 @type int
774 @return flag indicating successful deletion
775 @rtype bool
776 """
777 ok = False
778
779 if style in self.__subStyles and substyle in self.__subStyles[style]:
780 del self.__subStyles[style][substyle]
781 ok = True
782
783 return ok
784
785 def hasSubstyle(self, style, substyle):
786 """
787 Public method to test for a given sub-style definition.
788
789 @param style base style number
790 @type int
791 @param substyle sub-style number
792 @type int
793 @return flag indicating the existence of a sub-style definition
794 @rtype bool
795 """
796 return (style in self.__subStyles and
797 substyle in self.__subStyles[style])
798
799 def isBaseStyle(self, style):
800 """
801 Public method to test, if a given style may have sub-styles.
802
803 @param style base style number
804 @type int
805 @return flag indicating that the style may have sub-styles
806 @rtype bool
807 """
808 return style in self.baseStyles

eric ide

mercurial