import re from PyQt5 import QtCore, QtGui, QtWidgets, Qsci from chordpro.constants import KNOWN_DIRECTIVES, KNOWN_VERSE_TYPES PALETTE_ROLES = { 'window': QtGui.QPalette.WindowText, 'background': QtGui.QPalette.Background, 'windowtext': QtGui.QPalette.WindowText, 'foreground': QtGui.QPalette.Foreground, 'base': QtGui.QPalette.Base, 'alternatebase': QtGui.QPalette.AlternateBase, 'tooltipbase': QtGui.QPalette.ToolTipBase, 'tooltiptext': QtGui.QPalette.ToolTipText, 'placeholderText': QtGui.QPalette.PlaceholderText, 'text': QtGui.QPalette.Text, 'button': QtGui.QPalette.Button, 'buttontext': QtGui.QPalette.ButtonText, 'brighttext': QtGui.QPalette.BrightText, 'light': QtGui.QPalette.Light, 'midlight': QtGui.QPalette.Midlight, 'dark': QtGui.QPalette.Dark, 'mid': QtGui.QPalette.Mid, 'shadow': QtGui.QPalette.Shadow, 'highlight': QtGui.QPalette.Highlight, 'highlightedtext': QtGui.QPalette.HighlightedText, 'link': QtGui.QPalette.Link, 'linkvisited': QtGui.QPalette.LinkVisited } class ChordProStyle(object): Default = 0 Keyword = 1 Identifier = 2 Chord = 3 @staticmethod def to_string(style): """Return the name for the style""" return { ChordProStyle.Default: 'Default', ChordProStyle.Keyword: 'Keyword', ChordProStyle.Identifier: 'Identifier', ChordProStyle.Chord: 'Chord' }.get(style) class ChordProLexer(Qsci.QsciLexerCustom): def __init__(self, parent): super().__init__(parent) self.settings = QtCore.QSettings() self.settings.beginGroup('syntax') self.init_style() def _get_color(self, name, default): """Get a QColor object from the settings""" color = self.settings.value(name, default) if isinstance(color, str): if color.startswith('palette:'): palette_role = PALETTE_ROLES[color.split(':', 1)[1].lower()] color = QtWidgets.QApplication.palette().color(palette_role) else: color = QtGui.QColor(color) return color def _get_font(self, name): """Get a QFont object from the settings""" font = self.settings.value(name, None) if not font: font = QtGui.QFont('monospace') elif isinstance(font, str): font = QtGui.QFont.fromString(font) return font def init_style(self): """Set up the colours and fonts""" # Defaults self.setDefaultColor(self._get_color('default-foreground', 'palette:windowtext')) self.setDefaultPaper(self._get_color('default-background', 'palette:base')) self.setDefaultFont(self._get_font('default-font')) self.setColor(self._get_color('default-foreground', 'palette:windowtext'), ChordProStyle.Default) self.setPaper(self._get_color('default-background', 'palette:base'), ChordProStyle.Default) self.setFont(self._get_font('default-font'), ChordProStyle.Default) # Keywords kwfg = self._get_color('keywords-foreground', '#009fe3') if kwfg: self.setColor(kwfg, ChordProStyle.Keyword) kwbg = self._get_color('keywords-background', None) if kwbg: self.setPaper(kwbg, ChordProStyle.Keyword) kwft = self._get_font('keywords-font') if kwft: self.setFont(kwft, ChordProStyle.Keyword) # Identifiers idfg = self._get_color('identifiers-foreground', '#fdadff') if idfg: self.setColor(idfg, ChordProStyle.Identifier) idbg = self._get_color('identifiers-background', None) if idbg: self.setPaper(idbg, ChordProStyle.Identifier) idft = self._get_font('identifiers-font') if idft: self.setFont(idft, ChordProStyle.Identifier) # Chords chfg = self._get_color('chords-foreground', '#73ff6c') if chfg: self.setColor(chfg, ChordProStyle.Chord) chbg = self._get_color('chords-background', None) if chbg: self.setPaper(chbg, ChordProStyle.Chord) chft = self._get_font('chords-font') if chft: self.setFont(chft, ChordProStyle.Chord) def language(self): """Return the language name""" return "ChordPro" def description(self, style): """Return the name for the style number""" return ChordProStyle.to_string(style) def styleText(self, start, end): """Style the text""" keywords = ['verse', 'chorus', 'bridge'] + \ [d for t in KNOWN_DIRECTIVES for d in t] + \ ['start_of_' + vt for vt in KNOWN_VERSE_TYPES] + \ ['end_of_' + vt for vt in KNOWN_VERSE_TYPES] is_directive = False is_identifier = False is_chord = False pattern = re.compile(r'\s+|\w+|\W') self.startStyling(start) text = self.parent().text()[start:end] token_list = [(token, len(bytearray(token, "utf-8"))) for token in pattern.findall(text)] for idx, token in enumerate(token_list): if token[0] == '{': is_directive = True self.setStyling(token[1], ChordProStyle.Keyword) elif is_directive and token[0] in keywords: self.setStyling(token[1], ChordProStyle.Keyword) elif is_directive and token[0] == ':': is_identifier = True self.setStyling(token[1], ChordProStyle.Keyword) elif is_directive and token[0] == '}': is_directive = False is_identifier = False self.setStyling(token[1], ChordProStyle.Keyword) elif is_identifier: self.setStyling(token[1], ChordProStyle.Identifier) elif token[0] == '[': is_chord = True self.setStyling(token[1], ChordProStyle.Chord) elif is_chord and token[0] == ']': is_chord = False self.setStyling(token[1], ChordProStyle.Chord) elif is_chord: self.setStyling(token[1], ChordProStyle.Chord) else: self.setStyling(token[1], ChordProStyle.Default)