summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--ranger/api/commands.py4
-rw-r--r--ranger/api/keys.py1
-rw-r--r--ranger/core/actions.py86
-rw-r--r--ranger/defaults/commands.py155
-rw-r--r--ranger/defaults/keys.py36
-rw-r--r--ranger/gui/defaultui.py4
-rw-r--r--ranger/gui/widgets/console.py448
-rw-r--r--ranger/gui/widgets/console_mode.py55
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 */
Ranger v.1.0.4
==============

Ranger is a free console file manager that gives you greater flexibility
and a good overview of your files without having to leave your *nix console.
It visualizes the directory tree in two dimensions: the directory hierarchy
on one, lists of files on the other, with a preview to the right so you know
where you'll be going.  The default keys are similar to those of Vim, Emacs
and Midnight Commander, though Ranger is easily controllable with just the
arrow keys or the mouse.

The program is written in Python (2.6 or 3.1) and uses curses for the
text-based user interface.


About
-----

* Author:          Roman Zimbelmann  <romanz@lavabit.com>
* Website:         http://savannah.nongnu.org/projects/ranger
* License:         GNU General Public License Version 3
* Version:         1.0.4

* Download URL of the newest stable version:
http://git.savannah.gnu.org/cgit/ranger.git/snapshot/ranger-stable.tar.gz

* Git Clone URL:
git clone http://git.sv.gnu.org/r/ranger.git


Features
--------

* Multi-column display (Miller Columns)
* Preview of the selected file/directory
* Common file operations (create/chmod/copy/delete/...)
* VIM-like console and hotkeys
* Automatically determine file types and run them with correct programs
* Change the directory of your shell after exiting ranger
* Tabs, Bookmarks, Mouse support


Dependencies
------------

* A *nix-like operating system
* Python 2.6 or Python 3.1 with the curses module

Optional:
* The "file" program
* A pager ("less" by default)


Getting Started
---------------

Ranger can be started without installing.  Just run the executable (in
a terminal.)  The switch "--clean" will prevent it from creating or
accessing configuration files.

Follow the instructions in the INSTALL file for installing ranger.

After starting ranger, you should see 4 columns. The third one is the main
column, the directory where you're currently at.  To the left you see the
parent directories and to the right there's a preview of the object you're
pointing at.  Now use the Arrow Keys to navigate, Enter to open a file
or type Q to quit.

To customize ranger, copy the files from ranger/defaults/ to ~/.ranger/
and modify them according to your wishes.


Troubleshooting, Getting Help
-----------------------------

If you encounter an error, try running ranger with --debug.  This will
sometimes display more detailed information about the error.  Also, try
deactivating optimization:

PYTHONOPTIMIZE="" ranger --debug

Report bugs on savannah:  (please include as much information as possible)
http://savannah.nongnu.org/bugs/?func=additem&group=ranger

Ask questions on the mailing list:
http://lists.nongnu.org/mailman/listinfo/ranger-users


Further Reading
---------------

Check the man page for information on common features and hotkeys.

The most detailed manual is accessible by pressing "?" from inside ranger.
It is also available at ranger/help/, contained in the *.py files.

The file ranger/defaults/keys.py contains all key combinations, so that's
another place you may want to check out.
an> @@ -356,7 +277,7 @@ class CommandConsole(ConsoleWithTab): except: return None else: - return command_class(self.line, self.mode) + return command_class(self.line) def _get_cmd_class(self): return self.fm.commands.get_command(self.line.split()[0]) @@ -371,313 +292,34 @@ class CommandConsole(ConsoleWithTab): return self.fm.commands.command_generator(self.line) + def tab(self, n=1): + if self.tab_deque is None: + tab_result = self._get_tab() + + if isinstance(tab_result, str): + self.line = tab_result + self.pos = len(tab_result) + self.on_line_change() + + elif tab_result == None: + pass + + elif hasattr(tab_result, '__iter__'): + self.tab_deque = deque(tab_result) + self.tab_deque.appendleft(self.line) + + if self.tab_deque is not None: + self.tab_deque.rotate(-n) + self.line = self.tab_deque[0] + self.pos = len(self.line) + self.on_line_change() -class QuickCommandConsole(CommandConsole): - """ - The QuickCommandConsole is essentially the same as the - CommandConsole, and includes one additional feature: - After each letter you type, it checks whether the command as it - stands there could be executed in a meaningful way, and if it does, - run it right away. - - Example: - >cd .. - As you type the last dot, The console will recognize what you mean - and enter the parent directory saving you the time of pressing enter. - """ - prompt = '>' def on_line_change(self): try: cls = self._get_cmd_class() except (KeyError, ValueError, IndexError): pass else: - cmd = cls(self.line, self.mode) + cmd = cls(self.line) if cmd and cmd.quick(): self.execute(cmd) - - -class SearchConsole(Console): - prompt = '/' - - def init(self): - self.history = self.histories[SEARCH_HISTORY] - - def execute(self): - self.fm.search_file(self.line, regexp=True) - self.close() - - -class OpenConsole(ConsoleWithTab): - """ - The Open Console allows you to execute shell commands: - !vim * will run vim and open all files in the directory. - - %f will be replaced with the basename of the highlighted file - %s will be selected with all files in the selection - - There is a special syntax for more control: - - !d! mplayer will run mplayer with flags (d means detached) - !@ mplayer will open the selected files with mplayer - (equivalent to !mplayer %s) - - Those two can be combinated: - - !d!@mplayer will open the selection with a detached mplayer - (again, this is equivalent to !d!mplayer %s) - - For a list of other flags than "d", check chapter 2.5 of the documentation - """ - prompt = '!' - - def init(self): - self.history = self.histories[OPEN_HISTORY] - - def execute(self): - command, flags = self._parse() - if not command and 'p' in flags: - command = 'cat %f' - if command: - if _CustomTemplate.delimiter in command: - command = self._substitute_metachars(command) - self.fm.execute_command(command, flags=flags) - self.close() - - def _get_tab(self): - try: - i = self.line.index('!')+1 - except ValueError: - line = self.line - start = '' - else: - line = self.line[i:] - start = self.line[:i] - - try: - position_of_last_space = line.rindex(" ") - except ValueError: - return (start + program + ' ' for program \ - in get_executables() if program.startswith(line)) - if position_of_last_space == len(line) - 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)) - - def _substitute_metachars(self, command): - 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) - if tab_dir.pointed_obj: - 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) - if next_tab.pointed_obj: - macros['F'] = shell_quote(next_tab.pointed_obj.path) - macros['S'] = ' '.join(shell_quote(fl.path) - for fl in next_tab.get_selection()) - - return _CustomTemplate(command).safe_substitute(macros) - - def _parse(self): - if '!' in self.line: - flags, cmd = self.line.split('!', 1) - else: - flags, cmd = '', self.line - - add_selection = False - if cmd.startswith('@'): - cmd = cmd[1:] - add_selection = True - elif flags.startswith('@'): - flags = flags[1:] - add_selection = True - - if add_selection: - cmd += ' ' + ' '.join(shell_quote(fl.basename) \ - for fl in self.env.get_selection()) - - return (cmd, flags) - - -class QuickOpenConsole(ConsoleWithTab): - """ - The Quick Open Console allows you to open files with predefined programs - and modes very quickly. By adding flags to the command, you can specify - precisely how the program is run, e.g. the d-flag will run it detached - from the file manager. - - For a list of other flags than "d", check chapter 2.5 of the documentation - - The syntax is "open with: <application> <mode> <flags>". - The parsing of the arguments is very flexible. You can leave out one or - more arguments (or even all of them) and it will fall back to default - values. You can switch the order as well. - There is just one rule: - - If you supply the <application>, it has to be the first argument. - - Examples: - - open with: mplayer D open the selection in mplayer, but not detached - open with: 1 open it with the default handler in mode 1 - open with: d open it detached with the default handler - open with: p open it as usual, but pipe the output to "less" - open with: totem 1 Ds open in totem in mode 1, will not detach the - process (flag D) but discard the output (flag s) - """ - - prompt = 'open with: ' - - def init(self): - self.history = self.histories[QUICKOPEN_HISTORY] - - def execute(self): - split = self.line.split() - app, flags, mode = self._get_app_flags_mode() - self.fm.execute_file( - files = [self.env.cf], - app = app, - flags = flags, - mode = mode ) - self.close() - - def _get_app_flags_mode(self): - """ - Extracts the application, flags and mode from - a string entered into the "openwith_quick" console. - """ - # examples: - # "mplayer d 1" => ("mplayer", "d", 1) - # "aunpack 4" => ("aunpack", "", 4) - # "p" => ("", "p", 0) - # "" => None - - app = '' - flags = '' - mode = 0 - split = self.line.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): - if ' ' not in self.line: - all_apps = self.fm.apps.all() - if all_apps: - return (app for app in all_apps if app.startswith(self.line)) - - 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) diff --git a/ranger/gui/widgets/console_mode.py b/ranger/gui/widgets/console_mode.py deleted file mode 100644 index c29f9959..00000000 --- a/ranger/gui/widgets/console_mode.py +++ /dev/null @@ -1,55 +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/>. - -DEFAULT = 0 -COMMAND = 1 -COMMAND_QUICK = 2 -OPEN = 3 -OPEN_QUICK = 4 -SEARCH = 5 - -def is_valid_mode(mode): - """ - Returns True or False depending on whether the mode is valid or not. - """ - return isinstance(mode, int) and mode >= 0 and mode <= 5 - -def all_modes(mode): - """ - Returns a generator containing all valid modes. - """ - return range(6) - -def mode_to_class(mode): - """ - Associates modes with the actual classes - from ranger.gui.widgets.console. - """ - from .console import Console, CommandConsole, OpenConsole, \ - QuickOpenConsole, QuickCommandConsole, SearchConsole - - if mode == DEFAULT: - return Console - if mode == COMMAND: - return CommandConsole - if mode == OPEN: - return OpenConsole - if mode == OPEN_QUICK: - return QuickOpenConsole - if mode == COMMAND_QUICK: - return QuickCommandConsole - if mode == SEARCH: - return SearchConsole - raise ValueError