summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--README1
-rw-r--r--ranger/core/actions.py13
-rw-r--r--ranger/core/loader.py24
-rw-r--r--ranger/defaults/keys.py3
-rw-r--r--ranger/fsobject/directory.py27
-rw-r--r--ranger/fsobject/fsobject.py14
-rw-r--r--ranger/gui/ui.py2
-rw-r--r--ranger/gui/widgets/console.py28
-rw-r--r--ranger/gui/widgets/statusbar.py5
9 files changed, 88 insertions, 29 deletions
diff --git a/README b/README
index 0f43f6bd..2b46411f 100644
--- a/README
+++ b/README
@@ -61,6 +61,7 @@ Dependencies
 Optional:
 * The "file" program
 * A pager ("less" by default)
+* The python module "chardet", in case of encoding detection problems
 
 For scope.sh: (enhanced file previews)
 * img2txt (from caca-utils) for previewing images
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 8a57330b..89bd9389 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -18,7 +18,7 @@ import re
 import shutil
 import string
 from os.path import join, isdir, realpath
-from os import symlink, getcwd
+from os import link, symlink, getcwd
 from inspect import cleandoc
 
 import ranger
@@ -206,7 +206,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 				if not self.env.enter_dir(cf) and selection:
 					if self.execute_file(selection, mode=mode) is False:
 						self.open_console('open_with ')
-			elif direction.vertical():
+			elif direction.vertical() and cwd.files:
 				newpos = direction.move(
 						direction=direction.down(),
 						override=narg,
@@ -744,6 +744,13 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 			except Exception as x:
 				self.notify(x)
 
+	def paste_hardlink(self):
+		for f in self.env.copy:
+			try:
+				link(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"""
 		copied_files = tuple(self.env.copy)
@@ -795,7 +802,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		self.loader.add(obj)
 
 	def delete(self):
-		self.notify("Deleting!", duration=1)
+		self.notify("Deleting!")
 		selected = self.env.get_selection()
 		self.env.copy -= set(selected)
 		if selected:
diff --git a/ranger/core/loader.py b/ranger/core/loader.py
index 4341595c..7fd7dbab 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -23,7 +23,11 @@ import math
 import os
 import sys
 import select
-
+try:
+	import chardet
+	HAVE_CHARDET = True
+except:
+	HAVE_CHARDET = False
 
 class Loadable(object):
 	paused = False
@@ -89,13 +93,13 @@ class CommandLoader(Loadable, SignalDispatcher, FileManagerAware):
 						if rd == process.stderr:
 							read = rd.readline()
 							if py3:
-								read = read.decode('utf-8')
+								read = safeDecode(read)
 							if read:
 								self.fm.notify(read, bad=True)
 						elif rd == process.stdout:
 							read = rd.read(512)
 							if py3:
-								read = read.decode('utf-8')
+								read = safeDecode(read)
 							if read:
 								self.stdout_buffer += read
 				except select.error:
@@ -103,12 +107,12 @@ class CommandLoader(Loadable, SignalDispatcher, FileManagerAware):
 			if not self.silent:
 				for l in process.stderr.readlines():
 					if py3:
-						l = l.decode('utf-8')
+						l = safeDecode(l)
 					self.fm.notify(l, bad=True)
 			if self.read:
 				read = process.stdout.read()
 				if py3:
-					read = read.decode('utf-8')
+					read = safeDecode(read)
 				self.stdout_buffer += read
 		self.finished = True
 		self.signal_emit('after', process=process, loader=self)
@@ -137,6 +141,16 @@ class CommandLoader(Loadable, SignalDispatcher, FileManagerAware):
 			self.process.kill()
 
 
+def safeDecode(string):
+	try:
+		return string.decode("utf-8")
+	except (UnicodeDecodeError):
+		if HAVE_CHARDET:
+			return string.decode(chardet.detect(str)["encoding"])
+		else:
+			return ""
+
+
 class Loader(FileManagerAware):
 	seconds_of_work_time = 0.03
 	throbber_chars = r'/-\|'
diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py
index 782af310..ea98e4c5 100644
--- a/ranger/defaults/keys.py
+++ b/ranger/defaults/keys.py
@@ -180,6 +180,7 @@ 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('phl', fm.paste_hardlink())
 map('p<bg>', fm.hint('press *p* to confirm pasting' \
 		', *o*verwrite, create sym*l*inks, relative sym*L*inks'))
 
@@ -194,7 +195,7 @@ 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('zh', '<C-h>', '<backspace>', fm.toggle_boolean_option('show_hidden'))
+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'))
 map('zv', fm.toggle_boolean_option('use_preview_script'))
diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py
index 3c0f680d..7b8a7563 100644
--- a/ranger/fsobject/directory.py
+++ b/ranger/fsobject/directory.py
@@ -26,6 +26,7 @@ from ranger.ext.mount_path import mount_path
 from ranger.fsobject import BAD_INFO, File, FileSystemObject
 from ranger.core.shared import SettingsAware
 from ranger.ext.accumulator import Accumulator
+from ranger.ext.lazy_property import lazy_property
 import ranger.fsobject
 
 def sort_by_basename(path):
@@ -320,6 +321,32 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware):
 		else:
 			self.correct_pointer()
 
+	@lazy_property
+	def size(self):
+		try:
+			size = len(os.listdir(self.path))  # bite me
+		except OSError:
+			self.infostring = '?'
+			self.accessible = False
+			return 0
+		else:
+			self.infostring = ' %d' % size
+			self.accessible = True
+			self.runnable = True
+			return size
+
+	@lazy_property
+	def infostring(self):
+		self.size  # trigger the lazy property initializer
+		if self.is_link:
+			return '->' + self.infostring
+		return self.infostring
+
+	@lazy_property
+	def runnable(self):
+		self.size  # trigger the lazy property initializer
+		return self.runnable
+
 	def sort_if_outdated(self):
 		"""Sort the containing files if they are outdated"""
 		if self.order_outdated:
diff --git a/ranger/fsobject/fsobject.py b/ranger/fsobject/fsobject.py
index bf71ac94..647b7604 100644
--- a/ranger/fsobject/fsobject.py
+++ b/ranger/fsobject/fsobject.py
@@ -231,20 +231,10 @@ class FileSystemObject(FileManagerAware):
 			else:
 				self.size = 0
 				self.infostring = '?'
-		elif self.is_directory:
-			try:
-				self.size = len(listdir(path))  # bite me
-			except OSError:
-				self.size = 0
-				self.infostring = '?'
-				self.accessible = False
-			else:
-				self.infostring = ' %d' % self.size
-				self.accessible = True
-				self.runnable = True
 		if is_link:
-			self.infostring = '->' + self.infostring
 			self.is_link = True
+			if not self.is_directory:
+				self.infostring = '->' + self.infostring
 
 		self.stat = new_stat
 
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index 2f27f11e..a2babed8 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -139,6 +139,8 @@ class UI(DisplayableContainer):
 		if DisplayableContainer.press(self, key):
 			return
 
+		self.status.clear_message()
+
 		self.env.keymanager.use_context('browser')
 		self.env.key_append(key)
 		kbuf = self.env.keybuffer
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index 9b1b0642..12f685d4 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -259,15 +259,29 @@ class Console(Widget):
 		self.pos += len(self.copy)
 		self.on_line_change()
 
-	def delete_word(self):
+	def delete_word(self, backward=True):
 		if self.line:
 			self.tab_deque = None
-			i = len(self.line) - 2
-			while i >= 0 and re.match(r'[\w\d]', self.line[i], re.U):
-				i -= 1
-			self.copy = self.line[i + 1:]
-			self.line = self.line[:i + 1]
-			self.pos = len(self.line)
+			if backward:
+				right_part = self.line[self.pos:]
+				i = self.pos - 2
+				while i >= 0 and re.match(r'[\w\d]', self.line[i], re.U):
+					i -= 1
+				self.copy = self.line[i + 1:self.pos]
+				self.line = self.line[:i + 1] + right_part
+				self.pos = i + 1
+			else:
+				left_part = self.line[:self.pos]
+				i = self.pos + 1
+				while i < len(self.line) and re.match(r'[\w\d]', self.line[i], re.U):
+					i += 1
+				self.copy = self.line[self.pos:i]
+				if i >= len(self.line):
+					self.line = left_part
+					self.pos = len(self.line)
+				else:
+					self.line = left_part + self.line[i:]
+					self.pos = len(left_part)
 			self.on_line_change()
 
 	def delete(self, mod):
diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py
index 2f3c67cf..b7ab123c 100644
--- a/ranger/gui/widgets/statusbar.py
+++ b/ranger/gui/widgets/statusbar.py
@@ -53,9 +53,12 @@ class StatusBar(Widget):
 	def request_redraw(self):
 		self.need_redraw = True
 
-	def notify(self, text, duration=4, bad=False):
+	def notify(self, text, duration=0, bad=False):
 		self.msg = Message(text, duration, bad)
 
+	def clear_message(self):
+		self.msg = None
+
 	def draw(self):
 		"""Draw the statusbar"""