From 02cf8f611f9b7d836b141bb1b54f7116fa331e68 Mon Sep 17 00:00:00 2001 From: hut Date: Fri, 12 Mar 2010 01:17:56 +0100 Subject: moved ranger.actions and ranger.commands --- ranger/actions.py | 531 ------------------------------------------ ranger/commands.py | 474 ------------------------------------- ranger/core/actions.py | 531 ++++++++++++++++++++++++++++++++++++++++++ ranger/core/fm.py | 2 +- ranger/defaults/commands.py | 474 +++++++++++++++++++++++++++++++++++++ ranger/gui/widgets/console.py | 2 +- ranger/help/console.py | 2 +- 7 files changed, 1008 insertions(+), 1008 deletions(-) delete mode 100644 ranger/actions.py delete mode 100644 ranger/commands.py create mode 100644 ranger/core/actions.py create mode 100644 ranger/defaults/commands.py diff --git a/ranger/actions.py b/ranger/actions.py deleted file mode 100644 index 849a2a52..00000000 --- a/ranger/actions.py +++ /dev/null @@ -1,531 +0,0 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann -# -# 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 . - -import os -import shutil -from inspect import cleandoc - -import ranger -from ranger.shared import EnvironmentAware, SettingsAware -from ranger import fsobject -from ranger.gui.widgets import console_mode as cmode -from ranger.fsobject import File - -class Actions(EnvironmentAware, SettingsAware): - search_method = 'ctime' - search_forward = False - - def search(self, order=None, forward=True): - original_order = order - if self.search_forward: - direction = bool(forward) - else: - direction = not bool(forward) - - if order is None: - order = self.search_method - else: - self.set_search_method(order=order) - - if order in ('search', 'tag'): - if order == 'search': - arg = self.env.last_search - if arg is None: - return False - if hasattr(arg, 'search'): - fnc = lambda x: arg.search(x.basename) - else: - fnc = lambda x: arg in x.basename - elif order == 'tag': - fnc = lambda x: x.realpath in self.tags - - return self.env.pwd.search_fnc(fnc=fnc, forward=forward) - - elif order in ('size', 'mimetype', 'ctime'): - pwd = self.env.pwd - if original_order is not None or not pwd.cycle_list: - lst = list(pwd.files) - if order == 'size': - fnc = lambda item: -item.size - elif order == 'mimetype': - fnc = lambda item: item.mimetype - elif order == 'ctime': - fnc = lambda item: -int(item.stat and item.stat.st_ctime) - lst.sort(key=fnc) - pwd.set_cycle_list(lst) - return pwd.cycle(forward=None) - - return pwd.cycle(forward=forward) - - def set_search_method(self, order, forward=True): - if order in ('search', 'tag', 'size', 'mimetype', 'ctime'): - self.search_method = order - self.search_forward = forward - - def resize(self): - """Update the size of the UI""" - self.ui.update_size() - - def exit(self): - """Exit the program""" - raise SystemExit() - - def enter_dir(self, path, remember=False): - """Enter the directory at the given path""" - if remember: - pwd = self.env.pwd - result = self.env.enter_dir(path) - self.bookmarks.remember(pwd) - return result - return self.env.enter_dir(path) - - def cd(self, path, remember=True): - """enter the directory at the given path, remember=True""" - self.enter_dir(path, remember) - - def tag_toggle(self, movedown=None): - try: - toggle = self.tags.toggle - except AttributeError: - return - - sel = self.env.get_selection() - toggle(*tuple(map(lambda x: x.realpath, sel))) - - if movedown is None: - movedown = len(sel) == 1 - if movedown: - self.move_pointer(relative=1) - - if hasattr(self.ui, 'redraw_main_column'): - self.ui.redraw_main_column() - - def tag_remove(self, movedown=None): - try: - remove = self.tags.remove - except AttributeError: - return - - sel = self.env.get_selection() - remove(*tuple(map(lambda x: x.realpath, sel))) - - if movedown is None: - movedown = len(sel) == 1 - if movedown: - self.move_pointer(relative=1) - - if hasattr(self.ui, 'redraw_main_column'): - self.ui.redraw_main_column() - - def enter_bookmark(self, key): - """Enter the bookmark with the name """ - try: - destination = self.bookmarks[key] - pwd = self.env.pwd - if destination.path != pwd.path: - self.bookmarks.enter(key) - self.bookmarks.remember(pwd) - except KeyError: - pass - - def set_bookmark(self, key): - """Set the bookmark with the name to the current directory""" - self.bookmarks[key] = self.env.pwd - - def unset_bookmark(self, key): - """Delete the bookmark with the name """ - self.bookmarks.delete(key) - - 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 history_go(self, relative): - """Move back and forth in the history""" - self.env.history_go(relative) - - def handle_mouse(self): - """Handle mouse-buttons if one was pressed""" - self.ui.handle_mouse() - - def display_command_help(self, console_widget): - if not hasattr(self.ui, 'open_pager'): - return - - try: - command = console_widget._get_cmd_class() - except: - self.notify("Feature not available!", bad=True) - return - - if not command: - self.notify("Command not found!", bad=True) - return - - if not command.__doc__: - self.notify("Command has no docstring. Try using python without -OO", - bad=True) - return - - pager = self.ui.open_pager() - lines = cleandoc(command.__doc__).split('\n') - pager.set_source(lines) - - def display_help(self, topic='index', narg=None): - if not hasattr(self.ui, 'open_pager'): - return - - from ranger.help import get_help, get_help_by_index - - if narg is not None: - help_text = get_help_by_index(narg) - else: - help_text = get_help(topic) - - pager = self.ui.open_pager() - pager.markup = 'help' - lines = help_text.split('\n') - pager.set_source(lines) - - def display_log(self): - if not hasattr(self.ui, 'open_pager'): - return - - pager = self.ui.open_pager() - if self.log: - pager.set_source(["Message Log:"] + list(self.log)) - else: - pager.set_source(["Message Log:", "No messages!"]) - - def display_file(self): - if not hasattr(self.ui, 'open_embedded_pager'): - return - - try: - f = open(self.env.cf.path, 'r') - except: - pass - else: - pager = self.ui.open_embedded_pager() - pager.set_source(f) - - def execute_file(self, files, **kw): - """Execute a file. - app is the name of a method in Applications, without the "app_" - flags is a string consisting of runner.ALLOWED_FLAGS - mode is a positive integer. - Both flags and mode specify how the program is run.""" - - if isinstance(files, set): - files = list(files) - elif type(files) not in (list, tuple): - files = [files] - - return self.run(files=list(files), **kw) - - def execute_command(self, cmd, **kw): - return self.run(cmd, **kw) - - def edit_file(self, file=None): - """Calls execute_file with the current file and app='editor'""" - if file is None: - file = self.env.cf - elif isinstance(file, str): - file = File(os.path.expanduser(file)) - if file is None: - return - self.execute_file(file, app = 'editor') - - def open_console(self, mode=':', string=''): - """Open the console if the current UI supports that""" - if hasattr(self.ui, 'open_console'): - self.ui.open_console(mode, string) - - def move_pointer(self, relative = 0, absolute = None, narg=None): - """Move the pointer down by or to """ - self.env.pwd.move(relative=relative, - absolute=absolute, narg=narg) - - def move_pointer_by_pages(self, relative): - """Move the pointer down by pages""" - self.env.pwd.move(relative=int(relative * self.env.termsize[0])) - - def move_pointer_by_percentage(self, relative=0, absolute=None, narg=None): - """Move the pointer down by % or to %""" - try: - factor = len(self.env.pwd) / 100.0 - except: - return - - if narg is not None: - absolute = narg - - self.env.pwd.move( - relative=int(relative * factor), - absolute=int(absolute * factor)) - - def scroll(self, relative): - """Scroll down by lines""" - if hasattr(self.ui, 'scroll'): - self.ui.scroll(relative) - self.env.cf = self.env.pwd.pointed_obj - - def redraw_window(self): - """Redraw the window""" - self.ui.redraw_window() - - def reset(self): - """Reset the filemanager, clearing the directory buffer""" - old_path = self.env.pwd.path - self.env.directories = {} - self.enter_dir(old_path) - - def toggle_boolean_option(self, string): - """Toggle a boolean option named """ - if isinstance(self.env.settings[string], bool): - self.env.settings[string] ^= True - - def sort(self, func=None, reverse=None): - if reverse is not None: - self.env.settings['reverse'] = bool(reverse) - - if func is not None: - self.env.settings['sort'] = str(func) - - def force_load_preview(self): - cf = self.env.cf - if hasattr(cf, 'unload') and hasattr(cf, 'load_content'): - cf.unload() - cf.load_content() - - def reload_cwd(self): - try: - cwd = self.env.pwd - except: - pass - cwd.unload() - cwd.load_content() - - def traverse(self): - cf = self.env.cf - cwd = self.env.pwd - if cf is not None and cf.is_directory: - self.enter_dir(cf.path) - elif cwd.pointer >= len(cwd) - 1: - while True: - self.enter_dir('..') - cwd = self.env.pwd - if cwd.pointer < len(cwd) - 1: - break - if cwd.path == '/': - break - self.move_pointer(1) - self.traverse() - else: - self.move_pointer(1) - self.traverse() - - def set_filter(self, fltr): - try: - self.env.pwd.filter = fltr - except: - pass - - def notify(self, text, duration=4, bad=False): - if isinstance(text, Exception): - if ranger.arg.debug: - raise - bad = True - text = str(text) - self.log.appendleft(text) - if hasattr(self.ui, 'notify'): - self.ui.notify(text, duration=duration, bad=bad) - - def mark(self, all=False, toggle=False, val=None, movedown=None, narg=1): - """ - A wrapper for the directory.mark_xyz functions. - - Arguments: - all - change all files of the current directory at once? - toggle - toggle the marked-status? - val - mark or unmark? - """ - - if self.env.pwd is None: - return - - pwd = self.env.pwd - - if not pwd.accessible: - return - - if movedown is None: - movedown = not all - - if val is None and toggle is False: - return - - if all: - if toggle: - pwd.toggle_all_marks() - else: - pwd.mark_all(val) - else: - for i in range(pwd.pointer, min(pwd.pointer + narg, len(pwd))): - item = pwd.files[i] - if item is not None: - if toggle: - pwd.toggle_mark(item) - else: - pwd.mark_item(item, val) - - if movedown: - self.move_pointer(relative=narg) - - if hasattr(self.ui, 'redraw_main_column'): - self.ui.redraw_main_column() - if hasattr(self.ui, 'status'): - self.ui.status.need_redraw = True - - # ------------------------------------ filesystem operations - - def copy(self): - """Copy the selected items""" - - selected = self.env.get_selection() - self.env.copy = set(f for f in selected if f in self.env.pwd.files) - self.env.cut = False - - def cut(self): - self.copy() - self.env.cut = True - - def paste_symlink(self): - from os import symlink, getcwd - from os.path import join - - copied_files = self.env.copy - - if not copied_files: - return - - for f in copied_files: - try: - symlink(f.path, join(getcwd(), f.basename)) - except Exception as x: - self.notify(x) - - def paste(self, overwrite=False): - """Paste the selected items into the current directory""" - from os.path import join, isdir - from ranger.ext import shutil_generatorized as shutil_g - from ranger.fsobject.loader import LoadableObject - copied_files = tuple(self.env.copy) - - if not copied_files: - return - - original_path = self.env.pwd.path - try: - one_file = copied_files[0] - except: - one_file = None - - if self.env.cut: - self.env.copy.clear() - self.env.cut = False - if len(copied_files) == 1: - descr = "moving: " + one_file.path - else: - descr = "moving files from: " + one_file.dirname - def generate(): - for f in copied_files: - for _ in shutil_g.move(src=f.path, - dst=original_path, - overwrite=overwrite): - yield - pwd = self.env.get_directory(original_path) - pwd.load_content() - else: - if len(copied_files) == 1: - descr = "copying: " + one_file.path - else: - descr = "copying files from: " + one_file.dirname - def generate(): - for f in self.env.copy: - if isdir(f.path): - for _ in shutil_g.copytree(src=f.path, - dst=join(self.env.pwd.path, f.basename), - symlinks=True, - overwrite=overwrite): - yield - else: - for _ in shutil_g.copy2(f.path, original_path, - symlinks=True, - overwrite=overwrite): - yield - pwd = self.env.get_directory(original_path) - pwd.load_content() - - self.loader.add(LoadableObject(generate(), descr)) - - def delete(self): - self.notify("Deleting!", duration=1) - selected = self.env.get_selection() - self.env.copy -= set(selected) - if selected: - for f in selected: - if os.path.isdir(f.path) and not os.path.islink(f.path): - try: - shutil.rmtree(f.path) - except OSError as err: - self.notify(err) - else: - try: - os.remove(f.path) - except OSError as err: - self.notify(err) - - def mkdir(self, name): - try: - os.mkdir(os.path.join(self.env.pwd.path, name)) - except OSError as err: - self.notify(err) - - - def rename(self, src, dest): - if hasattr(src, 'path'): - src = src.path - - try: - os.rename(src, dest) - except OSError as err: - self.notify(err) diff --git a/ranger/commands.py b/ranger/commands.py deleted file mode 100644 index 834e358b..00000000 --- a/ranger/commands.py +++ /dev/null @@ -1,474 +0,0 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann -# -# 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 . - -import os -from collections import deque -from ranger.shared import FileManagerAware -from ranger.gui.widgets import console_mode as cmode -from ranger.ext.command_parser import LazyParser as parse - -class Command(FileManagerAware): - """Abstract command class""" - name = None - allow_abbrev = True - def __init__(self, line, mode): - self.line = line - self.mode = mode - - def execute(self): - """Override this""" - - def tab(self): - """Override this""" - - def quick_open(self): - """Override this""" - - def _tab_only_directories(self): - from os.path import dirname, basename, expanduser, join, isdir - - line = parse(self.line) - pwd = self.fm.env.pwd.path - - try: - rel_dest = line.rest(1) - except IndexError: - rel_dest = '' - - # expand the tilde into the user directory - if rel_dest.startswith('~'): - rel_dest = expanduser(rel_dest) - - # define some shortcuts - abs_dest = join(pwd, rel_dest) - abs_dirname = dirname(abs_dest) - rel_basename = basename(rel_dest) - rel_dirname = dirname(rel_dest) - - try: - # are we after a directory? - if rel_dest.endswith('/') or rel_dest == '': - _, dirnames, _ = os.walk(abs_dest).next() - - # are we in the middle of the filename? - else: - _, dirnames, _ = os.walk(abs_dirname).next() - dirnames = [dn for dn in dirnames \ - if dn.startswith(rel_basename)] - except (OSError, StopIteration): - # os.walk found nothing - pass - else: - dirnames.sort() - - # no results, return None - if len(dirnames) == 0: - return - - # one result. since it must be a directory, append a slash. - if len(dirnames) == 1: - return line + join(rel_dirname, dirnames[0]) + '/' - - # more than one result. append no slash, so the user can - # manually type in the slash to advance into that directory - return (line + join(rel_dirname, dirname) for dirname in dirnames) - - def _tab_directory_content(self): - from os.path import dirname, basename, expanduser, join, isdir - - line = parse(self.line) - pwd = self.fm.env.pwd.path - - try: - rel_dest = line.rest(1) - except IndexError: - rel_dest = '' - - # expand the tilde into the user directory - if rel_dest.startswith('~'): - rel_dest = expanduser(rel_dest) - - # define some shortcuts - abs_dest = join(pwd, rel_dest) - abs_dirname = dirname(abs_dest) - rel_basename = basename(rel_dest) - rel_dirname = dirname(rel_dest) - - try: - # are we after a directory? - if rel_dest.endswith('/') or rel_dest == '': - _, dirnames, filenames = os.walk(abs_dest).next() - names = dirnames + filenames - - # are we in the middle of the filename? - else: - _, dirnames, filenames = os.walk(abs_dirname).next() - names = [name for name in (dirnames + filenames) \ - if name.startswith(rel_basename)] - except (OSError, StopIteration): - # os.walk found nothing - pass - else: - names.sort() - - # no results, return None - if len(names) == 0: - return - - # one result. since it must be a directory, append a slash. - if len(names) == 1: - return line + join(rel_dirname, names[0]) + '/' - - # more than one result. append no slash, so the user can - # manually type in the slash to advance into that directory - return (line + join(rel_dirname, name) for name in names) - - -# -------------------------------- definitions - -class cd(Command): - """ - :cd - - The cd command changes the directory. - The command 'cd -' is equivalent to typing ``. - - In the quick console, the directory will be entered without the - need to press enter, as soon as there is one unambiguous match. - """ - - def execute(self): - line = parse(self.line) - try: - destination = line.rest(1) - except IndexError: - destination = '~' - - if destination == '-': - self.fm.enter_bookmark('`') - else: - self.fm.cd(destination) - - def tab(self): - return self._tab_only_directories() - - def quick_open(self): - from os.path import isdir, join, normpath - line = parse(self.line) - pwd = self.fm.env.pwd.path - - rel_dest = line.rest(1) - if not rel_dest: - return False - - abs_dest = normpath(join(pwd, rel_dest)) - return rel_dest != '.' and isdir(abs_dest) - - -class find(Command): - """ - :find - - The find command will attempt to find a partial, case insensitive - match in the filenames of the current directory. - - In the quick command console, once there is one unambiguous match, - the file will be run automatically. - """ - - count = 0 - tab = Command._tab_directory_content - - def execute(self): - if self.mode != cmode.COMMAND_QUICK: - self._search() - - import re - search = parse(self.line).rest(1) - search = re.escape(search) - self.fm.env.last_search = re.compile(search, re.IGNORECASE) - - if self.count == 1: - self.fm.move_right() - self.fm.block_input(0.5) - - def quick_open(self): - self._search() - if self.count == 1: - return True - - def _search(self): - self.count = 0 - line = parse(self.line) - pwd = self.fm.env.pwd - try: - arg = line.rest(1) - except IndexError: - return False - - deq = deque(pwd.files) - deq.rotate(-pwd.pointer) - i = 0 - for fsobj in deq: - filename = fsobj.basename_lower - if arg in filename: - self.count += 1 - if self.count == 1: - pwd.move(absolute=(pwd.pointer + i) % len(pwd.files)) - self.fm.env.cf = pwd.pointed_obj - if self.count > 1: - return False - i += 1 - - return self.count == 1 - - -class quit(Command): - """ - :quit - - Quits the program immediately. - """ - - def execute(self): - raise SystemExit - - -class delete(Command): - """ - :delete - - Tries to delete the selection. - - "Selection" is defined as all the "marked files" (by default, you - can mark files with space or v). If there are no marked files, - use the "current file" (where the cursor is) - """ - - allow_abbrev = False - - def execute(self): - self.fm.delete() - - -class mkdir(Command): - """ - :mkdir - - Creates a directory with the name . - """ - - def execute(self): - from os.path import join, expanduser, lexists - from os import mkdir - - line = parse(self.line) - dirname = join(self.fm.env.pwd.path, expanduser(line.rest(1))) - if not lexists(dirname): - mkdir(dirname) - else: - self.fm.notify("file/directory exists!", bad=True) - - -class touch(Command): - """ - :touch - - Creates a file with the name . - """ - - def execute(self): - from os.path import join, expanduser, lexists - from os import mkdir - - line = parse(self.line) - fname = join(self.fm.env.pwd.path, expanduser(line.rest(1))) - if not lexists(fname): - open(fname, 'a') - else: - self.fm.notify("file/directory exists!", bad=True) - - -class edit(Command): - """ - :edit - - Opens the specified file in vim - """ - - def execute(self): - line = parse(self.line) - self.fm.edit_file(line.rest(1)) - - def tab(self): - return self._tab_directory_content() - - -class eval_(Command): - """ - :eval - - Evaluates the python code. - `fm' is a reference to the FM instance. - To display text, use the function `p'. - - Examples: - :eval fm - :eval len(fm.env.directories) - :eval p("Hello World!") - """ - name = 'eval' - - def execute(self): - code = parse(self.line).rest(1) - fm = self.fm - p = fm.notify - try: - try: - result = eval(code) - except SyntaxError: - exec(code) - else: - if result: - p(result) - except Exception as err: - p(err) - - -class rename(Command): - """ - :rename - - Changes the name of the currently highlighted file to - """ - - def execute(self): - from ranger.fsobject.file import File - line = parse(self.line) - self.fm.rename(self.fm.env.cf, line.rest(1)) - f = File(line.rest(1)) - self.fm.env.pwd.pointed_obj = f - self.fm.env.cf = f - - def tab(self): - return self._tab_directory_content() - - -class chmod(Command): - """ - :chmod - - Sets the permissions of the selection to the octal number. - - The octal number is between 0 and 777. The digits specify the - permissions for the user, the group and others. - - A 1 permits execution, a 2 permits writing, a 4 permits reading. - Add those numbers to combine them. So a 7 permits everything. - """ - - def execute(self): - line = parse(self.line) - mode = line.rest(1) - - try: - mode = int(mode, 8) - if mode < 0 or mode > 511: - raise ValueError - except ValueError: - self.fm.notify("Need an octal number between 0 and 777!", bad=True) - return - - for file in self.fm.env.get_selection(): - try: - os.chmod(file.path, mode) - except Exception as ex: - self.fm.notify(ex) - - try: - # reloading directory. maybe its better to reload the selected - # files only. - self.fm.env.pwd.load_content() - except: - pass - - -class filter(Command): - """ - :filter - - Displays only the files which contain in their basename. - """ - - def execute(self): - line = parse(self.line) - self.fm.set_filter(line.rest(1)) - - -class grep(Command): - """ - :grep - - Looks for a string in all marked files or directories - """ - - def execute(self): - line = parse(self.line) - if line.rest(1): - action = ['grep', '--color=always', '--line-number'] - action.extend(['-e', line.rest(1), '-r']) - action.extend(f.path for f in self.fm.env.get_selection()) - self.fm.execute_command(action, flags='p') - - -# -------------------------------- rest - -by_name = {} -for varname, var in vars().copy().items(): - try: - if issubclass(var, Command) and var != Command: - by_name[var.name or varname] = var - except TypeError: - pass -del varname -del var - -def alias(**kw): - """Create an alias for commands, eg: alias(quit=exit)""" - for key, value in kw.items(): - by_name[key] = value - -def get_command(name, abbrev=True): - if abbrev: - lst = [cls for cmd, cls in by_name.items() \ - if cmd.startswith(name) \ - and cls.allow_abbrev \ - or cmd == name] - if len(lst) == 0: - raise KeyError - if len(lst) == 1 or by_name[name] in lst: - return lst[0] - raise ValueError("Ambiguous command") - else: - try: - return by_name[name] - except KeyError: - return None - -def command_generator(start): - return (cmd + ' ' for cmd in by_name if cmd.startswith(start)) - -alias(e=edit) # to make :e unambiguous. - diff --git a/ranger/core/actions.py b/ranger/core/actions.py new file mode 100644 index 00000000..849a2a52 --- /dev/null +++ b/ranger/core/actions.py @@ -0,0 +1,531 @@ +# Copyright (C) 2009, 2010 Roman Zimbelmann +# +# 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 . + +import os +import shutil +from inspect import cleandoc + +import ranger +from ranger.shared import EnvironmentAware, SettingsAware +from ranger import fsobject +from ranger.gui.widgets import console_mode as cmode +from ranger.fsobject import File + +class Actions(EnvironmentAware, SettingsAware): + search_method = 'ctime' + search_forward = False + + def search(self, order=None, forward=True): + original_order = order + if self.search_forward: + direction = bool(forward) + else: + direction = not bool(forward) + + if order is None: + order = self.search_method + else: + self.set_search_method(order=order) + + if order in ('search', 'tag'): + if order == 'search': + arg = self.env.last_search + if arg is None: + return False + if hasattr(arg, 'search'): + fnc = lambda x: arg.search(x.basename) + else: + fnc = lambda x: arg in x.basename + elif order == 'tag': + fnc = lambda x: x.realpath in self.tags + + return self.env.pwd.search_fnc(fnc=fnc, forward=forward) + + elif order in ('size', 'mimetype', 'ctime'): + pwd = self.env.pwd + if original_order is not None or not pwd.cycle_list: + lst = list(pwd.files) + if order == 'size': + fnc = lambda item: -item.size + elif order == 'mimetype': + fnc = lambda item: item.mimetype + elif order == 'ctime': + fnc = lambda item: -int(item.stat and item.stat.st_ctime) + lst.sort(key=fnc) + pwd.set_cycle_list(lst) + return pwd.cycle(forward=None) + + return pwd.cycle(forward=forward) + + def set_search_method(self, order, forward=True): + if order in ('search', 'tag', 'size', 'mimetype', 'ctime'): + self.search_method = order + self.search_forward = forward + + def resize(self): + """Update the size of the UI""" + self.ui.update_size() + + def exit(self): + """Exit the program""" + raise SystemExit() + + def enter_dir(self, path, remember=False): + """Enter the directory at the given path""" + if remember: + pwd = self.env.pwd + result = self.env.enter_dir(path) + self.bookmarks.remember(pwd) + return result + return self.env.enter_dir(path) + + def cd(self, path, remember=True): + """enter the directory at the given path, remember=True""" + self.enter_dir(path, remember) + + def tag_toggle(self, movedown=None): + try: + toggle = self.tags.toggle + except AttributeError: + return + + sel = self.env.get_selection() + toggle(*tuple(map(lambda x: x.realpath, sel))) + + if movedown is None: + movedown = len(sel) == 1 + if movedown: + self.move_pointer(relative=1) + + if hasattr(self.ui, 'redraw_main_column'): + self.ui.redraw_main_column() + + def tag_remove(self, movedown=None): + try: + remove = self.tags.remove + except AttributeError: + return + + sel = self.env.get_selection() + remove(*tuple(map(lambda x: x.realpath, sel))) + + if movedown is None: + movedown = len(sel) == 1 + if movedown: + self.move_pointer(relative=1) + + if hasattr(self.ui, 'redraw_main_column'): + self.ui.redraw_main_column() + + def enter_bookmark(self, key): + """Enter the bookmark with the name """ + try: + destination = self.bookmarks[key] + pwd = self.env.pwd + if destination.path != pwd.path: + self.bookmarks.enter(key) + self.bookmarks.remember(pwd) + except KeyError: + pass + + def set_bookmark(self, key): + """Set the bookmark with the name to the current directory""" + self.bookmarks[key] = self.env.pwd + + def unset_bookmark(self, key): + """Delete the bookmark with the name """ + self.bookmarks.delete(key) + + 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 history_go(self, relative): + """Move back and forth in the history""" + self.env.history_go(relative) + + def handle_mouse(self): + """Handle mouse-buttons if one was pressed""" + self.ui.handle_mouse() + + def display_command_help(self, console_widget): + if not hasattr(self.ui, 'open_pager'): + return + + try: + command = console_widget._get_cmd_class() + except: + self.notify("Feature not available!", bad=True) + return + + if not command: + self.notify("Command not found!", bad=True) + return + + if not command.__doc__: + self.notify("Command has no docstring. Try using python without -OO", + bad=True) + return + + pager = self.ui.open_pager() + lines = cleandoc(command.__doc__).split('\n') + pager.set_source(lines) + + def display_help(self, topic='index', narg=None): + if not hasattr(self.ui, 'open_pager'): + return + + from ranger.help import get_help, get_help_by_index + + if narg is not None: + help_text = get_help_by_index(narg) + else: + help_text = get_help(topic) + + pager = self.ui.open_pager() + pager.markup = 'help' + lines = help_text.split('\n') + pager.set_source(lines) + + def display_log(self): + if not hasattr(self.ui, 'open_pager'): + return + + pager = self.ui.open_pager() + if self.log: + pager.set_source(["Message Log:"] + list(self.log)) + else: + pager.set_source(["Message Log:", "No messages!"]) + + def display_file(self): + if not hasattr(self.ui, 'open_embedded_pager'): + return + + try: + f = open(self.env.cf.path, 'r') + except: + pass + else: + pager = self.ui.open_embedded_pager() + pager.set_source(f) + + def execute_file(self, files, **kw): + """Execute a file. + app is the name of a method in Applications, without the "app_" + flags is a string consisting of runner.ALLOWED_FLAGS + mode is a positive integer. + Both flags and mode specify how the program is run.""" + + if isinstance(files, set): + files = list(files) + elif type(files) not in (list, tuple): + files = [files] + + return self.run(files=list(files), **kw) + + def execute_command(self, cmd, **kw): + return self.run(cmd, **kw) + + def edit_file(self, file=None): + """Calls execute_file with the current file and app='editor'""" + if file is None: + file = self.env.cf + elif isinstance(file, str): + file = File(os.path.expanduser(file)) + if file is None: + return + self.execute_file(file, app = 'editor') + + def open_console(self, mode=':', string=''): + """Open the console if the current UI supports that""" + if hasattr(self.ui, 'open_console'): + self.ui.open_console(mode, string) + + def move_pointer(self, relative = 0, absolute = None, narg=None): + """Move the pointer down by or to """ + self.env.pwd.move(relative=relative, + absolute=absolute, narg=narg) + + def move_pointer_by_pages(self, relative): + """Move the pointer down by pages""" + self.env.pwd.move(relative=int(relative * self.env.termsize[0])) + + def move_pointer_by_percentage(self, relative=0, absolute=None, narg=None): + """Move the pointer down by % or to %""" + try: + factor = len(self.env.pwd) / 100.0 + except: + return + + if narg is not None: + absolute = narg + + self.env.pwd.move( + relative=int(relative * factor), + absolute=int(absolute * factor)) + + def scroll(self, relative): + """Scroll down by lines""" + if hasattr(self.ui, 'scroll'): + self.ui.scroll(relative) + self.env.cf = self.env.pwd.pointed_obj + + def redraw_window(self): + """Redraw the window""" + self.ui.redraw_window() + + def reset(self): + """Reset the filemanager, clearing the directory buffer""" + old_path = self.env.pwd.path + self.env.directories = {} + self.enter_dir(old_path) + + def toggle_boolean_option(self, string): + """Toggle a boolean option named """ + if isinstance(self.env.settings[string], bool): + self.env.settings[string] ^= True + + def sort(self, func=None, reverse=None): + if reverse is not None: + self.env.settings['reverse'] = bool(reverse) + + if func is not None: + self.env.settings['sort'] = str(func) + + def force_load_preview(self): + cf = self.env.cf + if hasattr(cf, 'unload') and hasattr(cf, 'load_content'): + cf.unload() + cf.load_content() + + def reload_cwd(self): + try: + cwd = self.env.pwd + except: + pass + cwd.unload() + cwd.load_content() + + def traverse(self): + cf = self.env.cf + cwd = self.env.pwd + if cf is not None and cf.is_directory: + self.enter_dir(cf.path) + elif cwd.pointer >= len(cwd) - 1: + while True: + self.enter_dir('..') + cwd = self.env.pwd + if cwd.pointer < len(cwd) - 1: + break + if cwd.path == '/': + break + self.move_pointer(1) + self.traverse() + else: + self.move_pointer(1) + self.traverse() + + def set_filter(self, fltr): + try: + self.env.pwd.filter = fltr + except: + pass + + def notify(self, text, duration=4, bad=False): + if isinstance(text, Exception): + if ranger.arg.debug: + raise + bad = True + text = str(text) + self.log.appendleft(text) + if hasattr(self.ui, 'notify'): + self.ui.notify(text, duration=duration, bad=bad) + + def mark(self, all=False, toggle=False, val=None, movedown=None, narg=1): + """ + A wrapper for the directory.mark_xyz functions. + + Arguments: + all - change all files of the current directory at once? + toggle - toggle the marked-status? + val - mark or unmark? + """ + + if self.env.pwd is None: + return + + pwd = self.env.pwd + + if not pwd.accessible: + return + + if movedown is None: + movedown = not all + + if val is None and toggle is False: + return + + if all: + if toggle: + pwd.toggle_all_marks() + else: + pwd.mark_all(val) + else: + for i in range(pwd.pointer, min(pwd.pointer + narg, len(pwd))): + item = pwd.files[i] + if item is not None: + if toggle: + pwd.toggle_mark(item) + else: + pwd.mark_item(item, val) + + if movedown: + self.move_pointer(relative=narg) + + if hasattr(self.ui, 'redraw_main_column'): + self.ui.redraw_main_column() + if hasattr(self.ui, 'status'): + self.ui.status.need_redraw = True + + # ------------------------------------ filesystem operations + + def copy(self): + """Copy the selected items""" + + selected = self.env.get_selection() + self.env.copy = set(f for f in selected if f in self.env.pwd.files) + self.env.cut = False + + def cut(self): + self.copy() + self.env.cut = True + + def paste_symlink(self): + from os import symlink, getcwd + from os.path import join + + copied_files = self.env.copy + + if not copied_files: + return + + for f in copied_files: + try: + symlink(f.path, join(getcwd(), f.basename)) + except Exception as x: + self.notify(x) + + def paste(self, overwrite=False): + """Paste the selected items into the current directory""" + from os.path import join, isdir + from ranger.ext import shutil_generatorized as shutil_g + from ranger.fsobject.loader import LoadableObject + copied_files = tuple(self.env.copy) + + if not copied_files: + return + + original_path = self.env.pwd.path + try: + one_file = copied_files[0] + except: + one_file = None + + if self.env.cut: + self.env.copy.clear() + self.env.cut = False + if len(copied_files) == 1: + descr = "moving: " + one_file.path + else: + descr = "moving files from: " + one_file.dirname + def generate(): + for f in copied_files: + for _ in shutil_g.move(src=f.path, + dst=original_path, + overwrite=overwrite): + yield + pwd = self.env.get_directory(original_path) + pwd.load_content() + else: + if len(copied_files) == 1: + descr = "copying: " + one_file.path + else: + descr = "copying files from: " + one_file.dirname + def generate(): + for f in self.env.copy: + if isdir(f.path): + for _ in shutil_g.copytree(src=f.path, + dst=join(self.env.pwd.path, f.basename), + symlinks=True, + overwrite=overwrite): + yield + else: + for _ in shutil_g.copy2(f.path, original_path, + symlinks=True, + overwrite=overwrite): + yield + pwd = self.env.get_directory(original_path) + pwd.load_content() + + self.loader.add(LoadableObject(generate(), descr)) + + def delete(self): + self.notify("Deleting!", duration=1) + selected = self.env.get_selection() + self.env.copy -= set(selected) + if selected: + for f in selected: + if os.path.isdir(f.path) and not os.path.islink(f.path): + try: + shutil.rmtree(f.path) + except OSError as err: + self.notify(err) + else: + try: + os.remove(f.path) + except OSError as err: + self.notify(err) + + def mkdir(self, name): + try: + os.mkdir(os.path.join(self.env.pwd.path, name)) + except OSError as err: + self.notify(err) + + + def rename(self, src, dest): + if hasattr(src, 'path'): + src = src.path + + try: + os.rename(src, dest) + except OSError as err: + self.notify(err) diff --git a/ranger/core/fm.py b/ranger/core/fm.py index 83426a04..74d2bb8a 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -17,7 +17,7 @@ from time import time from collections import deque import ranger -from ranger.actions import Actions +from ranger.core.actions import Actions from ranger.container import Bookmarks from ranger.core.runner import Runner from ranger import relpath_conf diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py new file mode 100644 index 00000000..834e358b --- /dev/null +++ b/ranger/defaults/commands.py @@ -0,0 +1,474 @@ +# Copyright (C) 2009, 2010 Roman Zimbelmann +# +# 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 . + +import os +from collections import deque +from ranger.shared import FileManagerAware +from ranger.gui.widgets import console_mode as cmode +from ranger.ext.command_parser import LazyParser as parse + +class Command(FileManagerAware): + """Abstract command class""" + name = None + allow_abbrev = True + def __init__(self, line, mode): + self.line = line + self.mode = mode + + def execute(self): + """Override this""" + + def tab(self): + """Override this""" + + def quick_open(self): + """Override this""" + + def _tab_only_directories(self): + from os.path import dirname, basename, expanduser, join, isdir + + line = parse(self.line) + pwd = self.fm.env.pwd.path + + try: + rel_dest = line.rest(1) + except IndexError: + rel_dest = '' + + # expand the tilde into the user directory + if rel_dest.startswith('~'): + rel_dest = expanduser(rel_dest) + + # define some shortcuts + abs_dest = join(pwd, rel_dest) + abs_dirname = dirname(abs_dest) + rel_basename = basename(rel_dest) + rel_dirname = dirname(rel_dest) + + try: + # are we after a directory? + if rel_dest.endswith('/') or rel_dest == '': + _, dirnames, _ = os.walk(abs_dest).next() + + # are we in the middle of the filename? + else: + _, dirnames, _ = os.walk(abs_dirname).next() + dirnames = [dn for dn in dirnames \ + if dn.startswith(rel_basename)] + except (OSError, StopIteration): + # os.walk found nothing + pass + else: + dirnames.sort() + + # no results, return None + if len(dirnames) == 0: + return + + # one result. since it must be a directory, append a slash. + if len(dirnames) == 1: + return line + join(rel_dirname, dirnames[0]) + '/' + + # more than one result. append no slash, so the user can + # manually type in the slash to advance into that directory + return (line + join(rel_dirname, dirname) for dirname in dirnames) + + def _tab_directory_content(self): + from os.path import dirname, basename, expanduser, join, isdir + + line = parse(self.line) + pwd = self.fm.env.pwd.path + + try: + rel_dest = line.rest(1) + except IndexError: + rel_dest = '' + + # expand the tilde into the user directory + if rel_dest.startswith('~'): + rel_dest = expanduser(rel_dest) + + # define some shortcuts + abs_dest = join(pwd, rel_dest) + abs_dirname = dirname(abs_dest) + rel_basename = basename(rel_dest) + rel_dirname = dirname(rel_dest) + + try: + # are we after a directory? + if rel_dest.endswith('/') or rel_dest == '': + _, dirnames, filenames = os.walk(abs_dest).next() + names = dirnames + filenames + + # are we in the middle of the filename? + else: + _, dirnames, filenames = os.walk(abs_dirname).next() + names = [name for name in (dirnames + filenames) \ + if name.startswith(rel_basename)] + except (OSError, StopIteration): + # os.walk found nothing + pass + else: + names.sort() + + # no results, return None + if len(names) == 0: + return + + # one result. since it must be a directory, append a slash. + if len(names) == 1: + return line + join(rel_dirname, names[0]) + '/' + + # more than one result. append no slash, so the user can + # manually type in the slash to advance into that directory + return (line + join(rel_dirname, name) for name in names) + + +# -------------------------------- definitions + +class cd(Command): + """ + :cd + + The cd command changes the directory. + The command 'cd -' is equivalent to typing ``. + + In the quick console, the directory will be entered without the + need to press enter, as soon as there is one unambiguous match. + """ + + def execute(self): + line = parse(self.line) + try: + destination = line.rest(1) + except IndexError: + destination = '~' + + if destination == '-': + self.fm.enter_bookmark('`') + else: + self.fm.cd(destination) + + def tab(self): + return self._tab_only_directories() + + def quick_open(self): + from os.path import isdir, join, normpath + line = parse(self.line) + pwd = self.fm.env.pwd.path + + rel_dest = line.rest(1) + if not rel_dest: + return False + + abs_dest = normpath(join(pwd, rel_dest)) + return rel_dest != '.' and isdir(abs_dest) + + +class find(Command): + """ + :find + + The find command will attempt to find a partial, case insensitive + match in the filenames of the current directory. + + In the quick command console, once there is one unambiguous match, + the file will be run automatically. + """ + + count = 0 + tab = Command._tab_directory_content + + def execute(self): + if self.mode != cmode.COMMAND_QUICK: + self._search() + + import re + search = parse(self.line).rest(1) + search = re.escape(search) + self.fm.env.last_search = re.compile(search, re.IGNORECASE) + + if self.count == 1: + self.fm.move_right() + self.fm.block_input(0.5) + + def quick_open(self): + self._search() + if self.count == 1: + return True + + def _search(self): + self.count = 0 + line = parse(self.line) + pwd = self.fm.env.pwd + try: + arg = line.rest(1) + except IndexError: + return False + + deq = deque(pwd.files) + deq.rotate(-pwd.pointer) + i = 0 + for fsobj in deq: + filename = fsobj.basename_lower + if arg in filename: + self.count += 1 + if self.count == 1: + pwd.move(absolute=(pwd.pointer + i) % len(pwd.files)) + self.fm.env.cf = pwd.pointed_obj + if self.count > 1: + return False + i += 1 + + return self.count == 1 + + +class quit(Command): + """ + :quit + + Quits the program immediately. + """ + + def execute(self): + raise SystemExit + + +class delete(Command): + """ + :delete + + Tries to delete the selection. + + "Selection" is defined as all the "marked files" (by default, you + can mark files with space or v). If there are no marked files, + use the "current file" (where the cursor is) + """ + + allow_abbrev = False + + def execute(self): + self.fm.delete() + + +class mkdir(Command): + """ + :mkdir + + Creates a directory with the name . + """ + + def execute(self): + from os.path import join, expanduser, lexists + from os import mkdir + + line = parse(self.line) + dirname = join(self.fm.env.pwd.path, expanduser(line.rest(1))) + if not lexists(dirname): + mkdir(dirname) + else: + self.fm.notify("file/directory exists!", bad=True) + + +class touch(Command): + """ + :touch + + Creates a file with the name . + """ + + def execute(self): + from os.path import join, expanduser, lexists + from os import mkdir + + line = parse(self.line) + fname = join(self.fm.env.pwd.path, expanduser(line.rest(1))) + if not lexists(fname): + open(fname, 'a') + else: + self.fm.notify("file/directory exists!", bad=True) + + +class edit(Command): + """ + :edit + + Opens the specified file in vim + """ + + def execute(self): + line = parse(self.line) + self.fm.edit_file(line.rest(1)) + + def tab(self): + return self._tab_directory_content() + + +class eval_(Command): + """ + :eval + + Evaluates the python code. + `fm' is a reference to the FM instance. + To display text, use the function `p'. + + Examples: + :eval fm + :eval len(fm.env.directories) + :eval p("Hello World!") + """ + name = 'eval' + + def execute(self): + code = parse(self.line).rest(1) + fm = self.fm + p = fm.notify + try: + try: + result = eval(code) + except SyntaxError: + exec(code) + else: + if result: + p(result) + except Exception as err: + p(err) + + +class rename(Command): + """ + :rename + + Changes the name of the currently highlighted file to + """ + + def execute(self): + from ranger.fsobject.file import File + line = parse(self.line) + self.fm.rename(self.fm.env.cf, line.rest(1)) + f = File(line.rest(1)) + self.fm.env.pwd.pointed_obj = f + self.fm.env.cf = f + + def tab(self): + return self._tab_directory_content() + + +class chmod(Command): + """ + :chmod + + Sets the permissions of the selection to the octal number. + + The octal number is between 0 and 777. The digits specify the + permissions for the user, the group and others. + + A 1 permits execution, a 2 permits writing, a 4 permits reading. + Add those numbers to combine them. So a 7 permits everything. + """ + + def execute(self): + line = parse(self.line) + mode = line.rest(1) + + try: + mode = int(mode, 8) + if mode < 0 or mode > 511: + raise ValueError + except ValueError: + self.fm.notify("Need an octal number between 0 and 777!", bad=True) + return + + for file in self.fm.env.get_selection(): + try: + os.chmod(file.path, mode) + except Exception as ex: + self.fm.notify(ex) + + try: + # reloading directory. maybe its better to reload the selected + # files only. + self.fm.env.pwd.load_content() + except: + pass + + +class filter(Command): + """ + :filter + + Displays only the files which contain in their basename. + """ + + def execute(self): + line = parse(self.line) + self.fm.set_filter(line.rest(1)) + + +class grep(Command): + """ + :grep + + Looks for a string in all marked files or directories + """ + + def execute(self): + line = parse(self.line) + if line.rest(1): + action = ['grep', '--color=always', '--line-number'] + action.extend(['-e', line.rest(1), '-r']) + action.extend(f.path for f in self.fm.env.get_selection()) + self.fm.execute_command(action, flags='p') + + +# -------------------------------- rest + +by_name = {} +for varname, var in vars().copy().items(): + try: + if issubclass(var, Command) and var != Command: + by_name[var.name or varname] = var + except TypeError: + pass +del varname +del var + +def alias(**kw): + """Create an alias for commands, eg: alias(quit=exit)""" + for key, value in kw.items(): + by_name[key] = value + +def get_command(name, abbrev=True): + if abbrev: + lst = [cls for cmd, cls in by_name.items() \ + if cmd.startswith(name) \ + and cls.allow_abbrev \ + or cmd == name] + if len(lst) == 0: + raise KeyError + if len(lst) == 1 or by_name[name] in lst: + return lst[0] + raise ValueError("Ambiguous command") + else: + try: + return by_name[name] + except KeyError: + return None + +def command_generator(start): + return (cmd + ' ' for cmd in by_name if cmd.startswith(start)) + +alias(e=edit) # to make :e unambiguous. + diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index a331c66d..2ffd1a62 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -23,7 +23,7 @@ import curses from collections import deque from . import Widget -from ranger import commands +from ranger.defaults import commands from ranger.gui.widgets.console_mode import is_valid_mode, mode_to_class from ranger import log from ranger.ext.shell_escape import shell_quote diff --git a/ranger/help/console.py b/ranger/help/console.py index 7ba15799..3a4428f3 100644 --- a/ranger/help/console.py +++ b/ranger/help/console.py @@ -127,7 +127,7 @@ one unambiguous match, will be pressed for you, giving you a very fast way to browse your files. -All commands are defined in ranger/commands.py. You can refer to this +All commands are defined in ranger/defaults/commands.py. You can refer to this file for a list of commands. Implementing new commands should be intuitive: Create a new class, a subclass of Command, and define the execute method is usually enough. For parsing command input, the command parser in -- cgit 1.4.1-2-gfad0