#!/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