summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xranger.py2
-rw-r--r--ranger/api/commands.py3
-rw-r--r--ranger/api/keys.py1
-rw-r--r--ranger/container/settingobject.py1
-rw-r--r--ranger/container/tags.py60
-rw-r--r--ranger/core/actions.py6
-rw-r--r--ranger/core/main.py5
-rw-r--r--ranger/data/mime.types2
-rwxr-xr-xranger/data/scope.sh9
-rw-r--r--ranger/defaults/commands.py1
-rw-r--r--ranger/defaults/keys.py27
-rw-r--r--ranger/defaults/options.py3
-rw-r--r--ranger/ext/keybinding_parser.py5
-rw-r--r--ranger/fsobject/directory.py1
-rw-r--r--ranger/gui/defaultui.py1
-rw-r--r--ranger/gui/ui.py8
-rw-r--r--ranger/gui/widgets/browsercolumn.py19
-rw-r--r--ranger/gui/widgets/console.py13
18 files changed, 125 insertions, 42 deletions
diff --git a/ranger.py b/ranger.py
index ffdf13aa..53fd8bdb 100755
--- a/ranger.py
+++ b/ranger.py
@@ -48,7 +48,7 @@ sys.dont_write_bytecode = '-c' in argv or '--clean' in argv
 __doc__ = """Ranger - file browser for the unix terminal"""
 
 # Don't import ./ranger when running an installed binary at /usr/bin/ranger
-if os.path.isdir('ranger'):
+if os.path.exists('ranger') and '/' in os.path.normpath(__file__):
 	try:
 		sys.path.remove(os.path.abspath('.'))
 	except:
diff --git a/ranger/api/commands.py b/ranger/api/commands.py
index 80827c4f..9a353eef 100644
--- a/ranger/api/commands.py
+++ b/ranger/api/commands.py
@@ -85,6 +85,9 @@ class Command(FileManagerAware):
 	def quick(self):
 		"""Override this"""
 
+	def cancel(self):
+		"""Override this"""
+
 	# COMPAT: this is still used in old commands.py configs
 	def _tab_only_directories(self):
 		from os.path import dirname, basename, expanduser, join, isdir
diff --git a/ranger/api/keys.py b/ranger/api/keys.py
index 7ba05c73..75de6237 100644
--- a/ranger/api/keys.py
+++ b/ranger/api/keys.py
@@ -21,6 +21,7 @@ from inspect import getargspec, ismethod
 from ranger import RANGERDIR
 from ranger.api import *
 from ranger.container.bookmarks import ALLOWED_KEYS as ALLOWED_BOOKMARK_KEYS
+from ranger.container.tags import ALLOWED_KEYS as ALLOWED_TAGS_KEYS
 from ranger.container.keymap import KeyMap, Direction, KeyMapWithDirections
 
 # A dummy that allows the generation of docstrings in ranger.defaults.keys
diff --git a/ranger/container/settingobject.py b/ranger/container/settingobject.py
index c56a18b5..d036245f 100644
--- a/ranger/container/settingobject.py
+++ b/ranger/container/settingobject.py
@@ -26,6 +26,7 @@ ALLOWED_SETTINGS = {
 	'dirname_in_tabs': bool,
 	'display_size_in_main_column': bool,
 	'display_size_in_status_bar': bool,
+	'display_tags_in_all_columns': bool,
 	'draw_bookmark_borders': bool,
 	'draw_borders': bool,
 	'flushinput': bool,
diff --git a/ranger/container/tags.py b/ranger/container/tags.py
index 9ef8a1b2..c2fe3067 100644
--- a/ranger/container/tags.py
+++ b/ranger/container/tags.py
@@ -14,8 +14,13 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from os.path import isdir, exists, dirname, abspath, realpath, expanduser
+import string
+
+ALLOWED_KEYS = string.ascii_letters + string.digits + string.punctuation
 
 class Tags(object):
+	default_tag = '*'
+
 	def __init__(self, filename):
 
 		self._filename = realpath(abspath(expanduser(filename)))
@@ -28,33 +33,47 @@ class Tags(object):
 	def __contains__(self, item):
 		return item in self.tags
 
-	def add(self, *items):
+	def add(self, *items, **others):
+		if 'tag' in others:
+			tag = others['tag']
+		else:
+			tag = self.defautag
 		self.sync()
 		for item in items:
-			self.tags.add(item)
+			self.tags[item] = tag
 		self.dump()
 
 	def remove(self, *items):
 		self.sync()
 		for item in items:
 			try:
-				self.tags.remove(item)
+				del(self.tags[item])
 			except KeyError:
 				pass
 		self.dump()
 
-	def toggle(self, *items):
+	def toggle(self, *items, **others):
+		if 'tag' in others:
+			tag = others['tag']
+		else:
+			tag = self.default_tag
 		self.sync()
 		for item in items:
-			if item in self:
-				try:
-					self.tags.remove(item)
-				except KeyError:
-					pass
-			else:
-				self.tags.add(item)
+			try:
+				if item in self and tag in (self.tags[item], self.default_tag):
+					del(self.tags[item])
+				else:
+					self.tags[item] = tag
+			except KeyError:
+				pass
 		self.dump()
 
+	def marker(self, item):
+		if item in self.tags:
+			return self.tags[item]
+		else:
+			return self.default_tag
+
 	def sync(self):
 		try:
 			f = open(self._filename, 'r')
@@ -74,13 +93,24 @@ class Tags(object):
 			f.close()
 
 	def _compile(self, f):
-		for line in self.tags:
-			f.write(line + '\n')
+		for path, tag in self.tags.items():
+			if tag == self.default_tag:
+				# COMPAT: keep the old format if the default tag is used
+				f.write(path + '\n')
+			elif tag in ALLOWED_KEYS:
+				f.write('{0}:{1}\n'.format(tag, path))
 
 	def _parse(self, f):
-		result = set()
+		result = dict()
 		for line in f:
-			result.add(line.strip())
+			line = line.strip()
+			if len(line) > 2 and line[1] == ':':
+				tag, path = line[0], line[2:]
+				if tag in ALLOWED_KEYS:
+					result[path] = tag
+			else:
+				result[line] = self.default_tag
+
 		return result
 
 	def __nonzero__(self):
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 89bd9389..cb692d4c 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -430,7 +430,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	# Tags are saved in ~/.config/ranger/tagged and simply mark if a
 	# file is important to you in any context.
 
-	def tag_toggle(self, paths=None, value=None, movedown=None):
+	def tag_toggle(self, paths=None, value=None, movedown=None, tag=None):
 		if not self.tags:
 			return
 		if paths is None:
@@ -438,11 +438,11 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		else:
 			tags = [realpath(path) for path in paths]
 		if value is True:
-			self.tags.add(*tags)
+			self.tags.add(*tags, tag=tag or self.tags.default_tag)
 		elif value is False:
 			self.tags.remove(*tags)
 		else:
-			self.tags.toggle(*tags)
+			self.tags.toggle(*tags, tag=tag or self.tags.default_tag)
 
 		if movedown is None:
 			movedown = len(tags) == 1 and paths is None
diff --git a/ranger/core/main.py b/ranger/core/main.py
index ade3bab5..e6392387 100644
--- a/ranger/core/main.py
+++ b/ranger/core/main.py
@@ -24,10 +24,15 @@ def main():
 	import locale
 	import os.path
 	import ranger
+	import sys
 	from ranger.core.shared import (EnvironmentAware, FileManagerAware,
 			SettingsAware)
 	from ranger.core.fm import FM
 
+	if not sys.stdin.isatty():
+		sys.stderr.write("Error: Must run ranger from terminal\n")
+		raise SystemExit(1)
+
 	try:
 		locale.setlocale(locale.LC_ALL, '')
 	except:
diff --git a/ranger/data/mime.types b/ranger/data/mime.types
index 35e34fdc..14c35a9a 100644
--- a/ranger/data/mime.types
+++ b/ranger/data/mime.types
@@ -13,8 +13,10 @@
 audio/flac					flac
 audio/musepack					mpc mpp mp+
 audio/ogg					oga ogg spx
+audio/wavpack				wv wvc
 
 video/mkv					mkv
+video/webm					webm
 video/flash					flv
 video/ogg					ogv ogm
 video/divx					div divx
diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh
index 4d778486..ca1f7e67 100755
--- a/ranger/data/scope.sh
+++ b/ranger/data/scope.sh
@@ -30,19 +30,24 @@ extension=$(echo "$path" | grep '\.' | grep -o '[^.]\+$')
 # Functions:
 # "have $1" succeeds if $1 is an existing command/installed program
 function have { type -P "$1" > /dev/null; }
-# "sucess" returns the exit code of the first program in the last pipe chain
+# "success" returns the exit code of the first program in the last pipe chain
 function success { test ${PIPESTATUS[0]} = 0; }
 
 case "$extension" in
 	# Archive extensions:
 	7z|a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\
 	rar|rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z|zip)
-		atool -l "$path" | head -n $maxln && exit 3
+		als "$path" | head -n $maxln
+		success && exit 0 || acat "$path" | head -n $maxln && exit 3
 		exit 1;;
 	# PDF documents:
 	pdf)
 		pdftotext -l 10 -nopgbrk -q "$path" - | head -n $maxln | fmt -s -w $width
 		success && exit 0 || exit 1;;
+	# BitTorrent Files
+	torrent)
+		transmission-show "$path" | head -n $maxln && exit 3
+		success && exit 5 || exit 1;;
 	# HTML Pages:
 	htm|html|xhtml)
 		have w3m    && w3m    -dump "$path" | head -n $maxln | fmt -s -w $width && exit 4
diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py
index e9572d3e..ad3fa0e5 100644
--- a/ranger/defaults/commands.py
+++ b/ranger/defaults/commands.py
@@ -19,6 +19,7 @@ This is the default file for command definitions.
 Each command is a subclass of `Command'.  Several methods are defined
 to interface with the console:
 	execute: call this method when the command is executed.
+	cancel: call this method when closing the console without executing.
 	tab: call this method when tab is pressed.
 	quick: call this method after each keypress.
 
diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py
index ea98e4c5..cd1c5d07 100644
--- a/ranger/defaults/keys.py
+++ b/ranger/defaults/keys.py
@@ -159,6 +159,8 @@ map('L', fm.history_go(1))
 # ----------------------------------------------- tagging / marking
 map('t', fm.tag_toggle())
 map('T', fm.tag_remove())
+for key in ALLOWED_TAGS_KEYS:
+		map('"' + key, fm.tag_toggle(tag=key))
 
 map(' ', fm.mark(toggle=True))
 map('v', fm.mark(all=True, toggle=True))
@@ -167,22 +169,26 @@ map('<C-V><dir>', fm.mark_in_direction(val=True))
 map('u<C-V><dir>', fm.mark_in_direction(val=False))
 
 # ------------------------------------------ file system operations
+map('y<bg>', fm.hint('*copy:* cop*y* *a*dd *r*emove ' \
+	'*p*ath_to_xsel *d*irpath_to_xsel base*n*ame_to_xsel'))
 map('yy', 'y<dir>', fm.copy())
 map('ya', fm.copy(mode='add'))
 map('yr', fm.copy(mode='remove'))
 map('yp', fm.execute_console('shell -d echo -n %d/%f | xsel -i'))
 map('yd', fm.execute_console('shell -d echo -n %d | xsel -i'))
 map('yn', fm.execute_console('shell -d echo -n %f | xsel -i'))
+map('d<bg>', fm.hint('disk_*u*sage *cut:* *d*:cut *a*dd *r*emove'))
 map('dd', 'd<dir>', fm.cut())
 map('da', fm.cut(mode='add'))
 map('dr', fm.cut(mode='remove'))
+map('p<bg>', fm.hint('*paste:* *p*aste *o*verwrite sym*l*inks ' \
+		'*h*ardlinks relative_sym*L*inks'))
 map('pp', fm.paste())
 map('po', fm.paste(overwrite=True))
 map('pl', fm.paste_symlink(relative=False))
 map('pL', fm.paste_symlink(relative=True))
+map('ph<bg>', fm.hint('*paste:* hard*l*inks'))
 map('phl', fm.paste_hardlink())
-map('p<bg>', fm.hint('press *p* to confirm pasting' \
-		', *o*verwrite, create sym*l*inks, relative sym*L*inks'))
 
 map('u<bg>', fm.hint("un*y*ank, unbook*m*ark, unselect:*v*"))
 map('ud', 'uy', fm.uncut())
@@ -193,8 +199,9 @@ map('E', fm.edit_file())
 map('du', fm.execute_console('shell -p du --max-depth=1 -h --apparent-size'))
 
 # -------------------------------------------------- toggle options
-map('z<bg>', fm.hint("[*cdfhimpPsv*] show_*h*idden *p*review_files "\
-		"*P*review_dirs *f*ilter flush*i*nput *m*ouse"))
+map('z<bg>', fm.hint('*f*ilter *options:* *d*irectories_first *c*ollape_preview ' \
+		'*s*ort_case_insensitive show_*h*idden *p*review_files '\
+		'*P*review_dirs use_pre*v*iew_script flush*i*nput *m*ouse'))
 map('zh', '<C-h>', fm.toggle_boolean_option('show_hidden'))
 map('zp', fm.toggle_boolean_option('preview_files'))
 map('zP', fm.toggle_boolean_option('preview_directories'))
@@ -207,13 +214,14 @@ map('zm', fm.toggle_boolean_option('mouse_enabled'))
 map('zf', fm.open_console('filter '))
 
 # ------------------------------------------------------------ sort
-map('o<bg>', 'O<bg>', fm.hint("*s*ize *b*asename *m*time" \
-	" *t*ype *r*everse *n*atural"))
+map('o<bg>', 'O<bg>', fm.hint('*sort by:* *s*ize *b*asename *m*time' \
+	' *t*ype *r*everse *n*atural'))
 sort_dict = {
 	's': 'size',
 	'b': 'basename',
 	'n': 'natural',
 	'm': 'mtime',
+	'c': 'ctime',
 	't': 'type',
 }
 
@@ -240,11 +248,13 @@ def insert_before_filename(arg):
 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('shell  %s', position=len('shell ')))
 map('#', fm.open_console('shell -p '))
 
 # --------------------------------------------- jump to directories
+map('g<bg>', fm.hint('*goto:* */* *d*ev *e*tc *h*:~ *m*edia ' \
+		'*M*nt *o*pt *s*rv *u*sr *v*ar *R*anger *link:* *l*:target_path ' \
+		'rea*L*_path *tab:* *c*lose *n*ew nex*t* *T*:prev'))
 map('gh', fm.cd('~'))
 map('ge', fm.cd('/etc'))
 map('gu', fm.cd('/usr'))
@@ -278,11 +288,12 @@ map('/', fm.open_console('search '))
 map('n', fm.search())
 map('N', fm.search(forward=False))
 
+map('c<bg>', fm.hint('*w*:rename ch*d*ir *search order:* ' \
+		'*c*time *m*imetype *s*ize *t*ag'))
 map('ct', fm.search(order='tag'))
 map('cc', fm.search(order='ctime'))
 map('cm', fm.search(order='mimetype'))
 map('cs', fm.search(order='size'))
-map('c<bg>', fm.hint('*c*time *m*imetype *s*ize *t*ag  *w*:rename'))
 
 # ------------------------------------------------------- bookmarks
 for key in ALLOWED_BOOKMARK_KEYS:
diff --git a/ranger/defaults/options.py b/ranger/defaults/options.py
index a2289729..c9cbf8ad 100644
--- a/ranger/defaults/options.py
+++ b/ranger/defaults/options.py
@@ -84,6 +84,9 @@ mouse_enabled = True
 display_size_in_main_column = True
 display_size_in_status_bar = False
 
+# Display files tags in all columns or only in main column?
+display_tags_in_all_columns = True
+
 # Set a title for the window?
 update_title = True
 
diff --git a/ranger/ext/keybinding_parser.py b/ranger/ext/keybinding_parser.py
index 93119bce..bfd1b6d4 100644
--- a/ranger/ext/keybinding_parser.py
+++ b/ranger/ext/keybinding_parser.py
@@ -67,6 +67,7 @@ def parse_keybinding(obj):
 DIRKEY = 9001
 ANYKEY = 9002
 PASSIVE_ACTION = 9003
+ALT_KEY = 9004
 
 very_special_keys = {
 	'dir': DIRKEY,
@@ -98,10 +99,10 @@ special_keys = {
 }
 
 for key, val in tuple(special_keys.items()):
-	special_keys['a-' + key] = (27, val)
+	special_keys['a-' + key] = (ALT_KEY, val)
 
 for char in ascii_lowercase + '0123456789':
-	special_keys['a-' + char] = (27, ord(char))
+	special_keys['a-' + char] = (ALT_KEY, ord(char))
 
 for char in ascii_lowercase:
 	special_keys['c-' + char] = ord(char) - 96
diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py
index 7b8a7563..e0623e23 100644
--- a/ranger/fsobject/directory.py
+++ b/ranger/fsobject/directory.py
@@ -87,6 +87,7 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware):
 		'natural': sort_naturally,
 		'size': lambda path: -path.size,
 		'mtime': lambda path: -(path.stat and path.stat.st_mtime or 1),
+		'ctime': lambda path: -(path.stat and path.stat.st_ctime or 1),
 		'type': lambda path: path.mimetype or '',
 	}
 
diff --git a/ranger/gui/defaultui.py b/ranger/gui/defaultui.py
index 434e6d45..933b56f7 100644
--- a/ranger/gui/defaultui.py
+++ b/ranger/gui/defaultui.py
@@ -112,6 +112,7 @@ class DefaultUI(UI):
 		self.browser.visible = False
 		self.taskview.visible = True
 		self.taskview.focused = True
+		self.fm.hint('*tasks:* *dd*:remove *J*:move_down *H*:move_up')
 
 	def redraw_main_column(self):
 		self.browser.main_column.need_redraw = True
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index a2babed8..cc2871af 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -22,6 +22,7 @@ from .displayable import DisplayableContainer
 from ranger.gui.curses_shortcuts import ascii_only
 from ranger.container.keymap import CommandArgs
 from .mouse_event import MouseEvent
+from ranger.ext.keybinding_parser import ALT_KEY
 
 TERMINALS_WITH_TITLE = ("xterm", "xterm-256color", "rxvt",
 		"rxvt-256color", "rxvt-unicode", "aterm", "Eterm",
@@ -183,9 +184,14 @@ class UI(DisplayableContainer):
 					keys.append(getkey)
 			if len(keys) == 1:
 				keys.append(-1)
+			elif keys[0] == 27:
+				keys[0] = ALT_KEY
 			if self.settings.xterm_alt_key:
 				if len(keys) == 2 and keys[1] in range(127, 256):
-					keys = [27, keys[1] - 128]
+					if keys[0] == 195:
+						keys = [ALT_KEY, keys[1] - 64]
+					elif keys[0] == 194:
+						keys = [ALT_KEY, keys[1] - 128]
 			self.handle_keys(*keys)
 			self.set_load_mode(previous_load_mode)
 			if self.settings.flushinput and not self.console.visible:
diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py
index b8277748..801b79fd 100644
--- a/ranger/gui/widgets/browsercolumn.py
+++ b/ranger/gui/widgets/browsercolumn.py
@@ -27,7 +27,6 @@ class BrowserColumn(Pager):
 	display_infostring = False
 	scroll_begin = 0
 	target = None
-	tagged_marker = '*'
 	last_redraw_time = -1
 	ellipsis = { False: '~', True: '…' }
 
@@ -228,9 +227,14 @@ class BrowserColumn(Pager):
 			text = drawn.basename
 			tagged = self.fm.tags and drawn.realpath in self.fm.tags
 
+			if tagged:
+				tagged_marker = self.fm.tags.marker(drawn.realpath)
+
 			space = self.wid - len(infostring)
 			if self.main_column:
 				space -= 2
+			elif self.settings.display_tags_in_all_columns:
+				space -= 1
 
 #			if len(text) > space:
 #				text = text[:space-1] + self.ellipsis
@@ -240,13 +244,13 @@ class BrowserColumn(Pager):
 
 			if drawn.marked:
 				this_color.append('marked')
-				if self.main_column:
+				if self.main_column or self.settings.display_tags_in_all_columns:
 					text = " " + text
 
 			if tagged:
 				this_color.append('tagged')
-				if self.main_column:
-					text = self.tagged_marker + text
+				if self.main_column or self.settings.display_tags_in_all_columns:
+					text = tagged_marker + text
 
 			if drawn.is_directory:
 				this_color.append('directory')
@@ -276,7 +280,7 @@ class BrowserColumn(Pager):
 			wtext = WideString(text)
 			if len(wtext) > space:
 				wtext = wtext[:space - 1] + ellipsis
-			if self.main_column:
+			if self.main_column or self.settings.display_tags_in_all_columns:
 				if tagged:
 					self.addstr(line, 0, str(wtext))
 				elif self.wid > 1:
@@ -296,9 +300,10 @@ class BrowserColumn(Pager):
 				start, wid = bad_info_color
 				self.color_at(line, start, wid, this_color, 'badinfo')
 
-			if self.main_column and tagged and self.wid > 2:
+			if (self.main_column or self.settings.display_tags_in_all_columns) \
+					and tagged and self.wid > 2:
 				this_color.append('tag_marker')
-				self.color_at(line, 0, len(self.tagged_marker), this_color)
+				self.color_at(line, 0, len(tagged_marker), this_color)
 
 			self.color_reset()
 
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index 12f685d4..a575405d 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -123,7 +123,14 @@ class Console(Widget):
 		self.history.add('')
 		return True
 
-	def close(self):
+	def close(self, trigger_cancel_function=True):
+		if trigger_cancel_function:
+			cmd = self._get_cmd()
+			if cmd:
+				try:
+					cmd.cancel()
+				except Exception as error:
+					self.fm.notify(error)
 		if self.last_cursor_mode is not None:
 			try:
 				curses.curs_set(self.last_cursor_mode)
@@ -288,7 +295,7 @@ class Console(Widget):
 		self.tab_deque = None
 		if mod == -1 and self.pos == 0:
 			if not self.line:
-				self.close()
+				self.close(trigger_cancel_function=False)
 			return
 		# Delete utf-char-wise
 		if self.fm.py3:
@@ -315,7 +322,7 @@ class Console(Widget):
 				self.fm.notify(error)
 
 		if self.allow_close:
-			self.close()
+			self.close(trigger_cancel_function=False)
 
 	def _get_cmd(self):
 		try: