Skip to content

Instantly share code, notes, and snippets.

@Lanny
Last active August 29, 2015 14:28
Show Gist options
  • Save Lanny/3f402467aab848247e11 to your computer and use it in GitHub Desktop.
Save Lanny/3f402467aab848247e11 to your computer and use it in GitHub Desktop.
like ctrl-p but for the terminal I guess?
#!/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