mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Syntax highlighting for template editor
This commit is contained in:
commit
b743365518
@ -5,10 +5,193 @@ __license__ = 'GPL v3'
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from PyQt4.Qt import Qt, QDialog, QDialogButtonBox
|
from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QSyntaxHighlighter,
|
||||||
|
QRegExp, QApplication,
|
||||||
|
QTextCharFormat, QFont, QColor, QCursor)
|
||||||
|
|
||||||
from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog
|
from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog
|
||||||
from calibre.utils.formatter_functions import formatter_functions
|
from calibre.utils.formatter_functions import formatter_functions
|
||||||
|
|
||||||
|
class ParenPosition:
|
||||||
|
|
||||||
|
def __init__(self, block, pos, paren):
|
||||||
|
self.block = block
|
||||||
|
self.pos = pos
|
||||||
|
self.paren = paren
|
||||||
|
self.highlight = False
|
||||||
|
|
||||||
|
def set_highlight(self, to_what):
|
||||||
|
self.highlight = to_what
|
||||||
|
|
||||||
|
class TemplateHighlighter(QSyntaxHighlighter):
|
||||||
|
|
||||||
|
Config = {}
|
||||||
|
Rules = []
|
||||||
|
Formats = {}
|
||||||
|
BN_FACTOR = 1000
|
||||||
|
|
||||||
|
KEYWORDS = ["program"]
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(TemplateHighlighter, self).__init__(parent)
|
||||||
|
|
||||||
|
self.initializeFormats()
|
||||||
|
|
||||||
|
TemplateHighlighter.Rules.append((QRegExp(
|
||||||
|
"|".join([r"\b%s\b" % keyword for keyword in self.KEYWORDS])),
|
||||||
|
"keyword"))
|
||||||
|
TemplateHighlighter.Rules.append((QRegExp(
|
||||||
|
"|".join([r"\b%s\b" % builtin for builtin in
|
||||||
|
formatter_functions.get_builtins()])),
|
||||||
|
"builtin"))
|
||||||
|
|
||||||
|
TemplateHighlighter.Rules.append((QRegExp(
|
||||||
|
r"\b[+-]?[0-9]+[lL]?\b"
|
||||||
|
r"|\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b"
|
||||||
|
r"|\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b"),
|
||||||
|
"number"))
|
||||||
|
|
||||||
|
stringRe = QRegExp(r"""(?:[^:]'[^']*'|"[^"]*")""")
|
||||||
|
stringRe.setMinimal(True)
|
||||||
|
TemplateHighlighter.Rules.append((stringRe, "string"))
|
||||||
|
|
||||||
|
lparenRe = QRegExp(r'\(')
|
||||||
|
lparenRe.setMinimal(True)
|
||||||
|
TemplateHighlighter.Rules.append((lparenRe, "lparen"))
|
||||||
|
rparenRe = QRegExp(r'\)')
|
||||||
|
rparenRe.setMinimal(True)
|
||||||
|
TemplateHighlighter.Rules.append((rparenRe, "rparen"))
|
||||||
|
|
||||||
|
self.regenerate_paren_positions()
|
||||||
|
self.highlighted_paren = False
|
||||||
|
|
||||||
|
def initializeFormats(self):
|
||||||
|
Config = self.Config
|
||||||
|
Config["fontfamily"] = "monospace"
|
||||||
|
#Config["fontsize"] = 10
|
||||||
|
for name, color, bold, italic in (
|
||||||
|
("normal", "#000000", False, False),
|
||||||
|
("keyword", "#000080", True, False),
|
||||||
|
("builtin", "#0000A0", False, False),
|
||||||
|
("comment", "#007F00", False, True),
|
||||||
|
("string", "#808000", False, False),
|
||||||
|
("number", "#924900", False, False),
|
||||||
|
("lparen", "#000000", True, True),
|
||||||
|
("rparen", "#000000", True, True)):
|
||||||
|
Config["%sfontcolor" % name] = color
|
||||||
|
Config["%sfontbold" % name] = bold
|
||||||
|
Config["%sfontitalic" % name] = italic
|
||||||
|
|
||||||
|
baseFormat = QTextCharFormat()
|
||||||
|
baseFormat.setFontFamily(Config["fontfamily"])
|
||||||
|
#baseFormat.setFontPointSize(Config["fontsize"])
|
||||||
|
|
||||||
|
for name in ("normal", "keyword", "builtin", "comment",
|
||||||
|
"string", "number", "lparen", "rparen"):
|
||||||
|
format = QTextCharFormat(baseFormat)
|
||||||
|
format.setForeground(QColor(Config["%sfontcolor" % name]))
|
||||||
|
if Config["%sfontbold" % name]:
|
||||||
|
format.setFontWeight(QFont.Bold)
|
||||||
|
format.setFontItalic(Config["%sfontitalic" % name])
|
||||||
|
self.Formats[name] = format
|
||||||
|
|
||||||
|
def find_paren(self, bn, pos):
|
||||||
|
dex = bn * self.BN_FACTOR + pos
|
||||||
|
return self.paren_pos_map.get(dex, None)
|
||||||
|
|
||||||
|
def highlightBlock(self, text):
|
||||||
|
bn = self.currentBlock().blockNumber()
|
||||||
|
textLength = text.length()
|
||||||
|
|
||||||
|
self.setFormat(0, textLength, self.Formats["normal"])
|
||||||
|
|
||||||
|
if text.isEmpty():
|
||||||
|
pass
|
||||||
|
elif text[0] == "#":
|
||||||
|
self.setFormat(0, text.length(), self.Formats["comment"])
|
||||||
|
return
|
||||||
|
|
||||||
|
for regex, format in TemplateHighlighter.Rules:
|
||||||
|
i = regex.indexIn(text)
|
||||||
|
while i >= 0:
|
||||||
|
length = regex.matchedLength()
|
||||||
|
if format in ['lparen', 'rparen']:
|
||||||
|
pp = self.find_paren(bn, i)
|
||||||
|
if pp and pp.highlight:
|
||||||
|
self.setFormat(i, length, self.Formats[format])
|
||||||
|
else:
|
||||||
|
self.setFormat(i, length, self.Formats[format])
|
||||||
|
i = regex.indexIn(text, i + length)
|
||||||
|
|
||||||
|
if self.generate_paren_positions:
|
||||||
|
t = unicode(text)
|
||||||
|
i = 0
|
||||||
|
foundQuote = False
|
||||||
|
while i < len(t):
|
||||||
|
c = t[i]
|
||||||
|
if c == ':':
|
||||||
|
# Deal with the funky syntax of template program mode.
|
||||||
|
# This won't work if there are more than one template
|
||||||
|
# expression in the document.
|
||||||
|
if not foundQuote and i+1 < len(t) and t[i+1] == "'":
|
||||||
|
i += 2
|
||||||
|
elif c in ["'", '"']:
|
||||||
|
foundQuote = True
|
||||||
|
i += 1
|
||||||
|
j = t[i:].find(c)
|
||||||
|
if j < 0:
|
||||||
|
i = len(t)
|
||||||
|
else:
|
||||||
|
i = i + j
|
||||||
|
elif c in ['(', ')']:
|
||||||
|
pp = ParenPosition(bn, i, c)
|
||||||
|
self.paren_positions.append(pp)
|
||||||
|
self.paren_pos_map[bn*self.BN_FACTOR+i] = pp
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
def rehighlight(self):
|
||||||
|
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||||
|
QSyntaxHighlighter.rehighlight(self)
|
||||||
|
QApplication.restoreOverrideCursor()
|
||||||
|
|
||||||
|
def check_cursor_pos(self, chr, block, pos_in_block):
|
||||||
|
found_pp = -1
|
||||||
|
for i, pp in enumerate(self.paren_positions):
|
||||||
|
pp.set_highlight(False)
|
||||||
|
if pp.block == block and pp.pos == pos_in_block:
|
||||||
|
found_pp = i
|
||||||
|
|
||||||
|
if chr not in ['(', ')']:
|
||||||
|
if self.highlighted_paren:
|
||||||
|
self.rehighlight()
|
||||||
|
self.highlighted_paren = False
|
||||||
|
return
|
||||||
|
|
||||||
|
if found_pp >= 0:
|
||||||
|
stack = 0
|
||||||
|
if chr == '(':
|
||||||
|
list = self.paren_positions[found_pp+1:]
|
||||||
|
else:
|
||||||
|
list = reversed(self.paren_positions[0:found_pp])
|
||||||
|
for pp in list:
|
||||||
|
if pp.paren == chr:
|
||||||
|
stack += 1;
|
||||||
|
elif stack:
|
||||||
|
stack -= 1
|
||||||
|
else:
|
||||||
|
pp.set_highlight(True)
|
||||||
|
self.paren_positions[found_pp].set_highlight(True)
|
||||||
|
break
|
||||||
|
self.highlighted_paren = True
|
||||||
|
self.rehighlight()
|
||||||
|
|
||||||
|
def regenerate_paren_positions(self):
|
||||||
|
self.generate_paren_positions = True
|
||||||
|
self.paren_positions = []
|
||||||
|
self.paren_pos_map = {}
|
||||||
|
self.rehighlight()
|
||||||
|
self.generate_paren_positions = False
|
||||||
|
|
||||||
class TemplateDialog(QDialog, Ui_TemplateDialog):
|
class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||||
|
|
||||||
def __init__(self, parent, text):
|
def __init__(self, parent, text):
|
||||||
@ -20,6 +203,11 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
|||||||
self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint))
|
self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint))
|
||||||
self.setWindowIcon(icon)
|
self.setWindowIcon(icon)
|
||||||
|
|
||||||
|
self.last_text = ''
|
||||||
|
self.highlighter = TemplateHighlighter(self.textbox.document())
|
||||||
|
self.textbox.cursorPositionChanged.connect(self.text_cursor_changed)
|
||||||
|
self.textbox.textChanged.connect(self.textbox_changed)
|
||||||
|
|
||||||
self.textbox.setTabStopWidth(10)
|
self.textbox.setTabStopWidth(10)
|
||||||
self.source_code.setTabStopWidth(10)
|
self.source_code.setTabStopWidth(10)
|
||||||
self.documentation.setReadOnly(True)
|
self.documentation.setReadOnly(True)
|
||||||
@ -46,6 +234,22 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
|||||||
self.function.setCurrentIndex(0)
|
self.function.setCurrentIndex(0)
|
||||||
self.function.currentIndexChanged[str].connect(self.function_changed)
|
self.function.currentIndexChanged[str].connect(self.function_changed)
|
||||||
|
|
||||||
|
def textbox_changed(self):
|
||||||
|
cur_text = unicode(self.textbox.toPlainText())
|
||||||
|
if self.last_text != cur_text:
|
||||||
|
self.last_text = cur_text
|
||||||
|
self.highlighter.regenerate_paren_positions()
|
||||||
|
|
||||||
|
def text_cursor_changed(self):
|
||||||
|
cursor = self.textbox.textCursor()
|
||||||
|
block_number = cursor.blockNumber()
|
||||||
|
pos_in_block = cursor.positionInBlock()
|
||||||
|
position = cursor.position()
|
||||||
|
t = unicode(self.textbox.toPlainText())
|
||||||
|
if position < len(t):
|
||||||
|
self.highlighter.check_cursor_pos(t[position], block_number,
|
||||||
|
pos_in_block)
|
||||||
|
|
||||||
def function_changed(self, toWhat):
|
def function_changed(self, toWhat):
|
||||||
name = unicode(toWhat)
|
name = unicode(toWhat)
|
||||||
self.source_code.clear()
|
self.source_code.clear()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user