about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--ranger/api/options.py1
-rw-r--r--ranger/core/actions.py9
-rw-r--r--ranger/defaults/options.py3
-rw-r--r--ranger/fsobject/file.py22
-rw-r--r--ranger/gui/ansi.py94
-rw-r--r--ranger/gui/curses_shortcuts.py9
-rw-r--r--ranger/gui/widgets/pager.py20
-rw-r--r--ranger/scripts/scope.sh22
-rw-r--r--ranger/shared/settings.py1
-rw-r--r--test/tc_ansi.py40
10 files changed, 206 insertions, 15 deletions
diff --git a/ranger/api/options.py b/ranger/api/options.py
index ee947b39..aee6150b 100644
--- a/ranger/api/options.py
+++ b/ranger/api/options.py
@@ -17,3 +17,4 @@ import re
 from re import compile as regexp
 from ranger.api import *
 from ranger.gui import color
+from ranger import relpath, relpath_conf
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 14f862c7..2db749cd 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -468,13 +468,8 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		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)
+		pager = self.ui.open_embedded_pager()
+		pager.set_source(self.env.cf.get_preview_source())
 
 	# --------------------------
 	# -- Tabs
diff --git a/ranger/defaults/options.py b/ranger/defaults/options.py
index 23752d97..4ff774c6 100644
--- a/ranger/defaults/options.py
+++ b/ranger/defaults/options.py
@@ -39,6 +39,9 @@ hidden_filter = regexp(
 	r'^\.|\.(?:pyc|pyo|bak|swp)$|~$|lost\+found')
 show_hidden = False
 
+# Which script is used to generate file previews?
+preview_script = relpath('scripts/scope.sh')  # relative to rangers path
+
 # Show dotfiles in the bookmark preview box?
 show_hidden_bookmarks = True
 
diff --git a/ranger/fsobject/file.py b/ranger/fsobject/file.py
index 0debb44c..216b4754 100644
--- a/ranger/fsobject/file.py
+++ b/ranger/fsobject/file.py
@@ -16,6 +16,9 @@
 import re
 import zipfile
 from ranger.fsobject import FileSystemObject
+from subprocess import Popen, PIPE
+from ranger.core.runner import devnull
+from ranger import relpath
 
 N_FIRST_BYTES = 20
 control_characters = set(chr(n) for n in
@@ -28,8 +31,8 @@ PREVIEW_BLACKLIST = re.compile(r"""
 			# one character extensions:
 				[oa]
 			# media formats:
-				| avi | [mj]pe?g | mp\d | og[gmv] | wm[av] | mkv | flv
-				| png | bmp | vob | wav | mpc | flac | divx? | xcf | pdf
+				| avi | mpe?g | mp\d | og[gmv] | wm[av] | mkv | flv
+				| vob | wav | mpc | flac | divx? | xcf | pdf
 			# binary files:
 				| torrent | class | so | img | py[co] | dmg
 			# containers:
@@ -78,17 +81,26 @@ class File(FileSystemObject):
 			return False
 		if not self.accessible or self.is_fifo or self.is_device:
 			return False
+		if self.image or self.container:
+			return True
 		if PREVIEW_WHITELIST.search(self.basename):
 			return True
 		if PREVIEW_BLACKLIST.search(self.basename):
 			return False
 		if self.path == '/dev/core' or self.path == '/proc/kcore':
 			return False
-		if self.extension not in ('zip',) and self.is_binary():
+		if self.is_binary():
 			return False
 		return True
 
 	def get_preview_source(self):
-		if self.extension == 'zip':
-			return '\n'.join(zipfile.ZipFile(self.path).namelist())
+		if self.fm.settings.preview_script:
+			try:
+				p = Popen([self.fm.settings.preview_script, self.path],
+						stdout=PIPE, stderr=devnull)
+				if p.poll():  # nonzero exit code
+					return None
+				return p.stdout
+			except:
+				pass
 		return open(self.path, 'r')
diff --git a/ranger/gui/ansi.py b/ranger/gui/ansi.py
new file mode 100644
index 00000000..a9b37665
--- /dev/null
+++ b/ranger/gui/ansi.py
@@ -0,0 +1,94 @@
+# Copyright (C) 2010 David Barnett <davidbarnett2@gmail.com>
+# Copyright (C) 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 ranger.gui import color
+import re
+
+ansi_re = re.compile('(\033' + r'\[\d*(?:;\d+)*?[a-zA-Z])')
+reset = '\033[0m'
+
+def split_ansi_from_text(ansi_text):
+	return ansi_re.split(ansi_text)
+
+def text_with_fg_bg_attr(ansi_text):
+	for chunk in split_ansi_from_text(ansi_text):
+		if chunk and chunk[0] == '\033':
+			if chunk[-1] != 'm':
+				continue
+			match = re.match(r'^.\[(.*).$', chunk)
+			attr_args = match.group(1)
+			fg, bg, attr = -1, -1, 0
+
+			# Convert arguments to attributes/colors
+			for arg in attr_args.split(';'):
+				try:
+					n = int(arg)
+				except:
+					if arg == '':
+						n = 0
+					else:
+						continue
+				if n == 0:
+					fg, bg, attr = -1, -1, 0
+				elif n == 1:
+					attr |= color.bold
+				elif n == 4:
+					attr |= color.underline
+				elif n == 7:
+					attr |= color.reverse
+				elif n == 8:
+					attr |= color.invisible
+				elif n >= 30 and n <= 37:
+					fg = n - 30
+				elif n == 39:
+					fg = -1
+				elif n >= 40 and n <= 47:
+					bg = n - 40
+				elif n == 49:
+					bg = -1
+			yield (fg, bg, attr)
+		else:
+			yield chunk
+
+def char_len(ansi_text):
+	return len(ansi_re.sub('', ansi_text))
+
+def char_slice(ansi_text, start, end):
+	slice_chunks = []
+	# skip to start
+	last_color = None
+	skip_len_left = start
+	len_left = end - start
+	for chunk in split_ansi_from_text(ansi_text):
+		m = ansi_re.match(chunk)
+		if m:
+			if chunk[-1] == 'm':
+				last_color = chunk
+		else:
+			if skip_len_left > len(chunk):
+				skip_len_left -= len(chunk)
+			else:		# finished skipping to start
+				if skip_len_left > 0:
+					chunk = chunk[skip_len_left:]
+				chunk_left = chunk[:len_left]
+				if len(chunk_left):
+					if last_color is not None:
+						slice_chunks.append(last_color)
+					slice_chunks.append(chunk_left)
+					len_left -= len(chunk_left)
+				if len_left == 0:
+					break
+	return ''.join(slice_chunks)
diff --git a/ranger/gui/curses_shortcuts.py b/ranger/gui/curses_shortcuts.py
index 3df45700..42f9dada 100644
--- a/ranger/gui/curses_shortcuts.py
+++ b/ranger/gui/curses_shortcuts.py
@@ -1,4 +1,5 @@
 # Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
+# Copyright (C) 2010 David Barnett <davidbarnett2@gmail.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
@@ -13,9 +14,11 @@
 # 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 curses
 import _curses
 
 from ranger.ext.iter_tools import flatten
+from ranger.gui.color import get_color
 from ranger.shared import SettingsAware
 
 def ascii_only(string):
@@ -95,6 +98,12 @@ class CursesShortcuts(SettingsAware):
 		except _curses.error:
 			pass
 
+	def set_fg_bg_attr(self, fg, bg, attr):
+		try:
+			self.win.attrset(curses.color_pair(get_color(fg, bg)) | attr)
+		except _curses.error:
+			pass
+
 	def color_reset(self):
 		"""Change the colors to the default colors"""
 		CursesShortcuts.color(self, 'reset')
diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py
index c0bc98b4..58fcdfd1 100644
--- a/ranger/gui/widgets/pager.py
+++ b/ranger/gui/widgets/pager.py
@@ -1,4 +1,5 @@
 # Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
+# Copyright (C) 2010 David Barnett <davidbarnett2@gmail.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
@@ -18,6 +19,7 @@ The pager displays text and allows you to scroll inside it.
 """
 import re
 from . import Widget
+from ranger.gui import ansi
 from ranger.ext.direction import Direction
 from ranger.container.keymap import CommandArgs
 
@@ -106,6 +108,13 @@ class Pager(Widget):
 
 			if TITLE_REGEXP.match(line):
 				self.color_at(i, 0, -1, 'title', *baseclr)
+		elif self.markup == 'ansi':
+			self.win.move(i, 0)
+			for chunk in ansi.text_with_fg_bg_attr(line):
+				if isinstance(chunk, tuple):
+					self.set_fg_bg_attr(*chunk)
+				else:
+					self.addstr(chunk)
 
 	def move(self, narg=None, **kw):
 		direction = Direction(kw)
@@ -158,12 +167,13 @@ class Pager(Widget):
 
 		if isinstance(source, str):
 			self.source_is_stream = False
-			self.lines = source.split('\n')
+			self.lines = source.splitlines()
 		elif hasattr(source, '__getitem__'):
 			self.source_is_stream = False
 			self.lines = source
 		elif hasattr(source, 'readline'):
 			self.source_is_stream = True
+			self.markup = 'ansi'
 			self.lines = []
 		else:
 			self.source = None
@@ -206,8 +216,12 @@ class Pager(Widget):
 		while True:
 			try:
 				line = self._get_line(i).expandtabs(4)
-				line = line[startx:self.wid + startx].rstrip()
-				yield line
+				if self.markup is 'ansi':
+					line = ansi.char_slice(line, startx, self.wid + startx) \
+							+ ansi.reset
+				else:
+					line = line[startx:self.wid + startx]
+				yield line.rstrip()
 			except IndexError:
 				raise StopIteration
 			i += 1
diff --git a/ranger/scripts/scope.sh b/ranger/scripts/scope.sh
new file mode 100644
index 00000000..998476dc
--- /dev/null
+++ b/ranger/scripts/scope.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# This script is responsible to generate the previews for ranger.
+mimetype=$(file --mime-type -Lb "$1")
+basetype=$(echo "$mimetype" | grep -o '^[^/]\+')
+extension=$(echo "$1" | grep '\.' | grep -o '[^.]\+$')
+
+case "$basetype" in
+	text)
+		highlight --ansi "$1" || cat "$1"
+		exit 0;;
+	image)
+		img2txt "$1" || exit 1
+		exit 0;;
+esac
+
+case "$extension" in
+	zip|gz)
+		atool -l "$1"
+		exit 0;;
+esac
+
+exit 1
diff --git a/ranger/shared/settings.py b/ranger/shared/settings.py
index f5d8614f..24aea39c 100644
--- a/ranger/shared/settings.py
+++ b/ranger/shared/settings.py
@@ -41,6 +41,7 @@ ALLOWED_SETTINGS = {
 	'tilde_in_titlebar': bool,
 	'max_history_size': (int, type(None)),
 	'max_console_history_size': (int, type(None)),
+	'preview_script': (str, type(None)),
 	'scroll_offset': int,
 	'preview_files': bool,
 	'preview_directories': bool,
diff --git a/test/tc_ansi.py b/test/tc_ansi.py
new file mode 100644
index 00000000..0a6ad8b1
--- /dev/null
+++ b/test/tc_ansi.py
@@ -0,0 +1,40 @@
+# Copyright (C) 2010  David Barnett <davidbarnett2@gmail.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/>.
+
+if __name__ == '__main__': from __init__ import init; init()
+
+import unittest
+from ranger.gui import ansi
+
+class TestDisplayable(unittest.TestCase):
+	def test_char_len(self):
+		ansi_string = "X"
+		self.assertEqual(ansi.char_len(ansi_string), 1)
+
+	def test_char_len2(self):
+		ansi_string = "XY"
+		self.assertEqual(ansi.char_len(ansi_string), 2)
+
+	def test_char_len3(self):
+		ansi_string = "XY"
+		self.assertEqual(ansi.char_len(ansi_string), 2)
+
+	def test_char_slice(self):
+		ansi_string = "XY"
+		expected = "X"
+		self.assertEqual(ansi.char_slice(ansi_string, 0, 1), expected)
+
+if __name__ == '__main__':
+	unittest.main()