Source code for openalea.oalab.editor.text_editor

# -*- python -*-
#
#       OpenAlea.OALab: Multi-Paradigm GUI
#
#       Copyright 2013 INRIA - CIRAD - INRA
#
#       File author(s): Julien Coste <julien.coste@inria.fr>
#
#       File contributor(s):
#
#       Distributed under the Cecill-C License.
#       See accompanying file LICENSE.txt or copy at
#           http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
#
#       OpenAlea WebSite : http://openalea.gforge.inria.fr
#
###############################################################################
__revision__ = ""

from openalea.vpltk.qt import QtCore, QtGui
from openalea.core.path import path
from openalea.oalab.editor.search import SearchWidget
from openalea.oalab.editor.completion import DictionaryCompleter
from openalea.oalab.editor.line_number import Margin
from openalea.oalab.editor.goto import GoToWidget
from openalea.core import logger
from openalea.core import settings

has_flake8 = False
try:
    from flake8 import run
    has_flake8 = True
except ImportError:
    logger.warning("You should install **flake8** (using: pip install flake8)")


[docs]class RichTextEditor(QtGui.QWidget): textChanged = QtCore.Signal() def __init__(self, parent=None): super(RichTextEditor, self).__init__(parent) self.completer = DictionaryCompleter(parent=self) self.editor = self._default_editor(parent=self) self.editor.textChanged.connect(self.textChanged.emit) # self.editor.setCompleter(self.completer) self.goto_widget = GoToWidget(parent=self.editor) self.search_widget = SearchWidget(parent=self.editor) self.layout = QtGui.QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.editor) self.layout.addWidget(self.search_widget) self.setLayout(self.layout) self.search_widget.hide() def _default_editor(self, *args, **kwargs): return TextEditor(*args, **kwargs)
[docs] def actions(self): """ :return: list of actions to set in the menu. """ return None
[docs] def mainMenu(self): return "Project"
[docs] def set_text(self, txt): """ Set text in the editor :param text: text you want to set """ self.editor.set_text(txt)
set_script = set_text
[docs] def get_selected_text(self): return self.editor.get_selected_text()
[docs] def get_text(self, start='sof', end='eof'): """ Return a part of the text. :param start: is the begining of what you want to get :param end: is the end of what you want to get :return: text which is contained in the editor between 'start' and 'end' """ return self.editor.get_text(start=start, end=end)
[docs] def get_code(self, start='sof', end='eof'): return self.get_text(start=start, end=end)
[docs] def replace_tab(self): return self.editor.replace_tab()
[docs] def goto(self): self.goto_widget.show()
[docs] def comment(self): self.editor.comment()
[docs] def uncomment(self): self.editor.uncomment()
[docs] def undo(self): self.editor.undo()
[docs] def redo(self): self.editor.redo()
[docs] def search(self): if self.search_widget.hiden: cursor = self.editor.textCursor() cursor.setPosition(0) self.editor.setTextCursor(cursor) self.search_widget.show() # self.search_widget.raise_() self.search_widget.lineEdit.setFocus() txt = self.get_selected_text() self.search_widget.lineEdit.setText(txt) self.search_widget.hiden = False else: self.search_widget.hide() self.search_widget.hiden = True
[docs]def fix_indentation(text, n=4): """Replace tabs by n spaces""" return text.replace('\t', ' ' * n)
[docs]class TextEditor(QtGui.QTextEdit): def __init__(self, parent=None): super(TextEditor, self).__init__(parent) self.setLineWrapMode(QtGui.QTextEdit.NoWrap) self.indentation = " " self.completer = None self.name = None # Line Number Area from LPy self.setViewportMargins(50, 0, 0, 0) self.sidebar = Margin(self, self) self.sidebar.setGeometry(0, 0, 50, 100) self.sidebar.show() self.cursorPositionChanged.connect(self.display_line_number) # QtCore.QObject.connect(self, QtCore.SIGNAL("cursorPositionChanged()"),self.highlightCurrentLine) self.read_settings()
[docs] def check_code(self): """ Check if code follow PEP-8 guide-lines thanks to module flake8. """ if has_flake8: txt = self.get_text() r = run.check_code(txt) return r else: return
[docs] def read_settings(self): config = settings.Settings() font = "Courier" try: font = config.get("Text Editor", "Font") except settings.NoSectionError, e: config.add_section("Text Editor") config.add_option("Text Editor", "Font", str(font)) except settings.NoOptionError, e: config.add_option("Text Editor", "Font", str(font)) self.set_font(font) font_size = 12 try: font_size = config.get("Text Editor", "Font Size") font_size = int(font_size) except settings.NoSectionError, e: config.add_section("Text Editor") config.add_option("Text Editor", "Font Size", str(font_size)) except settings.NoOptionError, e: config.add_option("Text Editor", "Font Size", str(font_size)) self.set_font_size(font_size) display_tab = True try: display_tab = config.get("Text Editor", "Display Tab and Spaces") display_tab = bool(eval(display_tab)) except settings.NoSectionError, e: config.add_section("Text Editor") config.add_option("Text Editor", "Display Tab and Spaces", str(display_tab)) except settings.NoOptionError, e: config.add_option("Text Editor", "Display Tab and Spaces", str(display_tab)) self.show_tab_and_spaces(display_tab)
[docs] def write_settings(self): config = settings.Settings() font = self.font() config.set("Text Editor", "Font", font) font_size = font.pointSize() config.get("Text Editor", "Font Size", font_size) config.write()
[docs] def show_tab_and_spaces(self, show=True): """ Display spaces and tabs. :param show: if true display tabs and spaces, else hide them """ doc = self.document() option = doc.defaultTextOption() if show: option.setFlags(option.flags() | QtGui.QTextOption.ShowTabsAndSpaces) else: option.setFlags(QtGui.QTextOption.Flags()) doc.setDefaultTextOption(option)
[docs] def set_font_size(self, size): """ Change the current font size. Not used for the moment! """ font = self.font() font.setPointSize(size) self.setFont(font) self.set_tab_size()
[docs] def set_font(self, font_name): """ Change the current font by name. :use: self.set_font("Courier") """ size = self.font().pointSize() font = QtGui.QFont(font_name) font.setStyleHint(QtGui.QFont.Monospace) font.setPointSize(size) font.setFixedPitch(True) self.setFont(font)
[docs] def set_tab_size(self): # Set tab size : to fix tabStop = len(self.indentation) font = self.currentFont() metrics = QtGui.QFontMetrics(font) self.setTabStopWidth(tabStop * metrics.width(' '))
[docs] def actions(self): """ :return: list of actions to set in the menu. """ return None
[docs] def mainMenu(self): return "Project"
[docs] def setText(self, txt): self.setPlainText(txt)
[docs] def set_text(self, txt): """ Set text in the editor :param text: text you want to set """ self.setText(txt)
set_script = set_text
[docs] def get_selected_text(self): cursor = self.textCursor() txt = cursor.selectedText() return unicode(txt).replace(u'\u2029', u'\n') # replace paragraph separators by new lines
[docs] def get_text(self, start='sof', end='eof'): """ Return a part of the text. :param start: is the begining of what you want to get :param end: is the end of what you want to get :return: text which is contained in the editor between 'start' and 'end' """ txt = self.toPlainText() if txt is None: txt = "" return unicode(txt).replace(u'\u2029', u'\n') # replace paragraph separators by new lines
[docs] def replace_tab(self): """ replace tab by spaces TODO: test it """ txt = self.get_text(start='sof', end='eof') txt = fix_indentation(txt, len(self.indentation)) self.set_text(txt)
[docs] def keyPressEvent(self, event): # Auto-indent if event.key() == QtCore.Qt.Key_Tab: cursor = self.textCursor() n = len(self.indentation) cursor.insertText(self.indentation[:-1]) event.ignore() elif event.key() == QtCore.Qt.Key_Return or event.key() == QtCore.Qt.Key_Enter: super(TextEditor, self).keyPressEvent(event) self.returnEvent() return elif self.completer and self.completer.popup().isVisible(): if event.key() in ( QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return, QtCore.Qt.Key_Escape, QtCore.Qt.Key_Tab, QtCore.Qt.Key_Backtab): event.ignore() return # has ctrl-E been pressed?? isShortcut = (event.modifiers() == QtCore.Qt.ControlModifier and event.key() == QtCore.Qt.Key_E) if (not self.completer or not isShortcut): super(TextEditor, self).keyPressEvent(event) # ctrl or shift key on it's own?? ctrlOrShift = event.modifiers() in (QtCore.Qt.ControlModifier, QtCore.Qt.ShiftModifier) if ctrlOrShift and event.text() is str(): # ctrl or shift key on it's own return eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=" # end of word hasModifier = ((event.modifiers() != QtCore.Qt.NoModifier) and not ctrlOrShift) completionPrefix = self.textUnderCursor() if len(event.text()) == 0: f = -1 else: f = eow.find(event.text()[-1]) if f == -1: finded = False else: finded = True if self.completer is not None: if (not isShortcut and (hasModifier or (event.text() is str()) or len(completionPrefix) < 3 or finded)): self.completer.popup().hide() return if (completionPrefix != self.completer.completionPrefix()): self.completer.setCompletionPrefix(completionPrefix) popup = self.completer.popup() popup.setCurrentIndex(self.completer.completionModel().index(0, 0)) cr = self.cursorRect() cr.setWidth(self.completer.popup().sizeHintForColumn(0) + self.completer.popup().verticalScrollBar().sizeHint().width()) self.completer.complete(cr) # popup it up! #################################################################### # Auto Indent (cf lpycodeeditor) ####################################################################
[docs] def returnEvent(self): cursor = self.textCursor() beg = cursor.selectionStart() end = cursor.selectionEnd() if beg == end: pos = cursor.position() ok = cursor.movePosition(QtGui.QTextCursor.PreviousBlock, QtGui.QTextCursor.MoveAnchor) if not ok: return txtok = True txt = '' while txtok: ok = cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor) if not ok: break txt2 = str(cursor.selection().toPlainText()) txtok = (txt2[-1] in ' \t') if txtok: txt = txt2 cursor.setPosition(pos) ok = cursor.movePosition(QtGui.QTextCursor.PreviousBlock, QtGui.QTextCursor.MoveAnchor) if ok: ok = cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.MoveAnchor) if ok: txtok = True while txtok: ok = cursor.movePosition(QtGui.QTextCursor.PreviousCharacter, QtGui.QTextCursor.KeepAnchor) if not ok: break txt2 = str(cursor.selection().toPlainText()) txtok = (txt2[0] in ' \t') if not txtok: if txt2[0] == ':': txt += self.indentation cursor.setPosition(pos) cursor.joinPreviousEditBlock() cursor.insertText(txt) cursor.endEditBlock() #################################################################### # (Un)Tab (cf lpycodeeditor) # TODO #################################################################### # def tab(self, initcursor = None): # if initcursor == False: # initcursor = None # cursor = self.textCursor() if initcursor is None else initcursor # beg = cursor.selectionStart() # end = cursor.selectionEnd() # pos = cursor.position() # if not initcursor : cursor.beginEditBlock() # cursor.setPosition(beg,QtGui.QTextCursor.MoveAnchor) # cursor.movePosition(QtGui.QTextCursor.StartOfBlock,QtGui.QTextCursor.MoveAnchor) # while cursor.position() <= end : # if self.replaceTab: # cursor.insertText(self.indentation) # end+=len(self.indentation) # else: # cursor.insertText('\t') # end+=1 # oldpos = cursor.position() # cursor.movePosition(QtGui.QTextCursor.NextBlock,QtGui.QTextCursor.MoveAnchor) # if cursor.position() == oldpos: # break # if not initcursor : cursor.endEditBlock() # cursor.setPosition(pos,QtGui.QTextCursor.MoveAnchor) # def untab(self): # cursor = self.textCursor() # beg = cursor.selectionStart() # end = cursor.selectionEnd() # pos = cursor.position() # cursor.beginEditBlock() # cursor.setPosition(beg,QtGui.QTextCursor.MoveAnchor) # cursor.movePosition(QtGui.QTextCursor.StartOfBlock,QtGui.QTextCursor.MoveAnchor) # while cursor.position() <= end: # cursor.movePosition(QtGui.QTextCursor.NextCharacter,QtGui.QTextCursor.KeepAnchor) # if cursor.selectedText() == '\t': # cursor.deleteChar() # else: # for i in xrange(len(self.indentation)-1): # b = cursor.movePosition(QtGui.QTextCursor.NextCharacter,QtGui.QTextCursor.KeepAnchor) # if not b : break # if cursor.selectedText() == self.indentation: # cursor.removeSelectedText() # end-=1 # cursor.movePosition(QtGui.QTextCursor.Down,QtGui.QTextCursor.MoveAnchor) # cursor.movePosition(QtGui.QTextCursor.StartOfBlock,QtGui.QTextCursor.MoveAnchor) # cursor.endEditBlock() # cursor.setPosition(pos,QtGui.QTextCursor.MoveAnchor) #################################################################### # (Un)Comment (cf lpycodeeditor) ####################################################################
[docs] def comment(self): cursor = self.textCursor() beg = cursor.selectionStart() end = cursor.selectionEnd() pos = cursor.position() cursor.beginEditBlock() cursor.setPosition(beg, QtGui.QTextCursor.MoveAnchor) cursor.movePosition(QtGui.QTextCursor.StartOfBlock, QtGui.QTextCursor.MoveAnchor) while cursor.position() <= end: cursor.insertText('#') oldpos = cursor.position() cursor.movePosition(QtGui.QTextCursor.NextBlock, QtGui.QTextCursor.MoveAnchor) if cursor.position() == oldpos: break end += 1 cursor.endEditBlock() cursor.setPosition(pos, QtGui.QTextCursor.MoveAnchor)
[docs] def uncomment(self): cursor = self.textCursor() beg = cursor.selectionStart() end = cursor.selectionEnd() cursor.beginEditBlock() cursor.setPosition(beg, QtGui.QTextCursor.MoveAnchor) cursor.movePosition(QtGui.QTextCursor.StartOfBlock, QtGui.QTextCursor.MoveAnchor) while cursor.position() <= end: cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor) if True: if cursor.selectedText() == '#': cursor.deleteChar() cursor.movePosition(QtGui.QTextCursor.NextBlock, QtGui.QTextCursor.MoveAnchor) end -= 1 cursor.endEditBlock() #################################################################### # Completer ####################################################################
[docs] def setCompleter(self, completer): logger.debug("set completer " + str(completer)) if self.completer: self.disconnect(self.completer, 0, self, 0) if not completer: return completer.setWidget(self) completer.setCompletionMode(QtGui.QCompleter.PopupCompletion) completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) self.completer = completer QtCore.QObject.connect(self.completer, QtCore.SIGNAL("activated(const QString&)"), self.insertCompletion)
[docs] def insertCompletion(self, completion): logger.debug("insert completion") tc = self.textCursor() extra = (len(completion) - len(self.completer.completionPrefix())) tc.movePosition(QtGui.QTextCursor.Left) tc.movePosition(QtGui.QTextCursor.EndOfWord) tc.insertText(completion[-extra:]) self.setTextCursor(tc) logger.debug("inserted completion")
[docs] def textUnderCursor(self): tc = self.textCursor() tc.select(QtGui.QTextCursor.WordUnderCursor) return tc.selectedText()
[docs] def focusInEvent(self, event): if self.completer: self.completer.setWidget(self) super(TextEditor, self).focusInEvent(event) #################################################################### # Line Number Area ####################################################################
[docs] def resizeEvent(self, event): self.sidebar.setGeometry(0, 0, 48, self.height()) super(TextEditor, self).resizeEvent(event)
[docs] def scrollContentsBy(self, dx, dy): self.sidebar.update() self.sidebar.setFont(QtGui.QFont(self.currentFont())) super(TextEditor, self).scrollContentsBy(dx, dy)
[docs] def display_line_number(self): self.sidebar.repaint(self.sidebar.rect()) #################################################################### # Line Number Area ####################################################################
[docs] def go_to_line(self, lineno): cursor = self.textCursor() cursor.setPosition(0) cursor.movePosition(QtGui.QTextCursor.NextBlock, QtGui.QTextCursor.MoveAnchor, lineno - 1) self.setTextCursor(cursor) self.ensureCursorVisible()
[docs]def main(): import sys from openalea.oalab.shell import get_shell_class from openalea.core.service.ipython import interpreter from openalea.oalab.editor.highlight import Highlighter app = QtGui.QApplication(sys.argv) edit = TextEditor() Highlighter(edit) interp = interpreter() shell = get_shell_class()(interp) win = QtGui.QMainWindow() win.setCentralWidget(edit) dock_widget = QtGui.QDockWidget("IPython", win) interp.locals['mainwindow'] = win interp.locals['editor'] = edit interp.locals['shell'] = shell interp.locals['interpreter'] = interp dock_widget.setWidget(shell) win.addDockWidget(QtCore.Qt.BottomDockWidgetArea, dock_widget) win.show() win.raise_() app.exec_()
if(__name__ == "__main__"): main()