Skip to content

Commit

Permalink
Merge pull request #22996 from athompson673/master
Browse files Browse the repository at this point in the history
PR: Add multi-cursor support to the Editor
  • Loading branch information
ccordoba12 authored Jan 10, 2025
2 parents 0dad5d1 + 55c29d6 commit 83ffd20
Show file tree
Hide file tree
Showing 11 changed files with 1,493 additions and 228 deletions.
12 changes: 7 additions & 5 deletions spyder/config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@
'check_eol_chars': True,
'convert_eol_on_save': False,
'convert_eol_on_save_to': 'LF',
'multicursor_support': True,
'tab_always_indent': False,
'intelligent_backspace': True,
'automatic_completions': True,
Expand Down Expand Up @@ -429,10 +430,8 @@
'find_replace/hide find and replace': "Escape",
# -- Editor --
'editor/code completion': CTRL+'+Space',
'editor/duplicate line up': (
"Ctrl+Alt+Up" if WIN else "Shift+Alt+Up"),
'editor/duplicate line down': (
"Ctrl+Alt+Down" if WIN else "Shift+Alt+Down"),
'editor/duplicate line up': CTRL + "+Alt+PgUp",
'editor/duplicate line down': CTRL + "+Alt+PgDown",
'editor/delete line': 'Ctrl+D',
'editor/transform to uppercase': 'Ctrl+Shift+U',
'editor/transform to lowercase': 'Ctrl+U',
Expand Down Expand Up @@ -513,6 +512,9 @@
'editor/enter array table': "Ctrl+M",
'editor/run cell in debugger': 'Alt+Shift+Return',
'editor/run selection in debugger': CTRL + '+F9',
'editor/add cursor up': 'Alt+Shift+Up',
'editor/add cursor down': 'Alt+Shift+Down',
'editor/clear extra cursors': 'Esc',
# -- Internal console --
'internal_console/inspect current object': "Ctrl+I",
'internal_console/clear shell': "Ctrl+L",
Expand Down Expand Up @@ -676,4 +678,4 @@
# or if you want to *rename* options, then you need to do a MAJOR update in
# version, e.g. from 3.0.0 to 4.0.0
# 3. You don't need to touch this value if you're just adding a new option
CONF_VERSION = '85.0.0'
CONF_VERSION = '85.1.0'
25 changes: 21 additions & 4 deletions spyder/plugins/editor/confpage.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def get_icon(self):
def setup_page(self):
newcb = self.create_checkbox

# --- Display tab ---
# ---- Display tab
showtabbar_box = newcb(_("Show tab bar"), 'show_tab_bar')
showclassfuncdropdown_box = newcb(
_("Show selector for classes and functions"),
Expand Down Expand Up @@ -130,7 +130,7 @@ def setup_page(self):
other_layout.addWidget(scroll_past_end_box)
other_group.setLayout(other_layout)

# --- Source code tab ---
# ---- Source code tab
closepar_box = newcb(
_("Automatic insertion of parentheses, braces and brackets"),
'close_parentheses')
Expand Down Expand Up @@ -242,7 +242,7 @@ def enable_tabwidth_spin(index):
indentation_layout.addWidget(tab_mode_box)
indentation_group.setLayout(indentation_layout)

# --- Advanced tab ---
# ---- Advanced tab
# -- Templates
templates_group = QGroupBox(_('Templates'))
template_btn = self.create_button(
Expand Down Expand Up @@ -361,6 +361,23 @@ def enable_tabwidth_spin(index):
eol_layout.addLayout(eol_on_save_layout)
eol_group.setLayout(eol_layout)

# -- Multi-cursor
multicursor_group = QGroupBox(_("Multi-Cursor"))
multicursor_label = QLabel(
_("Enable adding multiple cursors for simultaneous editing. "
"Additional cursors are added and removed using the Ctrl-Alt "
"click shortcut. A column of cursors can be added using the "
"Ctrl-Alt-Shift click shortcut."))
multicursor_label.setWordWrap(True)
multicursor_box = newcb(
_("Enable Multi-Cursor "),
'multicursor_support')

multicursor_layout = QVBoxLayout()
multicursor_layout.addWidget(multicursor_label)
multicursor_layout.addWidget(multicursor_box)
multicursor_group.setLayout(multicursor_layout)

# --- Tabs ---
self.create_tab(
_("Interface"),
Expand All @@ -372,7 +389,7 @@ def enable_tabwidth_spin(index):
self.create_tab(
_("Advanced settings"),
[templates_group, autosave_group, docstring_group,
annotations_group, eol_group]
annotations_group, eol_group, multicursor_group]
)

@on_conf_change(
Expand Down
2 changes: 1 addition & 1 deletion spyder/plugins/editor/panels/codefolding.py
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ def expand_all(self):
"""Expands all fold triggers."""
block = self.editor.document().firstBlock()
while block.isValid():
line_number = block.BlockNumber()
line_number = block.blockNumber()
if line_number in self.folding_regions:
end_line = self.folding_regions[line_number]
self.unfold_region(block, line_number, end_line)
Expand Down
18 changes: 14 additions & 4 deletions spyder/plugins/editor/panels/scrollflag.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def __init__(self):
self._unit_testing = False
self._range_indicator_is_visible = False
self._alt_key_is_down = False
self._ctrl_key_is_down = False

self._slider_range_color = QColor(Qt.gray)
self._slider_range_color.setAlphaF(.85)
Expand Down Expand Up @@ -130,7 +131,7 @@ def update_flags(self):
'breakpoint': [],
}

# Run this computation in a different thread to prevent freezing
# Run this computation in a different thread to prevent freezing
# the interface
if not self._update_flags_thread.isRunning():
self._update_flags_thread.start()
Expand Down Expand Up @@ -280,9 +281,12 @@ def paintEvent(self, event):

# Paint the slider range
if not self._unit_testing:
alt = QApplication.queryKeyboardModifiers() & Qt.AltModifier
modifiers = QApplication.queryKeyboardModifiers()
alt = modifiers & Qt.KeyboardModifier.AltModifier
ctrl = modifiers & Qt.KeyboardModifier.ControlModifier
else:
alt = self._alt_key_is_down
ctrl = self._ctrl_key_is_down

if self.slider:
cursor_pos = self.mapFromGlobal(QCursor().pos())
Expand All @@ -293,7 +297,7 @@ def paintEvent(self, event):
# determined if the cursor is over the editor or the flag scrollbar
# because the later gives a wrong result when a mouse button
# is pressed.
if is_over_self or (alt and is_over_editor):
if is_over_self or (alt and not ctrl and is_over_editor):
painter.setPen(self._slider_range_color)
painter.setBrush(self._slider_range_brush)
x, y, width, height = self.make_slider_range(
Expand Down Expand Up @@ -324,15 +328,21 @@ def mousePressEvent(self, event):

def keyReleaseEvent(self, event):
"""Override Qt method."""
if event.key() == Qt.Key_Alt:
if event.key() == Qt.Key.Key_Alt:
self._alt_key_is_down = False
self.update()
elif event.key() == Qt.Key.Key_Control:
self._ctrl_key_is_down = False
self.update()

def keyPressEvent(self, event):
"""Override Qt method"""
if event.key() == Qt.Key_Alt:
self._alt_key_is_down = True
self.update()
elif event.key() == Qt.Key.Key_Control:
self._ctrl_key_is_down = True
self.update()

def get_vertical_offset(self):
"""
Expand Down
10 changes: 6 additions & 4 deletions spyder/plugins/editor/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,9 @@ def test_go_to_prev_next_cursor_position(editor_plugin, python_files):
for history, expected_history in zip(main_widget.cursor_undo_history,
expected_cursor_undo_history):
assert history[0] == expected_history[0]
assert history[1].position() == expected_history[1]
# history[1] is a tuple of editor.all_cursor(s)
# only a single cursor is expected for this test
assert history[1][0].position() == expected_history[1]

# Navigate to previous and next cursor positions.

Expand All @@ -318,11 +320,11 @@ def test_go_to_prev_next_cursor_position(editor_plugin, python_files):
for history, expected_history in zip(main_widget.cursor_undo_history,
expected_cursor_undo_history[:1]):
assert history[0] == expected_history[0]
assert history[1].position() == expected_history[1]
assert history[1][0].position() == expected_history[1]
for history, expected_history in zip(main_widget.cursor_redo_history,
expected_cursor_undo_history[:0:-1]):
assert history[0] == expected_history[0]
assert history[1].position() == expected_history[1]
assert history[1][0].position() == expected_history[1]

# So we are now expected to be at index 0 in the cursor position history.
# From there, we go to the fourth file.
Expand All @@ -337,7 +339,7 @@ def test_go_to_prev_next_cursor_position(editor_plugin, python_files):
for history, expected_history in zip(main_widget.cursor_undo_history,
expected_cursor_undo_history):
assert history[0] == expected_history[0]
assert history[1].position() == expected_history[1]
assert history[1][0].position() == expected_history[1]
assert main_widget.cursor_redo_history == []


Expand Down
Loading

0 comments on commit 83ffd20

Please sign in to comment.