diff --git a/libmproxy/console.py b/libmproxy/console.py index a986cc1c8..d02132499 100644 --- a/libmproxy/console.py +++ b/libmproxy/console.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import Queue, mailcap, mimetypes, tempfile, os, subprocess +import Queue, mailcap, mimetypes, tempfile, os, subprocess, glob import os.path, sys import cStringIO import urwid.curses_display @@ -392,6 +392,70 @@ class ConnectionView(WWrap): return key +class _PathCompleter: + DEFAULTPATH = "/bin:/usr/bin:/usr/local/bin" + def __init__(self, _testing=False): + """ + _testing: disables reloading of the lookup table to make testing possible. + """ + self.lookup, self.offset = None, None + self.final = None + self._testing = _testing + + def reset(self): + self.lookup = None + self.offset = -1 + + def complete(self, txt): + """ + Returns the next completion for txt, or None if there is no completion. + """ + path = os.path.expanduser(txt) + if not self.lookup: + if not self._testing: + # Lookup is a set of (display value, actual value) tuples. + self.lookup = [] + if os.path.isdir(path): + files = glob.glob(os.path.join(path, "*")) + prefix = txt + else: + files = glob.glob(path+"*") + prefix = os.path.dirname(txt) + prefix = prefix.rstrip("/") or "./" + for f in files: + display = os.path.join(prefix, os.path.basename(f)) + if os.path.isdir(f): + display += "/" + self.lookup.append((display, f)) + if not self.lookup: + self.final = path + return path + self.lookup.sort() + self.offset = -1 + self.lookup.append((txt, txt)) + self.offset += 1 + if self.offset >= len(self.lookup): + self.offset = 0 + ret = self.lookup[self.offset] + self.final = ret[1] + return ret[0] + + +class PathEdit(urwid.Edit, _PathCompleter): + def __init__(self, *args, **kwargs): + urwid.Edit.__init__(self, *args, **kwargs) + _PathCompleter.__init__(self) + + def keypress(self, size, key): + if key == "tab": + comp = self.complete(self.get_edit_text()) + self.set_edit_text(comp) + self.set_edit_pos(len(comp)) + else: + self.reset() + return urwid.Edit.keypress(self, size, key) + + class ActionBar(WWrap): def __init__(self): self.message("") @@ -399,6 +463,9 @@ class ActionBar(WWrap): def selectable(self): return True + def path_prompt(self, prompt): + self.w = PathEdit(prompt) + def prompt(self, prompt): self.w = urwid.Edit(prompt) @@ -436,8 +503,11 @@ class StatusBar(WWrap): def get_edit_text(self): return self.ab.w.get_edit_text() + def path_prompt(self, prompt): + return self.ab.path_prompt(prompt) + def prompt(self, prompt): - self.ab.prompt(prompt) + return self.ab.prompt(prompt) def message(self, msg): self.ab.message(msg) @@ -786,6 +856,11 @@ class ConsoleMaster(controller.Master): self.nested = True self.make_view() + def path_prompt(self, prompt, callback): + self.statusbar.path_prompt(prompt) + self.view.set_focus("footer") + self.prompting = callback + def prompt(self, prompt, callback): self.statusbar.prompt(prompt) self.view.set_focus("footer") @@ -928,10 +1003,10 @@ class ConsoleMaster(controller.Master): else: raise Stop elif k == "S": - self.prompt("Save flows: ", self.save_flows) + self.path_prompt("Save flows: ", self.save_flows) k = None elif k == "L": - self.prompt("Load flows: ", self.load_flows) + self.path_prompt("Load flows: ", self.load_flows) k = None elif k == "c": self.prompt("Sticky cookie: ", self.set_stickycookie) diff --git a/test/test_console.py b/test/test_console.py index c5c856f86..cfafed964 100644 --- a/test/test_console.py +++ b/test/test_console.py @@ -288,9 +288,39 @@ class uformat_keyvals(libpry.AutoTree): ] ) +class uPathCompleter(libpry.AutoTree): + def test_completion(self): + c = console._PathCompleter(True) + c.reset() + c.lookup = [ + ("a", "x/a"), + ("aa", "x/aa"), + ] + assert c.complete("a") == "a" + assert c.final == "x/a" + assert c.complete("a") == "aa" + assert c.complete("a") == "a" + + c = console._PathCompleter(True) + r = c.complete("l") + assert c.final.endswith(r) + + c.reset() + assert c.complete("/nonexistent") == "/nonexistent" + assert c.final == "/nonexistent" + c.reset() + assert c.complete("~") != "~" + + c.reset() + s = "thisisatotallynonexistantpathforsure" + assert c.complete(s) == s + assert c.final == s + + tests = [ uFlow(), uformat_keyvals(), - uState() + uState(), + uPathCompleter() ]