diff options
-rw-r--r-- | ranger/api/commands.py | 4 | ||||
-rw-r--r-- | ranger/api/keys.py | 1 | ||||
-rw-r--r-- | ranger/core/actions.py | 86 | ||||
-rw-r--r-- | ranger/defaults/commands.py | 155 | ||||
-rw-r--r-- | ranger/defaults/keys.py | 36 | ||||
-rw-r--r-- | ranger/gui/defaultui.py | 4 | ||||
-rw-r--r-- | ranger/gui/widgets/console.py | 448 | ||||
-rw-r--r-- | ranger/gui/widgets/console_mode.py | 55 |
8 files changed, 295 insertions, 494 deletions
diff --git a/ranger/api/commands.py b/ranger/api/commands.py index ca3f730d..f4e2ca76 100644 --- a/ranger/api/commands.py +++ b/ranger/api/commands.py @@ -17,7 +17,6 @@ import os from collections import deque from ranger.api import * from ranger.shared import FileManagerAware -from ranger.gui.widgets import console_mode as cmode from ranger.ext.command_parser import LazyParser as parse @@ -71,9 +70,8 @@ class Command(FileManagerAware): """Abstract command class""" name = None allow_abbrev = True - def __init__(self, line, mode): + def __init__(self, line): self.line = line - self.mode = mode def execute(self): """Override this""" diff --git a/ranger/api/keys.py b/ranger/api/keys.py index 13a4b07f..5812de39 100644 --- a/ranger/api/keys.py +++ b/ranger/api/keys.py @@ -20,7 +20,6 @@ from inspect import getargspec, ismethod from ranger import RANGERDIR from ranger.api import * -from ranger.gui.widgets import console_mode as cmode from ranger.container.bookmarks import ALLOWED_KEYS as ALLOWED_BOOKMARK_KEYS from ranger.container.keymap import KeyMap, Direction, KeyMapWithDirections diff --git a/ranger/core/actions.py b/ranger/core/actions.py index a298b304..c93fa0a5 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -16,19 +16,25 @@ import os import re import shutil +import string from os.path import join, isdir from os import symlink, getcwd from inspect import cleandoc import ranger from ranger.ext.direction import Direction +from ranger.ext.shell_escape import shell_quote from ranger import fsobject from ranger.shared import FileManagerAware, EnvironmentAware, SettingsAware -from ranger.gui.widgets import console_mode as cmode from ranger.fsobject import File from ranger.ext import shutil_generatorized as shutil_g from ranger.core.loader import LoadableObject +class _MacroTemplate(string.Template): + """A template for substituting macros in commands""" + delimiter = '%' + idpattern = '\d?[a-z]' + class Actions(FileManagerAware, EnvironmentAware, SettingsAware): search_method = 'ctime' search_forward = False @@ -69,17 +75,80 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): """Redraw the window""" self.ui.redraw_window() - def open_console(self, mode=cmode.COMMAND, string='', prompt=None): + def open_console(self, string='', prompt=None, position=None): """Open the console if the current UI supports that""" if hasattr(self.ui, 'open_console'): - self.ui.open_console(mode, string, prompt=prompt) + self.ui.open_console(string, prompt=prompt, position=position) - def execute_console(self, string='', mode=cmode.COMMAND): + def execute_console(self, string=''): """Execute a command for the console""" - self.open_console(mode=mode, string=string) + self.open_console(string=string) self.ui.console.line = string self.ui.console.execute() + def substitute_metachars(self, string): + return _MacroTemplate(string).safe_substitute(self._get_macros()) + + def _get_macros(self): + macros = {} + + if self.fm.env.cf: + macros['f'] = shell_quote(self.fm.env.cf.basename) + else: + macros['f'] = '' + + macros['s'] = ' '.join(shell_quote(fl.basename) \ + for fl in self.fm.env.get_selection()) + + macros['c'] = ' '.join(shell_quote(fl.path) + for fl in self.fm.env.copy) + + macros['t'] = ' '.join(shell_quote(fl.basename) + for fl in self.fm.env.cwd.files + if fl.realpath in self.fm.tags) + + if self.fm.env.cwd: + macros['d'] = shell_quote(self.fm.env.cwd.path) + else: + macros['d'] = '.' + + # define d/f/s macros for each tab + for i in range(1,10): + try: + tab_dir_path = self.fm.tabs[i] + except: + continue + tab_dir = self.fm.env.get_directory(tab_dir_path) + i = str(i) + macros[i + 'd'] = shell_quote(tab_dir_path) + macros[i + 'f'] = shell_quote(tab_dir.pointed_obj.path) + macros[i + 's'] = ' '.join(shell_quote(fl.path) + for fl in tab_dir.get_selection()) + + # define D/F/S for the next tab + found_current_tab = False + next_tab_path = None + first_tab = None + for tab in self.fm.tabs: + if not first_tab: + first_tab = tab + if found_current_tab: + next_tab_path = self.fm.tabs[tab] + break + if self.fm.current_tab == tab: + found_current_tab = True + if found_current_tab and not next_tab_path: + next_tab_path = self.fm.tabs[first_tab] + next_tab = self.fm.env.get_directory(next_tab_path) + + macros['D'] = shell_quote(next_tab) + macros['F'] = shell_quote(next_tab.pointed_obj.path) + macros['S'] = ' '.join(shell_quote(fl.path) + for fl in next_tab.get_selection()) + + return macros + + def execute_file(self, files, **kw): """Execute a file. app is the name of a method in Applications, without the "app_" @@ -134,7 +203,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): 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.open_console('open_with ') elif direction.vertical(): newpos = direction.move( direction=direction.down(), @@ -297,7 +366,10 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): def search_file(self, text, regexp=True): if isinstance(text, str) and regexp: - text = re.compile(text, re.L | re.U | re.I) + try: + text = re.compile(text, re.L | re.U | re.I) + except: + return False self.env.last_search = text self.search(order='search') diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py index 8728f9be..acfbfffb 100644 --- a/ranger/defaults/commands.py +++ b/ranger/defaults/commands.py @@ -55,6 +55,8 @@ For a list of all actions, check /ranger/core/actions.py. ''' from ranger.api.commands import * +from ranger.ext.get_executables import get_executables +from ranger.core.runner import ALLOWED_FLAGS alias('e', 'edit') alias('q', 'quit') @@ -100,6 +102,154 @@ class cd(Command): return rel_dest != '.' and isdir(abs_dest) +class search(Command): + def execute(self): + self.fm.search_file(parse(self.line).rest(1), regexp=True) + + +class shell(Command): + def execute(self): + line = parse(self.line) + if line.chunk(1)[0] == '-': + flags = line.chunk(1)[1:] + command = line.rest(2) + else: + flags = '' + command = line.rest(1) + + if not command and 'p' in flags: command = 'cat %f' + if command: + if '%' in command: + command = self.fm.substitute_metachars(command) + self.fm.execute_command(command, flags=flags) + + def tab(self): + line = parse(self.line) + if line.chunk(1)[0] == '-': + flags = line.chunk(1)[1:] + command = line.rest(2) + else: + flags = '' + command = line.rest(1) + start = self.line[0:len(self.line) - len(command)] + + try: + position_of_last_space = command.rindex(" ") + except ValueError: + return (start + program + ' ' for program \ + in get_executables() if program.startswith(command)) + if position_of_last_space == len(command) - 1: + return self.line + '%s ' + else: + before_word, start_of_word = self.line.rsplit(' ', 1) + return (before_word + ' ' + file.shell_escaped_basename \ + for file in self.fm.env.cwd.files \ + if file.shell_escaped_basename.startswith(start_of_word)) + +class open_with(Command): + def execute(self): + line = parse(self.line) + app, flags, mode = self._get_app_flags_mode(line.rest(1)) + self.fm.execute_file( + files = [self.fm.env.cf], + app = app, + flags = flags, + mode = mode) + + def _get_app_flags_mode(self, string): + """ + Extracts the application, flags and mode from a string. + + examples: + "mplayer d 1" => ("mplayer", "d", 1) + "aunpack 4" => ("aunpack", "", 4) + "p" => ("", "p", 0) + "" => None + """ + + app = '' + flags = '' + mode = 0 + split = string.split() + + if len(split) == 0: + pass + + elif len(split) == 1: + part = split[0] + if self._is_app(part): + app = part + elif self._is_flags(part): + flags = part + elif self._is_mode(part): + mode = part + + elif len(split) == 2: + part0 = split[0] + part1 = split[1] + + if self._is_app(part0): + app = part0 + if self._is_flags(part1): + flags = part1 + elif self._is_mode(part1): + mode = part1 + elif self._is_flags(part0): + flags = part0 + if self._is_mode(part1): + mode = part1 + elif self._is_mode(part0): + mode = part0 + if self._is_flags(part1): + flags = part1 + + elif len(split) >= 3: + part0 = split[0] + part1 = split[1] + part2 = split[2] + + if self._is_app(part0): + app = part0 + if self._is_flags(part1): + flags = part1 + if self._is_mode(part2): + mode = part2 + elif self._is_mode(part1): + mode = part1 + if self._is_flags(part2): + flags = part2 + elif self._is_flags(part0): + flags = part0 + if self._is_mode(part1): + mode = part1 + elif self._is_mode(part0): + mode = part0 + if self._is_flags(part1): + flags = part1 + + return app, flags, int(mode) + + def _get_tab(self): + line = parse(self.line) + data = line.rest(1) + if ' ' not in data: + all_apps = self.fm.apps.all() + if all_apps: + return (app for app in all_apps if app.startswith(data)) + + return None + + def _is_app(self, arg): + return self.fm.apps.has(arg) or \ + (not self._is_flags(arg) and arg in get_executables()) + + def _is_flags(self, arg): + return all(x in ALLOWED_FLAGS for x in arg) + + def _is_mode(self, arg): + return all(x in '0123456789' for x in arg) + + class find(Command): """ :find <string> @@ -115,9 +265,6 @@ class find(Command): 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) @@ -281,7 +428,7 @@ class delete(Command): and len(os.listdir(cf.path)) > 0): # better ask for a confirmation, when attempting to # delete multiple files or a non-empty directory. - return self.fm.open_console(self.mode, DELETE_WARNING) + return self.fm.open_console(DELETE_WARNING) # no need for a confirmation, just delete self.fm.delete() diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py index 0806a494..e72f4c91 100644 --- a/ranger/defaults/keys.py +++ b/ranger/defaults/keys.py @@ -130,8 +130,8 @@ map('<F3>', fm.display_file()) map('<F4>', fm.edit_file()) map('<F5>', fm.copy()) map('<F6>', fm.cut()) -map('<F7>', fm.open_console(cmode.COMMAND, 'mkdir ')) -map('<F8>', fm.open_console(cmode.COMMAND, DELETE_WARNING)) +map('<F7>', fm.open_console('mkdir ')) +map('<F8>', fm.open_console(DELETE_WARNING)) map('<F10>', fm.exit()) # =================================================================== @@ -185,8 +185,7 @@ map('ud', 'uy', fm.uncut()) # ---------------------------------------------------- run programs map('S', fm.execute_command(os.environ['SHELL'])) map('E', fm.edit_file()) -map('du', fm.execute_console('p!du --max-depth=1 -h --apparent-size', - cmode.OPEN)) +map('du', fm.execute_console('shell -p du --max-depth=1 -h --apparent-size')) # -------------------------------------------------- toggle options map('z<bg>', fm.hint("[*cdfhimpPs*] show_*h*idden *p*review_files "\ @@ -199,7 +198,7 @@ map('zd', fm.toggle_boolean_option('sort_directories_first')) map('zc', fm.toggle_boolean_option('collapse_preview')) map('zs', fm.toggle_boolean_option('sort_case_insensitive')) map('zm', fm.toggle_boolean_option('mouse_enabled')) -map('zf', fm.open_console(cmode.COMMAND, 'filter ')) +map('zf', fm.open_console('filter ')) # ------------------------------------------------------------ sort map('o<bg>', 'O<bg>', fm.hint("*s*ize *b*ase*n*ame *m*time" \ @@ -225,19 +224,19 @@ map('or', 'Or', 'oR', 'OR', lambda arg: \ @map("A") def append_to_filename(arg): command = 'rename ' + arg.fm.env.cf.basename - arg.fm.open_console(cmode.COMMAND, command) + arg.fm.open_console(command) @map("I") def insert_before_filename(arg): - append_to_filename(arg) - arg.fm.ui.console.move(right=len('rename '), absolute=True) + command = 'rename ' + arg.fm.env.cf.basename + arg.fm.open_console(command, position=len('rename ')) -map('cw', fm.open_console(cmode.COMMAND, 'rename ')) -map('cd', fm.open_console(cmode.COMMAND, 'cd ')) -map('f', fm.open_console(cmode.COMMAND_QUICK, 'find ')) +map('cw', fm.open_console('rename ')) +map('cd', fm.open_console('cd ')) +map('f', fm.open_console('find ')) map('d<bg>', fm.hint('d*u* (disk usage) d*d* (cut)')) -map('@', fm.open_console(cmode.OPEN, '@')) -map('#', fm.open_console(cmode.OPEN, 'p!')) +map('@', fm.open_console('shell %s', position=len('shell '))) +map('#', fm.open_console('shell -p ')) # --------------------------------------------- jump to directories map('gh', fm.cd('~')) @@ -268,7 +267,7 @@ for n in range(1, 10): map('<A-' + str(n) + '>', fm.tab_open(n)) # ------------------------------------------------------- searching -map('/', fm.open_console(cmode.SEARCH)) +map('/', fm.open_console('search ')) map('n', fm.search()) map('N', fm.search(forward=False)) @@ -307,11 +306,10 @@ def ctrl_c(arg): arg.fm.notify("Aborting: " + item.get_description()) arg.fm.loader.remove(index=0) -map(':', ';', fm.open_console(cmode.COMMAND)) -map('>', fm.open_console(cmode.COMMAND_QUICK)) -map('!', fm.open_console(cmode.OPEN, prompt='!')) -map('s', fm.open_console(cmode.OPEN, prompt='$')) -map('r', fm.open_console(cmode.OPEN_QUICK)) +map(':', ';', fm.open_console('')) +map('!', fm.open_console('shell ')) +map('s', fm.open_console('shell ')) +map('r', fm.open_console('open_with ')) # =================================================================== diff --git a/ranger/gui/defaultui.py b/ranger/gui/defaultui.py index 4baea756..434e6d45 100644 --- a/ranger/gui/defaultui.py +++ b/ranger/gui/defaultui.py @@ -92,8 +92,8 @@ class DefaultUI(UI): def close_embedded_pager(self): self.browser.close_pager() - def open_console(self, mode, string='', prompt=None): - if self.console.open(mode, string, prompt=prompt): + def open_console(self, string='', prompt=None, position=None): + if self.console.open(string, prompt=prompt, position=position): self.status.msg = None self.console.on_close = self.close_console self.console.visible = True diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index 85b92548..9d0ea75f 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -18,38 +18,21 @@ The Console widget implements a vim-like console for entering commands, searching and executing files. """ -import string import curses import re from collections import deque from . import Widget -from ranger.gui.widgets.console_mode import is_valid_mode, mode_to_class from ranger import log, relpath_conf -from ranger.core.runner import ALLOWED_FLAGS -from ranger.ext.shell_escape import shell_quote from ranger.ext.utfwidth import uwid from ranger.container.keymap import CommandArgs -from ranger.ext.get_executables import get_executables from ranger.ext.direction import Direction from ranger.ext.utfwidth import uwid, uchars from ranger.container import History from ranger.container.history import HistoryEmptyException import ranger -DEFAULT_HISTORY = 0 -SEARCH_HISTORY = 1 -QUICKOPEN_HISTORY = 2 -OPEN_HISTORY = 3 - -class _CustomTemplate(string.Template): - """A string.Template subclass for use in the OpenConsole""" - delimiter = '%'normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ |