diff --git a/manuskript/ui/editors/MDFunctions.py b/manuskript/ui/editors/MDFunctions.py new file mode 100644 index 0000000..0fbd8b8 --- /dev/null +++ b/manuskript/ui/editors/MDFunctions.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re + +from PyQt5.QtCore import QRegExp +from PyQt5.QtGui import QTextCursor + + +def MDFormatSelection(editor, style): + """ + Formats the current selection of ``editor`` in the format given by ``style``, + style being: + 0: bold + 1: italic + 2: code + """ + print("Formatting:", style, " (Unimplemented yet !)") + # FIXME \ No newline at end of file diff --git a/manuskript/ui/editors/t2tFunctions.py b/manuskript/ui/editors/t2tFunctions.py deleted file mode 100644 index f226d5d..0000000 --- a/manuskript/ui/editors/t2tFunctions.py +++ /dev/null @@ -1,376 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import re - -from PyQt5.QtCore import QRegExp -from PyQt5.QtGui import QTextCursor - - -def t2tFormatSelection(editor, style): - """ - Formats the current selection of ``editor`` in the format given by ``style``, - style being: - 0: bold - 1: italic - 2: underline - 3: strike - 4: code - 5: tagged - """ - print("Formatting:", style) - formatChar = "*/_-`'"[style] - - # FIXME: doesn't work if selection spans over several blocks. - - cursor = editor.textCursor() - cursor.beginEditBlock() - - if cursor.hasSelection(): - pass - else: - # If no selection, selects the word in which the cursor is now - cursor.movePosition(QTextCursor.StartOfWord, - QTextCursor.MoveAnchor) - cursor.movePosition(QTextCursor.EndOfWord, - QTextCursor.KeepAnchor) - - if not cursor.hasSelection(): - # No selection means we are outside a word, - # so we insert markup and put cursor in the middle - cursor.insertText(formatChar * 4) - cursor.setPosition(cursor.position() - 2) - cursor.endEditBlock() - editor.setTextCursor(cursor) - # And we are done - return - - # Get start and end of selection - start = cursor.selectionStart() - cursor.block().position() - end = cursor.selectionEnd() - cursor.block().position() - if start > end: - start, end = end, start - - # Whole block - text = cursor.block().text() - - # Adjusts selection to exclude the markup - while text[start:start + 1] == formatChar: - start += 1 - while text[end - 1:end] == formatChar: - end -= 1 - - # Get the text without formatting, and the array of format - fText, fArray = textToFormatArrayNoMarkup(text) - # Get the translated start and end of selection in the unformated text - tStart, tEnd = translateSelectionToUnformattedText(text, start, end) - - # We want only the array that contains the propper formatting - propperArray = fArray[style] - - if 0 in propperArray[tStart:tEnd]: - # have some unformated text in the selection, so we format the - # whole selection - propperArray = propperArray[:tStart] + [1] * \ - (tEnd - tStart) + propperArray[tEnd:] - else: - # The whole selection is already formatted, so we remove the - # formatting - propperArray = propperArray[:tStart] + [0] * \ - (tEnd - tStart) + propperArray[tEnd:] - - fArray = fArray[0:style] + [propperArray] + fArray[style + 1:] - - text = reformatText(fText, fArray) - - # Replaces the whole block - cursor.movePosition(QTextCursor.StartOfBlock) - cursor.movePosition(QTextCursor.EndOfBlock, - QTextCursor.KeepAnchor) - cursor.insertText(text) - - cursor.setPosition(end + cursor.block().position()) - - cursor.endEditBlock() - - editor.setTextCursor(cursor) - - -def t2tClearFormat(editor): - """Clears format on ``editor``'s current selection.""" - - cursor = editor.textCursor() - cursor.beginEditBlock() - - text = cursor.selectedText() - t, a = textToFormatArrayNoMarkup(text) - - cursor.insertText(t) - cursor.endEditBlock() - editor.setTextCursor(cursor) - - -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, m in enumerate(markupArray): - # m = markupArray[k] - _open = False # Are we in an _openned markup - d = 0 - alreadySeen = [] - for i, t in enumerate(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, m2 in enumerate(markupArray): - # The other array still have the same length - if j > k: - # Insert 2 for bold, 3 for italic, etc. - m2.insert(i + d, k + 2) - m2.insert(i + d, k + 2) - alreadySeen = [] - d += 2 - rText += t - if _open: - rText += markup[k] - for j, m2 in enumerate(markupArray): - # The other array still have the same length - if j > k: - # Insert 2 for bold, 3 for italic, etc. - m2.insert(i + d, k + 2) - m2.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") - text = re.sub(r'(' + markup * 2 + ')(\s+?)(.+?)(' + markup * 2 + ')', - "\\2\\1\\3\\4", - text) - # r = QRegExp(r'(' + markup * 2 + ')(.+)(\s+)(' + markup * 2 + ')') - # r.setMinimal(True) - # text.replace(r, "\\1\\2\\4\\3") - text = re.sub(r'(' + markup * 2 + ')(.+?)(\s+?)(' + markup * 2 + ')', - "\\1\\2\\4\\3", - text) - - 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] + list(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) diff --git a/manuskript/ui/editors/t2tHighlighter.py b/manuskript/ui/editors/t2tHighlighter.py deleted file mode 100644 index c5f52e8..0000000 --- a/manuskript/ui/editors/t2tHighlighter.py +++ /dev/null @@ -1,547 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf8 -*- - - -# 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. -import re - -from PyQt5.QtCore import QRegExp, QDir, QFileInfo -from PyQt5.QtGui import QTextBlockFormat, QTextCursor, QTextCharFormat, QBrush - -from manuskript.ui.editors.basicHighlighter import basicHighlighter -from manuskript.ui.editors.blockUserData import blockUserData -from manuskript.ui.editors.t2tFunctions import State, textToFormatArray -from manuskript.ui.editors.t2tHighlighterStyle import t2tHighlighterStyle - - -class t2tHighlighter(basicHighlighter): - """Syntax highlighter for the Txt2Tags language. - """ - - def __init__(self, editor, style="Default"): - basicHighlighter.__init__(self, editor) - - # Stupid variable that fixes the loss of QTextBlockUserData. - self.thisDocument = editor.document() - - self.style = t2tHighlighterStyle(self.editor, self._defaultCharFormat, 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 setDefaultCharFormat(self, cf): - self._defaultCharFormat = cf - self.setStyle() - self.rehighlight() - - def highlightBlock(self, text): - """Apply syntax highlighting to the given block of text. - """ - basicHighlighter.highlightBlockBefore(self, text) - - # Check if syntax highlighting is enabled - if self.style is None: - default = QTextBlockFormat() - QTextCursor(self.currentBlock()).setBlockFormat(default) - print("t2tHighlighter.py: is style supposed to be None?") - 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, t in enumerate(text): - if t == "|": - 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), len(r.cap(2)), f) - self.setFormat(r.pos(1), len(r.cap(1)), op) - self.setFormat(r.pos(3), len(r.cap(3)), 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, len(r.cap(0)) - 1, - self.style.format(State.LINKS)) - self.setFormat(pos + len(r.cap(0)) - 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), len(r.cap(2)), _f) - - links.append([pos, len(r.cap(0))]) # 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 k[0] < pos < k[0] + k[1]: # already highlighted - break - else: - self.setFormat(pos, len(r.cap(0)), self.style.format(State.LINKS)) - - pos = r.indexIn(text, pos + 1) - - # Bold, Italic, Underline, Code, Tagged, Strikeout - for i, t in enumerate(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, len(r.cap(0)), - 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) - - basicHighlighter.highlightBlockAfter(self, text) - - 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 - # No header line here - # 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="Default"): - if style in t2tHighlighterStyle.validStyles: - self.style = t2tHighlighterStyle(self.editor, self._defaultCharFormat, 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(r'^%!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() diff --git a/manuskript/ui/editors/t2tHighlighterStyle.py b/manuskript/ui/editors/t2tHighlighterStyle.py deleted file mode 100644 index 318e217..0000000 --- a/manuskript/ui/editors/t2tHighlighterStyle.py +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf8 -*- - - -# TODO: creates a general way to generate styles (and edit/import/export) -from PyQt5.QtCore import QRegExp, Qt -from PyQt5.QtGui import QFont, QTextBlockFormat, QColor, QFontMetrics, QTextCharFormat -from PyQt5.QtWidgets import qApp - -from manuskript.ui.editors.blockUserData import blockUserData -from manuskript.ui.editors.t2tFunctions import State - - -class t2tHighlighterStyle(): - """Style for the Syntax highlighter for the Txt2Tags language. - """ - - validStyles = ["Default", "Monospace"] - - def __init__(self, editor, charFormat, name="Default"): - - self.editor = editor - self.name = name - self._defaultCharFormat = charFormat - - # Defaults - # self.defaultFontPointSize = self.editor.defaultFontPointSize - self.defaultFontFamily = qApp.font().family() - 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._defaultCharFormat.font().pointSize()) - 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._defaultCharFormat.font().pointSize())) - 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() - # _format.setFont(self.editor.font()) - # size = _format.fontPointSize() - _format = QTextCharFormat(self._defaultCharFormat) - - # 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 *= 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._defaultCharFormat.font().pointSize() - + 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 diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index 1ba1f9f..0c3e9d1 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -11,6 +11,7 @@ from manuskript.enums import Outline from manuskript.functions import AUC from manuskript.functions import toString from manuskript.models.outlineModel import outlineModel +from manuskript.ui.editors.MDFunctions import MDFormatSelection from manuskript.ui.editors.MMDHighlighter import MMDHighlighter from manuskript.ui.editors.basicHighlighter import basicHighlighter from manuskript.ui.editors.textFormat import textFormat @@ -456,16 +457,12 @@ class textEditView(QTextEdit): def applyFormat(self, _format): if self._textFormat == "md": - # FIXME - print("Not implemented yet.") - # Model: from t2tFunctions - # if self._textFormat == "t2t": - # if _format == "Bold": - # t2tFormatSelection(self, 0) - # elif _format == "Italic": - # t2tFormatSelection(self, 1) - # elif _format == "Underline": - # t2tFormatSelection(self, 2) - # elif _format == "Clear": - # t2tClearFormat(self) + if _format == "Bold": + MDFormatSelection(self, 0) + elif _format == "Italic": + MDFormatSelection(self, 1) + elif _format == "Code": + MDFormatSelection(self, 2) + elif _format == "Clear": + MDFormatSelection(self)