Created
May 9, 2010 02:02
-
-
Save rygwdn/394885 to your computer and use it in GitHub Desktop.
This file contains 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/python | |
"""Yet another curses-based directory tree browser, in Python. | |
I thought I could use something like this for filename entry, kind of | |
like the old 4DOS 'select' command --- cd $(cursoutline.py). So you | |
navigate and hit Enter, and it exits and spits out the file you're on. | |
Originally from: http://lists.canonical.org/pipermail/kragen-hacks/2005-December/000424.html | |
Originally by: Kragen Sitaker | |
""" | |
# There are several general approaches to the drawing-an-outline | |
# problem. This program supports the following operations: | |
# - move cursor to previous item (in preorder traversal) | |
# - move cursor to next item (likewise) | |
# - hide descendants | |
# - reveal children | |
# And because it runs over the filesystem, it must be at least somewhat lazy | |
# about expanding children. | |
# And it doesn't really bother to worry about someone else changing the outline | |
# behind its back. | |
# So the strategy is to store our current linear position in the | |
# inorder traversal, and defer operations on the current node until the next | |
# time we're traversing. | |
import curses.wrapper, time, random, cgitb, os, sys | |
cgitb.enable(format="text") | |
ESC = 27 | |
result = '' | |
start = '.' | |
def pad(data, width): | |
# XXX this won't work with UTF-8 | |
return data + ' ' * (width - len(data)) | |
class File: | |
def __init__(self, name): | |
self.name = name | |
def render(self, depth, width): | |
return pad('%s%s %s' % (' ' * 4 * depth, self.icon(), | |
os.path.basename(self.name)), width) | |
def icon(self): return ' ' | |
def traverse(self): yield self, 0 | |
def expand(self): pass | |
def collapse(self): pass | |
class Dir(File): | |
def __init__(self, name): | |
File.__init__(self, name) | |
try: self.kidnames = sorted(os.listdir(name)) | |
except: self.kidnames = None # probably permission denied | |
self.kids = None | |
self.expanded = False | |
def children(self): | |
if self.kidnames is None: return [] | |
if self.kids is None: | |
self.kids = [factory(os.path.join(self.name, kid)) | |
for kid in self.kidnames] | |
return self.kids | |
def icon(self): | |
if self.expanded: return '[-]' | |
elif self.kidnames is None: return '[?]' | |
elif self.children(): return '[+]' | |
else: return '[ ]' | |
def expand(self): self.expanded = True | |
def collapse(self): self.expanded = False | |
def traverse(self): | |
yield self, 0 | |
if not self.expanded: return | |
for child in self.children(): | |
for kid, depth in child.traverse(): | |
yield kid, depth + 1 | |
def factory(name): | |
if os.path.isdir(name): return Dir(name) | |
else: return File(name) | |
def main(stdscr): | |
cargo_cult_routine(stdscr) | |
stdscr.nodelay(0) | |
mydir = factory(start) | |
mydir.expand() | |
curidx = 3 | |
pending_action = None | |
pending_save = False | |
while 1: | |
stdscr.clear() | |
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) | |
line = 0 | |
offset = max(0, curidx - curses.LINES + 3) | |
for data, depth in mydir.traverse(): | |
if line == curidx: | |
stdscr.attrset(curses.color_pair(1) | curses.A_BOLD) | |
if pending_action: | |
getattr(data, pending_action)() | |
pending_action = None | |
elif pending_save: | |
global result | |
result = data.name | |
return | |
else: | |
stdscr.attrset(curses.color_pair(0)) | |
if 0 <= line - offset < curses.LINES - 1: | |
stdscr.addstr(line - offset, 0, | |
data.render(depth, curses.COLS)) | |
line += 1 | |
stdscr.refresh() | |
ch = stdscr.getch() | |
if ch == curses.KEY_UP: curidx -= 1 | |
elif ch == curses.KEY_DOWN: curidx += 1 | |
elif ch == curses.KEY_PPAGE: | |
curidx -= curses.LINES | |
if curidx < 0: curidx = 0 | |
elif ch == curses.KEY_NPAGE: | |
curidx += curses.LINES | |
if curidx >= line: curidx = line - 1 | |
elif ch == curses.KEY_RIGHT: pending_action = 'expand' | |
elif ch == curses.KEY_LEFT: pending_action = 'collapse' | |
elif ch == ESC: return | |
elif ch == ord('\n'): pending_save = True | |
curidx %= line | |
def cargo_cult_routine(win): | |
win.clear() | |
win.refresh() | |
curses.nl() | |
curses.noecho() | |
win.timeout(0) | |
def open_tty(): | |
saved_stdin = os.dup(0) | |
saved_stdout = os.dup(1) | |
os.close(0) | |
os.close(1) | |
stdin = os.open('/dev/tty', os.O_RDONLY) | |
stdout = os.open('/dev/tty', os.O_RDWR) | |
return saved_stdin, saved_stdout | |
def restore_stdio((saved_stdin, saved_stdout)): | |
os.close(0) | |
os.close(1) | |
os.dup(saved_stdin) | |
os.dup(saved_stdout) | |
if __name__ == '__main__': | |
global start | |
if len(sys.argv) > 1: | |
start = sys.argv[1] | |
saved_fds = open_tty() | |
try: curses.wrapper(main) | |
finally: restore_stdio(saved_fds) | |
print result |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment