summary refs log tree commit diff stats
path: root/ranger
diff options
context:
space:
mode:
Diffstat (limited to 'ranger')
-rwxr-xr-xranger34
-rw-r--r--ranger/__init__.py0
-rw-r--r--ranger/cli.py32
-rw-r--r--ranger/command.py77
-rw-r--r--ranger/debug.py12
-rw-r--r--ranger/defaultui.py39
-rw-r--r--ranger/directory.py74
-rw-r--r--ranger/directory.rb66
-rw-r--r--ranger/environment.py73
-rw-r--r--ranger/file.py6
-rw-r--r--ranger/fm.py42
-rw-r--r--ranger/fsobject.py103
-rw-r--r--ranger/fstype.py5
-rw-r--r--ranger/keys.py15
-rw-r--r--ranger/options.py5
-rw-r--r--ranger/ui.py68
-rw-r--r--ranger/wdisplay.py51
-rw-r--r--ranger/widget.py43
-rw-r--r--ranger/wstatusbar.py12
19 files changed, 723 insertions, 34 deletions
diff --git a/ranger b/ranger
deleted file mode 100755
index d83e8a3e..00000000
--- a/ranger
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/python
-# coding=utf-8
-
-# TODO: cd after exit
-
-from code import debug, fm, defaultui, options, environment
-
-# TODO: find out the real name of this script and include files relative to here
-
-# TODO: Parse arguments
-
-# TODO: load config
-
-def main():
-	import locale, os
-	os.stat_float_times(True)
-	locale.setlocale(locale.LC_ALL, 'en_US.utf8')
-
-	try:
-		path = '/srv/music/compilations/'
-		opt = options.get()
-		env = environment.Environment(opt)
-
-		my_ui = defaultui.DefaultUI(env)
-		my_fm = fm.FM(env)
-		my_fm.feed(path, my_ui)
-		my_fm.run()
-
-	except:
-		my_ui.exit()
-		raise
-
-
-if __name__ == "__main__": main()
diff --git a/ranger/__init__.py b/ranger/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ranger/__init__.py
diff --git a/ranger/cli.py b/ranger/cli.py
new file mode 100644
index 00000000..7f8fd77f
--- /dev/null
+++ b/ranger/cli.py
@@ -0,0 +1,32 @@
+import curses
+import _thread
+
+class CLIError(): pass
+
+class CLI():
+	def __init__(self):
+		self.lock = _thread.allocalte_lock()
+		self.running = False
+
+	def start(self):
+		with self.lock:
+			stdscr = curses.initscr()
+			self.running = True
+
+	def exit(self):
+		self.stop_unless_running()
+		with self.lock:
+			self.running = False
+			curses.nocbreak()
+			stdscr.keypad(1)
+			curses.endwin()
+
+	def stop_unless_running(self):
+		if not self.running:
+			raise CLIError("This function needs the cli to be runnig!")
+
+	def print(self, text, x=0, y=0, attr=None):
+		with self.lock:
+
+	
+
diff --git a/ranger/command.py b/ranger/command.py
new file mode 100644
index 00000000..c57edba0
--- /dev/null
+++ b/ranger/command.py
@@ -0,0 +1,77 @@
+class CommandDummy():
+	pass
+
+class CommandList():
+	def __init__(self):
+		self.commandlist = []
+		self.paths = {}
+		self.dummies_in_paths = False
+		self.dummy_object = CommandDummy
+
+	def rebuild_paths(self):
+		paths = self.paths
+
+		if self.dummies_in_paths:
+			self.remove_dummies()
+		
+		for cmd in self.commandlist:
+			for key in cmd.keys:
+				path = []
+				for path in self.keypath(key):
+					try: paths[path]
+					except KeyError:
+						paths[path] = self.dummy_object
+
+		self.dummies_in_paths = True
+
+	def keypath(self, tup):
+		current = []
+		all = []
+		
+		for i in range(len(tup) - 1):
+			current.append(tup[i])
+			all.append(tuple(current))
+
+		return all
+
+	def remove_dummies(self):
+		for k in tuple(paths.keys()):
+			if paths[k] == self.dummy_object: del paths[k]
+		self.dummies_in_paths = False
+
+
+	def str_to_tuple(self, obj):
+		"""splits a string into a tuple of integers"""
+		if type(obj) == tuple:
+			return obj
+		elif type(obj) == str:
+			return tuple(map(ord, list(obj)))
+		else:
+			raise TypeError('need a str or a tuple for str_to_tuple')
+	
+	def bind(self, fnc, *keys):
+		keys = tuple(map(self.str_to_tuple, keys))
+		cmd = Command(fnc, keys)
+		cmd.commandlist = self
+		self.commandlist.append(cmd)
+		for key in keys:
+			self.paths[key] = cmd
+	
+class Command():
+	def __init__(self, fnc, keys):
+		self.fnc = fnc
+		self.keys = keys
+		self.commandlist = None
+
+#	def rebind(keys):
+#		self.keys = keys
+#		self.commandlist.rebuild_paths()
+
+	def execute(self, fm):
+		self.fnc(fm)
+
+if __name__ == '__main__':
+	cl = CommandList()
+	cl.initialize_commands()
+
+	print(cl.paths)
diff --git a/ranger/debug.py b/ranger/debug.py
new file mode 100644
index 00000000..12d5d654
--- /dev/null
+++ b/ranger/debug.py
@@ -0,0 +1,12 @@
+# a module to faciliate debuggin
+
+LOGFILE = '/tmp/errorlog'
+
+def log(txt):
+	f = open(LOGFILE, 'a')
+	f.write("r1: ")
+	f.write(str(txt))
+	f.write("\n")
+	f.close()
+
+
diff --git a/ranger/defaultui.py b/ranger/defaultui.py
new file mode 100644
index 00000000..70ce75ff
--- /dev/null
+++ b/ranger/defaultui.py
@@ -0,0 +1,39 @@
+import ranger.ui
+from ranger.wdisplay import WDisplay
+from ranger.wstatusbar import WStatusBar
+
+class DefaultUI(ranger.ui.UI):
+	def setup(self):
+		self.statusbar = WStatusBar(self.win)
+		self.add_widget(self.statusbar)
+
+		self.displays = [
+				WDisplay(self.win, -2),
+				WDisplay(self.win, -1),
+				WDisplay(self.win, 0),
+				WDisplay(self.win, 1) ]
+		self.displays[2].display_infostring = True
+		self.displays[2].main_display = True
+		for disp in self.displays:
+			self.add_widget(disp)
+	
+	RATIO = ( 0.15, 0.15, 0.4, 0.3 )
+	
+	def resize(self):
+		ranger.ui.UI.resize(self)
+		y, x = self.win.getmaxyx()
+
+		leftborder = 0
+
+		i = 0
+		for ratio in DefaultUI.RATIO:
+			wid = int(ratio * x)
+			try:
+				self.displays[i].setdim(1, leftborder, y-1, wid - 1)
+			except KeyError:
+				pass
+			leftborder += wid
+			i += 1
+
+		self.statusbar.setdim(0, 0, 1, x)
+
diff --git a/ranger/directory.py b/ranger/directory.py
new file mode 100644
index 00000000..392f67bc
--- /dev/null
+++ b/ranger/directory.py
@@ -0,0 +1,74 @@
+import ranger.fsobject
+from ranger import file, debug
+
+class Directory(ranger.fsobject.FSObject):
+	def __init__(self, path):
+		ranger.fsobject.FSObject.__init__(self, path)
+		self.content_loaded = False
+		self.scheduled = False
+		self.enterable = False
+
+		self.filenames = None
+		self.files = None
+		self.filter = None
+		self.pointed_index = None
+		self.pointed_file = None
+		self.index = None
+	
+	def load_content(self):
+		self.stop_if_frozen()
+		self.load_if_outdated()
+		self.content_loaded = True
+		import os
+		if self.exists and self.runnable:
+			basenames = os.listdir(self.path)
+			mapped = map(lambda name: os.path.join(self.path, name), basenames)
+			self.filenames = list(mapped)
+			self.infostring = ' %d' % len(self.filenames) # update the infostring
+			self.files = []
+			for name in self.filenames:
+				if os.path.isdir(name):
+					f = Directory(name)
+				else:
+					f = file.File(name)
+				f.load()
+				self.files.append(f)
+		else:
+			self.filenames = None
+			self.files = None
+			self.infostring = ranger.fsobject.FSObject.BAD_INFO
+	
+	def load_content_once(self):
+		self.stop_if_frozen()
+		if not self.content_loaded:
+			self.load_content()
+			return True
+		return False
+
+	def load_content_if_outdated(self):
+		self.stop_if_frozen()
+		if self.load_content_once(): return True
+
+		import os
+		real_mtime = os.stat(self.path).st_mtime
+		cached_mtime = self.stat.st_mtime
+
+		if real_mtime != cached_mtime:
+			self.load_content()
+			return True
+		return False
+
+	def __len__(self):
+		if not self.accessible: raise ranger.fsobject.NotLoadedYet()
+		return len(self.filenames)
+	
+	def __getitem__(self, key):
+		if not self.accessible: raise ranger.fsobject.NotLoadedYet()
+		return self.files[key]
+
+if __name__ == '__main__':
+	d = Directory('.')
+	d.load_filenames()
+	print(d.filenames)
+	print(d[1])
+
diff --git a/ranger/directory.rb b/ranger/directory.rb
new file mode 100644
index 00000000..5c6e84c1
--- /dev/null
+++ b/ranger/directory.rb
@@ -0,0 +1,66 @@
+# A Class that contains data about directories
+class Directory
+	class LoadStatus
+		# @n contains a three bit number: x3x2x1
+		# x1:
+		# 0 = not scheduled
+		# 1 = scheduled
+		# x3x2:
+		# 00 = nothing loaded
+		# 01 = got the list of files
+		# 10 = <undefined>
+		# 11 = got the list of files and entry objects
+		def initialize(n = 0)
+			@n = 0
+		end
+
+		def got_files?
+			# is bit 2 nd 3 == 01
+			return n & 2 == 2
+		end
+
+		def scheduled?
+			# is the first bit 1?
+			return n & 1 == 1
+		end
+
+		def got_objects?
+			return n & 4 == 4
+		end
+		attr_accessor :n
+	end
+
+	def initialize(path)
+		@path = path
+		@status = LoadStatus.new(0)
+		@files = []
+		@sort_time = nil
+		@mtime = nil
+#		@width = 1000
+		@read = false
+		@free_space = nil
+		@empty = true
+		@scheduled = false
+	end
+
+	# {{{ Trivial
+	def inspect
+		return "<Directory: #{path}>"
+	end
+	alias to_s inspect
+
+	def size
+		return @files.size
+	end
+
+	def not_loaded?
+		return @level == 0
+	end
+	def file_list_loaded?
+		return @level >= 1
+	end
+	def ready?
+		return @level >= 2
+	end
+	# }}}
+end
diff --git a/ranger/environment.py b/ranger/environment.py
new file mode 100644
index 00000000..b5b3888c
--- /dev/null
+++ b/ranger/environment.py
@@ -0,0 +1,73 @@
+import os
+from ranger.directory import Directory
+
+class Vector():
+	def __init__(self, x, y):
+		self.x = x
+		self.y = y
+
+class Environment():
+	# A collection of data which is relevant for more than
+	# one class.
+	def __init__(self, opt):
+		self.opt = opt
+		self.path = None
+		self.pathway = ()
+		self.directories = {}
+		self.pwd = None # current directory
+		self.cf = None # current file
+		self.keybuffer = ()
+		self.copy = None
+		self.termsize = Vector(80, 24)
+
+	def key_append(self, key):
+		self.keybuffer += (key, )
+
+	def key_clear(self):
+		self.keybuffer = ()
+	
+	def at_level(self, level):
+		if level <= 0:
+			try:
+				return self.pathway[level - 1]
+			except IndexError:
+				return None
+		else:
+			return self.cf
+
+	def get_directory(self, path):
+		import os
+		path = os.path.abspath(path)
+		try:
+			return self.directories[path]
+		except KeyError:
+			self.directories[path] = Directory(path)
+			return self.directories[path]
+
+	def enter_dir(self, path):
+		# get the absolute path
+		path = os.path.normpath(os.path.join(self.path, path))
+
+		self.path = path
+		self.pwd = self.get_directory(path)
+
+		self.pwd.load_content()
+
+		# build the pathway, a tuple of directory objects which lie
+		# on the path to the current directory.
+		if path == '/':
+			self.pathway = (self.get_directory('/'), )
+		else:
+			pathway = []
+			currentpath = '/'
+			for dir in path.split('/'):
+				currentpath = os.path.join(currentpath, dir)
+#			debug.log(currentpath)
+				pathway.append(self.get_directory(currentpath))
+			self.pathway = tuple(pathway)
+
+		# set the current file.
+		if len(self.pwd) > 0:
+			self.cf = self.pwd[0]
+		else:
+			self.cf = None
diff --git a/ranger/file.py b/ranger/file.py
new file mode 100644
index 00000000..94c62f9a
--- /dev/null
+++ b/ranger/file.py
@@ -0,0 +1,6 @@
+import ranger.fsobject
+class File(ranger.fsobject.FSObject):
+	pass
+#	def __init__(self, path):
+#		fsobject.FSObject.__init__(self, path)
+
diff --git a/ranger/fm.py b/ranger/fm.py
new file mode 100644
index 00000000..d616523e
--- /dev/null
+++ b/ranger/fm.py
@@ -0,0 +1,42 @@
+import sys
+#LOGFILE = '/tmp/errorlog'
+#f = open(LOGFILE, 'a')
+#f.write(str(tuple(sys.path)) + "\n")
+#f.close()
+#import code.ui, code.debug, code.file, code.directory, code.fstype
+
+
+class FM():
+	def __init__(self, environment):
+		self.env = environment
+
+	def feed(self, path, ui):
+		self.ui = ui
+		self.env.path = path
+		self.env.enter_dir(path)
+
+	def run(self):
+		import time
+		while 1:
+			try:
+				self.ui.draw()
+				key = self.ui.get_next_key()
+				self.ui.press(key, self)
+			except KeyboardInterrupt:
+				self.env.key_clear()
+				time.sleep(0.2)
+			except:
+				raise
+
+	def exit(self):
+		raise SystemExit()
+
+	def move_left(self):
+		self.env.enter_dir('..')
+
+	def move_right(self):
+		self.env.enter_dir(self.env.cf.path)
+
+	def move_relative(self):
+		pass
+
diff --git a/ranger/fsobject.py b/ranger/fsobject.py
new file mode 100644
index 00000000..a21818c0
--- /dev/null
+++ b/ranger/fsobject.py
@@ -0,0 +1,103 @@
+import ranger.fstype
+
+class FrozenException(Exception): pass
+class NotLoadedYet(Exception): pass
+
+class FSObject(object):
+	BAD_INFO = ''
+	def __init__(self, path):
+		if type(self) == FSObject:
+			raise TypeError("FSObject is an abstract class and cannot be initialized.")
+		from os.path import basename
+		self.path = path
+		self.basename = basename(path)
+		self.exists = False
+		self.accessible = False
+		self.marked = False
+		self.tagged = False
+		self.frozen = False
+		self.loaded = False
+		self.runnable = False
+		self.islink = False
+		self.brokenlink = False
+		self.stat = None
+		self.infostring = None
+		self.permissions = None
+		self.type = ranger.fstype.Unknown
+	
+	def __str__(self):
+		return str(self.path)
+
+	# load() reads useful information about the file from the file system
+	# and caches it in instance attributes.
+	def load(self):
+		self.stop_if_frozen()
+		self.loaded = True
+
+		import os
+#		try:
+		if os.access(self.path, os.F_OK):
+			self.stat = os.stat(self.path)
+			self.islink = os.path.islink(self.path)
+			self.exists = True
+			self.accessible = True
+
+			if os.path.isdir(self.path):
+				self.type = ranger.fstype.Directory
+				try:
+					self.infostring = ' %d' % len(os.listdir(self.path))
+					self.runnable = True
+				except OSError:
+					self.infostring = FSObject.BAD_INFO
+					self.runnable = False
+					self.accessible = False
+			elif os.path.isfile(self.path):
+				self.type = ranger.fstype.File
+				self.infostring = ' %d' % self.stat.st_size
+			else:
+				self.type = ranger.fstype.Unknown
+				self.infostring = None
+
+		else:
+#		except OSError:
+			self.islink = False
+			self.infostring = None
+			self.type = ranger.fstype.Nonexistent
+			self.exists = False
+			self.runnable = False
+			self.accessible = False
+
+	def load_once(self):
+		self.stop_if_frozen()
+		if not self.loaded:
+			self.load()
+			return True
+		return False
+
+	def load_if_outdated(self):
+		self.stop_if_frozen()
+		if self.load_once(): return True
+
+		import os
+		real_mtime = os.stat(self.path).st_mtime
+		cached_mtime = self.stat.st_mtime
+
+		if real_mtime != cached_mtime:
+			self.load()
+			return True
+		return False
+
+	def clone(self):
+		clone = type(self)(self.path)
+		for key in iter(self.__dict__):
+			clone.__dict__[key] = self.__dict__[key]
+		return clone
+
+	def frozen_clone(self):
+		clone = self.clone()
+		clone.frozen = True
+		return clone
+
+	def stop_if_frozen(self):
+		if self.frozen: raise FrozenException('Cannot modify datastructure while it is frozen')
+
diff --git a/ranger/fstype.py b/ranger/fstype.py
new file mode 100644
index 00000000..4bd0988d
--- /dev/null
+++ b/ranger/fstype.py
@@ -0,0 +1,5 @@
+class File: pass
+class Directory: pass
+class Nonexistent: pass
+class Unknown: pass
+
diff --git a/ranger/keys.py b/ranger/keys.py
new file mode 100644
index 00000000..b710a2aa
--- /dev/null
+++ b/ranger/keys.py
@@ -0,0 +1,15 @@
+def initialize_commands(command_list):
+	from ranger.fm import FM
+
+	cl = command_list
+	
+	# note: the bound function will be called with one parameter, which
+	# is the FM instance. To use functions with multiple parameters, use:
+	# lambda fm: myfunction(fm, param1, param2, ...) as the function
+
+	cl.bind(FM.move_left, 'h', 'back')
+	cl.bind(FM.move_right, 'l', 'forward')
+	cl.bind(FM.exit, 'q')
+
+	cl.rebuild_paths()
+
diff --git a/ranger/options.py b/ranger/options.py
new file mode 100644
index 00000000..eed79f1e
--- /dev/null
+++ b/ranger/options.py
@@ -0,0 +1,5 @@
+def get():
+	return []
+
+def dummy():
+	return []
diff --git a/ranger/ui.py b/ranger/ui.py
new file mode 100644
index 00000000..6bc06b19
--- /dev/null
+++ b/ranger/ui.py
@@ -0,0 +1,68 @@
+import curses
+from ranger.debug import log
+class UI():
+	def __init__(self, env, commandlist):
+		self.env = env
+		self.commandlist = commandlist
+
+		self.widgets = []
+		self.win = curses.initscr()
+		self.win.leaveok(1)
+		curses.noecho()
+		curses.halfdelay(3)
+
+		self.setup()
+		self.resize()
+
+	def setup(self):
+		pass
+
+	def resize(self):
+		self.env.termsize = self.win.getmaxyx()
+
+	def add_widget(self, widg):
+		self.widgets.append(widg)
+
+	def feed_env(self, env):
+		self.env = env
+
+	def press(self, key, fm):
+		self.env.key_append(key)
+		log(self.env.keybuffer)
+
+		try:
+			cmd = self.commandlist.paths[self.env.keybuffer]
+		except KeyError:
+			self.env.key_clear()
+			return
+
+		if cmd == self.commandlist.dummy_object:
+			return
+
+		cmd.execute(fm)
+		self.env.key_clear()
+
+	def exit(self):
+		curses.nocbreak()
+		curses.echo()
+		curses.endwin()
+
+	def draw(self):
+		from ranger.debug import log
+		self.win.erase()
+		for widg in self.widgets:
+			widg.feed_env(self.env)
+			widg.draw()
+		self.win.refresh()
+		log(tuple(map(str, self.env.pathway)))
+
+#		for i in range(1, len(self.env.pwd)):
+#			f = self.env.pwd.files[i]
+#			self.win.addstr(i, 0, f.path)
+#			if f.infostring: self.win.addstr(i, 50, f.infostring)
+
+	def get_next_key(self):
+		key = self.win.getch()
+		curses.flushinp()
+		return key
+
diff --git a/ranger/wdisplay.py b/ranger/wdisplay.py
new file mode 100644
index 00000000..4bd84044
--- /dev/null
+++ b/ranger/wdisplay.py
@@ -0,0 +1,51 @@
+import ranger.widget
+import curses
+
+class WDisplay(ranger.widget.Widget):
+	def __init__(self, win, level):
+		ranger.widget.Widget.__init__(self,win)
+		self.level = level
+		self.main_display = False
+		self.display_infostring = False
+
+	def feed_env(self, env):
+		self.target = env.at_level(self.level)
+
+	def draw(self):
+		from ranger.file import File
+		from ranger.directory import Directory
+
+		if self.target is None:
+			pass
+#			self.win.addnstr(self.y, self.x, "---", self.wid)
+		elif type(self.target) == File:
+			self.draw_file()
+		elif type(self.target) == Directory:
+			self.draw_directory()
+		else:
+			self.win.addnstr(self.y, self.x, "unknown type.", self.wid)
+
+	def draw_file(self):
+		if not self.target.accessible:
+			self.win.addnstr(self.y, self.x, "not accessible", self.wid)
+			return
+		self.win.addnstr(self.y, self.x, "this is a file.", self.wid)
+
+	def draw_directory(self):
+		self.target.load_content_once()
+		if not self.target.accessible:
+			self.win.addnstr(self.y, self.x, "not accessible", self.wid)
+			return
+		for i in range(self.hei):
+			try:
+				drawed = self.target[i]
+			except IndexError:
+				break
+			self.win.addnstr(self.y + i, self.x, drawed.basename, self.wid)
+			if self.display_infostring and drawed.infostring:
+				info = drawed.infostring
+				x = self.x + self.wid - 1 - len(info)
+				if x > self.x:
+					self.win.addstr(self.y + i, x, str(info) + ' ')
+
+
diff --git a/ranger/widget.py b/ranger/widget.py
new file mode 100644
index 00000000..725810ec
--- /dev/null
+++ b/ranger/widget.py
@@ -0,0 +1,43 @@
+import curses
+
+class OutOfBoundsException(Exception): pass
+
+class Widget():
+	def __init__(self, win):
+		self.win = win
+		self.setdim(0, 0, 0, 0)
+
+	def setdim(self, y, x, hei=None, wid=None):
+		maxy, maxx = self.win.getmaxyx()
+		wid = wid or maxx - x
+		hei = hei or maxy - y
+		if x + wid > maxx and y + hei > maxy:
+			raise OutOfBoundsException("X and Y out of bounds!")
+		if x + wid > maxx:
+			raise OutOfBoundsException("X out of bounds!")
+		if y + hei > maxy:
+			raise OutOfBoundsException("Y out of bounds!")
+
+		self.x = x
+		self.y = y
+		self.wid = wid
+		self.hei = hei
+	
+	def contains_point(self, y, x):
+		return (x >= self.x and x < self.x + self.wid) and \
+				(y >= self.y and y < self.y + self.hei)
+
+	def feed_env(self):
+		pass
+
+	def feed(self):
+		pass
+
+	def click(self):
+		pass
+	
+	def draw(self):
+		pass
+
+	def destroy(self):
+		pass
diff --git a/ranger/wstatusbar.py b/ranger/wstatusbar.py
new file mode 100644
index 00000000..e68fb241
--- /dev/null
+++ b/ranger/wstatusbar.py
@@ -0,0 +1,12 @@
+import curses
+import ranger.widget
+
+class WStatusBar(ranger.widget.Widget):
+	def feed_env(self, env):
+		self.pathway = env.pathway
+
+	def draw(self):
+		self.win.move(self.y, self.x)
+		for path in self.pathway:
+			currentx = self.win.getyx()[1]
+			self.win.addnstr(path.basename + ' / ', (self.wid - currentx))