Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Add multi-cursor support to the Editor #22996

Merged
merged 113 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
8a24981
manually draw all text cursors and mouse shortcut to add cursors
athompson673 Nov 3, 2024
d64e95e
Merge remote-tracking branch 'upstream/master'
athompson673 Nov 3, 2024
d03be3e
non-shortcut mouse press clears extra cursors
athompson673 Nov 3, 2024
d3e3e65
draw extra cursor selections using text decorations
athompson673 Nov 3, 2024
13414fd
refactoring
athompson673 Nov 4, 2024
c457bb0
formatting and comments
athompson673 Nov 4, 2024
2c8d669
first attempt at handling keyEvent for all cursors
athompson673 Nov 4, 2024
df16552
implement various cursor movement
athompson673 Nov 4, 2024
244a4ba
Merge branch 'spyder-ide:master' into master
athompson673 Nov 5, 2024
a9ec7b8
Merge branch 'spyder-ide:master' into master
athompson673 Nov 5, 2024
7895b99
Fix missing line for delete key handling
athompson673 Nov 5, 2024
c121ec3
Add cursor EditBlock to key event for undo history and other changes
athompson673 Nov 6, 2024
daf592f
write multi-cursor cut, copy, paste
athompson673 Nov 6, 2024
7284757
Merge branch 'spyder-ide:master' into master
athompson673 Nov 6, 2024
1a955e9
rework delete handling for better undo/redo cursor positioning
athompson673 Nov 7, 2024
63fe5ed
rework cursor painting
athompson673 Nov 7, 2024
af348c5
overtype cursor rendering
athompson673 Nov 7, 2024
8b2c27c
implement overlapping cursor merge
athompson673 Nov 8, 2024
4c803fa
impl select-all, and template methods for wrapping other shortcuts
athompson673 Nov 8, 2024
a612b78
refactor prev/next word, and prevent auto completions when multi-cursor
athompson673 Nov 9, 2024
88d6fd2
impl wrappers for shortcut functions and delete_line function for mul…
athompson673 Nov 9, 2024
1c5685d
better cursor edit handling in for_each_cursor wrapper. Implements go…
athompson673 Nov 10, 2024
df71b7b
revert cursor_move_event in favor of wrapper function: for_each_curso…
athompson673 Nov 10, 2024
bee76c3
re-work callback wrappers to prevent QAction from sending extraneous …
athompson673 Nov 10, 2024
6b34797
style changes and #TODO comments
athompson673 Nov 10, 2024
15a20d9
Merge branch 'spyder-ide:master' into master
athompson673 Nov 10, 2024
6bb721f
docstrings, and unneeded func call cleanup
athompson673 Nov 10, 2024
cef9337
small refactoring
athompson673 Nov 10, 2024
3f35f0a
fix deletion of folded lines (delete and delete_line)
athompson673 Nov 11, 2024
528672b
qt6 enum naming, and attempt to get cursor to not enter hidden blocks
athompson673 Nov 11, 2024
82f5597
Merge branch 'spyder-ide:master' into master
athompson673 Nov 13, 2024
d76d32a
edit docstrings and todo notes
athompson673 Nov 13, 2024
6e94976
re-implement methods for next/prev cell
athompson673 Nov 13, 2024
128f306
move cursor clearing for several functions to go_to_line
athompson673 Nov 13, 2024
7b7c1a4
simplify multi-cursor keypress handling and add merge direction to fo…
athompson673 Nov 14, 2024
4bb130f
multi-cursor docstring shortcuts
athompson673 Nov 14, 2024
a0979d0
fix cursor blink typo so cursor is not hidden during rapid keypress e…
athompson673 Nov 14, 2024
525f8fd
Colum cursor creation and multi-cursor remove cursor click interactions
athompson673 Nov 14, 2024
64b8523
Fix column cursor shortcut: clamp cursor position to text length not …
athompson673 Nov 16, 2024
0596845
edit multi cursor paste: if single line on clipboard; repeat for all …
athompson673 Nov 16, 2024
a66d4e8
rewrite cursor rendering again for overwrite mode
athompson673 Nov 16, 2024
2c1c9bf
pep 8 changes (some, not all)
athompson673 Nov 16, 2024
7d35e3f
revert badly merged line from cut()
athompson673 Nov 16, 2024
f65df51
Merge branch 'spyder-ide:master' into master
athompson673 Nov 18, 2024
2aeb191
pep8 line length edits and attempt to fix delete behavior regression
athompson673 Nov 18, 2024
de42e08
reorganize multi-cursor keyPressEvent, and bugfix merge_cursors
athompson673 Nov 18, 2024
5ffd2df
rewrite delete and delete_line to fix behavior with folded code
athompson673 Nov 19, 2024
2f300d2
update multi_cursor_keypress handling to emit signals before changing…
athompson673 Nov 19, 2024
8fb4df7
make autoformatting clear extra cursors
athompson673 Nov 19, 2024
8b19db9
begin implementing multi-cursor toggle for settings option, and add d…
athompson673 Nov 20, 2024
0dd876e
Merge branch 'spyder-ide:master' into master
athompson673 Nov 21, 2024
c0f58b1
Merge branch 'spyder-ide:master' into master
athompson673 Nov 22, 2024
f6cbb27
multi-cursor enter key handling
athompson673 Nov 27, 2024
8c775b2
Merge branch 'spyder-ide:master' into master
athompson673 Nov 27, 2024
9c77b5c
Merge branch 'spyder-ide:master' into master
athompson673 Dec 2, 2024
d57f2f3
implement multi-cursor move line up/down
athompson673 Dec 2, 2024
3a80f3b
implement smart backspace
athompson673 Dec 2, 2024
4422efc
Create test_multicursor.py
athompson673 Dec 2, 2024
0e61dfe
small tweaks
athompson673 Dec 2, 2024
2068914
fix mouse click and add column cursor test
athompson673 Dec 2, 2024
91ca369
fix typo in function used for debugging
athompson673 Dec 3, 2024
7370430
re-implement duplicate line to handle folded code.
athompson673 Dec 3, 2024
5524076
Merge branch 'master' into master
athompson673 Dec 3, 2024
461380e
merge extra cursors on editorstack.advance line
athompson673 Dec 4, 2024
b2c57a0
Merge branch 'spyder-ide:master' into master
athompson673 Dec 4, 2024
280e903
restrict killring commands to single cursor for now
athompson673 Dec 4, 2024
c30a6e0
last_edit_position multicursor main_widget.py
athompson673 Dec 4, 2024
31c318d
last_edit_position multicursor editorstack.py
athompson673 Dec 4, 2024
03b8f66
last_edit_position multicursor helpers.py
athompson673 Dec 4, 2024
028fbf7
ensure extra cursors aren't added if multicursor is disabled
athompson673 Dec 5, 2024
38c5a1f
multicursor cursor position history codeeditor.py
athompson673 Dec 6, 2024
cd9b253
multi-cursor cursor position history main_widget.py
athompson673 Dec 6, 2024
7439758
remove print debug
athompson673 Dec 6, 2024
9b2ff7c
Update test_plugin.py for multi-cursor position history
athompson673 Dec 6, 2024
e8393e5
added setting toggle for multicursor support
athompson673 Dec 6, 2024
0bce26a
Merge branch 'master' of github.com:athompson673/spyder
athompson673 Dec 6, 2024
3432502
init editorstack with multicursor_support attr
athompson673 Dec 6, 2024
13e0085
fix cursor rendering/sync with multiple instances of same document vi…
athompson673 Dec 6, 2024
4185ebb
Merge branch 'spyder-ide:master' into master
athompson673 Dec 6, 2024
ca1a979
update todo comments
athompson673 Dec 6, 2024
e3d6c91
Fix exceptions in test_flag_painting
athompson673 Dec 6, 2024
ac71671
Merge branch 'spyder-ide:master' into master
athompson673 Dec 8, 2024
7f19855
fix bug: column cursor click on same line places cursor on every line…
athompson673 Dec 12, 2024
5ca91c4
multi-cursor test cases; refactor, several new, lots of notes.
athompson673 Dec 12, 2024
b77208c
Merge branch 'spyder-ide:master' into master
athompson673 Dec 12, 2024
4ada184
fix mistake in test_multicursor.test_overwrite_mode
athompson673 Dec 12, 2024
65d55ff
re-introduce smart home/end behavior
athompson673 Dec 17, 2024
d55785c
Apply suggestions from code review
athompson673 Dec 20, 2024
b600344
Apply suggestions from code review
athompson673 Dec 20, 2024
bc783ed
Refactor multi-cursor functions into separate mixin class.
athompson673 Dec 20, 2024
ea81125
commit new multicursor_mixin.MultiCursorMixin
athompson673 Dec 20, 2024
ac15f0e
import style edits
athompson673 Dec 20, 2024
2d5ba23
solved TODO: move line with multiple cursors on same line
athompson673 Dec 20, 2024
3dea758
WIP test_multicursor
athompson673 Dec 20, 2024
dff9d06
Style comments, and a few more tests written
athompson673 Dec 30, 2024
be52d75
Merge branch 'master' into master
athompson673 Dec 30, 2024
10cf0f3
fix bad merge edit
athompson673 Dec 30, 2024
04cd94b
call shortcuts directly to side-step key input issues with other plat…
athompson673 Dec 30, 2024
d22e028
Apply suggestions from code review
athompson673 Jan 3, 2025
22fc03a
Apply suggestions from code review
athompson673 Jan 3, 2025
5a79728
Apply suggestions from code review
athompson673 Jan 3, 2025
5d1d614
Remove extra newlines from github review accidentally applied twice. …
athompson673 Jan 4, 2025
0d8693c
configure extra cursor selection colors from spyder.utils.palette.Spy…
athompson673 Jan 4, 2025
490d5a0
Change extra selection color selection to retrieve from CodeEditor.pa…
athompson673 Jan 4, 2025
908c036
pep8
athompson673 Jan 4, 2025
3735bc5
style and removed imports no longer needed
athompson673 Jan 4, 2025
2cc8a31
Change multicursor_mixin to pull text selection colors from SpyderPal…
athompson673 Jan 5, 2025
0bf7f90
Added shortcuts for 'add cursor up/down'
athompson673 Jan 7, 2025
bc16948
Create Cursor creation functions within MultiCursorMixin
athompson673 Jan 8, 2025
1ec8b41
Move cursor creation to functions provided by MultiCursorMixin
athompson673 Jan 8, 2025
3d7a8e9
change default shortcuts for duplicate line to not conflict with add …
athompson673 Jan 9, 2025
2310863
hide scrollflag range while ctrl-alt is held
athompson673 Jan 9, 2025
55c29d6
Add shortcut to clear extra cursors
athompson673 Jan 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading