Last active
August 29, 2015 14:28
-
-
Save Lanny/3f402467aab848247e11 to your computer and use it in GitHub Desktop.
like ctrl-p but for the terminal I guess?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import os | |
import sys | |
import subprocess | |
from fnmatch import fnmatch | |
from multiprocessing import Process, Queue | |
from Queue import Empty as QEmpty | |
import urwid | |
VERSION = '0.4' | |
class Finder(object): | |
def __init__(self, filelist, status_text, num_results=2048): | |
self.POLL_FREQ = 0.1 | |
self.fl = filelist | |
self.status_text = status_text | |
self.in_q = Queue() | |
self.out_q = Queue() | |
self.num_results = num_results | |
ignore_opts = self._load_gitignore() | |
self.p = Process( | |
target=self._job_runner, | |
args=(self.in_q, self.out_q, ignore_opts)) | |
self.p.start() | |
def set_num_results(self, n): | |
self.num_results = n | |
def offer_main_loop(finder_obj, loop): | |
def poll_out(loop, _): | |
try: | |
r_type, data = finder_obj.out_q.get(False) | |
if r_type == 'result': | |
finder_obj.fl.set_files(data) | |
elif r_type == 'status_message': | |
finder_obj.status_text.set_text(data) | |
else: | |
raise Exception( | |
'Invalid message from finder process: %s' % r_type) | |
except QEmpty: | |
pass | |
loop.set_alarm_in(finder_obj.POLL_FREQ, poll_out) | |
poll_out(loop, None) | |
def set_query(self, query): | |
# Remove anything in the queue if it's there, if not great, ours will | |
# be the next job. | |
try: | |
while 1: | |
self.in_q.get(False) | |
except QEmpty: | |
pass | |
self.in_q.put(query) | |
def _load_gitignore(self): | |
try: | |
git_root = subprocess.check_output(['git', 'rev-parse', | |
'--show-toplevel']) | |
git_root = git_root[:-1] # strip newline | |
except subprocess.CalledProcessError: | |
# We probably weren't in a git directory | |
return | |
gitignore = open(os.path.join(git_root, '.gitignore'), 'r') | |
ignores = map(lambda x: x.strip(), gitignore.readlines()) | |
ignores.append('*.git/*') | |
return ignores | |
def _job_runner(self, in_q, out_q, num_results, ignore_list=()): | |
while 1: | |
out_q.put(('status_message', 'idle')) | |
job = in_q.get() | |
out_q.put(('status_message', 'finding...')) | |
cmd = ['find', '.', '-name', '*%s*' % job] | |
result = subprocess.check_output(cmd) | |
out_q.put(('status_message', 'filtering...')) | |
result_list = result.split('\n')[:-1] | |
filtered_items = [] | |
for item in result_list: | |
ignored = False | |
for pat in ignore_list: | |
if fnmatch(item, pat): | |
ignored = True | |
break | |
if not ignored: | |
filtered_items.append(item) | |
if len(filtered_items) >= num_results: | |
break | |
out_q.put(('result', filtered_items)) | |
class FileEntry(urwid.AttrMap): | |
def __init__(self, text): | |
self._text = text | |
self.textWidget = urwid.Text(text) | |
super(FileEntry, self).__init__(self.textWidget, '') | |
def get_text(self): | |
return self._text | |
def select(self): | |
self.set_attr_map({None: 'selected'}) | |
def unselect(self): | |
self.set_attr_map({None: ''}) | |
class FileList(urwid.ListBox): | |
def __init__(self): | |
self.i = 0 | |
self.body = urwid.SimpleListWalker([]) | |
super(FileList, self).__init__(self.body) | |
def add_item(self, path): | |
item = FileEntry(path) | |
self.body.append(item) | |
def selectItem(self, i): | |
if i > len(self.body) - 1 or i < 0: | |
return | |
self.body[self.i].unselect() | |
self.body[i].select() | |
self.i = i | |
def get_selected_file(self): | |
return self.body[self.i].get_text() | |
def selectNext(self): | |
self.selectItem(self.i + 1) | |
def selectPrev(self): | |
self.selectItem(self.i - 1) | |
def empty(self): | |
while len(self.body) > 0: | |
self.body.pop() | |
def set_files(self, files): | |
self.empty() | |
for path in files: | |
self.add_item(path) | |
self.selectItem(0) | |
class CooperativeEdit(urwid.Edit): | |
def __init__(self, prompt, callback): | |
self.callback = callback | |
super(CooperativeEdit, self).__init__(prompt) | |
def keypress(self, size, key): | |
key = super(CooperativeEdit, self).keypress(size, key) | |
self.callback(key) | |
class Irvine(urwid.Frame): | |
def __init__(self, path): | |
text = 'Irvine v%s\n' % VERSION | |
text += '-' * len(text) | |
self.title = urwid.Text(text) | |
self.status = urwid.Text('idle', align='right') | |
self.header = urwid.Columns((self.title, self.status)) | |
self.filelist = FileList() | |
self.textbox = CooperativeEdit('> ', self._keydown) | |
self.finder = Finder(self.filelist, self.status) | |
for filename in os.listdir(path): | |
self.filelist.add_item(filename) | |
self.filelist.selectItem(0) | |
super(Irvine, self).__init__(self.filelist, self.header, self.textbox) | |
self.set_focus('footer') | |
def _keydown(self, key): | |
if key == 'up': | |
self.filelist.selectPrev() | |
elif key == 'down': | |
self.filelist.selectNext() | |
elif key == 'enter': | |
subprocess.check_call(['mvim', self.filelist.get_selected_file()]) | |
else: | |
self.finder.set_query(self.textbox.edit_text) | |
def start(self, main_loop): | |
_, rows = loop.screen.get_cols_rows() | |
self.finder.set_num_results(rows - 3) | |
self.finder.offer_main_loop(main_loop) | |
try: | |
loop.run() | |
except KeyboardInterrupt: | |
sys.exit(0) | |
if __name__ == '__main__': | |
palette = [ | |
('selected', 'black', 'light gray'), | |
] | |
irvine = Irvine(os.getcwd()) | |
loop = urwid.MainLoop(irvine, palette) | |
irvine.start(loop) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment