diff options
-rw-r--r-- | ranger/api/keys.py | 4 | ||||
-rw-r--r-- | ranger/core/actions.py | 123 | ||||
-rw-r--r-- | ranger/defaults/commands.py | 4 | ||||
-rw-r--r-- | ranger/defaults/keys.py | 74 | ||||
-rw-r--r-- | ranger/ext/accumulator.py | 42 | ||||
-rw-r--r-- | ranger/ext/direction.py | 135 | ||||
-rw-r--r-- | ranger/ext/move.py | 23 | ||||
-rw-r--r-- | ranger/fsobject/directory.py | 2 | ||||
-rw-r--r-- | ranger/gui/ui.py | 2 | ||||
-rw-r--r-- | ranger/gui/widgets/browsercolumn.py | 12 | ||||
-rw-r--r-- | ranger/gui/widgets/console.py | 21 | ||||
-rw-r--r-- | ranger/gui/widgets/pager.py | 76 | ||||
-rw-r--r-- | ranger/gui/widgets/taskview.py | 4 | ||||
-rw-r--r-- | test/tc_direction.py | 81 |
14 files changed, 403 insertions, 200 deletions
diff --git a/ranger/api/keys.py b/ranger/api/keys.py index 5b833c34..00479b0d 100644 --- a/ranger/api/keys.py +++ b/ranger/api/keys.py @@ -54,9 +54,9 @@ class Wrapper(object): # If the method has an argument named "narg", pressing a number before # the key will pass that number as the narg argument. If you want the # same behaviour in a custom lambda function, you can write: -# bind('gg', fm.move_pointer(absolute=0)) +# bind('gg', fm.move(to=0)) # as: -# bind('gg', lambda arg: narg(arg.n, arg.fm.move_pointer, absolute=0)) +# bind('gg', lambda arg: narg(arg.n, arg.fm.move, to=0)) fm = Wrapper('fm') wdg = Wrapper('wdg') diff --git a/ranger/core/actions.py b/ranger/core/actions.py index 9bef8bdc..38744d94 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -18,24 +18,52 @@ import shutil from inspect import cleandoc import ranger -from ranger.shared import EnvironmentAware, SettingsAware +from ranger.ext.direction import Direction +from ranger.shared import FileManagerAware, EnvironmentAware, SettingsAware from ranger import fsobject from ranger.gui.widgets import console_mode as cmode from ranger.fsobject import File -class Actions(EnvironmentAware, SettingsAware): +class Actions(FileManagerAware, EnvironmentAware, SettingsAware): search_method = 'ctime' search_forward = False # -------------------------- # -- Backwards Compatibility # -------------------------- + # All methods defined here are just for backwards compatibility, + # allowing old configuration files to work with newer versions. + # You can delete them and they should change nothing if you use + # an up-to-date configuration file. def dummy(self, *args, **keywords): """For backwards compatibility only.""" handle_mouse = resize = dummy + def move_left(self, narg=1): + """Enter the parent directory""" + self.move(left=1, narg=narg) + + def move_right(self, narg=None): + """Enter the current directory or execute the current file""" + self.move(right=1, narg=narg) + + def move_pointer(self, relative=0, absolute=None, narg=None): + """Move the pointer down by <relative> or to <absolute>""" + dct = dict(down=relative, narg=narg) + if absolute is not None: + dct['to'] = absolute + self.move(**dct) + + def move_pointer_by_pages(self, relative): + """Move the pointer down by <relative> pages""" + self.move(down=relative, pages=True) + + def move_pointer_by_percentage(self, relative=0, absolute=None, narg=None): + """Move the pointer down to <absolute>%""" + self.move(to=absolute, percentage=True, narg=narg) + # -------------------------- # -- Basic Commands # -------------------------- @@ -95,48 +123,51 @@ class Actions(EnvironmentAware, SettingsAware): # -- Moving Around # -------------------------- - def move_left(self, narg=1): - """Enter the parent directory""" - try: - directory = os.path.join(*(['..'] * narg)) - except: - return - self.env.enter_dir(directory) - - def move_right(self, mode=0, narg=None): - """Enter the current directory or execute the current file""" - cf = self.env.cf - sel = self.env.get_selection() - - if isinstance(narg, int): - mode = narg - if not self.env.enter_dir(cf): - if sel: - if self.execute_file(sel, mode=mode) is False: - self.open_console(cmode.OPEN_QUICK) - - def move_pointer(self, relative = 0, absolute = None, narg=None): - """Move the pointer down by <relative> or to <absolute>""" - self.env.cwd.move(relative=relative, - absolute=absolute, narg=narg) + def move(self, narg=None, **kw): + """ + A universal movement method. - def move_pointer_by_pages(self, relative): - """Move the pointer down by <relative> pages""" - self.env.cwd.move(relative=int(relative * self.env.termsize[0])) + Accepts these parameters: + (int) down, (int) up, (int) left, (int) right, (int) to, + (bool) absolute, (bool) relative, (bool) pages, + (bool) percentage - def move_pointer_by_percentage(self, relative=0, absolute=None, narg=None): - """Move the pointer down by <relative>% or to <absolute>%""" - try: - factor = len(self.env.cwd) / 100.0 - except: - return + to=X is translated to down=X, absolute=True - if narg is not None: - absolute = narg + Example: + self.move(down=4, pages=True) # moves down by 4 pages. + self.move(to=2, pages=True) # moves to page 2. + self.move(to=1, percentage=True) # moves to 80% + """ + direction = Direction(kw) + if 'left' in direction: + steps = direction.left() + if narg is not None: + steps *= narg + try: + directory = os.path.join(*(['..'] * steps)) + except: + return + self.env.enter_dir(directory) + + elif 'right' in direction: + mode = 0 + if narg is not None: + mode = narg + cf = self.env.cf + selection = self.env.get_selection() + if not self.env.enter_dir(cf) and selection: + if self.execute_file(selection, mode=mode) is False: + self.open_console(cmode.OPEN_QUICK) - self.env.cwd.move( - relative=int(relative * factor), - absolute=int(absolute * factor)) + elif direction.vertical(): + newpos = direction.move( + direction=direction.down(), + override=narg, + maximum=len(self.env.cwd), + current=self.env.cwd.pointer, + pagesize=self.ui.browser.hei) + self.env.cwd.move(to=newpos) def history_go(self, relative): """Move back and forth in the history""" @@ -168,16 +199,16 @@ class Actions(EnvironmentAware, SettingsAware): self.enter_dir(cf.path) elif cwd.pointer >= len(cwd) - 1: while True: - self.enter_dir('..') + self.move(left=1) cwd = self.env.cwd if cwd.pointer < len(cwd) - 1: break if cwd.path == '/': break - self.move_pointer(1) + self.move(down=1) self.traverse() else: - self.move_pointer(1) + self.move(down=1) self.traverse() # -------------------------- @@ -258,7 +289,7 @@ class Actions(EnvironmentAware, SettingsAware): cwd.mark_item(item, val) if movedown: - self.move_pointer(relative=narg) + self.move(down=narg) if hasattr(self.ui, 'redraw_main_column'): self.ui.redraw_main_column() @@ -334,7 +365,7 @@ class Actions(EnvironmentAware, SettingsAware): if movedown is None: movedown = len(sel) == 1 if movedown: - self.move_pointer(relative=1) + self.move(down=1) if hasattr(self.ui, 'redraw_main_column'): self.ui.redraw_main_column() @@ -351,7 +382,7 @@ class Actions(EnvironmentAware, SettingsAware): if movedown is None: movedown = len(sel) == 1 if movedown: - self.move_pointer(relative=1) + self.move(down=1) if hasattr(self.ui, 'redraw_main_column'): self.ui.redraw_main_column() diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py index e69dcf90..03fa1634 100644 --- a/ranger/defaults/commands.py +++ b/ranger/defaults/commands.py @@ -202,7 +202,7 @@ class find(Command): self.fm.search_method = 'search' if self.count == 1: - self.fm.move_right() + self.fm.move(right=1) self.fm.block_input(0.5) def quick_open(self): @@ -227,7 +227,7 @@ class find(Command): if arg in filename: self.count += 1 if self.count == 1: - cwd.move(absolute=(cwd.pointer + i) % len(cwd.files)) + cwd.move(to=(cwd.pointer + i) % len(cwd.files)) self.fm.env.cf = cwd.pointed_obj if self.count > 1: return False diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py index 0c805cb6..136fe11d 100644 --- a/ranger/defaults/keys.py +++ b/ranger/defaults/keys.py @@ -53,26 +53,32 @@ def _vimlike_aliases(map): alias(KEY_HOME, 'gg') alias(KEY_END, 'G') + +def _emacs_aliases(map): + alias = map.alias + alias(KEY_LEFT, ctrl('b')) + alias(KEY_RIGHT, ctrl('f')) + alias(KEY_HOME, ctrl('a')) + alias(KEY_END, ctrl('e')) + alias(KEY_DC, ctrl('d')) + alias(DEL, ctrl('h')) + + def initialize_commands(map): """Initialize the commands for the main user interface""" # -------------------------------------------------------- movement _vimlike_aliases(map) - map.alias(KEY_LEFT, KEY_BACKSPACE, DEL) - - map(KEY_DOWN, fm.move_pointer(relative=1)) - map(KEY_UP, fm.move_pointer(relative=-1)) - map(KEY_RIGHT, KEY_ENTER, ctrl('j'), fm.move_right()) - map(KEY_LEFT, KEY_BACKSPACE, DEL, fm.move_left(1)) + _basic_movement(map) - map(KEY_HOME, fm.move_pointer(absolute=0)) - map(KEY_END, fm.move_pointer(absolute=-1)) + map.alias(KEY_LEFT, KEY_BACKSPACE, DEL) + map.alias(KEY_RIGHT, KEY_ENTER, ctrl('j')) - map('%', fm.move_pointer_by_percentage(absolute=50)) - map(KEY_NPAGE, ctrl('f'), fm.move_pointer_by_pages(1)) - map(KEY_PPAGE, ctrl('b'), fm.move_pointer_by_pages(-1)) - map(ctrl('d'), 'J', fm.move_pointer_by_pages(0.5)) - map(ctrl('u'), 'K', fm.move_pointer_by_pages(-0.5)) + map('%', fm.move(to=50, percentage=True)) + map(KEY_NPAGE, ctrl('f'), fm.move(down=1, pages=True)) + map(KEY_PPAGE, ctrl('b'), fm.move(up=1, pages=True)) + map(ctrl('d'), 'J', fm.move(down=0.5, pages=True)) + map(ctrl('u'), 'K', fm.move(up=0.5, pages=True)) def move_parent(n): def fnc(arg): @@ -233,27 +239,24 @@ def initialize_commands(map): def initialize_console_commands(map): """Initialize the commands for the console widget only""" + _basic_movement(map) + _emacs_aliases(map) + # -------------------------------------------------------- movement map(KEY_UP, wdg.history_move(-1)) map(KEY_DOWN, wdg.history_move(1)) - - map(ctrl('b'), KEY_LEFT, wdg.move(relative = -1)) - map(ctrl('f'), KEY_RIGHT, wdg.move(relative = 1)) - map(ctrl('a'), KEY_HOME, wdg.move(absolute = 0)) - map(ctrl('e'), KEY_END, wdg.move(absolute = -1)) + map(KEY_HOME, wdg.move(right=0, absolute=True)) + map(KEY_END, wdg.move(right=-1, absolute=True)) # ----------------------------------------- deleting / pasting text - map(ctrl('d'), KEY_DC, wdg.delete(0)) - map(ctrl('h'), KEY_BACKSPACE, DEL, wdg.delete(-1)) + map(KEY_DC, wdg.delete(0)) + map(KEY_BACKSPACE, DEL, wdg.delete(-1)) map(ctrl('w'), wdg.delete_word()) map(ctrl('k'), wdg.delete_rest(1)) map(ctrl('u'), wdg.delete_rest(-1)) map(ctrl('y'), wdg.paste()) # ------------------------------------------------ system functions - _system_functions(map) - map.unbind('Q') # we don't want to quit with Q in the console... - map(KEY_F1, lambda arg: arg.fm.display_command_help(arg.wdg)) map(ctrl('c'), ESC, wdg.close()) map(ctrl('j'), KEY_ENTER, wdg.execute()) @@ -293,19 +296,20 @@ def initialize_embedded_pager_commands(map): map('q', 'i', ESC, lambda arg: arg.fm.ui.close_embedded_pager()) map.rebuild_paths() + def _base_pager_commands(map): _basic_movement(map) _vimlike_aliases(map) _system_functions(map) # -------------------------------------------------------- movement - map(KEY_LEFT, wdg.move_horizontal(relative=-4)) - map(KEY_RIGHT, wdg.move_horizontal(relative=4)) - map(KEY_NPAGE, ctrl('f'), wdg.move(relative=1, pages=True)) - map(KEY_PPAGE, ctrl('b'), wdg.move(relative=-1, pages=True)) - map(ctrl('d'), wdg.move(relative=0.5, pages=True)) - map(ctrl('u'), wdg.move(relative=-0.5, pages=True)) - map(' ', wdg.move(relative=0.8, pages=True)) + map(KEY_LEFT, wdg.move(left=4)) + map(KEY_RIGHT, wdg.move(right=4)) + map(KEY_NPAGE, ctrl('f'), wdg.move(down=1, pages=True)) + map(KEY_PPAGE, ctrl('b'), wdg.move(up=1, pages=True)) + map(ctrl('d'), wdg.move(down=0.5, pages=True)) + map(ctrl('u'), wdg.move(up=0.5, pages=True)) + map(' ', wdg.move(down=0.8, pages=True)) # ---------------------------------------------------------- others map('E', fm.edit_file()) @@ -324,7 +328,9 @@ def _system_functions(map): def _basic_movement(map): - map(KEY_DOWN, wdg.move(relative=1)) - map(KEY_UP, wdg.move(relative=-1)) - map(KEY_HOME, wdg.move(absolute=0)) - map(KEY_END, wdg.move(absolute=-1)) + map(KEY_DOWN, wdg.move(down=1)) + map(KEY_UP, wdg.move(up=1)) + map(KEY_RIGHT, wdg.move(right=1)) + map(KEY_LEFT, wdg.move(left=1)) + map(KEY_HOME, wdg.move(to=0)) + map(KEY_END, wdg.move(to=-1)) diff --git a/ranger/ext/accumulator.py b/ranger/ext/accumulator.py index c65370d5..2e599c85 100644 --- a/ranger/ext/accumulator.py +++ b/ranger/ext/accumulator.py @@ -13,41 +13,27 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +from ranger.ext.direction import Direction + class Accumulator(object): def __init__(self): self.pointer = 0 self.pointed_obj = None - def move(self, relative=0, absolute=None, pages=None, narg=None): - i = self.pointer + def move(self, narg=None, **keywords): + direction = Direction(keywords) lst = self.get_list() if not lst: return self.pointer - length = len(lst) - - if isinstance(absolute, int): - if isinstance(narg, int): - absolute = narg - if absolute < 0: # wrap - i = absolute + length - else: - i = absolute - - if relative != 0: - if isinstance(pages, int): - relative *= pages * self.get_height() - if isinstance(narg, int): - relative *= narg - i = int(i + relative) - - if i >= length: - i = length - 1 - if i < 0: - i = 0 - - self.pointer = i + pointer = direction.move( + direction=direction.down(), + maximum=len(lst), + override=narg, + pagesize=self.get_height(), + current=self.pointer) + self.pointer = pointer self.correct_pointer() - return self.pointer + return pointer def move_to_obj(self, arg, attr=None): if not arg: @@ -77,10 +63,10 @@ class Accumulator(object): test = obj if test == good: - self.move(absolute=i) + self.move(to=i) return True - return self.move(absolute=self.pointer) + return self.move(to=self.pointer) def correct_pointer(self): lst = self.get_list() diff --git a/ranger/ext/direction.py b/ranger/ext/direction.py new file mode 100644 index 00000000..0b3f55f7 --- /dev/null +++ b/ranger/ext/direction.py @@ -0,0 +1,135 @@ +# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Directions provide convenience methods for movement operations. + +Direction objects are handled just like dicts but provide +methods like up() and down() which give you the correct value +for the vertical direction, even if only the "up" or "down" key +has been defined. + +Example application: +d = Direction(down=5) +print(d.up()) # prints -5 +print(bool(d.horizontal())) # False, since no horizontal direction is defined +""" + +class Direction(dict): + __doc__ = __doc__ # for nicer pydoc + + def __init__(self, dictionary=None, **keywords): + if dictionary is not None: + dict.__init__(self, dictionary) + else: + dict.__init__(self, keywords) + if 'to' in self: + self['down'] = self['to'] + self['absolute'] = True + + def copy(self): + return Direction(**self) + + def _get_bool(self, first, second, fallback=None): + try: return self[first] + except: + try: return not self[second] + except: return fallback + + def _get_direction(self, first, second, fallback=0): + try: return self[first] + except: + try: return -self[second] + except: return fallback + + def up(self): + return -Direction.down(self) + + def down(self): + return Direction._get_direction(self, 'down', 'up') + + def right(self): + return Direction._get_direction(self, 'right', 'left') + + def absolute(self): + return Direction._get_bool(self, 'absolute', 'relative') + + def left(self): + return -Direction.right(self) + + def relative(self): + return not Direction.absolute(self) + + def vertical_direction(self): + down = Direction.down(self) + return (down > 0) - (down < 0) + + def horizontal_direction(self): + right = Direction.right(self) + return (right > 0) - (right < 0) + + def vertical(self): + return set(self) & set(['up', 'down']) + + def horizontal(self): + return set(self) & set(['left', 'right']) + + def pages(self): + return 'pages' in self and self['pages'] + + def percentage(self): + return 'percentage' in self and self['percentage'] + + def multiply(self, n): + for key in ('up', 'right', 'down', 'left'): + try: + self[key] *= n + except: + pass + + def set(self, n): + for key in ('up', 'right', 'down', 'left'): + if key in self: + self[key] = n + + def move(self, direction, override=None, minimum=0, maximum=9999, + current=0, pagesize=1, offset=0): + """ + Calculates the new position in a given boundary. + + Example: + d = Direction(pages=True) + d.move(direction=3) # = 3 + d.move(direction=3, current=2) # = 5 + d.move(direction=3, pagesize=5) # = 15 + d.move(direction=3, pagesize=5, maximum=10) # = 10 + d.move(direction=9, override=2) # = 18 + """ + pos = direction + if override is not None: + if self.absolute(): + pos = override + else: + pos *= override + if self.pages(): + pos *= pagesize + elif self.percentage(): + pos *= maximum / 100.0 + if self.absolute(): + if pos < minimum: + pos += maximum + else: + pos += current + return int(max(min(pos, maximum + offset - 1), minimum)) diff --git a/ranger/ext/move.py b/ranger/ext/move.py deleted file mode 100644 index 948adae6..00000000 --- a/ranger/ext/move.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -def move_between(current, minimum, maximum, relative=0, absolute=None): - i = current - if isinstance(absolute, int): - i = absolute - if isinstance(relative, int): - i += relative - i = max(minimum, min(maximum - 1, i)) - return i diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py index 29f0042c..3574f329 100644 --- a/ranger/fsobject/directory.py +++ b/ranger/fsobject/directory.py @@ -215,7 +215,7 @@ class Directory(FileSystemObject, Accumulator, SettingsAware): if self.pointed_obj is not None: self.sync_index() else: - self.move(absolute=0) + self.move(to=0) else: self.filenames = None self.files = None diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index cdaf6cde..6d9c78ad 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -159,7 +159,7 @@ class UI(DisplayableContainer): self.hint(cmd.show_obj.hint) elif hasattr(cmd, 'execute'): try: - cmd.execute_wrap(self) + cmd.execute_wrap(self.fm) except Exception as error: self.fm.notify(error) self.env.key_clear() diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index 8301d26a..8cf8990c 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -102,7 +102,7 @@ class BrowserColumn(Pager): self.fm.enter_dir(self.target.path) if index < len(self.target): - self.fm.move_pointer(absolute = index) + self.fm.move(to=index) elif event.pressed(3): try: clicked_file = self.target.files[index] @@ -112,7 +112,7 @@ class BrowserColumn(Pager): else: if self.level > 0: - self.fm.move_right() + self.fm.move(right=0) return True @@ -361,11 +361,11 @@ class BrowserColumn(Pager): self.scroll_begin = self._get_scroll_begin() self.target.scroll_begin = self.scroll_begin - def scroll(self, relative): - """scroll by n lines""" + def scroll(self, n): + """scroll down by n lines""" self.need_redraw = True - self.target.move(relative=relative) - self.target.scroll_begin += 3 * relative + self.target.move(down=n) + self.target.scroll_begin += 3 * n def __str__(self): return self.__class__.__name__ + ' at level ' + str(self.level) diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index 8480ea42..677be2a3 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -30,6 +30,7 @@ from ranger import log, relpath_conf from ranger.core.runner import ALLOWED_FLAGS from ranger.ext.shell_escape import shell_quote from ranger.ext.get_executables import get_executables +from ranger.ext.direction import Direction from ranger.container import CommandList, History from ranger.container.history import HistoryEmptyException import ranger @@ -216,14 +217,16 @@ class Console(Widget): self.history.fast_forward() self.history.modify(self.line) - def move(self, relative = 0, absolute = None): - if absolute is not None: - if absolute < 0: - self.pos = len(self.line) + 1 + absolute - else: - self.pos = absolute - - self.pos = min(max(0, self.pos + relative), len(self.line)) + def move(self, **keywords): + from ranger import log + log(keywords) + direction = Direction(keywords) + if direction.horizontal(): + self.pos = direction.move( + direction=direction.right(), + minimum=0, + maximum=len(self.line) + 1, + current=self.pos) def delete_rest(self, direction): self.tab_deque = None @@ -262,7 +265,7 @@ class Console(Widget): pos = self.pos + mod self.line = self.line[0:pos] + self.line[pos+1:] - self.move(relative = mod) + self.move(right=mod) self.on_line_change() def execute(self): diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py index 2fc8ecda..91383a18 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -19,7 +19,7 @@ The pager displays text and allows you to scroll inside it. import re from . import Widget from ranger.container.commandlist import CommandList -from ranger.ext.move import move_between +from ranger.ext.direction import Direction BAR_REGEXP = re.compile(r'\|\d+\?\|') QUOTES_REGEXP = re.compile(r'"[^"]+?"') @@ -50,6 +50,10 @@ class Pager(Widget): keyfnc(self.commandlist) + def move_horizontal(self, *a, **k): + """For compatibility""" + self.fm.notify("Your keys.py is out of date. Can't scroll!", bad=True) + def open(self): self.scroll_begin = 0 self.markup = None @@ -116,50 +120,26 @@ class Pager(Widget): if TITLE_REGEXP.match(line): self.color_at(i, 0, -1, 'title', *baseclr) - - def move(self, relative=0, absolute=None, pages=None, narg=None): - i = self.scroll_begin - if isinstance(absolute, int): - if isinstance(narg, int): - absolute = narg - if absolute < 0: - i = absolute + len(self.lines) - else: - i = absolute - - if relative != 0: - if isinstance(pages, int): - relative *= pages * self.hei - if isinstance(narg, int): - relative *= narg - i = int(i + relative) - - length = len(self.lines) - self.hei - if i >= length: - self._get_line(i+self.hei) - - length = len(self.lines) - self.hei - if i >= length: - i = length - - if i < 0: - i = 0 - - self.scroll_begin = i - - def move_horizontal(self, relative=0, absolute=None, narg=None): - if narg is not None: - if absolute is None: - relative = relative < 0 and -narg or narg - else: - absolute = narg - - self.startx = move_between( - current=self.startx, - minimum=0, - maximum=999, - relative=relative, - absolute=absolute) + def move(self, narg=None, **kw): + direction = Direction(kw) + if direction.horizontal(): + self.startx = direction.move( + direction=direction.right(), + override=narg, + maximum=self._get_max_width(), + current=self.startx, + pagesize=self.wid, + offset=-self.wid) + if direction.vertical(): + if self.source_is_stream: + self._get_line(self.scroll_begin + self.hei * 2) + self.scroll_begin = direction.move( + direction=direction.down(), + override=narg, + maximum=len(self.lines), + current=self.scroll_begin, + pagesize=self.hei, + offset=-self.hei) def press(self, key): try: @@ -207,10 +187,11 @@ class Pager(Widget): n = event.ctrl() and 1 or 3 direction = event.mouse_wheel_direction() if direction: - self.move(relative=direction) + self.move(down=direction * n) return True def _get_line(self, n, attempt_to_read=True): + assert isinstance(n, int), n try: return self.lines[n] except (KeyError, IndexError): @@ -237,3 +218,6 @@ class Pager(Widget): except IndexError: raise StopIteration i += 1 + + def _get_max_width(self): + return max(len(line) for line in self.lines) diff --git a/ranger/gui/widgets/taskview.py b/ranger/gui/widgets/taskview.py index 6e86465c..6d025048 100644 --- a/ranger/gui/widgets/taskview.py +++ b/ranger/gui/widgets/taskview.py @@ -90,11 +90,11 @@ class TaskView(Widget, Accumulator): self.fm.loader.remove(index=i) - def task_move(self, absolute, i=None): + def task_move(self, to, i=None): if i is None: i = self.pointer - self.fm.loader.move(_from=i, to=absolute) + self.fm.loader.move(_from=i, to=to) def press(self, key): try: diff --git a/test/tc_direction.py b/test/tc_direction.py new file mode 100644 index 00000000..18f9eb4c --- /dev/null +++ b/test/tc_direction.py @@ -0,0 +1,81 @@ +# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +if __name__ == '__main__': from __init__ import init; init() + +import unittest +from ranger.ext.direction import Direction +from ranger.ext.openstruct import OpenStruct + +class TestDirections(unittest.TestCase): + def test_symmetry(self): + d1 = Direction(right=4, down=7, relative=True) + d2 = Direction(left=-4, up=-7, absolute=False) + + def subtest(d): + self.assertEqual(4, d.right()) + self.assertEqual(7, d.down()) + self.assertEqual(-4, d.left()) + self.assertEqual(-7, d.up()) + self.assertEqual(True, d.relative()) + self.assertEqual(False, d.absolute()) + + self.assertTrue(d.horizontal()) + self.assertTrue(d.vertical()) + + subtest(d1) + subtest(d2) + + def test_conflicts(self): + d3 = Direction(right=5, left=2, up=3, down=6, + absolute=True, relative=True) + self.assertEqual(d3.right(), -d3.left()) + self.assertEqual(d3.left(), -d3.right()) + self.assertEqual(d3.up(), -d3.down()) + self.assertEqual(d3.down(), -d3.up()) + self.assertEqual(d3.absolute(), not d3.relative()) + self.assertEqual(d3.relative(), not d3.absolute()) + + def test_copy(self): + d = Direction(right=5) + c = d.copy() + self.assertEqual(c.right(), d.right()) + d['right'] += 3 + self.assertNotEqual(c.right(), d.right()) + c['right'] += 3 + self.assertEqual(c.right(), d.right()) + + self.assertFalse(d.vertical()) + self.assertTrue(d.horizontal()) + +# Doesn't work in python2? +# def test_duck_typing(self): +# dct = dict(right=7, down=-3) +# self.assertEqual(-7, Direction.left(dct)) +# self.assertEqual(3, Direction.up(dct)) + + def test_move(self): + d = Direction(pages=True) + self.assertEqual(3, d.move(direction=3)) + self.assertEqual(5, d.move(direction=3, current=2)) + self.assertEqual(15, d.move(direction=3, pagesize=5)) + self.assertEqual(9, d.move(direction=3, pagesize=5, maximum=10)) + self.assertEqual(18, d.move(direction=9, override=2)) + d2 = Direction(absolute=True) + self.assertEqual(5, d2.move(direction=9, override=5)) + +if __name__ == '__main__': + unittest.main() + |