summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xranger.py2
-rw-r--r--ranger/commands.py99
-rw-r--r--ranger/defaults/keys.py5
-rw-r--r--ranger/fm.py15
-rw-r--r--ranger/fsobject/fsobject.py2
-rw-r--r--ranger/gui/widgets/console.py53
-rw-r--r--ranger/gui/widgets/filelistcontainer.py2
-rw-r--r--uml/2.session6
8 files changed, 146 insertions, 38 deletions
diff --git a/ranger.py b/ranger.py
index 807e28a6..2e380581 100755
--- a/ranger.py
+++ b/ranger.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python -OO
 # coding=utf-8
 # ranger: Browse your files inside the terminal.
 
diff --git a/ranger/commands.py b/ranger/commands.py
index e61e9fd8..a4ef2b22 100644
--- a/ranger/commands.py
+++ b/ranger/commands.py
@@ -1,10 +1,10 @@
 import os
-
 from ranger.shared import FileManagerAware
 
 # -------------------------------- helper classes
 
 class parse(object):
+	"""Parse commands and extract information"""
 	def __init__(self, line):
 		self.line = line
 		self.chunks = line.split()
@@ -14,13 +14,24 @@ class parse(object):
 		except ValueError:
 			self.firstpart = ''
 
+	def chunk(self, n, otherwise=''):
+		if len(self.chunks) >= n:
+			return self.chunks[n]
+		else:
+			return otherwise
+
+
 	def __add__(self, newpart):
 		return self.firstpart + newpart
 
 class Command(FileManagerAware):
+	"""Abstract command class"""
 	name = None
-	def __init__(self, line):
+	mode = ':'
+	line = ''
+	def __init__(self, line, mode):
 		self.line = line
+		self.mode = mode
 
 	def execute(self):
 		pass
@@ -28,12 +39,16 @@ class Command(FileManagerAware):
 	def tab(self):
 		pass
 
-	def _no_change(self):
-		return (self.line for i in range(100))
 
 # -------------------------------- definitions
 
 class cd(Command):
+	"""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:
@@ -84,6 +99,65 @@ class cd(Command):
 				return line + join(rel_dirname, dirnames[0]) + '/'
 
 			return (line + join(rel_dirname, dirname) for dirname in dirnames)
+	
+	def quick_open(self):
+		from os.path import isdir, join, normpath
+		line = parse(self.line)
+		pwd = self.fm.env.pwd.path
+
+		try:
+			rel_dest = line.chunks[1]
+		except IndexError:
+			return False
+
+		abs_dest = normpath(join(pwd, rel_dest))
+		return rel_dest != '.' and isdir(abs_dest)
+
+class find(Command):
+	"""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
+	def execute(self):
+		if self.mode != '>':
+			self._search()
+
+		import re
+		search = parse(self.line).chunk(1)
+		search = re.escape(search)
+		self.fm.env.last_search = re.compile(search, re.IGNORECASE)
+
+	def quick_open(self):
+		self._search()
+		if self.count == 1:
+			self.fm.move_right()
+			self.fm.block_input(0.5)
+			return True
+
+	def _search(self):
+		self.count = 0
+		line = parse(self.line)
+		pwd = self.fm.env.pwd
+		try:
+			arg = line.chunks[1]
+		except IndexError:
+			return False
+		
+		length = len(pwd.files)
+		for i in range(length):
+			actual_index = (pwd.pointed_index + i) % length
+			filename = pwd.files[actual_index].basename_lower
+			if arg in filename:
+				self.count += 1
+				if self.count == 1:
+					pwd.move_pointer(absolute=actual_index)
+					self.fm.env.cf = pwd.pointed_file
+			if self.count > 1:
+				return False
+
+		return self.count == 1
 
 # -------------------------------- rest
 
@@ -94,20 +168,3 @@ for varname, var in vars().copy().items():
 			by_name[var.name or varname] = var
 	except TypeError:
 		pass
-
-def execute(name, line):
-	try:
-		command = by_name[name](line)
-	except KeyError:
-		pass
-	else:
-		command.execute()
-
-def tab(name, line):
-	try:
-		command = by_name[name](line)
-	except KeyError:
-		pass
-	else:
-		return command.tab()
-
diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py
index 20af8063..acfe1178 100644
--- a/ranger/defaults/keys.py
+++ b/ranger/defaults/keys.py
@@ -1,5 +1,6 @@
 import curses
 from curses.ascii import *
+from ranger import RANGERDIR
 from ranger.gui.widgets.console import Console
 from ranger.container.bookmarks import ALLOWED_KEYS as ALLOWED_BOOKMARK_KEYS
 
@@ -40,6 +41,7 @@ def initialize_commands(command_list):
 	bind('td', do('toggle_boolean_option', 'directories_first'))
 
 	bind('cd', do('open_console', ':', 'cd '))
+	bind('f', do('open_console', '>', 'find '))
 
 	# key combinations which change the current directory
 	def cd(path):
@@ -53,6 +55,7 @@ def initialize_commands(command_list):
 	bind('gn', do('cd', '/mnt'))
 	bind('gt', do('cd', '~/.trash'))
 	bind('gs', do('cd', '/srv'))
+	bind('gR', do('cd', RANGERDIR))
 
 	bind('n', do('search_forward'))
 	bind('N', do('search_backward'))
@@ -71,6 +74,7 @@ def initialize_commands(command_list):
 	bind(curses.KEY_RESIZE, do('resize'))
 	bind(curses.KEY_MOUSE, do('handle_mouse'))
 	bind(':', do('open_console', ':'))
+	bind('>', do('open_console', '>'))
 	bind('/', do('open_console', '/'))
 	bind('?', do('open_console', '?'))
 	bind('!', do('open_console', '!'))
@@ -103,6 +107,7 @@ def initialize_console_commands(command_list):
 	bind(ctrl('w'), do('delete_word'))
 	bind(ctrl('k'), do('delete_rest', 1))
 	bind(ctrl('u'), do('delete_rest', -1))
+	bind(ctrl('y'), do('paste'))
 
 	# system functions
 	bind(ctrl('c'), ESC, do('close'))
diff --git a/ranger/fm.py b/ranger/fm.py
index 271e4138..b59a3e8d 100644
--- a/ranger/fm.py
+++ b/ranger/fm.py
@@ -1,3 +1,5 @@
+from time import time
+
 from ranger.actions import Actions
 from ranger.container import Bookmarks
 from ranger.ext.relpath import relpath_conf
@@ -7,6 +9,8 @@ CTRL_C = 3
 TICKS_BEFORE_COLLECTING_GARBAGE = 100
 
 class FM(Actions):
+	input_blocked = False
+	input_blocked_until = 0
 	def __init__(self, ui = None, bookmarks = None):
 		"""Initialize FM."""
 		Actions.__init__(self)
@@ -36,6 +40,10 @@ class FM(Actions):
 			self.ui = DefaultUI()
 			self.ui.initialize()
 
+	def block_input(self, sec=0):
+		self.input_blocked = sec != 0
+		self.input_blocked_until = time() + sec
+
 	def loop(self):
 		"""The main loop consists of:
 1. reloading bookmarks if outdated
@@ -57,7 +65,12 @@ class FM(Actions):
 					self.ui.finalize()
 
 					key = self.ui.get_next_key()
-					self.ui.handle_key(key)
+
+					if self.input_blocked and \
+							time() > self.input_blocked_until:
+						self.input_blocked = False
+					if not self.input_blocked:
+						self.ui.handle_key(key)
 
 					gc_tick += 1
 					if gc_tick > TICKS_BEFORE_COLLECTING_GARBAGE:
diff --git a/ranger/fsobject/fsobject.py b/ranger/fsobject/fsobject.py
index 30ed7839..2f65fbc5 100644
--- a/ranger/fsobject/fsobject.py
+++ b/ranger/fsobject/fsobject.py
@@ -7,6 +7,7 @@ from ranger.shared import MimeTypeAware, FileManagerAware
 class FileSystemObject(MimeTypeAware, FileManagerAware):
 	path = None
 	basename = None
+	basename_lower = None
 	dirname = None
 	extension = None
 	exists = False
@@ -42,6 +43,7 @@ class FileSystemObject(MimeTypeAware, FileManagerAware):
 
 		self.path = path
 		self.basename = basename(path)
+		self.basename_lower = self.basename.lower()
 		self.dirname = dirname(path)
 
 		try:
diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py
index 7421db98..ca21189c 100644
--- a/ranger/gui/widgets/console.py
+++ b/ranger/gui/widgets/console.py
@@ -11,6 +11,7 @@ class Console(Widget):
 	commandlist = None
 	last_cursor_mode = 1
 	prompt = ':'
+	copy = ''
 	tab_deque = None
 	original_line = None
 
@@ -90,6 +91,7 @@ class Console(Widget):
 			self.line = self.line[:self.pos] + key + self.line[self.pos:]
 
 		self.pos += len(key)
+		self.on_line_change()
 
 	def move(self, relative = 0, absolute = None):
 		if absolute is not None:
@@ -103,10 +105,21 @@ class Console(Widget):
 	def delete_rest(self, direction):
 		self.tab_deque = None
 		if direction > 0:
+			self.copy = self.line[self.pos:]
 			self.line = self.line[:self.pos]
 		else:
+			self.copy = self.line[:self.pos]
 			self.line = self.line[self.pos:]
 			self.pos = 0
+		self.on_line_change()
+
+	def paste(self):
+		if self.pos == len(self.line):
+			self.line += self.copy
+		else:
+			self.line = self.line[:self.pos] + self.copy + self.line[self.pos:]
+		self.pos += len(self.copy)
+		self.on_line_change()
 
 	def delete_word(self):
 		self.tab_deque = None
@@ -117,6 +130,7 @@ class Console(Widget):
 		except ValueError:
 			self.line = ''
 			self.pos = 0
+		self.on_line_change()
 	
 	def delete(self, mod):
 		self.tab_deque = None
@@ -126,21 +140,26 @@ class Console(Widget):
 
 		self.line = self.line[0:pos] + self.line[pos+1:]
 		self.move(relative = mod)
+		self.on_line_change()
 
 	def execute(self):
 		self.tab_deque = None
-		self.line = ''
-		self.pos = 0
+		self.clear()
 		self.close()
 
 	def tab(self):
 		pass
 
+	def on_line_change(self):
+		pass
+
 class CommandConsole(Console):
 	prompt = ':'
 
 	def execute(self):
-		commands.execute(self._get_cmd(), self.line)
+		cmd = self._get_cmd()
+		if cmd: cmd.execute()
+
 		Console.execute(self)
 	
 	def tab(self, n=1):
@@ -150,6 +169,7 @@ class CommandConsole(Console):
 			if isinstance(tab_result, str):
 				self.line = tab_result
 				self.pos = len(tab_result)
+				self.on_line_change()
 
 			elif tab_result == None:
 				pass
@@ -162,24 +182,35 @@ class CommandConsole(Console):
 			self.tab_deque.rotate(-n)
 			self.line = self.tab_deque[0]
 			self.pos = len(self.line)
+			self.on_line_change()
 
 	def _get_cmd(self):
 		try:
-			return self.line.split()[0]
+			command_name = self.line.split()[0]
 		except:
-			return ''
+			return None
+
+		try:
+			command_class = commands.by_name[command_name]
+		except KeyError:
+			return None
+
+		return command_class(self.line, self.mode)
 	
 	def _get_tab(self):
 		cmd = self._get_cmd()
-		try:
-			return commands.tab(cmd, self.line)
-		except KeyError:
-			return commands.tab(None, self.line)
+		if cmd:
+			return cmd.tab()
+		else:
+			return None
 
 
-class QuickCommandConsole(Console):
+class QuickCommandConsole(CommandConsole):
 	prompt = '>'
-
+	def on_line_change(self):
+		cmd = self._get_cmd()
+		if cmd and cmd.quick_open():
+			self.execute()
 
 class SearchConsole(Console):
 	prompt = '/'
diff --git a/ranger/gui/widgets/filelistcontainer.py b/ranger/gui/widgets/filelistcontainer.py
index cc45f2c9..6e925f83 100644
--- a/ranger/gui/widgets/filelistcontainer.py
+++ b/ranger/gui/widgets/filelistcontainer.py
@@ -20,7 +20,7 @@ class FileListContainer(Widget, DisplayableContainer):
 		ratio_sum = float(reduce(lambda x,y: x + y, ratios))
 		self.ratios = tuple(map(lambda x: x / ratio_sum, ratios))
 
-		if self.ratios >= 2:
+		if len(self.ratios) >= 2:
 			self.stretch_ratios = self.ratios[:-2] + \
 					((self.ratios[-2] + self.ratios[-1] * 0.9), \
 					(self.ratios[-1] * 0.1))
diff --git a/uml/2.session b/uml/2.session
index 5af415f6..7110e9db 100644
--- a/uml/2.session
+++ b/uml/2.session
@@ -1,15 +1,15 @@
 window_sizes 1678 1033 393 1275 797 144
 motifplus_style
 diagrams
-  classdiagram_ref 128002 // Displayable Hierarchy
+  active  classdiagram_ref 128002 // Displayable Hierarchy
     1275 795 100 4 0 0
   classdiagram_ref 134530 // Overview
     1275 795 100 4 0 0
-  active  sequencediagram_ref 141058 // Basic Logic
+  sequencediagram_ref 141058 // Basic Logic
     1275 795 100 4 0 0
 end
 show_stereotypes
-selected sequencediagram_ref 141058 // Basic Logic
+selected classdiagram_ref 128002 // Displayable Hierarchy
 open
   class_ref 128002 // Displayable
   class_ref 128130 // UI