Adds txt2tags highlighter, from an other project -- probably buggy

This commit is contained in:
Olivier Keshavjee 2015-06-06 16:21:08 +02:00
parent a7068f1d4f
commit 03a0124093
14 changed files with 1188 additions and 18 deletions

View file

@ -1,4 +1,4 @@
UI := $(wildcard src/ui/*.ui) $(wildcard src/ui/*.qrc)
UI := $(wildcard src/ui/*.ui) $(wildcard src/ui/*/*.ui) $(wildcard src/ui/*.qrc)
UIs= $(UI:.ui=.py) $(UI:.qrc=_rc.py)

View file

@ -41,3 +41,4 @@ class Outline(Enum):
goalPercentage = 12
setGoal = 13 # The goal set by the user, if any. Can be different from goal which can be computed
# (sum of all sub-items' goals)
textFormat = 14

View file

View file

@ -0,0 +1,68 @@
#!/usr/bin/python
# -*- coding: utf8 -*-
from qt import *
class blockUserData (QTextBlockUserData):
@staticmethod
def getUserData(block):
"Returns userData if it exists, or a blank one."
data = block.userData()
if data is None:
data = blockUserData()
return data
@staticmethod
def getUserState(block):
"Returns the block state."
state = block.userState()
while state >= 100:
state -= 100 # +100 means in a list
return state
def __init__(self):
QTextBlockUserData.__init__(self)
self._listLevel = 0
self._leadingSpaces = 0
self._emptyLinesBefore = 0
self._listSymbol = ""
def isList(self):
return self._listLevel > 0
def listLevel(self):
return self._listLevel
def setListLevel(self, level):
self._listLevel = level
def listSymbol(self):
return self._listSymbol
def setListSymbol(self, s):
self._listSymbol = s
def leadingSpaces(self):
return self._leadingSpaces
def setLeadingSpaces(self, n):
self._leadingSpaces = n
def emptyLinesBefore(self):
return self._emptyLinesBefore
def setEmptyLinesBefore(self, n):
self._emptyLinesBefore = n
def text(self):
return str(self.listLevel()) + "|" + str(self.leadingSpaces()) + "|" + str(self.emptyLinesBefore())
def __eq__(self, b):
return self._listLevel == b._listLevel and \
self._leadingSpaces == b._leadingSpaces and \
self._emptyLinesBefore == b._emptyLinesBefore
def __ne__(self, b):
return not self == b

View file

@ -0,0 +1,31 @@
#!/usr/bin/env python
#--!-- coding: utf8 --!--
from __future__ import print_function
from __future__ import unicode_literals
from qt import *
from enums import *
from ui.editors.t2tHighlighter import *
try:
import enchant
except ImportError:
enchant = None
class customTextEdit(QTextEdit):
def __init__(self, parent=None):
QTextEdit.__init__(self, parent)
self.defaultFontPointSize = 9
self.highlightWord = ""
self.highligtCS = False
self.highlighter = t2tHighlighter(self)
# Spellchecking
if enchant:
self.dict = enchant.Dict("fr_CH")
self.spellcheck = True
else:
self.spellcheck = False

View file

@ -6,7 +6,7 @@ from __future__ import unicode_literals
from qt import *
from enums import *
from ui.editorWidget_ui import *
from ui.editors.editorWidget_ui import *
class GrowingTextEdit(QTextEdit):

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'src/ui/editorWidget_ui.ui'
# Form implementation generated from reading ui file 'src/ui/editors/editorWidget_ui.ui'
#
# Created by: PyQt5 UI code generator 5.4.1
#
@ -22,7 +22,7 @@ class Ui_editorWidget_ui(object):
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.scene)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.txtRedacText = QtWidgets.QPlainTextEdit(self.scene)
self.txtRedacText = customTextEdit(self.scene)
self.txtRedacText.setObjectName("txtRedacText")
self.horizontalLayout_2.addWidget(self.txtRedacText)
self.stack.addWidget(self.scene)
@ -45,10 +45,11 @@ class Ui_editorWidget_ui(object):
self.horizontalLayout.addWidget(self.stack)
self.retranslateUi(editorWidget_ui)
self.stack.setCurrentIndex(1)
self.stack.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(editorWidget_ui)
def retranslateUi(self, editorWidget_ui):
_translate = QtCore.QCoreApplication.translate
editorWidget_ui.setWindowTitle(_translate("editorWidget_ui", "Form"))
from ui.editors.customTextEdit import customTextEdit

View file

@ -20,7 +20,7 @@
<item>
<widget class="QStackedWidget" name="stack">
<property name="currentIndex">
<number>1</number>
<number>0</number>
</property>
<widget class="QWidget" name="scene">
<layout class="QHBoxLayout" name="horizontalLayout_2">
@ -28,7 +28,7 @@
<number>0</number>
</property>
<item>
<widget class="QPlainTextEdit" name="txtRedacText"/>
<widget class="customTextEdit" name="txtRedacText"/>
</item>
</layout>
</widget>
@ -66,6 +66,13 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>customTextEdit</class>
<extends>QTextEdit</extends>
<header>ui.editors.customTextEdit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,264 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from qt import *
def textToFormatArray(text):
"""
Take some text and returns an array of array containing informations
about how the text is formatted:
r = [ [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1],
...
]
Each sub-array is for one of the beautifier:
0: bold
1: italic
2: underline
3: strike
4: code
5: tagged
Each sub-array contains an element for each character of the text, with the
value 1 if it is formatted in the specific format, -1 if it is markup, and
0 otherwise.
removeMarks returns a both the array and a string, in which all of the
formatting marks have been removed.
"""
result = []
for markup in ["\*", "/", "_", "-", "`", "\'"]:
rList = []
r = QRegExp(r'(' + markup * 2 + ')(.+)(' + markup * 2 + ')')
r.setMinimal(True)
pos = r.indexIn(text, 0)
lastPos = 0
while pos >= 0:
#We have a winner
rList += [0] * (pos - lastPos)
rList += [2] * 2
rList += [1] * len(r.cap(2))
rList += [2] * 2
lastPos = pos + len(r.cap(0))
pos = r.indexIn(text, len(rList))
if len(rList) < len(text):
rList += [0] * (len(text) - len(rList))
result.append(rList)
return result
def textToFormatArrayNoMarkup(text):
"""
Same as textToFormatArray, except that it removes all the markup from the
text and returns two elements:
the array
the text without markup
"""
r = textToFormatArray(text)
result = [[], [], [], [], [], []]
rText = ""
for i in range(len(text)):
t = max([k[i] for k in r]) # kind of flattens all the format array
if t != 2:
rText += text[i]
[result[k].append(r[k][i]) for k in range(len(r))]
return rText, result
def translateSelectionToUnformattedText(text, start, end):
"""
Translate the start / end of selection from a formatted text to an
unformatted one.
"""
r = textToFormatArray(text)
rStart, rEnd = start, end
for i in range(len(text)):
t = max([k[i] for k in r]) # kind of flattens all the format array
if t == 2: # t == 2 means this character is markup
if i <= start: rStart -= 1
if i < end: rEnd -= 1
return rStart, rEnd
def translateSelectionToFormattedText(text, start, end):
"""
Translate the start / end of selection from a formatted text to an
unformatted one.
"""
r = textToFormatArray(text)
rStart, rEnd = start, end
for i in range(len(text)):
t = max([k[i] for k in r]) # kind of flattens all the format array
if t == 2: # t == 2 means this character is markup
if i <= start: rStart -= 1
if i < end: rEnd -= 1
return rStart, rEnd
def printArray(array):
print("".join([str(j) for j in array]))
def printArrays(arrays):
for i in arrays: printArray(i)
def reformatText(text, markupArray):
"""
Takes a text without formatting markup, and an array generated by
textToFormatArray, and adds the propper markup.
"""
rText = ""
markup = ["**", "//", "__", "--", "``", "''"]
for k in range(len(markupArray)):
m = markupArray[k]
open = False # Are we in an openned markup
d = 0
alreadySeen = []
for i in range(len(text)):
insert = False
if not open and m[i] == 1:
insert = True
open = True
if open and m[i] == 0:
insert = True
open = False
if open and m[i] > 1:
z = i
while m[z] == m[i]: z += 1
if m[z] != 1 and not m[i] in alreadySeen:
insert = True
open = False
alreadySeen.append(m[i])
if insert:
rText += markup[k]
for j in range(len(markupArray)):
# The other array still have the same length
if j > k:
#Insert 2 for bold, 3 for italic, etc.
markupArray[j].insert(i + d, k + 2)
markupArray[j].insert(i + d, k + 2)
alreadySeen = []
d += 2
rText += text[i]
if open:
rText += markup[k]
for j in range(len(markupArray)):
# The other array still have the same length
if j > k:
#Insert 2 for bold, 3 for italic, etc.
markupArray[j].insert(i + d, k + 2)
markupArray[j].insert(i + d, k + 2)
text = rText
rText = ""
## Clean up
# Exclude first and last space of the markup
for markup in ["\*", "/", "_", "-", "`", "\'"]:
r = QRegExp(r'(' + markup * 2 + ')(\s+)(.+)(' + markup * 2 + ')')
r.setMinimal(True)
text.replace(r, "\\2\\1\\3\\4")
r = QRegExp(r'(' + markup * 2 + ')(.+)(\s+)(' + markup * 2 + ')')
r.setMinimal(True)
text.replace(r, "\\1\\2\\4\\3")
return text
def cleanFormat(text):
"Makes markup clean (removes doubles, etc.)"
t, a = textToFormatArrayNoMarkup(text)
return reformatText(t, a)
class State:
NORMAL = 0
TITLE_1 = 1
TITLE_2 = 2
TITLE_3 = 3
TITLE_4 = 4
TITLE_5 = 5
NUMBERED_TITLE_1 = 6
NUMBERED_TITLE_2 = 7
NUMBERED_TITLE_3 = 8
NUMBERED_TITLE_4 = 9
NUMBERED_TITLE_5 = 10
TITLES = [TITLE_1, TITLE_2, TITLE_3, TITLE_4, TITLE_5, NUMBERED_TITLE_1,
NUMBERED_TITLE_2, NUMBERED_TITLE_3, NUMBERED_TITLE_4,
NUMBERED_TITLE_5]
# AREA
COMMENT_AREA = 11
CODE_AREA = 12
RAW_AREA = 13
TAGGED_AREA = 14
# AREA MARKUP
COMMENT_AREA_BEGINS = 15
COMMENT_AREA_ENDS = 16
CODE_AREA_BEGINS = 17
CODE_AREA_ENDS = 18
RAW_AREA_BEGINS = 19
RAW_AREA_ENDS = 20
TAGGED_AREA_BEGINS = 21
TAGGED_AREA_ENDS = 22
#LINE
COMMENT_LINE = 30
CODE_LINE = 31
RAW_LINE = 32
TAGGED_LINE = 33
SETTINGS_LINE = 34
BLOCKQUOTE_LINE = 35
HORIZONTAL_LINE = 36
HEADER_LINE = 37
# LIST
LIST_BEGINS = 40
LIST_ENDS = 41
LIST_EMPTY = 42
LIST_BULLET = 43
LIST_BULLET_ENDS = 44
LIST = [40, 41, 42] + range(100, 201)
# TABLE
TABLE_LINE = 50
TABLE_HEADER = 51
#OTHER
MARKUP = 60
LINKS = 61
MACRO = 62
DEFAULT = 63
@staticmethod
def titleLevel(state):
"""
Returns the level of the title, from the block state.
"""
return {
State.TITLE_1: 1,
State.TITLE_2: 2,
State.TITLE_3: 3,
State.TITLE_4: 4,
State.TITLE_5: 5,
State.NUMBERED_TITLE_1: 1,
State.NUMBERED_TITLE_2: 2,
State.NUMBERED_TITLE_3: 3,
State.NUMBERED_TITLE_4: 4,
State.NUMBERED_TITLE_5: 5,
}.get(state, -1)

View file

@ -0,0 +1,553 @@
#!/usr/bin/python
# -*- coding: utf8 -*-
from qt import *
from ui.editors.t2tFunctions import *
from ui.editors.blockUserData import blockUserData
from ui.editors.t2tHighlighterStyle import t2tHighlighterStyle
import re
# This is aiming at implementing every rule from www.txt2tags.org/rules.html
# But we're not there yet.
#FIXME: macro words not hilighted properly if at the begining of a line.
#TODO: parse %!postproc et !%preproc, et si la ligne se termine par une couleur en commentaire (%#FF00FF), utiliser cette couleur pour highlighter. Permet des règles customisées par document, facilement.
class t2tHighlighter (QSyntaxHighlighter):
"""Syntax highlighter for the Txt2Tags language.
"""
def __init__(self, editor, style="Default"):
QSyntaxHighlighter.__init__(self, editor.document())
self.editor = editor
# Stupid variable that fixes the loss of QTextBlockUserData.
self.thisDocument = editor.document()
self.style = t2tHighlighterStyle(self.editor, style)
self.inDocRules = []
rules = [
(r'^\s*[-=_]{20,}\s*$', State.HORIZONTAL_LINE),
(r'^\s*(\+{1})([^\+].*[^\+])(\+{1})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_1),
(r'^\s*(\+{2})([^\+].*[^\+])(\+{2})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_2),
(r'^\s*(\+{3})([^\+].*[^\+])(\+{3})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_3),
(r'^\s*(\+{4})([^\+].*[^\+])(\+{4})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_4),
(r'^\s*(\+{5})([^\+].*[^\+])(\+{5})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_5),
(r'^\s*(={1})([^=].*[^=])(={1})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_1),
(r'^\s*(={2})([^=].*[^=])(={2})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_2),
(r'^\s*(={3})([^=].*[^=])(={3})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_3),
(r'^\s*(={4})([^=].*[^=])(={4})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_4),
(r'^\s*(={5})([^=].*[^=])(={5})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_5),
(r'^%!.*$', State.SETTINGS_LINE),
(r'^%[^!]?.*$', State.COMMENT_LINE),
(r'^\t.+$', State.BLOCKQUOTE_LINE),
(r'^(```)(.+)$', State.CODE_LINE),
(r'^(""")(.+)$', State.RAW_LINE),
(r'^(\'\'\')(.+)$', State.TAGGED_LINE),
(r'^\s*[-+:] [^ ].*$', State.LIST_BEGINS),
(r'^\s*[-+:]\s*$', State.LIST_ENDS),
(r'^ *\|\| .*$', State.TABLE_HEADER),
(r'^ *\| .*$', State.TABLE_LINE)
]
# Generate rules to identify blocks
State.Rules = [(QRegExp(pattern), state)
for (pattern, state) in rules]
State.Recursion = 0
def highlightBlock(self, text):
"""Apply syntax highlighting to the given block of text.
"""
# Check if syntax highlighting is enabled
if self.style is None:
default = QTextBlockFormat()
QTextCursor(self.currentBlock()).setBlockFormat(default)
return
block = self.currentBlock()
oldState = blockUserData.getUserState(block)
self.identifyBlock(block)
# formatBlock prevent undo/redo from working
# TODO: find a todo/undo compatible way of formatting block
#self.formatBlock(block)
state = blockUserData.getUserState(block)
data = blockUserData.getUserData(block)
inList = self.isList(block)
op = self.style.format(State.MARKUP)
self.setFormat(0, len(text), self.style.format(State.DEFAULT))
# InDocRules: is it a settings which might have a specific rule,
# a comment which contains color infos, or a include conf?
# r'^%!p[or][se]t?proc[^\s]*\s*:\s*\'(.*)\'\s*\'.*\''
rlist = [QRegExp(r'^%!p[or][se]t?proc[^\s]*\s*:\s*((\'[^\']*\'|\"[^\"]*\")\s*(\'[^\']*\'|\"[^\"]*\"))'), # pre/postproc
QRegExp(r'^%.*\s\((.*)\)'), # comment
QRegExp(r'^%!includeconf:\s*([^\s]*)\s*')] # includeconf
for r in rlist:
if r.indexIn(text) != -1:
self.parseInDocRules()
# Format the whole line:
for lineState in [
State.BLOCKQUOTE_LINE,
State.HORIZONTAL_LINE,
State.HEADER_LINE,
]:
if not inList and state == lineState:
self.setFormat(0, len(text), self.style.format(lineState))
for (lineState, marker) in [
(State.COMMENT_LINE, "%"),
(State.CODE_LINE, "```"),
(State.RAW_LINE, "\"\"\""),
(State.TAGGED_LINE, "'''"),
(State.SETTINGS_LINE, "%!")
]:
if state == lineState and \
not (inList and state == State.SETTINGS_LINE):
n = 0
# If it's a comment, we want to highlight all '%'.
if state == State.COMMENT_LINE:
while text[n:n + 1] == "%":
n += 1
n -= 1
# Apply Format
self.setFormat(0, len(marker) + n, op)
self.setFormat(len(marker) + n,
len(text) - len(marker) - n,
self.style.format(lineState))
# If it's a setting, we might do something
if state == State.SETTINGS_LINE:
# Target
r = QRegExp(r'^%!([^\s]+)\s*:\s*(\b\w*\b)$')
if r.indexIn(text) != -1:
setting = r.cap(1)
val = r.cap(2)
if setting == "target" and \
val in self.editor.main.targetsNames:
self.editor.fileWidget.preview.setPreferredTarget(val)
# Pre/postproc
r = QRegExp(r'^%!p[or][se]t?proc[^\s]*\s*:\s*((\'[^\']*\'|\"[^\"]*\")\s*(\'[^\']*\'|\"[^\"]*\"))')
if r.indexIn(text) != -1:
p = r.pos(1)
length = len(r.cap(1))
self.setFormat(p, length, self.style.makeFormat(base=self.format(p),
fixedPitch=True))
# Tables
for lineState in [State.TABLE_LINE, State.TABLE_HEADER]:
if state == lineState:
for i in range(len(text)):
if text[i] == "|":
self.setFormat(i, 1, op)
else:
self.setFormat(i, 1, self.style.format(lineState))
# Lists
#if text == " p": print(data.isList())
if data.isList():
r = QRegExp(r'^\s*[\+\-\:]? ?')
r.indexIn(text)
self.setFormat(0, r.matchedLength(), self.style.format(State.LIST_BULLET))
#if state == State.LIST_BEGINS:
#r = QRegExp(r'^\s*[+-:] ')
#r.indexIn(text)
#self.setFormat(0, r.matchedLength(), self.style.format(State.LIST_BULLET))
if state == State.LIST_ENDS:
self.setFormat(0, len(text), self.style.format(State.LIST_BULLET_ENDS))
# Titles
if not inList and state in State.TITLES:
r = [i for (i, s) in State.Rules if s == state][0]
pos = r.indexIn(text)
if pos >= 0:
f = self.style.format(state)
# Uncomment for markup to be same size as title
#op = self.formats(preset="markup",
#base=self.formats(preset=state))
self.setFormat(r.pos(2), r.cap(2).length(), f)
self.setFormat(r.pos(1), r.cap(1).length(), op)
self.setFormat(r.pos(3), r.cap(3).length(), op)
# Areas: comment, code, raw tagged
for (begins, middle, ends) in [
(State.COMMENT_AREA_BEGINS, State.COMMENT_AREA, State.COMMENT_AREA_ENDS),
(State.CODE_AREA_BEGINS, State.CODE_AREA, State.CODE_AREA_ENDS),
(State.RAW_AREA_BEGINS, State.RAW_AREA, State.RAW_AREA_ENDS),
(State.TAGGED_AREA_BEGINS, State.TAGGED_AREA, State.TAGGED_AREA_ENDS),
]:
if state == middle:
self.setFormat(0, len(text), self.style.format(middle))
elif state in [begins, ends]:
self.setFormat(0, len(text), op)
# Inline formatting
if state not in [
#State.COMMENT_AREA,
#State.COMMENT_LINE,
State.RAW_AREA,
State.RAW_LINE,
State.CODE_AREA,
State.CODE_LINE,
State.TAGGED_AREA,
State.TAGGED_LINE,
State.SETTINGS_LINE,
State.HORIZONTAL_LINE,
] and state not in State.TITLES:
formatArray = textToFormatArray(text)
# InDocRules
for (r, c) in self.inDocRules:
i = re.finditer(r.decode('utf8'), text, re.UNICODE)
for m in i:
f = self.format(m.start())
l = m.end() - m.start()
if "," in c:
c1, c2 = c.split(",")
self.setFormat(m.start(), l,
self.style.makeFormat(color=c1, bgcolor=c2, base=f))
else:
self.setFormat(m.start(), l,
self.style.makeFormat(color=c, base=f))
# Links
if state not in [State.COMMENT_LINE, State.COMMENT_AREA]:
r = QRegExp(r'\[(\[[^\]]*\])?[^\]]*\s*([^\s]+)\]')
r.setMinimal(False)
pos = r.indexIn(text)
links = []
while pos >= 0:
#TODO: The text should not be formatted if [**not bold**]
#if max([k[pos] for k in formatArray]) == 0 or 1 == 1:
self.setFormat(pos, 1,
self.style.format(State.MARKUP))
self.setFormat(pos + 1, r.cap(0).length() - 1,
self.style.format(State.LINKS))
self.setFormat(pos + r.cap(0).length() - 1, 1,
self.style.format(State.MARKUP))
if r.pos(2) > 0:
_f = QTextCharFormat(self.style.format(State.LINKS))
_f.setForeground(QBrush(_f.foreground()
.color().lighter()))
_f.setFontUnderline(True)
self.setFormat(r.pos(2), r.cap(2).length(), _f)
links.append([pos, r.cap(0).length()]) # To remember for the next highlighter (single links)
pos = r.indexIn(text, pos + 1)
# Links like www.theologeek.ch, http://www.fsf.org, ...
# FIXME: - "http://adresse et http://adresse" is detected also as italic
# - some error, like "http://adress.htm." also color the final "."
# - also: adresse@email.com, ftp://, www2, www3, etc.
# - But for now, does the job
r = QRegExp(r'http://[^\s]*|www\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+[^\s]*')
#r.setMinimal(True)
pos = r.indexIn(text)
while pos >= 0:
for k in links:
#print pos, k[0], k[1]
if pos > k[0] and pos < k[0] + k[1]: # already highlighted
break
else:
self.setFormat(pos, r.cap(0).length(), self.style.format(State.LINKS))
pos = r.indexIn(text, pos + 1)
# Bold, Italic, Underline, Code, Tagged, Strikeout
for i in range(len(text)):
f = self.format(i)
beautifiers = [k[i] for k in formatArray]
self.setFormat(i, 1, self.style.beautifyFormat(f, beautifiers))
# Macro words
for r in [r'(%%)\b\w+\b', r'(%%)\b\w+\b\(.+\)']:
r = QRegExp(r)
r.setMinimal(True)
pos = r.indexIn(text)
while pos >= 0:
if max([k[pos] for k in formatArray]) == 0:
self.setFormat(pos, r.cap(0).length(),
self.style.format(State.MACRO))
pos = r.indexIn(text, pos + 1)
# Highlighted word (for search)
if self.editor.highlightWord:
if self.editor.highligtCS and self.editor.highlightWord in text or \
not self.editor.highlightCs and self.editor.highlightWord.lower() in text.lower():
#if self.editor.highlightCS:
#s = self.editor.highlightWord
#else:
#s = self.editor.highlightWord.toLower()
#print(s)
p = text.indexOf(self.editor.highlightWord, cs=self.editor.highlightCS)
while p >= 0:
self.setFormat(p, len(self.editor.highlightWord),
self.style.makeFormat(preset="higlighted", base=self.format(p)))
p = text.indexOf(self.editor.highlightWord, p + 1, cs=self.editor.highlightCS)
### Highlight Selection
### TODO: way to slow, find another way.
##sel = self.editor.textCursor().selectedText()
##if len(sel) > 5: self.keywordRules.append((QRegExp(sel), "selected"))
## Do keyword formatting
#for expression, style in self.keywordRules:
#expression.setMinimal( True )
#index = expression.indexIn(text, 0)
## There might be more than one on the same line
#while index >= 0:
#length = expression.cap(0).length()
#f = self.formats(preset=style, base=self.formats(index))
#self.setFormat(index, length, f)
#index = expression.indexIn(text, index + length)
# Spell checking
# Based on http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/
WORDS = u'(?iu)[\w\']+'
if state not in [State.SETTINGS_LINE]:
if self.editor.spellcheck:
for word_object in re.finditer(WORDS, text):
if not self.editor.dict.check(word_object.group()):
format = self.format(word_object.start())
format.setUnderlineColor(Qt.red)
format.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
self.setFormat(word_object.start(),
word_object.end() - word_object.start(), format)
# If a title was changed, we emit the corresponding signal
if oldState in State.TITLES or \
self.currentBlockState() in State.TITLES:
self.editor.structureChanged.emit()
#FIXME: si du texte est supprimé et qu'il y a un titre dedans,
# cela n'est pas détecté et le signal pas émis.
def identifyBlock(self, block):
"""Identifies what block type it is, and set userState and userData
accordingly."""
text = block.text()
data = blockUserData.getUserData(block)
# Header Lines
if block.blockNumber() == 0:
block.setUserState(State.HEADER_LINE)
return
elif block.blockNumber() in [1, 2] and \
self.document().findBlockByNumber(0).text():
block.setUserState(State.HEADER_LINE)
return
state = 0
inList = False
blankLinesBefore = 0
#if text.contains(QRegExp(r'^\s*[-+:] [^ ].*[^-+]{1}\s*$')):
if QRegExp(r'^\s*[-+:] [^ ].*[^-+]{1}\s*$').indexIn(text) <> -1:
state = State.LIST_BEGINS
# List stuff
if self.isList(block.previous()) or state == State.LIST_BEGINS:
inList = True
# listLevel and leadingSpaces
#FIXME: not behaving exactly correctly...
lastData = blockUserData.getUserData(block.previous())
if state == State.LIST_BEGINS:
leadingSpaces = QRegExp(r'[-+:]').indexIn(text, 0)
data.setLeadingSpaces(leadingSpaces)
data.setListSymbol(text[leadingSpaces])
if self.isList(block.previous()):
# The last block was also a list.
# We need to check if this is the same level, or a sublist
if leadingSpaces > lastData.leadingSpaces():
# This is a sublevel list
data.setListLevel(lastData.listLevel() + 1)
else:
# This is same level
data.setListLevel(lastData.listLevel())
else:
data.setListLevel(1)
else:
data.setListLevel(lastData.listLevel())
data.setLeadingSpaces(lastData.leadingSpaces())
data.setListSymbol(lastData.listSymbol())
# Blank lines before (two = end of list)
blankLinesBefore = self.getBlankLines(block.previous())
if not QRegExp(r'^\s*$').indexIn(block.previous().text()) <> -1 and \
not blockUserData.getUserState(block.previous()) in [State.COMMENT_LINE,
State.COMMENT_AREA, State.COMMENT_AREA_BEGINS,
State.COMMENT_AREA_ENDS]:
blankLinesBefore = 0
elif not blockUserData.getUserState(block.previous()) in \
[State.COMMENT_LINE, State.COMMENT_AREA,
State.COMMENT_AREA_BEGINS, State.COMMENT_AREA_ENDS]:
blankLinesBefore += 1
if blankLinesBefore == 2:
# End of list.
blankLinesBefore = 0
inList = False
if inList and QRegExp(r'^\s*$').indexIn(text) <> -1:
state = State.LIST_EMPTY
# Areas
for (begins, middle, ends, marker) in [
(State.COMMENT_AREA_BEGINS, State.COMMENT_AREA, State.COMMENT_AREA_ENDS, "^%%%\s*$"),
(State.CODE_AREA_BEGINS, State.CODE_AREA, State.CODE_AREA_ENDS, "^```\s*$"),
(State.RAW_AREA_BEGINS, State.RAW_AREA, State.RAW_AREA_ENDS, "^\"\"\"\s*$"),
(State.TAGGED_AREA_BEGINS, State.TAGGED_AREA, State.TAGGED_AREA_ENDS, '^\'\'\'\s*$'),
]:
if QRegExp(marker).indexIn(text) <> -1:
if blockUserData.getUserState(block.previous()) in [begins, middle]:
state = ends
break
else:
state = begins
break
if blockUserData.getUserState(block.previous()) in [middle, begins]:
state = middle
break
# Patterns (for lines)
if not state:
for (pattern, lineState) in State.Rules:
pos = pattern.indexIn(text)
if pos >= 0:
state = lineState
break
if state in [State.BLOCKQUOTE_LINE, State.LIST_ENDS]:
#FIXME: doesn't work exactly. Closes only the current level, not
#FIXME: the whole list.
inList = False
if inList and not state == State.LIST_BEGINS:
state += 100
if blankLinesBefore:
state += 100
block.setUserState(state)
block.setUserData(data)
def formatBlock(self, block):
"""
Formats the block according to its state.
"""
#TODO: Use QTextDocument format presets, and QTextBlock's
#TODO: blockFormatIndex. And move that in t2tHighlighterStyle.
state = block.userState()
blockFormat = QTextBlockFormat()
if state in [State.BLOCKQUOTE_LINE,
State.HEADER_LINE] + State.LIST:
blockFormat = self.style.formatBlock(block, state)
QTextCursor(block).setBlockFormat(blockFormat)
def getBlankLines(self, block):
"Returns if there is a blank line before in the list."
state = block.userState()
if state >= 200:
return 1
else:
return 0
def isList(self, block):
"Returns TRUE if the block is in a list."
if block.userState() == State.LIST_BEGINS or\
block.userState() >= 100:
return True
def setStyle(self, style):
if style in t2tHighlighterStyle.validStyles:
self.style = t2tHighlighterStyle(self.editor, style)
else:
self.style = None
self.rehighlight()
def setFontPointSize(self, size):
self.defaultFontPointSize = size
self.style = t2tHighlighterStyle(self.editor, self.style.name)
self.rehighlight()
def parseInDocRules(self):
oldRules = self.inDocRules
self.inDocRules = []
t = self.thisDocument.toPlainText()
# Get all conf files
confs = []
lines = t.split("\n")
for l in lines:
r = QRegExp(r'^%!includeconf:\s*([^\s]*)\s*')
if r.indexIn(l) != -1:
confs.append(r.cap(1))
# Try to load conf files
for c in confs:
try:
import codecs
f = self.editor.fileWidget.file
d = QDir.cleanPath(QFileInfo(f).absoluteDir().absolutePath()+"/"+c)
file = codecs.open(d, 'r', "utf-8")
except:
print("Error: cannot open {}.".format(c))
continue
# We add the content to the current lines of the current document
lines += file.readlines() #lines.extend(file.readlines())
#b = self.thisDocument.firstBlock()
lastColor = ""
#while b.isValid():
for l in lines:
text = l #b.text()
r = QRegExp(ur'^%!p[or][se]t?proc[^\s]*\s*:\s*(\'[^\']*\'|\"[^\"]*\")\s*(\'[^\']*\'|\"[^\"]*\")')
if r.indexIn(text) != -1:
rule = r.cap(1)[1:-1]
# Check if there was a color-comment above that post/preproc bloc
if lastColor:
self.inDocRules.append((str(rule), lastColor))
# Check if previous block is a comment like it should
else:
previousText = lines[lines.indexOf(l)-1] #b.previous().text()
r = QRegExp(r'^%.*\s\((.*)\)')
if r.indexIn(previousText) != -1:
lastColor = r.cap(1)
self.inDocRules.append((str(rule), lastColor))
else:
lastColor = ""
#b = b.next()
if oldRules != self.inDocRules:
#Rules have changed, we need to rehighlight
#print("Rules have changed.", len(self.inDocRules))
#self.rehighlight() # Doesn't work (seg fault), why?
pass
#b = self.thisDocument.firstBlock()
#while b.isValid():
#for (r, c) in self.inDocRules:
#r = QRegExp(r)
#pos = r.indexIn(b.text())
#if pos >= 0:
#print("rehighlighting:", b.text())
#self.rehighlightBlock(b)
#break
#b = b.next()

View file

@ -0,0 +1,245 @@
#!/usr/bin/python
# -*- coding: utf8 -*-
from qt import *
from ui.editors.t2tFunctions import *
from ui.editors.blockUserData import blockUserData
#TODO: creates a general way to generate styles (and edit/import/export)
class t2tHighlighterStyle ():
"""Style for the Syntax highlighter for the Txt2Tags language.
"""
validStyles = ["Default", "Monospace"]
def __init__(self, editor, name="Default"):
self.editor = editor
self.name = name
# Defaults
self.defaultFontPointSize = self.editor.defaultFontPointSize
self.defaultFontFamily = ""
self.tabStopWidth = 40
self.setupEditor()
if self.name == "Default":
self.initDefaults()
#Temporary other theme
elif self.name == "Monospace":
self.defaultFontFamily = "Monospace"
self.initDefaults()
for i in self.styles:
f = self.styles[i]
f.setFontFixedPitch(True)
f.setFontFamily(self.defaultFontFamily)
f.setFontPointSize(self.defaultFontPointSize)
self.styles[i] = f
def setupEditor(self):
self.editor.setTabStopWidth(self.tabStopWidth)
def initDefaults(self):
self.styles = {}
for i in [State.CODE_AREA,
State.CODE_LINE,
State.COMMENT_AREA,
State.COMMENT_LINE,
State.SETTINGS_LINE,
State.BLOCKQUOTE_LINE,
State.RAW_AREA,
State.RAW_LINE,
State.TAGGED_AREA,
State.TAGGED_LINE,
State.TITLE_1,
State.TITLE_2,
State.TITLE_3,
State.TITLE_4,
State.TITLE_5,
State.NUMBERED_TITLE_1,
State.NUMBERED_TITLE_2,
State.NUMBERED_TITLE_3,
State.NUMBERED_TITLE_4,
State.NUMBERED_TITLE_5,
State.TABLE_HEADER,
State.TABLE_LINE,
State.HORIZONTAL_LINE,
State.MARKUP,
State.LIST_BULLET,
State.LIST_BULLET_ENDS,
State.LINKS,
State.MACRO,
State.DEFAULT,
State.HEADER_LINE]:
self.styles[i] = self.makeFormat(preset=i)
def format(self, state):
return self.styles[state]
def beautifyFormat(self, base, beautifiers):
"Apply beautifiers given in beautifiers array to format"
if max(beautifiers) == 2:
return self.makeFormat(preset=State.MARKUP, base=base)
else:
if beautifiers[0]: # bold
base.setFontWeight(QFont.Bold)
if beautifiers[1]: # italic
base.setFontItalic(True)
if beautifiers[2]: # underline
base.setFontUnderline(True)
if beautifiers[3]: # strikeout
base.setFontStrikeOut(True)
if beautifiers[4]: # code
base = self.makeFormat(base=base, preset=State.CODE_LINE)
if beautifiers[5]: # tagged
base = self.makeFormat(base=base, preset=State.TAGGED_LINE)
return base
def formatBlock(self, block, state):
"Apply transformation to given block."
blockFormat = QTextBlockFormat()
if state == State.BLOCKQUOTE_LINE:
# Number of tabs
n = block.text().indexOf(QRegExp(r'[^\t]'), 0)
blockFormat.setIndent(0)
blockFormat.setTextIndent(-self.tabStopWidth * n)
blockFormat.setLeftMargin(self.tabStopWidth * n)
#blockFormat.setRightMargin(self.editor.contentsRect().width()
# - self.editor.lineNumberAreaWidth()
# - fm.width("X") * self.editor.LimitLine
#+ self.editor.tabStopWidth())
blockFormat.setAlignment(Qt.AlignJustify)
if self.name == "Default" :
blockFormat.setTopMargin(5)
blockFormat.setBottomMargin(5)
elif state == State.HEADER_LINE:
blockFormat.setBackground(QColor("#EEEEEE"))
elif state in State.LIST:
data = blockUserData.getUserData(block)
if str(data.listSymbol()) in "+-":
blockFormat.setBackground(QColor("#EEFFEE"))
else:
blockFormat.setBackground(QColor("#EEEEFA"))
n = blockUserData.getUserData(block).leadingSpaces() + 1
f = QFontMetrics(QFont(self.defaultFontFamily,
self.defaultFontPointSize))
fm = f.width(" " * n +
blockUserData.getUserData(block).listSymbol())
blockFormat.setTextIndent(-fm)
blockFormat.setLeftMargin(fm)
if blockUserData.getUserState(block) == State.LIST_BEGINS and\
self.name == "Default":
blockFormat.setTopMargin(5)
return blockFormat
def makeFormat(self, color='', style='', size='', base='', fixedPitch='',
preset='', title_level='', bgcolor=''):
"""
Returns a QTextCharFormat with the given attributes, using presets.
"""
_color = QColor()
_format = QTextCharFormat()
size = self.defaultFontPointSize
_format.setFontFamily(self.defaultFontFamily)
# Base
if base: _format = base
# Presets
if preset in [State.CODE_AREA, State.CODE_LINE, "code"]:
style = "bold"
color = "black"
fixedPitch = True
_format.setBackground(QColor("#EEEEEE"))
if preset in [State.COMMENT_AREA, State.COMMENT_LINE, "comment"]:
style = "italic"
color = "darkGreen"
if preset in [State.SETTINGS_LINE, "setting", State.MACRO]:
#style = "italic"
color = "magenta"
if preset in [State.BLOCKQUOTE_LINE]:
color = "red"
if preset in [State.HEADER_LINE]:
size = size * 2
print size
if preset in [State.RAW_AREA, State.RAW_LINE, "raw"]:
color = "blue"
if preset in [State.TAGGED_AREA, State.TAGGED_LINE, "tagged"]:
color = "purple"
if preset in State.TITLES:
style = "bold"
color = "darkRed" if State.titleLevel(preset) % 2 == 1 else "blue"
size = (self.defaultFontPointSize
+ 11 - 2 * State.titleLevel(preset))
if preset == State.TABLE_HEADER:
style = "bold"
color = "darkMagenta"
if preset == State.TABLE_LINE:
color = "darkMagenta"
if preset == State.LIST_BULLET:
color = "red"
style = "bold"
fixedPitch = True
if preset == State.LIST_BULLET_ENDS:
color = "darkGray"
fixedPitch = True
if preset in [State.MARKUP, "markup"]:
color = "darkGray"
if preset in [State.HORIZONTAL_LINE]:
color = "cyan"
fixedPitch = True
if preset == State.LINKS:
color="blue"
#style="underline"
if preset == "selected":
_format.setBackground(QColor("yellow"))
if preset == "higlighted":
bgcolor = "yellow"
if preset == State.DEFAULT:
size = self.defaultFontPointSize
_format.setFontFamily(self.defaultFontFamily)
# Manual formatting
if color:
_color.setNamedColor(color)
_format.setForeground(_color)
if bgcolor:
_color.setNamedColor(bgcolor)
_format.setBackground(_color)
if 'bold' in style:
_format.setFontWeight(QFont.Bold)
if 'italic' in style:
_format.setFontItalic(True)
if 'strike' in style:
_format.setFontStrikeOut(True)
if 'underline' in style:
_format.setFontUnderline(True)
if size:
_format.setFontPointSize(size)
if fixedPitch:
_format.setFontFixedPitch(True)
return _format

View file

@ -1103,7 +1103,7 @@ class Ui_MainWindow(object):
self.menubar.addAction(self.menu_Aide.menuAction())
self.retranslateUi(MainWindow)
self.tabMain.setCurrentIndex(5)
self.tabMain.setCurrentIndex(6)
self.tabSummary.setCurrentIndex(0)
self.tabPersos.setCurrentIndex(0)
self.tabPlot.setCurrentIndex(0)
@ -1273,9 +1273,9 @@ class Ui_MainWindow(object):
self.actShowHelp.setText(_translate("MainWindow", "Afficher les &bulles d\'aide"))
self.actShowHelp.setShortcut(_translate("MainWindow", "Ctrl+Shift+B"))
from ui.collapsibleGroupBox2 import collapsibleGroupBox2
from ui.cmbOutlinePersoChoser import cmbOutlinePersoChoser
from ui.sldImportance import sldImportance
from ui.cmbOutlineStatusChoser import cmbOutlineStatusChoser
from ui.cmbOutlinePersoChoser import cmbOutlinePersoChoser
from ui.collapsibleGroupBox2 import collapsibleGroupBox2
from ui.chkOutlineCompile import chkOutlineCompile
from ui.editorWidget import editorWidget
from ui.sldImportance import sldImportance
from ui.editors.editorWidget import editorWidget

View file

@ -18,7 +18,7 @@
<item>
<widget class="QTabWidget" name="tabMain">
<property name="currentIndex">
<number>5</number>
<number>6</number>
</property>
<property name="documentMode">
<bool>true</bool>
@ -2185,7 +2185,7 @@
<customwidget>
<class>editorWidget</class>
<extends>QWidget</extends>
<header>ui.editorWidget.h</header>
<header>ui.editors.editorWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>

View file

@ -1,9 +1,9 @@
<?xml version='1.0' encoding='UTF-8'?>
<outlineItem title="root" type="folder" compile="2" wordCount="355" setGoal="112">
<outlineItem title="root" type="folder" compile="2" wordCount="389" setGoal="112">
<outlineItem title="Nouveau" type="folder" compile="2" wordCount="15">
<outlineItem title="Nouveau" type="scene" compile="2" text="return QTextEdit.resizeEvent(self, e) ad ad ad ad adaasd ad adsdasd ad e drset" wordCount="15"/>
</outlineItem>
<outlineItem title="Parent" type="folder" status="TODO" compile="2" wordCount="123">
<outlineItem title="Parent" type="folder" status="TODO" compile="2" wordCount="127">
<outlineItem title="Nouveau" type="folder" summarySentance="asd asd asd " status="First draft" compile="2" wordCount="99">
<outlineItem title="A" type="scene" compile="2" text="Du texteDu texteDu text ad ad ad ad " wordCount="8" setGoal="10"/>
<outlineItem title="B" type="scene" compile="2" setGoal="3"/>
@ -14,12 +14,12 @@
</outlineItem>
</outlineItem>
<outlineItem title="MOIMOIMOI" type="scene" POV="1" compile="2" text="ASDASd ASD ASDASd ASD ASDASd ASD " wordCount="6" setGoal="10"/>
<outlineItem title="Nouveau" type="scene" POV="1" compile="2" text="ASDASd ASD ASDASd ASD " wordCount="4" setGoal="10"/>
<outlineItem title="Nouveau" type="scene" POV="1" compile="2" text="ASDASd ASD ASDASd ASD asd sss ad ad " wordCount="8" setGoal="10"/>
<outlineItem title="Nouveau" type="scene" compile="2" text="ASDASd ASD ASDASd ASD " wordCount="4" setGoal="10"/>
<outlineItem title="B" type="scene" compile="2" text="asd asd asd asd asd asd asd asd asd " wordCount="10" setGoal="10"/>
<outlineItem title="Nouveau" type="folder" compile="2"/>
</outlineItem>
<outlineItem title="MOIMOIMOI" type="scene" status="Second draft" compile="2" setGoal="2500"/>
<outlineItem title="MOIMOIMOI" type="scene" status="Second draft" compile="2" text="&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;&#10;&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;&#10;p, li { white-space: pre-wrap; }&#10;&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Oxygen-Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;&#10;&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; -qt-user-state:37;&quot;&gt;Text **gras**&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;" wordCount="30" setGoal="2500"/>
<outlineItem title="A" type="scene" compile="2" setGoal="146"/>
<outlineItem title="Nouveau A" type="folder" compile="2" wordCount="217">
<outlineItem title="Nouveau" type="scene" compile="2" text="ASDASd ASD ASDASd ASD " wordCount="4"/>