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
|
||||
|
||||
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.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):
|
||||
|
||||
def __init__(self, parent, text):
|
||||
@ -20,6 +203,11 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||
self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint))
|
||||
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.source_code.setTabStopWidth(10)
|
||||
self.documentation.setReadOnly(True)
|
||||
@ -46,6 +234,22 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||
self.function.setCurrentIndex(0)
|
||||
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):
|
||||
name = unicode(toWhat)
|
||||
self.source_code.clear()
|
||||
|
Loading…
x
Reference in New Issue
Block a user