forked from danielmagnussons/orgmode
-
Notifications
You must be signed in to change notification settings - Fork 0
/
navigation_history.py
203 lines (147 loc) · 5.6 KB
/
navigation_history.py
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
'''
http://www.sublimetext.com/forum/viewtopic.php?f=5&t=2738
https://github.com/optilude/SublimeTextMisc
Put this in your "Packages" directory and then configure "Key bindings - User" to use it. I use these keybindings for it on OS X:
{ "keys": ["alt+left"], "command": "navigation_history_back"},
{ "keys": ["alt+right"], "command": "navigation_history_forward"}
'''
import sublime
import 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 __bool__(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)