mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Working CSS syntax highlighting
This commit is contained in:
parent
c9f9df0eb0
commit
966f4ab841
@ -6,11 +6,127 @@ from __future__ import (unicode_literals, division, absolute_import,
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import re
|
||||
from functools import partial
|
||||
|
||||
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
|
||||
|
||||
from PyQt4.Qt import QTextCharFormat
|
||||
|
||||
space_pat = re.compile(r'[ \n\t\r\f]+')
|
||||
cdo_pat = re.compile(r'/\*')
|
||||
sheet_tokens = [(re.compile(k), v, n) for k, v, n in [
|
||||
(r'\:[a-zA-Z0-9_-]+', 'pseudo_selector', 'pseudo-selector'),
|
||||
(r'\.[a-zA-Z0-9_-]+', 'class_selector', 'class-selector'),
|
||||
(r'\#[a-zA-Z0-9_-]+', 'id_selector', 'id-selector'),
|
||||
(r'@[a-zA-Z0-9_-]+', 'preproc', 'atrule'),
|
||||
(r'[a-zA-Z0-9_-]+', 'tag', 'tag'),
|
||||
(r'[~\^\*!%&\[\]\(\)<>\|+=@:;,./?-]', 'operator', 'operator'),
|
||||
]]
|
||||
|
||||
content_tokens = [(re.compile(k), v, n) for k, v, n in [
|
||||
(r'url\(.*?\)', 'string', 'url'),
|
||||
(r'@\S+', 'preproc', 'at-rule'),
|
||||
(r'(azimuth|background-attachment|background-color|'
|
||||
r'background-image|background-position|background-repeat|'
|
||||
r'background|border-bottom-color|border-bottom-style|'
|
||||
r'border-bottom-width|border-left-color|border-left-style|'
|
||||
r'border-left-width|border-right|border-right-color|'
|
||||
r'border-right-style|border-right-width|border-top-color|'
|
||||
r'border-top-style|border-top-width|border-bottom|'
|
||||
r'border-collapse|border-left|border-width|border-color|'
|
||||
r'border-spacing|border-style|border-top|border|caption-side|'
|
||||
r'clear|clip|color|content|counter-increment|counter-reset|'
|
||||
r'cue-after|cue-before|cue|cursor|direction|display|'
|
||||
r'elevation|empty-cells|float|font-family|font-size|'
|
||||
r'font-size-adjust|font-stretch|font-style|font-variant|'
|
||||
r'font-weight|font|height|letter-spacing|line-height|'
|
||||
r'list-style-type|list-style-image|list-style-position|'
|
||||
r'list-style|margin-bottom|margin-left|margin-right|'
|
||||
r'margin-top|margin|marker-offset|marks|max-height|max-width|'
|
||||
r'min-height|min-width|opacity|orphans|outline|outline-color|'
|
||||
r'outline-style|outline-width|overflow(?:-x|-y)?|padding-bottom|'
|
||||
r'padding-left|padding-right|padding-top|padding|page|'
|
||||
r'page-break-after|page-break-before|page-break-inside|'
|
||||
r'pause-after|pause-before|pause|pitch|pitch-range|'
|
||||
r'play-during|position|quotes|richness|right|size|'
|
||||
r'speak-header|speak-numeral|speak-punctuation|speak|'
|
||||
r'speech-rate|stress|table-layout|text-align|text-decoration|'
|
||||
r'text-indent|text-shadow|text-transform|top|unicode-bidi|'
|
||||
r'vertical-align|visibility|voice-family|volume|white-space|'
|
||||
r'widows|width|word-spacing|z-index|bottom|left|'
|
||||
r'above|absolute|always|armenian|aural|auto|avoid|baseline|'
|
||||
r'behind|below|bidi-override|blink|block|bold|bolder|both|'
|
||||
r'capitalize|center-left|center-right|center|circle|'
|
||||
r'cjk-ideographic|close-quote|collapse|condensed|continuous|'
|
||||
r'crop|crosshair|cross|cursive|dashed|decimal-leading-zero|'
|
||||
r'decimal|default|digits|disc|dotted|double|e-resize|embed|'
|
||||
r'extra-condensed|extra-expanded|expanded|fantasy|far-left|'
|
||||
r'far-right|faster|fast|fixed|georgian|groove|hebrew|help|'
|
||||
r'hidden|hide|higher|high|hiragana-iroha|hiragana|icon|'
|
||||
r'inherit|inline-table|inline|inset|inside|invert|italic|'
|
||||
r'justify|katakana-iroha|katakana|landscape|larger|large|'
|
||||
r'left-side|leftwards|level|lighter|line-through|list-item|'
|
||||
r'loud|lower-alpha|lower-greek|lower-roman|lowercase|ltr|'
|
||||
r'lower|low|medium|message-box|middle|mix|monospace|'
|
||||
r'n-resize|narrower|ne-resize|no-close-quote|no-open-quote|'
|
||||
r'no-repeat|none|normal|nowrap|nw-resize|oblique|once|'
|
||||
r'open-quote|outset|outside|overline|pointer|portrait|px|'
|
||||
r'relative|repeat-x|repeat-y|repeat|rgb|ridge|right-side|'
|
||||
r'rightwards|s-resize|sans-serif|scroll|se-resize|'
|
||||
r'semi-condensed|semi-expanded|separate|serif|show|silent|'
|
||||
r'slow|slower|small-caps|small-caption|smaller|soft|solid|'
|
||||
r'spell-out|square|static|status-bar|super|sw-resize|'
|
||||
r'table-caption|table-cell|table-column|table-column-group|'
|
||||
r'table-footer-group|table-header-group|table-row|'
|
||||
r'table-row-group|text|text-bottom|text-top|thick|thin|'
|
||||
r'transparent|ultra-condensed|ultra-expanded|underline|'
|
||||
r'upper-alpha|upper-latin|upper-roman|uppercase|url|'
|
||||
r'visible|w-resize|wait|wider|x-fast|x-high|x-large|x-loud|'
|
||||
r'x-low|x-small|x-soft|xx-large|xx-small|yes)\b', 'keyword', 'keyword'),
|
||||
(r'(indigo|gold|firebrick|indianred|yellow|darkolivegreen|'
|
||||
r'darkseagreen|mediumvioletred|mediumorchid|chartreuse|'
|
||||
r'mediumslateblue|black|springgreen|crimson|lightsalmon|brown|'
|
||||
r'turquoise|olivedrab|cyan|silver|skyblue|gray|darkturquoise|'
|
||||
r'goldenrod|darkgreen|darkviolet|darkgray|lightpink|teal|'
|
||||
r'darkmagenta|lightgoldenrodyellow|lavender|yellowgreen|thistle|'
|
||||
r'violet|navy|orchid|blue|ghostwhite|honeydew|cornflowerblue|'
|
||||
r'darkblue|darkkhaki|mediumpurple|cornsilk|red|bisque|slategray|'
|
||||
r'darkcyan|khaki|wheat|deepskyblue|darkred|steelblue|aliceblue|'
|
||||
r'gainsboro|mediumturquoise|floralwhite|coral|purple|lightgrey|'
|
||||
r'lightcyan|darksalmon|beige|azure|lightsteelblue|oldlace|'
|
||||
r'greenyellow|royalblue|lightseagreen|mistyrose|sienna|'
|
||||
r'lightcoral|orangered|navajowhite|lime|palegreen|burlywood|'
|
||||
r'seashell|mediumspringgreen|fuchsia|papayawhip|blanchedalmond|'
|
||||
r'peru|aquamarine|white|darkslategray|ivory|dodgerblue|'
|
||||
r'lemonchiffon|chocolate|orange|forestgreen|slateblue|olive|'
|
||||
r'mintcream|antiquewhite|darkorange|cadetblue|moccasin|'
|
||||
r'limegreen|saddlebrown|darkslateblue|lightskyblue|deeppink|'
|
||||
r'plum|aqua|darkgoldenrod|maroon|sandybrown|magenta|tan|'
|
||||
r'rosybrown|pink|lightblue|palevioletred|mediumseagreen|'
|
||||
r'dimgray|powderblue|seagreen|snow|mediumblue|midnightblue|'
|
||||
r'paleturquoise|palegoldenrod|whitesmoke|darkorchid|salmon|'
|
||||
r'lightslategray|lawngreen|lightgreen|tomato|hotpink|'
|
||||
r'lightyellow|lavenderblush|linen|mediumaquamarine|green|'
|
||||
r'blueviolet|peachpuff)\b', 'colorname', 'colorname'),
|
||||
(r'\!important', 'preproc', 'important'),
|
||||
(r'\#[a-zA-Z0-9]{1,6}', 'number', 'hexnumber'),
|
||||
(r'[\.-]?[0-9]*[\.]?[0-9]+(em|px|pt|pc|in|mm|cm|ex|s|rem)\b', 'number', 'dimension'),
|
||||
(r'[\.-]?[0-9]*[\.]?[0-9]+%($|[ \n\t\f\r;}{()\[\]])', 'number', 'dimension'),
|
||||
(r'-?[0-9]+', 'number', 'number'),
|
||||
(r'[~\^\*!%&<>\|+=@:,./?-]+', 'operator', 'operator'),
|
||||
(r'[\[\]();]+', 'bracket', 'bracket'),
|
||||
(r'[a-zA-Z_][a-zA-Z0-9_]*', 'identifier', 'ident')
|
||||
|
||||
]]
|
||||
|
||||
class State(object):
|
||||
|
||||
NORMAL = 0
|
||||
IN_COMMENT = 1
|
||||
IN_SQS = 2
|
||||
IN_DQS = 3
|
||||
IN_CONTENT = 4
|
||||
|
||||
def __init__(self, num):
|
||||
self.parse = num & 0b1111
|
||||
@ -22,11 +138,85 @@ class State(object):
|
||||
|
||||
|
||||
def normal(state, text, i, formats):
|
||||
' The normal state (outside everything) '
|
||||
return [(len(text), None)]
|
||||
' The normal state (outside content blocks {})'
|
||||
m = space_pat.match(text, i)
|
||||
if m is not None:
|
||||
return [(len(m.group()), None)]
|
||||
cdo = cdo_pat.match(text, i)
|
||||
if cdo is not None:
|
||||
state.parse = State.IN_COMMENT
|
||||
return [(len(cdo.group()), formats['comment'])]
|
||||
if text[i] == '"':
|
||||
state.parse = State.IN_DQS
|
||||
return [(1, formats['string'])]
|
||||
if text[i] == "'":
|
||||
state.parse = State.IN_SQS
|
||||
return [(1, formats['string'])]
|
||||
if text[i] == '{':
|
||||
state.parse = State.IN_CONTENT
|
||||
state.blocks += 1
|
||||
return [(1, formats['bracket'])]
|
||||
for token, fmt, name in sheet_tokens:
|
||||
m = token.match(text, i)
|
||||
if m is not None:
|
||||
return [(len(m.group()), formats[fmt])]
|
||||
|
||||
return [(len(text) - i, formats['unknown-normal'])]
|
||||
|
||||
def content(state, text, i, formats):
|
||||
' Inside content blocks '
|
||||
m = space_pat.match(text, i)
|
||||
if m is not None:
|
||||
return [(len(m.group()), None)]
|
||||
cdo = cdo_pat.match(text, i)
|
||||
if cdo is not None:
|
||||
state.parse = State.IN_COMMENT
|
||||
return [(len(cdo.group()), formats['comment'])]
|
||||
if text[i] == '"':
|
||||
state.parse = State.IN_DQS
|
||||
return [(1, formats['string'])]
|
||||
if text[i] == "'":
|
||||
state.parse = State.IN_SQS
|
||||
return [(1, formats['string'])]
|
||||
if text[i] == '}':
|
||||
state.blocks -= 1
|
||||
state.parse = State.NORMAL if state.blocks < 1 else State.IN_CONTENT
|
||||
return [(1, formats['bracket'])]
|
||||
if text[i] == '{':
|
||||
state.blocks += 1
|
||||
return [(1, formats['bracket'])]
|
||||
for token, fmt, name in content_tokens:
|
||||
m = token.match(text, i)
|
||||
if m is not None:
|
||||
return [(len(m.group()), formats[fmt])]
|
||||
|
||||
return [(len(text) - i, formats['unknown-normal'])]
|
||||
|
||||
def comment(state, text, i, formats):
|
||||
' Inside a comment '
|
||||
pos = text.find('*/', i)
|
||||
if pos == -1:
|
||||
return [(len(text), formats['comment'])]
|
||||
state.parse = State.NORMAL
|
||||
return [(pos - i + 2, formats['comment'])]
|
||||
|
||||
def in_string(q, state, text, i, formats):
|
||||
'Inside a string'
|
||||
pos = text.find(q, i)
|
||||
if pos == -1:
|
||||
if text[-1] == '\\':
|
||||
return [(len(text) - i, formats['string'])]
|
||||
state.parse = (State.NORMAL if state.blocks < 1 else State.IN_CONTENT)
|
||||
return [(len(text) - i, formats['unterminated-string'])]
|
||||
state.parse = (State.NORMAL if state.blocks < 1 else State.IN_CONTENT)
|
||||
return [(pos - i + len(q), formats['string'])]
|
||||
|
||||
state_map = {
|
||||
State.NORMAL:normal,
|
||||
State.IN_COMMENT: comment,
|
||||
State.IN_SQS: partial(in_string, "'"),
|
||||
State.IN_DQS: partial(in_string, '"'),
|
||||
State.IN_CONTENT: content,
|
||||
}
|
||||
|
||||
class CSSHighlighter(SyntaxHighlighter):
|
||||
@ -38,14 +228,49 @@ class CSSHighlighter(SyntaxHighlighter):
|
||||
SyntaxHighlighter.__init__(self, parent)
|
||||
|
||||
def create_formats(self):
|
||||
# t = self.theme
|
||||
t = self.theme
|
||||
self.formats = {
|
||||
'comment': t['Comment'],
|
||||
'error': t['Error'],
|
||||
'string': t['String'],
|
||||
'preproc': t['PreProc'],
|
||||
'keyword': t['Keyword'],
|
||||
'colorname': t['Constant'],
|
||||
'number': t['Number'],
|
||||
'operator': t['Function'],
|
||||
'bracket': t['Special'],
|
||||
'identifier': t['Identifier'],
|
||||
'id_selector': t['Special'],
|
||||
'class_selector': t['Special'],
|
||||
'pseudo_selector': t['Special'],
|
||||
'tag': t['Identifier'],
|
||||
}
|
||||
for name, msg in {
|
||||
'unknown-normal': _('Invalid text'),
|
||||
'unterminated-string': _('Unterminated string'),
|
||||
}.iteritems():
|
||||
f = self.formats[name] = QTextCharFormat(self.formats['error'])
|
||||
f.setToolTip(msg)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from calibre.gui2.tweak_book.editor.text import launch_editor
|
||||
launch_editor('''\
|
||||
@charset "utf-8";
|
||||
/* A demonstration css sheet */
|
||||
body {
|
||||
color: green;
|
||||
font-size: 12pt
|
||||
}
|
||||
|
||||
div#main > a:hover {
|
||||
background: url("../image.png");
|
||||
font-family: "A font", sans-serif;
|
||||
}
|
||||
|
||||
li[rel="mewl"], p.mewl {
|
||||
margin-top: 2% 0 23pt 1em;
|
||||
}
|
||||
|
||||
''', path_is_raw=True, syntax='css')
|
||||
|
||||
|
@ -33,13 +33,13 @@ THEMES = {
|
||||
|
||||
Comment fg=99968b
|
||||
Todo fg=8f8f8f
|
||||
Constant fg=e5786d
|
||||
String fg=95e454
|
||||
Identifier fg=cae682
|
||||
Function fg=cae682
|
||||
Type fg=cae682
|
||||
Statement fg=8ac6f2
|
||||
Keyword fg=8ac6f2
|
||||
Constant fg=e5786d
|
||||
PreProc fg=e5786d
|
||||
Number fg=e5786d
|
||||
Special fg=e7f6da
|
||||
|
Loading…
x
Reference in New Issue
Block a user