179 lines
5.6 KiB
Python
179 lines
5.6 KiB
Python
# Forked from: https://github.com/optilude/SublimeTextMisc/blob/master/navigationHistory.py
|
|
import sublime, sublime_plugin
|
|
from collections import deque
|
|
|
|
MAX_SIZE = 64
|
|
LINE_THRESHOLD = 2
|
|
|
|
class Location(object):
|
|
"""A location in the history
|
|
"""
|
|
|
|
def __init__(self, path, line, col):
|
|
self.path = path
|
|
self.line = line
|
|
self.col = col
|
|
|
|
def __eq__(self, other):
|
|
return self.path == other.path and self.line == other.line
|
|
|
|
def __ne__(self, other):
|
|
return not self.__eq__(other)
|
|
|
|
def __nonzero__(self):
|
|
return (self.path is not None and self.line is not None)
|
|
|
|
def near(self, other):
|
|
return self.path == other.path and abs(self.line - other.line) <= LINE_THRESHOLD
|
|
|
|
def copy(self):
|
|
return Location(self.path, self.line, self.col)
|
|
|
|
class History(object):
|
|
"""Keep track of the history for a single window
|
|
"""
|
|
|
|
def __init__(self, max_size=MAX_SIZE):
|
|
self._current = None # current location as far as the
|
|
# history is concerned
|
|
self._back = deque([], max_size) # items before self._current
|
|
self._forward = deque([], max_size) # items after self._current
|
|
|
|
self._last_movement = None # last recorded movement
|
|
|
|
def record_movement(self, location):
|
|
"""Record movement to the given location, pushing history if
|
|
applicable
|
|
"""
|
|
|
|
if location:
|
|
if self.has_changed(location):
|
|
#Push the current location if it hasn't been added already, so when going back after using goto it goes to the correct location
|
|
if self._back and self._last_movement is not self._back[-1]:
|
|
self.push(self._last_movement)
|
|
self.push(location)
|
|
self.mark_location(location)
|
|
|
|
def mark_location(self, location):
|
|
"""Remember the current location, for the purposes of being able
|
|
to do a has_changed() check.
|
|
"""
|
|
self._last_movement = location.copy()
|
|
|
|
def has_changed(self, location):
|
|
"""Determine if the given location combination represents a
|
|
significant enough change to warrant pushing history.
|
|
"""
|
|
|
|
return self._last_movement is None or not self._last_movement.near(location)
|
|
|
|
def push(self, location):
|
|
"""Push the given location to the back history. Clear the forward
|
|
history.
|
|
"""
|
|
|
|
if self._current is not None:
|
|
self._back.append(self._current.copy())
|
|
self._current = location.copy()
|
|
self._forward.clear()
|
|
|
|
def back(self):
|
|
"""Move backward in history, returning the location to jump to.
|
|
Returns None if no history.
|
|
"""
|
|
|
|
if not self._back:
|
|
return None
|
|
|
|
self._forward.appendleft(self._current)
|
|
self._current = self._back.pop()
|
|
self._last_movement = self._current # preempt, so we don't re-push
|
|
return self._current
|
|
|
|
def forward(self):
|
|
"""Move forward in history, returning the location to jump to.
|
|
Returns None if no history.
|
|
"""
|
|
|
|
if not self._forward:
|
|
return None
|
|
|
|
self._back.append(self._current)
|
|
self._current = self._forward.popleft()
|
|
self._last_movement = self._current # preempt, so we don't re-push
|
|
return self._current
|
|
|
|
_histories = {} # window id -> History
|
|
|
|
def get_history():
|
|
"""Get a History object for the current window,
|
|
creating a new one if required
|
|
"""
|
|
|
|
window = sublime.active_window()
|
|
if window is None:
|
|
return None
|
|
|
|
window_id = window.id()
|
|
history = _histories.get(window_id, None)
|
|
if history is None:
|
|
_histories[window_id] = history = History()
|
|
return history
|
|
|
|
class NavigationHistoryRecorder(sublime_plugin.EventListener):
|
|
"""Keep track of history
|
|
"""
|
|
|
|
def on_selection_modified(self, view):
|
|
"""When the selection is changed, possibly record movement in the
|
|
history
|
|
"""
|
|
history = get_history()
|
|
if history is None:
|
|
return
|
|
|
|
path = view.file_name()
|
|
row, col = view.rowcol(view.sel()[0].a)
|
|
history.record_movement(Location(path, row + 1, col + 1))
|
|
|
|
# def on_close(self, view):
|
|
# """When a view is closed, check to see if the window was closed too
|
|
# and clean up orphan histories
|
|
# """
|
|
#
|
|
# # XXX: This doesn't work - event runs before window is removed
|
|
# # from sublime.windows()
|
|
#
|
|
# windows_with_history = set(_histories.keys())
|
|
# window_ids = set([w.id() for w in sublime.windows()])
|
|
# closed_windows = windows_with_history.difference(window_ids)
|
|
# for window_id in closed_windows:
|
|
# del _histories[window_id]
|
|
|
|
class NavigationHistoryBack(sublime_plugin.TextCommand):
|
|
"""Go back in history
|
|
"""
|
|
|
|
def run(self, edit):
|
|
history = get_history()
|
|
if history is None:
|
|
return
|
|
|
|
location = history.back()
|
|
if location:
|
|
window = sublime.active_window()
|
|
window.open_file("{0}:{1}:{2}".format(location.path, location.line, location.col), sublime.ENCODED_POSITION)
|
|
|
|
class NavigationHistoryForward(sublime_plugin.TextCommand):
|
|
"""Go forward in history
|
|
"""
|
|
|
|
def run(self, edit):
|
|
history = get_history()
|
|
if history is None:
|
|
return
|
|
|
|
location = history.forward()
|
|
if location:
|
|
window = sublime.active_window()
|
|
window.open_file("{0}:{1}:{2}".format(location.path, location.line, location.col), sublime.ENCODED_POSITION) |