summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--ranger/core/actions.py8
-rw-r--r--ranger/defaults/keys.py5
-rw-r--r--ranger/ext/relative_symlink.py39
-rw-r--r--ranger/help/fileop.py3
-rw-r--r--test/tc_relative_symlink.py47
5 files changed, 97 insertions, 5 deletions
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index a5ee0d4d..a93344a4 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -23,6 +23,7 @@ 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
@@ -660,11 +661,14 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		self.env.cut = True
 		self.ui.browser.main_column.request_redraw()
 
-	def paste_symlink(self):
+	def paste_symlink(self, relative=False):
 		copied_files = self.env.copy
 		for f in copied_files:
 			try:
-				symlink(f.path, join(getcwd(), f.basename))
+				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)
 
diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py
index 7d8ccc4a..9f0c78cb 100644
--- a/ranger/defaults/keys.py
+++ b/ranger/defaults/keys.py
@@ -175,9 +175,10 @@ map('da', fm.cut(mode='add'))
 map('dr', fm.cut(mode='remove'))
 map('pp', fm.paste())
 map('po', fm.paste(overwrite=True))
-map('pl', fm.paste_symlink())
+map('pl', fm.paste_symlink(relative=False))
+map('pL', fm.paste_symlink(relative=True))
 map('p<bg>', fm.hint('press *p* to confirm pasting' \
-		', *o* to overwrite or *l* to create symlinks'))
+		', *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())
diff --git a/ranger/ext/relative_symlink.py b/ranger/ext/relative_symlink.py
new file mode 100644
index 00000000..bba00e39
--- /dev/null
+++ b/ranger/ext/relative_symlink.py
@@ -0,0 +1,39 @@
+# 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/>.
+
+from os import symlink, sep
+from os.path import dirname, join
+
+def relative_symlink(src, dst):
+	common_base = get_common_base(src, dst)
+	symlink(get_relative_source_file(src, dst, common_base), dst)
+
+def get_relative_source_file(src, dst, common_base=None):
+	if common_base is None:
+		common_base = get_common_base(src, dst)
+	return '../' * dst.count('/', len(common_base)) + src[len(common_base):]
+
+def get_common_base(src, dst):
+	if not src or not dst:
+		return '/'
+	i = 0
+	while True:
+		new_i = src.find(sep, i + 1)
+		if new_i == -1:
+			break
+		if not dst.startswith(src[:new_i + 1]):
+			break
+		i = new_i
+	return src[:i + 1]
diff --git a/ranger/help/fileop.py b/ranger/help/fileop.py
index f8401800..ac23c6d4 100644
--- a/ranger/help/fileop.py
+++ b/ranger/help/fileop.py
@@ -31,7 +31,7 @@ harm your files:
 :chmod <number>    Change the rights of the selection
 :delete            DELETES ALL FILES IN THE SELECTION
 :rename <newname>  Change the name of the current file
-pp, pl, po         Pastes the copied files in different ways
+pp, pl, pL, po     Pastes the copied files in different ways
 
 Think twice before using these commands or key combinations.
 
@@ -67,6 +67,7 @@ The "highlighted file", or the "current file", is the one below the cursor.
 		Instead, a "_" character will be appended to the new filename.
 	po	paste the copied/cut files. Existing files are overwritten.
 	pl	create symbolic links to the copied/cut files.
+	pL	create relative symbolic links to the copied/cut files.
 
 The difference between copying and cutting should be intuitive:
 
diff --git a/test/tc_relative_symlink.py b/test/tc_relative_symlink.py
new file mode 100644
index 00000000..a202513d
--- /dev/null
+++ b/test/tc_relative_symlink.py
@@ -0,0 +1,47 @@
+# 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.path
+import sys
+rangerpath = os.path.join(os.path.dirname(__file__), '..')
+if sys.path[1] != rangerpath:
+	sys.path[1:1] = [rangerpath]
+
+import unittest
+from ranger.ext.relative_symlink import *
+rel = get_relative_source_file
+
+class Test(unittest.TestCase):
+	def test_foo(self):
+		self.assertEqual('../foo', rel('/foo', '/x/bar'))
+		self.assertEqual('../../foo', rel('/foo', '/x/y/bar'))
+		self.assertEqual('../../a/b/foo', rel('/a/b/foo', '/x/y/bar'))
+		self.assertEqual('../../x/b/foo', rel('/x/b/foo', '/x/y/bar',
+			common_base='/'))
+		self.assertEqual('../b/foo', rel('/x/b/foo', '/x/y/bar'))
+		self.assertEqual('../b/foo', rel('/x/b/foo', '/x/y/bar'))
+
+	def test_get_common_base(self):
+		self.assertEqual('/', get_common_base('', ''))
+		self.assertEqual('/', get_common_base('', '/'))
+		self.assertEqual('/', get_common_base('/', ''))
+		self.assertEqual('/', get_common_base('/', '/'))
+		self.assertEqual('/', get_common_base('/bla/bar/x', '/foo/bar/a'))
+		self.assertEqual('/foo/bar/', get_common_base('/foo/bar/x', '/foo/bar/a'))
+		self.assertEqual('/foo/', get_common_base('/foo/bar/x', '/foo/baz/a'))
+		self.assertEqual('/foo/', get_common_base('/foo/bar/x', '/foo/baz/a'))
+		self.assertEqual('/', get_common_base('//foo/bar/x', '/foo/baz/a'))
+
+if __name__ == '__main__': unittest.main()