summary refs log tree commit diff stats
path: root/doc
Commit message (Collapse)AuthorAgeFilesLines
* Fixed bug #65 by adding flag "--fail-if-run"hut2010-04-261-1/+5
|
* updated pydochut2010-04-205-248/+10
|
* updated pydochut2010-04-1941-2286/+247
|
* ranger.1: added S key to man pagehut2010-04-161-0/+3
|
* Fixed suggested cd-after-exit-script for zshhut2010-04-131-1/+1
|
* added doc/print_keys.pyhut2010-04-081-0/+14
|
* corrected documentationhut2010-04-061-1/+1
|
* Improved tabshut2010-04-061-0/+3
|
* updated keybindings and documentationhut2010-04-061-1/+21
|
* ranger.1: updatehut2010-04-011-4/+7
|
* ranger.1: updatedhut2010-04-011-1/+1
|
* added a man pagehut2010-04-011-0/+187
|
* rebuilt pydochut2010-03-3118-65/+188
|
* removed doc/pick.sh, pointless to have it therehut2010-03-311-25/+0
|
* removed UML stuff, it's uselesshut2010-03-3115-2337/+0
|
* removed the cd-after-exit hackhut2010-03-291-161/+0
| | | | | | | | | Fear not. You still get the same functionality by using a function like: ranger() { $(which ranger) $@ && cd "$(grep \^\' ~/.ranger/bookmarks | cut -b3-)" }
* updated TODO and pydochut2010-03-2120-52/+85
|
* doc/colorschemes: ugh, its no logical but bitwise OR!hut2010-03-171-1/+1
|
* incremented verison number v1.0.4hut2010-03-121-2/+2
|
* standardized formatting of headings in doc/hut2010-03-122-13/+31
|
* added doc/uml.txthut2010-03-121-0/+5
|
* updated pydochut2010-03-1217-161/+33
|
* updated pydochut2010-03-1270-3946/+816
|
* added two new colorschemes using 88 colorshut2010-03-121-0/+23
|
* added documentation on how colorschemes workhut2010-03-121-0/+91
|
* added symlink: doc/help => ranger/helphut2010-02-281-0/+1
|
* incremented version number and updated pydoc html files v1.0.3hut2010-02-1675-1972/+1415
|
* doc/cd-after-exit: updatedhut2010-02-141-21/+15
|
* doc/pick.sh: corrected commit orderhut2010-02-091-1/+1
|
* doc: what breaks cd-after-exit support in zshhut2010-02-091-0/+2
|
* pick.sh: added -m to checkout commadshut2010-02-051-3/+3
|
* pick.sh: added variables for easier customizationhut2010-02-041-7/+8
|
* added doc/pick.shhut2010-02-041-0/+24
|
* updated dochut2010-01-211-4/+20
|
* 1.0.2! v1.0.2hut2010-01-1430-84/+116
|
* updated pydoc documentationhut2010-01-1361-846/+795
|
* todo: added more info on bug #31hut2010-01-091-0/+5
|
* random cleanups and fixeshut2010-01-071-5/+6
|
* new minor version v1.0.1hut2010-01-022-4/+4
|
* updated pydoc documentationhut2010-01-0248-788/+3167
|
* notify: merged into statusbar, allow to view the log in the pagerhut2010-01-013-35/+2
|
* cleanupshut2009-12-311-1/+5
|
* rename filelist(container) to browsercolumn/browserviewhut2009-12-313-38/+76
|
* updated uml projecthut2009-12-305-73/+215
|
* shorten comment in ranger.pyhut2009-12-261-0/+4
|
* moved /uml to /doc/umlhut2009-12-2514-0/+2180
|
* Explained cd-after-exit featurehut2009-12-251-0/+132
|
* moved pydoc pages to doc/pydochut2009-12-2565-0/+0
|
* updated pydoc pageshut2009-12-2565-0/+10505
} .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 */
# 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
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.relative_symlink import relative_symlink
from ranger.ext.shell_escape import shell_quote
from ranger import fsobject
from ranger.shared import FileManagerAware, EnvironmentAware, SettingsAware
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

	# --------------------------
	# -- Basic Commands
	# --------------------------

	def exit(self):
		"""Exit the program"""
		raise SystemExit()

	def reset(self):
		"""Reset the filemanager, clearing the directory buffer"""
		old_path = self.env.cwd.path
		self.env.garbage_collect(-1)
		self.enter_dir(old_path)

	def reload_cwd(self):
		try:
			cwd = self.env.cwd
		except:
			pass
		cwd.unload()
		cwd.load_content()

	def notify(self, text, duration=4, bad=False):
		if isinstance(text, Exception):
			if ranger.arg.debug:
				raise
			bad = True
		text = str(text)
		self.log.appendleft(text)
		if hasattr(self.ui, 'notify'):
			self.ui.notify(text, duration=duration, bad=bad)

	def redraw_window(self):
		"""Redraw the window"""
		self.ui.redraw_window()

	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(string, prompt=prompt, position=position)

	def execute_console(self, string=''):
		"""Execute a command for the console"""
		self.open_console(string=string)
		self.ui.console.line = string
		self.ui.console.execute()

	def substitute_macros(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_"
		flags is a string consisting of runner.ALLOWED_FLAGS
		mode is a positive integer.
		Both flags and mode specify how the program is run."""

		if isinstance(files, set):
			files = list(files)
		elif type(files) not in (list, tuple):
			files = [files]

		return self.run(files=list(files), **kw)

	# --------------------------
	# -- Moving Around
	# --------------------------

	def move(self, narg=None, **kw):
		"""
		A universal movement method.

		Accepts these parameters:
		(int) down, (int) up, (int) left, (int) right, (int) to,
		(bool) absolute, (bool) relative, (bool) pages,
		(bool) percentage

		to=X is translated to down=X, absolute=True

		Example:
		self.move(down=4, pages=True)  # moves down by 4 pages.
		self.move(to=2, pages=True)  # moves to page 2.
		self.move(to=1, percentage=True)  # moves to 80%
		"""
		cwd = self.env.cwd
		direction = Direction(kw)
		if 'left' in direction or direction.left() > 0:
			steps = direction.left()
			if narg is not None:
				steps *= narg
			try:
				directory = os.path.join(*(['..'] * steps))
			except:
				return
			self.env.enter_dir(directory)
		if cwd and cwd.accessible and cwd.content_loaded:
			if 'right' in direction:
				mode = 0
				if narg is not None:
					mode = narg
				cf = self.env.cf
				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('open_with ')
			elif direction.vertical():
				newpos = direction.move(
						direction=direction.down(),
						override=narg,
						maximum=len(cwd),
						current=cwd.pointer,
						pagesize=self.ui.browser.hei)
				cwd.move(to=newpos)

	def move_parent(self, n):
		parent = self.env.at_level(-1)
		try:
			self.env.enter_dir(parent.files[parent.pointer+n])
		except IndexError:
			pass

	def history_go(self, relative):
		"""Move back and forth in the history"""
		self.env.history_go(relative)

	def scroll(self, relative):
		"""Scroll down by <relative> lines"""
		if hasattr(self.ui, 'scroll'):
			self.ui.scroll(relative)
			self.env.cf = self.env.cwd.pointed_obj

	def enter_dir(self, path, remember=False, history=True):
		"""Enter the directory at the given path"""
		if remember:
			cwd = self.env.cwd
			result = self.env.enter_dir(path, history=history)
			self.bookmarks.remember(cwd)
			return result
		return self.env.enter_dir(path, history=history)

	def cd(self, path, remember=True):
		"""enter the directory at the given path, remember=True"""
		self.enter_dir(path, remember=remember)

	def traverse(self):
		cf = self.env.cf
		cwd = self.env.cwd
		if cf is not None and cf.is_directory:
			self.enter_dir(cf.path)
		elif cwd.pointer >= len(cwd) - 1:
			while True:
				self.move(left=1)
				cwd = self.env.cwd
				if cwd.pointer < len(cwd) - 1:
					break
				if cwd.path == '/':
					break
			self.move(down=1)
			self.traverse()
		else:
			self.move(down=1)
			self.traverse()

	# --------------------------
	# -- Shortcuts / Wrappers
	# --------------------------

	def execute_command(self, cmd, **kw):
		return self.run(cmd, **kw)

	def edit_file(self, file=None):
		"""Calls execute_file with the current file and app='editor'"""
		if file is None:
			file = self.env.cf
		elif isinstance(file, str):
			file = File(os.path.expanduser(file))
		if file is None:
			return
		self.execute_file(file, app = 'editor')

	def hint(self, text):
		self.ui.hint(text)

	def toggle_boolean_option(self, string):
		"""Toggle a boolean option named <string>"""
		if isinstance(self.env.settings[string], bool):
			self.env.settings[string] ^= True

	def set_option(self, optname, value):
		"""Set the value of an option named <optname>"""
		self.env.settings[optname] = value

	def sort(self, func=None, reverse=None):
		if reverse is not None:
			self.env.settings['sort_reverse'] = bool(reverse)

		if func is not None:
			self.env.settings['sort'] = str(func)

	def set_filter(self, fltr):
		try:
			self.env.cwd.filter = fltr
		except:
			pass

	def mark(self, all=False, toggle=False, val=None, movedown=None, narg=1):
		"""
		A wrapper for the directory.mark_xyz functions.

		Arguments:
		all - change all files of the current directory at once?
		toggle - toggle the marked-status?
		val - mark or unmark?
		"""

		if self.env.cwd is None:
			return

		cwd = self.env.cwd

		if not cwd.accessible:
			return

		if movedown is None:
			movedown = not all

		if val is None and toggle is False:
			return

		if all:
			if toggle:
				cwd.toggle_all_marks()
			else:
				cwd.mark_all(val)
		else:
			for i in range(cwd.pointer, min(cwd.pointer + narg, len(cwd))):
				item = cwd.files[i]
				if item is not None:
					if toggle:
						cwd.toggle_mark(item)
					else:
						cwd.mark_item(item, val)

		if movedown:
			self.move(down=narg)

		if hasattr(self.ui, 'redraw_main_column'):
			self.ui.redraw_main_column()
		if hasattr(self.ui, 'status'):
			self.ui.status.need_redraw = True

	def mark_in_direction(self, val=True, dirarg=None):
		cwd = self.env.cwd
		direction = Direction(dirarg)
		pos, selected = direction.select(lst=cwd.files, current=cwd.pointer,
				pagesize=self.env.termsize[0])
		cwd.pointer = pos
		cwd.correct_pointer()
		for item in selected:
			cwd.mark_item(item, val)

	# --------------------------
	# -- Searching
	# --------------------------

	def search_file(self, text, regexp=True):
		if isinstance(text, str) and regexp:
			try:
				text = re.compile(text, re.L | re.U | re.I)
			except:
				return False
		self.env.last_search = text
		self.search(order='search')

	def search(self, order=None, forward=True):
		original_order = order
		if self.search_forward:
			direction = bool(forward)
		else:
			direction = not bool(forward)

		if order is None:
			order = self.search_method
		else:
			self.set_search_method(order=order)

		if order in ('search', 'tag'):
			if order == 'search':
				arg = self.env.last_search
				if arg is None:
					return False
				if hasattr(arg, 'search'):
					fnc = lambda x: arg.search(x.basename)
				else:
					fnc = lambda x: arg in x.basename
			elif order == 'tag':
				fnc = lambda x: x.realpath in self.tags

			return self.env.cwd.search_fnc(fnc=fnc, forward=forward)

		elif order in ('size', 'mimetype', 'ctime'):
			cwd = self.env.cwd
			if original_order is not None or not cwd.cycle_list:
				lst = list(cwd.files)
				if order == 'size':
					fnc = lambda item: -item.size
				elif order == 'mimetype':
					fnc = lambda item: item.mimetype
				elif order == 'ctime':
					fnc = lambda item: -int(item.stat and item.stat.st_ctime)
				lst.sort(key=fnc)
				cwd.set_cycle_list(lst)
				return cwd.cycle(forward=None)

			return cwd.cycle(forward=forward)

	def set_search_method(self, order, forward=True):
		if order in ('search', 'tag', 'size', 'mimetype', 'ctime'):
			self.search_method = order
			self.search_forward = forward

	# --------------------------
	# -- Tags
	# --------------------------
	# Tags are saved in ~/.config/ranger/tagged and simply mark if a
	# file is important to you in any context.

	def tag_toggle(self, movedown=None):
		try:
			toggle = self.tags.toggle
		except AttributeError:
			return

		sel = self.env.get_selection()
		toggle(*tuple(map(lambda x: x.realpath, sel)))

		if movedown is None:
			movedown = len(sel) == 1
		if movedown:
			self.move(down=1)

		if hasattr(self.ui, 'redraw_main_column'):
			self.ui.redraw_main_column()

	def tag_remove(self, movedown=None):
		try:
			remove = self.tags.remove
		except AttributeError:
			return

		sel = self.env.get_selection()
		remove(*tuple(map(lambda x: x.realpath, sel)))

		if movedown is None:
			movedown = len(sel) == 1
		if movedown:
			self.move(down=1)

		if hasattr(self.ui, 'redraw_main_column'):
			self.ui.redraw_main_column()

	# --------------------------
	# -- Bookmarks
	# --------------------------
	# Using ranger.container.bookmarks.

	def enter_bookmark(self, key):
		"""Enter the bookmark with the name <key>"""
		try:
			destination = self.bookmarks[key]
			cwd = self.env.cwd
			if destination.path != cwd.path:
				self.bookmarks.enter(key)
				self.bookmarks.remember(cwd)
		except KeyError:
			pass

	def set_bookmark(self, key):
		"""Set the bookmark with the name <key> to the current directory"""
		self.bookmarks[key] = self.env.cwd

	def unset_bookmark(self, key):
		"""Delete the bookmark with the name <key>"""
		self.bookmarks.delete(key)

	def draw_bookmarks(self):
		self.ui.browser.draw_bookmarks = True

	def hide_bookmarks(self):
		self.ui.browser.draw_bookmarks = False

	# --------------------------
	# -- Pager
	# --------------------------
	# These commands open the built-in pager and set specific sources.

	def display_command_help(self, console_widget):
		if not hasattr(self.ui, 'open_pager'):
			return

		try:
			command = console_widget._get_cmd_class()
		except:
			self.notify("Feature not available!", bad=True)
			return

		if not command:
			self.notify("Command not found!", bad=True)
			return

		if not command.__doc__:
			self.notify("Command has no docstring. Try using python without -OO",
					bad=True)
			return

		pager = self.ui.open_pager()
		lines = cleandoc(command.__doc__).split('\n')
		pager.set_source(lines)

	def display_help(self, topic='index', narg=None):
		if not hasattr(self.ui, 'open_pager'):
			return

		from ranger.help import get_help, get_help_by_index

		scroll_to_line = 0
		if narg is not None:
			chapter, subchapter = int(str(narg)[0]), str(narg)[1:]
			help_text = get_help_by_index(chapter)
			lines = help_text.split('\n')
			if chapter:
				chapternumber = str(chapter) + '.' + subchapter + '. '
				skip_to_content = True
				for line_number, line in enumerate(lines):
					if skip_to_content:
						if line[:10] == '==========':
							skip_to_content = False
					else:
						if line.startswith(chapternumber):
							scroll_to_line = line_number
		else:
			help_text = get_help(topic)
			lines = help_text.split('\n')

		pager = self.ui.open_pager()
		pager.markup = 'help'
		pager.set_source(lines)
		pager.move(down=scroll_to_line)

	def display_log(self):
		if not hasattr(self.ui, 'open_pager'):
			return

		pager = self.ui.open_pager()
		if self.log:
			pager.set_source(["Message Log:"] + list(self.log))
		else:
			pager.set_source(["Message Log:", "No messages!"])

	def display_file(self):
		if not hasattr(self.ui, 'open_embedded_pager'):
			return

		try:
			f = open(self.env.cf.path, 'r')
		except:
			pass
		else:
			pager = self.ui.open_embedded_pager()
			pager.set_source(f)

	# --------------------------
	# -- Tabs
	# --------------------------
	# This implementation of tabs is very simple and keeps track of
	# directory paths only.

	def tab_open(self, name, path=None):
		do_emit_signal = name != self.current_tab
		self.current_tab = name
		if path or (name in self.tabs):
			self.enter_dir(path or self.tabs[name])
		else:
			self._update_current_tab()
		if do_emit_signal:
			self.signal_emit('tab.change')

	def tab_close(self, name=None):
		if name is None:
			name = self.current_tab
		if name == self.current_tab:
			direction = -1 if name == self._get_tab_list()[-1] else 1
			previous = self.current_tab
			self.tab_move(direction)
			if previous == self.current_tab:
				return  # can't close last tab
		if name in self.tabs:
			del self.tabs[name]

	def tab_move(self, offset):
		assert isinstance(offset, int)
		tablist = self._get_tab_list()
		current_index = tablist.index(self.current_tab)
		newtab = tablist[(current_index + offset) % len(tablist)]
		if newtab != self.current_tab:
			self.tab_open(newtab)

	def tab_new(self):
		for i in range(1, 10):
			if not i in self.tabs:
				self.tab_open(i)
				break

	def _get_tab_list(self):
		assert len(self.tabs) > 0, "There must be >=1 tabs at all times"
		return sorted(self.tabs)

	def _update_current_tab(self):
		self.tabs[self.current_tab] = self.env.cwd.path

	# --------------------------
	# -- File System Operations
	# --------------------------

	def uncut(self):
		self.env.copy = set()
		self.env.cut = False
		self.ui.browser.main_column.request_redraw()

	def copy(self, mode='set', narg=None, dirarg=None):
		"""Copy the selected items.  Modes are: 'set', 'add', 'remove'."""
		assert mode in ('set', 'add', 'remove')
		cwd = self.env.cwd
		if not narg and not dirarg:
			selected = (f for f in self.env.get_selection() if f in cwd.files)
		else:
			if not dirarg and narg:
				direction = Direction(down=1)
				offset = 0
			else:
				direction = Direction(dirarg)
				offset = 1
			pos, selected = direction.select(
					override=narg, lst=cwd.files, current=cwd.pointer,
					pagesize=self.env.termsize[0], offset=offset)
			cwd.pointer = pos
			cwd.correct_pointer()
		if mode == 'set':
			self.env.copy = set(selected)
		elif mode == 'add':
			self.env.copy.update(set(selected))
		elif mode == 'remove':
			self.env.copy.difference_update(set(selected))
		self.env.cut = False
		self.ui.browser.main_column.request_redraw()

	def cut(self, mode='set', narg=None, dirarg=None):
		self.copy(mode=mode, narg=narg, dirarg=dirarg)
		self.env.cut = True
		self.ui.browser.main_column.request_redraw()

	def paste_symlink(self, relative=False):
		copied_files = self.env.copy
		for f in copied_files:
			try:
				if relative:
					relative_symlink(f.path, join(getcwd(), f.basename))
				else:
					symlink(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)

		if not copied_files:
			return

		original_path = self.env.cwd.path
		try:
			one_file = copied_files[0]
		except:
			one_file = None

		if self.env.cut:
			self.env.copy.clear()
			self.env.cut = False
			if len(copied_files) == 1:
				descr = "moving: " + one_file.path
			else:
				descr = "moving files from: " + one_file.dirname
			def generate():
				for f in copied_files:
					for _ in shutil_g.move(src=f.path,
							dst=original_path,
							overwrite=overwrite):
						yield
				cwd = self.env.get_directory(original_path)
				cwd.load_content()
		else:
			if len(copied_files) == 1:
				descr = "copying: " + one_file.path
			else:
				descr = "copying files from: " + one_file.dirname
			def generate():
				for f in self.env.copy:
					if isdir(f.path):
						for _ in shutil_g.copytree(src=f.path,
								dst=join(original_path, f.basename),
								symlinks=True,
								overwrite=overwrite):
							yield
					else:
						for _ in shutil_g.copy2(f.path, original_path,
								symlinks=True,
								overwrite=overwrite):
							yield
				cwd = self.env.get_directory(original_path)
				cwd.load_content()

		self.loader.add(LoadableObject(generate(), descr))

	def delete(self):
		self.notify("Deleting!", duration=1)
		selected = self.env.get_selection()
		self.env.copy -= set(selected)
		if selected:
			for f in selected:
				if isdir(f.path) and not os.path.islink(f.path):
					try:
						shutil.rmtree(f.path)
					except OSError as err:
						self.notify(err)
				else:
					try:
						os.remove(f.path)
					except OSError as err:
						self.notify(err)
		self.env.ensure_correct_pointer()

	def mkdir(self, name):
		try:
			os.mkdir(os.path.join(self.env.cwd.path, name))
		except OSError as err:
			self.notify(err)

	def rename(self, src, dest):
		if hasattr(src, 'path'):
			src = src.path

		try:
			os.rename(src, dest)
		except OSError as err:
			self.notify(err)