示例#1
0
 def __init__(self, highlight_caret_scope=False):
     Panel.__init__(self)
     self._native = True
     self._custom_indicators = (
         ':/pyqode-icons/rc/arrow_right_off.png',
         ':/pyqode-icons/rc/arrow_right_on.png',
         ':/pyqode-icons/rc/arrow_down_off.png',
         ':/pyqode-icons/rc/arrow_down_on.png'
     )
     self._custom_color = QtGui.QColor('gray')
     self._block_nbr = -1
     self._highlight_caret = False
     self.highlight_caret_scope = highlight_caret_scope
     self._indic_size = 16
     #: the list of deco used to highlight the current fold region (
     #: surrounding regions are darker)
     self._scope_decos = []
     #: the list of folded blocs decorations
     self._block_decos = []
     self.setMouseTracking(True)
     self.scrollable = True
     self._mouse_over_line = None
     self._current_scope = None
     self._prev_cursor = None
     self.context_menu = None
     self.action_collapse = None
     self.action_expand = None
     self.action_collapse_all = None
     self.action_expand_all = None
     self._original_background = None
     self._highlight_runner = DelayJobRunner(delay=250)
示例#2
0
 def __init__(self, delay=1000):
     Mode.__init__(self)
     QtCore.QObject.__init__(self)
     self._jobRunner = DelayJobRunner(delay=delay)
     #: The list of results (elements might have children; this is actually
     #: a tree).
     self.results = []
示例#3
0
 def __init__(self):
     QtCore.QObject.__init__(self)
     Mode.__init__(self)
     self._previous_cursor_start = -1
     self._previous_cursor_end = -1
     self._deco = None
     self._cursor = None
     self._timer = DelayJobRunner(delay=200)
示例#4
0
 def __init__(self, worker, delay=1000):
     Mode.__init__(self)
     QtCore.QObject.__init__(self)
     self._worker = worker
     self._jobRunner = DelayJobRunner(delay=delay)
     #: The list of definitions found in the file, each item is a
     #: pyqode.core.share.Definition.
     self._results = []
示例#5
0
 def __init__(self):
     super(OccurrencesHighlighterMode, self).__init__()
     self._decorations = []
     #: Timer used to run the search request with a specific delay
     self.timer = DelayJobRunner(delay=1000)
     self._sub = None
     self._background = QtGui.QColor('#80CC80')
     self._foreground = QtGui.QColor('#404040')
     self._underlined = True
示例#6
0
 def __init__(self):
     super(GoToAssignmentsMode, self).__init__()
     self._definitions = []
     self._goto_requested = False
     self.action_goto = QtWidgets.QAction("Go to assignments", self)
     self.action_goto.setShortcut(self.shortcut)
     self.action_goto.triggered.connect(self.request_goto)
     self.word_clicked.connect(self._on_word_clicked)
     self._runner = DelayJobRunner(delay=1)
示例#7
0
class HtmlPreviewWidget(QtWebWidgets.QWebView):
    hide_requested = QtCore.Signal()
    show_requested = QtCore.Signal()

    def __init__(self, parent=None):
        super(HtmlPreviewWidget, self).__init__(parent)
        self._editor = None
        self._timer = DelayJobRunner(delay=1000)
        try:
            # prevent opening internal links when using QtWebKit
            self.page().setLinkDelegationPolicy(
                QtWebWidgets.QWebPage.DelegateAllLinks)
        except (TypeError, AttributeError):
            # no needed with QtWebEngine, internal links are properly handled
            # by the default implementation
            pass

    def set_editor(self, editor):
        url = QtCore.QUrl('')
        if editor is not None:
            url = QtCore.QUrl.fromLocalFile(editor.file.path)
        try:
            self.setHtml(editor.to_html(), url)
        except (TypeError, AttributeError):
            self.setHtml('<center>No preview available...</center>', url)
            self._editor = None
            self.hide_requested.emit()
        else:
            if self._editor is not None and editor != self._editor:
                try:
                    self._editor.textChanged.disconnect(self._on_text_changed)
                except TypeError:
                    pass
            editor.textChanged.connect(self._on_text_changed)
            self._editor = proxy(editor)
            self.show_requested.emit()

    def _on_text_changed(self, *_):
        self._timer.request_job(self._update_preview)

    def _update_preview(self):
        url = QtCore.QUrl('')
        if self._editor is not None:
            url = QtCore.QUrl.fromLocalFile(self._editor.file.path)
        try:
            try:
                pos = self.page().mainFrame().scrollBarValue(QtCore.Qt.Vertical)
                self.setHtml(self._editor.to_html(), url)
                self.page().mainFrame().setScrollBarValue(QtCore.Qt.Vertical, pos)
            except AttributeError:
                # Not possible with QtWebEngine???
                # self._scroll_pos = self.page().mainFrame().scrollBarValue(
                    # QtCore.Qt.Vertical)
                self.setHtml(self._editor.to_html(), url)
        except (TypeError, AttributeError):
            self.setHtml('<center>No preview available...</center>', url)
            self.hide_requested.emit()
示例#8
0
 def __init__(self):
     super(GoToAssignmentsMode, self).__init__()
     self._definitions = []
     self._goto_requested = False
     self.action_goto = QtWidgets.QAction(_("Go to assignments"), self)
     self.action_goto.setShortcut(self.shortcut)
     self.action_goto.triggered.connect(self.request_goto)
     icon = icons.icon(qta_name='fa.share')
     if icon:
         self.action_goto.setIcon(icon)
     self.word_clicked.connect(self._on_word_clicked)
     self._runner = DelayJobRunner(delay=1)
示例#9
0
 def __init__(self):
     QObject.__init__(self)
     Mode.__init__(self)
     self._previous_cursor_start = -1
     self._previous_cursor_end = -1
     self._definition = None
     self._deco = None
     self._pending = False
     self.action_goto = QAction(_("Go to assignments"), self)
     self.action_goto.setShortcut('F7')
     self.action_goto.triggered.connect(self.request_goto)
     self.word_clicked.connect(self.request_goto)
     self._timer = DelayJobRunner(delay=200)
示例#10
0
class HtmlPreviewWidget(QtWidgets.QTextEdit):
    """
    Display html preview of a document as rich text in a QTextEdit.
    """
    hide_requested = QtCore.Signal()
    show_requested = QtCore.Signal()

    def __init__(self, parent=None):
        super(HtmlPreviewWidget, self).__init__(parent)
        self._editor = None
        self._timer = DelayJobRunner(delay=1000)

    def set_editor(self, editor):
        try:
            self.setHtml(editor.to_html())
        except (TypeError, AttributeError):
            self.setHtml('<center>No preview available...</center>')
            self._editor = None
            self.hide_requested.emit()
        else:
            if self._editor is not None and editor != self._editor:
                try:
                    self._editor.textChanged.disconnect(self._on_text_changed)
                except TypeError:
                    pass
            editor.textChanged.connect(self._on_text_changed)
            self._editor = proxy(editor)
            self.show_requested.emit()

    def _on_text_changed(self, *_):
        self._timer.request_job(self._update_preview)

    def _update_preview(self):
        try:
            # remember cursor/scrollbar position
            p = self.textCursor().position()
            v = self.verticalScrollBar().value()

            # display new html
            self.setHtml(self._editor.to_html())

            # restore cursor/scrollbar position
            c = self.textCursor()
            c.setPosition(p)
            self.setTextCursor(c)
            self.verticalScrollBar().setValue(v)
        except (TypeError, AttributeError):
            self.setHtml('<center>No preview available...</center>')
            self.hide_requested.emit()
示例#11
0
 def __init__(self):
     QObject.__init__(self)
     Mode.__init__(self)
     self._root_node = None
     self._vars = []
     self._paragraphs = []
     self._runner = DelayJobRunner()
示例#12
0
 def __init__(self):
     super(CheckerPanel, self).__init__()
     self._previous_line = -1
     self.scrollable = True
     self._job_runner = DelayJobRunner(delay=100)
     self.setMouseTracking(True)
     #: Info icon
     self.info_icon = QtGui.QIcon.fromTheme(
         'dialog-info', QtGui.QIcon(':pyqode-icons/rc/dialog-info.png'))
     #: Warning icon
     self.warning_icon = QtGui.QIcon.fromTheme(
         'dialog-warning',
         QtGui.QIcon(':pyqode-icons/rc/dialog-warning.png'))
     #: Error icon
     self.error_icon = QtGui.QIcon.fromTheme(
         'dialog-error', QtGui.QIcon(':pyqode-icons/rc/dialog-error.png'))
示例#13
0
 def __init__(self):
     super(CheckerPanel, self).__init__()
     self._previous_line = -1
     self.scrollable = True
     self._job_runner = DelayJobRunner(delay=100)
     self.setMouseTracking(True)
     #: Info icon
     self.info_icon = icons.icon(
         'dialog-info', ':pyqode-icons/rc/dialog-info.png',
         'fa.info-circle', qta_options={'color': '#4040DD'})
     self.warning_icon = icons.icon(
         'dialog-warning', ':pyqode-icons/rc/dialog-warning.png',
         'fa.exclamation-triangle', qta_options={'color': '#DDDD40'})
     self.error_icon = icons.icon(
         'dialog-error', ':pyqode-icons/rc/dialog-error.png',
         'fa.exclamation-circle', qta_options={'color': '#DD4040'})
 def __init__(self, delay=1000):
     Mode.__init__(self)
     QtCore.QObject.__init__(self)
     self._jobRunner = DelayJobRunner(delay=delay)
     #: The list of results (elements might have children; this is actually
     #: a tree).
     self.results = []
示例#15
0
 def __init__(self):
     QtCore.QObject.__init__(self)
     Mode.__init__(self)
     self._previous_cursor_start = -1
     self._previous_cursor_end = -1
     self._deco = None
     self._cursor = None
     self._timer = DelayJobRunner(delay=200)
 def __init__(self, worker, delay=1000):
     Mode.__init__(self)
     QtCore.QObject.__init__(self)
     self._worker = worker
     self._jobRunner = DelayJobRunner(delay=delay)
     #: The list of definitions found in the file, each item is a
     #: pyqode.core.share.Definition.
     self._results = []
示例#17
0
 def __init__(self):
     super(OccurrencesHighlighterMode, self).__init__()
     self._decorations = []
     #: Timer used to run the search request with a specific delay
     self.timer = DelayJobRunner(delay=1000)
     self._sub = None
     self._background = QtGui.QColor('#CCFFCC')
     self._foreground = None
     self._underlined = False
 def __init__(self):
     super(GoToAssignmentsMode, self).__init__()
     self._definitions = []
     self._goto_requested = False
     self.action_goto = QtWidgets.QAction("Go to assignments", self)
     self.action_goto.setShortcut(self.shortcut)
     self.action_goto.triggered.connect(self.request_goto)
     self.word_clicked.connect(self._on_word_clicked)
     self._runner = DelayJobRunner(delay=1)
示例#19
0
 def __init__(self, parent=None):
     super(HtmlPreviewWidget, self).__init__(parent)
     self._editor = None
     self._timer = DelayJobRunner(delay=1000)
     try:
         # prevent opening internal links when using QtWebKit
         self.page().setLinkDelegationPolicy(
             QtWebWidgets.QWebPage.DelegateAllLinks)
     except (TypeError, AttributeError):
         # no needed with QtWebEngine, internal links are properly handled
         # by the default implementation
         pass
示例#20
0
 def __init__(self):
     super(GoToAssignmentsMode, self).__init__()
     self._definitions = []
     self._goto_requested = False
     self.action_goto = QtWidgets.QAction(_("Go to assignments"), self)
     self.action_goto.setShortcut(self.shortcut)
     self.action_goto.triggered.connect(self.request_goto)
     icon = icons.icon(qta_name='fa.share')
     if icon:
         self.action_goto.setIcon(icon)
     self.word_clicked.connect(self._on_word_clicked)
     self._runner = DelayJobRunner(delay=1)
示例#21
0
class TabBar(QtWidgets.QTabBar):
    """
    Tab bar specialized to allow the user to close a tab using mouse middle
    click. Also exposes a double clicked signal.
    """
    double_clicked = QtCore.Signal()

    def __init__(self, parent):
        QtWidgets.QTabBar.__init__(self, parent)
        self.setTabsClosable(True)
        self._timer = DelayJobRunner(delay=1)

    def mousePressEvent(self, event):
        QtWidgets.QTabBar.mousePressEvent(self, event)
        if event.button() == QtCore.Qt.MiddleButton:
            tab = self.tabAt(event.pos())
            self._timer.request_job(self.parentWidget().tabCloseRequested.emit,
                                    tab)

    def mouseDoubleClickEvent(self, event):
        self.double_clicked.emit()
示例#22
0
class TabBar(QtWidgets.QTabBar):
    """
    Tab bar specialized to allow the user to close a tab using mouse middle
    click. Also exposes a double clicked signal.
    """
    double_clicked = QtCore.Signal()

    def __init__(self, parent):
        QtWidgets.QTabBar.__init__(self, parent)
        self.setTabsClosable(True)
        self._timer = DelayJobRunner(delay=1)

    def mousePressEvent(self, event):
        QtWidgets.QTabBar.mousePressEvent(self, event)
        if event.button() == QtCore.Qt.MiddleButton:
            tab = self.tabAt(event.pos())
            self._timer.request_job(
                self.parentWidget().tabCloseRequested.emit, tab)

    def mouseDoubleClickEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            self.double_clicked.emit()
示例#23
0
 def __init__(self, window=None):
     super().__init__()
     self._query_thread = None
     self.icon_provider = widgets.FileIconProvider()
     self._runner = DelayJobRunner(delay=300)
     self.main_window = window
     self.ui = locator_ui.Ui_Frame()
     self.ui.setupUi(self)
     self.ui.lineEdit.textChanged.connect(self.request_search)
     self.ui.lineEdit.prompt_text = _('Type to locate...')
     self.ui.lineEdit.installEventFilter(self)
     self.ui.treeWidget.installEventFilter(self)
     self.ui.treeWidget.setItemDelegate(HTMLDelegate())
     self.setWindowFlags(QtCore.Qt.Popup)
     self.ui.lineEdit.setFocus(True)
     self.ui.bt_close.clicked.connect(self.hide)
     self.ui.bt_infos.clicked.connect(self._show_help)
     self.mode = self.MODE_GOTO_FILE
     self.ui.treeWidget.currentItemChanged.connect(
         self._on_current_item_changed)
     self.ui.treeWidget.itemDoubleClicked.connect(self._activate)
     self.ui.cb_non_project_files.toggled.connect(self._search)
示例#24
0
 def __init__(self):
     QObject.__init__(self)
     Mode.__init__(self)
     self._previous_cursor_start = -1
     self._previous_cursor_end = -1
     self._definition = None
     self._deco = None
     self._pending = False
     self.action_goto = QAction(_("Go to assignments"), self)
     self.action_goto.setShortcut('F7')
     self.action_goto.triggered.connect(self.request_goto)
     self.word_clicked.connect(self.request_goto)
     self._timer = DelayJobRunner(delay=200)
示例#25
0
 def __init__(self):
     super(CheckerPanel, self).__init__()
     self._previous_line = -1
     self.scrollable = True
     self._job_runner = DelayJobRunner(delay=100)
     self.setMouseTracking(True)
     #: Info icon
     self.info_icon = QtGui.QIcon.fromTheme(
         'dialog-info', QtGui.QIcon(':pyqode-icons/rc/dialog-info.png'))
     #: Warning icon
     self.warning_icon = QtGui.QIcon.fromTheme(
         'dialog-warning',
         QtGui.QIcon(':pyqode-icons/rc/dialog-warning.png'))
     #: Error icon
     self.error_icon = QtGui.QIcon.fromTheme(
         'dialog-error', QtGui.QIcon(':pyqode-icons/rc/dialog-error.png'))
示例#26
0
 def __init__(self):
     super(CheckerPanel, self).__init__()
     self._previous_line = -1
     self.scrollable = True
     self._job_runner = DelayJobRunner(delay=100)
     self.setMouseTracking(True)
     #: Info icon
     self.info_icon = icons.icon(
         'dialog-info', ':pyqode-icons/rc/dialog-info.png',
         'fa.info-circle', qta_options={'color': '#4040DD'})
     self.warning_icon = icons.icon(
         'dialog-warning', ':pyqode-icons/rc/dialog-warning.png',
         'fa.exclamation-triangle', qta_options={'color': '#DDDD40'})
     self.error_icon = icons.icon(
         'dialog-error', ':pyqode-icons/rc/dialog-error.png',
         'fa.exclamation-circle', qta_options={'color': '#DD4040'})
示例#27
0
 def __init__(self, window):
     super().__init__()
     self.main_window = window
     self.parser_plugins = []
     self._indexors = []
     self._locator = LocatorWidget(self.main_window)
     self._locator.activated.connect(self._on_locator_activated)
     self._locator.cancelled.connect(self._on_locator_cancelled)
     self._widget = QtWidgets.QWidget(self.main_window)
     self._job_runner = DelayJobRunner()
     self._cached_files = []
     self._running_tasks = False
     self._widget.installEventFilter(self)
     self._setup_filesystem_treeview()
     self._setup_prj_selector_widget(self.main_window)
     self._setup_dock_widget()
     self._setup_tab_bar_context_menu(self.main_window)
     self._setup_locator()
     self._setup_project_menu()
     self._load_parser_plugins()
     api.signals.connect_slot(api.signals.CURRENT_EDITOR_CHANGED,
                              self._on_current_editor_changed)
class GoToAssignmentsMode(WordClickMode):
    """
    Goes to the assignments (using jedi.Script.goto_assignments) when the user
    execute the shortcut or click word. If there are more than one assignments,
    an input dialog is used to ask the user to choose the desired assignment.

    This mode will emit the :attr:`out_of_doc` signal if the definition can
    not be reached in the current document. IDE will typically connects a slot
    that open a new editor tab and goes to the definition position.
    """
    #: Signal emitted when the definition cannot be reached in the current
    #: document
    out_of_doc = QtCore.Signal(Assignment)

    #: Signal emitted when no results could be found.
    no_results_found = QtCore.Signal()

    shortcut = 'Alt+F2'

    def __init__(self):
        super(GoToAssignmentsMode, self).__init__()
        self._definitions = []
        self._goto_requested = False
        self.action_goto = QtWidgets.QAction("Go to assignments", self)
        self.action_goto.setShortcut(self.shortcut)
        self.action_goto.triggered.connect(self.request_goto)
        self.word_clicked.connect(self._on_word_clicked)
        self._runner = DelayJobRunner(delay=1)

    def on_state_changed(self, state):
        super(GoToAssignmentsMode, self).on_state_changed(state)
        if state:
            self.editor.add_action(self.action_goto)
        else:
            self.editor.remove_action(self.action_goto)

    def request_goto(self):
        """
        Request a goto action for the word under the text cursor.
        """
        self._goto_requested = True
        self._check_word_cursor()

    def _check_word_cursor(self, tc=None):
        """
        Request a go to assignment.

        :param tc: Text cursor which contains the text that we must look for
                   its assignment. Can be None to go to the text that is under
                   the text cursor.
        :type tc: QtGui.QTextCursor
        """
        if not tc:
            tc = TextHelper(self.editor).word_under_cursor()

        request_data = {
            'code': self.editor.toPlainText(),
            'line': tc.blockNumber(),
            'column': tc.columnNumber(),
            'path': self.editor.file.path,
            'encoding': self.editor.file.encoding
        }
        try:
            self.editor.backend.send_request(
                workers.goto_assignments, request_data,
                on_receive=self._on_results_available)
        except NotRunning:
            pass

    def _goto(self, definition):
        fp = ''
        if self.editor.file.path:
            fp = os.path.normpath(self.editor.file.path.replace(".pyc", ".py"))
        if definition.module_path == fp:
            line = definition.line
            col = definition.column
            _logger().debug("Go to %s" % definition)
            self._runner.request_job(
                TextHelper(self.editor).goto_line,
                line, move=True, column=col)
        else:
            _logger().debug("Out of doc: %s" % definition)
            self.out_of_doc.emit(definition)

    def _unique(self, seq):
        """
        Not performant but works.
        """
        # order preserving
        checked = []
        for e in seq:
            present = False
            for c in checked:
                if str(c) == str(e):
                    present = True
                    break
            if not present:
                checked.append(e)
        return checked

    def _clear_selection(self):
        super(GoToAssignmentsMode, self)._clear_selection()
        self._definitions[:] = []

    def _validate_definitions(self, definitions):
        if definitions:
            if len(definitions) == 1:
                return definitions[0].line is not None
            return True
        return False

    def _on_results_available(self, definitions):
        _logger().debug("Got %r" % definitions)
        definitions = [Assignment(path, line, col, full_name)
                       for path, line, col, full_name in definitions]
        definitions = self._unique(definitions)
        self._definitions = definitions
        if self._validate_definitions(definitions):
            if self._goto_requested:
                self._perform_goto(definitions)
            else:
                self._select_word_cursor()
                self.editor.set_mouse_cursor(QtCore.Qt.PointingHandCursor)
        else:
            self._clear_selection()
            self.editor.set_mouse_cursor(QtCore.Qt.IBeamCursor)
        self._goto_requested = False

    def _perform_goto(self, definitions):
        if len(definitions) == 1:
            definition = definitions[0]
            if definition:
                self._goto(definition)
        elif len(definitions) > 1:
            _logger().debug(
                "More than 1 assignments in different modules, user "
                "need to make a choice: %s" % definitions)
            def_str, result = QtWidgets.QInputDialog.getItem(
                self.editor, "Choose a definition",
                "Choose the definition you want to go to:",
                [str(d) for d in definitions])
            if result:
                for definition in definitions:
                    if definition and str(definition) == def_str:
                        self._goto(definition)
                        break

    def _on_word_clicked(self):
        self._perform_goto(self._definitions)
示例#29
0
class DocumentAnalyserMode(Mode, QtCore.QObject):
    """ Analyses the document outline as a tree of statements.

    This mode analyses the structure of a document (a tree of
    :class:`pyqode.python.backend.workers.Definition`.

    :attr:`pyqode.python.modes.DocumentAnalyserMode.document_changed`
    is emitted whenever the document structure changed.

    To keep good performances, the analysis task is run when the application is
    idle for more than 1 second (by default).
    """
    #: Signal emitted when the document structure changed.
    document_changed = QtCore.Signal()

    def __init__(self, delay=1000):
        Mode.__init__(self)
        QtCore.QObject.__init__(self)
        self._jobRunner = DelayJobRunner(delay=delay)
        #: The list of results (elements might have children; this is actually
        #: a tree).
        self.results = []

    def on_state_changed(self, state):
        if state:
            self.editor.new_text_set.connect(self._run_analysis)
            self.editor.textChanged.connect(self._request_analysis)
        else:
            self.editor.textChanged.disconnect(self._request_analysis)
            self.editor.new_text_set.disconnect(self._run_analysis)
            self._jobRunner.cancel_requests()

    def _request_analysis(self):
        self._jobRunner.request_job(self._run_analysis)

    def _run_analysis(self):
        if self.enabled and self.editor and self.editor.toPlainText() and \
                self.editor.file:
            request_data = {
                'code': self.editor.toPlainText(),
                'path': self.editor.file.path,
                'encoding': self.editor.file.encoding
            }
            try:
                self.editor.backend.send_request(
                    defined_names,
                    request_data,
                    on_receive=self._on_results_available)
            except NotRunning:
                QtCore.QTimer.singleShot(100, self._run_analysis)
        else:
            self.results = []
            self.document_changed.emit()

    def _on_results_available(self, results):
        if results:
            results = [Definition().from_dict(ddict) for ddict in results]
        self.results = results
        if self.results is not None:
            _logger().debug("Document structure changed")
            self.document_changed.emit()

    @property
    def flattened_results(self):
        """
        Flattens the document structure tree as a simple sequential list.
        """
        ret_val = []
        for d in self.results:
            ret_val.append(d)
            for sub_d in d.children:
                nd = Definition(sub_d.name, sub_d.icon, sub_d.line,
                                sub_d.column, sub_d.full_name)
                nd.name = "  " + nd.name
                nd.full_name = "  " + nd.full_name
                ret_val.append(nd)
        return ret_val

    def to_tree_widget_items(self, to_collapse=None):
        """
        Returns the results as a list of top level QTreeWidgetItem.

        This is a convenience function that you can use to update a document
        tree widget wheneve the document changed.
        """
        def convert(name, editor, to_collapse):
            ti = QtWidgets.QTreeWidgetItem()
            ti.setText(0, name.name)
            ti.setIcon(0, QtGui.QIcon(name.icon))
            name.block = editor.document().findBlockByNumber(name.line)
            ti.setData(0, QtCore.Qt.UserRole, name)
            block_data = name.block.userData()
            if block_data is None:
                block_data = TextBlockUserData()
                name.block.setUserData(block_data)
            block_data.tree_item = ti

            if to_collapse is not None and \
                    TextBlockHelper.get_fold_trigger_state(name.block):
                to_collapse.append(ti)

            for ch in name.children:
                ti_ch, to_collapse = convert(ch, editor, to_collapse)
                if ti_ch:
                    ti.addChild(ti_ch)
            return ti, to_collapse

        items = []
        for d in self.results:
            value, to_collapse = convert(d, self.editor, to_collapse)
            items.append(value)
        if to_collapse is not None:
            return items, to_collapse
        return items
示例#30
0
class CheckerPanel(Panel):
    """ Shows messages collected by one or more checker modes """
    def __init__(self):
        super(CheckerPanel, self).__init__()
        self._previous_line = -1
        self.scrollable = True
        self._job_runner = DelayJobRunner(delay=100)
        self.setMouseTracking(True)
        #: Info icon
        self.info_icon = icons.icon(
            'dialog-info', ':pyqode-icons/rc/dialog-info.png',
            'fa.info-circle', qta_options={'color': '#4040DD'})
        self.warning_icon = icons.icon(
            'dialog-warning', ':pyqode-icons/rc/dialog-warning.png',
            'fa.exclamation-triangle', qta_options={'color': '#DDDD40'})
        self.error_icon = icons.icon(
            'dialog-error', ':pyqode-icons/rc/dialog-error.png',
            'fa.exclamation-circle', qta_options={'color': '#DD4040'})

    def marker_for_line(self, line):
        """
        Returns the marker that is displayed at the specified line number if
        any.

        :param line: The marker line.

        :return: Marker of None
        :rtype: pyqode.core.Marker
        """
        block = self.editor.document().findBlockByNumber(line)
        try:
            return block.userData().messages
        except AttributeError:
            return []

    def sizeHint(self):
        """
        Returns the panel size hint. (fixed with of 16px)
        """
        metrics = QtGui.QFontMetricsF(self.editor.font())
        size_hint = QtCore.QSize(metrics.height(), metrics.height())
        if size_hint.width() > 16:
            size_hint.setWidth(16)
        return size_hint

    def on_uninstall(self):
        self._job_runner.cancel_requests()
        super(CheckerPanel, self).on_uninstall()

    def paintEvent(self, event):
        super(CheckerPanel, self).paintEvent(event)
        painter = QtGui.QPainter(self)
        for top, block_nbr, block in self.editor.visible_blocks:
            user_data = block.userData()
            if user_data and user_data.messages:
                for msg in user_data.messages:
                    icon = self._icon_from_message(msg)
                    if icon:
                        rect = QtCore.QRect()
                        rect.setX(0)
                        rect.setY(top)
                        rect.setWidth(self.sizeHint().width())
                        rect.setHeight(self.sizeHint().height())
                        icon.paint(painter, rect)

    def _icon_from_message(self, message):
        icons = {
            CheckerMessages.INFO: self.info_icon,
            CheckerMessages.WARNING: self.warning_icon,
            CheckerMessages.ERROR: self.error_icon
        }
        return icons[message.status]

    def mouseMoveEvent(self, event):
        # Requests a tooltip if the cursor is currently over a marker.
        line = TextHelper(self.editor).line_nbr_from_position(event.pos().y())
        if line:
            markers = self.marker_for_line(line)
            text = '\n'.join([marker.description for marker in markers if
                              marker.description])
            if len(markers):
                if self._previous_line != line:
                    top = TextHelper(self.editor).line_pos_from_number(
                        markers[0].line)
                    if top:
                        self._job_runner.request_job(self._display_tooltip,
                                                     text, top)
            else:
                self._job_runner.cancel_requests()
            self._previous_line = line

    def leaveEvent(self, *args):
        """
        Hide tooltip when leaving the panel region.
        """
        QtWidgets.QToolTip.hideText()
        self._previous_line = -1

    def _display_tooltip(self, tooltip, top):
        """
        Display tooltip at the specified top position.
        """
        QtWidgets.QToolTip.showText(self.mapToGlobal(QtCore.QPoint(
            self.sizeHint().width(), top)), tooltip, self)
class DocumentAnalyserMode(Mode, QtCore.QObject):
    """ Analyses the document outline as a tree of statements.

    This mode analyses the structure of a document (a tree of
    :class:`pyqode.python.backend.workers.Definition`.

    :attr:`pyqode.python.modes.DocumentAnalyserMode.document_changed`
    is emitted whenever the document structure changed.

    To keep good performances, the analysis task is run when the application is
    idle for more than 1 second (by default).
    """
    #: Signal emitted when the document structure changed.
    document_changed = QtCore.Signal()

    def __init__(self, delay=1000):
        Mode.__init__(self)
        QtCore.QObject.__init__(self)
        self._jobRunner = DelayJobRunner(delay=delay)
        #: The list of results (elements might have children; this is actually
        #: a tree).
        self.results = []

    def on_state_changed(self, state):
        if state:
            self.editor.new_text_set.connect(self._run_analysis)
            self.editor.textChanged.connect(self._request_analysis)
        else:
            self.editor.textChanged.disconnect(self._request_analysis)
            self.editor.new_text_set.disconnect(self._run_analysis)
            self._jobRunner.cancel_requests()

    def _request_analysis(self):
        self._jobRunner.request_job(self._run_analysis)

    def _run_analysis(self):
        if self.enabled and self.editor and self.editor.toPlainText() and \
                self.editor.file:
            request_data = {
                'code': self.editor.toPlainText(),
                'path': self.editor.file.path,
                'encoding': self.editor.file.encoding
            }
            try:
                self.editor.backend.send_request(
                    defined_names, request_data,
                    on_receive=self._on_results_available)
            except NotRunning:
                QtCore.QTimer.singleShot(100, self._run_analysis)
        else:
            self.results = []
            self.document_changed.emit()

    def _on_results_available(self, results):
        if results:
            results = [Definition().from_dict(ddict) for ddict in results]
        self.results = results
        if self.results is not None:
            _logger().debug("Document structure changed")
            self.document_changed.emit()

    @property
    def flattened_results(self):
        """
        Flattens the document structure tree as a simple sequential list.
        """
        ret_val = []
        for d in self.results:
            ret_val.append(d)
            for sub_d in d.children:
                nd = Definition(sub_d.name, sub_d.icon, sub_d.line,
                                sub_d.column, sub_d.full_name)
                nd.name = "  " + nd.name
                nd.full_name = "  " + nd.full_name
                ret_val.append(nd)
        return ret_val

    def to_tree_widget_items(self, to_collapse=None):
        """
        Returns the results as a list of top level QTreeWidgetItem.

        This is a convenience function that you can use to update a document
        tree widget wheneve the document changed.
        """
        def convert(name, editor, to_collapse):
            ti = QtWidgets.QTreeWidgetItem()
            ti.setText(0, name.name)
            ti.setIcon(0, QtGui.QIcon(name.icon))
            name.block = editor.document().findBlockByNumber(name.line)
            ti.setData(0, QtCore.Qt.UserRole, name)
            block_data = name.block.userData()
            if block_data is None:
                block_data = TextBlockUserData()
                name.block.setUserData(block_data)
            block_data.tree_item = ti

            if to_collapse is not None and \
                    TextBlockHelper.get_fold_trigger_state(name.block):
                to_collapse.append(ti)

            for ch in name.children:
                ti_ch, to_collapse = convert(ch, editor, to_collapse)
                if ti_ch:
                    ti.addChild(ti_ch)
            return ti, to_collapse

        items = []
        for d in self.results:
            value, to_collapse = convert(d, self.editor, to_collapse)
            items.append(value)
        if to_collapse is not None:
            return items, to_collapse
        return items
示例#32
0
class DocumentOutlineMode(QObject, Mode):
    """
    Parses the current cobol document when the text changed and emit the
    changed event if any properties of any document node has changed.

    This mode can be used to implement a document outline widget.

    """
    #: Signal emitted when the document layout changed
    changed = Signal(Name, list, list)

    @property
    def root_node(self):
        """
        Returns the document root node.
        """
        return self._root_node

    @property
    def variables(self):
        """
        Returns the list of variable document nodes
        """
        return self._vars

    @property
    def paragraphs(self):
        """
        Returns the list of paragraphs document nodes
        """
        return self._paragraphs

    def __init__(self):
        QObject.__init__(self)
        Mode.__init__(self)
        self._root_node = None
        self._vars = []
        self._paragraphs = []
        self._runner = DelayJobRunner()

    def on_state_changed(self, state):
        """
        Called when the mode is activated/deactivated
        """
        if state:
            self.editor.new_text_set.connect(self.parse)
            self.editor.textChanged.connect(self._parse)
        else:
            self.editor.new_text_set.disconnect(self.parse)
            self.editor.textChanged.disconnect(self._parse)
            self._runner.cancel_requests()

    def _parse(self):
        self._runner.request_job(self.parse)

    def parse(self):
        """ Parse the document layout.

        To get the results, use the following properties:
            - root_node
            - variables
            - paragraphs
        """
        # preview in preferences dialog have no file path
        if not self.editor.file.path:
            return
        txt = self.editor.toPlainText()
        fmt = self.editor.free_format
        try:
            root_node, variables, paragraphs = defined_names(txt, fmt)
        except AttributeError:
            # this should never happen but we must exit gracefully
            _logger().exception("Failed to parse document, probably due to "
                                "a malformed syntax.")
        else:
            changed = False
            if self._root_node is None or cmp_name(root_node, self._root_node):
                changed = True
            self._root_node = root_node
            self._vars = variables
            self._paragraphs = paragraphs
            if changed:
                _logger().debug('changed')
                self.changed.emit(
                    self.root_node, self.variables, self.paragraphs)
示例#33
0
class OccurrencesHighlighterMode(Mode):
    """ Highlights occurrences of the word under the text text cursor.

    The ``delay`` before searching for occurrences is configurable.
    """
    @property
    def delay(self):
        """
        Delay before searching for occurrences. The timer is rearmed as soon
        as the cursor position changed.
        """
        return self.timer.delay

    @delay.setter
    def delay(self, value):
        self.timer.delay = value
        if self.editor:
            for clone in self.editor.clones:
                try:
                    clone.modes.get(self.__class__).delay = value
                except KeyError:
                    # this should never happen since we're working with clones
                    pass

    @property
    def background(self):
        """
        Background or underline color (if underlined is True).
        """
        return self._background

    @background.setter
    def background(self, value):
        self._background = value
        if self.editor:
            for clone in self.editor.clones:
                try:
                    clone.modes.get(self.__class__).background = value
                except KeyError:
                    # this should never happen since we're working with clones
                    pass

    @property
    def foreground(self):
        """
        Foreground color of occurences, not used if underlined is True.
        """
        return self._foreground

    @foreground.setter
    def foreground(self, value):
        self._foreground = value
        if self.editor:
            for clone in self.editor.clones:
                try:
                    clone.modes.get(self.__class__).foreground = value
                except KeyError:
                    # this should never happen since we're working with clones
                    pass

    @property
    def underlined(self):
        """
        True to use to underlined occurrences instead of
        changing the background. Default is True.

        If this mode is ON, the foreground color is ignored, the
        background color is then used as the underline color.

        """
        return self._underlined

    @underlined.setter
    def underlined(self, value):
        self._underlined = value
        if self.editor:
            for clone in self.editor.clones:
                try:
                    clone.modes.get(self.__class__).underlined = value
                except KeyError:
                    # this should never happen since we're working with clones
                    pass

    @property
    def case_sensitive(self):
        return self._case_sensitive

    @case_sensitive.setter
    def case_sensitive(self, value):
        self._case_sensitive = value
        self._request_highlight()

    def __init__(self):
        super(OccurrencesHighlighterMode, self).__init__()
        self._decorations = []
        #: Timer used to run the search request with a specific delay
        self.timer = DelayJobRunner(delay=1000)
        self._sub = None
        self._background = QtGui.QColor('#CCFFCC')
        self._foreground = None
        self._underlined = False
        self._case_sensitive = False

    def on_state_changed(self, state):
        if state:
            self.editor.cursorPositionChanged.connect(self._request_highlight)
        else:
            self.editor.cursorPositionChanged.disconnect(
                self._request_highlight)
            self.timer.cancel_requests()

    def _clear_decos(self):
        for d in self._decorations:
            self.editor.decorations.remove(d)
        self._decorations[:] = []

    def _request_highlight(self):
        if self.editor is not None:
            sub = TextHelper(self.editor).word_under_cursor(
                select_whole_word=True).selectedText()
            if sub != self._sub:
                self._clear_decos()
                if len(sub) > 1:
                    self.timer.request_job(self._send_request)

    def _send_request(self):
        if self.editor is None:
            return
        cursor = self.editor.textCursor()
        self._sub = TextHelper(self.editor).word_under_cursor(
            select_whole_word=True).selectedText()
        if not cursor.hasSelection() or cursor.selectedText() == self._sub:
            request_data = {
                'string': self.editor.toPlainText(),
                'sub': self._sub,
                'regex': False,
                'whole_word': True,
                'case_sensitive': self.case_sensitive
            }
            try:
                self.editor.backend.send_request(findall, request_data,
                                                 self._on_results_available)
            except NotRunning:
                self._request_highlight()

    def _on_results_available(self, results):
        if len(results) > 500:
            # limit number of results (on very big file where a lots of
            # occurrences can be found, this would totally freeze the editor
            # during a few seconds, with a limit of 500 we can make sure
            # the editor will always remain responsive).
            results = results[:500]
        current = self.editor.textCursor().position()
        if len(results) > 1:
            for start, end in results:
                if start <= current <= end:
                    continue
                deco = TextDecoration(self.editor.textCursor(),
                                      start_pos=start, end_pos=end)
                if self.underlined:
                    deco.set_as_underlined(self._background)
                else:
                    deco.set_background(QtGui.QBrush(self._background))
                    if self._foreground is not None:
                        deco.set_foreground(self._foreground)
                deco.draw_order = 3
                self.editor.decorations.append(deco)
                self._decorations.append(deco)

    def clone_settings(self, original):
        self.delay = original.delay
        self.background = original.background
        self.foreground = original.foreground
        self.underlined = original.underlined
示例#34
0
class WordClickMode(Mode, QtCore.QObject):
    """ Adds support for word click events.

    It will highlight the click-able word when the user press control and move
    the mouse over a word.

    Detecting whether a word is click-able is the responsability of the
    subclasses. You must override ``_check_word_cursor`` and call
    ``_select_word_cursor`` if this is a click-able word (this
    process might be asynchrone) otherwise _clear_selection.

    :attr:`pyqode.core.modes.WordClickMode.word_clicked` is emitted
    when the word is clicked by the user (while keeping control pressed).
    """
    #: Signal emitted when a word is clicked. The parameter is a
    #: QTextCursor with the clicked word set as the selected text.
    word_clicked = QtCore.Signal(QtGui.QTextCursor)

    def __init__(self):
        QtCore.QObject.__init__(self)
        Mode.__init__(self)
        self._previous_cursor_start = -1
        self._previous_cursor_end = -1
        self._deco = None
        self._cursor = None
        self._timer = DelayJobRunner(delay=200)

    def on_state_changed(self, state):
        if state:
            self.editor.mouse_moved.connect(self._on_mouse_moved)
            self.editor.mouse_released.connect(self._on_mouse_released)
            self.editor.key_released.connect(self._on_key_released)
            self.editor.mouse_double_clicked.connect(
                self._on_mouse_double_clicked)
        else:
            self.editor.mouse_moved.disconnect(self._on_mouse_moved)
            self.editor.mouse_released.disconnect(self._on_mouse_released)
            self.editor.key_released.disconnect(self._on_key_released)
            self.editor.mouse_double_clicked.disconnect(
                self._on_mouse_double_clicked)

    def _on_mouse_double_clicked(self):
        self._timer.cancel_requests()

    def _on_key_released(self, event):
        if event.key() == QtCore.Qt.Key_Control:
            self._clear_selection()
            self._cursor = None

    def _select_word_cursor(self):
        """ Selects the word under the mouse cursor. """
        cursor = TextHelper(self.editor).word_under_mouse_cursor()
        if (self._previous_cursor_start != cursor.selectionStart() and
                self._previous_cursor_end != cursor.selectionEnd()):
            self._remove_decoration()
            self._add_decoration(cursor)
        self._previous_cursor_start = cursor.selectionStart()
        self._previous_cursor_end = cursor.selectionEnd()

    def _clear_selection(self):
        try:
            self._remove_decoration()
        except ValueError:
            pass
        self.editor.set_mouse_cursor(QtCore.Qt.IBeamCursor)
        self._previous_cursor_start = -1
        self._previous_cursor_end = -1

    def _on_mouse_moved(self, event):
        """ mouse moved callback """
        if event.modifiers() & QtCore.Qt.ControlModifier:
            cursor = TextHelper(self.editor).word_under_mouse_cursor()
            if (not self._cursor or
                    cursor.position() != self._cursor.position()):
                self._check_word_cursor(cursor)
            self._cursor = cursor
        else:
            self._cursor = None
            self._clear_selection()

    def _check_word_cursor(self, cursor):
        pass

    def _on_mouse_released(self, event):
        """ mouse pressed callback """
        if event.button() == 1 and self._deco:
            cursor = TextHelper(self.editor).word_under_mouse_cursor()
            if cursor and cursor.selectedText():
                self._timer.request_job(
                    self.word_clicked.emit, cursor)

    def _add_decoration(self, cursor):
        """
        Adds a decoration for the word under ``cursor``.
        """
        if self._deco is None:
            if cursor.selectedText():
                self._deco = TextDecoration(cursor)
                if self.editor.background.lightness() < 128:
                    self._deco.set_foreground(QtGui.QColor('#0681e0'))
                else:
                    self._deco.set_foreground(QtCore.Qt.blue)
                self._deco.set_as_underlined()
                self.editor.decorations.append(self._deco)
                self.editor.set_mouse_cursor(QtCore.Qt.PointingHandCursor)
            else:
                self.editor.set_mouse_cursor(QtCore.Qt.IBeamCursor)

    def _remove_decoration(self):
        """
        Removes the word under cursor's decoration
        """
        if self._deco is not None:
            self.editor.decorations.remove(self._deco)
            self._deco = None
示例#35
0
 def __init__(self, parent):
     QtWidgets.QTabBar.__init__(self, parent)
     self.setTabsClosable(True)
     self._timer = DelayJobRunner(delay=1)
示例#36
0
class OutlineMode(Mode, QtCore.QObject):
    """
    Generic mode that provides outline information through the
    document_changed signal and a specialised worker function.

    To use this mode, you need to write a worker function that returns a list
    of pyqode.core.share.Definition (see
    pyqode.python.backend.workers.defined_names() for an example of how to
    implement the worker function).
    """

    #: Signal emitted when the document structure changed.
    document_changed = QtCore.Signal()

    @property
    def definitions(self):
        """
        Gets the list of top level definitions.
        """
        return self._results

    def __init__(self, worker, delay=1000):
        Mode.__init__(self)
        QtCore.QObject.__init__(self)
        self._worker = worker
        self._jobRunner = DelayJobRunner(delay=delay)
        #: The list of definitions found in the file, each item is a
        #: pyqode.core.share.Definition.
        self._results = []

    def on_state_changed(self, state):
        if state:
            self.editor.new_text_set.connect(self._run_analysis)
            self.editor.textChanged.connect(self._request_analysis)
        else:
            self.editor.textChanged.disconnect(self._request_analysis)
            self.editor.new_text_set.disconnect(self._run_analysis)
            self._jobRunner.cancel_requests()

    def _request_analysis(self):
        self._jobRunner.request_job(self._run_analysis)

    def _run_analysis(self):
        try:
            self.editor.file
            self.editor.toPlainText()
        except (RuntimeError, AttributeError):
            # called by the timer after the editor got deleted
            return
        if self.enabled:
            request_data = {
                'code': self.editor.toPlainText(),
                'path': self.editor.file.path,
                'encoding': self.editor.file.encoding
            }
            try:
                self.editor.backend.send_request(
                    self._worker,
                    request_data,
                    on_receive=self._on_results_available)
            except NotRunning:
                QtCore.QTimer.singleShot(100, self._run_analysis)
        else:
            self._results = []
            self.document_changed.emit()

    def _on_results_available(self, results):
        if results:
            results = [Definition.from_dict(ddict) for ddict in results]
        self._results = results
        if self._results is not None:
            _logger().log(5, "Document structure changed")
            self.document_changed.emit()
示例#37
0
class CheckerPanel(Panel):
    """ Shows messages collected by one or more checker modes """
    def __init__(self):
        super(CheckerPanel, self).__init__()
        self._previous_line = -1
        self.scrollable = True
        self._job_runner = DelayJobRunner(delay=100)
        self.setMouseTracking(True)
        #: Info icon
        self.info_icon = icons.icon('dialog-info',
                                    ':pyqode-icons/rc/dialog-info.png',
                                    'fa.info-circle',
                                    qta_options={'color': '#4040DD'})
        self.warning_icon = icons.icon('dialog-warning',
                                       ':pyqode-icons/rc/dialog-warning.png',
                                       'fa.exclamation-triangle',
                                       qta_options={'color': '#DDDD40'})
        self.error_icon = icons.icon('dialog-error',
                                     ':pyqode-icons/rc/dialog-error.png',
                                     'fa.exclamation-circle',
                                     qta_options={'color': '#DD4040'})

    def marker_for_line(self, line):
        """
        Returns the marker that is displayed at the specified line number if
        any.

        :param line: The marker line.

        :return: Marker of None
        :rtype: pyqode.core.Marker
        """
        block = self.editor.document().findBlockByNumber(line)
        try:
            return block.userData().messages
        except AttributeError:
            return []

    def sizeHint(self):
        """
        Returns the panel size hint. (fixed with of 16px)
        """
        metrics = QtGui.QFontMetricsF(self.editor.font())
        size_hint = QtCore.QSize(metrics.height(), metrics.height())
        if size_hint.width() > 16:
            size_hint.setWidth(16)
        return size_hint

    def on_uninstall(self):
        self._job_runner.cancel_requests()
        super(CheckerPanel, self).on_uninstall()

    def paintEvent(self, event):
        super(CheckerPanel, self).paintEvent(event)
        painter = QtGui.QPainter(self)
        for top, block_nbr, block in self.editor.visible_blocks:
            user_data = block.userData()
            if user_data and user_data.messages:
                for msg in user_data.messages:
                    icon = self._icon_from_message(msg)
                    if icon:
                        rect = QtCore.QRect()
                        rect.setX(0)
                        rect.setY(top)
                        rect.setWidth(self.sizeHint().width())
                        rect.setHeight(self.sizeHint().height())
                        icon.paint(painter, rect)

    def _icon_from_message(self, message):
        icons = {
            CheckerMessages.INFO: self.info_icon,
            CheckerMessages.WARNING: self.warning_icon,
            CheckerMessages.ERROR: self.error_icon
        }
        return icons[message.status]

    def mouseMoveEvent(self, event):
        # Requests a tooltip if the cursor is currently over a marker.
        line = TextHelper(self.editor).line_nbr_from_position(event.pos().y())
        if line:
            markers = self.marker_for_line(line)
            text = '\n'.join([
                marker.description for marker in markers if marker.description
            ])
            if len(markers):
                if self._previous_line != line:
                    top = TextHelper(self.editor).line_pos_from_number(
                        markers[0].line)
                    if top:
                        self._job_runner.request_job(self._display_tooltip,
                                                     text, top)
            else:
                self._job_runner.cancel_requests()
            self._previous_line = line

    def leaveEvent(self, *args):
        """
        Hide tooltip when leaving the panel region.
        """
        QtWidgets.QToolTip.hideText()
        self._previous_line = -1

    def _display_tooltip(self, tooltip, top):
        """
        Display tooltip at the specified top position.
        """
        QtWidgets.QToolTip.showText(
            self.mapToGlobal(QtCore.QPoint(self.sizeHint().width(), top)),
            tooltip, self)
class GoToAssignmentsMode(WordClickMode):
    """
    Goes to the assignments (using jedi.Script.goto_assignments) when the user
    execute the shortcut or click word. If there are more than one assignments,
    an input dialog is used to ask the user to choose the desired assignment.

    This mode will emit the :attr:`out_of_doc` signal if the definition can
    not be reached in the current document. IDE will typically connects a slot
    that open a new editor tab and goes to the definition position.
    """
    #: Signal emitted when the definition cannot be reached in the current
    #: document
    out_of_doc = QtCore.Signal(Assignment)

    #: Signal emitted when no results could be found.
    no_results_found = QtCore.Signal()

    shortcut = 'Alt+F2'

    def __init__(self):
        super(GoToAssignmentsMode, self).__init__()
        self._definitions = []
        self._goto_requested = False
        self.action_goto = QtWidgets.QAction("Go to assignments", self)
        self.action_goto.setShortcut(self.shortcut)
        self.action_goto.triggered.connect(self.request_goto)
        icon = icons.icon(qta_name='fa.share')
        if icon:
            self.action_goto.setIcon(icon)
        self.word_clicked.connect(self._on_word_clicked)
        self._runner = DelayJobRunner(delay=1)

    def on_state_changed(self, state):
        super(GoToAssignmentsMode, self).on_state_changed(state)
        if state:
            self.editor.add_action(self.action_goto)
        else:
            self.editor.remove_action(self.action_goto)

    def request_goto(self):
        """
        Request a goto action for the word under the text cursor.
        """
        self._goto_requested = True
        self._check_word_cursor()

    def _check_word_cursor(self, tc=None):
        """
        Request a go to assignment.

        :param tc: Text cursor which contains the text that we must look for
                   its assignment. Can be None to go to the text that is under
                   the text cursor.
        :type tc: QtGui.QTextCursor
        """
        if not tc:
            tc = TextHelper(self.editor).word_under_cursor()

        request_data = {
            'code': self.editor.toPlainText(),
            'line': tc.blockNumber(),
            'column': tc.columnNumber(),
            'path': self.editor.file.path,
            'encoding': self.editor.file.encoding
        }
        try:
            self.editor.backend.send_request(
                workers.goto_assignments, request_data,
                on_receive=self._on_results_available)
        except NotRunning:
            pass

    def _goto(self, definition):
        fp = ''
        if self.editor.file.path:
            fp = os.path.normpath(self.editor.file.path.replace(".pyc", ".py"))
        if definition.module_path == fp:
            line = definition.line
            col = definition.column
            _logger().debug("Go to %s" % definition)
            self._runner.request_job(
                TextHelper(self.editor).goto_line,
                line, move=True, column=col)
        else:
            _logger().debug("Out of doc: %s" % definition)
            self.out_of_doc.emit(definition)

    def _unique(self, seq):
        """
        Not performant but works.
        """
        # order preserving
        checked = []
        for e in seq:
            present = False
            for c in checked:
                if str(c) == str(e):
                    present = True
                    break
            if not present:
                checked.append(e)
        return checked

    def _clear_selection(self):
        super(GoToAssignmentsMode, self)._clear_selection()
        self._definitions[:] = []

    def _validate_definitions(self, definitions):
        if definitions:
            if len(definitions) == 1:
                return definitions[0].line is not None
            return True
        return False

    def _on_results_available(self, definitions):
        _logger().debug("Got %r" % definitions)
        definitions = [Assignment(path, line, col, full_name)
                       for path, line, col, full_name in definitions]
        definitions = self._unique(definitions)
        self._definitions = definitions
        if self._validate_definitions(definitions):
            if self._goto_requested:
                self._perform_goto(definitions)
            else:
                self._select_word_cursor()
                self.editor.set_mouse_cursor(QtCore.Qt.PointingHandCursor)
        else:
            self._clear_selection()
            self.editor.set_mouse_cursor(QtCore.Qt.IBeamCursor)
        self._goto_requested = False

    def _perform_goto(self, definitions):
        if len(definitions) == 1:
            definition = definitions[0]
            if definition:
                self._goto(definition)
        elif len(definitions) > 1:
            _logger().debug(
                "More than 1 assignments in different modules, user "
                "need to make a choice: %s" % definitions)
            def_str, result = QtWidgets.QInputDialog.getItem(
                self.editor, "Choose a definition",
                "Choose the definition you want to go to:",
                [str(d) for d in definitions])
            if result:
                for definition in definitions:
                    if definition and str(definition) == def_str:
                        self._goto(definition)
                        break

    def _on_word_clicked(self):
        self._perform_goto(self._definitions)
示例#39
0
class WordClickMode(Mode, QtCore.QObject):
    """ Adds support for word click events.

    It will highlight the click-able word when the user press control and move
    the mouse over a word.

    Detecting whether a word is click-able is the responsability of the
    subclasses. You must override ``_check_word_cursor`` and call
    ``_select_word_cursor`` if this is a click-able word (this
    process might be asynchrone) otherwise _clear_selection.

    :attr:`pyqode.core.modes.WordClickMode.word_clicked` is emitted
    when the word is clicked by the user (while keeping control pressed).
    """
    #: Signal emitted when a word is clicked. The parameter is a
    #: QTextCursor with the clicked word set as the selected text.
    word_clicked = QtCore.Signal(QtGui.QTextCursor)

    def __init__(self):
        QtCore.QObject.__init__(self)
        Mode.__init__(self)
        self._previous_cursor_start = -1
        self._previous_cursor_end = -1
        self._deco = None
        self._cursor = None
        self._timer = DelayJobRunner(delay=200)

    def on_state_changed(self, state):
        if state:
            self.editor.mouse_moved.connect(self._on_mouse_moved)
            self.editor.mouse_pressed.connect(self._on_mouse_pressed)
            self.editor.key_released.connect(self._on_key_released)
            self.editor.mouse_double_clicked.connect(
                self._on_mouse_double_clicked)
        else:
            self.editor.mouse_moved.disconnect(self._on_mouse_moved)
            self.editor.mouse_pressed.disconnect(self._on_mouse_pressed)
            self.editor.key_released.disconnect(self._on_key_released)
            self.editor.mouse_double_clicked.disconnect(
                self._on_mouse_double_clicked)

    def _on_mouse_double_clicked(self):
        self._timer.cancel_requests()

    def _on_key_released(self, event):
        if event.key() == QtCore.Qt.Key_Control:
            self._clear_selection()
            self._cursor = None

    def _select_word_cursor(self):
        """ Selects the word under the mouse cursor. """
        cursor = TextHelper(self.editor).word_under_mouse_cursor()
        if (self._previous_cursor_start != cursor.selectionStart()
                and self._previous_cursor_end != cursor.selectionEnd()):
            self._remove_decoration()
            self._add_decoration(cursor)
        self._previous_cursor_start = cursor.selectionStart()
        self._previous_cursor_end = cursor.selectionEnd()

    def _clear_selection(self):
        try:
            self._remove_decoration()
        except ValueError:
            pass
        self.editor.set_mouse_cursor(QtCore.Qt.IBeamCursor)
        self._previous_cursor_start = -1
        self._previous_cursor_end = -1

    def _on_mouse_moved(self, event):
        """ mouse moved callback """
        if event.modifiers() & QtCore.Qt.ControlModifier:
            cursor = TextHelper(self.editor).word_under_mouse_cursor()
            if (not self._cursor
                    or cursor.position() != self._cursor.position()):
                self._check_word_cursor(cursor)
            self._cursor = cursor
        else:
            self._cursor = None
            self._clear_selection()

    def _check_word_cursor(self, cursor):
        pass

    def _on_mouse_pressed(self, event):
        """ mouse pressed callback """
        if event.button() == 1 and self._deco:
            cursor = TextHelper(self.editor).word_under_mouse_cursor()
            if cursor and cursor.selectedText():
                self._timer.request_job(self.word_clicked.emit, cursor)

    def _add_decoration(self, cursor):
        """
        Adds a decoration for the word under ``cursor``.
        """
        if self._deco is None:
            if cursor.selectedText():
                self._deco = TextDecoration(cursor)
                if self.editor.background.lightness() < 128:
                    self._deco.set_foreground(QtGui.QColor('#0681e0'))
                else:
                    self._deco.set_foreground(QtCore.Qt.blue)
                self._deco.set_as_underlined()
                self.editor.decorations.append(self._deco)
                self.editor.set_mouse_cursor(QtCore.Qt.PointingHandCursor)
            else:
                self.editor.set_mouse_cursor(QtCore.Qt.IBeamCursor)

    def _remove_decoration(self):
        """
        Removes the word under cursor's decoration
        """
        if self._deco is not None:
            self.editor.decorations.remove(self._deco)
            self._deco = None
示例#40
0
class GoToDefinitionMode(Mode, QObject):
    """
    Go to the definition of the symbol under the word cursor.
    """
    #: Signal emitted when a word is clicked. The parameter is a
    #: QTextCursor with the clicked word set as the selected text.
    word_clicked = Signal(QTextCursor)

    def __init__(self):
        QObject.__init__(self)
        Mode.__init__(self)
        self._previous_cursor_start = -1
        self._previous_cursor_end = -1
        self._definition = None
        self._deco = None
        self._pending = False
        self.action_goto = QAction(_("Go to assignments"), self)
        self.action_goto.setShortcut('F7')
        self.action_goto.triggered.connect(self.request_goto)
        self.word_clicked.connect(self.request_goto)
        self._timer = DelayJobRunner(delay=200)

    def on_state_changed(self, state):
        """
        Connects/disconnects slots to/from signals when the mode state
        changed.
        """
        super(GoToDefinitionMode, self).on_state_changed(state)
        if state:
            self.editor.mouse_moved.connect(self._on_mouse_moved)
            self.editor.mouse_released.connect(self._on_mouse_released)
            self.editor.add_action(self.action_goto, sub_menu='COBOL')
            self.editor.mouse_double_clicked.connect(
                self._timer.cancel_requests)
        else:
            self.editor.mouse_moved.disconnect(self._on_mouse_moved)
            self.editor.mouse_released.disconnect(self._on_mouse_released)
            self.editor.remove_action(self.action_goto, sub_menu='Python')
            self.editor.mouse_double_clicked.disconnect(
                self._timer.cancel_requests)

    def _select_word_under_mouse_cursor(self):
        """ Selects the word under the mouse cursor. """
        cursor = TextHelper(self.editor).word_under_mouse_cursor()
        if (self._previous_cursor_start != cursor.selectionStart()
                and self._previous_cursor_end != cursor.selectionEnd()):
            self._remove_decoration()
            self._add_decoration(cursor)
        self._previous_cursor_start = cursor.selectionStart()
        self._previous_cursor_end = cursor.selectionEnd()

    def _on_mouse_moved(self, event):
        """ mouse moved callback """
        if event.modifiers() & Qt.ControlModifier:
            self._select_word_under_mouse_cursor()
        else:
            self._remove_decoration()
            self.editor.set_mouse_cursor(Qt.IBeamCursor)
            self._previous_cursor_start = -1
            self._previous_cursor_end = -1

    def _on_mouse_released(self, event):
        """ mouse pressed callback """
        if event.button() == 1 and self._deco:
            cursor = TextHelper(self.editor).word_under_mouse_cursor()
            if cursor and cursor.selectedText():
                self._timer.request_job(self.word_clicked.emit, cursor)

    def find_definition(self, symbol, definition):
        if symbol.lower() == definition.name.lower().replace(
                " section", "").replace(" division", ""):
            return definition
        for ch in definition.children:
            d = self.find_definition(symbol, ch)
            if d is not None:
                return d
        return None

    def select_word(self, cursor):
        symbol = cursor.selectedText()
        analyser = self.editor.outline_mode
        for definition in analyser.definitions:
            node = self.find_definition(symbol, definition)
            if node is not None:
                break
        else:
            node = None
        self._definition = None
        if node and node.line != cursor.block().blockNumber():
            self._definition = node
            if self._deco is None:
                if cursor.selectedText():
                    self._deco = TextDecoration(cursor)
                    self._deco.set_foreground(Qt.blue)
                    self._deco.set_as_underlined()
                    self.editor.decorations.append(self._deco)
                    return True
        return False

    def _add_decoration(self, cursor):
        """
        Adds a decoration for the word under ``cursor``.
        """
        if self.select_word(cursor):
            self.editor.set_mouse_cursor(Qt.PointingHandCursor)
        else:
            self.editor.set_mouse_cursor(Qt.IBeamCursor)

    def _remove_decoration(self):
        """
        Removes the word under cursor's decoration
        """
        if self._deco is not None:
            self.editor.decorations.remove(self._deco)
            self._deco = None

    def request_goto(self, tc=None):
        """
        Request a go to assignment.

        :param tc: Text cursor which contains the text that we must look for
                   its assignment. Can be None to go to the text that is under
                   the text cursor.
        :type tc: QtGui.QTextCursor
        """
        if not tc:
            tc = TextHelper(
                self.editor).word_under_cursor(select_whole_word=True)
        if not self._definition or isinstance(self.sender(), QAction):
            self.select_word(tc)
        if self._definition is not None:
            QTimer.singleShot(100, self._goto_def)

    def _goto_def(self):
        if self._definition:
            line = self._definition.line
            col = self._definition.column
            TextHelper(self.editor).goto_line(line, move=True, column=col)
示例#41
0
class OccurrencesHighlighterMode(Mode):
    """ Highlights occurrences of the word under the text text cursor.

    The ``delay`` before searching for occurrences is configurable.
    """
    @property
    def delay(self):
        """
        Delay before searching for occurrences. The timer is rearmed as soon
        as the cursor position changed.
        """
        return self.timer.delay

    @delay.setter
    def delay(self, value):
        self.timer.delay = value
        if self.editor:
            for clone in self.editor.clones:
                try:
                    clone.modes.get(self.__class__).delay = value
                except KeyError:
                    # this should never happen since we're working with clones
                    pass

    @property
    def background(self):
        """
        Background or underline color (if underlined is True).
        """
        return self._background

    @background.setter
    def background(self, value):
        self._background = value
        if self.editor:
            for clone in self.editor.clones:
                try:
                    clone.modes.get(self.__class__).background = value
                except KeyError:
                    # this should never happen since we're working with clones
                    pass

    @property
    def foreground(self):
        """
        Foreground color of occurences, not used if underlined is True.
        """
        return self._foreground

    @foreground.setter
    def foreground(self, value):
        self._foreground = value
        if self.editor:
            for clone in self.editor.clones:
                try:
                    clone.modes.get(self.__class__).foreground = value
                except KeyError:
                    # this should never happen since we're working with clones
                    pass

    @property
    def underlined(self):
        """
        True to use to underlined occurrences instead of
        changing the background. Default is True.

        If this mode is ON, the foreground color is ignored, the
        background color is then used as the underline color.

        """
        return self._underlined

    @underlined.setter
    def underlined(self, value):
        self._underlined = value
        if self.editor:
            for clone in self.editor.clones:
                try:
                    clone.modes.get(self.__class__).underlined = value
                except KeyError:
                    # this should never happen since we're working with clones
                    pass

    @property
    def case_sensitive(self):
        return self._case_sensitive

    @case_sensitive.setter
    def case_sensitive(self, value):
        self._case_sensitive = value
        self._request_highlight()

    def __init__(self):
        super(OccurrencesHighlighterMode, self).__init__()
        self._decorations = []
        #: Timer used to run the search request with a specific delay
        self.timer = DelayJobRunner(delay=1000)
        self._sub = None
        self._background = QtGui.QColor('#CCFFCC')
        self._foreground = None
        self._underlined = False
        self._case_sensitive = False

    def on_state_changed(self, state):
        if state:
            self.editor.cursorPositionChanged.connect(self._request_highlight)
        else:
            self.editor.cursorPositionChanged.disconnect(
                self._request_highlight)
            self.timer.cancel_requests()

    def _clear_decos(self):
        for d in self._decorations:
            self.editor.decorations.remove(d)
        self._decorations[:] = []

    def _request_highlight(self):
        if self.editor is not None:
            sub = TextHelper(self.editor).word_under_cursor(
                select_whole_word=True).selectedText()
            if sub != self._sub:
                self._clear_decos()
                if len(sub) > 1:
                    self.timer.request_job(self._send_request)

    def _send_request(self):
        if self.editor is None:
            return
        cursor = self.editor.textCursor()
        self._sub = TextHelper(self.editor).word_under_cursor(
            select_whole_word=True).selectedText()
        if not cursor.hasSelection() or cursor.selectedText() == self._sub:
            request_data = {
                'string': self.editor.toPlainText(),
                'sub': self._sub,
                'regex': False,
                'whole_word': True,
                'case_sensitive': self.case_sensitive
            }
            try:
                self.editor.backend.send_request(findall, request_data,
                                                 self._on_results_available)
            except NotRunning:
                self._request_highlight()

    def _on_results_available(self, results):
        if len(results) > 500:
            # limit number of results (on very big file where a lots of
            # occurrences can be found, this would totally freeze the editor
            # during a few seconds, with a limit of 500 we can make sure
            # the editor will always remain responsive).
            results = results[:500]
        current = self.editor.textCursor().position()
        if len(results) > 1:
            for start, end in results:
                if start <= current <= end:
                    continue
                deco = TextDecoration(self.editor.textCursor(),
                                      start_pos=start,
                                      end_pos=end)
                if self.underlined:
                    deco.set_as_underlined(self._background)
                else:
                    deco.set_background(QtGui.QBrush(self._background))
                    if self._foreground is not None:
                        deco.set_foreground(self._foreground)
                deco.draw_order = 3
                self.editor.decorations.append(deco)
                self._decorations.append(deco)

    def clone_settings(self, original):
        self.delay = original.delay
        self.background = original.background
        self.foreground = original.foreground
        self.underlined = original.underlined
示例#42
0
class FoldingPanel(Panel):

    """ Displays the document outline and lets the user collapse/expand blocks.

    The data represented by the panel come from the text block user state and
    is set by the SyntaxHighlighter mode.

    The panel does not expose any function that you can use directly. To
    interact with the fold tree, you need to modify text block fold level or
    trigger state using :class:`pyqode.core.api.utils.TextBlockHelper` or
    :mod:`pyqode.core.api.folding`
    """
    #: signal emitted when a fold trigger state has changed, parameters are
    #: the concerned text block and the new state (collapsed or not).
    trigger_state_changed = QtCore.Signal(QtGui.QTextBlock, bool)
    collapse_all_triggered = QtCore.Signal()
    expand_all_triggered = QtCore.Signal()

    @property
    def native_look(self):
        """
        Defines whether the panel will use native indicator icons and color or
        use custom one.

        If you want to use custom indicator icons and color, you must first
        set this flag to False.
        """
        return self._native

    @native_look.setter
    def native_look(self, value):
        self._native = value
        # propagate changes to every clone
        if self.editor:
            for clone in self.editor.clones:
                try:
                    clone.modes.get(self.__class__).native_look = value
                except KeyError:
                    # this should never happen since we're working with clones
                    pass

    @property
    def custom_indicators_icons(self):
        """
        Gets/sets the custom icon for the fold indicators.

        The list of indicators is interpreted as follow::

            (COLLAPSED_OFF, COLLAPSED_ON, EXPANDED_OFF, EXPANDED_ON)

        To use this property you must first set `native_look` to False.

        :returns: tuple(str, str, str, str)
        """
        return self._custom_indicators

    @custom_indicators_icons.setter
    def custom_indicators_icons(self, value):
        if len(value) != 4:
            raise ValueError('The list of custom indicators must contains 4 '
                             'strings')
        self._custom_indicators = value
        if self.editor:
            # propagate changes to every clone
            for clone in self.editor.clones:
                try:
                    clone.modes.get(
                        self.__class__).custom_indicators_icons = value
                except KeyError:
                    # this should never happen since we're working with clones
                    pass

    @property
    def custom_fold_region_background(self):
        """
        Custom base color for the fold region background

        :return: QColor
        """
        return self._custom_color

    @custom_fold_region_background.setter
    def custom_fold_region_background(self, value):
        self._custom_color = value
        # propagate changes to every clone
        if self.editor:
            for clone in self.editor.clones:
                try:
                    clone.modes.get(
                        self.__class__).custom_fold_region_background = value
                except KeyError:
                    # this should never happen since we're working with clones
                    pass

    @property
    def highlight_caret_scope(self):
        """
        True to highlight the caret scope automatically.

        (Similar to the ``Highlight blocks in Qt Creator``.

        Default is False.
        """
        return self._highlight_caret

    @highlight_caret_scope.setter
    def highlight_caret_scope(self, value):
        if value != self._highlight_caret:
            self._highlight_caret = value
            if self.editor:
                if value:
                    self._block_nbr = -1
                    self.editor.cursorPositionChanged.connect(
                        self._highlight_caret_scope)
                else:
                    self._block_nbr = -1
                    self.editor.cursorPositionChanged.disconnect(
                        self._highlight_caret_scope)
                for clone in self.editor.clones:
                    try:
                        clone.modes.get(
                            self.__class__).highlight_caret_scope = value
                    except KeyError:
                        # this should never happen since we're working with
                        # clones
                        pass

    def __init__(self, highlight_caret_scope=False):
        Panel.__init__(self)
        self._native = True
        self._custom_indicators = (
            ':/pyqode-icons/rc/arrow_right_off.png',
            ':/pyqode-icons/rc/arrow_right_on.png',
            ':/pyqode-icons/rc/arrow_down_off.png',
            ':/pyqode-icons/rc/arrow_down_on.png'
        )
        self._custom_color = QtGui.QColor('gray')
        self._block_nbr = -1
        self._highlight_caret = False
        self.highlight_caret_scope = highlight_caret_scope
        self._indic_size = 16
        #: the list of deco used to highlight the current fold region (
        #: surrounding regions are darker)
        self._scope_decos = []
        #: the list of folded blocs decorations
        self._block_decos = []
        self.setMouseTracking(True)
        self.scrollable = True
        self._mouse_over_line = None
        self._current_scope = None
        self._prev_cursor = None
        self.context_menu = None
        self.action_collapse = None
        self.action_expand = None
        self.action_collapse_all = None
        self.action_expand_all = None
        self._original_background = None
        self._highlight_runner = DelayJobRunner(delay=250)

    def on_install(self, editor):
        """
        Add the folding menu to the editor, on install.

        :param editor: editor instance on which the mode has been installed to.
        """
        super(FoldingPanel, self).on_install(editor)
        self.context_menu = QtWidgets.QMenu('Folding', self.editor)
        action = self.action_collapse = QtWidgets.QAction(
            'Collapse', self.context_menu)
        action.setShortcut('Shift+-')
        action.triggered.connect(self._on_action_toggle)
        self.context_menu.addAction(action)
        action = self.action_expand = QtWidgets.QAction('Expand',
                                                        self.context_menu)
        action.setShortcut('Shift++')
        action.triggered.connect(self._on_action_toggle)
        self.context_menu.addAction(action)
        self.context_menu.addSeparator()
        action = self.action_collapse_all = QtWidgets.QAction(
            'Collapse all', self.context_menu)
        action.setShortcut('Ctrl+Shift+-')
        action.triggered.connect(self._on_action_collapse_all_triggered)
        self.context_menu.addAction(action)
        action = self.action_expand_all = QtWidgets.QAction(
            'Expand all', self.context_menu)
        action.setShortcut('Ctrl+Shift++')
        action.triggered.connect(self._on_action_expand_all_triggered)
        self.context_menu.addAction(action)
        self.editor.add_menu(self.context_menu)

    def sizeHint(self):
        """ Returns the widget size hint (based on the editor font size) """
        fm = QtGui.QFontMetricsF(self.editor.font())
        size_hint = QtCore.QSize(fm.height(), fm.height())
        if size_hint.width() > 16:
            size_hint.setWidth(16)
        return size_hint

    def paintEvent(self, event):
        # Paints the fold indicators and the possible fold region background
        # on the folding panel.
        super(FoldingPanel, self).paintEvent(event)
        painter = QtGui.QPainter(self)
        # Draw background over the selected non collapsed fold region
        if self._mouse_over_line is not None:
            block = self.editor.document().findBlockByNumber(
                self._mouse_over_line)
            try:
                self._draw_fold_region_background(block, painter)
            except ValueError:
                pass
        # Draw fold triggers
        for top_position, line_number, block in self.editor.visible_blocks:
            if TextBlockHelper.is_fold_trigger(block):
                collapsed = TextBlockHelper.get_fold_trigger_state(block)
                mouse_over = self._mouse_over_line == line_number
                self._draw_fold_indicator(
                    top_position, mouse_over, collapsed, painter)
                if collapsed:
                    # check if the block already has a decoration, it might
                    # have been folded by the parent editor/document in the
                    # case of cloned editor
                    for deco in self._block_decos:
                        if deco.block == block:
                            # no need to add a deco, just go to the next block
                            break
                    else:
                        self._add_fold_decoration(block, FoldScope(block))
                else:
                    for deco in self._block_decos:
                        # check if the block decoration has been removed, it
                        # might have been unfolded by the parent
                        # editor/document in the case of cloned editor
                        if deco.block == block:
                            # remove it and
                            self._block_decos.remove(deco)
                            self.editor.decorations.remove(deco)
                            del deco
                            break

    def _draw_fold_region_background(self, block, painter):
        """
        Draw the fold region when the mouse is over and non collapsed
        indicator.

        :param top: Top position
        :param block: Current block.
        :param painter: QPainter
        """
        r = folding.FoldScope(block)
        th = TextHelper(self.editor)
        start, end = r.get_range(ignore_blank_lines=True)
        if start > 0:
            top = th.line_pos_from_number(start)
        else:
            top = 0
        bottom = th.line_pos_from_number(end + 1)
        h = bottom - top
        if h == 0:
            h = self.sizeHint().height()
        w = self.sizeHint().width()
        self._draw_rect(QtCore.QRectF(0, top, w, h), painter)

    def _draw_rect(self, rect, painter):
        """
        Draw the background rectangle using the current style primitive color
        or foldIndicatorBackground if nativeFoldingIndicator is true.

        :param rect: The fold zone rect to draw

        :param painter: The widget's painter.
        """
        c = self._custom_color
        if self._native:
            c = self.get_system_bck_color()
        grad = QtGui.QLinearGradient(rect.topLeft(),
                                     rect.topRight())
        if sys.platform == 'darwin':
            grad.setColorAt(0, c.lighter(100))
            grad.setColorAt(1, c.lighter(110))
            outline = c.darker(110)
        else:
            grad.setColorAt(0, c.lighter(110))
            grad.setColorAt(1, c.lighter(130))
            outline = c.darker(100)
        painter.fillRect(rect, grad)
        painter.setPen(QtGui.QPen(outline))
        painter.drawLine(rect.topLeft() +
                         QtCore.QPointF(1, 0),
                         rect.topRight() -
                         QtCore.QPointF(1, 0))
        painter.drawLine(rect.bottomLeft() +
                         QtCore.QPointF(1, 0),
                         rect.bottomRight() -
                         QtCore.QPointF(1, 0))
        painter.drawLine(rect.topRight() +
                         QtCore.QPointF(0, 1),
                         rect.bottomRight() -
                         QtCore.QPointF(0, 1))
        painter.drawLine(rect.topLeft() +
                         QtCore.QPointF(0, 1),
                         rect.bottomLeft() -
                         QtCore.QPointF(0, 1))

    @staticmethod
    def get_system_bck_color():
        """
        Gets a system color for drawing the fold scope background.
        """
        def merged_colors(colorA, colorB, factor):
            maxFactor = 100
            colorA = QtGui.QColor(colorA)
            colorB = QtGui.QColor(colorB)
            tmp = colorA
            tmp.setRed((tmp.red() * factor) / maxFactor +
                       (colorB.red() * (maxFactor - factor)) / maxFactor)
            tmp.setGreen((tmp.green() * factor) / maxFactor +
                         (colorB.green() * (maxFactor - factor)) / maxFactor)
            tmp.setBlue((tmp.blue() * factor) / maxFactor +
                        (colorB.blue() * (maxFactor - factor)) / maxFactor)
            return tmp

        pal = QtWidgets.QApplication.instance().palette()
        b = pal.window().color()
        h = pal.highlight().color()
        return merged_colors(b, h, 50)

    def _draw_fold_indicator(self, top, mouse_over, collapsed, painter):
        """
        Draw the fold indicator/trigger (arrow).

        :param top: Top position
        :param mouse_over: Whether the mouse is over the indicator
        :param collapsed: Whether the trigger is collapsed or not.
        :param painter: QPainter
        """
        rect = QtCore.QRect(0, top, self.sizeHint().width(),
                            self.sizeHint().height())
        if self._native:
            if os.environ['QT_API'].lower() not in PYQT5_API:
                opt = QtGui.QStyleOptionViewItemV2()
            else:
                opt = QtWidgets.QStyleOptionViewItem()
            opt.rect = rect
            opt.state = (QtWidgets.QStyle.State_Active |
                         QtWidgets.QStyle.State_Item |
                         QtWidgets.QStyle.State_Children)
            if not collapsed:
                opt.state |= QtWidgets.QStyle.State_Open
            if mouse_over:
                opt.state |= (QtWidgets.QStyle.State_MouseOver |
                              QtWidgets.QStyle.State_Enabled |
                              QtWidgets.QStyle.State_Selected)
                opt.palette.setBrush(QtGui.QPalette.Window,
                                     self.palette().highlight())
            opt.rect.translate(-2, 0)
            self.style().drawPrimitive(QtWidgets.QStyle.PE_IndicatorBranch,
                                       opt, painter, self)
        else:
            index = 0
            if not collapsed:
                index = 2
            if mouse_over:
                index += 1
            QtGui.QIcon(self._custom_indicators[index]).paint(painter, rect)

    @staticmethod
    def find_parent_scope(block):
        """
        Find parent scope, if the block is not a fold trigger.

        """
        original = block
        if not TextBlockHelper.is_fold_trigger(block):
            # search level of next non blank line
            while block.text().strip() == '' and block.isValid():
                block = block.next()
            ref_lvl = TextBlockHelper.get_fold_lvl(block) - 1
            block = original
            while (block.blockNumber() and
                   (not TextBlockHelper.is_fold_trigger(block) or
                    TextBlockHelper.get_fold_lvl(block) > ref_lvl)):
                block = block.previous()
        return block

    def _clear_scope_decos(self):
        """
        Clear scope decorations (on the editor)

        """
        for deco in self._scope_decos:
            self.editor.decorations.remove(deco)
        self._scope_decos[:] = []

    def _get_scope_highlight_color(self):
        """
        Gets the base scope highlight color (derivated from the editor
        background)

        """
        color = self.editor.background
        if color.lightness() < 128:
            color = drift_color(color, 130)
        else:
            color = drift_color(color, 105)
        return color

    def _add_scope_deco(self, start, end, parent_start, parent_end, base_color,
                        factor):
        """
        Adds a scope decoration that enclose the current scope
        :param start: Start of the current scope
        :param end: End of the current scope
        :param parent_start: Start of the parent scope
        :param parent_end: End of the parent scope
        :param base_color: base color for scope decoration
        :param factor: color factor to apply on the base color (to make it
            darker).
        """
        color = drift_color(base_color, factor=factor)
        # upper part
        if start > 0:
            d = TextDecoration(self.editor.document(),
                               start_line=parent_start, end_line=start)
            d.set_full_width(True, clear=False)
            d.draw_order = 2
            d.set_background(color)
            self.editor.decorations.append(d)
            self._scope_decos.append(d)
        # lower part
        if end <= self.editor.document().blockCount():
            d = TextDecoration(self.editor.document(),
                               start_line=end, end_line=parent_end + 1)
            d.set_full_width(True, clear=False)
            d.draw_order = 2
            d.set_background(color)
            self.editor.decorations.append(d)
            self._scope_decos.append(d)

    def _add_scope_decorations(self, block, start, end):
        """
        Show a scope decoration on the editor widget

        :param start: Start line
        :param end: End line
        """
        try:
            parent = FoldScope(block).parent()
        except ValueError:
            parent = None
        if TextBlockHelper.is_fold_trigger(block):
            base_color = self._get_scope_highlight_color()
            factor_step = 5
            if base_color.lightness() < 128:
                factor_step = 10
                factor = 70
            else:
                factor = 100
            while parent:
                # highlight parent scope
                parent_start, parent_end = parent.get_range()
                self._add_scope_deco(
                    start, end + 1, parent_start, parent_end,
                    base_color, factor)
                # next parent scope
                start = parent_start
                end = parent_end
                parent = parent.parent()
                factor += factor_step
            # global scope
            parent_start = 0
            parent_end = self.editor.document().blockCount()
            self._add_scope_deco(
                start, end + 1, parent_start, parent_end, base_color,
                factor + factor_step)
        else:
            self._clear_scope_decos()

    def _highlight_surrounding_scopes(self, block):
        """
        Highlights the scopes surrounding the current fold scope.

        :param block: Block that starts the current fold scope.
        """
        scope = FoldScope(block)
        if (self._current_scope is None or
                self._current_scope.get_range() != scope.get_range()):
            self._current_scope = scope
            self._clear_scope_decos()
            # highlight surrounding parent scopes with a darker color
            start, end = scope.get_range()
            if not TextBlockHelper.get_fold_trigger_state(block):
                self._add_scope_decorations(block, start, end)

    def mouseMoveEvent(self, event):
        """
        Detect mouser over indicator and highlight the current scope in the
        editor (up and down decoration arround the foldable text when the mouse
        is over an indicator).

        :param event: event
        """
        super(FoldingPanel, self).mouseMoveEvent(event)
        th = TextHelper(self.editor)
        line = th.line_nbr_from_position(event.pos().y())
        if line >= 0:
            block = FoldScope.find_parent_scope(
                self.editor.document().findBlockByNumber(line))
            if TextBlockHelper.is_fold_trigger(block):
                if self._mouse_over_line is None:
                    # mouse enter fold scope
                    QtWidgets.QApplication.setOverrideCursor(
                        QtGui.QCursor(QtCore.Qt.PointingHandCursor))
                if self._mouse_over_line != block.blockNumber() and \
                        self._mouse_over_line is not None:
                    # fold scope changed, a previous block was highlighter so
                    # we quickly update our highlighting
                    self._mouse_over_line = block.blockNumber()
                    self._highlight_surrounding_scopes(block)
                else:
                    # same fold scope, request highlight
                    self._mouse_over_line = block.blockNumber()
                    self._highlight_runner.request_job(
                        self._highlight_surrounding_scopes, block)
                self._highight_block = block
            else:
                # no fold scope to highlight, cancel any pending requests
                self._highlight_runner.cancel_requests()
                self._mouse_over_line = None
                QtWidgets.QApplication.restoreOverrideCursor()
            self.repaint()

    def leaveEvent(self, event):
        """
        Removes scope decorations and background from the editor and the panel
        if highlight_caret_scope, else simply update the scope decorations to
        match the caret scope.

        """
        super(FoldingPanel, self).leaveEvent(event)
        QtWidgets.QApplication.restoreOverrideCursor()
        self._highlight_runner.cancel_requests()
        if not self.highlight_caret_scope:
            self._clear_scope_decos()
            self._mouse_over_line = None
            self._current_scope = None
        else:
            self._block_nbr = -1
            self._highlight_caret_scope()
        self.editor.repaint()

    def _add_fold_decoration(self, block, region):
        """
        Add fold decorations (boxes arround a folded block in the editor
        widget).
        """
        _logger().debug('add fold deco %r', block)
        deco = TextDecoration(block)
        deco.signals.clicked.connect(self._on_fold_deco_clicked)
        deco.tooltip = region.text(max_lines=25)
        deco.draw_order = 1
        deco.block = block
        deco.select_line()
        deco.set_outline(drift_color(
            self._get_scope_highlight_color(), 110))
        deco.set_background(self._get_scope_highlight_color())
        deco.set_foreground(QtGui.QColor('#808080'))
        self._block_decos.append(deco)
        self.editor.decorations.append(deco)

    def toggle_fold_trigger(self, block):
        """
        Toggle a fold trigger block (expand or collapse it).

        :param block: The QTextBlock to expand/collapse
        """
        if not TextBlockHelper.is_fold_trigger(block):
            return
        region = FoldScope(block)
        if region.collapsed:
            region.unfold()
            if self._mouse_over_line is not None:
                self._add_scope_decorations(
                    region._trigger, *region.get_range())
        else:
            region.fold()
            self._clear_scope_decos()
        self._refresh_editor_and_scrollbars()
        self.trigger_state_changed.emit(region._trigger, region.collapsed)

    def mousePressEvent(self, event):
        """ Folds/unfolds the pressed indicator if any. """
        if self._mouse_over_line is not None:
            block = self.editor.document().findBlockByNumber(
                self._mouse_over_line)
            self.toggle_fold_trigger(block)

    def _on_fold_deco_clicked(self, deco):
        """
        Unfold a folded block that has just been clicked by the user
        """
        self.toggle_fold_trigger(deco.block)

    def on_state_changed(self, state):
        """
        On state changed we (dis)connect to the cursorPositionChanged signal
        """
        if state:
            self.editor.key_pressed.connect(self._on_key_pressed)
            if self._highlight_caret:
                self.editor.cursorPositionChanged.connect(
                    self._highlight_caret_scope)
                self._block_nbr = -1
            self.editor.new_text_set.connect(self._clear_block_deco)
        else:
            self.editor.key_pressed.disconnect(self._on_key_pressed)
            if self._highlight_caret:
                self.editor.cursorPositionChanged.disconnect(
                    self._highlight_caret_scope)
                self._block_nbr = -1
            self.editor.new_text_set.disconnect(self._clear_block_deco)

    def _select_scope(self, block, c):
        """
        Select the content of a scope
        """
        start_block = block
        _, end = FoldScope(block).get_range()
        end_block = self.editor.document().findBlockByNumber(end)
        c.beginEditBlock()
        c.setPosition(start_block.position())
        c.setPosition(end_block.position(), c.KeepAnchor)
        c.deleteChar()
        c.endEditBlock()

    def _on_key_pressed(self, event):
        """
        Override key press to select the current scope if the user wants
        to deleted a folded scope (without selecting it).
        """
        keys = [QtCore.Qt.Key_Delete, QtCore.Qt.Key_Backspace]
        if event.key() in keys:
            c = self.editor.textCursor()
            assert isinstance(c, QtGui.QTextCursor)
            if c.hasSelection():
                for deco in self._block_decos:
                    if c.selectedText() == deco.cursor.selectedText():
                        block = deco.block
                        self._select_scope(block, c)
                        event.accept()
                        break

    @staticmethod
    def _show_previous_blank_lines(block):
        """
        Show the block previous blank lines
        """
        # set previous blank lines visibles
        pblock = block.previous()
        while (pblock.text().strip() == '' and
               pblock.blockNumber() >= 0):
            pblock.setVisible(True)
            pblock = pblock.previous()

    def refresh_decorations(self, force=False):
        """
        Refresh decorations colors. This function is called by the syntax
        highlighter when the style changed so that we may update our
        decorations colors according to the new style.

        """
        cursor = self.editor.textCursor()
        if (self._prev_cursor is None or force or
                self._prev_cursor.blockNumber() != cursor.blockNumber()):
            for deco in self._block_decos:
                self.editor.decorations.remove(deco)
            for deco in self._block_decos:
                deco.set_outline(drift_color(
                    self._get_scope_highlight_color(), 110))
                deco.set_background(self._get_scope_highlight_color())
                self.editor.decorations.append(deco)
        self._prev_cursor = cursor

    def _refresh_editor_and_scrollbars(self):
        """
        Refrehes editor content and scollbars.

        We generate a fake resize event to refresh scroll bar.

        We have the same problem as described here:
        http://www.qtcentre.org/threads/44803 and we apply the same solution
        (don't worry, there is no visual effect, the editor does not grow up
        at all, even with a value = 500)
        """
        TextHelper(self.editor).mark_whole_doc_dirty()
        self.editor.repaint()
        s = self.editor.size()
        s.setWidth(s.width() + 1)
        self.editor.resizeEvent(QtGui.QResizeEvent(self.editor.size(), s))

    def collapse_all(self):
        """
        Collapses all triggers and makes all blocks with fold level > 0
        invisible.
        """
        self._clear_block_deco()
        block = self.editor.document().firstBlock()
        last = self.editor.document().lastBlock()
        while block.isValid():
            lvl = TextBlockHelper.get_fold_lvl(block)
            trigger = TextBlockHelper.is_fold_trigger(block)
            if trigger:
                if lvl == 0:
                    self._show_previous_blank_lines(block)
                TextBlockHelper.set_fold_trigger_state(block, True)
            block.setVisible(lvl == 0)
            if block == last and block.text().strip() == '':
                block.setVisible(True)
                self._show_previous_blank_lines(block)
            block = block.next()
        self._refresh_editor_and_scrollbars()
        tc = self.editor.textCursor()
        tc.movePosition(tc.Start)
        self.editor.setTextCursor(tc)
        self.collapse_all_triggered.emit()

    def _clear_block_deco(self):
        """
        Clear the folded block decorations.
        """
        for deco in self._block_decos:
            self.editor.decorations.remove(deco)
        self._block_decos[:] = []

    def expand_all(self):
        """
        Expands all fold triggers.
        """
        block = self.editor.document().firstBlock()
        while block.isValid():
            TextBlockHelper.set_fold_trigger_state(block, False)
            block.setVisible(True)
            block = block.next()
        self._clear_block_deco()
        self._refresh_editor_and_scrollbars()
        self.expand_all_triggered.emit()

    def _on_action_toggle(self):
        """
        Toggle the current fold trigger.
        """
        block = FoldScope.find_parent_scope(self.editor.textCursor().block())
        self.toggle_fold_trigger(block)

    def _on_action_collapse_all_triggered(self):
        """
        Closes all top levels fold triggers recursively
        """
        self.collapse_all()

    def _on_action_expand_all_triggered(self):
        """
        Expands all fold triggers
        :return:
        """
        self.expand_all()

    def _highlight_caret_scope(self):
        """
        Highlight the scope surrounding the current caret position.

        This get called only if :attr:`
        pyqode.core.panels.FoldingPanel.highlight_care_scope` is True.
        """
        cursor = self.editor.textCursor()
        block_nbr = cursor.blockNumber()
        if self._block_nbr != block_nbr:
            block = FoldScope.find_parent_scope(
                self.editor.textCursor().block())
            try:
                s = FoldScope(block)
            except ValueError:
                self._clear_scope_decos()
            else:
                self._mouse_over_line = block.blockNumber()
                if TextBlockHelper.is_fold_trigger(block):
                    self._highlight_surrounding_scopes(block)
        self._block_nbr = block_nbr

    def clone_settings(self, original):
        self.native_look = original.native_look
        self.custom_indicators_icons = original.custom_indicators_icons
        self.highlight_caret_scope = original.highlight_caret_scope
        self.custom_fold_region_background = \
            original.custom_fold_region_background
示例#43
0
class GoToDefinitionMode(Mode, QObject):
    """
    Go to the definition of the symbol under the word cursor.
    """
    #: Signal emitted when a word is clicked. The parameter is a
    #: QTextCursor with the clicked word set as the selected text.
    word_clicked = Signal(QTextCursor)

    def __init__(self):
        QObject.__init__(self)
        Mode.__init__(self)
        self._previous_cursor_start = -1
        self._previous_cursor_end = -1
        self._definition = None
        self._deco = None
        self._pending = False
        self.action_goto = QAction(_("Go to assignments"), self)
        self.action_goto.setShortcut('F7')
        self.action_goto.triggered.connect(self.request_goto)
        self.word_clicked.connect(self.request_goto)
        self._timer = DelayJobRunner(delay=200)

    def on_state_changed(self, state):
        """
        Connects/disconnects slots to/from signals when the mode state
        changed.
        """
        super(GoToDefinitionMode, self).on_state_changed(state)
        if state:
            self.editor.mouse_moved.connect(self._on_mouse_moved)
            self.editor.mouse_pressed.connect(self._on_mouse_pressed)
            self.editor.add_action(self.action_goto, sub_menu='COBOL')
            self.editor.mouse_double_clicked.connect(
                self._timer.cancel_requests)
        else:
            self.editor.mouse_moved.disconnect(self._on_mouse_moved)
            self.editor.mouse_pressed.disconnect(self._on_mouse_pressed)
            self.editor.remove_action(self.action_goto, sub_menu='Python')
            self.editor.mouse_double_clicked.disconnect(
                self._timer.cancel_requests)

    def _select_word_under_mouse_cursor(self):
        """ Selects the word under the mouse cursor. """
        cursor = TextHelper(self.editor).word_under_mouse_cursor()
        if (self._previous_cursor_start != cursor.selectionStart() and
                self._previous_cursor_end != cursor.selectionEnd()):
            self._remove_decoration()
            self._add_decoration(cursor)
        self._previous_cursor_start = cursor.selectionStart()
        self._previous_cursor_end = cursor.selectionEnd()

    def _on_mouse_moved(self, event):
        """ mouse moved callback """
        if event.modifiers() & Qt.ControlModifier:
            self._select_word_under_mouse_cursor()
        else:
            self._remove_decoration()
            self.editor.set_mouse_cursor(Qt.IBeamCursor)
            self._previous_cursor_start = -1
            self._previous_cursor_end = -1

    def _on_mouse_pressed(self, event):
        """ mouse pressed callback """
        if event.button() == 1 and self._deco:
            cursor = TextHelper(self.editor).word_under_mouse_cursor()
            if cursor and cursor.selectedText():
                self._timer.request_job(self.word_clicked.emit, cursor)

    def find_definition(self, symbol, definition):
        if symbol in TextHelper(self.editor).line_text(definition.line):
            return definition
        for ch in definition.children:
            d = self.find_definition(symbol, ch)
            if d is not None:
                return d
        return None

    def select_word(self, cursor):
        symbol = cursor.selectedText()
        analyser = self.editor.outline_mode
        for definition in analyser.definitions:
            node = self.find_definition(symbol, definition)
            if node is not None:
                break
        else:
            node = None
        self._definition = None
        if node and node.line != cursor.block().blockNumber():
            self._definition = node
            if self._deco is None:
                if cursor.selectedText():
                    self._deco = TextDecoration(cursor)
                    self._deco.set_foreground(Qt.blue)
                    self._deco.set_as_underlined()
                    self.editor.decorations.append(self._deco)
                    return True
        return False

    def _add_decoration(self, cursor):
        """
        Adds a decoration for the word under ``cursor``.
        """
        if self.select_word(cursor):
            self.editor.set_mouse_cursor(Qt.PointingHandCursor)
        else:
            self.editor.set_mouse_cursor(Qt.IBeamCursor)

    def _remove_decoration(self):
        """
        Removes the word under cursor's decoration
        """
        if self._deco is not None:
            self.editor.decorations.remove(self._deco)
            self._deco = None

    def request_goto(self, tc=None):
        """
        Request a go to assignment.

        :param tc: Text cursor which contains the text that we must look for
                   its assignment. Can be None to go to the text that is under
                   the text cursor.
        :type tc: QtGui.QTextCursor
        """
        print('request goto')
        if not tc:
            tc = TextHelper(self.editor).word_under_cursor(
                select_whole_word=True)
        if not self._definition or isinstance(self.sender(), QAction):
            self.select_word(tc)
        if self._definition is not None:
            QTimer.singleShot(100, self._goto_def)

    def _goto_def(self):
        line = self._definition.line
        col = self._definition.column
        TextHelper(self.editor).goto_line(line, move=True, column=col)
class OutlineMode(Mode, QtCore.QObject):
    """
    Generic mode that provides outline information through the
    document_changed signal and a specialised worker function.

    To use this mode, you need to write a worker function that returns a list
    of pyqode.core.share.Definition (see
    pyqode.python.backend.workers.defined_names() for an example of how to
    implement the worker function).
    """

    #: Signal emitted when the document structure changed.
    document_changed = QtCore.Signal()

    @property
    def definitions(self):
        """
        Gets the list of top level definitions.
        """
        return self._results

    def __init__(self, worker, delay=1000):
        Mode.__init__(self)
        QtCore.QObject.__init__(self)
        self._worker = worker
        self._jobRunner = DelayJobRunner(delay=delay)
        #: The list of definitions found in the file, each item is a
        #: pyqode.core.share.Definition.
        self._results = []

    def on_state_changed(self, state):
        if state:
            self.editor.new_text_set.connect(self._run_analysis)
            self.editor.textChanged.connect(self._request_analysis)
        else:
            self.editor.textChanged.disconnect(self._request_analysis)
            self.editor.new_text_set.disconnect(self._run_analysis)
            self._jobRunner.cancel_requests()

    def _request_analysis(self):
        self._jobRunner.request_job(self._run_analysis)

    def _run_analysis(self):
        try:
            self.editor.file
            self.editor.toPlainText()
        except (RuntimeError, AttributeError):
            # called by the timer after the editor got deleted
            return
        if self.enabled:
            request_data = {
                'code': self.editor.toPlainText(),
                'path': self.editor.file.path,
                'encoding': self.editor.file.encoding
            }
            try:
                self.editor.backend.send_request(
                    self._worker, request_data,
                    on_receive=self._on_results_available)
            except NotRunning:
                QtCore.QTimer.singleShot(100, self._run_analysis)
        else:
            self._results = []
            self.document_changed.emit()

    def _on_results_available(self, results):
        if results:
            results = [Definition.from_dict(ddict) for ddict in results]
        self._results = results
        if self._results is not None:
            _logger().debug("Document structure changed")
            self.document_changed.emit()
示例#45
0
class LocatorWidget(QtWidgets.QFrame):
    """
    Popup widget that let the user quickly locate a file in a project.

    The user can also locate a symbol in the current editor with the ``@``
    operator or a symbol in the whole project with the ``#`` operator.

    The ``:`` operator can be used to specify the line number to go to.

    Note that all those operators are exclusive, you cannot mix
    ``@`` with ``:`` or with ``#``.
    """
    activated = QtCore.pyqtSignal(str, int)
    cancelled = QtCore.pyqtSignal()

    GOTO_LINE_PATTERN = re.compile(r'^.*:.*')
    GOTO_SYMBOL_PATTERN = re.compile('^@.*')
    GOTO_SYMBOL_IN_PROJ_PATTERN = re.compile('^!.*')

    MODE_GOTO_FILE = 0
    MODE_GOTO_SYMBOL = 1
    MODE_GOTO_SYMBOL_IN_PROJECT = 2
    MODE_GOTO_LINE = 3

    def __init__(self, window=None):
        super().__init__()
        self._query_thread = None
        self.icon_provider = widgets.FileIconProvider()
        self._runner = DelayJobRunner(delay=300)
        self.main_window = window
        self.ui = locator_ui.Ui_Frame()
        self.ui.setupUi(self)
        self.ui.lineEdit.textChanged.connect(self.request_search)
        self.ui.lineEdit.prompt_text = _('Type to locate...')
        self.ui.lineEdit.installEventFilter(self)
        self.ui.treeWidget.installEventFilter(self)
        self.ui.treeWidget.setItemDelegate(HTMLDelegate())
        self.setWindowFlags(QtCore.Qt.Popup)
        self.ui.lineEdit.setFocus(True)
        self.ui.bt_close.clicked.connect(self.hide)
        self.ui.bt_infos.clicked.connect(self._show_help)
        self.mode = self.MODE_GOTO_FILE
        self.ui.treeWidget.currentItemChanged.connect(
            self._on_current_item_changed)
        self.ui.treeWidget.itemDoubleClicked.connect(self._activate)
        self.ui.cb_non_project_files.toggled.connect(self._search)

    def showEvent(self, ev):
        self._activated = False
        self.ui.lineEdit.clear()
        if self.mode == self.MODE_GOTO_FILE:
            self.search_files()
            self.ui.cb_non_project_files.setVisible(True)
        elif self.mode == self.MODE_GOTO_SYMBOL:
            self.ui.lineEdit.setText('@')
            self.search_symbol()
            self.ui.cb_non_project_files.setVisible(False)
        elif self.mode == self.MODE_GOTO_SYMBOL_IN_PROJECT:
            self.ui.lineEdit.setText('!')
            self.search_symbol_in_project()
            self.ui.cb_non_project_files.setVisible(True)
        elif self.mode == self.MODE_GOTO_LINE:
            self.ui.lineEdit.setText(':')
            self.ui.treeWidget.hide()
            self.adjustSize()
            self.ui.cb_non_project_files.setVisible(False)
        self.ui.lineEdit.setFocus()
        super().showEvent(ev)

    def closeEvent(self, ev):
        if not self._activated:
            self.cancelled.emit()
        if self._query_thread:
            self._query_thread.quit()
            self._on_query_finished()
        super().closeEvent(ev)

    def eventFilter(self, obj, ev):
        if obj == self.ui.lineEdit and ev.type() == QtCore.QEvent.KeyPress:
            if ev.key() == QtCore.Qt.Key_Down:
                next_item = self.ui.treeWidget.itemBelow(
                    self.ui.treeWidget.currentItem())
                if next_item is None:
                    next_item = self.ui.treeWidget.topLevelItem(0)
                self.ui.treeWidget.setCurrentItem(next_item)
                return True
            elif ev.key() == QtCore.Qt.Key_Up:
                next_item = self.ui.treeWidget.itemAbove(
                    self.ui.treeWidget.currentItem())
                if next_item is None:
                    next_item = self.ui.treeWidget.topLevelItem(
                        self.ui.treeWidget.topLevelItemCount() - 1)
                self.ui.treeWidget.setCurrentItem(next_item)
                return True
            if ev.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter] and \
                    ev.modifiers() & QtCore.Qt.ShiftModifier:
                self.ui.cb_non_project_files.setChecked(
                    not self.ui.cb_non_project_files.isChecked())
                self.ui.lineEdit.setFocus(True)
                return True
            elif ev.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter]:
                self._activate()
                return True
            if ev.key() in [QtCore.Qt.Key_Tab, QtCore.Qt.Key_Backtab]:
                # tab should not have any effect
                return True
        elif obj == self.ui.treeWidget and ev.type() == QtCore.QEvent.KeyPress\
                and ev.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter]:
            self._activate()
            return True
        return super().eventFilter(obj, ev)

    def _activate(self, *_):
        current_item = self.ui.treeWidget.currentItem()
        if self.ui.treeWidget.isVisible() and current_item:
            data = current_item.data(0, QtCore.Qt.UserRole)
        else:
            data = self.main_window.current_tab.file.path
        if isinstance(data, str):
            text = self.ui.lineEdit.text()
            if self.GOTO_LINE_PATTERN.match(text):
                line = self._get_requested_line_nbr()
            else:
                line = -1
            self.activated.emit(data, line)
        elif isinstance(data, tuple):
            line, path = data
            self.activated.emit(path, line)
        self._activated = True
        self.close()

    def request_search(self):
        self._runner.request_job(self._search)

    def _search(self):
        text = self.ui.lineEdit.text()
        # check the source to use for search (symbols or files).
        if self.GOTO_SYMBOL_PATTERN.match(text):
            self.ui.cb_non_project_files.setVisible(False)
            if self.GOTO_LINE_PATTERN.match(text):
                self.ui.treeWidget.hide()
                self.adjustSize()
            elif editor.get_current_editor():
                self.search_symbol()
        elif self.GOTO_SYMBOL_IN_PROJ_PATTERN.match(text):
            if self.GOTO_LINE_PATTERN.match(text):
                self.ui.cb_non_project_files.setVisible(False)
                self.ui.treeWidget.hide()
                self.adjustSize()
            else:
                self.ui.cb_non_project_files.setVisible(True)
                self.search_symbol_in_project()
        else:
            if not text.startswith(':'):
                self.ui.cb_non_project_files.setVisible(True)
                self.search_files()
            else:
                self.ui.cb_non_project_files.setVisible(False)
                # will be used to goto line in the current editor.
                self.ui.treeWidget.hide()
                self.adjustSize()
                e = editor.get_current_editor()
                try:
                    TextHelper(e).goto_line(self._get_requested_line_nbr() - 1)
                except (ValueError, AttributeError):
                    _logger().debug('failed to go to line on editor %r', e)

    def _get_projects(self):
        if self.ui.cb_non_project_files.isChecked():
            return None
        # all active project
        return project.get_projects()

    def search_symbol(self):
        search_term = self._get_search_term()
        fpath = editor.get_current_editor().file.path
        self._request_exec_query(index.get_symbols,
                                 self._display_search_symbol_results,
                                 name_filter=search_term,
                                 file=fpath)

    def _display_search_symbol_results(self, symbols):
        self.ui.treeWidget.clear()
        search_term = self._get_search_term()
        first_item = None
        for symbol_item, file_item in symbols:
            name = symbol_item.name
            line = symbol_item.line + 1
            path = file_item.path
            enriched = self.get_enriched_text(name, search_term)
            if enriched == name and search_term:
                continue
            text = '%s<br><i>%s:%d</i>' % (enriched, path, line)
            item = QtWidgets.QTreeWidgetItem()
            item.setText(0, text)
            item.setIcon(0, self.icon_provider.icon(path))
            item.setData(0, QtCore.Qt.UserRole, (line, path))
            if first_item is None:
                first_item = item
            self.ui.treeWidget.addTopLevelItem(item)
        if self.ui.treeWidget.topLevelItemCount():
            self.ui.treeWidget.show()
            self.ui.treeWidget.setCurrentItem(first_item)
        else:
            item = QtWidgets.QTreeWidgetItem()
            item.setText(0, _('No match found'))
            item.setIcon(0, QtGui.QIcon.fromTheme('dialog-warning'))
            self.ui.treeWidget.addTopLevelItem(item)
        self.adjustSize()

    def search_symbol_in_project(self):
        search_term = self._get_search_term()
        self._request_exec_query(
            index.get_symbols,
            self._display_search_symbol_in_project_results,
            name_filter=search_term,
            projects=self._get_projects())

    def _display_search_symbol_in_project_results(self, symbols):
        search_term = self._get_search_term()
        # display
        self.ui.treeWidget.clear()
        first_item = None
        for symbol_item, file_item in symbols:
            name = symbol_item.name
            line = symbol_item.line + 1
            path = file_item.path
            enriched = self.get_enriched_text(name, search_term)
            if enriched == name and search_term:
                continue
            text = '%s<br><i>%s:%d</i>' % (enriched, path, line)
            item = QtWidgets.QTreeWidgetItem()
            item.setText(0, text)
            item.setIcon(0, self.icon_provider.icon(path))
            item.setData(0, QtCore.Qt.UserRole, (line, path))
            if first_item is None:
                first_item = item
            self.ui.treeWidget.addTopLevelItem(item)
        if self.ui.treeWidget.topLevelItemCount():
            self.ui.treeWidget.show()
            self.ui.treeWidget.setCurrentItem(first_item)
        else:
            item = QtWidgets.QTreeWidgetItem()
            item.setText(0, _('No match found'))
            item.setIcon(0, QtGui.QIcon.fromTheme('dialog-warning'))
            self.ui.treeWidget.addTopLevelItem(item)
        self.adjustSize()

    def get_definition_icon(self, icon):
        if isinstance(icon, list):
            icon = tuple(icon)
        return self._get_definition_icon(icon)

    @utils.memoized
    @staticmethod
    def _get_definition_icon(icon):
        if isinstance(icon, tuple):
            icon = QtGui.QIcon.fromTheme(icon[0], QtGui.QIcon(icon[1]))
        elif isinstance(icon, str):
            if QtGui.QIcon.hasThemeIcon(icon):
                icon = QtGui.QIcon.fromTheme(icon)
            else:
                icon = QtGui.QIcon(icon)
        return icon

    def _request_exec_query(self, query_fct, callback, **kwargs):
        class QueryThread(QtCore.QThread):
            results_available = QtCore.pyqtSignal(object)

            def __init__(self, query_fct, **kwargs):
                super().__init__()
                self.query_fct = query_fct
                self.kwargs = kwargs

            def run(self):
                generator = query_fct(**kwargs)
                ret = []
                try:
                    for i, value in enumerate(generator):
                        if query_fct == index.get_files:
                            if not os.path.exists(value.path):
                                continue
                        elif query_fct == index.get_symbols:
                            s, f = value
                            if not os.path.exists(f.path):
                                continue
                        ret.append(value)
                        if i > LIMIT:
                            break
                except sqlite3.OperationalError:
                    _logger().exception('failed to execute sql query')
                    ret = []
                self.results_available.emit(ret)

        if self._query_thread is None:
            self._query_thread = QueryThread(query_fct, **kwargs)
            self._query_thread.setParent(self)
            self._query_thread.results_available.connect(callback)
            self._query_thread.finished.connect(self._on_query_finished)
            self._query_thread.start()

    def _on_query_finished(self):
        self._query_thread = None

    def search_files(self):
        name_filter = self._get_search_term()
        self._request_exec_query(index.get_files,
                                 self._display_search_results,
                                 name_filter=name_filter,
                                 projects=self._get_projects())

    def _display_search_results(self, project_files):
        name_filter = self._get_search_term()
        self.ui.treeWidget.clear()
        first_item = None
        for file_item in project_files:
            path = file_item.path
            name = file_item.name
            enriched = self.get_enriched_text(name, name_filter)
            if enriched == name and name_filter:
                continue
            text = '%s<br><i>%s</i>' % (enriched, os.path.dirname(path))
            item = QtWidgets.QTreeWidgetItem()
            item.setText(0, text)
            item.setIcon(0, self.icon_provider.icon(path))
            item.setData(0, QtCore.Qt.UserRole, path)
            if first_item is None:
                first_item = item
            self.ui.treeWidget.addTopLevelItem(item)
        if self.ui.treeWidget.topLevelItemCount():
            self.ui.treeWidget.show()
            self.ui.treeWidget.setCurrentItem(first_item)
        else:
            item = QtWidgets.QTreeWidgetItem()
            item.setText(0, _('No match found'))
            item.setIcon(0, QtGui.QIcon.fromTheme('dialog-information'))
            self.ui.treeWidget.addTopLevelItem(item)
        self.adjustSize()

    @staticmethod
    def get_match_spans(expr, item):
        spans = []
        item = item.lower()
        search_expr = DbHelper._get_searchable_name(expr)
        for token in get_search_tokens(search_expr):
            if not token:
                continue
            try:
                start = item.index(token)
                length = len(token)
                spans.append((start, length))
                to_replace = item[start:length]
                item = item.replace(to_replace, len(to_replace) * '*')
            except ValueError:
                pass
        return spans

    @staticmethod
    def get_enriched_text(item, expr):
        spans = LocatorWidget.get_match_spans(expr, item)
        offset = 0
        enriched = ''
        for start, end in sorted(spans, key=lambda x: x[0]):
            enriched += item[offset:start] + '<b>' + \
                item[start:start + end] + '</b>'
            offset = start + end
        enriched += item[offset:]
        return enriched

    def _get_search_term(self):
        text = self.ui.lineEdit.text()
        if text.startswith('@'):
            text = text[1:]
        elif text.startswith('!'):
            text = text[1:]
        if self.GOTO_LINE_PATTERN.match(text):
            text = text.split(':')[0]
        return text.strip().replace("'", '').replace('"', '').replace('*', '')

    def _get_requested_line_nbr(self):
        text = self.ui.lineEdit.text()
        try:
            return int(text.split(':')[1])
        except (IndexError, ValueError):
            return -1

    def _show_help(self):
        help_text = _('''<p>Use <i>Goto</i> to navigate your project’s files
swiftly.</p>

<p>Use the <i>arrow keys</i> to navigate into the list and press <i>ENTER</i>
to open the selected entry. Press <i>ESCAPE</i> to close the popup window.<p>

<p><i>Goto</i> accepts several operators:

<ul>
    <li> <b>@</b> to locate a symbol in the current editor.</li>
    <li> <b>!</b> to locate a symbol in the opened project(s).</li>
    <li> <b>:</b> to specify the line number to go to.</li>
</ul>

</p>
''')
        QtWidgets.QMessageBox.information(self, _('Goto: help'), help_text)
        self.show()

    def _on_current_item_changed(self, item):
        text = self.ui.lineEdit.text()
        if item and self.GOTO_SYMBOL_PATTERN.match(text):
            line, path = item.data(0, QtCore.Qt.UserRole)
            TextHelper(editor.get_current_editor()).goto_line(line - 1)
示例#46
0
 def __init__(self, parent):
     QtWidgets.QTabBar.__init__(self, parent)
     self.setTabsClosable(True)
     self._timer = DelayJobRunner(delay=1)
示例#47
0
 def __init__(self, parent=None):
     super(HtmlPreviewWidget, self).__init__(parent)
     self._editor = None
     self._timer = DelayJobRunner(delay=1000)
class DocumentAnalyserMode(Mode, QtCore.QObject):
    """
    This mode analyses the structure of a document (a tree of
    :class:`pyqode.python.backend.workers.Definition`.

    :attr:`pyqode.python.modes.DocumentAnalyserMode.document_changed`
    is emitted whenever the document structure changed.

    To keep good performances, the analysis task is run when the application is
    idle for more than 1 second (by default).
    """
    #: Signal emitted when the document structure changed.
    document_changed = QtCore.Signal()

    def __init__(self, delay=1000):
        Mode.__init__(self)
        QtCore.QObject.__init__(self)
        self._jobRunner = DelayJobRunner(delay=delay)
        #: The list of results (elements might have children; this is actually
        #: a tree).
        self.results = []

    def on_state_changed(self, state):
        if state:
            self.editor.blockCountChanged.connect(self._on_line_count_changed)
            self.editor.new_text_set.connect(self._run_analysis)
        else:
            self.editor.blockCountChanged.disconnect(
                self._on_line_count_changed)
            self.editor.new_text_set.disconnect(self._run_analysis)

    def _on_line_count_changed(self, e):
        self._jobRunner.request_job(self._run_analysis)

    def _run_analysis(self):
        if self.editor and self.editor.toPlainText() and self.editor.file:
            request_data = {
                'code': self.editor.toPlainText(),
                'path': self.editor.file.path,
                'encoding': self.editor.file.encoding
            }
            try:
                self.editor.backend.send_request(
                    defined_names, request_data,
                    on_receive=self._on_results_available)
            except NotConnected:
                QtCore.QTimer.singleShot(100, self._run_analysis)
        else:
            self.results = []
            self.document_changed.emit()

    def _on_results_available(self, status, results):
        if results:
            results = [Definition().from_dict(ddict) for ddict in results]
        self.results = results
        if self.results is not None:
            _logger().debug("Document structure changed")
            self.document_changed.emit()

    @property
    def flattened_results(self):
        """
        Flattens the document structure tree as a simple sequential list.
        """
        ret_val = []
        for d in self.results:
            ret_val.append(d)
            for sub_d in d.children:
                nd = Definition(sub_d.name, sub_d.icon, sub_d.line,
                                sub_d.column, sub_d.full_name)
                nd.name = "  " + nd.name
                nd.full_name = "  " + nd.full_name
                ret_val.append(nd)
        return ret_val