-
Notifications
You must be signed in to change notification settings - Fork 2
/
toothbrush.py
executable file
·159 lines (131 loc) · 5.09 KB
/
toothbrush.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
#!/usr/bin/python
import sys, tty, termios, subprocess, os, json, glob, time, re, threading
DIR_PATH_NOTES = os.path.expanduser("~/Dropbox/tbrush_notes")
DIR_PATH_META = os.path.expanduser('~/.toothbrush_meta')
def getch():
# Return a single character from stdin.
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
def main_loop():
# Load notes and saved_query.
if not os.path.exists(DIR_PATH_META):
print(
"\nWelcome! Toothbrush helps you maintain a directory of txt notes. Just start typing and "
"hit return. This will create a new text file. Go ahead and type whatever in there. Save "
"and close your editor. Run toothbrush again. Enter the same query. Notice that your saved "
"note comes up. Press the down key to select it and hit return to open."
)
os.makedirs(DIR_PATH_META)
if not os.path.exists(DIR_PATH_NOTES):
os.makedirs(DIR_PATH_NOTES)
query_string = ' '.join(sys.argv[1:])
query_path = os.path.join(DIR_PATH_META, 'saved_query.txt')
if not query_string.strip() and os.path.exists(query_path):
with open(query_path) as f:
query_string = f.read()
notes = Notes(query_string)
# Wait for a key, build up the query string.
is_first_key = True
while True:
notes.search(query_string)
ch = getch()
# print('ord(ch):', ord(ch))
if ord(ch) == 3: # ctrl+c
raise KeyboardInterrupt
elif ord(ch) == 23: # ctrl+w
stripped = query_string.strip()
query_string = (stripped.rsplit(' ', 1)[0] + ' ') if ' ' in stripped else ''
elif ord(ch) == 127: # backspace
query_string = query_string[:-1]
elif ord(ch) == 13: # return
if notes.selected_index is None or notes.selected_index >= len(notes.matched_basenames):
notes.new_note(query_string)
else:
notes.open_index(notes.selected_index)
break
elif ord(ch) == 27: # esc code
ch = getch() # skip the [
ch = getch()
if ord(ch) == 66 or ord(ch) == 65: # up/down arrows
notes.adjust_selection(1 if ord(ch) == 66 else -1)
else:
if is_first_key:
query_string = ''
query_string += ch
is_first_key = False
with open(query_path, 'w') as f:
f.write(query_string)
class Notes:
def __init__(self, initial_query_string):
self.dir_path = os.path.expanduser(DIR_PATH_NOTES)
self.selected_index = None
self.basename_to_content = {}
self.basename_to_content_lower = {}
self.matched_basenames = []
self.query_string = initial_query_string
glob_path = os.path.join(self.dir_path, '*.txt')
for path in glob.glob(glob_path):
basename = os.path.splitext(os.path.basename(path))[0]
with open(path) as f:
self.basename_to_content[basename] = f.read()
self.basename_to_content_lower[basename] = self.basename_to_content[basename].lower()
def search(self, query_string):
print('\nquery: [{}]\n'.format(query_string))
self.matched_basenames = []
self.query_string = query_string
terms = set(query_string.lower().split())
for basename in self.basename_to_content_lower.keys():
content = self.basename_to_content_lower[basename]
for term in terms:
if term not in basename and term not in content:
break
else:
self.matched_basenames.append(basename)
self.matched_basenames.sort(key=self.score, reverse=True)
num_matches_to_show = 10
for i, basename in enumerate(self.matched_basenames[:num_matches_to_show]):
print('{}{}'.format('> ' if i == self.selected_index else ' ', basename))
if i == self.selected_index:
full_text = self.basename_to_content[basename].strip()
lines = full_text.splitlines()
lines = lines[:10] + (['...'] if len(lines) > 10 else [])
indented_lines = [' ' + line for line in lines]
content_preview = '\n'.join(indented_lines)
print(content_preview)
if not self.matched_basenames and self.basename_to_content_lower:
print('~ nothing found ~')
elif len(self.matched_basenames) > num_matches_to_show:
print(' ...')
def score(self, basename):
return 10 if self.query_string == basename else 0
def open_index(self, index):
basename = self.matched_basenames[index]
path = os.path.join(self.dir_path, basename) + '.txt'
self.open_path(path)
def open_path(self, path):
print('opening:')
print('"{}"'.format(path))
subprocess.call(['open', path])
def new_note(self, query_string):
new_path = os.path.join(DIR_PATH_NOTES, query_string) + '.txt'
if not os.path.exists(new_path):
with open(new_path, 'w') as f:
f.write('')
self.open_path(new_path)
def adjust_selection(self, amount):
if not self.matched_basenames:
self.selected_index = None
return
if self.selected_index is None:
self.selected_index = 0
else:
self.selected_index += amount
self.selected_index %= min(len(self.matched_basenames), 10)
if __name__ == '__main__':
main_loop()