summary refs log tree commit diff stats
path: root/ranger/defaults/commands.py
diff options
context:
space:
mode:
Diffstat (limited to 'ranger/defaults/commands.py')
-rw-r--r--ranger/defaults/commands.py474
1 files changed, 474 insertions, 0 deletions
diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py
new file mode 100644
index 00000000..834e358b
--- /dev/null
+++ b/ranger/defaults/commands.py
@@ -0,0 +1,474 @@
+# 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/>.
+
+import os
+from collections import deque
+from ranger.shared import FileManagerAware
+from ranger.gui.widgets import console_mode as cmode
+from ranger.ext.command_parser import LazyParser as parse
+
+class Command(FileManagerAware):
+	"""Abstract command class"""
+	name = None
+	allow_abbrev = True
+	def __init__(self, line, mode):
+		self.line = line
+		self.mode = mode
+
+	def execute(self):
+		"""Override this"""
+
+	def tab(self):
+		"""Override this"""
+
+	def quick_open(self):
+		"""Override this"""
+
+	def _tab_only_directories(self):
+		from os.path import dirname, basename, expanduser, join, isdir
+
+		line = parse(self.line)
+		pwd = self.fm.env.pwd.path
+
+		try:
+			rel_dest = line.rest(1)
+		except IndexError:
+			rel_dest = ''
+
+		# expand the tilde into the user directory
+		if rel_dest.startswith('~'):
+			rel_dest = expanduser(rel_dest)
+
+		# define some shortcuts
+		abs_dest = join(pwd, rel_dest)
+		abs_dirname = dirname(abs_dest)
+		rel_basename = basename(rel_dest)
+		rel_dirname = dirname(rel_dest)
+
+		try:
+			# are we after a directory?
+			if rel_dest.endswith('/') or rel_dest == '':
+				_, dirnames, _ = os.walk(abs_dest).next()
+
+			# are we in the middle of the filename?
+			else:
+				_, dirnames, _ = os.walk(abs_dirname).next()
+				dirnames = [dn for dn in dirnames \
+						if dn.startswith(rel_basename)]
+		except (OSError, StopIteration):
+			# os.walk found nothing
+			pass
+		else:
+			dirnames.sort()
+
+			# no results, return None
+			if len(dirnames) == 0:
+				return
+
+			# one result. since it must be a directory, append a slash.
+			if len(dirnames) == 1:
+				return line + join(rel_dirname, dirnames[0]) + '/'
+
+			# more than one result. append no slash, so the user can
+			# manually type in the slash to advance into that directory
+			return (line + join(rel_dirname, dirname) for dirname in dirnames)
+
+	def _tab_directory_content(self):
+		from os.path import dirname, basename, expanduser, join, isdir
+
+		line = parse(self.line)
+		pwd = self.fm.env.pwd.path
+
+		try:
+			rel_dest = line.rest(1)
+		except IndexError:
+			rel_dest = ''
+
+		# expand the tilde into the user directory
+		if rel_dest.startswith('~'):
+			rel_dest = expanduser(rel_dest)
+
+		# define some shortcuts
+		abs_dest = join(pwd, rel_dest)
+		abs_dirname = dirname(abs_dest)
+		rel_basename = basename(rel_dest)
+		rel_dirname = dirname(rel_dest)
+
+		try:
+			# are we after a directory?
+			if rel_dest.endswith('/') or rel_dest == '':
+				_, dirnames, filenames = os.walk(abs_dest).next()
+				names = dirnames + filenames
+
+			# are we in the middle of the filename?
+			else:
+				_, dirnames, filenames = os.walk(abs_dirname).next()
+				names = [name for name in (dirnames + filenames) \
+						if name.startswith(rel_basename)]
+		except (OSError, StopIteration):
+			# os.walk found nothing
+			pass
+		else:
+			names.sort()
+
+			# no results, return None
+			if len(names) == 0:
+				return
+
+			# one result. since it must be a directory, append a slash.
+			if len(names) == 1:
+				return line + join(rel_dirname, names[0]) + '/'
+
+			# more than one result. append no slash, so the user can
+			# manually type in the slash to advance into that directory
+			return (line + join(rel_dirname, name) for name in names)
+
+
+# -------------------------------- definitions
+
+class cd(Command):
+	"""
+	:cd <dirname>
+
+	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:
+			destination = line.rest(1)
+		except IndexError:
+			destination = '~'
+
+		if destination == '-':
+			self.fm.enter_bookmark('`')
+		else:
+			self.fm.cd(destination)
+
+	def tab(self):
+		return self._tab_only_directories()
+
+	def quick_open(self):
+		from os.path import isdir, join, normpath
+		line = parse(self.line)
+		pwd = self.fm.env.pwd.path
+
+		rel_dest = line.rest(1)
+		if not rel_dest:
+			return False
+
+		abs_dest = normpath(join(pwd, rel_dest))
+		return rel_dest != '.' and isdir(abs_dest)
+
+
+class find(Command):
+	"""
+	:find <string>
+
+	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
+	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)
+		self.fm.env.last_search = re.compile(search, re.IGNORECASE)
+
+		if self.count == 1:
+			self.fm.move_right()
+			self.fm.block_input(0.5)
+
+	def quick_open(self):
+		self._search()
+		if self.count == 1:
+			return True
+
+	def _search(self):
+		self.count = 0
+		line = parse(self.line)
+		pwd = self.fm.env.pwd
+		try:
+			arg = line.rest(1)
+		except IndexError:
+			return False
+
+		deq = deque(pwd.files)
+		deq.rotate(-pwd.pointer)
+		i = 0
+		for fsobj in deq:
+			filename = fsobj.basename_lower
+			if arg in filename:
+				self.count += 1
+				if self.count == 1:
+					pwd.move(absolute=(pwd.pointer + i) % len(pwd.files))
+					self.fm.env.cf = pwd.pointed_obj
+			if self.count > 1:
+				return False
+			i += 1
+
+		return self.count == 1
+
+
+class quit(Command):
+	"""
+	:quit
+
+	Quits the program immediately.
+	"""
+
+	def execute(self):
+		raise SystemExit
+
+
+class delete(Command):
+	"""
+	:delete
+
+	Tries to delete the selection.
+
+	"Selection" is defined as all the "marked files" (by default, you
+	can mark files with space or v). If there are no marked files,
+	use the "current file" (where the cursor is)
+	"""
+
+	allow_abbrev = False
+
+	def execute(self):
+		self.fm.delete()
+
+
+class mkdir(Command):
+	"""
+	:mkdir <dirname>
+
+	Creates a directory with the name <dirname>.
+	"""
+
+	def execute(self):
+		from os.path import join, expanduser, lexists
+		from os import mkdir
+
+		line = parse(self.line)
+		dirname = join(self.fm.env.pwd.path, expanduser(line.rest(1)))
+		if not lexists(dirname):
+			mkdir(dirname)
+		else:
+			self.fm.notify("file/directory exists!", bad=True)
+
+
+class touch(Command):
+	"""
+	:touch <fname>
+
+	Creates a file with the name <fname>.
+	"""
+
+	def execute(self):
+		from os.path import join, expanduser, lexists
+		from os import mkdir
+
+		line = parse(self.line)
+		fname = join(self.fm.env.pwd.path, expanduser(line.rest(1)))
+		if not lexists(fname):
+			open(fname, 'a')
+		else:
+			self.fm.notify("file/directory exists!", bad=True)
+
+
+class edit(Command):
+	"""
+	:edit <filename>
+
+	Opens the specified file in vim
+	"""
+
+	def execute(self):
+		line = parse(self.line)
+		self.fm.edit_file(line.rest(1))
+
+	def tab(self):
+		return self._tab_directory_content()
+
+
+class eval_(Command):
+	"""
+	:eval <python code>
+
+	Evaluates the python code.
+	`fm' is a reference to the FM instance.
+	To display text, use the function `p'.
+
+	Examples:
+	:eval fm
+	:eval len(fm.env.directories)
+	:eval p("Hello World!")
+	"""
+	name = 'eval'
+
+	def execute(self):
+		code = parse(self.line).rest(1)
+		fm = self.fm
+		p = fm.notify
+		try:
+			try:
+				result = eval(code)
+			except SyntaxError:
+				exec(code)
+			else:
+				if result:
+					p(result)
+		except Exception as err:
+			p(err)
+
+
+class rename(Command):
+	"""
+	:rename <newname>
+
+	Changes the name of the currently highlighted file to <newname>
+	"""
+
+	def execute(self):
+		from ranger.fsobject.file import File
+		line = parse(self.line)
+		self.fm.rename(self.fm.env.cf, line.rest(1))
+		f = File(line.rest(1))
+		self.fm.env.pwd.pointed_obj = f
+		self.fm.env.cf = f
+
+	def tab(self):
+		return self._tab_directory_content()
+
+
+class chmod(Command):
+	"""
+	:chmod <octal number>
+
+	Sets the permissions of the selection to the octal number.
+
+	The octal number is between 0 and 777. The digits specify the
+	permissions for the user, the group and others.
+
+	A 1 permits execution, a 2 permits writing, a 4 permits reading.
+	Add those numbers to combine them. So a 7 permits everything.
+	"""
+
+	def execute(self):
+		line = parse(self.line)
+		mode = line.rest(1)
+
+		try:
+			mode = int(mode, 8)
+			if mode < 0 or mode > 511:
+				raise ValueError
+		except ValueError:
+			self.fm.notify("Need an octal number between 0 and 777!", bad=True)
+			return
+
+		for file in self.fm.env.get_selection():
+			try:
+				os.chmod(file.path, mode)
+			except Exception as ex:
+				self.fm.notify(ex)
+
+		try:
+			# reloading directory.  maybe its better to reload the selected
+			# files only.
+			self.fm.env.pwd.load_content()
+		except:
+			pass
+
+
+class filter(Command):
+	"""
+	:filter <string>
+
+	Displays only the files which contain <string> in their basename.
+	"""
+
+	def execute(self):
+		line = parse(self.line)
+		self.fm.set_filter(line.rest(1))
+
+
+class grep(Command):
+	"""
+	:grep <string>
+
+	Looks for a string in all marked files or directories
+	"""
+
+	def execute(self):
+		line = parse(self.line)
+		if line.rest(1):
+			action = ['grep', '--color=always', '--line-number']
+			action.extend(['-e', line.rest(1), '-r'])
+			action.extend(f.path for f in self.fm.env.get_selection())
+			self.fm.execute_command(action, flags='p')
+
+
+# -------------------------------- rest
+
+by_name = {}
+for varname, var in vars().copy().items():
+	try:
+		if issubclass(var, Command) and var != Command:
+			by_name[var.name or varname] = var
+	except TypeError:
+		pass
+del varname
+del var
+
+def alias(**kw):
+	"""Create an alias for commands, eg: alias(quit=exit)"""
+	for key, value in kw.items():
+		by_name[key] = value
+
+def get_command(name, abbrev=True):
+	if abbrev:
+		lst = [cls for cmd, cls in by_name.items() \
+				if cmd.startswith(name) \
+				and cls.allow_abbrev \
+				or cmd == name]
+		if len(lst) == 0:
+			raise KeyError
+		if len(lst) == 1 or by_name[name] in lst:
+			return lst[0]
+		raise ValueError("Ambiguous command")
+	else:
+		try:
+			return by_name[name]
+		except KeyError:
+			return None
+
+def command_generator(start):
+	return (cmd + ' ' for cmd in by_name if cmd.startswith(start))
+
+alias(e=edit)  # to make :e unambiguous.
+