# 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)