예제 #1
0
    def _colorizeThreadFunc (self, text, editor, enableSpellChecking):
        helper = TextEditorHelper()
        textlength = helper.calcByteLen (text)
        stylebytes = [0] * textlength

        if enableSpellChecking:
            for start, end in self._splitText (text):
                if not self._runColorizingEvent.is_set():
                    return
                editor.runSpellChecking (stylebytes, text, start, end)

        self._updateStyles (editor, text, None, stylebytes, 0, len (text))
예제 #2
0
    def _colorizeThreadFunc(self, text, editor, enableSpellChecking):
        helper = TextEditorHelper()
        textlength = helper.calcByteLen(text)
        stylebytes = [0] * textlength

        if enableSpellChecking:
            for start, end in self._splitText(text):
                if not self._runColorizingEvent.is_set():
                    return
                editor.runSpellChecking(stylebytes, text, start, end)

        self.updateStyles(editor, text, None, stylebytes, 0, len(text))
예제 #3
0
class WikiColorizer(object):
    def __init__(self, editor, colorizeSyntax, enableSpellChecking, runEvent):
        self._editor = editor
        self._helper = TextEditorHelper()
        self._enableSpellChecking = enableSpellChecking
        self._runEvent = runEvent

        self.text = TextFactory.make(None)
        self.bold = FontsFactory.makeBold(None).setParseAction(
            lambda s, l, t: None)
        self.italic = FontsFactory.makeItalic(None).setParseAction(
            lambda s, l, t: None)
        self.bold_italic = FontsFactory.makeBoldItalic(None).setParseAction(
            lambda s, l, t: None)
        self.underline = FontsFactory.makeUnderline(None).setParseAction(
            lambda s, l, t: None)
        self.heading = HeadingFactory.make(None).setParseAction(
            lambda s, l, t: None)
        self.command = CommandFactory.make(None).setParseAction(
            lambda s, l, t: None)
        self.link = LinkFactory.make(None).setParseAction(lambda s, l, t: None)
        self.url = UrlFactory.make(None).setParseAction(lambda s, l, t: None)
        self.linebreak = LineBreakFactory.make(None).setParseAction(
            lambda s, l, t: None)
        self.noformat = NoFormatFactory.make(None).setParseAction(
            lambda s, l, t: None)
        self.preformat = PreFormatFactory.make(None).setParseAction(
            lambda s, l, t: None)

        if colorizeSyntax:
            self.colorParser = (self.url | self.text | self.linebreak
                                | self.link | self.noformat | self.preformat
                                | self.command | self.bold_italic | self.bold
                                | self.italic | self.underline | self.heading)

            self.insideBlockParser = (self.url | self.text | self.linebreak
                                      | self.link | self.noformat
                                      | self.preformat | self.bold_italic
                                      | self.bold | self.italic
                                      | self.underline)
        else:
            self.colorParser = self.text
            self.insideBlockParser = self.text

    def colorize(self, fullText):
        textlength = self._helper.calcByteLen(fullText)
        stylelist = [0] * textlength
        self._colorizeText(fullText, fullText, 0, textlength, self.colorParser,
                           stylelist)

        return stylelist

    def _colorizeText(self, fullText, text, start, end, parser, stylelist):
        tokens = parser.scanString(text[start:end])

        for token in tokens:
            if not self._runEvent.is_set():
                break

            pos_start = token[1] + start
            pos_end = token[2] + start

            tokenname = token[0].getName()

            if (tokenname == "text" or tokenname == "noformat"
                    or tokenname == "preformat"):
                if self._enableSpellChecking:
                    self._editor.runSpellChecking(stylelist, fullText,
                                                  pos_start, pos_end)
                continue

            if tokenname == "linebreak":
                continue

            # Нас интересует позиция в байтах, а не в символах
            bytepos_start = self._helper.calcBytePos(text, pos_start)
            bytepos_end = self._helper.calcBytePos(text, pos_end)

            # Применим стиль
            if tokenname == "bold":
                self._helper.addStyle(stylelist, self._editor.STYLE_BOLD_ID,
                                      bytepos_start, bytepos_end)
                self._colorizeText(fullText, text,
                                   pos_start + len(BoldToken.start),
                                   pos_end - len(BoldToken.end),
                                   self.insideBlockParser, stylelist)

            elif tokenname == "italic":
                self._helper.addStyle(stylelist, self._editor.STYLE_ITALIC_ID,
                                      bytepos_start, bytepos_end)
                self._colorizeText(fullText, text,
                                   pos_start + len(ItalicToken.start),
                                   pos_end - len(ItalicToken.end),
                                   self.insideBlockParser, stylelist)

            elif tokenname == "bold_italic":
                self._helper.addStyle(stylelist,
                                      self._editor.STYLE_BOLD_ITALIC_ID,
                                      bytepos_start, bytepos_end)
                self._colorizeText(fullText, text,
                                   pos_start + len(BoldItalicToken.start),
                                   pos_end - len(BoldItalicToken.end),
                                   self.insideBlockParser, stylelist)

            elif tokenname == "underline":
                self._helper.addStyle(stylelist,
                                      self._editor.STYLE_UNDERLINE_ID,
                                      bytepos_start, bytepos_end)
                self._colorizeText(fullText, text,
                                   pos_start + len(UnderlineToken.start),
                                   pos_end - len(UnderlineToken.end),
                                   self.insideBlockParser, stylelist)

            elif tokenname == "heading":
                self._helper.setStyle(stylelist, self._editor.STYLE_HEADING_ID,
                                      bytepos_start, bytepos_end)
                if self._enableSpellChecking:
                    self._editor.runSpellChecking(stylelist, fullText,
                                                  pos_start, pos_end)

            elif tokenname == "command":
                self._helper.setStyle(stylelist, self._editor.STYLE_COMMAND_ID,
                                      bytepos_start, bytepos_end)

            elif tokenname == "link":
                self._helper.addStyle(stylelist, self._editor.STYLE_LINK_ID,
                                      bytepos_start, bytepos_end)
                self._linkSpellChecking(fullText, text, stylelist, pos_start,
                                        pos_end)

            elif tokenname == "url":
                self._helper.addStyle(stylelist, self._editor.STYLE_LINK_ID,
                                      bytepos_start, bytepos_end)

    def _linkSpellChecking(self, fullText, text, stylelist, pos_start,
                           pos_end):
        separator1 = u'->'
        separator2 = u'|'

        link = text[pos_start:pos_end]
        sep1_pos = link.find(separator1)
        if sep1_pos != -1:
            if self._enableSpellChecking:
                self._editor.runSpellChecking(stylelist, fullText, pos_start,
                                              pos_start + sep1_pos)
            return

        sep2_pos = link.find(separator2)
        if sep2_pos != -1:
            if self._enableSpellChecking:
                self._editor.runSpellChecking(
                    stylelist, fullText,
                    pos_start + sep2_pos + len(separator2), pos_end)
예제 #4
0
class TextEditorBase(wx.Panel):
    def __init__(self, parent):
        super(TextEditorBase, self).__init__(parent, style=0)
        self.textCtrl = StyledTextCtrl(self, -1)

        # Создание панели поиска и ее контроллера
        self._searchPanel = SearchReplacePanel(self)
        self._searchPanelController = SearchReplaceController(
            self._searchPanel, self)
        self._searchPanel.setController(self._searchPanelController)

        self._do_layout()

        self.__createCoders()
        self._helper = TextEditorHelper()
        self._bind()
        self._setDefaultSettings()

    def _bind(self):
        self.textCtrl.Bind(wx.EVT_KEY_DOWN, self.__onKeyDown)

    def _do_layout(self):
        mainSizer = wx.FlexGridSizer(rows=2, cols=0, vgap=0, hgap=0)
        mainSizer.AddGrowableRow(0)
        mainSizer.AddGrowableCol(0)

        mainSizer.Add(self.textCtrl, 0, wx.EXPAND, 0)
        mainSizer.Add(self._searchPanel, 0, wx.EXPAND, 0)
        self.SetSizer(mainSizer)

        self._searchPanel.Hide()
        self.Layout()

    def __createCoders(self):
        encoding = outwiker.core.system.getOS().inputEncoding
        self.mbcsEnc = codecs.getencoder(encoding)

    def __onKeyDown(self, event):
        key = event.GetKeyCode()

        if key == wx.WXK_ESCAPE:
            self._searchPanel.Close()

        event.Skip()

    def _setDefaultSettings(self):
        self.textCtrl.SetEndAtLastLine(False)
        self.textCtrl.StyleClearAll()
        self.textCtrl.SetWrapMode(wx.stc.STC_WRAP_WORD)
        self.textCtrl.SetWrapVisualFlags(wx.stc.STC_WRAPVISUALFLAG_END)
        self.textCtrl.SetTabWidth(4)
        self._setDefaultHotKeys()

    def _setDefaultHotKeys(self):
        self.textCtrl.CmdKeyClearAll()

        # Clear Cmd keys for Ubuntu
        for key in list(range(ord('A'),
                              ord('Z') + 1)) + list(
                                  range(ord('0'),
                                        ord('9') + 1)):
            self.textCtrl.CmdKeyClear(
                key, wx.stc.STC_SCMOD_ALT | wx.stc.STC_SCMOD_CTRL)
            self.textCtrl.CmdKeyClear(
                key, wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT
                | wx.stc.STC_SCMOD_CTRL)

        self.textCtrl.CmdKeyClear(wx.stc.STC_KEY_UP, wx.stc.STC_SCMOD_CTRL)
        self.textCtrl.CmdKeyClear(wx.stc.STC_KEY_DOWN, wx.stc.STC_SCMOD_CTRL)

        # Code from Wikidpad sources
        # Default mapping based on Scintilla's "KeyMap.cxx" file
        defaultHotKeys = (
            (wx.stc.STC_KEY_DOWN, wx.stc.STC_SCMOD_NORM,
             wx.stc.STC_CMD_LINEDOWN),
            (wx.stc.STC_KEY_UP, wx.stc.STC_SCMOD_NORM, wx.stc.STC_CMD_LINEUP),
            # (wx.stc.STC_KEY_DOWN,        wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_LINESCROLLDOWN),
            # (wx.stc.STC_KEY_UP,          wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_LINESCROLLUP),
            (wx.stc.STC_KEY_UP, wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_LINEUPEXTEND),
            (wx.stc.STC_KEY_DOWN, wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_LINEDOWNEXTEND),
            (ord('['), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_PARAUP),
            (ord('['), wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,
             wx.stc.STC_CMD_PARAUPEXTEND),
            (ord(']'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_PARADOWN),
            (ord(']'), wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,
             wx.stc.STC_CMD_PARADOWNEXTEND),
            (wx.stc.STC_KEY_LEFT, wx.stc.STC_SCMOD_NORM,
             wx.stc.STC_CMD_CHARLEFT),
            (wx.stc.STC_KEY_LEFT, wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_CHARLEFTEXTEND),
            # (wx.stc.STC_KEY_LEFT,        wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDLEFT),
            # (wx.stc.STC_KEY_LEFT,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDLEFTEXTEND),
            (wx.stc.STC_KEY_RIGHT, wx.stc.STC_SCMOD_NORM,
             wx.stc.STC_CMD_CHARRIGHT),
            (wx.stc.STC_KEY_RIGHT, wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_CHARRIGHTEXTEND),
            # (wx.stc.STC_KEY_RIGHT,        wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDRIGHT),
            # (wx.stc.STC_KEY_RIGHT,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDRIGHTEXTEND),
            (ord('/'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_WORDPARTLEFT),
            (ord('/'), wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,
             wx.stc.STC_CMD_WORDPARTLEFTEXTEND),
            (ord('\\'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_WORDPARTRIGHT),
            (ord('\\'), wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,
             wx.stc.STC_CMD_WORDPARTRIGHTEXTEND),
            (wx.stc.STC_KEY_HOME, wx.stc.STC_SCMOD_NORM,
             wx.stc.STC_CMD_VCHOME),
            (wx.stc.STC_KEY_HOME, wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_VCHOMEEXTEND),
            (wx.stc.STC_KEY_HOME, wx.stc.STC_SCMOD_CTRL,
             wx.stc.STC_CMD_DOCUMENTSTART),
            (wx.stc.STC_KEY_HOME,
             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,
             wx.stc.STC_CMD_DOCUMENTSTARTEXTEND),
            (wx.stc.STC_KEY_HOME, wx.stc.STC_SCMOD_ALT,
             wx.stc.STC_CMD_HOMEDISPLAY),
            (wx.stc.STC_KEY_END, wx.stc.STC_SCMOD_NORM,
             wx.stc.STC_CMD_LINEEND),
            (wx.stc.STC_KEY_END, wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_LINEENDEXTEND),
            (wx.stc.STC_KEY_END, wx.stc.STC_SCMOD_CTRL,
             wx.stc.STC_CMD_DOCUMENTEND),
            (wx.stc.STC_KEY_END,
             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,
             wx.stc.STC_CMD_DOCUMENTENDEXTEND),
            (wx.stc.STC_KEY_END, wx.stc.STC_SCMOD_ALT,
             wx.stc.STC_CMD_LINEENDDISPLAY),
            (wx.stc.STC_KEY_PRIOR, wx.stc.STC_SCMOD_NORM,
             wx.stc.STC_CMD_PAGEUP),
            (wx.stc.STC_KEY_PRIOR, wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_PAGEUPEXTEND),
            (wx.stc.STC_KEY_NEXT, wx.stc.STC_SCMOD_NORM,
             wx.stc.STC_CMD_PAGEDOWN),
            (wx.stc.STC_KEY_NEXT, wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_PAGEDOWNEXTEND),
            (wx.stc.STC_KEY_DELETE, wx.stc.STC_SCMOD_NORM,
             wx.stc.STC_CMD_CLEAR),
            (wx.stc.STC_KEY_INSERT, wx.stc.STC_SCMOD_NORM,
             wx.stc.STC_CMD_EDITTOGGLEOVERTYPE),
            (wx.stc.STC_KEY_ESCAPE, wx.stc.STC_SCMOD_NORM,
             wx.stc.STC_CMD_CANCEL),
            (wx.stc.STC_KEY_BACK, wx.stc.STC_SCMOD_NORM,
             wx.stc.STC_CMD_DELETEBACK),
            (wx.stc.STC_KEY_BACK, wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_DELETEBACK),
            (wx.stc.STC_KEY_BACK, wx.stc.STC_SCMOD_ALT, wx.stc.STC_CMD_UNDO),
            (ord('Z'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_UNDO),
            (ord('Y'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_REDO),
            (ord('A'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_SELECTALL),
            (wx.stc.STC_KEY_INSERT,
             wx.stc.STC_SCMOD_CTRL | wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_COPY),
            (wx.stc.STC_KEY_INSERT, wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_PASTE),
            (ord('C'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_COPY),
            (ord('X'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_CUT),
            (ord('V'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_PASTE),
            (wx.stc.STC_KEY_TAB, wx.stc.STC_SCMOD_NORM, wx.stc.STC_CMD_TAB),
            (wx.stc.STC_KEY_TAB, wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_BACKTAB),
            (wx.stc.STC_KEY_RETURN, wx.stc.STC_SCMOD_NORM,
             wx.stc.STC_CMD_NEWLINE),
            (wx.stc.STC_KEY_RETURN, wx.stc.STC_SCMOD_SHIFT,
             wx.stc.STC_CMD_NEWLINE),
            (wx.stc.STC_KEY_ADD, wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_ZOOMIN),
            (wx.stc.STC_KEY_SUBTRACT, wx.stc.STC_SCMOD_CTRL,
             wx.stc.STC_CMD_ZOOMOUT),
            (wx.stc.STC_KEY_DOWN,
             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,
             wx.stc.STC_CMD_LINEDOWNRECTEXTEND),
            (wx.stc.STC_KEY_UP, wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,
             wx.stc.STC_CMD_LINEUPRECTEXTEND),
            (wx.stc.STC_KEY_LEFT,
             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,
             wx.stc.STC_CMD_CHARLEFTRECTEXTEND),
            (wx.stc.STC_KEY_RIGHT,
             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,
             wx.stc.STC_CMD_CHARRIGHTRECTEXTEND),
            (wx.stc.STC_KEY_HOME,
             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,
             wx.stc.STC_CMD_VCHOMERECTEXTEND),
            (wx.stc.STC_KEY_END, wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,
             wx.stc.STC_CMD_LINEENDRECTEXTEND),
            (wx.stc.STC_KEY_PRIOR,
             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,
             wx.stc.STC_CMD_PAGEUPRECTEXTEND),
            (wx.stc.STC_KEY_NEXT,
             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,
             wx.stc.STC_CMD_PAGEDOWNRECTEXTEND),

            # (wx.stc.STC_KEY_DELETE,    wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_DELLINERIGHT),
            # (wx.stc.STC_KEY_BACK,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_DELLINELEFT),
            # (wx.stc.STC_KEY_BACK,        wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_DELWORDLEFT),
            # (wx.stc.STC_KEY_DELETE,     wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_DELWORDRIGHT),
            # (wx.stc.STC_KEY_DIVIDE,    wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_SETZOOM),
            #        (ord('L'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LINECUT),
            #        (ord('L'),             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LINEDELETE),
            #        (ord('T'),             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LINECOPY),
            #        (ord('T'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LINETRANSPOSE),
            #        (ord('D'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_SELECTIONDUPLICATE),
            #        (ord('U'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LOWERCASE),
            #        (ord('U'),             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_UPPERCASE),
        )

        [self.textCtrl.CmdKeyAssign(*key) for key in defaultHotKeys]

    @property
    def searchPanel(self):
        """
        Возвращает контроллер панели поиска
        """
        return self._searchPanelController

    def Print(self):
        selectedtext = self.textCtrl.GetSelectedText()
        text = self.textCtrl.GetText()

        printer = TextPrinter(self)
        printer.printout(text if len(selectedtext) == 0 else selectedtext)

    def getPosChar(self, posBytes):
        return len(self.textCtrl.GetTextRange(0, posBytes))

    def AddText(self, text):
        self.textCtrl.AddText(text)

    def replaceText(self, text):
        self.textCtrl.ReplaceSelection(text)

    def toddleLinePrefix(self, line, prefix):
        """
        If line with number "line" starts with prefix, prefix will be removed
        else prefix will be added.
        """
        assert line < self.GetLineCount()
        line_text = self.GetLine(line)
        if line_text.startswith(prefix):
            line_text = line_text[len(prefix):]
        else:
            line_text = prefix + line_text
        self.SetLine(line, line_text)

    def toddleSelectedLinesPrefix(self, prefix):
        """
        Apply toddleLinePrefix method to selected lines
        """
        self.BeginUndoAction()
        old_sel_start = self.GetSelectionStart()
        old_sel_end = self.GetSelectionEnd()

        first_line, last_line = self.GetSelectionLines()
        [
            self.toddleLinePrefix(n, prefix)
            for n in range(first_line, last_line + 1)
        ]

        if old_sel_start != old_sel_end:
            new_sel_start = self.GetLineStartPosition(first_line)
            new_sel_end = self.GetLineEndPosition(last_line)
        else:
            new_sel_start = new_sel_end = self.GetLineEndPosition(last_line)

        self.SetSelection(new_sel_start, new_sel_end)
        self.EndUndoAction()

    def turnText(self, lefttext, righttext):
        selText = self.textCtrl.GetSelectedText()
        newtext = lefttext + selText + righttext
        self.textCtrl.ReplaceSelection(newtext)

        currPos = self.GetSelectionEnd()
        if len(selText) == 0:
            """
            Если не оборачиваем текст, а делаем пустой тег, то поместим каретку до закрывающегося тега
            """
            newpos = currPos - len(righttext)
            self.SetSelection(newpos, newpos)
        else:
            self.SetSelection(currPos - len(selText) - len(righttext),
                              currPos - len(righttext))

    def escapeHtml(self):
        selText = self.textCtrl.GetSelectedText()
        text = html.escape(selText, quote=False)
        self.textCtrl.ReplaceSelection(text)

    def SetReadOnly(self, readonly):
        self.textCtrl.SetReadOnly(readonly)

    def GetReadOnly(self):
        return self.textCtrl.GetReadOnly()

    def GetText(self):
        return self.textCtrl.GetText()

    def SetText(self, text):
        self.textCtrl.SetText(text)

    def EmptyUndoBuffer(self):
        self.textCtrl.EmptyUndoBuffer()

    def GetSelectedText(self):
        return self.textCtrl.GetSelectedText()

    def GetCurrentLine(self):
        return self.textCtrl.GetCurrentLine()

    def ScrollToLine(self, line):
        self.textCtrl.ScrollToLine(line)

    def SetSelection(self, start, end):
        """
        start и end в символах, а не в байтах, в отличие от исходного
        StyledTextCtrl
        """
        startText = self.GetText()[:start]
        endText = self.GetText()[:end]

        firstByte = self._helper.calcByteLen(startText)
        endByte = self._helper.calcByteLen(endText)

        self.textCtrl.SetSelection(firstByte, endByte)

    def GotoPos(self, pos):
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        self.textCtrl.GotoPos(pos_bytes)

    def GetCurrentPosition(self):
        """
        Возвращает номер символа(а не байта), перед которых находится курсор
        """
        return self._calcCharPos(self.textCtrl.GetCurrentPos())

    def GetSelectionStart(self):
        """
        Возвращает позицию начала выбранной области в символах, а не в байтах
        """
        return self._calcCharPos(self.textCtrl.GetSelectionStart())

    def GetSelectionLines(self):
        """
        Return tuple (first selected line, last selected line)
        """
        start_bytes = self.textCtrl.GetSelectionStart()
        end_bytes = self.textCtrl.GetSelectionEnd()
        return (self.textCtrl.LineFromPosition(start_bytes),
                self.textCtrl.LineFromPosition(end_bytes))

    def GetSelectionEnd(self):
        """
        Возвращает позицию конца выбранной области в символах, а не в байтах
        """
        return self._calcCharPos(self.textCtrl.GetSelectionEnd())

    def SetFocus(self):
        self.textCtrl.SetFocus()
        self.textCtrl.SetSTCFocus(True)

    def GetLine(self, line):
        """
        Return line with the "line" number. \n included.
        """
        return self.textCtrl.GetLine(line)

    def SetLine(self, line, newline):
        """
        Replace line with the number "line" newline.
        Newline will be ended with "\n" else line will be joined with next line
        """
        linecount = self.GetLineCount()
        assert line < linecount

        line_start_bytes = self.textCtrl.PositionFromLine(line)
        line_end_bytes = self.textCtrl.PositionFromLine(line + 1)
        self.textCtrl.Replace(line_start_bytes, line_end_bytes, newline)

    def GetLineCount(self):
        return self.textCtrl.GetLineCount()

    def GetLineStartPosition(self, line):
        """
        Retrieve the position at the start of a line in symbols (not bytes)
        """
        return self._calcCharPos(self.textCtrl.PositionFromLine(line))

    def GetLineEndPosition(self, line):
        """
        Get the position after the last visible characters on a line
            in symbols (not bytes)
        """
        return self._calcCharPos(self.textCtrl.GetLineEndPosition(line))

    def MoveSelectedLinesUp(self):
        """
        Move the selected lines up one line,
        shifting the line above after the selection.
        """
        self.textCtrl.MoveSelectedLinesUp()

    def MoveSelectedLinesDown(self):
        """
        Move the selected lines down one line,
        shifting the line below before the selection.
        """
        self.textCtrl.MoveSelectedLinesDown()

    def LineDuplicate(self):
        """
        Duplicate the current line.
        """
        self.textCtrl.LineDuplicate()

    def LineDelete(self):
        """
        Delete the current line.
        """
        self.textCtrl.LineDelete()

    def BeginUndoAction(self):
        self.textCtrl.BeginUndoAction()

    def EndUndoAction(self):
        self.textCtrl.EndUndoAction()

    def JoinLines(self):
        """
        Join selected lines
        """
        first_line, last_line = self.GetSelectionLines()
        if first_line != last_line:
            last_line -= 1

        self.BeginUndoAction()

        for _ in range(first_line, last_line + 1):
            line = self.GetLine(first_line).replace(u'\r\n', u'\n')
            if line.endswith(u'\n'):
                newline = line[:-1]
                self.SetLine(first_line, newline)

        new_sel_pos = self.GetLineEndPosition(first_line)
        self.SetSelection(new_sel_pos, new_sel_pos)

        self.EndUndoAction()

    def DelWordLeft(self):
        self.textCtrl.DelWordLeft()

    def DelWordRight(self):
        self.textCtrl.DelWordRight()

    def DelLineLeft(self):
        """
        Delete back from the current position to the start of the line
        """
        self.textCtrl.DelLineLeft()

    def DelLineRight(self):
        """
        Delete forwards from the current position to the end of the line
        """
        self.textCtrl.DelLineRight()

    def WordLeft(self):
        self.textCtrl.WordLeft()

    def WordRight(self):
        self.textCtrl.WordRight()

    def WordLeftEnd(self):
        self.textCtrl.WordLeftEnd()

    def WordRightEnd(self):
        self.textCtrl.WordRightEnd()

    def WordLeftExtend(self):
        self.textCtrl.WordLeftExtend()

    def WordRightExtend(self):
        self.textCtrl.WordRightExtend()

    def GotoWordStart(self):
        self.WordRight()
        self.WordLeft()

    def GotoWordEnd(self):
        self.WordLeftEnd()
        self.WordRightEnd()

    def ScrollLineToCursor(self):
        maxlines = self.textCtrl.LinesOnScreen()
        line = self.GetCurrentLine()
        if line >= maxlines:
            delta = min(10, maxlines / 3)
            line -= delta
            if line < 0:
                line = 0
            self.ScrollToLine(line)

    def WordStartPosition(self, pos):
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        result_bytes = self.textCtrl.WordStartPosition(pos_bytes, True)
        return self.getPosChar(result_bytes)

    def WordEndPosition(self, pos):
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        result_bytes = self.textCtrl.WordEndPosition(pos_bytes, True)
        return self.getPosChar(result_bytes)

    def GetWord(self, pos):
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        word_start_bytes = self.textCtrl.WordStartPosition(pos_bytes, True)
        word_end_bytes = self.textCtrl.WordEndPosition(pos_bytes, True)
        word = self.textCtrl.GetTextRange(word_start_bytes, word_end_bytes)
        return word

    def _calcCharPos(self, pos_bytes):
        """
        Пересчет позиции в байтах в позицию в символах
        """
        text_left = self.textCtrl.GetTextRange(0, pos_bytes)
        currpos = len(text_left)
        return currpos

    def _getTextForParse(self):
        # Табуляция в редакторе считается за несколько символов
        return self.textCtrl.GetText().replace("\t", " ")
예제 #5
0
class WikiColorizer (object):
    def __init__ (self, editor, colorizeSyntax, enableSpellChecking, runEvent):
        self._editor = editor
        self._helper = TextEditorHelper()
        self._enableSpellChecking = enableSpellChecking
        self._runEvent = runEvent

        self.text = TextFactory.make (None)
        self.bold = FontsFactory.makeBold (None).setParseAction(lambda s, l, t: None)
        self.italic = FontsFactory.makeItalic (None).setParseAction(lambda s, l, t: None)
        self.bold_italic = FontsFactory.makeBoldItalic (None).setParseAction(lambda s, l, t: None)
        self.underline = FontsFactory.makeUnderline (None).setParseAction(lambda s, l, t: None)
        self.heading = HeadingFactory.make (None).setParseAction(lambda s, l, t: None)
        self.command = CommandFactory.make (None).setParseAction(lambda s, l, t: None)
        self.link = LinkFactory.make (None).setParseAction(lambda s, l, t: None)
        self.url = UrlFactory.make (None).setParseAction(lambda s, l, t: None)
        self.linebreak = LineBreakFactory.make (None).setParseAction(lambda s, l, t: None)
        self.noformat = NoFormatFactory.make (None).setParseAction(lambda s, l, t: None)
        self.preformat = PreFormatFactory.make (None).setParseAction(lambda s, l, t: None)

        if colorizeSyntax:
            self.colorParser = (
                self.url |
                self.text |
                self.linebreak |
                self.link |
                self.noformat |
                self.preformat |
                self.command |
                self.bold_italic |
                self.bold |
                self.italic |
                self.underline |
                self.heading)

            self.insideBlockParser = (
                self.url |
                self.text |
                self.linebreak |
                self.link |
                self.noformat |
                self.preformat |
                self.bold_italic |
                self.bold |
                self.italic |
                self.underline)
        else:
            self.colorParser = self.text
            self.insideBlockParser = self.text


    def colorize (self, fullText):
        textlength = self._helper.calcByteLen (fullText)
        stylelist = [0] * textlength
        self._colorizeText (fullText, fullText, 0, textlength, self.colorParser, stylelist)

        return stylelist


    def _colorizeText (self, fullText, text, start, end, parser, stylelist):
        tokens = parser.scanString (text[start: end])

        for token in tokens:
            if not self._runEvent.is_set():
                break

            pos_start = token[1] + start
            pos_end = token[2] + start

            tokenname = token[0].getName()

            if (tokenname == "text" or
                    tokenname == "noformat" or
                    tokenname == "preformat"):
                if self._enableSpellChecking:
                    self._editor.runSpellChecking (stylelist,
                                                   fullText,
                                                   pos_start,
                                                   pos_end)
                continue

            if tokenname == "linebreak":
                continue

            # Нас интересует позиция в байтах, а не в символах
            bytepos_start = self._helper.calcBytePos (text, pos_start)
            bytepos_end = self._helper.calcBytePos (text, pos_end)

            # Применим стиль
            if tokenname == "bold":
                self._helper.addStyle (stylelist,
                                       self._editor.STYLE_BOLD_ID,
                                       bytepos_start,
                                       bytepos_end)
                self._colorizeText (fullText,
                                    text,
                                    pos_start + len (BoldToken.start),
                                    pos_end - len (BoldToken.end),
                                    self.insideBlockParser, stylelist)

            elif tokenname == "italic":
                self._helper.addStyle (stylelist,
                                       self._editor.STYLE_ITALIC_ID,
                                       bytepos_start,
                                       bytepos_end)
                self._colorizeText (fullText,
                                    text,
                                    pos_start + len (ItalicToken.start),
                                    pos_end - len (ItalicToken.end),
                                    self.insideBlockParser, stylelist)

            elif tokenname == "bold_italic":
                self._helper.addStyle (stylelist,
                                       self._editor.STYLE_BOLD_ITALIC_ID,
                                       bytepos_start,
                                       bytepos_end)
                self._colorizeText (fullText,
                                    text,
                                    pos_start + len (BoldItalicToken.start),
                                    pos_end - len (BoldItalicToken.end),
                                    self.insideBlockParser, stylelist)

            elif tokenname == "underline":
                self._helper.addStyle (stylelist,
                                       self._editor.STYLE_UNDERLINE_ID,
                                       bytepos_start,
                                       bytepos_end)
                self._colorizeText (fullText,
                                    text,
                                    pos_start + len (UnderlineToken.start),
                                    pos_end - len (UnderlineToken.end),
                                    self.insideBlockParser,
                                    stylelist)

            elif tokenname == "heading":
                self._helper.setStyle (stylelist,
                                       self._editor.STYLE_HEADING_ID,
                                       bytepos_start,
                                       bytepos_end)
                if self._enableSpellChecking:
                    self._editor.runSpellChecking (stylelist,
                                                   fullText,
                                                   pos_start,
                                                   pos_end)

            elif tokenname == "command":
                self._helper.setStyle (stylelist,
                                       self._editor.STYLE_COMMAND_ID,
                                       bytepos_start,
                                       bytepos_end)

            elif tokenname == "link":
                self._helper.addStyle (stylelist,
                                       self._editor.STYLE_LINK_ID,
                                       bytepos_start,
                                       bytepos_end)
                self._linkSpellChecking (fullText,
                                         text,
                                         stylelist,
                                         pos_start,
                                         pos_end)

            elif tokenname == "url":
                self._helper.addStyle (stylelist,
                                       self._editor.STYLE_LINK_ID,
                                       bytepos_start,
                                       bytepos_end)


    def _linkSpellChecking (self, fullText, text, stylelist, pos_start, pos_end):
        separator1 = u'->'
        separator2 = u'|'

        link = text[pos_start: pos_end]
        sep1_pos = link.find (separator1)
        if sep1_pos != -1:
            if self._enableSpellChecking:
                self._editor.runSpellChecking (stylelist,
                                               fullText,
                                               pos_start,
                                               pos_start + sep1_pos)
            return

        sep2_pos = link.find (separator2)
        if sep2_pos != -1:
            if self._enableSpellChecking:
                self._editor.runSpellChecking (stylelist,
                                               fullText,
                                               pos_start + sep2_pos + len (separator2),
                                               pos_end)
예제 #6
0
class MarkdownColorizer(object):
    def __init__(self, editor, colorizeSyntax, enableSpellChecking, runEvent):
        self._editor = editor
        self._helper = TextEditorHelper()
        self._enableSpellChecking = enableSpellChecking
        self._runEvent = runEvent

        self.text = TextFactory.make()
        self.bold = FontsFactory.makeBold()
        self.italic = FontsFactory.makeItalic()
        self.bold_italic = FontsFactory.makeBoldItalic()
        self.heading = HeadingFactory.make()
        self.link = LinkFactory.make()
        self.code = FontsFactory.makeCode()

        if colorizeSyntax:
            self.colorParser = (
                self.heading |
                self.text |
                self.link |
                self.bold_italic |
                self.bold |
                self.italic |
                self.code
            )

            self.insideBlockParser = (
                self.text |
                self.link |
                self.bold_italic |
                self.bold |
                self.italic
            )
        else:
            self.colorParser = self.text
            self.insideBlockParser = self.text

    def colorize(self, fullText):
        textlength = self._helper.calcByteLen(fullText)
        stylelist = [0] * textlength
        self._colorizeText(fullText,
                           fullText,
                           0,
                           textlength,
                           self.colorParser,
                           stylelist)

        return stylelist

    def _colorizeText(self, fullText, text, start, end, parser, stylelist):
        tokens = parser.scanString(text[start: end])

        for token in tokens:
            if not self._runEvent.is_set():
                break

            pos_start = token[1] + start
            pos_end = token[2] + start

            tokenname = token[0].getName()

            if (tokenname == "text" or
                    tokenname == "noformat" or
                    tokenname == "preformat"):
                if self._enableSpellChecking:
                    self._editor.runSpellChecking(stylelist,
                                                  fullText,
                                                  pos_start,
                                                  pos_end)
                continue

            # Нас интересует позиция в байтах, а не в символах
            bytepos_start = self._helper.calcBytePos(text, pos_start)
            bytepos_end = self._helper.calcBytePos(text, pos_end)

            # Применим стиль
            if tokenname == "bold":
                self._helper.addStyle(stylelist,
                                      self._editor.STYLE_BOLD_ID,
                                      bytepos_start,
                                      bytepos_end)
                self._colorizeText(fullText,
                                   text,
                                   pos_start + len(BoldToken.start_1),
                                   pos_end - len(BoldToken.end_1),
                                   self.insideBlockParser, stylelist)

            elif tokenname == "italic":
                self._helper.addStyle(stylelist,
                                      self._editor.STYLE_ITALIC_ID,
                                      bytepos_start,
                                      bytepos_end)
                self._colorizeText(fullText,
                                   text,
                                   pos_start + len(ItalicToken.start_1),
                                   pos_end - len(ItalicToken.end_1),
                                   self.insideBlockParser, stylelist)

            elif tokenname == "bold_italic":
                self._helper.addStyle(stylelist,
                                      self._editor.STYLE_BOLD_ITALIC_ID,
                                      bytepos_start,
                                      bytepos_end)
                self._colorizeText(fullText,
                                   text,
                                   pos_start + len(BoldItalicToken.start),
                                   pos_end - len(BoldItalicToken.end),
                                   self.insideBlockParser, stylelist)

            elif tokenname == "heading":
                self._helper.setStyle(stylelist,
                                      self._editor.STYLE_HEADING_ID,
                                      bytepos_start,
                                      bytepos_end)
                if self._enableSpellChecking:
                    self._editor.runSpellChecking(stylelist,
                                                  fullText,
                                                  pos_start,
                                                  pos_end)

            elif tokenname == "link":
                self._helper.addStyle(stylelist,
                                      self._editor.STYLE_LINK_ID,
                                      bytepos_start,
                                      bytepos_end)
                if self._enableSpellChecking:
                    self._linkSpellChecking(fullText,
                                            text,
                                            stylelist,
                                            pos_start,
                                            pos_end,
                                            token)

            elif tokenname == "code":
                self._helper.addStyle(stylelist,
                                      self._editor.STYLE_COMMAND_ID,
                                      bytepos_start,
                                      bytepos_end)

    def _linkSpellChecking(self, fullText, text, stylelist,
                           pos_start, pos_end, token):
        self._editor.runSpellChecking(stylelist,
                                      fullText,
                                      pos_start + 1,
                                      pos_start + 1 + len(token[0][0]))
        link = token[0][1]
        space_pos = link.find(u' ')
        if space_pos != -1:
            self._editor.runSpellChecking(
                stylelist,
                fullText,
                pos_start + 1 + len(token[0][0]) + 2 + space_pos,
                pos_start + 1 + len(token[0][0]) + 2 + space_pos +
                len(token[0][1]))
예제 #7
0
class MarkdownColorizer(object):
    def __init__(self, editor, colorizeSyntax, enableSpellChecking, runEvent):
        self._editor = editor
        self._helper = TextEditorHelper()
        self._enableSpellChecking = enableSpellChecking
        self._runEvent = runEvent

        self.text = TextFactory.make()
        self.bold = FontsFactory.makeBold()
        self.italic = FontsFactory.makeItalic()
        self.bold_italic = FontsFactory.makeBoldItalic()
        self.heading = HeadingFactory.make()
        self.link = LinkFactory.make()

        if colorizeSyntax:
            self.colorParser = (self.heading | self.text | self.link
                                | self.bold_italic | self.bold | self.italic)

            self.insideBlockParser = (self.text | self.link | self.bold_italic
                                      | self.bold | self.italic)
        else:
            self.colorParser = self.text
            self.insideBlockParser = self.text

    def colorize(self, fullText):
        textlength = self._helper.calcByteLen(fullText)
        stylelist = [0] * textlength
        self._colorizeText(fullText, fullText, 0, textlength, self.colorParser,
                           stylelist)

        return stylelist

    def _colorizeText(self, fullText, text, start, end, parser, stylelist):
        tokens = parser.scanString(text[start:end])

        for token in tokens:
            if not self._runEvent.is_set():
                break

            pos_start = token[1] + start
            pos_end = token[2] + start

            tokenname = token[0].getName()

            if (tokenname == "text" or tokenname == "noformat"
                    or tokenname == "preformat"):
                if self._enableSpellChecking:
                    self._editor.runSpellChecking(stylelist, fullText,
                                                  pos_start, pos_end)
                continue

            # Нас интересует позиция в байтах, а не в символах
            bytepos_start = self._helper.calcBytePos(text, pos_start)
            bytepos_end = self._helper.calcBytePos(text, pos_end)

            # Применим стиль
            if tokenname == "bold":
                self._helper.addStyle(stylelist, self._editor.STYLE_BOLD_ID,
                                      bytepos_start, bytepos_end)
                self._colorizeText(fullText, text,
                                   pos_start + len(BoldToken.start_1),
                                   pos_end - len(BoldToken.end_1),
                                   self.insideBlockParser, stylelist)

            elif tokenname == "italic":
                self._helper.addStyle(stylelist, self._editor.STYLE_ITALIC_ID,
                                      bytepos_start, bytepos_end)
                self._colorizeText(fullText, text,
                                   pos_start + len(ItalicToken.start_1),
                                   pos_end - len(ItalicToken.end_1),
                                   self.insideBlockParser, stylelist)

            elif tokenname == "bold_italic":
                self._helper.addStyle(stylelist,
                                      self._editor.STYLE_BOLD_ITALIC_ID,
                                      bytepos_start, bytepos_end)
                self._colorizeText(fullText, text,
                                   pos_start + len(BoldItalicToken.start),
                                   pos_end - len(BoldItalicToken.end),
                                   self.insideBlockParser, stylelist)

            elif tokenname == "heading":
                self._helper.setStyle(stylelist, self._editor.STYLE_HEADING_ID,
                                      bytepos_start, bytepos_end)
                if self._enableSpellChecking:
                    self._editor.runSpellChecking(stylelist, fullText,
                                                  pos_start, pos_end)

            elif tokenname == "link":
                self._helper.addStyle(stylelist, self._editor.STYLE_LINK_ID,
                                      bytepos_start, bytepos_end)
                if self._enableSpellChecking:
                    self._linkSpellChecking(fullText, text, stylelist,
                                            pos_start, pos_end, token)

    def _linkSpellChecking(self, fullText, text, stylelist, pos_start, pos_end,
                           token):
        self._editor.runSpellChecking(stylelist, fullText, pos_start + 1,
                                      pos_start + 1 + len(token[0][0]))
        link = token[0][1]
        space_pos = link.find(u' ')
        if space_pos != -1:
            self._editor.runSpellChecking(
                stylelist, fullText,
                pos_start + 1 + len(token[0][0]) + 2 + space_pos, pos_start +
                1 + len(token[0][0]) + 2 + space_pos + len(token[0][1]))
예제 #8
0
class TextEditor(wx.Panel):
    _fontConfigSection = "Font"

    def __init__(self, *args, **kwds):
        kwds["style"] = wx.TAB_TRAVERSAL
        wx.Panel.__init__(self, *args, **kwds)

        self._config = EditorConfig (Application.config)

        self._enableSpellChecking = True
        self._spellChecker = None

        self.SPELL_ERROR_INDICATOR = 0

        self._spellErrorText = None
        self._spellSuggestList = []

        self._spellMaxSuggest = len (TextEditorMenu.ID_SUGGESTS)
        self._spellStartByteError = -1
        self._spellEndByteError = -1

        # Уже были установлены стили текста (раскраска)
        self._styleSet = False

        self.__stylebytes = None
        self.__indicatorsbytes = None

        # Начинаем раскраску кода не менее чем через это время с момента его изменения
        self._DELAY = timedelta (milliseconds=300)

        # Время последней модификации текста страницы.
        # Используется для замера времени после модификации, чтобы не парсить текст
        # после каждой введенной буквы
        self._lastEdit = datetime.now() - self._DELAY * 2

        self.textCtrl = StyledTextCtrl(self, -1)

        self._popupMenu = TextEditorMenu(self)

        # Создание панели поиска и ее контроллера
        self._searchPanel = SearchReplacePanel (self)
        self._searchPanelController = SearchReplaceController (self._searchPanel, self)
        self._searchPanel.setController (self._searchPanelController)

        self.__do_layout()
        self.__createCoders()
        self._helper = TextEditorHelper()

        self.__showlinenumbers = self._config.lineNumbers.value

        self.setDefaultSettings()
        self.__bindEvents()


    def __bindEvents (self):
        self.textCtrl.Bind(wx.EVT_MENU, self.__onCopyFromEditor, id = wx.ID_COPY)
        self.textCtrl.Bind(wx.EVT_MENU, self.__onCutFromEditor, id = wx.ID_CUT)
        self.textCtrl.Bind(wx.EVT_MENU, self.__onPasteToEditor, id = wx.ID_PASTE)
        self.textCtrl.Bind(wx.EVT_MENU, self.__onUndo, id = wx.ID_UNDO)
        self.textCtrl.Bind(wx.EVT_MENU, self.__onRedo, id = wx.ID_REDO)
        self.textCtrl.Bind(wx.EVT_MENU, self.__onSelectAll, id = wx.ID_SELECTALL)

        self.textCtrl.Bind(wx.EVT_MENU, self.__onAddWordToDict, id = TextEditorMenu.ID_ADD_WORD)
        self.textCtrl.Bind(wx.EVT_MENU, self.__onAddWordLowerToDict, id = TextEditorMenu.ID_ADD_WORD_LOWER)

        for suggestId in TextEditorMenu.ID_SUGGESTS:
            self.textCtrl.Bind(wx.EVT_MENU, self.__onSpellSuggest, id = suggestId)

        self.textCtrl.Bind (wx.EVT_CHAR, self.__OnChar_ImeWorkaround)
        self.textCtrl.Bind (wx.EVT_KEY_DOWN, self.__onKeyDown)
        self.textCtrl.Bind (wx.EVT_CONTEXT_MENU, self.__onContextMenu)

        # self.textCtrl.Bind (wx.stc.EVT_STC_STYLENEEDED, self._onStyleNeeded)
        self.textCtrl.Bind (wx.EVT_IDLE, self._onStyleNeeded)
        self.Bind (EVT_APPLY_STYLE, self._onApplyStyle)

        # При перехвате этого сообщения в других классах, нужно вызывать event.Skip(),
        # чтобы это сообщение дошло досюда
        self.textCtrl.Bind (wx.stc.EVT_STC_CHANGE, self.__onChange)


    @property
    def config (self):
        return self._config


    @property
    def enableSpellChecking (self):
        return self._enableSpellChecking


    @enableSpellChecking.setter
    def enableSpellChecking (self, value):
        self._enableSpellChecking = value
        self._styleSet = False


    def __onChange (self, event):
        self._styleSet = False
        self._lastEdit = datetime.now()
        self.__setMarginWidth (self.textCtrl)


    @property
    def searchPanel (self):
        """
        Возвращает контроллер панели поиска
        """
        return self._searchPanelController


    def Print (self):
        selectedtext = self.textCtrl.GetSelectedText()
        text = self.textCtrl.GetText()

        printer = TextPrinter (self)
        printer.printout (text if len (selectedtext) == 0 else selectedtext)


    def __onCopyFromEditor (self, event):
        self.textCtrl.Copy()


    def __onCutFromEditor (self, event):
        self.textCtrl.Cut()


    def __onPasteToEditor (self, event):
        self.textCtrl.Paste()


    def __onUndo (self, event):
        self.textCtrl.Undo()


    def __onRedo (self, event):
        self.textCtrl.Redo()


    def __onSelectAll (self, event):
        self.textCtrl.SelectAll()


    def __do_layout(self):
        mainSizer = wx.FlexGridSizer(rows=2)
        mainSizer.AddGrowableRow(0)
        mainSizer.AddGrowableCol(0)

        mainSizer.Add(self.textCtrl, 0, wx.EXPAND, 0)
        mainSizer.Add(self._searchPanel, 0, wx.EXPAND, 0)
        self.SetSizer(mainSizer)

        self._searchPanel.Hide()
        self.Layout()


    def setDefaultSettings (self):
        """
        Установить стили и настройки по умолчанию в контрол StyledTextCtrl
        """
        self._spellChecker = self.getSpellChecker()

        size = self._config.fontSize.value
        faceName = self._config.fontName.value
        isBold = self._config.fontIsBold.value
        isItalic = self._config.fontIsItalic.value
        fontColor = self._config.fontColor.value
        backColor = self._config.backColor.value

        self.__showlinenumbers = self._config.lineNumbers.value
        self.textCtrl.SetEndAtLastLine (False)

        self.textCtrl.StyleSetSize (wx.stc.STC_STYLE_DEFAULT, size)
        self.textCtrl.StyleSetFaceName (wx.stc.STC_STYLE_DEFAULT, faceName)
        self.textCtrl.StyleSetBold (wx.stc.STC_STYLE_DEFAULT, isBold)
        self.textCtrl.StyleSetItalic (wx.stc.STC_STYLE_DEFAULT, isItalic)
        self.textCtrl.StyleSetForeground (wx.stc.STC_STYLE_DEFAULT, fontColor)
        self.textCtrl.StyleSetBackground (wx.stc.STC_STYLE_DEFAULT, backColor)

        self.textCtrl.StyleClearAll()

        self.textCtrl.SetCaretForeground (fontColor)
        self.textCtrl.SetCaretLineBack (backColor)

        # Заблокируем горячую клавишу Ctrl+D, чтобы использовать ее как добавление закладки
        self.textCtrl.CmdKeyClear (ord ("D"), wx.stc.STC_SCMOD_CTRL)
        self.textCtrl.CmdKeyClear (ord ("R"), wx.stc.STC_SCMOD_CTRL | wx.stc.STC_SCMOD_SHIFT)
        self.textCtrl.SetWrapMode (wx.stc.STC_WRAP_WORD)
        self.textCtrl.SetWrapVisualFlags (wx.stc.STC_WRAPVISUALFLAG_END)

        self.__setMarginWidth (self.textCtrl)
        self.textCtrl.SetTabWidth (self._config.tabWidth.value)

        self.enableSpellChecking = self._config.spellEnabled.value
        self._spellChecker.skipWordsWithNumbers = self.config.spellSkipDigits.value

        if self._config.homeEndKeys.value == EditorConfig.HOME_END_OF_LINE:
            # Клавиши Home / End переносят курсор на начало / конец строки
            self.textCtrl.CmdKeyAssign (wx.stc.STC_KEY_HOME,
                                        0,
                                        wx.stc.STC_CMD_HOMEDISPLAY)

            self.textCtrl.CmdKeyAssign (wx.stc.STC_KEY_HOME,
                                        wx.stc.STC_SCMOD_ALT,
                                        wx.stc.STC_CMD_HOME)

            self.textCtrl.CmdKeyAssign (wx.stc.STC_KEY_END,
                                        0,
                                        wx.stc.STC_CMD_LINEENDDISPLAY)

            self.textCtrl.CmdKeyAssign (wx.stc.STC_KEY_END,
                                        wx.stc.STC_SCMOD_ALT,
                                        wx.stc.STC_CMD_LINEEND)
        else:
            # Клавиши Home / End переносят курсор на начало / конец абзаца
            self.textCtrl.CmdKeyAssign (wx.stc.STC_KEY_HOME,
                                        0,
                                        wx.stc.STC_CMD_HOME)

            self.textCtrl.CmdKeyAssign (wx.stc.STC_KEY_HOME,
                                        wx.stc.STC_SCMOD_ALT,
                                        wx.stc.STC_CMD_HOMEDISPLAY)

            self.textCtrl.CmdKeyAssign (wx.stc.STC_KEY_END,
                                        0,
                                        wx.stc.STC_CMD_LINEEND)

            self.textCtrl.CmdKeyAssign (wx.stc.STC_KEY_END,
                                        wx.stc.STC_SCMOD_ALT,
                                        wx.stc.STC_CMD_LINEENDDISPLAY)

        self.textCtrl.IndicatorSetStyle(self.SPELL_ERROR_INDICATOR, wx.stc.STC_INDIC_SQUIGGLE)
        self.textCtrl.IndicatorSetForeground(self.SPELL_ERROR_INDICATOR, "red")
        self._styleSet = False


    def __setMarginWidth (self, editor):
        """
        Установить размер левой области, где пишутся номера строк в зависимости от шрифта
        """
        if self.__showlinenumbers:
            editor.SetMarginWidth (0, self.__getMarginWidth())
            editor.SetMarginWidth (1, 5)
        else:
            editor.SetMarginWidth (0, 0)
            editor.SetMarginWidth (1, 8)


    def __getMarginWidth (self):
        """
        Расчет размера серой области с номером строк
        """
        fontSize = self._config.fontSize.value
        linescount = len (self.GetText().split("\n"))

        if linescount == 0:
            width = 10
        else:
            # Количество десятичных цифр в числе строк
            digits = int (math.log10 (linescount) + 1)
            width = int (1.2 * fontSize * digits)

        return width


    def getPosChar (self, posBytes):
        return len (self.textCtrl.GetTextRange (0, posBytes))


    def __createCoders (self):
        encoding = outwiker.core.system.getOS().inputEncoding
        self.mbcsEnc = codecs.getencoder(encoding)


    def __onKeyDown (self, event):
        key = event.GetKeyCode()

        if key == wx.WXK_ESCAPE:
            self._searchPanel.Close()

        event.Skip()


    def __OnChar_ImeWorkaround(self, evt):
        """
        Обработка клавиш вручную, чтобы не было проблем с вводом русских букв в Linux.
        Основа кода взята из Wikidpad (WikiTxtCtrl.py -> OnChar_ImeWorkaround)
        """
        key = evt.GetKeyCode()

        # Return if this doesn't seem to be a real character input
        if evt.ControlDown() or (0 < key < 32):
            evt.Skip()
            return

        if key >= wx.WXK_START and evt.GetUnicodeKey() != key:
            evt.Skip()
            return

        unichar = unichr(evt.GetUnicodeKey())

        self.textCtrl.ReplaceSelection(self.mbcsEnc (unichar, "replace")[0])


    def AddText (self, text):
        self.textCtrl.AddText (text)


    def replaceText (self, text):
        self.textCtrl.ReplaceSelection (text)


    def turnText (self, lefttext, righttext):
        selText = self.textCtrl.GetSelectedText()
        newtext = lefttext + selText + righttext
        self.textCtrl.ReplaceSelection (newtext)

        currPos = self.GetSelectionEnd()
        if len (selText) == 0:
            """
            Если не оборачиваем текст, а делаем пустой тег, то поместим каретку до закрывающегося тега
            """
            newpos = currPos - len (righttext)
            self.SetSelection (newpos, newpos)
        else:
            self.SetSelection (currPos - len (selText) - len (righttext),
                               currPos - len (righttext))


    def escapeHtml (self):
        selText = self.textCtrl.GetSelectedText()
        text = cgi.escape (selText, quote=False)
        self.textCtrl.ReplaceSelection (text)


    def SetReadOnly (self, readonly):
        self.textCtrl.SetReadOnly (readonly)


    def GetReadOnly (self):
        return self.textCtrl.GetReadOnly()


    def GetText(self):
        return self.textCtrl.GetText()


    def SetText (self, text):
        self.textCtrl.SetText (text)


    def EmptyUndoBuffer (self):
        self.textCtrl.EmptyUndoBuffer()


    def GetSelectedText (self):
        return self.textCtrl.GetSelectedText()


    def SetSelection (self, start, end):
        """
        start и end в символах, а не в байтах, в отличие от исходного StyledTextCtrl
        """
        startText = self.GetText()[:start]
        endText = self.GetText()[:end]

        firstByte = self._helper.calcByteLen (startText)
        endByte = self._helper.calcByteLen (endText)

        self.textCtrl.SetSelection (firstByte, endByte)


    def GetCurrentPosition (self):
        """
        Возвращает номер символа (а не байта), перед которых находится курсор
        """
        return self.__calcCharPos (self.textCtrl.GetCurrentPos())


    def GetSelectionStart (self):
        """
        Возвращает позицию начала выбранной области в символах, а не в байтах
        """
        return self.__calcCharPos (self.textCtrl.GetSelectionStart())


    def GetSelectionEnd (self):
        """
        Возвращает позицию конца выбранной области в символах, а не в байтах
        """
        return self.__calcCharPos (self.textCtrl.GetSelectionEnd())


    def SetFocus (self):
        self.textCtrl.SetFocus()
        self.textCtrl.SetSTCFocus(True)


    def __calcCharPos (self, pos_bytes):
        """
        Пересчет позиции в байтах в позицию в символах
        """
        text_left = self.textCtrl.GetTextRange (0, pos_bytes)
        currpos = len (text_left)
        return currpos


    def _getTextForParse (self):
        # Табуляция в редакторе считается за несколько символов
        return self.textCtrl.GetText().replace ("\t", " ")


    def runSpellChecking (self, stylelist, fullText, start, end):
        errors = self._spellChecker.findErrors (fullText[start: end])

        for word, err_start, err_end in errors:
            self._helper.setSpellError (stylelist,
                                        fullText,
                                        err_start + start,
                                        err_end + start)


    def _onStyleNeeded (self, event):
        if (not self._styleSet and
                datetime.now() - self._lastEdit >= self._DELAY):
            page = Application.selectedPage
            text = self._getTextForParse()
            params = EditorStyleNeededParams (self,
                                              text,
                                              self._enableSpellChecking)
            Application.onEditorStyleNeeded (page, params)
            self._styleSet = True


    def _onApplyStyle (self, event):
        if event.text == self._getTextForParse():
            startByte = self._helper.calcBytePos (event.text, event.start)
            endByte = self._helper.calcBytePos (event.text, event.end)
            lenBytes = endByte - startByte

            textlength = self._helper.calcByteLen (event.text)
            self.__stylebytes = [0] * textlength

            if event.stylebytes is not None:
                self.__stylebytes = event.stylebytes

            if event.indicatorsbytes is not None:
                self.__stylebytes = [item1 | item2
                                     for item1, item2
                                     in zip (self.__stylebytes, event.indicatorsbytes)]

            stylebytesstr = "".join ([chr(byte) for byte in self.__stylebytes])


            if event.stylebytes is not None:
                self.textCtrl.StartStyling (startByte, 0xff ^ wx.stc.STC_INDICS_MASK)
                self.textCtrl.SetStyleBytes (lenBytes, stylebytesstr[startByte:endByte])

            if event.indicatorsbytes is not None:
                self.textCtrl.StartStyling (startByte, wx.stc.STC_INDICS_MASK)
                self.textCtrl.SetStyleBytes (lenBytes, stylebytesstr[startByte:endByte])

            self._styleSet = True


    def getSpellChecker (self):
        langlist = self._getDictsFromConfig()
        spellDirList = outwiker.core.system.getSpellDirList()

        spellChecker = SpellChecker (Application, langlist, spellDirList)
        spellChecker.addCustomDict (os.path.join (spellDirList[-1], CUSTOM_DICT_FILE_NAME))

        return spellChecker


    def _getDictsFromConfig (self):
        dictsStr = self._config.spellCheckerDicts.value
        return [item.strip()
                for item
                in dictsStr.split(',')
                if item.strip()]


    def _getMenu (self):
        """
        pos_byte - nearest text position (in bytes) where was produce a click
        """
        self._popupMenu.RefreshItems ()
        return self._popupMenu


    def __onContextMenu (self, event):
        point = self.textCtrl.ScreenToClient (event.GetPosition())
        pos_byte = self.textCtrl.PositionFromPoint(point)

        popupMenu = self._getMenu ()
        self._appendSpellItems (popupMenu, pos_byte)

        Application.onEditorPopupMenu (
            Application.selectedPage,
            EditorPopupMenuParams (self, popupMenu, point, pos_byte)
        )

        self.textCtrl.PopupMenu(popupMenu)


    def getCachedStyleBytes (self):
        return self.__stylebytes


    def __onAddWordToDict (self, event):
        if self._spellErrorText is not None:
            self.__addWordToDict (self._spellErrorText)


    def __onAddWordLowerToDict (self, event):
        if self._spellErrorText is not None:
            self.__addWordToDict (self._spellErrorText.lower())


    def __addWordToDict (self, word):
        self._spellChecker.addToCustomDict (0, word)
        self._spellErrorText = None
        self._styleSet = False


    def _appendSpellItems (self, menu, pos_byte):
        stylebytes = self.getCachedStyleBytes()
        if stylebytes is None:
            return

        stylebytes_len = len (stylebytes)

        if (stylebytes is None or
                pos_byte >= stylebytes_len or
                stylebytes[pos_byte] & self._helper.SPELL_ERROR_INDICATOR_MASK == 0):
            return

        endSpellError = startSpellError = pos_byte

        while (startSpellError >= 0 and
               stylebytes[startSpellError] & self._helper.SPELL_ERROR_INDICATOR_MASK != 0):
            startSpellError -= 1


        while (endSpellError < stylebytes_len and
               stylebytes[endSpellError] & self._helper.SPELL_ERROR_INDICATOR_MASK != 0):
            endSpellError += 1

        self._spellStartByteError = startSpellError + 1
        self._spellEndByteError = endSpellError
        self._spellErrorText = self.textCtrl.GetTextRange (self._spellStartByteError, self._spellEndByteError)
        self._spellSuggestList = self._spellChecker.getSuggest (self._spellErrorText)[:self._spellMaxSuggest]

        menu.AppendSeparator()
        menu.AppendSpellSubmenu (self._spellErrorText, self._spellSuggestList)


    def __onSpellSuggest (self, event):
        assert event.GetId() in TextEditorMenu.ID_SUGGESTS

        index = TextEditorMenu.ID_SUGGESTS.index (event.GetId())
        word = self._spellSuggestList[index]

        self.textCtrl.SetSelection (self._spellStartByteError, self._spellEndByteError)
        self.textCtrl.ReplaceSelection (word)
예제 #9
0
class TextEditorBase(wx.Panel):
    '''
    Added in outwiker.gui 1.3
    '''
    def __init__(self, parent):
        super(TextEditorBase, self).__init__(parent, style=wx.TAB_TRAVERSAL)
        self.textCtrl = StyledTextCtrl(self, -1)

        # Создание панели поиска и ее контроллера
        self._searchPanel = SearchReplacePanel(self)
        self._searchPanelController = SearchReplaceController(
            self._searchPanel,
            self)
        self._searchPanel.setController(self._searchPanelController)

        self._do_layout()

        self.__createCoders()
        self._helper = TextEditorHelper()
        self._bind()
        self._setDefaultSettings()

    def _bind(self):
        self.textCtrl.Bind(wx.EVT_KEY_DOWN, self.__onKeyDown)

    def _do_layout(self):
        mainSizer = wx.FlexGridSizer(rows=2)
        mainSizer.AddGrowableRow(0)
        mainSizer.AddGrowableCol(0)

        mainSizer.Add(self.textCtrl, 0, wx.EXPAND, 0)
        mainSizer.Add(self._searchPanel, 0, wx.EXPAND, 0)
        self.SetSizer(mainSizer)

        self._searchPanel.Hide()
        self.Layout()

    def __createCoders(self):
        encoding = outwiker.core.system.getOS().inputEncoding
        self.mbcsEnc = codecs.getencoder(encoding)

    def __onKeyDown(self, event):
        key = event.GetKeyCode()

        if key == wx.WXK_ESCAPE:
            self._searchPanel.Close()

        event.Skip()

    def _setDefaultSettings(self):
        self.textCtrl.SetEndAtLastLine(False)
        self.textCtrl.StyleClearAll()
        self.textCtrl.SetWrapMode(wx.stc.STC_WRAP_WORD)
        self.textCtrl.SetWrapVisualFlags(wx.stc.STC_WRAPVISUALFLAG_END)
        self.textCtrl.SetTabWidth(4)

    @property
    def searchPanel(self):
        """
        Возвращает контроллер панели поиска
        """
        return self._searchPanelController

    def Print(self):
        selectedtext = self.textCtrl.GetSelectedText()
        text = self.textCtrl.GetText()

        printer = TextPrinter(self)
        printer.printout(text if len(selectedtext) == 0 else selectedtext)

    def getPosChar(self, posBytes):
        return len(self.textCtrl.GetTextRange(0, posBytes))

    def AddText(self, text):
        self.textCtrl.AddText(text)

    def replaceText(self, text):
        self.textCtrl.ReplaceSelection(text)

    def toddleLinePrefix(self, line, prefix):
        """
        If line with number "line" starts with prefix, prefix will be removed
        else prefix will be added.

        Added in OutWiker 2.0.0.795.
        """
        assert line < self.GetLineCount()
        line_text = self.GetLine(line)
        if line_text.startswith(prefix):
            line_text = line_text[len(prefix):]
        else:
            line_text = prefix + line_text
        self.SetLine(line, line_text)

    def toddleSelectedLinesPrefix(self, prefix):
        """
        Apply toddleLinePrefix method to selected lines

        Added in OutWiker 2.0.0.795.
        """
        self.BeginUndoAction()
        old_sel_start = self.GetSelectionStart()
        old_sel_end = self.GetSelectionEnd()

        first_line, last_line = self.GetSelectionLines()
        map(lambda n: self.toddleLinePrefix(n, prefix),
            xrange(first_line, last_line + 1))

        if old_sel_start != old_sel_end:
            new_sel_start = self.GetLineStartPosition(first_line)
            new_sel_end = self.GetLineEndPosition(last_line)
        else:
            new_sel_start = new_sel_end = self.GetLineEndPosition(last_line)

        self.SetSelection(new_sel_start, new_sel_end)
        self.EndUndoAction()

    def turnText(self, lefttext, righttext):
        selText = self.textCtrl.GetSelectedText()
        newtext = lefttext + selText + righttext
        self.textCtrl.ReplaceSelection(newtext)

        currPos = self.GetSelectionEnd()
        if len(selText) == 0:
            """
            Если не оборачиваем текст, а делаем пустой тег, то поместим каретку до закрывающегося тега
            """
            newpos = currPos - len(righttext)
            self.SetSelection(newpos, newpos)
        else:
            self.SetSelection(currPos - len(selText) - len(righttext),
                              currPos - len(righttext))

    def escapeHtml(self):
        selText = self.textCtrl.GetSelectedText()
        text = cgi.escape(selText, quote=False)
        self.textCtrl.ReplaceSelection(text)

    def SetReadOnly(self, readonly):
        self.textCtrl.SetReadOnly(readonly)

    def GetReadOnly(self):
        return self.textCtrl.GetReadOnly()

    def GetText(self):
        return self.textCtrl.GetText()

    def SetText(self, text):
        self.textCtrl.SetText(text)

    def EmptyUndoBuffer(self):
        self.textCtrl.EmptyUndoBuffer()

    def GetSelectedText(self):
        return self.textCtrl.GetSelectedText()

    def GetCurrentLine(self):
        return self.textCtrl.GetCurrentLine()

    def ScrollToLine(self, line):
        self.textCtrl.ScrollToLine(line)

    def SetSelection(self, start, end):
        """
        start и end в символах, а не в байтах, в отличие от исходного
        StyledTextCtrl
        """
        startText = self.GetText()[:start]
        endText = self.GetText()[:end]

        firstByte = self._helper.calcByteLen(startText)
        endByte = self._helper.calcByteLen(endText)

        self.textCtrl.SetSelection(firstByte, endByte)

    def GotoPos(self, pos):
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        self.textCtrl.GotoPos(pos_bytes)

    def GetCurrentPosition(self):
        """
        Возвращает номер символа(а не байта), перед которых находится курсор
        """
        return self._calcCharPos(self.textCtrl.GetCurrentPos())

    def GetSelectionStart(self):
        """
        Возвращает позицию начала выбранной области в символах, а не в байтах
        """
        return self._calcCharPos(self.textCtrl.GetSelectionStart())

    def GetSelectionLines(self):
        """
        Return tuple (first selected line, last selected line)

        Added in OutWiker 2.0.0.795.
        """
        start_bytes = self.textCtrl.GetSelectionStart()
        end_bytes = self.textCtrl.GetSelectionEnd()
        return (self.textCtrl.LineFromPosition(start_bytes),
                self.textCtrl.LineFromPosition(end_bytes))

    def GetSelectionEnd(self):
        """
        Возвращает позицию конца выбранной области в символах, а не в байтах
        """
        return self._calcCharPos(self.textCtrl.GetSelectionEnd())

    def SetFocus(self):
        self.textCtrl.SetFocus()
        self.textCtrl.SetSTCFocus(True)

    def GetLine(self, line):
        """
        Return line with the "line" number. \n included.
        Added in OutWiker 2.0.0.795
        """
        return self.textCtrl.GetLine(line)

    def SetLine(self, line, newline):
        """
        Replace line with the number "line" newline.
        Newline will be ended with "\n" else line will be joined with next line

        Added in OutWiker 2.0.0.795
        """
        linecount = self.GetLineCount()
        assert line < linecount

        line_start_bytes = self.textCtrl.PositionFromLine(line)
        line_end_bytes = self.textCtrl.PositionFromLine(line + 1)
        self.textCtrl.Replace(line_start_bytes, line_end_bytes, newline)

    def GetLineCount(self):
        return self.textCtrl.GetLineCount()

    def GetLineStartPosition(self, line):
        """
        Retrieve the position at the start of a line in symbols (not bytes)

        Added in OutWiker 2.0.0.795
        """
        return self._calcCharPos(self.textCtrl.PositionFromLine(line))

    def GetLineEndPosition(self, line):
        """
        Get the position after the last visible characters on a line
            in symbols (not bytes)

        Added in OutWiker 2.0.0.795
        """
        return self._calcCharPos(self.textCtrl.GetLineEndPosition(line))

    def MoveSelectedLinesUp(self):
        """
        Move the selected lines up one line,
        shifting the line above after the selection.

        Added in OutWiker 2.0.0.795
        """
        self.textCtrl.MoveSelectedLinesUp()

    def MoveSelectedLinesDown(self):
        """
        Move the selected lines down one line,
        shifting the line below before the selection.

        Added in OutWiker 2.0.0.795
        """
        self.textCtrl.MoveSelectedLinesDown()

    def LineDuplicate(self):
        """
        Duplicate the current line.

        Added in OutWiker 2.0.0.795
        """
        self.textCtrl.LineDuplicate()

    def LineDelete(self):
        """
        Delete the current line.

        Added in OutWiker 2.0.0.795
        """
        self.textCtrl.LineDelete()

    def BeginUndoAction(self):
        """
        Added in OutWiker 2.0.0.797
        """
        self.textCtrl.BeginUndoAction()

    def EndUndoAction(self):
        """
        Added in OutWiker 2.0.0.797
        """
        self.textCtrl.EndUndoAction()

    def JoinLines(self):
        """
        Join selected lines

        Added in OutWiker 2.0.0.797
        """
        first_line, last_line = self.GetSelectionLines()
        if first_line != last_line:
            last_line -= 1

        self.BeginUndoAction()

        for _ in range(first_line, last_line + 1):
            line = self.GetLine(first_line).replace(u'\r\n', u'\n')
            if line.endswith(u'\n'):
                newline = line[:-1]
                self.SetLine(first_line, newline)

        new_sel_pos = self.GetLineEndPosition(first_line)
        self.SetSelection(new_sel_pos, new_sel_pos)

        self.EndUndoAction()

    def DelWordLeft(self):
        """
        Added in OutWiker 2.0.0.797
        """
        self.textCtrl.DelWordLeft()

    def DelWordRight(self):
        """
        Added in OutWiker 2.0.0.797
        """
        self.textCtrl.DelWordRight()

    def DelLineLeft(self):
        """
        Delete back from the current position to the start of the line

        Added in OutWiker 2.0.0.797
        """
        self.textCtrl.DelLineLeft()

    def DelLineRight(self):
        """
        Delete forwards from the current position to the end of the line

        Added in OutWiker 2.0.0.797
        """
        self.textCtrl.DelLineRight()

    def WordLeft(self):
        """
        Added in outwiker.gui 1.2
        """
        self.textCtrl.WordLeft()

    def WordRight(self):
        """
        Added in outwiker.gui 1.2
        """
        self.textCtrl.WordRight()

    def WordLeftEnd(self):
        """
        Added in outwiker.gui 1.2
        """
        self.textCtrl.WordLeftEnd()

    def WordRightEnd(self):
        """
        Added in outwiker.gui 1.2
        """
        self.textCtrl.WordRightEnd()

    def WordLeftExtend(self):
        """
        Added in outwiker.gui 1.2
        """
        self.textCtrl.WordLeftExtend()

    def WordRightExtend(self):
        """
        Added in outwiker.gui 1.2
        """
        self.textCtrl.WordRightExtend()

    def GotoWordStart(self):
        """
        Added in outwiker.gui 1.2
        """
        self.WordRight()
        self.WordLeft()

    def GotoWordEnd(self):
        """
        Added in outwiker.gui 1.2
        """
        self.WordLeftEnd()
        self.WordRightEnd()

    def ScrollLineToCursor(self):
        """
        Added in outwiker.gui 1.1
        """
        maxlines = self.textCtrl.LinesOnScreen()
        line = self.GetCurrentLine()
        if line >= maxlines:
            delta = min(10, maxlines / 3)
            line -= delta
            if line < 0:
                line = 0
            self.ScrollToLine(line)

    def WordStartPosition(self, pos):
        """
        Added in outwiker.gui 1.2
        """
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        result_bytes = self.textCtrl.WordStartPosition(pos_bytes, True)
        return self.getPosChar(result_bytes)

    def WordEndPosition(self, pos):
        """
        Added in outwiker.gui 1.2
        """
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        result_bytes = self.textCtrl.WordEndPosition(pos_bytes, True)
        return self.getPosChar(result_bytes)

    def GetWord(self, pos):
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        word_start_bytes = self.textCtrl.WordStartPosition(pos_bytes, True)
        word_end_bytes = self.textCtrl.WordEndPosition(pos_bytes, True)
        word = self.textCtrl.GetTextRange(word_start_bytes, word_end_bytes)
        return word

    def _calcCharPos(self, pos_bytes):
        """
        Пересчет позиции в байтах в позицию в символах
        """
        text_left = self.textCtrl.GetTextRange(0, pos_bytes)
        currpos = len(text_left)
        return currpos

    def _getTextForParse(self):
        # Табуляция в редакторе считается за несколько символов
        return self.textCtrl.GetText().replace("\t", " ")
예제 #10
0
class MarkdownColorizer(object):
    def __init__(self, editor, colorizeSyntax, enableSpellChecking, runEvent):
        self._editor = editor
        self._helper = TextEditorHelper()
        self._enableSpellChecking = enableSpellChecking
        self._runEvent = runEvent

        self.text = TextFactory.make()
        self.bold = FontsFactory.makeBold()
        self.italic = FontsFactory.makeItalic()
        self.bold_italic = FontsFactory.makeBoldItalic()
        self.heading = HeadingFactory.make()
        self.link = LinkFactory.make()
        self.code = FontsFactory.makeCode()
        self.comment = FontsFactory.makeComment()

        if colorizeSyntax:
            self.colorParser = (self.heading | self.text | self.link
                                | self.bold_italic | self.bold | self.italic
                                | self.code | self.comment)

            self.insideBlockParser = (self.text | self.link | self.bold_italic
                                      | self.bold | self.italic | self.comment)
        else:
            self.colorParser = self.text
            self.insideBlockParser = self.text

    def colorize(self, text):
        textlength = self._helper.calcByteLen(text)
        stylelist = [0] * textlength
        spellStatusFlags = [True] * len(text)
        self._colorizeText(text, 0, textlength, self.colorParser, stylelist,
                           spellStatusFlags)
        wx.CallAfter(self._editor.markSpellErrors, spellStatusFlags)
        return stylelist

    def _checkSpell(self, text, start, end, spellStatusFlags):
        spellChecker = self._editor.getSpellChecker()
        errors = spellChecker.findErrors(text[start:end])

        for _word, err_start, err_end in errors:
            spellStatusFlags[err_start + start:err_end +
                             start] = [False] * (err_end - err_start)

    def _colorizeText(self, text, start, end, parser, stylelist,
                      spellStatusFlags):
        tokens = parser.scanString(text[start:end])

        for token in tokens:
            if not self._runEvent.is_set():
                break

            pos_start = token[1] + start
            pos_end = token[2] + start

            tokenname = token[0].getName()

            if (tokenname == "text" or tokenname == "noformat"
                    or tokenname == "preformat"):
                if self._enableSpellChecking:
                    self._checkSpell(text, pos_start, pos_end,
                                     spellStatusFlags)
                continue

            # Нас интересует позиция в байтах, а не в символах
            bytepos_start = self._helper.calcBytePos(text, pos_start)
            bytepos_end = self._helper.calcBytePos(text, pos_end)

            # Применим стиль
            if tokenname == "bold":
                self._helper.addStyle(stylelist, self._editor.STYLE_BOLD_ID,
                                      bytepos_start, bytepos_end)
                self._colorizeText(text, pos_start + len(BoldToken.start_1),
                                   pos_end - len(BoldToken.end_1),
                                   self.insideBlockParser, stylelist,
                                   spellStatusFlags)

            elif tokenname == "italic":
                self._helper.addStyle(stylelist, self._editor.STYLE_ITALIC_ID,
                                      bytepos_start, bytepos_end)
                self._colorizeText(text, pos_start + len(ItalicToken.start_1),
                                   pos_end - len(ItalicToken.end_1),
                                   self.insideBlockParser, stylelist,
                                   spellStatusFlags)

            elif tokenname == "bold_italic":
                self._helper.addStyle(stylelist,
                                      self._editor.STYLE_BOLD_ITALIC_ID,
                                      bytepos_start, bytepos_end)
                self._colorizeText(text,
                                   pos_start + len(BoldItalicToken.start),
                                   pos_end - len(BoldItalicToken.end),
                                   self.insideBlockParser, stylelist,
                                   spellStatusFlags)

            elif tokenname == "heading":
                self._helper.setStyle(stylelist, self._editor.STYLE_HEADING_ID,
                                      bytepos_start, bytepos_end)
                if self._enableSpellChecking:
                    self._checkSpell(text, pos_start, pos_end,
                                     spellStatusFlags)

            elif tokenname == "link":
                self._helper.addStyle(stylelist, self._editor.STYLE_LINK_ID,
                                      bytepos_start, bytepos_end)
                if self._enableSpellChecking:
                    self._linkSpellChecking(text, stylelist, spellStatusFlags,
                                            pos_start, pos_end, token)

            elif tokenname == "code":
                self._helper.addStyle(stylelist, self._editor.STYLE_COMMAND_ID,
                                      bytepos_start, bytepos_end)

            elif tokenname == "comment":
                self._helper.setStyle(stylelist, self._editor.STYLE_COMMENT_ID,
                                      bytepos_start, bytepos_end)
                if self._enableSpellChecking:
                    self._checkSpell(text, pos_start, pos_end,
                                     spellStatusFlags)

    def _linkSpellChecking(self, text, stylelist, spellStatusFlags, pos_start,
                           pos_end, token):
        self._checkSpell(text, pos_start + 1, pos_start + 1 + len(token[0][0]),
                         spellStatusFlags)
        link = token[0][1]
        space_pos = link.find(u' ')
        if space_pos != -1:
            self._checkSpell(
                text, pos_start + 1 + len(token[0][0]) + 2 + space_pos,
                pos_start + 1 + len(token[0][0]) + 2 + space_pos +
                len(token[0][1]), spellStatusFlags)
예제 #11
0
class TextEditor(wx.Panel):
    _fontConfigSection = "Font"

    def __init__(self, *args, **kwds):
        kwds["style"] = wx.TAB_TRAVERSAL
        wx.Panel.__init__(self, *args, **kwds)

        self._config = EditorConfig(Application.config)

        self._enableSpellChecking = True
        self._spellChecker = None

        self.SPELL_ERROR_INDICATOR = 0

        self._spellErrorText = None
        self._spellSuggestList = []

        self._spellMaxSuggest = 10
        self._suggestMenuItems = []
        self._spellStartByteError = -1
        self._spellEndByteError = -1

        # Уже были установлены стили текста(раскраска)
        self._styleSet = False

        self.__stylebytes = None
        self.__indicatorsbytes = None

        # Начинаем раскраску кода не менее чем через это время
        # с момента его изменения
        self._DELAY = timedelta(milliseconds=300)

        # Время последней модификации текста страницы.
        # Используется для замера времени после модификации,
        # чтобы не парсить текст после каждой введенной буквы
        self._lastEdit = datetime.now() - self._DELAY * 2

        self.textCtrl = StyledTextCtrl(self, -1)

        # Создание панели поиска и ее контроллера
        self._searchPanel = SearchReplacePanel(self)
        self._searchPanelController = SearchReplaceController(
            self._searchPanel, self)
        self._searchPanel.setController(self._searchPanelController)

        self.__do_layout()
        self.__createCoders()
        self._helper = TextEditorHelper()

        self.__showlinenumbers = self._config.lineNumbers.value

        self.setDefaultSettings()
        self.__bindEvents()


    def __bindEvents(self):
        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onCopyFromEditor,
                           id=wx.ID_COPY)
        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onCutFromEditor,
                           id=wx.ID_CUT)
        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onPasteToEditor,
                           id=wx.ID_PASTE)
        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onUndo,
                           id=wx.ID_UNDO)
        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onRedo,
                           id=wx.ID_REDO)
        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onSelectAll,
                           id=wx.ID_SELECTALL)

        self.textCtrl.Bind(wx.EVT_KEY_DOWN, self.__onKeyDown)
        self.textCtrl.Bind(wx.EVT_CONTEXT_MENU, self.__onContextMenu)

        # self.textCtrl.Bind(wx.stc.EVT_STC_STYLENEEDED, self._onStyleNeeded)
        self.textCtrl.Bind(wx.EVT_IDLE, self._onStyleNeeded)
        self.Bind(EVT_APPLY_STYLE, self._onApplyStyle)

        # При перехвате этого сообщения в других классах,
        # нужно вызывать event.Skip(), чтобы это сообщение дошло сюда
        self.textCtrl.Bind(wx.stc.EVT_STC_CHANGE, self.__onChange)

    @property
    def config(self):
        return self._config

    @property
    def enableSpellChecking(self):
        return self._enableSpellChecking

    @enableSpellChecking.setter
    def enableSpellChecking(self, value):
        self._enableSpellChecking = value
        self._styleSet = False

    def __onChange(self, event):
        self._styleSet = False
        self._lastEdit = datetime.now()
        self.__setMarginWidth(self.textCtrl)

    @property
    def searchPanel(self):
        """
        Возвращает контроллер панели поиска
        """
        return self._searchPanelController

    def Print(self):
        selectedtext = self.textCtrl.GetSelectedText()
        text = self.textCtrl.GetText()

        printer = TextPrinter(self)
        printer.printout(text if len(selectedtext) == 0 else selectedtext)

    def __onCopyFromEditor(self, event):
        self.textCtrl.Copy()

    def __onCutFromEditor(self, event):
        self.textCtrl.Cut()

    def __onPasteToEditor(self, event):
        self.textCtrl.Paste()

    def __onUndo(self, event):
        self.textCtrl.Undo()

    def __onRedo(self, event):
        self.textCtrl.Redo()

    def __onSelectAll(self, event):
        self.textCtrl.SelectAll()

    def __do_layout(self):
        mainSizer = wx.FlexGridSizer(rows=2)
        mainSizer.AddGrowableRow(0)
        mainSizer.AddGrowableCol(0)

        mainSizer.Add(self.textCtrl, 0, wx.EXPAND, 0)
        mainSizer.Add(self._searchPanel, 0, wx.EXPAND, 0)
        self.SetSizer(mainSizer)

        self._searchPanel.Hide()
        self.Layout()

    def setDefaultSettings(self):
        """
        Установить стили и настройки по умолчанию в контрол StyledTextCtrl
        """
        self._spellChecker = self.getSpellChecker()

        size = self._config.fontSize.value
        faceName = self._config.fontName.value
        isBold = self._config.fontIsBold.value
        isItalic = self._config.fontIsItalic.value
        fontColor = self._config.fontColor.value
        backColor = self._config.backColor.value

        self.__showlinenumbers = self._config.lineNumbers.value
        self.textCtrl.SetEndAtLastLine(False)

        self.textCtrl.StyleSetSize(wx.stc.STC_STYLE_DEFAULT, size)
        self.textCtrl.StyleSetFaceName(wx.stc.STC_STYLE_DEFAULT, faceName)
        self.textCtrl.StyleSetBold(wx.stc.STC_STYLE_DEFAULT, isBold)
        self.textCtrl.StyleSetItalic(wx.stc.STC_STYLE_DEFAULT, isItalic)
        self.textCtrl.StyleSetForeground(wx.stc.STC_STYLE_DEFAULT, fontColor)
        self.textCtrl.StyleSetBackground(wx.stc.STC_STYLE_DEFAULT, backColor)

        self.textCtrl.StyleClearAll()

        self.textCtrl.SetCaretForeground(fontColor)
        self.textCtrl.SetCaretLineBack(backColor)
        self.textCtrl.SetWrapMode(wx.stc.STC_WRAP_WORD)
        self.textCtrl.SetWrapVisualFlags(wx.stc.STC_WRAPVISUALFLAG_END)

        self._setDefaultHotKeys()

        self.__setMarginWidth(self.textCtrl)
        self.textCtrl.SetTabWidth(self._config.tabWidth.value)

        self.enableSpellChecking = self._config.spellEnabled.value
        self._spellChecker.skipWordsWithNumbers = self.config.spellSkipDigits.value


        self.textCtrl.IndicatorSetStyle(self.SPELL_ERROR_INDICATOR,
                                        wx.stc.STC_INDIC_SQUIGGLE)
        self.textCtrl.IndicatorSetForeground(self.SPELL_ERROR_INDICATOR, "red")
        self._styleSet = False


    def _setDefaultHotKeys(self):
        self.textCtrl.CmdKeyClearAll()

        # Code from Wikidpad sources
        # Default mapping based on Scintilla's "KeyMap.cxx" file
        defaultHotKeys = (
            (wx.stc.STC_KEY_DOWN,        wx.stc.STC_SCMOD_NORM,     wx.stc.STC_CMD_LINEDOWN),
            (wx.stc.STC_KEY_DOWN,        wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_LINEDOWNEXTEND),
            (wx.stc.STC_KEY_DOWN,        wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_LINESCROLLDOWN),
            (wx.stc.STC_KEY_UP,          wx.stc.STC_SCMOD_NORM,     wx.stc.STC_CMD_LINEUP),
            (wx.stc.STC_KEY_UP,          wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_LINEUPEXTEND),
            (wx.stc.STC_KEY_UP,          wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_LINESCROLLUP),
            (ord('['),            wx.stc.STC_SCMOD_CTRL,            wx.stc.STC_CMD_PARAUP),
            (ord('['),            wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_PARAUPEXTEND),
            (ord(']'),            wx.stc.STC_SCMOD_CTRL,        wx.stc.STC_CMD_PARADOWN),
            (ord(']'),            wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_PARADOWNEXTEND),
            (wx.stc.STC_KEY_LEFT,        wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_CHARLEFT),
            (wx.stc.STC_KEY_LEFT,        wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_CHARLEFTEXTEND),
            # (wx.stc.STC_KEY_LEFT,        wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDLEFT),
            # (wx.stc.STC_KEY_LEFT,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDLEFTEXTEND),
            (wx.stc.STC_KEY_RIGHT,        wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_CHARRIGHT),
            (wx.stc.STC_KEY_RIGHT,        wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_CHARRIGHTEXTEND),
            # (wx.stc.STC_KEY_RIGHT,        wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDRIGHT),
            # (wx.stc.STC_KEY_RIGHT,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDRIGHTEXTEND),
            (ord('/'),        wx.stc.STC_SCMOD_CTRL,        wx.stc.STC_CMD_WORDPARTLEFT),
            (ord('/'),        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDPARTLEFTEXTEND),
            (ord('\\'),        wx.stc.STC_SCMOD_CTRL,        wx.stc.STC_CMD_WORDPARTRIGHT),
            (ord('\\'),        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDPARTRIGHTEXTEND),
            (wx.stc.STC_KEY_HOME,        wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_VCHOME),
            (wx.stc.STC_KEY_HOME,         wx.stc.STC_SCMOD_SHIFT,     wx.stc.STC_CMD_VCHOMEEXTEND),
            (wx.stc.STC_KEY_HOME,         wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_DOCUMENTSTART),
            (wx.stc.STC_KEY_HOME,         wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_DOCUMENTSTARTEXTEND),
            (wx.stc.STC_KEY_HOME,         wx.stc.STC_SCMOD_ALT,     wx.stc.STC_CMD_HOMEDISPLAY),
            (wx.stc.STC_KEY_END,         wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_LINEEND),
            (wx.stc.STC_KEY_END,         wx.stc.STC_SCMOD_SHIFT,     wx.stc.STC_CMD_LINEENDEXTEND),
            (wx.stc.STC_KEY_END,         wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_DOCUMENTEND),
            (wx.stc.STC_KEY_END,         wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_DOCUMENTENDEXTEND),
            (wx.stc.STC_KEY_END,         wx.stc.STC_SCMOD_ALT,     wx.stc.STC_CMD_LINEENDDISPLAY),
            (wx.stc.STC_KEY_PRIOR,        wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_PAGEUP),
            (wx.stc.STC_KEY_PRIOR,        wx.stc.STC_SCMOD_SHIFT,     wx.stc.STC_CMD_PAGEUPEXTEND),
            (wx.stc.STC_KEY_NEXT,         wx.stc.STC_SCMOD_NORM,     wx.stc.STC_CMD_PAGEDOWN),
            (wx.stc.STC_KEY_NEXT,         wx.stc.STC_SCMOD_SHIFT,     wx.stc.STC_CMD_PAGEDOWNEXTEND),
            (wx.stc.STC_KEY_DELETE,     wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_CLEAR),
            (wx.stc.STC_KEY_INSERT,         wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_EDITTOGGLEOVERTYPE),
            (wx.stc.STC_KEY_ESCAPE,      wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_CANCEL),
            (wx.stc.STC_KEY_BACK,        wx.stc.STC_SCMOD_NORM,     wx.stc.STC_CMD_DELETEBACK),
            (wx.stc.STC_KEY_BACK,        wx.stc.STC_SCMOD_SHIFT,     wx.stc.STC_CMD_DELETEBACK),
            (wx.stc.STC_KEY_BACK,         wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_UNDO),
            (ord('Z'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_UNDO),
            (ord('Y'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_REDO),
            (ord('A'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_SELECTALL),
            (wx.stc.STC_KEY_TAB,        wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_TAB),
            (wx.stc.STC_KEY_TAB,        wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_BACKTAB),
            (wx.stc.STC_KEY_RETURN,     wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_NEWLINE),
            (wx.stc.STC_KEY_RETURN,     wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_NEWLINE),
            (wx.stc.STC_KEY_ADD,         wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_ZOOMIN),
            (wx.stc.STC_KEY_SUBTRACT,    wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_ZOOMOUT),
            (wx.stc.STC_KEY_DOWN,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_LINEDOWNRECTEXTEND),
            (wx.stc.STC_KEY_UP,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_LINEUPRECTEXTEND),
            (wx.stc.STC_KEY_LEFT,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_CHARLEFTRECTEXTEND),
            (wx.stc.STC_KEY_RIGHT,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_CHARRIGHTRECTEXTEND),
            (wx.stc.STC_KEY_HOME,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_VCHOMERECTEXTEND),
            (wx.stc.STC_KEY_END,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_LINEENDRECTEXTEND),
            (wx.stc.STC_KEY_PRIOR,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_PAGEUPRECTEXTEND),
            (wx.stc.STC_KEY_NEXT,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_PAGEDOWNRECTEXTEND),

            # (wx.stc.STC_KEY_DELETE,    wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_DELLINERIGHT),
            # (wx.stc.STC_KEY_BACK,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_DELLINELEFT),
            # (wx.stc.STC_KEY_BACK,        wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_DELWORDLEFT),
            # (wx.stc.STC_KEY_DELETE,     wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_DELWORDRIGHT),
            # (wx.stc.STC_KEY_DIVIDE,    wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_SETZOOM),
            #        (ord('L'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LINECUT),
            #        (ord('L'),             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LINEDELETE),
            #        (ord('T'),             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LINECOPY),
            #        (ord('T'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LINETRANSPOSE),
            #        (ord('D'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_SELECTIONDUPLICATE),
            #        (ord('U'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LOWERCASE),
            #        (ord('U'),             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_UPPERCASE),
        )

        map(lambda key: self.textCtrl.CmdKeyAssign(key[0], key[1], key[2]),
            defaultHotKeys)

        if self._config.homeEndKeys.value == EditorConfig.HOME_END_OF_LINE:
            # Клавиши Home / End переносят курсор на начало / конец строки
            self.textCtrl.CmdKeyAssign(wx.stc.STC_KEY_HOME,
                                       0,
                                       wx.stc.STC_CMD_HOMEDISPLAY)

            self.textCtrl.CmdKeyAssign(wx.stc.STC_KEY_HOME,
                                       wx.stc.STC_SCMOD_ALT,
                                       wx.stc.STC_CMD_HOME)

            self.textCtrl.CmdKeyAssign(wx.stc.STC_KEY_END,
                                       0,
                                       wx.stc.STC_CMD_LINEENDDISPLAY)

            self.textCtrl.CmdKeyAssign(wx.stc.STC_KEY_END,
                                       wx.stc.STC_SCMOD_ALT,
                                       wx.stc.STC_CMD_LINEEND)
        else:
            # Клавиши Home / End переносят курсор на начало / конец абзаца
            self.textCtrl.CmdKeyAssign(wx.stc.STC_KEY_HOME,
                                       0,
                                       wx.stc.STC_CMD_HOME)

            self.textCtrl.CmdKeyAssign(wx.stc.STC_KEY_HOME,
                                       wx.stc.STC_SCMOD_ALT,
                                       wx.stc.STC_CMD_HOMEDISPLAY)

            self.textCtrl.CmdKeyAssign(wx.stc.STC_KEY_END,
                                       0,
                                       wx.stc.STC_CMD_LINEEND)

            self.textCtrl.CmdKeyAssign(wx.stc.STC_KEY_END,
                                       wx.stc.STC_SCMOD_ALT,
                                       wx.stc.STC_CMD_LINEENDDISPLAY)

    def __setMarginWidth(self, editor):
        """
        Установить размер левой области, где пишутся номера строк в
        зависимости от шрифта
        """
        if self.__showlinenumbers:
            editor.SetMarginWidth(0, self.__getMarginWidth())
            editor.SetMarginWidth(1, 5)
        else:
            editor.SetMarginWidth(0, 0)
            editor.SetMarginWidth(1, 8)

    def __getMarginWidth(self):
        """
        Расчет размера серой области с номером строк
        """
        fontSize = self._config.fontSize.value
        linescount = len(self.GetText().split("\n"))

        if linescount == 0:
            width = 10
        else:
            # Количество десятичных цифр в числе строк
            digits = int(math.log10(linescount) + 1)
            width = int(1.2 * fontSize * digits)

        return width

    def getPosChar(self, posBytes):
        return len(self.textCtrl.GetTextRange(0, posBytes))

    def __createCoders(self):
        encoding = outwiker.core.system.getOS().inputEncoding
        self.mbcsEnc = codecs.getencoder(encoding)

    def __onKeyDown(self, event):
        key = event.GetKeyCode()

        if key == wx.WXK_ESCAPE:
            self._searchPanel.Close()

        event.Skip()

    def AddText(self, text):
        self.textCtrl.AddText(text)

    def replaceText(self, text):
        self.textCtrl.ReplaceSelection(text)

    def toddleLinePrefix(self, line, prefix):
        """
        If line with number "line" starts with prefix, prefix will be removed
        else prefix will be added.

        Added in OutWiker 2.0.0.795.
        """
        assert line < self.GetLineCount()
        line_text = self.GetLine(line)
        if line_text.startswith(prefix):
            line_text = line_text[len(prefix):]
        else:
            line_text = prefix + line_text
        self.SetLine(line, line_text)

    def toddleSelectedLinesPrefix(self, prefix):
        """
        Apply toddleLinePrefix method to selected lines

        Added in OutWiker 2.0.0.795.
        """
        self.BeginUndoAction()
        old_sel_start = self.GetSelectionStart()
        old_sel_end = self.GetSelectionEnd()

        first_line, last_line = self.GetSelectionLines()
        map(lambda n: self.toddleLinePrefix(n, prefix),
            xrange(first_line, last_line + 1))

        if old_sel_start != old_sel_end:
            new_sel_start = self.GetLineStartPosition(first_line)
            new_sel_end = self.GetLineEndPosition(last_line)
        else:
            new_sel_start = new_sel_end = self.GetLineEndPosition(last_line)

        self.SetSelection(new_sel_start, new_sel_end)
        self.EndUndoAction()

    def turnText(self, lefttext, righttext):
        selText = self.textCtrl.GetSelectedText()
        newtext = lefttext + selText + righttext
        self.textCtrl.ReplaceSelection(newtext)

        currPos = self.GetSelectionEnd()
        if len(selText) == 0:
            """
            Если не оборачиваем текст, а делаем пустой тег, то поместим каретку до закрывающегося тега
            """
            newpos = currPos - len(righttext)
            self.SetSelection(newpos, newpos)
        else:
            self.SetSelection(currPos - len(selText) - len(righttext),
                              currPos - len(righttext))

    def escapeHtml(self):
        selText = self.textCtrl.GetSelectedText()
        text = cgi.escape(selText, quote=False)
        self.textCtrl.ReplaceSelection(text)

    def SetReadOnly(self, readonly):
        self.textCtrl.SetReadOnly(readonly)

    def GetReadOnly(self):
        return self.textCtrl.GetReadOnly()

    def GetText(self):
        return self.textCtrl.GetText()

    def SetText(self, text):
        self.textCtrl.SetText(text)

    def EmptyUndoBuffer(self):
        self.textCtrl.EmptyUndoBuffer()

    def GetSelectedText(self):
        return self.textCtrl.GetSelectedText()

    def GetCurrentLine(self):
        return self.textCtrl.GetCurrentLine()

    def ScrollToLine(self, line):
        self.textCtrl.ScrollToLine(line)

    def SetSelection(self, start, end):
        """
        start и end в символах, а не в байтах, в отличие от исходного
        StyledTextCtrl
        """
        startText = self.GetText()[:start]
        endText = self.GetText()[:end]

        firstByte = self._helper.calcByteLen(startText)
        endByte = self._helper.calcByteLen(endText)

        self.textCtrl.SetSelection(firstByte, endByte)

    def GotoPos(self, pos):
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        self.textCtrl.GotoPos(pos_bytes)

    def GetCurrentPosition(self):
        """
        Возвращает номер символа(а не байта), перед которых находится курсор
        """
        return self.__calcCharPos(self.textCtrl.GetCurrentPos())

    def GetSelectionStart(self):
        """
        Возвращает позицию начала выбранной области в символах, а не в байтах
        """
        return self.__calcCharPos(self.textCtrl.GetSelectionStart())

    def GetSelectionLines(self):
        """
        Return tuple (first selected line, last selected line)

        Added in OutWiker 2.0.0.795.
        """
        start_bytes = self.textCtrl.GetSelectionStart()
        end_bytes = self.textCtrl.GetSelectionEnd()
        return (self.textCtrl.LineFromPosition(start_bytes),
                self.textCtrl.LineFromPosition(end_bytes))

    def GetSelectionEnd(self):
        """
        Возвращает позицию конца выбранной области в символах, а не в байтах
        """
        return self.__calcCharPos(self.textCtrl.GetSelectionEnd())

    def SetFocus(self):
        self.textCtrl.SetFocus()
        self.textCtrl.SetSTCFocus(True)

    def GetLine(self, line):
        """
        Return line with the "line" number. \n included.
        Added in OutWiker 2.0.0.795
        """
        return self.textCtrl.GetLine(line)

    def SetLine(self, line, newline):
        """
        Replace line with the number "line" newline.
        Newline will be ended with "\n" else line will be joined with next line

        Added in OutWiker 2.0.0.795
        """
        linecount = self.GetLineCount()
        assert line < linecount

        line_start_bytes = self.textCtrl.PositionFromLine(line)
        line_end_bytes = self.textCtrl.PositionFromLine(line + 1)
        self.textCtrl.Replace(line_start_bytes, line_end_bytes, newline)

    def GetLineCount(self):
        return self.textCtrl.GetLineCount()

    def GetLineStartPosition(self, line):
        """
        Retrieve the position at the start of a line in symbols (not bytes)

        Added in OutWiker 2.0.0.795
        """
        return self.__calcCharPos(self.textCtrl.PositionFromLine(line))

    def GetLineEndPosition(self, line):
        """
        Get the position after the last visible characters on a line
            in symbols (not bytes)

        Added in OutWiker 2.0.0.795
        """
        return self.__calcCharPos(self.textCtrl.GetLineEndPosition(line))

    def MoveSelectedLinesUp(self):
        """
        Move the selected lines up one line,
        shifting the line above after the selection.

        Added in OutWiker 2.0.0.795
        """
        self.textCtrl.MoveSelectedLinesUp()

    def MoveSelectedLinesDown(self):
        """
        Move the selected lines down one line,
        shifting the line below before the selection.

        Added in OutWiker 2.0.0.795
        """
        self.textCtrl.MoveSelectedLinesDown()

    def LineDuplicate(self):
        """
        Duplicate the current line.

        Added in OutWiker 2.0.0.795
        """
        self.textCtrl.LineDuplicate()

    def LineDelete(self):
        """
        Delete the current line.

        Added in OutWiker 2.0.0.795
        """
        self.textCtrl.LineDelete()

    def BeginUndoAction(self):
        """
        Added in OutWiker 2.0.0.797
        """
        self.textCtrl.BeginUndoAction()

    def EndUndoAction(self):
        """
        Added in OutWiker 2.0.0.797
        """
        self.textCtrl.EndUndoAction()

    def JoinLines(self):
        """
        Join selected lines

        Added in OutWiker 2.0.0.797
        """
        first_line, last_line = self.GetSelectionLines()
        if first_line != last_line:
            last_line -= 1

        self.BeginUndoAction()

        for _ in range(first_line, last_line + 1):
            line = self.GetLine(first_line).replace(u'\r\n', u'\n')
            if line.endswith(u'\n'):
                newline = line[:-1]
                self.SetLine(first_line, newline)

        new_sel_pos = self.GetLineEndPosition(first_line)
        self.SetSelection(new_sel_pos, new_sel_pos)

        self.EndUndoAction()

    def DelWordLeft(self):
        """
        Added in OutWiker 2.0.0.797
        """
        self.textCtrl.DelWordLeft()

    def DelWordRight(self):
        """
        Added in OutWiker 2.0.0.797
        """
        self.textCtrl.DelWordRight()

    def DelLineLeft(self):
        """
        Delete back from the current position to the start of the line

        Added in OutWiker 2.0.0.797
        """
        self.textCtrl.DelLineLeft()

    def DelLineRight(self):
        """
        Delete forwards from the current position to the end of the line

        Added in OutWiker 2.0.0.797
        """
        self.textCtrl.DelLineRight()

    def WordLeft(self):
        """
        Added in outwiker.gui 1.2
        """
        self.textCtrl.WordLeft()

    def WordRight(self):
        """
        Added in outwiker.gui 1.2
        """
        self.textCtrl.WordRight()

    def WordLeftEnd(self):
        """
        Added in outwiker.gui 1.2
        """
        self.textCtrl.WordLeftEnd()

    def WordRightEnd(self):
        """
        Added in outwiker.gui 1.2
        """
        self.textCtrl.WordRightEnd()

    def WordLeftExtend(self):
        """
        Added in outwiker.gui 1.2
        """
        self.textCtrl.WordLeftExtend()

    def WordRightExtend(self):
        """
        Added in outwiker.gui 1.2
        """
        self.textCtrl.WordRightExtend()

    def GotoWordStart(self):
        """
        Added in outwiker.gui 1.2
        """
        self.WordRight()
        self.WordLeft()

    def GotoWordEnd(self):
        """
        Added in outwiker.gui 1.2
        """
        self.WordLeftEnd()
        self.WordRightEnd()

    def ScrollLineToCursor(self):
        """
        Added in outwiker.gui 1.1
        """
        maxlines = self.textCtrl.LinesOnScreen()
        line = self.GetCurrentLine()
        if line >= maxlines:
            delta = min(10, maxlines / 3)
            line -= delta
            if line < 0:
                line = 0
            self.ScrollToLine(line)

    def WordStartPosition(self, pos):
        """
        Added in outwiker.gui 1.2
        """
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        result_bytes = self.textCtrl.WordStartPosition(pos_bytes, True)
        return self.getPosChar(result_bytes)

    def WordEndPosition(self, pos):
        """
        Added in outwiker.gui 1.2
        """
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        result_bytes = self.textCtrl.WordEndPosition(pos_bytes, True)
        return self.getPosChar(result_bytes)

    def GetWord(self, pos):
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        word_start_bytes = self.textCtrl.WordStartPosition(pos_bytes, True)
        word_end_bytes = self.textCtrl.WordEndPosition(pos_bytes, True)
        word = self.textCtrl.GetTextRange(word_start_bytes, word_end_bytes)
        return word

    def __calcCharPos(self, pos_bytes):
        """
        Пересчет позиции в байтах в позицию в символах
        """
        text_left = self.textCtrl.GetTextRange(0, pos_bytes)
        currpos = len(text_left)
        return currpos

    def _getTextForParse(self):
        # Табуляция в редакторе считается за несколько символов
        return self.textCtrl.GetText().replace("\t", " ")

    def runSpellChecking(self, stylelist, fullText, start, end):
        errors = self._spellChecker.findErrors(fullText[start: end])

        for word, err_start, err_end in errors:
            self._helper.setSpellError(stylelist,
                                       fullText,
                                       err_start + start,
                                       err_end + start)

    def _onStyleNeeded(self, event):
        if (not self._styleSet and
                datetime.now() - self._lastEdit >= self._DELAY):
            page = Application.selectedPage
            text = self._getTextForParse()
            params = EditorStyleNeededParams(self,
                                             text,
                                             self._enableSpellChecking)
            Application.onEditorStyleNeeded(page, params)
            self._styleSet = True

    def _onApplyStyle(self, event):
        if event.text == self._getTextForParse():
            startByte = self._helper.calcBytePos(event.text, event.start)
            endByte = self._helper.calcBytePos(event.text, event.end)
            lenBytes = endByte - startByte

            textlength = self._helper.calcByteLen(event.text)
            self.__stylebytes = [0] * textlength

            if event.stylebytes is not None:
                self.__stylebytes = event.stylebytes

            if event.indicatorsbytes is not None:
                self.__stylebytes = [item1 | item2
                                     for item1, item2
                                     in zip(self.__stylebytes,
                                            event.indicatorsbytes)]

            stylebytesstr = "".join([chr(byte) for byte in self.__stylebytes])


            if event.stylebytes is not None:
                self.textCtrl.StartStyling(startByte,
                                           0xff ^ wx.stc.STC_INDICS_MASK)
                self.textCtrl.SetStyleBytes(lenBytes,
                                            stylebytesstr[startByte:endByte])

            if event.indicatorsbytes is not None:
                self.textCtrl.StartStyling(startByte, wx.stc.STC_INDICS_MASK)
                self.textCtrl.SetStyleBytes(lenBytes,
                                            stylebytesstr[startByte:endByte])

            self._styleSet = True

    def getSpellChecker(self):
        langlist = self._getDictsFromConfig()
        spellDirList = outwiker.core.system.getSpellDirList()

        spellChecker = SpellChecker(Application, langlist, spellDirList)
        spellChecker.addCustomDict(os.path.join(spellDirList[-1], CUSTOM_DICT_FILE_NAME))

        return spellChecker

    def _getDictsFromConfig(self):
        dictsStr = self._config.spellCheckerDicts.value
        return [item.strip()
                for item
                in dictsStr.split(',')
                if item.strip()]

    def __onContextMenu(self, event):
        point = self.textCtrl.ScreenToClient(event.GetPosition())
        pos_byte = self.textCtrl.PositionFromPoint(point)

        popupMenu = TextEditorMenu()
        self._appendSpellMenuItems(popupMenu, pos_byte)

        Application.onEditorPopupMenu(
            Application.selectedPage,
            EditorPopupMenuParams(self, popupMenu, point, pos_byte)
        )

        self.textCtrl.PopupMenu(popupMenu)
        popupMenu.Destroy()

    def getCachedStyleBytes(self):
        return self.__stylebytes

    def __onAddWordToDict(self, event):
        if self._spellErrorText is not None:
            self.__addWordToDict(self._spellErrorText)

    def __onAddWordLowerToDict(self, event):
        if self._spellErrorText is not None:
            self.__addWordToDict(self._spellErrorText.lower())

    def __addWordToDict(self, word):
        self._spellChecker.addToCustomDict(0, word)
        self._spellErrorText = None
        self._styleSet = False

    def _appendSpellMenuItems(self, menu, pos_byte):
        stylebytes = self.getCachedStyleBytes()
        if stylebytes is None:
            return

        stylebytes_len = len(stylebytes)

        if (stylebytes is None or
                pos_byte >= stylebytes_len or
                stylebytes[pos_byte] & self._helper.SPELL_ERROR_INDICATOR_MASK == 0):
            return

        endSpellError = startSpellError = pos_byte

        while (startSpellError >= 0 and
               stylebytes[startSpellError] & self._helper.SPELL_ERROR_INDICATOR_MASK != 0):
            startSpellError -= 1


        while (endSpellError < stylebytes_len and
               stylebytes[endSpellError] & self._helper.SPELL_ERROR_INDICATOR_MASK != 0):
            endSpellError += 1

        self._spellStartByteError = startSpellError + 1
        self._spellEndByteError = endSpellError
        self._spellErrorText = self.textCtrl.GetTextRange(
            self._spellStartByteError,
            self._spellEndByteError)

        self._spellSuggestList = self._spellChecker.getSuggest(self._spellErrorText)[:self._spellMaxSuggest]

        menu.AppendSeparator()
        self._suggestMenuItems = menu.AppendSpellSubmenu(self._spellErrorText,
                                                         self._spellSuggestList)

        for menuItem in self._suggestMenuItems:
            self.textCtrl.Bind(wx.EVT_MENU, self.__onSpellSuggest, menuItem)

        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onAddWordToDict,
                           id=menu.ID_ADD_WORD)

        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onAddWordLowerToDict,
                           id=menu.ID_ADD_WORD_LOWER)


    def __onSpellSuggest(self, event):
        word = event.GetEventObject().GetLabelText(event.GetId())

        self.textCtrl.SetSelection(self._spellStartByteError,
                                   self._spellEndByteError)
        self.textCtrl.ReplaceSelection(word)
예제 #12
0
class TextEditorBase(wx.Panel):
    def __init__(self, parent):
        super(TextEditorBase, self).__init__(parent, style=0)
        self.textCtrl = StyledTextCtrl(self, -1)

        # Создание панели поиска и ее контроллера
        self._searchPanel = SearchReplacePanel(self)
        self._searchPanelController = SearchReplaceController(
            self._searchPanel,
            self)
        self._searchPanel.setController(self._searchPanelController)

        self._do_layout()

        self.__createCoders()
        self._helper = TextEditorHelper()
        self._bind()
        self._setDefaultSettings()

    def _bind(self):
        self.textCtrl.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)

    def _do_layout(self):
        mainSizer = wx.FlexGridSizer(rows=2, cols=0, vgap=0, hgap=0)
        mainSizer.AddGrowableRow(0)
        mainSizer.AddGrowableCol(0)

        mainSizer.Add(self.textCtrl, 0, wx.EXPAND, 0)
        mainSizer.Add(self._searchPanel, 0, wx.EXPAND, 0)
        self.SetSizer(mainSizer)

        self._searchPanel.Hide()
        self.Layout()

    def __createCoders(self):
        encoding = outwiker.core.system.getOS().inputEncoding
        self.mbcsEnc = codecs.getencoder(encoding)

    def onKeyDown(self, event):
        key = event.GetKeyCode()

        if key == wx.WXK_ESCAPE:
            self._searchPanel.Close()

        event.Skip()

    def _setDefaultSettings(self):
        self.textCtrl.SetEndAtLastLine(False)
        self.textCtrl.StyleClearAll()
        self.textCtrl.SetWrapMode(wx.stc.STC_WRAP_WORD)
        self.textCtrl.SetWrapVisualFlags(wx.stc.STC_WRAPVISUALFLAG_END)
        self.textCtrl.SetTabWidth(4)
        self._setDefaultHotKeys()

    def _setDefaultHotKeys(self):
        self.textCtrl.CmdKeyClearAll()

        # Clear Cmd keys for Ubuntu
        for key in list(range(ord('A'), ord('Z') + 1)) + list(range(ord('0'), ord('9') + 1)):
            self.textCtrl.CmdKeyClear(key, wx.stc.STC_SCMOD_ALT | wx.stc.STC_SCMOD_CTRL)
            self.textCtrl.CmdKeyClear(key, wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT | wx.stc.STC_SCMOD_CTRL)

        self.textCtrl.CmdKeyClear(wx.stc.STC_KEY_UP, wx.stc.STC_SCMOD_CTRL)
        self.textCtrl.CmdKeyClear(wx.stc.STC_KEY_DOWN, wx.stc.STC_SCMOD_CTRL)

        # Code from Wikidpad sources
        # Default mapping based on Scintilla's "KeyMap.cxx" file
        defaultHotKeys = (
            (wx.stc.STC_KEY_DOWN,        wx.stc.STC_SCMOD_NORM,     wx.stc.STC_CMD_LINEDOWN),
            (wx.stc.STC_KEY_UP,          wx.stc.STC_SCMOD_NORM,     wx.stc.STC_CMD_LINEUP),
            # (wx.stc.STC_KEY_DOWN,        wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_LINESCROLLDOWN),
            # (wx.stc.STC_KEY_UP,          wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_LINESCROLLUP),
            (wx.stc.STC_KEY_UP,          wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_LINEUPEXTEND),
            (wx.stc.STC_KEY_DOWN,        wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_LINEDOWNEXTEND),

            (ord('['),            wx.stc.STC_SCMOD_CTRL,            wx.stc.STC_CMD_PARAUP),
            (ord('['),            wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_PARAUPEXTEND),
            (ord(']'),            wx.stc.STC_SCMOD_CTRL,        wx.stc.STC_CMD_PARADOWN),
            (ord(']'),            wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_PARADOWNEXTEND),
            (wx.stc.STC_KEY_LEFT,        wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_CHARLEFT),
            (wx.stc.STC_KEY_LEFT,        wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_CHARLEFTEXTEND),
            # (wx.stc.STC_KEY_LEFT,        wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDLEFT),
            # (wx.stc.STC_KEY_LEFT,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDLEFTEXTEND),
            (wx.stc.STC_KEY_RIGHT,        wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_CHARRIGHT),
            (wx.stc.STC_KEY_RIGHT,        wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_CHARRIGHTEXTEND),
            # (wx.stc.STC_KEY_RIGHT,        wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDRIGHT),
            # (wx.stc.STC_KEY_RIGHT,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDRIGHTEXTEND),
            (ord('/'),        wx.stc.STC_SCMOD_CTRL,        wx.stc.STC_CMD_WORDPARTLEFT),
            (ord('/'),        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDPARTLEFTEXTEND),
            (ord('\\'),        wx.stc.STC_SCMOD_CTRL,        wx.stc.STC_CMD_WORDPARTRIGHT),
            (ord('\\'),        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_WORDPARTRIGHTEXTEND),
            (wx.stc.STC_KEY_HOME,        wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_VCHOME),
            (wx.stc.STC_KEY_HOME,         wx.stc.STC_SCMOD_SHIFT,     wx.stc.STC_CMD_VCHOMEEXTEND),
            (wx.stc.STC_KEY_HOME,         wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_DOCUMENTSTART),
            (wx.stc.STC_KEY_HOME,         wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_DOCUMENTSTARTEXTEND),
            (wx.stc.STC_KEY_HOME,         wx.stc.STC_SCMOD_ALT,     wx.stc.STC_CMD_HOMEDISPLAY),
            (wx.stc.STC_KEY_END,         wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_LINEEND),
            (wx.stc.STC_KEY_END,         wx.stc.STC_SCMOD_SHIFT,     wx.stc.STC_CMD_LINEENDEXTEND),
            (wx.stc.STC_KEY_END,         wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_DOCUMENTEND),
            (wx.stc.STC_KEY_END,         wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_DOCUMENTENDEXTEND),
            (wx.stc.STC_KEY_END,         wx.stc.STC_SCMOD_ALT,     wx.stc.STC_CMD_LINEENDDISPLAY),
            (wx.stc.STC_KEY_PRIOR,        wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_PAGEUP),
            (wx.stc.STC_KEY_PRIOR,        wx.stc.STC_SCMOD_SHIFT,     wx.stc.STC_CMD_PAGEUPEXTEND),
            (wx.stc.STC_KEY_NEXT,         wx.stc.STC_SCMOD_NORM,     wx.stc.STC_CMD_PAGEDOWN),
            (wx.stc.STC_KEY_NEXT,         wx.stc.STC_SCMOD_SHIFT,     wx.stc.STC_CMD_PAGEDOWNEXTEND),
            (wx.stc.STC_KEY_DELETE,     wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_CLEAR),
            (wx.stc.STC_KEY_INSERT,         wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_EDITTOGGLEOVERTYPE),
            (wx.stc.STC_KEY_ESCAPE,      wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_CANCEL),
            (wx.stc.STC_KEY_BACK,        wx.stc.STC_SCMOD_NORM,     wx.stc.STC_CMD_DELETEBACK),
            (wx.stc.STC_KEY_BACK,        wx.stc.STC_SCMOD_SHIFT,     wx.stc.STC_CMD_DELETEBACK),
            (wx.stc.STC_KEY_BACK,         wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_UNDO),
            (ord('Z'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_UNDO),
            (ord('Y'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_REDO),
            (ord('A'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_SELECTALL),

            (wx.stc.STC_KEY_INSERT,             wx.stc.STC_SCMOD_CTRL | wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_COPY),
            (wx.stc.STC_KEY_INSERT,             wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_PASTE),
            (ord('C'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_COPY),
            (ord('X'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_CUT),
            (ord('V'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_PASTE),

            (wx.stc.STC_KEY_TAB,        wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_TAB),
            (wx.stc.STC_KEY_TAB,        wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_BACKTAB),
            (wx.stc.STC_KEY_RETURN,     wx.stc.STC_SCMOD_NORM,    wx.stc.STC_CMD_NEWLINE),
            (wx.stc.STC_KEY_RETURN,     wx.stc.STC_SCMOD_SHIFT,    wx.stc.STC_CMD_NEWLINE),
            (wx.stc.STC_KEY_ADD,         wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_ZOOMIN),
            (wx.stc.STC_KEY_SUBTRACT,    wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_ZOOMOUT),
            (wx.stc.STC_KEY_DOWN,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_LINEDOWNRECTEXTEND),
            (wx.stc.STC_KEY_UP,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_LINEUPRECTEXTEND),
            (wx.stc.STC_KEY_LEFT,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_CHARLEFTRECTEXTEND),
            (wx.stc.STC_KEY_RIGHT,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_CHARRIGHTRECTEXTEND),
            (wx.stc.STC_KEY_HOME,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_VCHOMERECTEXTEND),
            (wx.stc.STC_KEY_END,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_LINEENDRECTEXTEND),
            (wx.stc.STC_KEY_PRIOR,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_PAGEUPRECTEXTEND),
            (wx.stc.STC_KEY_NEXT,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_ALT,    wx.stc.STC_CMD_PAGEDOWNRECTEXTEND),

            # (wx.stc.STC_KEY_DELETE,    wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_DELLINERIGHT),
            # (wx.stc.STC_KEY_BACK,        wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_DELLINELEFT),
            # (wx.stc.STC_KEY_BACK,        wx.stc.STC_SCMOD_CTRL,     wx.stc.STC_CMD_DELWORDLEFT),
            # (wx.stc.STC_KEY_DELETE,     wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_DELWORDRIGHT),
            # (wx.stc.STC_KEY_DIVIDE,    wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_SETZOOM),
            #        (ord('L'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LINECUT),
            #        (ord('L'),             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LINEDELETE),
            #        (ord('T'),             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LINECOPY),
            #        (ord('T'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LINETRANSPOSE),
            #        (ord('D'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_SELECTIONDUPLICATE),
            #        (ord('U'),             wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_LOWERCASE),
            #        (ord('U'),             wx.stc.STC_SCMOD_SHIFT | wx.stc.STC_SCMOD_CTRL,    wx.stc.STC_CMD_UPPERCASE),
        )

        [self.textCtrl.CmdKeyAssign(*key) for key in defaultHotKeys]

    @property
    def searchPanel(self):
        """
        Возвращает контроллер панели поиска
        """
        return self._searchPanelController

    def Print(self):
        selectedtext = self.textCtrl.GetSelectedText()
        text = self.textCtrl.GetText()

        printer = TextPrinter(self)
        printer.printout(text if len(selectedtext) == 0 else selectedtext)

    def getPosChar(self, posBytes):
        return len(self.textCtrl.GetTextRange(0, posBytes))

    def AddText(self, text):
        self.textCtrl.AddText(text)

    def replaceText(self, text):
        self.textCtrl.ReplaceSelection(text)

    def toddleLinePrefix(self, line, prefix):
        """
        If line with number "line" starts with prefix, prefix will be removed
        else prefix will be added.
        """
        assert line < self.GetLineCount()
        line_text = self.GetLine(line)
        if line_text.startswith(prefix):
            line_text = line_text[len(prefix):]
        else:
            line_text = prefix + line_text
        self.SetLine(line, line_text)

    def toddleSelectedLinesPrefix(self, prefix):
        """
        Apply toddleLinePrefix method to selected lines
        """
        self.BeginUndoAction()
        old_sel_start = self.GetSelectionStart()
        old_sel_end = self.GetSelectionEnd()

        first_line, last_line = self.GetSelectionLines()
        [self.toddleLinePrefix(n, prefix)
         for n
         in range(first_line, last_line + 1)]

        if old_sel_start != old_sel_end:
            new_sel_start = self.GetLineStartPosition(first_line)
            new_sel_end = self.GetLineEndPosition(last_line)
        else:
            new_sel_start = new_sel_end = self.GetLineEndPosition(last_line)

        self.SetSelection(new_sel_start, new_sel_end)
        self.EndUndoAction()

    def turnText(self, lefttext, righttext):
        selText = self.textCtrl.GetSelectedText()
        newtext = lefttext + selText + righttext
        self.textCtrl.ReplaceSelection(newtext)

        currPos = self.GetSelectionEnd()
        if len(selText) == 0:
            # Если не оборачиваем текст, а делаем пустой тег,
            # то поместим каретку до закрывающегося тега
            newpos = currPos - len(righttext)
            self.SetSelection(newpos, newpos)
        else:
            self.SetSelection(currPos - len(selText) - len(righttext),
                              currPos - len(righttext))

    def escapeHtml(self):
        selText = self.textCtrl.GetSelectedText()
        text = html.escape(selText, quote=False)
        self.textCtrl.ReplaceSelection(text)

    def SetReadOnly(self, readonly):
        self.textCtrl.SetReadOnly(readonly)

    def GetReadOnly(self) -> bool:
        return self.textCtrl.GetReadOnly()

    def GetText(self) -> str:
        return self.textCtrl.GetText()

    def SetText(self, text: str) -> None:
        self.textCtrl.SetText(text)

    def EmptyUndoBuffer(self):
        self.textCtrl.EmptyUndoBuffer()

    def GetSelectedText(self) -> str:
        return self.textCtrl.GetSelectedText()

    def GetCurrentLine(self) -> int:
        '''
        Returns the line number of the line with the caret.
        '''
        return self.textCtrl.GetCurrentLine()

    def GetCurrentLineText(self) -> str:
        '''
        Retrieve the text of the line containing the caret.
        '''
        return self.textCtrl.GetCurLine()[0]

    def ScrollToLine(self, line):
        self.textCtrl.ScrollToLine(line)

    def SetSelection(self, start, end):
        """
        start и end в символах, а не в байтах, в отличие от исходного
        StyledTextCtrl
        """
        startText = self.GetText()[:start]
        endText = self.GetText()[:end]

        firstByte = self._helper.calcByteLen(startText)
        endByte = self._helper.calcByteLen(endText)

        self.textCtrl.SetSelection(firstByte, endByte)

    def GotoPos(self, pos):
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        self.textCtrl.GotoPos(pos_bytes)

    def GetCurrentPosition(self):
        """
        Возвращает номер символа(а не байта), перед которых находится курсор
        """
        return self._calcCharPos(self.textCtrl.GetCurrentPos())

    def GetSelectionStart(self):
        """
        Возвращает позицию начала выбранной области в символах, а не в байтах
        """
        return self._calcCharPos(self.textCtrl.GetSelectionStart())

    def GetSelectionLines(self):
        """
        Return tuple (first selected line, last selected line)
        """
        start_bytes = self.textCtrl.GetSelectionStart()
        end_bytes = self.textCtrl.GetSelectionEnd()
        return (self.textCtrl.LineFromPosition(start_bytes),
                self.textCtrl.LineFromPosition(end_bytes))

    def GetSelectionEnd(self):
        """
        Возвращает позицию конца выбранной области в символах, а не в байтах
        """
        return self._calcCharPos(self.textCtrl.GetSelectionEnd())

    def SetFocus(self):
        self.textCtrl.SetFocus()
        self.textCtrl.SetSTCFocus(True)

    def GetLine(self, line):
        """
        Return line with the "line" number. \n included.
        """
        return self.textCtrl.GetLine(line)

    def SetLine(self, line, newline):
        """
        Replace line with the number "line" newline.
        Newline will be ended with "\n" else line will be joined with next line
        """
        linecount = self.GetLineCount()
        assert line < linecount

        line_start_bytes = self.textCtrl.PositionFromLine(line)
        line_end_bytes = self.textCtrl.PositionFromLine(line + 1)
        self.textCtrl.Replace(line_start_bytes, line_end_bytes, newline)

    def GetLineCount(self):
        return self.textCtrl.GetLineCount()

    def GetLineStartPosition(self, line):
        """
        Retrieve the position at the start of a line in symbols (not bytes)
        """
        return self._calcCharPos(self.textCtrl.PositionFromLine(line))

    def GetLineEndPosition(self, line: int) -> int:
        """
        Get the position after the last visible characters on a line
            in symbols (not bytes)
        """
        return self._calcCharPos(self.textCtrl.GetLineEndPosition(line))

    def MoveSelectedLinesUp(self):
        """
        Move the selected lines up one line,
        shifting the line above after the selection.
        """
        self.textCtrl.MoveSelectedLinesUp()

    def MoveSelectedLinesDown(self):
        """
        Move the selected lines down one line,
        shifting the line below before the selection.
        """
        self.textCtrl.MoveSelectedLinesDown()

    def LineDuplicate(self):
        """
        Duplicate the current line.
        """
        self.textCtrl.LineDuplicate()

    def LineDelete(self):
        """
        Delete the current line.
        """
        self.textCtrl.LineDelete()

    def BeginUndoAction(self):
        self.textCtrl.BeginUndoAction()

    def EndUndoAction(self):
        self.textCtrl.EndUndoAction()

    def JoinLines(self):
        """
        Join selected lines
        """
        first_line, last_line = self.GetSelectionLines()
        if first_line != last_line:
            last_line -= 1

        self.BeginUndoAction()

        for _ in range(first_line, last_line + 1):
            line = self.GetLine(first_line).replace(u'\r\n', u'\n')
            if line.endswith(u'\n'):
                newline = line[:-1]
                self.SetLine(first_line, newline)

        new_sel_pos = self.GetLineEndPosition(first_line)
        self.SetSelection(new_sel_pos, new_sel_pos)

        self.EndUndoAction()

    def DelWordLeft(self):
        self.textCtrl.DelWordLeft()

    def DelWordRight(self):
        self.textCtrl.DelWordRight()

    def DelLineLeft(self):
        """
        Delete back from the current position to the start of the line
        """
        self.textCtrl.DelLineLeft()

    def DelLineRight(self):
        """
        Delete forwards from the current position to the end of the line
        """
        self.textCtrl.DelLineRight()

    def WordLeft(self):
        self.textCtrl.WordLeft()

    def WordRight(self):
        self.textCtrl.WordRight()

    def WordLeftEnd(self):
        self.textCtrl.WordLeftEnd()

    def WordRightEnd(self):
        self.textCtrl.WordRightEnd()

    def WordLeftExtend(self):
        self.textCtrl.WordLeftExtend()

    def WordRightExtend(self):
        self.textCtrl.WordRightExtend()

    def GotoWordStart(self):
        self.WordRight()
        self.WordLeft()

    def GotoWordEnd(self):
        self.WordLeftEnd()
        self.WordRightEnd()

    def ScrollLineToCursor(self):
        maxlines = self.textCtrl.LinesOnScreen()
        line = self.GetCurrentLine()
        if line >= maxlines:
            delta = min(10, maxlines / 3)
            line -= delta
            if line < 0:
                line = 0
            self.ScrollToLine(line)

    def WordStartPosition(self, pos):
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        result_bytes = self.textCtrl.WordStartPosition(pos_bytes, True)
        return self.getPosChar(result_bytes)

    def WordEndPosition(self, pos):
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        result_bytes = self.textCtrl.WordEndPosition(pos_bytes, True)
        return self.getPosChar(result_bytes)

    def GetWord(self, pos):
        pos_bytes = self._helper.calcBytePos(self.GetText(), pos)
        word_start_bytes = self.textCtrl.WordStartPosition(pos_bytes, True)
        word_end_bytes = self.textCtrl.WordEndPosition(pos_bytes, True)
        word = self.textCtrl.GetTextRange(word_start_bytes, word_end_bytes)
        return word

    def GetLineSelStartPosition(self, line: int) -> int:
        '''
        Retrieve the position of the end of the selection at the given line
        (wx.stc.STC_INVALID_POSITION if no selection on this line).
        '''
        return self.textCtrl.GetLineSelStartPosition(line)

    def GetLineSelEndPosition(self, line: int) -> int:
        '''
        Retrieve the position of the end of the selection at the given line
        (wx.stc.STC_INVALID_POSITION if no selection on this line).
        '''
        return self.textCtrl.GetLineSelEndPosition(line)

    def _calcCharPos(self, pos_bytes):
        """
        Пересчет позиции в байтах в позицию в символах
        """
        text_left = self.textCtrl.GetTextRange(0, pos_bytes)
        currpos = len(text_left)
        return currpos

    def _getTextForParse(self):
        # Табуляция в редакторе считается за несколько символов
        return self.textCtrl.GetText().replace("\t", " ")

    def _bindStandardMenuItems(self):
        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onCopyFromEditor,
                           id=wx.ID_COPY)
        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onCutFromEditor,
                           id=wx.ID_CUT)
        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onPasteToEditor,
                           id=wx.ID_PASTE)
        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onUndo,
                           id=wx.ID_UNDO)
        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onRedo,
                           id=wx.ID_REDO)
        self.textCtrl.Bind(wx.EVT_MENU,
                           self.__onSelectAll,
                           id=wx.ID_SELECTALL)

    def __onCopyFromEditor(self, event):
        self.textCtrl.Copy()

    def __onCutFromEditor(self, event):
        self.textCtrl.Cut()

    def __onPasteToEditor(self, event):
        self.textCtrl.Paste()

    def __onUndo(self, event):
        self.textCtrl.Undo()

    def __onRedo(self, event):
        self.textCtrl.Redo()

    def __onSelectAll(self, event):
        self.textCtrl.SelectAll()