Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions client/ayon_core/tools/console_interpreter/ui/code_style.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
from pygments.style import Style
from pygments.token import (
Comment,
Error,
Generic,
Keyword,
Literal,
Name,
Number,
Operator,
Other,
Punctuation,
String,
Text,
Whitespace,
)


pl = {
"syntax-comment": "#8b949e",
"syntax-constant": "#79c0ff",
"syntax-entity": "#d2a8ff",
"syntax-storage-modifier-import": "#c9d1d9",
"syntax-entity-tag": "#7ee787",
"syntax-keyword": "#ff7b72",
"syntax-string": "#a5d6ff",
"syntax-variable": "#ffa657",
"syntax-brackethighlighter-unmatched": "#f85149",
"syntax-invalid-illegal-text": "#f0f6fc",
"syntax-invalid-illegal-bg": "#8e1519",
"syntax-carriage-return-text": "#f0f6fc",
"syntax-carriage-return-bg": "#b62324",
"syntax-string-regexp": "#7ee787",
"syntax-markup-list": "#f2cc60",
"syntax-markup-heading": "#1f6feb",
"syntax-markup-italic": "#c9d1d9",
"syntax-markup-bold": "#e6edf3",
"syntax-markup-deleted-text": "#ffdcd7",
"syntax-markup-deleted-bg": "#67060c",
"syntax-markup-inserted-text": "#aff5b4",
"syntax-markup-inserted-bg": "#033a16",
"syntax-markup-changed-text": "#ffdfb6",
"syntax-markup-changed-bg": "#5a1e02",
"syntax-markup-ignored-text": "#c9d1d9",
"syntax-markup-ignored-bg": "#1158c7",
"syntax-meta-diff-range": "#d2a8ff",
"syntax-brackethighlighter-angle": "#8b949e",
"syntax-sublimelinter-gutter-mark": "#484f58",
"syntax-constant-other-reference-link": "#a5d6ff",
}


class AYONCodeStyle(Style):
"""A Pygments style similar to GitHub's pretty lights dark theme"""

background_color = None
default_style = ''

styles = {
Whitespace: "#f0f6fc",

Comment: pl["syntax-comment"],
Comment.Hashbang: pl["syntax-comment"],
Comment.Multiline: pl["syntax-comment"],
Comment.Preproc: pl["syntax-comment"],
Comment.Single: pl["syntax-comment"],
Comment.Special: pl["syntax-comment"],

Generic: "#f0f6fc",
Generic.Deleted: "#8b080b",
Generic.Emph: "#f8f8f2 underline",
Generic.Error: "#f8f8f2",
Generic.Heading: "#f8f8f2 bold",
Generic.Inserted: "#f8f8f2 bold",
Generic.Output: "#adaeb6",
Generic.Prompt: "#f8f8f2",
Generic.Strong: "#f8f8f2",
Generic.Subheading: "#f8f8f2 bold",
Generic.Traceback: "#f8f8f2",
Error: "#f8f8f2",

Keyword: pl["syntax-keyword"],
Keyword.Constant: pl["syntax-constant"], # Ex. None
Keyword.Declaration: pl["syntax-keyword"],
Keyword.Namespace: pl["syntax-keyword"],
Keyword.Pseudo: pl["syntax-entity"],
Keyword.Reserved: pl["syntax-constant"],
Keyword.Type: pl["syntax-constant"],

Literal: "#f8f8f2",
Literal.Date: "#f8f8f2",
Literal.String.Affix: "#f8f8f2",
Literal.String.Doc: "#f8f8f2",
Literal.String.Double: "#f8f8f2",
Literal.String.Interpol: "#f8f8f2",
Literal.String.Single: "#f8f8f2",

Name: pl["syntax-markup-bold"],
Name.Variable: pl["syntax-markup-bold"],
Name.Attribute: pl["syntax-markup-bold"],
Name.Builtin.Pseudo: pl["syntax-markup-bold"], # Ex. self
Name.Builtin: pl["syntax-entity"], # Ex. print()
Name.Class: pl["syntax-variable"],
Name.Constant: pl["syntax-constant"],
Name.Decorator: pl["syntax-entity"],
Name.Entity: pl["syntax-entity"],
Name.Exception: pl["syntax-variable"],
Name.Function: pl["syntax-entity"],
Name.Function.Magic: pl["syntax-entity"],
# Name.Label: "#8be9fd italic",
Name.Namespace: pl["syntax-markup-bold"],
Name.Other: pl["syntax-markup-bold"],
# Name.Other: pl["syntax-variable"],
# Name.Tag: "#ff79c6",
Name.Variable.Class: pl["syntax-variable"],
Name.Variable.Global: pl["syntax-variable"],
Name.Variable.Instance: pl["syntax-markup-bold"],
Name.Variable.Magic: pl["syntax-markup-bold"],

Number: pl["syntax-constant"],
Number.Bin: pl["syntax-constant"],
Number.Float: pl["syntax-constant"],
Number.Hex: pl["syntax-constant"],
Number.Integer: pl["syntax-constant"],
Number.Integer.Long: pl["syntax-constant"],
Number.Oct: pl["syntax-constant"],
Operator: pl["syntax-constant"],
Operator.Word: pl["syntax-constant"],

# Other: "#f8f8f2",
Other.Constant: pl["syntax-constant"],
Punctuation: "#f8f8f2",
Punctuation.Definition.Comment: pl["syntax-comment"],
String: pl["syntax-string"],
String.Affix: pl["syntax-string"],
String.Backtick: pl["syntax-string"],
String.Char: pl["syntax-string"],
String.Comment: pl["syntax-comment"],
String.Doc: pl["syntax-string"],
String.Double: pl["syntax-string"],
String.Escape: pl["syntax-string"],
String.Heredoc: pl["syntax-string"],
String.Interpol: pl["syntax-string"],
String.Other: pl["syntax-string"],
String.Regex: pl["syntax-string"],
String.Single: pl["syntax-string"],
String.Symbol: pl["syntax-string"],
Text: pl["syntax-markup-bold"],
}
93 changes: 93 additions & 0 deletions client/ayon_core/tools/console_interpreter/ui/syntax_highlight.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from __future__ import annotations

import os

from qtpy import QtGui
import pygments.lexers
import pygments.styles
import pygments.token
from pygments.util import ClassNotFound

from .code_style import AYONCodeStyle


class PythonSyntaxHighlighter(QtGui.QSyntaxHighlighter):
def __init__(self, document, style_name: str = ""):
self.lexer = pygments.lexers.PythonLexer()
self.style = AYONCodeStyle

# Allow to override style by environment variable
override_style_name = os.getenv("AYON_CONSOLE_INTERPRETER_STYLE")
if override_style_name:

try:
style = pygments.styles.get_style_by_name(override_style_name)
except ClassNotFound:
all_styles = ", ".join(pygments.styles.get_all_styles())
raise ValueError(
f"'{override_style_name}' not found. "
f"Installed styles: {all_styles}."
)
else:
self.style = style

self.formats = self.style_to_formats(self.style)

# init after validating and loading the style
super().__init__(document)

def highlightBlock(self, text: str | None) -> None:
if text is None:
return

index = 0
for token, value in self.lexer.get_tokens(text):
length = len(value)
if fmt := self.formats.get(token):
self.setFormat(index, length, fmt)

index += length

def _format_for_token(self, token):
if token is None:
return None
if token in self.formats:
return self.formats[token]
if token.parent:
return self._format_for_token(token.parent)
return None

def style_to_formats(
self,
style: pygments.styles.Style
) -> dict[pygments.token.Token, QtGui.QTextCharFormat]:
"""Convert a Pygments style to a dictionary of Qt formats.

Args:
style: The Pygments style to convert.

Returns:
token_format = QTextCharFormat for each token in the style.

"""
formats = {}
for token, _ in style.styles.items():

token_style = style.style_for_token(token)
if not token_style:
continue

token_format = QtGui.QTextCharFormat()
if color := token_style.get("color"):
color = f"#{color}"
token_format.setForeground(QtGui.QColor(color))
if token_style.get("bold"):
token_format.setFontWeight(QtGui.QFont.Bold)
if token_style.get("italic"):
token_format.setFontItalic(True)
if token_style.get("underline"):
token_format.setFontUnderline(True)

formats[token] = token_format

return formats
27 changes: 26 additions & 1 deletion client/ayon_core/tools/console_interpreter/ui/widgets.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from code import InteractiveInterpreter
from __future__ import annotations

from code import InteractiveInterpreter
from qtpy import QtCore, QtWidgets, QtGui

try:
from .syntax_highlight import PythonSyntaxHighlighter
except ImportError:
PythonSyntaxHighlighter = None # type: ignore


class PythonCodeEditor(QtWidgets.QPlainTextEdit):
execute_requested = QtCore.Signal()
Expand All @@ -12,6 +18,25 @@ def __init__(self, parent):
self.setObjectName("PythonCodeEditor")

self._indent = 4
self._apply_syntax_highlighter()

def _apply_syntax_highlighter(self):
if PythonSyntaxHighlighter is None:
return

try:
highlighter = PythonSyntaxHighlighter(self.document())
except ValueError as e:
print(f"Error applying syntax highlighter: {e!s}")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't able to find an easy way to access a logger instance in this context,
so I went with a print for now.

is there any existing logger around we could use here?

Image

return

# Apply background color from the style
if background_color := highlighter.style.background_color:
self.setStyleSheet((
"QPlainTextEdit {"
f"background-color: {background_color};"
"}"
))

def _tab_shift_right(self):
cursor = self.textCursor()
Expand Down
1 change: 1 addition & 0 deletions client/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ markdown = "^3.4.1"
clique = "1.6.*"
jsonschema = "^2.6.0"
pyblish-base = "^1.8.11"
pygments = "^2.18.0"
speedcopy = "^2.1"
six = "^1.15"
qtawesome = "0.7.3"
Expand Down
Loading