diff --git a/spyder/api/panel.py b/spyder/api/panel.py index 04aa5a0830f..c1644cfd806 100644 --- a/spyder/api/panel.py +++ b/spyder/api/panel.py @@ -27,6 +27,7 @@ # Local imports from spyder.api.editorextension import EditorExtension +from spyder.plugins.outlineexplorer.api import is_cell_header logger = logging.getLogger(__name__) @@ -87,6 +88,7 @@ def __init__(self, dynamic=False): self.order_in_zone = -1 self._scrollable = False self._background_brush = None + self.linecell_color = QColor(Qt.darkGray) self._foreground_pen = None # Position in the editor (top, left, right, bottom) self.position = -1 @@ -135,6 +137,21 @@ def paintEvent(self, event): else: logger.debug(f'paintEvent method must be defined in {self}') + def paint_cell(self, painter): + """Paint cell dividers in the visible region if needed.""" + for top_position, line_number, block in self.editor.visible_blocks: + if ( + is_cell_header(block) + and (self.position in [self.Position.LEFT, self.Position.RIGHT]) + ): + pen = painter.pen() + pen.setStyle(Qt.SolidLine) + pen.setBrush(self.linecell_color) + painter.setPen(pen) + painter.drawLine(0, top_position, self.width(), + top_position) + + def sizeHint(self): """ Return the widget size hint, overriding the Qt method. diff --git a/spyder/config/main.py b/spyder/config/main.py index 8afe16c8cb9..8e771e70f2f 100644 --- a/spyder/config/main.py +++ b/spyder/config/main.py @@ -428,6 +428,7 @@ 'editor/go to new line': "Ctrl+Shift+Return", 'editor/go to definition': "F3" if MAC else "Ctrl+G", 'editor/toggle comment': "Ctrl+1", + 'editor/create_new_cell': "Ctrl+2", 'editor/blockcomment': "Ctrl+4", 'editor/unblockcomment': "Ctrl+5", 'editor/start of line': "Meta+A", diff --git a/spyder/plugins/debugger/panels/debuggerpanel.py b/spyder/plugins/debugger/panels/debuggerpanel.py index d5c874053e2..d86538d8bbf 100644 --- a/spyder/plugins/debugger/panels/debuggerpanel.py +++ b/spyder/plugins/debugger/panels/debuggerpanel.py @@ -90,6 +90,7 @@ def paintEvent(self, event): super(DebuggerPanel, self).paintEvent(event) painter = QPainter(self) painter.fillRect(event.rect(), self.editor.sideareas_color) + self.paint_cell(painter) for top, line_number, block in self.editor.visible_blocks: if self.line_number_hint == line_number: diff --git a/spyder/plugins/editor/panels/codefolding.py b/spyder/plugins/editor/panels/codefolding.py index 4baaca22246..d8ed5d19ca7 100644 --- a/spyder/plugins/editor/panels/codefolding.py +++ b/spyder/plugins/editor/panels/codefolding.py @@ -239,7 +239,7 @@ def paintEvent(self, event): # on the folding panel. super(FoldingPanel, self).paintEvent(event) painter = QPainter(self) - + self.paint_cell(painter) if not self._display_folding and not self._key_pressed: if any(self.folding_status.values()): for info in self.editor.visible_blocks: @@ -248,6 +248,7 @@ def paintEvent(self, event): line_number, top_position, block, painter, mouse_hover=True) return + # Draw background over the selected non collapsed fold region if self._mouse_over_line is not None: block = self.editor.document().findBlockByNumber( diff --git a/spyder/plugins/editor/panels/linenumber.py b/spyder/plugins/editor/panels/linenumber.py index a2f11e931c9..98dc4042656 100644 --- a/spyder/plugins/editor/panels/linenumber.py +++ b/spyder/plugins/editor/panels/linenumber.py @@ -69,6 +69,7 @@ def paintEvent(self, event): Painting line number area """ + painter = QPainter(self) painter.fillRect(event.rect(), self.editor.sideareas_color) font_height = self.editor.fontMetrics().height() @@ -94,6 +95,7 @@ def draw_pixmap(xleft, ytop, pixmap): # The editor doesn't care about leading, so each line # must be drawn independently. self.draw_linenumbers_slow(painter) + self.paint_cell(painter) for top, line_number, block in self.editor.visible_blocks: data = block.userData() diff --git a/spyder/plugins/editor/plugin.py b/spyder/plugins/editor/plugin.py index fa651d98ff4..4c72c208c88 100644 --- a/spyder/plugins/editor/plugin.py +++ b/spyder/plugins/editor/plugin.py @@ -796,6 +796,7 @@ def get_plugin_actions(self): name="Replace text") # --- Run toolbar --- + # --- Source code Toolbar --- self.todo_list_action = create_action(self, _("Show todo list"), icon=ima.icon('todo_list'), @@ -863,6 +864,14 @@ def get_plugin_actions(self): add_shortcut_to_tip=True) # --- Edit Toolbar --- + create_new_cell = create_action(self, _("Create new cell at the " + "current line"), + icon=ima.icon('cell'), + tip=_("Create new cell"), + triggered=self.create_cell, + context=Qt.WidgetShortcut) + self.register_shortcut(create_new_cell, context="Editor", + name="create_new_cell") self.toggle_comment_action = create_action(self, _("Comment")+"/"+_("Uncomment"), icon=ima.icon('comment'), tip=_("Comment current line or selection"), @@ -1125,6 +1134,7 @@ def get_plugin_actions(self): file_toolbar_actions = ([self.new_action, self.open_action, self.save_action, self.save_all_action] + + [create_new_cell] + self.main.file_toolbar_actions) self.main.file_toolbar_actions += file_toolbar_actions @@ -2956,6 +2966,12 @@ def __move_cursor_position(self, index_move): self.__ignore_cursor_history = False self.update_cursorpos_actions() + @Slot() + def create_cell(self): + editor = self.get_current_editor() + if editor is not None: + editor.create_new_cell() + @Slot() def go_to_previous_cursor_position(self): self.__ignore_cursor_history = True diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py index 46b4a228f53..ba8b9af2e6c 100644 --- a/spyder/plugins/editor/widgets/codeeditor.py +++ b/spyder/plugins/editor/widgets/codeeditor.py @@ -699,6 +699,7 @@ def create_shortcuts(self): ('editor', 'go to definition', self.go_to_definition_from_cursor), ('editor', 'toggle comment', self.toggle_comment), ('editor', 'blockcomment', self.blockcomment), + ('editor', 'create_new_cell', self.create_new_cell), ('editor', 'unblockcomment', self.unblockcomment), ('editor', 'transform to uppercase', self.transform_to_uppercase), ('editor', 'transform to lowercase', self.transform_to_lowercase), @@ -4233,6 +4234,27 @@ def __in_block_comment(cursor): cursor3.endEditBlock() return True + def create_new_cell(self): + firstline = '# %%' + self.get_line_separator() + endline = self.get_line_separator() + cursor = self.textCursor() + if self.has_selected_text(): + self.extend_selection_to_complete_lines() + start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd() + endline = self.get_line_separator() + '# %%' + else: + start_pos = end_pos = cursor.position() + + # Add cell comment or enclose current selection in cells + cursor.beginEditBlock() + cursor.setPosition(end_pos) + cursor.movePosition(QTextCursor.EndOfBlock) + cursor.insertText(endline) + cursor.setPosition(start_pos) + cursor.movePosition(QTextCursor.StartOfBlock) + cursor.insertText(firstline) + cursor.endEditBlock() + # ---- Kill ring handlers # Taken from Jupyter's QtConsole # Copyright (c) 2001-2015, IPython Development Team @@ -5529,7 +5551,7 @@ def _draw_editor_cell_divider(self): for top, line_number, block in self.visible_blocks: if is_cell_header(block): - painter.drawLine(4, top, self.width(), top) + painter.drawLine(0, top, self.width(), top) @property def visible_blocks(self):