#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
**highlighters.py**
**Platform:**
Windows, Linux, Mac Os X.
**Description:**
Defines the Application highlighters classes.
**Others:**
Portions of the code from PyQtWiki: http://diotavelli.net/PyQtWiki/Python%20syntax%20highlighting
"""
#**********************************************************************************************************************
#*** Future imports.
#**********************************************************************************************************************
from __future__ import unicode_literals
#**********************************************************************************************************************
#*** External imports.
#**********************************************************************************************************************
import re
from PyQt4.QtGui import QSyntaxHighlighter
from PyQt4.QtGui import QTextCharFormat
#**********************************************************************************************************************
#*** Internal imports.
#**********************************************************************************************************************
import foundations.common
import foundations.dataStructures
import foundations.decorators
import foundations.exceptions
import foundations.verbose
from foundations.nodes import AbstractCompositeNode
from umbra.ui.nodes import DefaultNode
from umbra.ui.nodes import FormatNode
from umbra.ui.themes import DEFAULT_THEME
#**********************************************************************************************************************
#*** Module attributes.
#**********************************************************************************************************************
__author__ = "Thomas Mansencal"
__copyright__ = "Copyright (C) 2008 - 2014 - Thomas Mansencal"
__license__ = "GPL V3.0 - http://www.gnu.org/licenses/"
__maintainer__ = "Thomas Mansencal"
__email__ = "[email protected]"
__status__ = "Production"
__all__ = ["LOGGER", "Rule",
"FormatsTree",
"AbstractHighlighter",
"DefaultHighlighter"]
LOGGER = foundations.verbose.installLogger()
#**********************************************************************************************************************
#*** Module classes and definitions.
#**********************************************************************************************************************
[docs]class Rule(foundations.dataStructures.Structure):
"""
Defines a storage object for highlighters rule.
"""
def __init__(self, **kwargs):
"""
Initializes the class.
:param \*\*kwargs: pattern, format.
:type \*\*kwargs: dict
"""
LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
foundations.dataStructures.Structure.__init__(self, **kwargs)
[docs]class AbstractHighlighter(QSyntaxHighlighter):
"""
Defines a `QSyntaxHighlighter <http://doc.qt.nokia.com/qsyntaxhighlighter.html>`_ subclass used
as a base for highlighters classes.
"""
def __init__(self, parent=None):
"""
Initializes the class.
:param parent: Widget parent.
:type parent: QObject
"""
LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
QSyntaxHighlighter.__init__(self, parent)
# --- Setting class attributes. ---
self.__formats = None
self.__rules = None
#******************************************************************************************************************
#*** Attributes properties.
#******************************************************************************************************************
@property
def formats(self):
"""
Property for **self.__formats** attribute.
:return: self.__formats.
:rtype: FormatsTree
"""
return self.__formats
@formats.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(AssertionError)
def formats(self, value):
"""
Setter for **self.__formats** attribute.
:param value: Attribute value.
:type value: FormatsTree
"""
if value is not None:
assert type(value) is FormatsTree, "'{0}' attribute: '{1}' type is not 'FormatsTree'!".format("formats", value)
self.__formats = value
@formats.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
@property
def rules(self):
"""
Property for **self.__rules** attribute.
:return: self.__rules.
:rtype: tuple or list
"""
return self.__rules
@rules.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(AssertionError)
def rules(self, value):
"""
Setter for **self.__rules** attribute.
:param value: Attribute value.
:type value: tuple or list
"""
if value is not None:
assert type(value) in (tuple, list), "'{0}' attribute: '{1}' type is not 'tuple' or 'list'!".format(
"rules", value)
self.__rules = value
@rules.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def rules(self):
"""
Deleter for **self.__rules** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "rules"))
#******************************************************************************************************************
#*** Class methods.
#******************************************************************************************************************
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(NotImplementedError)
[docs] def highlightBlock(self, block):
"""
Reimplements the :meth:`QSyntaxHighlighter.highlightBlock` method.
:param block: Text block.
:type block: QString
"""
raise NotImplementedError("{0} | '{1}' must be implemented by '{2}' subclasses!".format(self.__class__.__name__,
self.highlightBlock.__name__,
self.__class__.__name__))
[docs] def highlightText(self, text, start, end):
"""
Highlights given text.
:param text: Text.
:type text: QString
:param start: Text start index.
:type start: int
:param end: Text end index.
:type end: int
:return: Method success.
:rtype: bool
"""
for rule in self.__rules:
index = rule.pattern.indexIn(text, start)
while index >= start and index < end:
length = rule.pattern.matchedLength()
format = self.formats.getFormat(rule.name) or self.formats.getFormat("default")
self.setFormat(index, min(length, end - index), format)
index = rule.pattern.indexIn(text, index + length)
return True
[docs]class DefaultHighlighter(AbstractHighlighter):
"""
Defines a :class:`AbstractHighlighter` subclass providing syntax highlighting for documents.
"""
def __init__(self, parent=None, rules=None, theme=None):
"""
Initializes the class.
:param parent: Widget parent.
:type parent: QObject
:param rules: Rules.
:type rules: tuple or list
:param theme: Theme.
:type theme: dict
"""
LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
AbstractHighlighter.__init__(self, parent)
# --- Setting class attributes. ---
self.rules = rules
self.__theme = None
self.theme = theme
self.__setFormats()
#******************************************************************************************************************
#*** Attributes properties.
#******************************************************************************************************************
@property
def theme(self):
"""
Property for **self.__theme** attribute.
:return: self.__theme.
:rtype: dict
"""
return self.__theme
@theme.setter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(AssertionError)
def theme(self, value):
"""
Setter for **self.__theme** attribute.
:param value: Attribute value.
:type value: dict
"""
if value is not None:
assert type(value) is dict, "'{0}' attribute: '{1}' type is not 'dict'!".format("theme", value)
self.__theme = value
@theme.deleter
# Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def theme(self):
"""
Deleter for **self.__theme** attribute.
"""
raise foundations.exceptions.ProgrammingError(
"{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "theme"))
#******************************************************************************************************************
#*** Class methods.
#******************************************************************************************************************
def __setFormats(self):
"""
Sets the highlighting formats.
"""
self.formats = FormatsTree(self.__theme)
[docs] def highlightBlock(self, block):
"""
Reimplements the :meth:`AbstractHighlighter.highlightBlock` method.
:param block: Text block.
:type block: QString
"""
self.highlightText(block, 0, len(block))
self.setCurrentBlockState(0)
state = 1
for rule in self.rules:
if re.match("comment\.block\.[\w\.]*start", rule.name):
format = self.formats.getFormat(rule.name) or self.formats.getFormat("default")
if self.highlightMultilineBlock(block, rule.pattern, foundations.common.getFirstItem([item for item in self.rules
if item.name == rule.name.replace("start", "end")]).pattern, state, format):
break
state += 1
[docs] def highlightMultilineBlock(self, block, startPattern, endPattern, state, format):
"""
Highlights given multiline text block.
:param block: Text block.
:type block: QString
:param pattern: Start regex pattern.
:type pattern: QRegExp
:param pattern: End regex pattern.
:type pattern: QRegExp
:param format: Format.
:type format: QTextCharFormat
:param state: Block state.
:type state: int
:return: Current block matching state.
:rtype: bool
"""
if self.previousBlockState() == state:
start = 0
extend = 0
else:
start = startPattern.indexIn(block)
extend = startPattern.matchedLength()
while start >= 0:
end = endPattern.indexIn(block, start + extend)
if end >= extend:
length = end - start + extend + endPattern.matchedLength()
self.setCurrentBlockState(0)
else:
self.setCurrentBlockState(state)
length = block.length() - start + extend
self.setFormat(start, length, format)
start = startPattern.indexIn(block, start + length)
if self.currentBlockState() == state:
return True
else:
return False