summary refs log tree commit diff stats
path: root/ranger/gui/widgets/console.py
diff options
context:
space:
mode:
Diffstat (limited to 'ranger/gui/widgets/console.py')
-rw-r--r--ranger/gui/widgets/console.py838
1 files changed, 419 insertions, 419 deletions
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index 4d1855f7..f6f433bb 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -17,422 +17,422 @@ from ranger.container.history import History, HistoryEmptyException
 import ranger
 
 class Console(Widget):
-	visible = False
-	last_cursor_mode = None
-	history_search_pattern = None
-	prompt = ':'
-	copy = ''
-	tab_deque = None
-	original_line = None
-	history = None
-	history_backup = None
-	override = None
-	allow_close = False
-	historypath = None
-	wait_for_command_input = False
-	unicode_buffer = ""
-
-	def __init__(self, win):
-		Widget.__init__(self, win)
-		self.clear()
-		self.history = History(self.settings.max_console_history_size)
-		# load history from files
-		if not ranger.arg.clean:
-			self.historypath = self.fm.confpath('history')
-			try:
-				f = open(self.historypath, 'r')
-			except:
-				pass
-			else:
-				for line in f:
-					self.history.add(line[:-1])
-				f.close()
-		self.line = ""
-		self.history_backup = History(self.history)
-
-		# NOTE: the console is considered in the "question mode" when the
-		# question_queue is non-empty.  In that case, the console will draw the
-		# question instead of the regular console, and the input you give is
-		# used to answer the question instead of typing in commands.
-		#
-		# A question is a tuple of (question_string, callback_func,
-		# tuple_of_choices).  callback_func is a function that is called when
-		# the question is answered which gets the answer as an argument.
-		# tuple_of_choices looks like ('y', 'n').  Only one-letter-answers are
-		# currently supported.  Pressing enter uses the first choice whereas
-		# pressing ESC uses the second choice.
-		self.question_queue = []
-
-	def destroy(self):
-		# save history to files
-		if ranger.arg.clean or not self.settings.save_console_history:
-			return
-		if self.historypath:
-			try:
-				f = open(self.historypath, 'w')
-			except:
-				pass
-			else:
-				for entry in self.history_backup:
-					try:
-						f.write(entry + '\n')
-					except UnicodeEncodeError:
-						pass
-				f.close()
-
-	def draw(self):
-		self.win.erase()
-		if self.question_queue:
-			assert isinstance(self.question_queue[0], tuple)
-			assert len(self.question_queue[0]) == 3
-			self.addstr(0, 0, self.question_queue[0][0])
-			return
-
-		self.addstr(0, 0, self.prompt)
-		line = WideString(self.line)
-		overflow = -self.wid + len(self.prompt) + len(line) + 1
-		if overflow > 0: 
-			self.addstr(0, len(self.prompt), str(line[overflow:]))
-		else:
-			self.addstr(0, len(self.prompt), self.line)
-
-	def finalize(self):
-		move = self.fm.ui.win.move
-		if self.question_queue:
-			try:
-				move(self.y, len(self.question_queue[0][0]))
-			except:
-				pass
-		else:
-			try:
-				pos = uwid(self.line[0:self.pos]) + len(self.prompt)
-				move(self.y, self.x + min(self.wid-1, pos))
-			except:
-				pass
-
-	def open(self, string='', prompt=None, position=None):
-		if prompt is not None:
-			assert isinstance(prompt, str)
-			self.prompt = prompt
-		elif 'prompt' in self.__dict__:
-			del self.prompt
-
-		if self.last_cursor_mode is None:
-			try:
-				self.last_cursor_mode = curses.curs_set(1)
-			except:
-				pass
-		self.allow_close = False
-		self.tab_deque = None
-		self.unicode_buffer = ""
-		self.line = string
-		self.history_search_pattern = self.line
-		self.pos = len(string)
-		if position is not None:
-			self.pos = min(self.pos, position)
-		self.history_backup.fast_forward()
-		self.history = History(self.history_backup)
-		self.history.add('')
-		self.wait_for_command_input = True
-		return True
-
-	def close(self, trigger_cancel_function=True):
-		if self.question_queue:
-			question = self.question_queue[0]
-			answers = question[2]
-			if len(answers) >= 2:
-				self._answer_question(answers[1])
-		else:
-			self._close_command_prompt(trigger_cancel_function)
-
-	def _close_command_prompt(self, trigger_cancel_function=True):
-		if trigger_cancel_function:
-			cmd = self._get_cmd(quiet=True)
-			if cmd:
-				try:
-					cmd.cancel()
-				except Exception as error:
-					self.fm.notify(error)
-		if self.last_cursor_mode is not None:
-			try:
-				curses.curs_set(self.last_cursor_mode)
-			except:
-				pass
-			self.last_cursor_mode = None
-		self.fm.hide_console_info()
-		self.add_to_history()
-		self.tab_deque = None
-		self.clear()
-		self.__class__ = Console
-		self.wait_for_command_input = False
-
-	def clear(self):
-		self.pos = 0
-		self.line = ''
-
-	def press(self, key):
-		self.fm.ui.keymaps.use_keymap('console')
-		if not self.fm.ui.press(key):
-			self.type_key(key)
-
-	def _answer_question(self, answer):
-		if not self.question_queue:
-			return False
-		question = self.question_queue[0]
-		text, callback, answers = question
-		if answer in answers:
-			self.question_queue.pop(0)
-			callback(answer)
-			return True
-		return False
-
-	def type_key(self, key):
-		self.tab_deque = None
-
-		line = "" if self.question_queue else self.line
-		result = self._add_character(key, self.unicode_buffer, line, self.pos)
-		if result[1] == line:
-			# line didn't change, so we don't need to do anything, just update
-			# the unicode _buffer.
-			self.unicode_buffer = result[0]
-			return
-
-		if self.question_queue:
-			self.unicode_buffer, answer, self.pos = result
-			self._answer_question(answer)
-		else:
-			self.unicode_buffer, self.line, self.pos = result
-			self.on_line_change()
-
-	def _add_character(self, key, unicode_buffer, line, pos):
-		# Takes the pressed key, a string "unicode_buffer" containing a
-		# potentially incomplete unicode character, the current line and the
-		# position of the cursor inside the line.
-		# This function returns the new unicode buffer, the modified line and
-		# position.
-		if isinstance(key, int):
-			try:
-				key = chr(key)
-			except ValueError:
-				return unicode_buffer, line, pos
-
-		if self.fm.py3:
-			unicode_buffer += key
-			try:
-				decoded = unicode_buffer.encode("latin-1").decode("utf-8")
-			except UnicodeDecodeError:
-				return unicode_buffer, line, pos
-			except UnicodeEncodeError:
-				return unicode_buffer, line, pos
-			else:
-				unicode_buffer = ""
-				if pos == len(line):
-					line += decoded
-				else:
-					line = line[:pos] + decoded + line[pos:]
-				pos += len(decoded)
-		else:
-			if pos == len(line):
-				line += key
-			else:
-				line = line[:pos] + key + line[pos:]
-			pos += len(key)
-		return unicode_buffer, line, pos
-
-	def history_move(self, n):
-		try:
-			current = self.history.current()
-		except HistoryEmptyException:
-			pass
-		else:
-			if self.line != current and self.line != self.history.top():
-				self.history.modify(self.line)
-			if self.history_search_pattern:
-				self.history.search(self.history_search_pattern, n)
-			else:
-				self.history.move(n)
-			current = self.history.current()
-			if self.line != current:
-				self.line = self.history.current()
-				self.pos = len(self.line)
-
-	def add_to_history(self):
-		self.history_backup.fast_forward()
-		self.history_backup.add(self.line)
-		self.history = History(self.history_backup)
-
-	def move(self, **keywords):
-		direction = Direction(keywords)
-		if direction.horizontal():
-			# Ensure that the pointer is moved utf-char-wise
-			if self.fm.py3:
-				self.pos = direction.move(
-						direction=direction.right(),
-						minimum=0,
-						maximum=len(self.line) + 1,
-						current=self.pos)
-			else:
-				if self.fm.py3:
-					uc = list(self.line)
-					upos = len(self.line[:self.pos])
-				else:
-					uc = list(self.line.decode('utf-8', 'ignore'))
-					upos = len(self.line[:self.pos].decode('utf-8', 'ignore'))
-				newupos = direction.move(
-						direction=direction.right(),
-						minimum=0,
-						maximum=len(uc) + 1,
-						current=upos)
-				self.pos = len(''.join(uc[:newupos]).encode('utf-8', 'ignore'))
-
-	def delete_rest(self, direction):
-		self.tab_deque = None
-		if direction > 0:
-			self.copy = self.line[self.pos:]
-			self.line = self.line[:self.pos]
-		else:
-			self.copy = self.line[:self.pos]
-			self.line = self.line[self.pos:]
-			self.pos = 0
-		self.on_line_change()
-
-	def paste(self):
-		if self.pos == len(self.line):
-			self.line += self.copy
-		else:
-			self.line = self.line[:self.pos] + self.copy + self.line[self.pos:]
-		self.pos += len(self.copy)
-		self.on_line_change()
-
-	def delete_word(self, backward=True):
-		if self.line:
-			self.tab_deque = None
-			if backward:
-				right_part = self.line[self.pos:]
-				i = self.pos - 2
-				while i >= 0 and re.match(r'[\w\d]', self.line[i], re.U):
-					i -= 1
-				self.copy = self.line[i + 1:self.pos]
-				self.line = self.line[:i + 1] + right_part
-				self.pos = i + 1
-			else:
-				left_part = self.line[:self.pos]
-				i = self.pos + 1
-				while i < len(self.line) and re.match(r'[\w\d]', self.line[i], re.U):
-					i += 1
-				self.copy = self.line[self.pos:i]
-				if i >= len(self.line):
-					self.line = left_part
-					self.pos = len(self.line)
-				else:
-					self.line = left_part + self.line[i:]
-					self.pos = len(left_part)
-			self.on_line_change()
-
-	def delete(self, mod):
-		self.tab_deque = None
-		if mod == -1 and self.pos == 0:
-			if not self.line:
-				self.close(trigger_cancel_function=False)
-			return
-		# Delete utf-char-wise
-		if self.fm.py3:
-			left_part = self.line[:self.pos + mod]
-			self.pos = len(left_part)
-			self.line = left_part + self.line[self.pos + 1:]
-		else:
-			uc = list(self.line.decode('utf-8', 'ignore'))
-			upos = len(self.line[:self.pos].decode('utf-8', 'ignore')) + mod
-			left_part = ''.join(uc[:upos]).encode('utf-8', 'ignore')
-			self.pos = len(left_part)
-			self.line = left_part + ''.join(uc[upos+1:]).encode('utf-8', 'ignore')
-		self.on_line_change()
-
-	def execute(self, cmd=None):
-		if self.question_queue and cmd is None:
-			question = self.question_queue[0]
-			answers = question[2]
-			if len(answers) >= 1:
-				self._answer_question(answers[0])
-			else:
-				self.question_queue.pop(0)
-			return
-
-		self.allow_close = True
-		self.fm.execute_console(self.line)
-		if self.allow_close:
-			self._close_command_prompt(trigger_cancel_function=False)
-
-	def _get_cmd(self, quiet=False):
-		try:
-			command_class = self._get_cmd_class()
-		except KeyError:
-			if not quiet:
-				error = "Command not found: `%s'" % self.line.split()[0]
-				self.fm.notify(error, bad=True)
-		except:
-			return None
-		else:
-			return command_class(self.line)
-
-	def _get_cmd_class(self):
-		return self.fm.commands.get_command(self.line.split()[0])
-
-	def _get_tab(self):
-		if ' ' in self.line:
-			cmd = self._get_cmd()
-			if cmd:
-				return cmd.tab()
-			else:
-				return None
-
-		return self.fm.commands.command_generator(self.line)
-
-	def tab(self, n=1):
-		if self.tab_deque is None:
-			tab_result = self._get_tab()
-
-			if isinstance(tab_result, str):
-				self.line = tab_result
-				self.pos = len(tab_result)
-				self.on_line_change()
-
-			elif tab_result == None:
-				pass
-
-			elif hasattr(tab_result, '__iter__'):
-				self.tab_deque = deque(tab_result)
-				self.tab_deque.appendleft(self.line)
-
-		if self.tab_deque is not None:
-			self.tab_deque.rotate(-n)
-			self.line = self.tab_deque[0]
-			self.pos = len(self.line)
-			self.on_line_change()
-
-	def on_line_change(self):
-		self.history_search_pattern = self.line
-		try:
-			cls = self._get_cmd_class()
-		except (KeyError, ValueError, IndexError):
-			pass
-		else:
-			cmd = cls(self.line)
-			if cmd and cmd.quick():
-				self.execute(cmd)
-
-	def ask(self, text, callback, choices=['y', 'n']):
-		"""
-		Open a question prompt with predefined choices
-
-		The "text" is displayed as the question text and should include a list
-		of possible keys that the user can type.  The "callback" is a function
-		that is called when the question is answered.  It only gets the answer
-		as an argument.  "choices" is a tuple of one-letter strings that can be
-		typed in by the user.  Every other input gets ignored, except <Enter>
-		and <ESC>.
-
-		The first choice is used when the user presses <Enter>, the second
-		choice is used when the user presses <ESC>.
-		"""
-		self.question_queue.append((text, callback, choices))
+    visible = False
+    last_cursor_mode = None
+    history_search_pattern = None
+    prompt = ':'
+    copy = ''
+    tab_deque = None
+    original_line = None
+    history = None
+    history_backup = None
+    override = None
+    allow_close = False
+    historypath = None
+    wait_for_command_input = False
+    unicode_buffer = ""
+
+    def __init__(self, win):
+        Widget.__init__(self, win)
+        self.clear()
+        self.history = History(self.settings.max_console_history_size)
+        # load history from files
+        if not ranger.arg.clean:
+            self.historypath = self.fm.confpath('history')
+            try:
+                f = open(self.historypath, 'r')
+            except:
+                pass
+            else:
+                for line in f:
+                    self.history.add(line[:-1])
+                f.close()
+        self.line = ""
+        self.history_backup = History(self.history)
+
+        # NOTE: the console is considered in the "question mode" when the
+        # question_queue is non-empty.  In that case, the console will draw the
+        # question instead of the regular console, and the input you give is
+        # used to answer the question instead of typing in commands.
+        #
+        # A question is a tuple of (question_string, callback_func,
+        # tuple_of_choices).  callback_func is a function that is called when
+        # the question is answered which gets the answer as an argument.
+        # tuple_of_choices looks like ('y', 'n').  Only one-letter-answers are
+        # currently supported.  Pressing enter uses the first choice whereas
+        # pressing ESC uses the second choice.
+        self.question_queue = []
+
+    def destroy(self):
+        # save history to files
+        if ranger.arg.clean or not self.settings.save_console_history:
+            return
+        if self.historypath:
+            try:
+                f = open(self.historypath, 'w')
+            except:
+                pass
+            else:
+                for entry in self.history_backup:
+                    try:
+                        f.write(entry + '\n')
+                    except UnicodeEncodeError:
+                        pass
+                f.close()
+
+    def draw(self):
+        self.win.erase()
+        if self.question_queue:
+            assert isinstance(self.question_queue[0], tuple)
+            assert len(self.question_queue[0]) == 3
+            self.addstr(0, 0, self.question_queue[0][0])
+            return
+
+        self.addstr(0, 0, self.prompt)
+        line = WideString(self.line)
+        overflow = -self.wid + len(self.prompt) + len(line) + 1
+        if overflow > 0: 
+            self.addstr(0, len(self.prompt), str(line[overflow:]))
+        else:
+            self.addstr(0, len(self.prompt), self.line)
+
+    def finalize(self):
+        move = self.fm.ui.win.move
+        if self.question_queue:
+            try:
+                move(self.y, len(self.question_queue[0][0]))
+            except:
+                pass
+        else:
+            try:
+                pos = uwid(self.line[0:self.pos]) + len(self.prompt)
+                move(self.y, self.x + min(self.wid-1, pos))
+            except:
+                pass
+
+    def open(self, string='', prompt=None, position=None):
+        if prompt is not None:
+            assert isinstance(prompt, str)
+            self.prompt = prompt
+        elif 'prompt' in self.__dict__:
+            del self.prompt
+
+        if self.last_cursor_mode is None:
+            try:
+                self.last_cursor_mode = curses.curs_set(1)
+            except:
+                pass
+        self.allow_close = False
+        self.tab_deque = None
+        self.unicode_buffer = ""
+        self.line = string
+        self.history_search_pattern = self.line
+        self.pos = len(string)
+        if position is not None:
+            self.pos = min(self.pos, position)
+        self.history_backup.fast_forward()
+        self.history = History(self.history_backup)
+        self.history.add('')
+        self.wait_for_command_input = True
+        return True
+
+    def close(self, trigger_cancel_function=True):
+        if self.question_queue:
+            question = self.question_queue[0]
+            answers = question[2]
+            if len(answers) >= 2:
+                self._answer_question(answers[1])
+        else:
+            self._close_command_prompt(trigger_cancel_function)
+
+    def _close_command_prompt(self, trigger_cancel_function=True):
+        if trigger_cancel_function:
+            cmd = self._get_cmd(quiet=True)
+            if cmd:
+                try:
+                    cmd.cancel()
+                except Exception as error:
+                    self.fm.notify(error)
+        if self.last_cursor_mode is not None:
+            try:
+                curses.curs_set(self.last_cursor_mode)
+            except:
+                pass
+            self.last_cursor_mode = None
+        self.fm.hide_console_info()
+        self.add_to_history()
+        self.tab_deque = None
+        self.clear()
+        self.__class__ = Console
+        self.wait_for_command_input = False
+
+    def clear(self):
+        self.pos = 0
+        self.line = ''
+
+    def press(self, key):
+        self.fm.ui.keymaps.use_keymap('console')
+        if not self.fm.ui.press(key):
+            self.type_key(key)
+
+    def _answer_question(self, answer):
+        if not self.question_queue:
+            return False
+        question = self.question_queue[0]
+        text, callback, answers = question
+        if answer in answers:
+            self.question_queue.pop(0)
+            callback(answer)
+            return True
+        return False
+
+    def type_key(self, key):
+        self.tab_deque = None
+
+        line = "" if self.question_queue else self.line
+        result = self._add_character(key, self.unicode_buffer, line, self.pos)
+        if result[1] == line:
+            # line didn't change, so we don't need to do anything, just update
+            # the unicode _buffer.
+            self.unicode_buffer = result[0]
+            return
+
+        if self.question_queue:
+            self.unicode_buffer, answer, self.pos = result
+            self._answer_question(answer)
+        else:
+            self.unicode_buffer, self.line, self.pos = result
+            self.on_line_change()
+
+    def _add_character(self, key, unicode_buffer, line, pos):
+        # Takes the pressed key, a string "unicode_buffer" containing a
+        # potentially incomplete unicode character, the current line and the
+        # position of the cursor inside the line.
+        # This function returns the new unicode buffer, the modified line and
+        # position.
+        if isinstance(key, int):
+            try:
+                key = chr(key)
+            except ValueError:
+                return unicode_buffer, line, pos
+
+        if self.fm.py3:
+            unicode_buffer += key
+            try:
+                decoded = unicode_buffer.encode("latin-1").decode("utf-8")
+            except UnicodeDecodeError:
+                return unicode_buffer, line, pos
+            except UnicodeEncodeError:
+                return unicode_buffer, line, pos
+            else:
+                unicode_buffer = ""
+                if pos == len(line):
+                    line += decoded
+                else:
+                    line = line[:pos] + decoded + line[pos:]
+                pos += len(decoded)
+        else:
+            if pos == len(line):
+                line += key
+            else:
+                line = line[:pos] + key + line[pos:]
+            pos += len(key)
+        return unicode_buffer, line, pos
+
+    def history_move(self, n):
+        try:
+            current = self.history.current()
+        except HistoryEmptyException:
+            pass
+        else:
+            if self.line != current and self.line != self.history.top():
+                self.history.modify(self.line)
+            if self.history_search_pattern:
+                self.history.search(self.history_search_pattern, n)
+            else:
+                self.history.move(n)
+            current = self.history.current()
+            if self.line != current:
+                self.line = self.history.current()
+                self.pos = len(self.line)
+
+    def add_to_history(self):
+        self.history_backup.fast_forward()
+        self.history_backup.add(self.line)
+        self.history = History(self.history_backup)
+
+    def move(self, **keywords):
+        direction = Direction(keywords)
+        if direction.horizontal():
+            # Ensure that the pointer is moved utf-char-wise
+            if self.fm.py3:
+                self.pos = direction.move(
+                        direction=direction.right(),
+                        minimum=0,
+                        maximum=len(self.line) + 1,
+                        current=self.pos)
+            else:
+                if self.fm.py3:
+                    uc = list(self.line)
+                    upos = len(self.line[:self.pos])
+                else:
+                    uc = list(self.line.decode('utf-8', 'ignore'))
+                    upos = len(self.line[:self.pos].decode('utf-8', 'ignore'))
+                newupos = direction.move(
+                        direction=direction.right(),
+                        minimum=0,
+                        maximum=len(uc) + 1,
+                        current=upos)
+                self.pos = len(''.join(uc[:newupos]).encode('utf-8', 'ignore'))
+
+    def delete_rest(self, direction):
+        self.tab_deque = None
+        if direction > 0:
+            self.copy = self.line[self.pos:]
+            self.line = self.line[:self.pos]
+        else:
+            self.copy = self.line[:self.pos]
+            self.line = self.line[self.pos:]
+            self.pos = 0
+        self.on_line_change()
+
+    def paste(self):
+        if self.pos == len(self.line):
+            self.line += self.copy
+        else:
+            self.line = self.line[:self.pos] + self.copy + self.line[self.pos:]
+        self.pos += len(self.copy)
+        self.on_line_change()
+
+    def delete_word(self, backward=True):
+        if self.line:
+            self.tab_deque = None
+            if backward:
+                right_part = self.line[self.pos:]
+                i = self.pos - 2
+                while i >= 0 and re.match(r'[\w\d]', self.line[i], re.U):
+                    i -= 1
+                self.copy = self.line[i + 1:self.pos]
+                self.line = self.line[:i + 1] + right_part
+                self.pos = i + 1
+            else:
+                left_part = self.line[:self.pos]
+                i = self.pos + 1
+                while i < len(self.line) and re.match(r'[\w\d]', self.line[i], re.U):
+                    i += 1
+                self.copy = self.line[self.pos:i]
+                if i >= len(self.line):
+                    self.line = left_part
+                    self.pos = len(self.line)
+                else:
+                    self.line = left_part + self.line[i:]
+                    self.pos = len(left_part)
+            self.on_line_change()
+
+    def delete(self, mod):
+        self.tab_deque = None
+        if mod == -1 and self.pos == 0:
+            if not self.line:
+                self.close(trigger_cancel_function=False)
+            return
+        # Delete utf-char-wise
+        if self.fm.py3:
+            left_part = self.line[:self.pos + mod]
+            self.pos = len(left_part)
+            self.line = left_part + self.line[self.pos + 1:]
+        else:
+            uc = list(self.line.decode('utf-8', 'ignore'))
+            upos = len(self.line[:self.pos].decode('utf-8', 'ignore')) + mod
+            left_part = ''.join(uc[:upos]).encode('utf-8', 'ignore')
+            self.pos = len(left_part)
+            self.line = left_part + ''.join(uc[upos+1:]).encode('utf-8', 'ignore')
+        self.on_line_change()
+
+    def execute(self, cmd=None):
+        if self.question_queue and cmd is None:
+            question = self.question_queue[0]
+            answers = question[2]
+            if len(answers) >= 1:
+                self._answer_question(answers[0])
+            else:
+                self.question_queue.pop(0)
+            return
+
+        self.allow_close = True
+        self.fm.execute_console(self.line)
+        if self.allow_close:
+            self._close_command_prompt(trigger_cancel_function=False)
+
+    def _get_cmd(self, quiet=False):
+        try:
+            command_class = self._get_cmd_class()
+        except KeyError:
+            if not quiet:
+                error = "Command not found: `%s'" % self.line.split()[0]
+                self.fm.notify(error, bad=True)
+        except:
+            return None
+        else:
+            return command_class(self.line)
+
+    def _get_cmd_class(self):
+        return self.fm.commands.get_command(self.line.split()[0])
+
+    def _get_tab(self):
+        if ' ' in self.line:
+            cmd = self._get_cmd()
+            if cmd:
+                return cmd.tab()
+            else:
+                return None
+
+        return self.fm.commands.command_generator(self.line)
+
+    def tab(self, n=1):
+        if self.tab_deque is None:
+            tab_result = self._get_tab()
+
+            if isinstance(tab_result, str):
+                self.line = tab_result
+                self.pos = len(tab_result)
+                self.on_line_change()
+
+            elif tab_result == None:
+                pass
+
+            elif hasattr(tab_result, '__iter__'):
+                self.tab_deque = deque(tab_result)
+                self.tab_deque.appendleft(self.line)
+
+        if self.tab_deque is not None:
+            self.tab_deque.rotate(-n)
+            self.line = self.tab_deque[0]
+            self.pos = len(self.line)
+            self.on_line_change()
+
+    def on_line_change(self):
+        self.history_search_pattern = self.line
+        try:
+            cls = self._get_cmd_class()
+        except (KeyError, ValueError, IndexError):
+            pass
+        else:
+            cmd = cls(self.line)
+            if cmd and cmd.quick():
+                self.execute(cmd)
+
+    def ask(self, text, callback, choices=['y', 'n']):
+        """
+        Open a question prompt with predefined choices
+
+        The "text" is displayed as the question text and should include a list
+        of possible keys that the user can type.  The "callback" is a function
+        that is called when the question is answered.  It only gets the answer
+        as an argument.  "choices" is a tuple of one-letter strings that can be
+        typed in by the user.  Every other input gets ignored, except <Enter>
+        and <ESC>.
+
+        The first choice is used when the user presses <Enter>, the second
+        choice is used when the user presses <ESC>.
+        """
+        self.question_queue.append((text, callback, choices))