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: Improve cursor position history (Editor) #16921

Merged
merged 6 commits into from
Dec 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
215 changes: 135 additions & 80 deletions spyder/plugins/editor/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,9 @@ def __init__(self, parent, ignore_last_opened_files=False):
layout.addWidget(self.dock_toolbar)

self.last_edit_cursor_pos = None
self.cursor_pos_history = []
self.cursor_pos_index = None
self.__ignore_cursor_position = True
self.cursor_undo_history = []
self.cursor_redo_history = []
self.__ignore_cursor_history = True

# Completions setup
self.completion_capabilities = {}
Expand Down Expand Up @@ -287,14 +287,12 @@ def __init__(self, parent, ignore_last_opened_files=False):
self.edit_filetypes = None
self.edit_filters = None

self.__ignore_cursor_position = False
self.__ignore_cursor_history = False
current_editor = self.get_current_editor()
if current_editor is not None:
filename = self.get_current_filename()
position = current_editor.get_position('cursor')
line, column = current_editor.get_cursor_line_column()
self.add_cursor_position_to_history(filename, position, line,
column)
cursor = current_editor.textCursor()
self.add_cursor_to_history(filename, cursor)
self.update_cursorpos_actions()
self.set_path()

Expand Down Expand Up @@ -2106,6 +2104,8 @@ def load(self, filenames=None, goto=None, word='',
(So that the end position is start_column + end_column)
Alternatively, the first match of word is used as a position.
"""
cursor_history_state = self.__ignore_cursor_history
self.__ignore_cursor_history = True
# Switch to editor before trying to load a file
try:
self.switch_to_plugin()
Expand Down Expand Up @@ -2178,6 +2178,7 @@ def load(self, filenames=None, goto=None, word='',
if filenames:
filenames = [osp.normpath(fname) for fname in filenames]
else:
self.__ignore_cursor_history = cursor_history_state
return

focus_widget = QApplication.focusWidget()
Expand Down Expand Up @@ -2262,6 +2263,9 @@ def _convert(fname):
pdb_last_step = ipyconsole.get_pdb_last_step()
self.update_pdb_state(current_pdb_state, pdb_last_step)

self.__ignore_cursor_history = cursor_history_state
self.add_cursor_to_history()

@Slot()
def print_file(self):
"""Print current file"""
Expand Down Expand Up @@ -2497,28 +2501,28 @@ def unblockcomment(self):
def go_to_next_todo(self):
self.switch_to_plugin()
editor = self.get_current_editor()
position = editor.go_to_next_todo()
editor.go_to_next_todo()
filename = self.get_current_filename()
line, column = editor.get_cursor_line_column()
self.add_cursor_position_to_history(filename, position, line, column)
cursor = editor.textCursor()
self.add_cursor_to_history(filename, cursor)

@Slot()
def go_to_next_warning(self):
self.switch_to_plugin()
editor = self.get_current_editor()
position = editor.go_to_next_warning()
editor.go_to_next_warning()
filename = self.get_current_filename()
line, column = editor.get_cursor_line_column()
self.add_cursor_position_to_history(filename, position, line, column)
cursor = editor.textCursor()
self.add_cursor_to_history(filename, cursor)

@Slot()
def go_to_previous_warning(self):
self.switch_to_plugin()
editor = self.get_current_editor()
position = editor.go_to_previous_warning()
editor.go_to_previous_warning()
filename = self.get_current_filename()
line, column = editor.get_cursor_line_column()
self.add_cursor_position_to_history(filename, position, line, column)
cursor = editor.textCursor()
self.add_cursor_to_history(filename, cursor)

def toggle_eol_chars(self, os_name, checked):
if checked:
Expand Down Expand Up @@ -2548,44 +2552,46 @@ def fix_indentation(self):
#------ Cursor position history management
def update_cursorpos_actions(self):
self.previous_edit_cursor_action.setEnabled(
self.last_edit_cursor_pos is not None)
self.last_edit_cursor_pos is not None)
self.previous_cursor_action.setEnabled(
self.cursor_pos_index is not None and self.cursor_pos_index > 0)
self.next_cursor_action.setEnabled(self.cursor_pos_index is not None \
and self.cursor_pos_index < len(self.cursor_pos_history)-1)
len(self.cursor_undo_history) > 0)
self.next_cursor_action.setEnabled(
len(self.cursor_redo_history) > 0)

def add_cursor_position_to_history(self, filename, position, line, column,
fc=False):
if self.__ignore_cursor_position:
def add_cursor_to_history(self, filename=None, cursor=None):
if self.__ignore_cursor_history:
return
for index, (fname, pos, c_line, c_col) in enumerate(
self.cursor_pos_history):
if filename is None:
filename = self.get_current_filename()
if cursor is None:
editor = self._get_editor(filename)
if editor is None:
return
cursor = editor.textCursor()

replace_last_entry = False
if len(self.cursor_undo_history) > 0:
fname, hist_cursor = self.cursor_undo_history[-1]
if fname == filename:
if pos == position or pos == 0 or line == c_line:
if fc:
self.cursor_pos_history[index] = (filename, position,
line, column)
self.cursor_pos_index = index
self.update_cursorpos_actions()
return
else:
if self.cursor_pos_index >= index:
self.cursor_pos_index -= 1
self.cursor_pos_history.pop(index)
break
if self.cursor_pos_index is not None:
self.cursor_pos_history = \
self.cursor_pos_history[:self.cursor_pos_index+1]
self.cursor_pos_history.append((filename, position, line, column))
self.cursor_pos_index = len(self.cursor_pos_history)-1
if cursor.blockNumber() == hist_cursor.blockNumber():
# Only one cursor per line
replace_last_entry = True

if replace_last_entry:
self.cursor_undo_history.pop()
else:
# Drop redo stack as we moved
self.cursor_redo_history = []

self.cursor_undo_history.append((filename, cursor))
self.update_cursorpos_actions()

def text_changed_at(self, filename, position):
self.last_edit_cursor_pos = (to_text_string(filename), position)

def current_file_changed(self, filename, position, line, column):
self.add_cursor_position_to_history(to_text_string(filename), position,
line, column, fc=True)
cursor = self.get_current_editor().textCursor()
self.add_cursor_to_history(to_text_string(filename), cursor)

# Hide any open tooltips
current_stack = self.get_current_editorstack()
Expand All @@ -2603,22 +2609,25 @@ def current_editor_cursor_changed(self, line, column):
"""Handles the change of the cursor inside the current editor."""
code_editor = self.get_current_editor()
filename = code_editor.filename
position = code_editor.get_position('cursor')
line, column = code_editor.get_cursor_line_column()
self.add_cursor_position_to_history(
to_text_string(filename), position, line, column, fc=True)
cursor = code_editor.textCursor()
self.add_cursor_to_history(
to_text_string(filename), cursor)

def remove_file_cursor_history(self, id, filename):
"""Remove the cursor history of a file if the file is closed."""
new_history = []
for i, (cur_filename, pos, line, column) in enumerate(
self.cursor_pos_history):
if cur_filename == filename:
if i < self.cursor_pos_index:
self.cursor_pos_index = self.cursor_pos_index - 1
else:
new_history.append((cur_filename, pos, line, column))
self.cursor_pos_history = new_history
for i, (cur_filename, cursor) in enumerate(
self.cursor_undo_history):
if cur_filename != filename:
new_history.append((cur_filename, cursor))
self.cursor_undo_history = new_history

new_redo_history = []
for i, (cur_filename, cursor) in enumerate(
self.cursor_redo_history):
if cur_filename != filename:
new_redo_history.append((cur_filename, cursor))
self.cursor_redo_history = new_redo_history

@Slot()
def go_to_last_edit_location(self):
Expand All @@ -2633,46 +2642,92 @@ def go_to_last_edit_location(self):
if position < editor.document().characterCount():
editor.set_cursor_position(position)

def _pop_next_cursor_diff(self, history, current_filename, current_cursor):
"""Get the next cursor from history that is different from current."""
while history:
steff456 marked this conversation as resolved.
Show resolved Hide resolved
filename, cursor = history.pop()
if (filename != current_filename or
cursor.position() != current_cursor.position()):
return filename, cursor
return None, None

def _history_steps(self, number_steps,
backwards_history, forwards_history,
current_filename, current_cursor):
"""
Move number_steps in the forwards_history, filling backwards_history.
"""
for i in range(number_steps):
if len(forwards_history) > 0:
# Put the current cursor in history
backwards_history.append(
(current_filename, current_cursor))
# Extract the next different cursor
current_filename, current_cursor = (
self._pop_next_cursor_diff(
forwards_history,
current_filename, current_cursor))
if current_cursor is None:
# Went too far, back up once
current_filename, current_cursor = (
backwards_history.pop())
return current_filename, current_cursor


def __move_cursor_position(self, index_move):
"""
Move the cursor position forward or backward in the cursor
position history by the specified index increment.
"""
if self.cursor_pos_index is None:
return
filename, _position, _line, _column = (
self.cursor_pos_history[self.cursor_pos_index])
cur_line, cur_col = self.get_current_editor().get_cursor_line_column()
self.cursor_pos_history[self.cursor_pos_index] = (
filename, self.get_current_editor().get_position('cursor'),
cur_line, cur_col)
self.__ignore_cursor_position = True
old_index = self.cursor_pos_index
self.cursor_pos_index = min(len(self.cursor_pos_history) - 1,
max(0, self.cursor_pos_index + index_move))
filename, position, line, col = (
self.cursor_pos_history[self.cursor_pos_index])
self.__ignore_cursor_history = True
# Remove last position as it will be replaced by the current position
if self.cursor_undo_history:
self.cursor_undo_history.pop()

# Update last position on the line
current_filename = self.get_current_filename()
current_cursor = self.get_current_editor().textCursor()

if index_move < 0:
# Undo
current_filename, current_cursor = self._history_steps(
-index_move,
self.cursor_redo_history,
self.cursor_undo_history,
current_filename, current_cursor)

else:
# Redo
current_filename, current_cursor = self._history_steps(
index_move,
self.cursor_undo_history,
self.cursor_redo_history,
current_filename, current_cursor)

# Place current cursor in history
self.cursor_undo_history.append(
(current_filename, current_cursor))
filenames = self.get_current_editorstack().get_filenames()
if not osp.isfile(filename) and filename not in filenames:
self.cursor_pos_history.pop(self.cursor_pos_index)
if self.cursor_pos_index <= old_index:
old_index -= 1
self.cursor_pos_index = old_index
if (not osp.isfile(current_filename)
and current_filename not in filenames):
self.cursor_undo_history.pop()
else:
self.load(filename)
self.load(current_filename)
editor = self.get_current_editor()
if position < editor.document().characterCount():
editor.set_cursor_position(position)
self.__ignore_cursor_position = False
editor.setTextCursor(current_cursor)
editor.ensureCursorVisible()
self.__ignore_cursor_history = False
self.update_cursorpos_actions()

@Slot()
def go_to_previous_cursor_position(self):
self.__ignore_cursor_history = True
self.switch_to_plugin()
self.__move_cursor_position(-1)

@Slot()
def go_to_next_cursor_position(self):
self.__ignore_cursor_history = True
self.switch_to_plugin()
self.__move_cursor_position(1)

Expand Down
Loading