cadquery-freecad-module/Libs/pyqode/core/api/syntax_highlighter.py

387 lines
13 KiB
Python

"""
This module contains the syntax highlighter API.
"""
import logging
import sys
import time
from pygments.styles import get_style_by_name, get_all_styles
from pygments.token import Token, Punctuation
from pygments.util import ClassNotFound
from pyqode.core.api.mode import Mode
from pyqode.core.api.utils import drift_color
from pyqode.qt import QtGui, QtCore, QtWidgets
def _logger():
return logging.getLogger(__name__)
#: A sorted list of available pygments styles, for convenience
PYGMENTS_STYLES = sorted(list(get_all_styles()))
if hasattr(sys, 'frozen'):
# frozen executables won't see the builtin pyqode pygments style.
PYGMENTS_STYLES += ['darcula', 'qt']
#: The list of color schemes keys (and their associated pygments token)
COLOR_SCHEME_KEYS = {
# editor background
"background": None,
# highlight color (used for caret line)
"highlight": None,
# normal text
"normal": Token.Text,
# any keyword
"keyword": Token.Keyword,
# namespace keywords (from ... import ... as)
"namespace": Token.Keyword.Namespace,
# type keywords
"type": Token.Keyword.Type,
# reserved keyword
"keyword_reserved": Token.Keyword.Reserved,
# any builtin name
"builtin": Token.Name.Builtin,
# any definition (class or function)
"definition": Token.Name.Class,
# any comment
"comment": Token.Comment,
# any string
"string": Token.Literal.String,
# any docstring (python docstring, c++ doxygen comment,...)
"docstring": Token.Literal.String.Doc,
# any number
"number": Token.Number,
# any instance variable
"instance": Token.Name.Variable,
# whitespace color
"whitespace": Token.Text.Whitespace,
# any tag name (e.g. shinx doctags,...)
'tag': Token.Name.Tag,
# self paramter (or this in other languages)
'self': Token.Name.Builtin.Pseudo,
# python decorators
'decorator': Token.Name.Decorator,
# colors of punctuation characters
'punctuation': Punctuation,
# name or keyword constant
'constant': Token.Name.Constant,
# function definition
'function': Token.Name.Function,
# operator
'operator': Token.Operator,
# operator words (and, not)
'operator_word': Token.Operator.Word
}
class ColorScheme(object):
"""
Translates a pygments style into a dictionary of colors associated with a
style key.
See :attr:`pyqode.core.api.syntax_highligter.COLOR_SCHEM_KEYS` for the
available keys.
"""
@property
def name(self):
"""
Name of the color scheme, this is usually the name of the associated
pygments style.
"""
return self._name
@property
def background(self):
"""
Gets the background color.
:return:
"""
return self.formats['background'].background().color()
@property
def highlight(self):
"""
Gets the highlight color.
:return:
"""
return self.formats['highlight'].background().color()
def __init__(self, style):
"""
:param style: name of the pygments style to load
"""
self._name = style
self._brushes = {}
#: Dictionary of formats colors (keys are the same as for
#: :attr:`pyqode.core.api.COLOR_SCHEME_KEYS`
self.formats = {}
try:
style = get_style_by_name(style)
except ClassNotFound:
if style == 'darcula':
from pyqode.core.styles.darcula import DarculaStyle
style = DarculaStyle
else:
from pyqode.core.styles.qt import QtStyle
style = QtStyle
self._load_formats_from_style(style)
def _load_formats_from_style(self, style):
# background
self.formats['background'] = self._get_format_from_color(
style.background_color)
# highlight
self.formats['highlight'] = self._get_format_from_color(
style.highlight_color)
for key, token in COLOR_SCHEME_KEYS.items():
if token and key:
self.formats[key] = self._get_format_from_style(token, style)
def _get_format_from_color(self, color):
fmt = QtGui.QTextCharFormat()
fmt.setBackground(self._get_brush(color))
return fmt
def _get_format_from_style(self, token, style):
""" Returns a QTextCharFormat for token by reading a Pygments style.
"""
result = QtGui.QTextCharFormat()
items = list(style.style_for_token(token).items())
for key, value in items:
if value is None and key == 'color':
# make sure to use a default visible color for the foreground
# brush
value = drift_color(self.background, 1000).name()
if value:
if key == 'color':
result.setForeground(self._get_brush(value))
elif key == 'bgcolor':
result.setBackground(self._get_brush(value))
elif key == 'bold':
result.setFontWeight(QtGui.QFont.Bold)
elif key == 'italic':
result.setFontItalic(value)
elif key == 'underline':
result.setUnderlineStyle(
QtGui.QTextCharFormat.SingleUnderline)
elif key == 'sans':
result.setFontStyleHint(QtGui.QFont.SansSerif)
elif key == 'roman':
result.setFontStyleHint(QtGui.QFont.Times)
elif key == 'mono':
result.setFontStyleHint(QtGui.QFont.TypeWriter)
if token in [Token.Literal.String, Token.Literal.String.Doc,
Token.Comment]:
# mark strings, comments and docstrings regions for further queries
result.setObjectType(result.UserObject)
return result
def _get_brush(self, color):
""" Returns a brush for the color.
"""
result = self._brushes.get(color)
if result is None:
qcolor = self._get_color(color)
result = QtGui.QBrush(qcolor)
self._brushes[color] = result
return result
@staticmethod
def _get_color(color):
""" Returns a QColor built from a Pygments color string. """
color = str(color).replace("#", "")
qcolor = QtGui.QColor()
qcolor.setRgb(int(color[:2], base=16),
int(color[2:4], base=16),
int(color[4:6], base=16))
return qcolor
class SyntaxHighlighter(QtGui.QSyntaxHighlighter, Mode):
"""
Abstract base class for syntax highlighter modes.
It fills up the document with our custom block data (fold levels,
triggers,...).
It **does not do any syntax highlighting**, that task is left to
sublasses such as :class:`pyqode.core.modes.PygmentsSyntaxHighlighter`.
Subclasses **must** override the
:meth:`pyqode.core.api.SyntaxHighlighter.highlight_block` method to
apply custom highlighting.
.. note:: Since version 2.1 and for performance reasons, we store all
our data in the block user state as a bit-mask. You should always
use :class:`pyqode.core.api.TextBlockHelper` to retrieve or modify
those data.
"""
#: Signal emitted at the start of highlightBlock. Parameters are the
#: highlighter instance and the current text block
block_highlight_started = QtCore.Signal(object, object)
#: Signal emitted at the end of highlightBlock. Parameters are the
#: highlighter instance and the current text block
block_highlight_finished = QtCore.Signal(object, object)
@property
def formats(self):
"""
Returns the color shcme formats dict.
"""
return self._color_scheme.formats
@property
def color_scheme(self):
"""
Gets/Sets the color scheme of the syntax highlighter, this will trigger
a rehighlight automatically.
"""
return self._color_scheme
@color_scheme.setter
def color_scheme(self, color_scheme):
if isinstance(color_scheme, str):
color_scheme = ColorScheme(color_scheme)
if color_scheme.name != self._color_scheme.name:
self._color_scheme = color_scheme
self.refresh_editor(color_scheme)
self.rehighlight()
def refresh_editor(self, color_scheme):
"""
Refresh editor settings (background and highlight colors) when color
scheme changed.
:param color_scheme: new color scheme.
"""
self.editor.background = color_scheme.background
self.editor.foreground = color_scheme.formats[
'normal'].foreground().color()
self.editor.whitespaces_foreground = color_scheme.formats[
'whitespace'].foreground().color()
try:
mode = self.editor.modes.get('CaretLineHighlighterMode')
except KeyError:
pass
else:
mode.background = color_scheme.highlight
mode.refresh()
try:
mode = self.editor.panels.get('FoldingPanel')
except KeyError:
pass
else:
mode.refresh_decorations(force=True)
self.editor._reset_stylesheet()
def __init__(self, parent, color_scheme=None):
"""
:param parent: parent document (QTextDocument)
:param color_scheme: color scheme to use.
"""
QtGui.QSyntaxHighlighter.__init__(self, parent)
Mode.__init__(self)
if not color_scheme:
color_scheme = ColorScheme('qt')
self._color_scheme = color_scheme
self._spaces_ptrn = QtCore.QRegExp(r'[ \t]+')
#: Fold detector. Set it to a valid FoldDetector to get code folding
#: to work. Default is None
self.fold_detector = None
self.WHITESPACES = QtCore.QRegExp(r'\s+')
def on_state_changed(self, state):
if self._on_close:
return
if state:
self.setDocument(self.editor.document())
else:
self.setDocument(None)
def _highlight_whitespaces(self, text):
index = self.WHITESPACES.indexIn(text, 0)
while index >= 0:
index = self.WHITESPACES.pos(0)
length = len(self.WHITESPACES.cap(0))
self.setFormat(index, length, self.formats['whitespace'])
index = self.WHITESPACES.indexIn(text, index + length)
@staticmethod
def _find_prev_non_blank_block(current_block):
previous_block = (current_block.previous()
if current_block.blockNumber() else None)
# find the previous non-blank block
while (previous_block and previous_block.blockNumber() and
previous_block.text().strip() == ''):
previous_block = previous_block.previous()
return previous_block
def highlightBlock(self, text):
"""
Highlights a block of text. Please do not override, this method.
Instead you should implement
:func:`pyqode.core.api.SyntaxHighlighter.highlight_block`.
:param text: text to highlight.
"""
if not self.enabled:
return
current_block = self.currentBlock()
previous_block = self._find_prev_non_blank_block(current_block)
if self.editor:
self.highlight_block(text, current_block)
if self.editor.show_whitespaces:
self._highlight_whitespaces(text)
if self.fold_detector is not None:
self.fold_detector.editor = self.editor
self.fold_detector.process_block(
current_block, previous_block, text)
def highlight_block(self, text, block):
"""
Abstract method. Override this to apply syntax highlighting.
:param text: Line of text to highlight.
:param block: current block
"""
raise NotImplementedError()
def rehighlight(self):
"""
Rehighlight the entire document, may be slow.
"""
start = time.time()
QtWidgets.QApplication.setOverrideCursor(
QtGui.QCursor(QtCore.Qt.WaitCursor))
try:
super(SyntaxHighlighter, self).rehighlight()
except RuntimeError:
# cloned widget, no need to rehighlight the same document twice ;)
pass
QtWidgets.QApplication.restoreOverrideCursor()
end = time.time()
_logger().debug('rehighlight duration: %fs' % (end - start))
def on_install(self, editor):
super(SyntaxHighlighter, self).on_install(editor)
self.refresh_editor(self.color_scheme)
def clone_settings(self, original):
self._color_scheme = original.color_scheme
class TextBlockUserData(QtGui.QTextBlockUserData):
"""
Custom text block user data, mainly used to store checker messages and
markers.
"""
def __init__(self):
super(TextBlockUserData, self).__init__()
#: List of checker messages associated with the block.
self.messages = []
#: List of markers draw by a marker panel.
self.markers = []