def test_history(self): hist = History(3) for i in range(6): hist.add(i) self.assertEqual([3,4,5], list(hist)) hist.back() self.assertEqual(4, hist.current()) self.assertEqual([3,4], list(hist._left())) self.assertEqual(5, hist.top()) hist.back() self.assertEqual(3, hist.current()) self.assertEqual([3], list(hist._left())) # no change if current == bottom self.assertEqual(hist.current(), hist.bottom()) last = hist.current() hist.back() self.assertEqual(hist.current(), last) self.assertEqual(5, hist.top()) hist.forward() hist.forward() self.assertEqual(5, hist.current()) self.assertEqual([3,4,5], list(hist._left())) self.assertEqual(3, hist.bottom()) hist.add(6) self.assertEqual(4, hist.bottom()) self.assertEqual([4,5,6], list(hist._left())) hist.back() hist.fast_forward() self.assertEqual([4,5,6], list(hist._left())) hist.back() hist.back() hist.fast_forward() self.assertEqual([4,5,6], list(hist._left())) hist.back() hist.back() hist.back() hist.fast_forward() self.assertEqual([4,5,6], list(hist._left())) hist.back() hist.back() hist.back() hist.back() hist.fast_forward() self.assertEqual([4,5,6], list(hist._left()))
def test_history(self): hist = History(3) for i in range(6): hist.add(i) self.assertEqual([3, 4, 5], list(hist)) hist.back() self.assertEqual(4, hist.current()) self.assertEqual([3, 4], list(hist._left())) self.assertEqual(5, hist.top()) hist.back() self.assertEqual(3, hist.current()) self.assertEqual([3], list(hist._left())) # no change if current == bottom self.assertEqual(hist.current(), hist.bottom()) last = hist.current() hist.back() self.assertEqual(hist.current(), last) self.assertEqual(5, hist.top()) hist.forward() hist.forward() self.assertEqual(5, hist.current()) self.assertEqual([3, 4, 5], list(hist._left())) self.assertEqual(3, hist.bottom()) hist.add(6) self.assertEqual(4, hist.bottom()) self.assertEqual([4, 5, 6], list(hist._left())) hist.back() hist.fast_forward() self.assertEqual([4, 5, 6], list(hist._left())) hist.back() hist.back() hist.fast_forward() self.assertEqual([4, 5, 6], list(hist._left())) hist.back() hist.back() hist.back() hist.fast_forward() self.assertEqual([4, 5, 6], list(hist._left())) hist.back() hist.back() hist.back() hist.back() hist.fast_forward() self.assertEqual([4, 5, 6], list(hist._left()))
class Environment(SettingsAware, SignalDispatcher): """ A collection of data which is relevant for more than one class. """ cwd = None # current directory copy = None cmd = None cut = None termsize = None history = None directories = None last_search = None pathway = None path = None keybuffer = None keymanager = None def __init__(self, path): SignalDispatcher.__init__(self) self.path = abspath(expanduser(path)) self._cf = None self.pathway = () self.directories = {} self.keybuffer = KeyBuffer(None, None) self.keymanager = KeyManager(self.keybuffer, ALLOWED_CONTEXTS) self.copy = set() self.history = History(self.settings.max_history_size, unique=False) try: self.username = pwd.getpwuid(os.geteuid()).pw_name except: self.username = "******" + str(os.geteuid()) self.hostname = socket.gethostname() self.home_path = os.path.expanduser("~") self.signal_bind("move", self._set_cf_from_signal, priority=0.1, weak=True) def _set_cf_from_signal(self, signal): self._cf = signal.new def _set_cf(self, value): if value is not self._cf: previous = self._cf self.signal_emit("move", previous=previous, new=value) def _get_cf(self): return self._cf cf = property(_get_cf, _set_cf) def key_append(self, key): """Append a key to the keybuffer""" # special keys: if key == curses.KEY_RESIZE: self.keybuffer.clear() self.keybuffer.add(key) def key_clear(self): """Clear the keybuffer""" self.keybuffer.clear() def at_level(self, level): """ Returns the FileSystemObject at the given level. level >0 => previews level 0 => current file/directory level <0 => parent directories """ if level <= 0: try: return self.pathway[level - 1] except IndexError: return None else: directory = self.cf for i in range(level - 1): if directory is None: return None if directory.is_directory: directory = directory.pointed_obj else: return None try: return self.directories[directory.path] except AttributeError: return None except KeyError: return directory def garbage_collect(self, age): """Delete unused directory objects""" for key in tuple(self.directories): value = self.directories[key] if value.is_older_than(age) and not value in self.pathway: del self.directories[key] if value.is_directory: value.files = None def get_selection(self): if self.cwd: return self.cwd.get_selection() return set() def get_directory(self, path): """Get the directory object at the given path""" path = abspath(path) try: return self.directories[path] except KeyError: obj = Directory(path) self.directories[path] = obj return obj def get_free_space(self, path): stat = os.statvfs(path) return stat.f_bavail * stat.f_bsize def assign_cursor_positions_for_subdirs(self): """Assign correct cursor positions for subdirectories""" last_path = None for path in reversed(self.pathway): if last_path is None: last_path = path continue path.move_to_obj(last_path) last_path = path def ensure_correct_pointer(self): if self.cwd: self.cwd.correct_pointer() def history_go(self, relative): """Move relative in history""" if self.history: self.history.move(relative).go(history=False) def enter_dir(self, path, history=True): """Enter given path""" if path is None: return path = str(path) previous = self.cwd # get the absolute path path = normpath(join(self.path, expanduser(path))) if not isdir(path): return False new_cwd = self.get_directory(path) try: os.chdir(path) except: return True self.path = path self.cwd = new_cwd self.cwd.load_content_if_outdated() # build the pathway, a tuple of directory objects which lie # on the path to the current directory. if path == "/": self.pathway = (self.get_directory("/"),) else: pathway = [] currentpath = "/" for dir in path.split("/"): currentpath = join(currentpath, dir) pathway.append(self.get_directory(currentpath)) self.pathway = tuple(pathway) self.assign_cursor_positions_for_subdirs() # set the current file. self.cwd.sort_directories_first = self.settings.sort_directories_first self.cwd.sort_reverse = self.settings.sort_reverse self.cwd.sort_if_outdated() self.cf = self.cwd.pointed_obj if history: self.history.add(new_cwd) self.signal_emit("cd", previous=previous, new=self.cwd) return True
class Environment(SettingsAware, SignalDispatcher): """ A collection of data which is relevant for more than one class. """ cwd = None # current directory copy = None cmd = None cut = None termsize = None history = None directories = None last_search = None pathway = None path = None keybuffer = None keymanager = None def __init__(self, path): SignalDispatcher.__init__(self) self.path = abspath(expanduser(path)) self._cf = None self.pathway = () self.directories = {} self.keybuffer = KeyBuffer(None, None) self.keymanager = KeyManager(self.keybuffer, ALLOWED_CONTEXTS) self.copy = set() self.history = History(self.settings.max_history_size, unique=False) try: self.username = pwd.getpwuid(os.geteuid()).pw_name except: self.username = '******' + str(os.geteuid()) self.hostname = socket.gethostname() self.home_path = os.path.expanduser('~') self.signal_bind('move', self._set_cf_from_signal, priority=0.1, weak=True) def _set_cf_from_signal(self, signal): self._cf = signal.new def _set_cf(self, value): if value is not self._cf: previous = self._cf self.signal_emit('move', previous=previous, new=value) def _get_cf(self): return self._cf cf = property(_get_cf, _set_cf) def key_append(self, key): """Append a key to the keybuffer""" # special keys: if key == curses.KEY_RESIZE: self.keybuffer.clear() self.keybuffer.add(key) def key_clear(self): """Clear the keybuffer""" self.keybuffer.clear() def at_level(self, level): """ Returns the FileSystemObject at the given level. level >0 => previews level 0 => current file/directory level <0 => parent directories """ if level <= 0: try: return self.pathway[level - 1] except IndexError: return None else: directory = self.cf for i in range(level - 1): if directory is None: return None if directory.is_directory: directory = directory.pointed_obj else: return None try: return self.directories[directory.path] except AttributeError: return None except KeyError: return directory def garbage_collect(self, age): """Delete unused directory objects""" for key in tuple(self.directories): value = self.directories[key] if age == -1 or \ (value.is_older_than(age) and not value in self.pathway): del self.directories[key] if value.is_directory: value.files = None self.settings.signal_garbage_collect() self.signal_garbage_collect() def get_selection(self): if self.cwd: return self.cwd.get_selection() return set() def get_directory(self, path): """Get the directory object at the given path""" path = abspath(path) try: return self.directories[path] except KeyError: obj = Directory(path) self.directories[path] = obj return obj def get_free_space(self, path): stat = os.statvfs(path) return stat.f_bavail * stat.f_bsize def assign_cursor_positions_for_subdirs(self): """Assign correct cursor positions for subdirectories""" last_path = None for path in reversed(self.pathway): if last_path is None: last_path = path continue path.move_to_obj(last_path) last_path = path def ensure_correct_pointer(self): if self.cwd: self.cwd.correct_pointer() def history_go(self, relative): """Move relative in history""" if self.history: self.history.move(relative).go(history=False) def enter_dir(self, path, history=True): """Enter given path""" if path is None: return path = str(path) previous = self.cwd # get the absolute path path = normpath(join(self.path, expanduser(path))) if not isdir(path): return False new_cwd = self.get_directory(path) try: os.chdir(path) except: return True self.path = path self.cwd = new_cwd self.cwd.load_content_if_outdated() # build the pathway, a tuple of directory objects which lie # on the path to the current directory. if path == '/': self.pathway = (self.get_directory('/'), ) else: pathway = [] currentpath = '/' for dir in path.split('/'): currentpath = join(currentpath, dir) pathway.append(self.get_directory(currentpath)) self.pathway = tuple(pathway) self.assign_cursor_positions_for_subdirs() # set the current file. self.cwd.sort_directories_first = self.settings.sort_directories_first self.cwd.sort_reverse = self.settings.sort_reverse self.cwd.sort_if_outdated() self.cf = self.cwd.pointed_obj if history: self.history.add(new_cwd) self.signal_emit('cd', previous=previous, new=self.cwd) return True
class Console(Widget): visible = False last_cursor_mode = None history_search_pattern = None prompt = ':' copy = '' tab_deque = None original_line = None history = None override = None allow_close = False historypath = None 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() 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: f.write(entry + '\n') f.close() def draw(self): self.win.erase() self.addstr(0, 0, self.prompt) if self.fm.py3: overflow = -self.wid + len(self.prompt) + len(self.line) + 1 else: overflow = -self.wid + len(self.prompt) + uwid(self.line) + 1 if overflow > 0: #XXX: cut uft-char-wise, consider width self.addstr(self.line[overflow:]) else: self.addstr(self.line) def finalize(self): try: if self.fm.py3: xpos = sum(utf_char_width_(ord(c)) for c in self.line[0:self.pos]) \ + len(self.prompt) else: xpos = uwid(self.line[0:self.pos]) + len(self.prompt) self.fm.ui.win.move(self.y, self.x + min(self.wid - 1, xpos)) 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.focused = True self.visible = True 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.fast_forward() self.history.add('') return True def close(self): if self.last_cursor_mode is not None: try: curses.curs_set(self.last_cursor_mode) except: pass self.last_cursor_mode = None self.add_to_history() self.tab_deque = None self.clear() self.__class__ = Console self.focused = False self.visible = False if hasattr(self, 'on_close'): self.on_close() def clear(self): self.pos = 0 self.line = '' def press(self, key): self.env.keymanager.use_context('console') self.env.key_append(key) kbuf = self.env.keybuffer cmd = kbuf.command if kbuf.failure: kbuf.clear() return elif not cmd: return self.env.cmd = cmd if cmd.function: try: cmd.function(CommandArgs.from_widget(self)) except Exception as error: self.fm.notify(error) if kbuf.done: kbuf.clear() else: kbuf.clear() def type_key(self, key): self.tab_deque = None if isinstance(key, int): try: key = chr(key) except ValueError: return if self.fm.py3: self.unicode_buffer += key try: decoded = self.unicode_buffer.encode("latin-1").decode("utf-8") except UnicodeDecodeError: pass else: self.unicode_buffer = "" if self.pos == len(self.line): self.line += decoded else: pos = self.pos self.line = self.line[:pos] + decoded + self.line[pos:] self.pos += len(decoded) else: if self.pos == len(self.line): self.line += key else: self.line = self.line[:self.pos] + key + self.line[self.pos:] self.pos += len(key) self.on_line_change() 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.fast_forward() self.history.modify(self.line, unique=True) 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: uc = uchars(self.line) upos = len(uchars(self.line[:self.pos])) newupos = direction.move(direction=direction.right(), minimum=0, maximum=len(uc) + 1, current=upos) self.pos = len(''.join(uc[:newupos])) 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() 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 = uchars(self.line) upos = len(uchars(self.line[:self.pos])) + mod left_part = ''.join(uc[:upos]) self.pos = len(left_part) self.line = left_part + ''.join(uc[upos + 1:]) self.on_line_change() def execute(self, cmd=None): self.allow_close = True if cmd is None: cmd = self._get_cmd() if cmd: try: cmd.execute() except Exception as error: self.fm.notify(error) if self.allow_close: self.close() def _get_cmd(self): try: command_class = self._get_cmd_class() except KeyError: self.fm.notify("Invalid command! Press ? for help.", 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)
class Console(Widget): visible = False last_cursor_mode = None history_search_pattern = None prompt = ":" copy = "" tab_deque = None original_line = None history = None override = None allow_close = False historypath = None 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() 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: f.write(entry + "\n") f.close() def draw(self): self.win.erase() self.addstr(0, 0, self.prompt) if self.fm.py3: overflow = -self.wid + len(self.prompt) + len(self.line) + 1 else: overflow = -self.wid + len(self.prompt) + uwid(self.line) + 1 if overflow > 0: # XXX: cut uft-char-wise, consider width self.addstr(self.line[overflow:]) else: self.addstr(self.line) def finalize(self): try: if self.fm.py3: xpos = sum(utf_char_width_(ord(c)) for c in self.line[0 : self.pos]) + len(self.prompt) else: xpos = uwid(self.line[0 : self.pos]) + len(self.prompt) self.fm.ui.win.move(self.y, self.x + min(self.wid - 1, xpos)) 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.focused = True self.visible = True 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.fast_forward() self.history.add("") return True def close(self): if self.last_cursor_mode is not None: try: curses.curs_set(self.last_cursor_mode) except: pass self.last_cursor_mode = None self.add_to_history() self.tab_deque = None self.clear() self.__class__ = Console self.focused = False self.visible = False if hasattr(self, "on_close"): self.on_close() def clear(self): self.pos = 0 self.line = "" def press(self, key): self.env.keymanager.use_context("console") self.env.key_append(key) kbuf = self.env.keybuffer cmd = kbuf.command if kbuf.failure: kbuf.clear() return elif not cmd: return self.env.cmd = cmd if cmd.function: try: cmd.function(CommandArgs.from_widget(self)) except Exception as error: self.fm.notify(error) if kbuf.done: kbuf.clear() else: kbuf.clear() def type_key(self, key): self.tab_deque = None if isinstance(key, int): try: key = chr(key) except ValueError: return if self.fm.py3: self.unicode_buffer += key try: decoded = self.unicode_buffer.encode("latin-1").decode("utf-8") except UnicodeDecodeError: pass else: self.unicode_buffer = "" if self.pos == len(self.line): self.line += decoded else: pos = self.pos self.line = self.line[:pos] + decoded + self.line[pos:] self.pos += len(decoded) else: if self.pos == len(self.line): self.line += key else: self.line = self.line[: self.pos] + key + self.line[self.pos :] self.pos += len(key) self.on_line_change() 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.fast_forward() self.history.modify(self.line, unique=True) 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: uc = uchars(self.line) upos = len(uchars(self.line[: self.pos])) newupos = direction.move(direction=direction.right(), minimum=0, maximum=len(uc) + 1, current=upos) self.pos = len("".join(uc[:newupos])) 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): if self.line: self.tab_deque = None i = len(self.line) - 2 while i >= 0 and re.match(r"[\w\d]", self.line[i], re.U): i -= 1 self.copy = self.line[i + 1 :] self.line = self.line[: i + 1] self.pos = len(self.line) 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() 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 = uchars(self.line) upos = len(uchars(self.line[: self.pos])) + mod left_part = "".join(uc[:upos]) self.pos = len(left_part) self.line = left_part + "".join(uc[upos + 1 :]) self.on_line_change() def execute(self, cmd=None): self.allow_close = True if cmd is None: cmd = self._get_cmd() if cmd: try: cmd.execute() except Exception as error: self.fm.notify(error) if self.allow_close: self.close() def _get_cmd(self): try: command_class = self._get_cmd_class() except KeyError: self.fm.notify("Invalid command! Press ? for help.", 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)