ukatali/src/ukatali/lexer.py

169 lines
6.2 KiB
Python

import re
from PyQt5 import QtCore, QtGui, QtWidgets, Qsci
from igitar.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)