Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Martin Aspeli August 13, 2011
file 176 lines (137 sloc) 5.371 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
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):
                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("%s:%d:%d" % (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("%s:%d:%d" % (location.path, location.line, location.col), sublime.ENCODED_POSITION)
Something went wrong with that request. Please try again.