-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathCommandHistory.py
132 lines (111 loc) · 4.9 KB
/
CommandHistory.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
import re
from common import fuzzy_match
class CommandHistory:
"""
Handle all things related to managing and navigating the command history
"""
def __init__(self):
# The actual command list
self.list = []
# The current search filter
self.filter = ''
# A filtered list based on the current filter
self.filtered_list = []
# A trail of visited indices (while navigating)
self.trail = []
def start(self, line):
"""
Start history navigation
"""
#print '\n\nStart\n\n'
self.filter = line
# Create a list of regex patterns to use when navigating the history
# using a filter
# A. First use just the space as word separator; these are the most
# useful matches (think acronyms 'g c m' for 'git checkout master' etc)
words = [re.escape(w) for w in re.findall('[^\\s]+', line)] # Split the filter into words
boundary = '[\\s]+'
patterns = [
# Prefixes match for each word in the command (strongest, these will be the
# first in the list
'^' + boundary.join(['(' + word + ')[^\\s]*' for word in words]) + '$',
# Prefixes match for some words in the command
boundary.join(['(' + word + ')[^\\s]*' for word in words]),
]
# B. Then split based on other separator characters as well
words = [re.escape(w) for w in re.findall('[a-zA-Z0-9]+', line)] # Split the filter into words
boundary = '[\\s\\.\\-\\\\_]+' # Word boundary characters
patterns += [
# Prefixes match for each word in the command (strongest, these will be the
# first in the list
'^' + boundary.join(['(' + word + ')[a-zA-Z0-9]*' for word in words]) + '$',
# Prefixes match for some words in the command
boundary.join(['(' + word + ')[a-zA-Z0-9]*' for word in words]),
# Exact string match
'(' + re.escape(line) + ')',
# Substring match in different words
boundary.join(['(' + word + ').*' for word in words]),
# Substring match anywhere (weakest, these will be the last results)
''.join(['(' + word + ').*' for word in words])
]
if len(words) <= 1:
# Optimization: Skip the advanced word-based matching for empty or
# simple (one-word) filters -- this saves a lot of computation effort
# as these filters will yield a long list of matched lines!
patterns = [patterns[4]]
# Traverse the history and build the filtered list
self.filtered_list = []
for pattern in patterns:
#print '\n\n', pattern, '\n\n'
for line in reversed(self.list):
if line in [l for (l, p) in self.filtered_list]:
# We already added this line, skip
continue
# No need to re.compile() this, the re library automatically caches compiled
# versions of the recently used expressions
matches = re.search(pattern, line, re.IGNORECASE)
if matches:
self.filtered_list.insert(0, (line, [matches.span(i) for i in range(1, matches.lastindex + 1)]))
# print '\n\n', self.filtered_list[0], '\n\n'
# optimization: limit the results added to filtered_list.
# the filter list is traversed by keyup/keydown so more than tens in the list looks crazy
# The high selective pattern should always be used when necessary.
if len(self.filtered_list) >= 30 :
break;
if len(self.filtered_list) >= 30 :
break;
# We use the trail to navigate back in the same order
# don't update self.trail if there is no match in filter
if self.filtered_list :
self.trail = [(self.filter, [(0, len(self.filter))])]
def up(self):
"""
Navigate back in the command history
"""
if self.filtered_list:
self.trail.append(self.filtered_list.pop())
return True
else :
return False
def down(self):
"""
Navigate forward in the command history
"""
if self.trail:
self.filtered_list.append(self.trail.pop())
def reset(self):
"""Reset browsing through the history"""
self.filter = ''
self.filtered_list = []
self.trail = []
def add(self, line):
"""Add a new line to the history"""
if line:
#print 'Adding "' + line + '"'
if line in self.list:
self.list.remove(line)
self.list.append(line)
self.reset()
def current(self):
"""Return the current history item"""
return self.trail[-1] if self.trail else ('', [])