summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog21
-rw-r--r--code/debug.rb67
-rw-r--r--code/draw.rb265
-rw-r--r--code/extensions.rb487
-rw-r--r--code/fm.rb393
-rw-r--r--code/keys.rb321
-rw-r--r--code/old_fm.rb233
-rw-r--r--code/types.rb50
-rwxr-xr-xfm57
-rw-r--r--interface/ncurses.rb68
10 files changed, 1313 insertions, 649 deletions
diff --git a/ChangeLog b/ChangeLog
index bb331506..375565f6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,9 +1,26 @@
+Version 0.2
+
+2009-04-10  hut
+	Generating Previews in the background
+	Help screen
+	Tooltips for (S) and (t)
+	Added more filetype associations
+	Split up fm.rb into several files
+
+2009-04-09  hut
+	New Hotkey (S) to change sorting order
+	Generalized file-modifying tasks in the Action module
+
+2009-04-08  hut
+	Files are now Objects instead of Strings
+	Threads and Progress Bars for copying and moving
+
 Version 0.1
 
 2009-04-08  hut
-	Well-running Version with some bugs here and there.
+	Well-running Version with some bugs here and there
 	Starting new minor version
 
 2009-04-03  hut
-	Project was created.
+	The project was created
 
diff --git a/code/debug.rb b/code/debug.rb
new file mode 100644
index 00000000..6261ed0e
--- /dev/null
+++ b/code/debug.rb
@@ -0,0 +1,67 @@
+
+require 'pp'
+
+module Debug
+	@@logfile = '/tmp/errorlog'
+	@@logstream = File.open(@@logfile, 'a')
+
+	def self.write(str)
+		@@logstream.write(str)
+		@@logstream.flush
+		return str
+	end
+	def self.puts(str)
+		@@logstream.puts(str)
+		@@logstream.flush
+		return str
+	end
+
+	def try(&block)
+		return unless block
+		begin
+			yield
+		rescue Exception
+		end
+	end
+
+	if LOG_LEVEL > 0
+		def __log__(obj, level)
+			if level <= LOG_LEVEL
+				obj = obj.nil? ? "checkpoint at #{Time.now}" : obj
+				Debug.puts(obj)
+			end
+		end
+		def __logpp__(obj, level)
+			if level <= LOG_LEVEL
+				$stdout = @@logstream
+				pp obj
+				$stdout.flush
+				$stdout = STDOUT
+			end
+		end
+
+		def logfatal(obj = nil) __log__(obj, 1) end
+		def logppfatal(obj = nil) __logpp__(obj, 1) end
+
+		def logerr(obj = nil) __log__(obj, 2) end
+		def logpperr(obj = nil) __logpp__(obj, 2) end
+
+		def log(obj = nil) __log__(obj, 3) end
+		def logpp(obj = nil) __logpp__(obj, 3) end
+
+		def trace() __logpp__(caller, 3) end
+	else
+		def __log__(a, b) end
+		def __logpp__(a, b) end
+
+		def logfatal(a=nil) end
+		def logppfatal(a=nil) end
+
+		def logerr(a=nil) end
+		def logpperr(a=nil) end
+
+		def log(a=nil) end
+		def logpp(a=nil) end
+		def trace() end
+	end
+end
diff --git a/code/draw.rb b/code/draw.rb
new file mode 100644
index 00000000..1a2236cc
--- /dev/null
+++ b/code/draw.rb
@@ -0,0 +1,265 @@
+module Fm
+	DONT_PREVIEW_THESE_FILES = /\.(avi|[mj]pe?g|iso|mp\d|og[gmv]|wm[av]|mkv|torrent|so|class|flv|png|bmp|zip|rar|7z|tar|gz|vob|divx?)$/i
+
+	def self.column_put_file(n, file)
+		i = 0
+		if OPTIONS['filepreview'] and file.path !~ DONT_PREVIEW_THESE_FILES
+			m = lines - 2
+			color 7
+			bold false
+			File.open(file.path, 'r') do |f|
+				check = true
+				left, wid = get_boundaries(n)
+				f.lines.each do |l|
+					if check
+						check = false
+						break unless l.each_char.all? {|x| x[0] > 0 and x[0] < 128}
+					end
+					puti i+1, left, l.gsub("\t","   ")[0, wid-1].ljust(wid)
+					i += 1
+					break if i == m
+				end
+			end
+		end
+		column_clear(n, i)
+	end
+
+	def self.put_directory(c, d)
+		l = 0
+		if d
+			infos = (c == COLUMNS - 2)
+			left, wid = get_boundaries(c)
+
+			offset = get_offset(d, lines)
+			(lines - 1).times do |l|
+				lpo = l + offset
+				bg = -1
+				break if (f = d.files[lpo]) == nil
+
+				dir = false
+				if f.symlink?
+					bld = true
+					if f.broken_symlink?
+						clr = [1, bg]
+					else
+						clr = [6, bg]
+					end
+					dir = f.dir?
+				elsif f.dir?
+					bld = true
+					dir = true
+					clr = [4, bg]
+				elsif f.executable?
+					bld = true
+					clr = [2, bg]
+				else
+					bld = false
+					clr = [7, bg]
+				end
+
+				fn = f.basename
+				if f.marked?
+					fn = "*#{fn}"
+				end
+				if infos
+					myinfo = " #{f.infostring}  "
+					str = fn[0, wid-1].ljust(wid+1)
+					if str.size > myinfo.size
+						str[-myinfo.size..-1] = myinfo
+						yes = true
+					else
+						yes = false
+					end
+					puti l+1, left, str
+					if dir and yes
+						args = l+1, left+wid-myinfo.size, myinfo.size, *clr
+						color_bold_at(*args)
+					end
+				else
+					puti l+1, left, fn[0, wid-1].ljust(wid+1)
+				end
+
+				args = l+1, left, fn.size.limit(wid), *clr
+
+				if d.pos == lpo
+					color_reverse_at(*args)
+				else
+					if bld then color_bold_at(*args) else color_at(*args) end
+				end
+			end
+		end
+
+		column_clear(c, l)
+	end
+
+	def self.column_clear(n, from=0)
+		color(-1,-1)
+		left, wid = get_boundaries(n)
+		(from -1).upto(lines) do |l|
+			puti l+2, left, ' ' * (wid+1)
+		end
+	end
+
+	def self.get_offset(dir, max)
+		pos = dir.pos
+		len = dir.files.size
+		max -= 2
+		if len <= max or pos < max/2
+			return 0
+		elsif pos >= (len - max/2)
+			return len - max
+		else
+			return pos - max/2
+		end
+	end
+
+	def self.get_boundaries(column)
+		cols = Interface.cols # to cache
+		case column
+		when 0
+			return 0, cols / 8
+			
+		when 1
+			q = cols / 8
+			return q, q
+
+		when 2
+			q = cols / 4
+			w = @path.last.width.limit(cols/2, cols/8)
+			return q, w
+			
+		when 3
+			l = cols / 4 + 1
+			l += @path.last.width.limit(cols/2, cols/8)
+
+			return l, cols - l
+		end
+	end
+
+	def self.draw
+		bold false
+		@cur_y = get_boundaries(COLUMNS-2)[0]
+
+		if @buffer == '?'
+			cleari
+			puti 0, "      - - - Help - - -"
+			puti 2, "   h/j/k/l: Movement    J/K: fast Movement"
+			puti 3, "   H: Descend directory with respect to symlinks"
+			puti 4, "   L: Wait for <Enter> after execution of a program"
+			puti 6, "   t: Toggle Option     S: Change Sorting"
+			puti 7, "   E: Edit file         s: Enter Shell"
+			puti 8, "   rmdir: Remove whole dir  dD: Delete file or empty dir"
+			puti 9, "   dd: Move file to ~/.trash and memorize it's new path"
+			puti 10,"   yy: Memorize path    p: Copy memorized file here"
+			puti 11,"   mv<place>: move file to place  mkdir<name>: obvious"
+			puti 12,"   mX: Bookmark dir     'X: Enter bookmarked dir"
+			puti 13,"   '': Enter last visited dir (note: ' and ` are equal)"
+			puti 13,"   !<command> executes command"
+			puti 15,"   To interrupt current operations: <Ctrl-C>"
+			puti 16,"   To quit: q / ZZ / <Ctrl-D> / <Ctrl-C><Ctrl-C> (twice in a row)"
+			puti 18,"   Press one of those keys for more information: g f"
+		elsif @buffer == '?f'
+			cleari
+			puti 0, "      - - - Help - - -"
+			puti 2, "   f<regexp> or /<regexp> searches for pattern and presses l"
+			puti 3, "       when a matching file is found."
+			puti 4, "       Pressing L in this mode is like pressing l outside"
+			puti 6, "   F<regexp> like f but stay in this mode until <esc> is pressed"
+		elsif @buffer == '?g'
+			cleari
+			puti 0, "      - - - Help - - -"
+			puti 2, "   gg: go to top"
+			puti 3, "   G:  go to bottom"
+			puti 4, "   g0: go to /"
+			puti 5, "   gu: go to /usr/"
+			puti 6, "   gm: go to /media/"
+			puti 7, "   ge: go to /etc/"
+			puti 8, "   gh: go to ~/"
+			puti 9, "   gt: go to ~/.trash/"
+		else
+			s1 = "  "
+			s2 = "#{@path.last.path}#{"/" unless @path.size == 1}"
+			cf = currentfile
+			s3 = "#{cf ? cf.basename : ''}"
+			
+			puti 0, (s1 + s2 + s3).ljust(cols)
+
+			bg = -1
+			color_at 0, 0, -1, 7, bg
+			color_at 0, 0, s1.size, 7, bg
+			color_at 0, s1.size, s2.size, 6, bg
+			color_at 0, s1.size + s2.size, s3.size, 5, bg
+
+			bold false
+
+			begin
+				if cf.dir?
+					put_directory(3, @dirs[cf.path])
+				elsif cf.file?
+					column_put_file(3, cf)
+				else
+					column_clear(3)
+				end
+			rescue
+				column_clear(3)
+			end
+
+			pos_constant = @path.size - COLUMNS + 1
+
+			(COLUMNS - 1).times do |c|
+				pos = pos_constant + c
+
+				if pos >= 0
+					put_directory(c, @path[pos])
+				else
+					column_clear(c)
+				end
+			end
+
+			bold false
+			color -1, -1
+			btm = lines - 1
+
+			case @buffer
+			when 'S'
+				puti btm, "Sort by (n)ame (s)ize (m)time (c)time (CAPITAL:reversed)"
+			when 't'
+				puti btm, "Toggle (h)idden_files (d)irs_first (c)olor (f)ilepreview"
+			else
+#				log(@pwd)
+				puti btm, "#@buffer    #{@pwd.file_size.bytes(false)},#{@pwd.size},#{@pwd.pos+1}    ".rjust(cols)
+				more = ''
+				if cf.symlink?
+					more = "#{cf.readlink}"
+				end
+				puti btm, "  #{Time.now.strftime("%H:%M:%S %a %b %d")}  #{cf.rights} #{more}"
+
+				color_at btm, 23, 10, (cf.writable? ? 6 : 5), -1
+				if more
+					color_at btm, 34, more.size, (cf.exists? ? 6 : 1), -1
+				end
+			end
+
+			draw_bars unless @bars.empty?
+
+			movi(@pwd.pos + 1 - get_offset(@pwd, lines), @cur_y)
+		end
+	end
+
+	def self.draw_bars()
+		@bars.each_with_index do |bar, ix|
+			bar.update
+
+			l = -ix - 1
+			puti l, bar.text[0..cols-1].ljust(cols)
+			done = bar.done
+			c = (done * cols).to_i
+			unless done == 0
+				color_at l, 0, c, 0, 2
+			end
+			unless done == cols
+				color_at l, c, -1, 0, 3
+			end
+		end
+	end
+end
diff --git a/code/extensions.rb b/code/extensions.rb
index db238614..a15fc952 100644
--- a/code/extensions.rb
+++ b/code/extensions.rb
@@ -1,13 +1,348 @@
+class Bar
+	def initialize( text = '' )
+		@text = text
+		@done = 0
+		@thread = nil
+		@update_proc = nil
+		Fm.bar_add(self)
+	end
+
+	def kill(evil = true)
+		Fm.bar_del(self)
+		Fm.force_update
+
+		@thread.kill
+	end
+
+	def update(&block)
+		if block
+			@update_proc = block
+		elsif @update_proc
+			@update_proc.call(self)
+		end
+	end
+
+	attr_accessor :text, :done, :thread
+end
+
+class CopyBar < Bar
+	def initialize( text = '' )
+		super
+
+		@update_proc = proc do |b|
+			begin
+				b.done = File.size(fname).to_f / finished
+			rescue
+				b.done = 0
+			end
+		end
+	end
+end
+
+module Action
+#	def self.get_all_files(path)
+#		glob = Dir.new(path).to_a
+#	end
+	
+	def self.make_a_bar_for_one(text, command)
+		bar = CopyBar.new(test)
+
+		finished = File.size(from[0]).to_f
+		fname = File.join(to, File.basename(from[0]))
+
+		bar.thread = Thread.new do
+			begin
+				system('ionice', '-c3', command, *(from + [to]))
+			ensure
+				bar.kill(false)
+			end
+		end
+	end
+
+	def self.copy(from, to)
+#		log [from, to]
+
+#		if String === from[0]
+#			from[0] = Directory::Entry.new(from[0])
+#		end
+
+		if from.size == 1 and from[0].file?
+			from = from[0]
+			bar = Bar.new("Copying...")
+			finished = from.size.to_f
+			fname = File.join(to, from.basename)
+
+			bar.update do |b|
+				begin
+					b.done = File.size(fname).to_f / finished
+				rescue
+					b.done = 0
+				end
+			end
+
+			bar.thread = Thread.new do
+				begin
+					system('cp', from.to_s, to)
+				ensure
+					bar.kill(false)
+				end
+			end
+
+		else
+			bar = Bar.new("Copying...")
+			from = from.dup
+			from = [from] unless Array === from
+			finished = Dir.number_of_files(*from.map{|x| x.to_s})
+			count = 0
+
+			bar.update do |b|
+				begin
+					b.done = count / finished
+				rescue
+					b.done = 0
+				end
+			end
+			
+			from.map!{|x| x.to_s}
+			bar.thread = Thread.new do
+				begin
+					system('cp', '-r', *(from + [to.to_s]))
+#					IO.popen("cp -vr #{from.join(' ')} #{to.sh}") do |f|
+#					IO.popen(['cp', '-vr', *(from + [to])]) do |f|
+#						count += 1 while f.gets =~ /' -> `/
+#					end
+				ensure
+					bar.kill(false)
+				end
+			end
+		end
+	end
+
+	def self.move(from, to)
+#		log [from, to]
+		
+#		if String === from[0]
+#			from[0] = Directory::Entry.new(from[0])
+#		end
+
+		if from.size == 1 and from[0].file?
+			from = from[0]
+			bar = Bar.new("Moving...")
+			finished = from.size.to_f
+			fname = File.join(to, from.basename)
+
+			bar.update do |b|
+				begin
+					b.done = File.size(fname).to_f / finished
+				rescue
+					b.done = 0
+				end
+			end
+
+			bar.thread = Thread.new do
+				begin
+					system('mv', from.to_s, to)
+				ensure
+					bar.kill(false)
+				end
+			end
+
+		else
+			bar = Bar.new("Moving...")
+			from = from.dup
+			from = [from] unless Array === from
+			finished = Dir.number_of_files(*from.map{|x| x.to_s})
+			count = 0
+
+			bar.update do |b|
+				begin
+					b.done = count / finished
+				rescue
+					b.done = 0
+				end
+			end
+			
+			from.map!{|x| x.to_s}
+			bar.thread = Thread.new do
+				begin
+					system('mv', *(from + [to.to_s]))
+#					IO.popen("mv -v #{from.join(' ')} #{to.sh}") do |f|
+#						count += 1 while f.gets =~ /' -> `/
+#					end
+				ensure
+					bar.kill(false)
+				end
+			end
+		end
+	end
+end
+
 class Directory
-	def initialize(path)
+	BAD_TIME = Time.at(0)
+	class Entry #{{{
+		# Let's just cache every shit, because i don't want
+		# to call File methods all the time
+		
+		
+		def initialize(dirname, basename=nil)
+			if basename
+				@path = File.join(dirname, basename)
+				@dirname = dirname
+				@basename = basename
+			else
+				@path = dirname
+				@dirname = File.dirname(dirname)
+				@basename = File.basename(dirname)
+			end
+			@size = 0
+			@exists = false
+			@rights = '----------'
+			@readlink = ''
+			@symlink = false
+			@writable = false
+			@infostring = ''
+			@executable = false
+			@type = :nonexistent
+			@mtime = BAD_TIME
+			@ctime = BAD_TIME
+			@marked = false
+		end
+
+		attr_reader(:basename, :mtime, :rights, :type, :path,
+					  :infostring, :readlink, :basename, :size, :ctime)
+
+		attr_accessor(:marked)
+		
+		def to_s() @path end
+		def exists?() @exists end
+		def marked?() @marked end
+		def symlink?() @symlink end
+		def broken_symlink?() @symlink and !@exists end
+		def dir?() @type == :dir end
+		def file?() @type == :file end
+		def writable?() @writable end
+		def executable?() @executable end
+
+		def delete!
+			if @type == :dir
+				Dir.delete(@path) rescue nil
+			else
+				File.delete(@path) rescue nil
+			end
+		end
+
+		def refresh
+			if File.exists?(@path)
+				if File.ctime(@path) != @ctime
+					get_data
+				end
+			else
+				get_data
+			end
+		end
+
+		def sh
+			res = @path.dup
+			res.gsub!('\\\\', "\000")
+			res.gsub!(' ', '\\ ')
+			res.gsub!('(', '\\(')
+			res.gsub!('&', '\\&')
+			res.gsub!(')', '\\)')
+			res.gsub!('*', '\\*')
+			res.gsub!('\'', '\\\'')
+			res.gsub!('"', '\\"')
+			res.gsub!("\000", '\\\\')
+			return res
+		end
+
+		def in? path
+			to_s[0, path.size] == path
+		end
+
+		def get_data
+			@size = 0
+			@infostring = ''
+
+			@exists = File.exists?(@path)
+			if @exists
+				@writable = File.writable?(@path)
+				@symlink = File.symlink?(@path)
+				if @symlink
+					@readlink = File.readlink(@path)
+				end
+				if File.directory?(@path)
+					@type = :dir
+					begin
+						sz = Dir.entries(@path).size - 2
+						@size = sz
+					rescue
+						sz = "?"
+					end
+					@infostring << "#{sz}"
+				elsif File.socket?(@path)
+					@type = :socket
+				else
+					@type = :file
+					@size = File.size(@path)
+					if File.size?(@path)
+						@infostring << " #{File.size(@path).bytes 2}"
+					else
+						@infostring << ""
+					end
+				end
+				@rights = File.modestr(@path)
+				@executable = File.executable?(@path)
+				@mtime = File.mtime(@path)
+				@ctime = File.ctime(@path)
+
+			else
+				if File.symlink?(@path)
+					@readlink = File.readlink(@path)
+					@infostring = '->'
+					@symlink = true
+				else
+					@symlink = false
+				end
+				@executable = false
+				@writable = false
+				@type = :nonexistent
+				@rights = '----------'
+				@mtime = BAD_TIME
+				@ctime = BAD_TIME
+			end
+		end
+	end #}}}
+
+	PLACEHOLDER = Entry.new('/', 'placeholder')
+
+	def initialize(path, allow_delay=false)
 		@path = path
 		@pos = 0
-		@pointed_file = ''
-		@width = 0
+		@files = [PLACEHOLDER]
+		@file_size = 0
+		@pointed_file = nil
+		@width = 1000
+
 		refresh
 	end
 
-	attr_reader(:path, :files, :pos, :width, :infos)
+	def read_dir
+		@mtime = File.mtime(@path)
+		@files = Dir.new(@path).to_a
+		if OPTIONS['hidden']
+			@files -= ['.', '..', 'lost+found']
+		else
+			@files.reject!{|x| x[0] == ?. or x == 'lost+found'}
+		end
+		if @files.empty?
+			@files = ['.']
+		end
+
+		@files_raw = @files.map{|bn| File.join(@path, bn)}
+		@files.map!{|basename| Entry.new(@path, basename)}
+	end
+
+	attr_reader(:path, :files, :pos, :width, :files_raw, :file_size)
 
 	def pos=(x)
 		@pos = x
@@ -15,10 +350,16 @@ class Directory
 		resize
 	end
 
+	def restore()
+		for f in @files
+			f.marked = false
+		end
+	end
+
 	def pointed_file=(x)
-		if @files.include?(x)
+		if @files_raw.include?(x)
 			@pointed_file = x
-			@pos = @files.index(x)
+			@pos = @files_raw.index(x)
 		else
 			self.pos = 0
 		end
@@ -35,57 +376,90 @@ class Directory
 			@width = 0
 			@files[pos, lines-2].each_with_index do |fn, ix|
 				ix += pos
-#				log File.basename(fn) + @infos[ix]
-				sz = File.basename(fn).size + @infos[ix].size + 2
+				sz = fn.basename.size + fn.infostring.size + 2
 				@width = sz if @width < sz
 			end
 #			@width = @files[pos,lines-2].map{|x| File.basename(x).size}.max
 		end
 	end
 
-	def get_file_infos()
-		@infos = []
-		@files.each do |fn|
-			if File.directory?(fn)
-				begin
-					sz = Dir.entries(fn).size - 2
-				rescue
-					sz = "?"
-				end
-				@infos << "#{sz}"
-			else
-				if File.size?(fn)
-					@infos << " #{File.size(fn).bytes 2}"
-				else
-					@infos << ""
-				end
-			end
+	def get_file_info()
+		@file_size = 0
+		@files.each do |f|
+			f.refresh
+			@file_size += f.size if f.file?
 		end
 	end
 
-	def refresh()
-		glob = Dir.new(@path).to_a.sort!
-		if OPTIONS['hidden']
-		glob -= ['.', '..', 'lost+found']
-		else
-			glob.reject!{|x| x[0] == ?. or x == 'lost+found'}
+#	def refresh()
+#		@files = Dir.new(@path).to_a
+#		if OPTIONS['hidden']
+#			@files -= ['.', '..', 'lost+found']
+#		else
+#			@files.reject!{|x| x[0] == ?. or x == 'lost+found'}
+#		end
+#		if @files.empty?
+#			@files = ['.']
+#		end
+#		@files.map!{|basename| Entry.new(@path, basename)}
+#
+#		if @pos >= @files.size
+#			@pos = @files.size - 1
+#		elsif @files.include?(@pointed_file)
+#			@pos = @files.index(@pointed_file)
+#		end
+#	end
+	def refresh(info=false)
+		if File.mtime(@path) != @mtime
+			read_dir
 		end
-		if glob.empty?
-			glob = ['.']
+		if info
+			log("getting file info of #{@path}")
+			get_file_info 
 		end
-		glob.map!{|x| File.join(@path, x)}
-		dirs = glob.select{|x| File.directory?(x)}
-		@files = dirs + (glob - dirs)
+		sort
+	end
 
-		get_file_infos
-		resize
+	def schedule()
+		Fm.schedule(self)
+	end
+
+	def refresh!()
+		read_dir
+		get_file_info
+		sort
+	end
 
-		if @pos >= @files.size
-			@pos = @files.size - 1
-		elsif @files.include?(@pointed_file)
-			@pos = @files.index(@pointed_file)
+	def sort_sub(x, y)
+		case OPTIONS['sort']
+		when :name
+			x.basename <=> y.basename
+		when :size
+			x.size <=> y.size
+		when :ctime
+			x.ctime <=> y.ctime
+		when :mtime
+			x.mtime <=> y.mtime
+		else
+			x.basename <=> y.basename
 		end
 	end
+
+	def sort()
+		@files = @files.sort {|x,y|
+			if OPTIONS['dir_first']
+				if x.dir?
+					if y.dir? then sort_sub(x, y) else -1 end
+				else
+					if y.dir? then 1 else sort_sub(x, y) end
+				end
+			else
+				sort_sub(x, y)
+			end
+		}
+		@files.reverse! if OPTIONS['sort_reverse']
+		@files_raw = @files.map{|x| x.to_s}
+	end
 end
 
 
@@ -122,12 +496,27 @@ class File
 	end
 end
 
+class Dir
+	def self.number_of_files(*dirs)
+		n = 0
+		dirs.each do |entry|
+			if File.directory?(entry)
+				n += 1 + number_of_files(*(Dir.new(entry).to_a - ['.', '..']).map\
+												 {|x| File.join entry, x } )
+			else
+				n += 1
+			end
+		end
+		return n
+	end
+end
+
 class Numeric
 	def limit(max, min = 0)
 		self < min ? min : (self > max ? max : self)
 	end
 
-	def bytes n_round = 2
+	def bytes space = true, n_round = 2
 		n = 1024
 		a = %w(B K M G T Y)
 
@@ -146,7 +535,7 @@ class Numeric
 		int = flt.to_i
 		flt = int if int == flt
 
-		return flt.to_s + ' ' + a[i]
+		return flt.to_s + (space ? ' ' + a[i] : a[i])
 	end
 end
 
@@ -160,11 +549,18 @@ class String
 	def clear
 		replace String.new
 	end
+	if RUBY_VERSION < '1.9'
+		def ord
+			self[0]
+		end
+	end
+
 	def sh
-		res = dup
+		res = self.dup
 		res.gsub!('\\\\', "\000")
 		res.gsub!(' ', '\\ ')
 		res.gsub!('(', '\\(')
+		res.gsub!('&', '\\&')
 		res.gsub!(')', '\\)')
 		res.gsub!('*', '\\*')
 		res.gsub!('\'', '\\\'')
@@ -172,5 +568,6 @@ class String
 		res.gsub!("\000", '\\\\')
 		return res
 	end
+
 end
 
diff --git a/code/fm.rb b/code/fm.rb
index 307866f8..e5b66aad 100644
--- a/code/fm.rb
+++ b/code/fm.rb
@@ -1,22 +1,40 @@
-
 OPTIONS = {
 	'hidden' => false,
+	'sort' => :name,
+	'dir_first' => true,
+	'sort_reverse' => false,
+	'color' => true,
+	'filepreview' => true,
 }
 
+
 module Fm
-	def self.initialize
+	SCHEDULED = []
+	COLUMNS = 4
+	UPDATE_SIGNAL = 31
+	VI = "vi -c 'map h :quit<CR>' -c 'map q :quit<CR>'" <<
+		" -c 'map H :unmap h<CR>:unmap H<CR>' %s"
+
+	def self.initialize(pwd=nil)
+		@bars = []
+		@bars_thread = nil
+		
 		@buffer = ''
-		@pwd = ''
+#		@mutex = Mutex.new
+		@pwd = nil
+		@search_string = ''
 		@copy = []
 		@ignore_until = nil
 		@trash = File.expand_path('~/.trash')
-		pwd = Dir.getwd
+		pwd ||= Dir.getwd
 
+		# `' and `` are the original PWD unless overwritten by .fmrc
 		@memory = {
 			'`' => pwd,
 			'\'' => pwd
 		}
 
+		# Read the .fmrc
 		@fmrc = File.expand_path('~/.fmrc')
 		if (File.exists?(@fmrc))
 			loaded = Marshal.load(File.read(@fmrc))
@@ -25,18 +43,58 @@ module Fm
 			end
 		end
 
+		# `0 is always the original PWD
 		@memory['0'] = pwd
 
+		# Give me some way to redraw screen while waiting for
+		# input from Interface.geti
+		Signal.trap(UPDATE_SIGNAL) do
+			@pwd.refresh
+			draw
+		end
+
+
+		# This thread inspects directories
+		@scheduler_active = false
+		@scheduler = Thread.new do
+			while true
+				Thread.stop
+				if @scheduler_active and !SCHEDULED.empty?
+#					@mutex.synchronize {
+						while dir = SCHEDULED.shift
+							dir.refresh(true)
+							dir.resize
+							force_update
+						end
+#					}
+				end
+			end
+		end
+
+
 		@dirs = Hash.new() do |hash, key|
-			hash[key] = Directory.new(key)
+			hash[key] = newdir = Directory.new(key)
+			schedule newdir
+			newdir
 		end
 
 		@path = [@dirs['/']]
-		enter_dir(Dir.pwd)
+		enter_dir(pwd)
+
+		@scheduler_active = true
+		@scheduler.run
 	end
+
 	attr_reader(:dirs, :pwd)
 
-	VI = "vi -c 'map h :quit<CR>' -c 'map q :quit<CR>' -c 'map H :unmap h<CR>:unmap H<CR>' %s"
+	def self.force_update
+		# Send a signal to this process
+		Process.kill(UPDATE_SIGNAL, PID)
+	end
+
+	def self.lines
+		Interface::lines - @bars.size
+	end
 
 	def self.dump
 		remember_dir
@@ -46,29 +104,60 @@ module Fm
 		end
 	end
 
-	def self.rescue_me
+	def self.on_interrupt
 		@buffer = ''
 		sleep 0.2
 	end
 
 	def self.main_loop
 		while true
+			if @pwd.size == 0 or @pwd.pos < 0
+				@pwd.pos = 0
+			elsif @pwd.pos >= @pwd.size - 1
+				@pwd.pos = @pwd.size - 1
+			end
+
 			begin
-				draw()
+#				@mutex.synchronize {
+					draw()
+#				}
 			rescue Interrupt
-				rescue_me
+				on_interrupt
+			rescue Exception
+#				log($!)
+#				log(caller)
 			end
+
 			begin
-				press(geti)
+				key = geti
+#				@mutex.synchronize {
+					press(key)
+#				}
 			rescue Interrupt
-				rescue_me
+				on_interrupt
 			end
 		end
 	end
 
 	def self.current_path() @pwd.path end
 
+	def self.enter_dir_safely(dir)
+		dir = File.expand_path(dir)
+		if File.exists?(dir) and File.directory?(dir)
+			olddir = @pwd.path
+			begin
+				enter_dir(dir)
+				return true
+			rescue
+				enter_dir(olddir)
+				return false
+			end
+		end
+	end
+
 	def self.enter_dir(dir)
+		@pwd.restore if @pwd
+		@marks = 0
 		dir = File.expand_path(dir)
 
 		oldpath = @path.dup
@@ -85,17 +174,23 @@ module Fm
 				end
 			end
 		end
+
 		@pwd = @path.last
+
+		@pwd.files_raw.dup.each do |x|
+			@dirs[x] if File.directory?(x)
+		end
+
 		set_title "fm: #{@pwd.path}"
 
 		if @path.size < oldpath.size
-			@pwd.pos = @pwd.files.index(oldpath.last.path) || 0
+			@pwd.pos = @pwd.files_raw.index(oldpath.last.path) || 0
 		end
 
 		i = 0
 
 		@path.each_with_index do |p, i|
-			p.refresh
+			schedule(p)
 			unless i == @path.size - 1
 				p.pointed_file = @path[i+1].path
 			end
@@ -104,254 +199,72 @@ module Fm
 		Dir.chdir(@pwd.path)
 	end
 
-	def self.currentfile
-		@dirs[@currentdir][1][@dirs[@currentdir][0] || 0]
-	end
-	def self.currentfile() @pwd.files.at(@pwd.pos) end
-
-	def self.get_offset(dir, max)
-		pos = dir.pos
-		len = dir.files.size
-		max -= 2
-		if len <= max or pos < max/2
-			return 0
-		elsif pos >= (len - max/2)
-			return len - max
-		else
-			return pos - max/2
-		end
-	end
-
-	COLUMNS = 4
+	def self.currentfile() @pwd.files[@pwd.pos] end
 
-	def self.get_boundaries(column)
-		cols = Interface.cols # to cache
-		case column
-		when 0
-			return 0, cols / 8 - 1
-			
-		when 1
-			q = cols / 8
-			return q, q
-
-		when 2
-			q = cols / 4
-			w = @path.last.width.limit(cols/2, cols/8) + 1
-			return q, w
-			
-		when 3
-			l = cols / 4 + 1
-			l += @path.last.width.limit(cols/2, cols/8)
-
-			return l, cols - l
-			
-		end
+	def self.schedule(dir)
+		SCHEDULED << dir
+		@scheduler.run
 	end
 
-	def self.put_directory(c, d)
-		l = 0
-		if d
-			infos = (c == COLUMNS - 2)
-			left, wid = get_boundaries(c)
-
-			offset = get_offset(d, lines)
-			(lines - 1).times do |l|
-				lpo = l + offset
-				bg = -1
-				break if (f = d.files[lpo]) == nil
-
-				dir = false
-				if File.symlink?(f)
-					bld = true
-					if File.exists?(f)
-						clr = [6, bg]
-					else
-						clr = [1, bg]
-					end
-					dir = File.directory?(f)
-				elsif File.directory?(f)
-					bld = true
-					dir = true
-					clr = [4, bg]
-				elsif File.executable?(f)
-					bld = true
-					clr = [2, bg]
-				else
-					bld = false
-					clr = [7, bg]
-				end
-
-				fn = File.basename(f)
-				if infos
-					myinfo = " #{d.infos[lpo]}  "
-					str = fn[0, wid-1].ljust(wid)
-					if str.size > myinfo.size
-						str[-myinfo.size..-1] = myinfo
-						yes = true
-					else
-						yes = false
-					end
-					puti l+1, left, str
-					if dir and yes
-						args = l+1, left+wid-myinfo.size, myinfo.size, *clr
-						color_bold_at(*args)
-					end
-				else
-					puti l+1, left, fn[0, wid-1].ljust(wid+1)
-				end
-
-				args = l+1, left, fn.size.limit(wid-1), *clr
-
-				if d.pos == lpo
-					color_reverse_at(*args)
-				else
-					if bld then color_bold_at(*args) else color_at(*args) end
-				end
-			end
+	def self.move_to_trash!(fn)
+		unless File.exists?(@trash)
+			Dir.mkdir(@trash)
 		end
+		new_path = File.join(@trash, fn.basename)
 
-		column_clear(c, l)
-	end
+		Action.move(fn, new_path)
 
-	def self.column_clear(n, from=0)
-		color(-1,-1)
-		left, wid = get_boundaries(n)
-		(from -1).upto(lines) do |l|
-			puti l+2, left, ' ' * (wid)
-		end
+		return new_path
 	end
 
-	def self.column_put_file(n, file)
-		m = lines - 2
-		i = 0
-		color 7
-		bold false
-		File.open(file, 'r') do |f|
-			check = true
-			left, wid = get_boundaries(n)
-			f.lines.each do |l|
-				if check
-					check = false
-					break unless l.each_char.all? {|x| x[0] > 0 and x[0] < 128}
-				end
-				puti i+1, left, l.gsub("\t","   ")[0, wid-1].ljust(wid)
-				i += 1
-				break if i == m
-			end
+	def self.move_to_trash(file)
+		unless file
+			return
 		end
-		column_clear(n, i)
-	end
-
-	def self.draw
-		bold false
-		@cur_y = get_boundaries(COLUMNS-2)[0]
-		@pwd.get_file_infos
-
-		s1 = "  "
-		s2 = "#{@path.last.path}#{"/" unless @path.size == 1}"
-		f = currentfile
-		s3 = "#{f ? File.basename(f) : ''}"
-		
-		puti 0, (s1 + s2 + s3).ljust(cols)
 
-		bg = -1
-		color_at 0, 0, -1, 7, bg
-		color_at 0, 0, s1.size, 7, bg
-		color_at 0, s1.size, s2.size, 6, bg
-		color_at 0, s1.size + s2.size, s3.size, 5, bg
-
-		bold false
-
-		f = currentfile
-		begin
-			if File.directory?(f)
-				put_directory(3, @dirs[currentfile])
-			else
-				column_put_file(3, currentfile)
-			end
-		rescue
-			column_clear(3)
+		if String === file
+			file = Directory::Entry.new(file)
 		end
 
-		pos_constant = @path.size - COLUMNS + 1
-
-		(COLUMNS - 1).times do |c|
-			pos = pos_constant + c
-
-			if pos >= 0
-				put_directory(c, @path[pos])
+		if file.exists?
+			if file.dir?
+				if !file.in?(@trash) and file.size > 0
+					return move_to_trash!(file)
+				else
+					Dir.rmdir(file.path) rescue nil
+				end
+			elsif file.symlink?
+				file.delete!
 			else
-				column_clear(c)
+				if !file.in?(@trash) and file.size > 0
+					return move_to_trash!(file)
+				else
+					file.delete!
+				end
 			end
 		end
-
-		bold false
-		color -1, -1
-		puti -1, "#@buffer    #{@pwd.pos+1},#{@pwd.size},#{@path.size}    ".rjust(cols)
-		more = ''
-		if File.symlink?(currentfile)
-			more = "#{File.readlink(currentfile)}"
-		end
-		puti -1, "  #{Time.now.strftime("%H:%M:%S %a %b %d")}  #{File.modestr(currentfile)} #{more}"
-
-		color_at -1, 23, 10, (File.writable?(currentfile) ? 6 : 5), -1
-		if more
-			color_at -1, 34, more.size, (File.exists?(currentfile) ? 6 : 1), -1
-		end
-
-		movi(@pwd.pos + 1 - get_offset(@pwd, lines), @cur_y)
+		return nil
 	end
 
-	def self.enter_dir_safely(dir)
-		dir = File.expand_path(dir)
-		if File.exists?(dir) and File.directory?(dir)
-			olddir = @pwd.path
-			begin
-				enter_dir(dir)
-				return true
-			rescue
-				enter_dir(olddir)
-				return false
+	def self.bar_add(bar)
+		if @bars.empty?
+			# This thread updates the statusbars
+			@bars_thread = Thread.new do
+				while true
+					draw_bars
+					sleep 0.5
+				end
 			end
 		end
+		@bars << bar
 	end
 
-	def self.move_to_trash!(fn)
-		unless File.exists?(@trash)
-			Dir.mkdir(@trash)
-		end
-		new_path = File.join(@trash, File.basename(fn))
-
-		closei
-		system('mv','-v', fn, new_path)
-		starti
-
-		return new_path
-	end
-
-	def self.in_trash?(fn)
-		fn[0,@trash.size] == @trash
-	end
-
-	def self.move_to_trash(fn)
-		if fn and File.exists?(fn)
-			if File.directory?(fn)
-				if !in_trash?(fn) and Dir.entries(fn).size > 2
-					return move_to_trash!(fn)
-				else
-					Dir.rmdir(fn) rescue nil
-				end
-			elsif File.symlink?(fn)
-				File.delete(fn)
-			else
-				if !in_trash?(fn) and File.size?(fn)
-					return move_to_trash!(fn)
-				else
-					File.delete(fn)
-				end
-			end
+	def self.bar_del(bar)
+		@bars.delete(bar)
+		if @bars.empty?
+			@bars_thread.kill
+			@bars_thread = nil
 		end
-		return nil
 	end
 end
 
diff --git a/code/keys.rb b/code/keys.rb
index 0ce660a9..d6ae01ea 100644
--- a/code/keys.rb
+++ b/code/keys.rb
@@ -1,9 +1,11 @@
 module Fm
-	# ALL combinations of multiple keys (without the last letter)
+	# ALL combinations of multiple keys (but without the last letter)
 	# or regexps which match combinations need to be in here!
 	COMBS = %w(
-		g d y c Z rmdi t
-		/[m`']/ /[f/!].*/
+		g d y c Z delet cu
+		t S ? ?g ?f
+
+		/[m`']/ /[fF/!].*/
 		/(cw|cd|mv).*/
 		/m(k(d(i(r(.*)?)?)?)?)?/
 		/r(e(n(a(m(e(.*)?)?)?)?)?)?/
@@ -15,7 +17,13 @@ module Fm
 		if token =~ /^\/(.*)\/$/
 			ary << $1
 		elsif token.size > 0
-			ary << token.each_char.map {|t| "(?:#{t}" }.join +
+			ary << token.each_char.map {|t|
+				if t == '?'
+					t = '\?'
+				end
+
+				"(?:#{t}"
+			}.join +
 				(')?' * (token.size - 1)) + ')'
 		end
 	end
@@ -26,9 +34,13 @@ module Fm
 	end
 
 	def self.search(str, offset=0, backwards=false)
-		rx = Regexp.new(str, Regexp::IGNORECASE)
+		begin
+			rx = Regexp.new(str, Regexp::IGNORECASE)
+		rescue
+			return false
+		end
 
-		ary = @pwd.files.dup
+		ary = @pwd.files_raw.dup
 		ary.wrap(@pwd.pos + offset)
 
 		ary.reverse! if backwards
@@ -43,9 +55,13 @@ module Fm
 	end
 
 	def self.hints(str)
-		rx = Regexp.new(str, Regexp::IGNORECASE)
+		begin
+			rx = Regexp.new(str, Regexp::IGNORECASE)
+		rescue
+			return false
+		end
 
-		ary = @pwd.files.dup
+		ary = @pwd.files_raw.dup
 		ary.wrap(@pwd.pos)
 
 		n = 0
@@ -73,59 +89,126 @@ module Fm
 
 		@ignore_until = nil
 
-		case @buffer << key
+		if key == '<bs>'
+			if @buffer.empty?
+				@buffer = key
+			elsif @buffer == 'F'
+				descend
+			elsif @buffer[-1] == ?>
+				@buffer.slice! /(<.*)?>$/
+			else
+				@buffer.slice! -1
+			end
+		else
+			@buffer << key
+		end
 
+		case @buffer
 		when '<redraw>'
 			closei
 			starti
 
-		when 'j'
-			if @pwd.size == 0
-				@pwd.pos = 0
-			elsif @pwd.pos >= @pwd.size - 1
-				@pwd.pos = @pwd.size - 1
-			else
-				@pwd.pos += 1
-			end
+		when 'j', '<down>'
+			@pwd.pos += 1
 
 		when 's'
 			closei
 			system('clear')
-			system('ls', '--color=auto', '--group-directories-first')
+			ls = ['ls']
+			ls << '--color=auto' if OPTIONS['color']
+			ls << '--group-directories-first' if OPTIONS['color']
+			system(*ls)
 			system('bash')
-			@pwd.refresh
+			@pwd.schedule
 			starti
 
+
+		when /S(.)/
+			OPTIONS['sort_reverse'] = $1.ord.between?(65, 90)
+
+			case $1
+			when 'n'
+				OPTIONS['sort'] = :name
+			when 's'
+				OPTIONS['sort'] = :size
+			when 'e'
+				OPTIONS['sort'] = :extension
+			when 'm'
+				OPTIONS['sort'] = :mtime
+			when 'c', 't'
+				OPTIONS['sort'] = :ctime
+			end
+			@pwd.schedule
+
+		when 'r', 'R'
+			@pwd.refresh!
+
+		when 'x'
+			@bars.first.kill unless @bars.empty?
+
+		when 'X'
+			@bars.last.kill unless @bars.empty?
+
 		when 'J'
-			(lines/2).times { press 'j' }
+			@pwd.pos += lines/2
 
 		when 'K'
-			(lines/2).times { press 'k' }
+			@pwd.pos -= lines/2
 
 		when 'cp', 'yy'
-			@copy = [currentfile]
+			if @marked.empty?
+				@copy = [currentfile]
+			else
+				@copy = @marked.dup
+			end
+			@cut = false
+
+		when 'cut'
+			if @marked.empty?
+				@copy = [currentfile]
+			else
+				@copy = @marked.dup
+			end
+			@cut = true
 
 		when 'n'
 			search(@search_string, 1)
 
-		when 'x'
-			fork {
-				sleep 1
-				Ncurses.ungetch(104)
-			}
-
 		when 'N'
 			search(@search_string, 0, true)
 
-		when 'fh'
-			@buffer.clear
-			press('h')
+#		when 'fh'
+#			@buffer.clear
+#			press('h')
+
+		when /^F(.+)$/
+			str = $1
+			if str =~ /^\s?(.*)(<cr>|<esc>)$/
+				if $2 == '<cr>'
+					ascend
+					@buffer = 'F'
+				else
+					@buffer.clear
+					@search_string = $1
+				end
+			else
+				test = hints(str)
+				if test == 1
+					if ascend
+						@buffer.clear
+					else
+						@buffer = 'F'
+					end
+					ignore_keys_for 0.5
+				elsif test == 0
+					@buffer = 'F'
+					ignore_keys_for 1
+				end
+			end
 
 		when /^f(.+)$/
 			str = $1
-			if @buffer =~ /^(.*).<bs>$/
-				@buffer = $1
-			elsif str =~ /^\s?(.*)(L|;|<cr>|<esc>)$/
+			if str =~ /^\s?(.*)(L|;|<cr>|<esc>)$/
 				@buffer = ''
 				@search_string = $1
 				press('l') if $2 == ';' or $2 == 'L'
@@ -143,9 +226,7 @@ module Fm
 
 		when /^\/(.+)$/
 			str = $1
-			if @buffer =~ /^(.*).<bs>$/
-				@buffer = $1
-			elsif str =~ /^\s?(.*)(L|;|<cr>|<esc>)$/
+			if str =~ /^\s?(.*)(L|;|<cr>|<esc>)$/
 				@buffer = ''
 				@search_string = $1
 				press('l') if $2 == ';' or $2 == 'L'
@@ -155,38 +236,32 @@ module Fm
 
 		when /^mkdir(.*)$/
 			str = $1
-			if @buffer =~ /^(.*).<bs>$/
-				@buffer = $1
-			elsif str =~ /^\s?(.*)(<cr>|<esc>)$/
+			if str =~ /^\s?(.*)(<cr>|<esc>)$/
 				@buffer = ''
 				if $2 == '<cr>'
 					closei
 					system('mkdir', $1)
 					starti
-					@pwd.refresh
+					@pwd.schedule
 				end
 			end
 			
 		when /^!(.+)$/
 			str = $1
-			if @buffer =~ /^(.*).<bs>$/
-				@buffer = $1
-			elsif str =~ /^(\!?)(.*)(<cr>|<esc>)$/
+			if str =~ /^(\!?)(.*)(<cr>|<esc>)$/
 				@buffer = ''
 				if $3 == '<cr>'
 					closei
 					system("bash", "-c", $2)
 					gets unless $1.empty?
 					starti
-					@pwd.refresh
+					@pwd.schedule
 				end
 			end
 
 		when /^cd(.+)$/
 			str = $1
-			if @buffer =~ /^(.*).<bs>$/
-				@buffer = $1
-			elsif str =~ /^\s?(.*)(<cr>|<esc>)$/
+			if str =~ /^\s?(.*)(<cr>|<esc>)$/
 				@buffer = ''
 				if $2 == '<cr>'
 					remember_dir
@@ -196,37 +271,44 @@ module Fm
 
 		when /^(?:mv|cw|rename)(.+)$/
 			str = $1
-			if @buffer =~ /^(.*).<bs>$/
-				@buffer = $1
-			elsif str =~ /^\s?(.*)(<cr>|<esc>)$/
+			if str =~ /^\s?(.*)(<cr>|<esc>)$/
 				@buffer = ''
 				if $2 == '<cr>'
-					closei
-					system('mv', '-v', currentfile, $1)
-					starti
-					@pwd.refresh
+					Action.move(currentfile, $1)
 				end
+				@pwd.schedule
 			end
 
+		when 'tc'
+			OPTIONS['color'] ^= true
+
+		when 'tf'
+			OPTIONS['filepreview'] ^= true
+
 		when 'th'
 			OPTIONS['hidden'] ^= true
-			@pwd.refresh
-
-		when 'rmdir'
-			cf = currentfile
-			if cf and File.exists?(cf)
-				if File.directory?(cf)
-					system('rm', '-r', cf)
-					@pwd.refresh
+			@pwd.refresh!
+
+		when 'td'
+			OPTIONS['dir_first'] ^= true
+			@pwd.schedule
+
+		when 'delete'
+			files = @marked.empty? ? [currentfile] : @marked
+			@marked = []
+			for f in files
+				if f and f.exists? and f.dir?
+					system('rm', '-r', f.to_s)
+					@pwd.schedule
 				end
 			end
 
 		when 'p'
-			unless @copy.empty?
-				closei
-				system('cp','-v',*(@copy+[@pwd.path]))
-				starti
-				@pwd.refresh
+			if @cut
+				Action.move(@copy, @pwd.path)
+				@cut = false
+			else
+				Action.copy(@copy, @pwd.path)
 			end
 
 		when /^[`'](.)$/
@@ -235,26 +317,63 @@ module Fm
 				enter_dir_safely(dir)
 			end
 
+		when '<tab>'
+			if dir = @memory['`'] and not @pwd.path == dir
+				remember_dir
+				enter_dir_safely(dir)
+			end
+			
+
 		when /^m(.)$/
 			@memory[$1] = @pwd.path
 
+		when ' '
+			if currentfile.marked
+				@marked.delete(currentfile)
+				currentfile.marked = false
+			else
+				@marked << currentfile
+				currentfile.marked = true
+			end
+
+			@pwd.pos += 1
+
+		when 'v'
+			@marked = []
+			for file in @pwd.files
+				if file.marked
+					file.marked = false
+				else
+					file.marked = true
+					@marked << file
+				end
+			end
+
+		when 'V'
+			for file in @marked
+				file.marked = false
+			end
+			@marked = []
+
+
 		when 'gg'
 			@pwd.pos = 0
 
 		when 'dd'
 			new_path = move_to_trash(currentfile)
-			@copy = [new_path] if new_path
-			@pwd.refresh
+			if new_path
+				new_path = Directory::Entry.new(new_path)
+				new_path.get_data
+				@copy = [new_path]
+				@cut = false
+			end
+			@pwd.schedule
 
 		when 'dD'
 			cf = currentfile
-			if cf and File.exists?(cf)
-				if File.directory?(cf)
-					Dir.delete(cf) rescue nil
-				else
-					File.delete(cf) rescue nil
-				end
-				@pwd.refresh
+			if cf and cf.exists?
+				cf.delete!
+				@pwd.schedule
 			end
 
 		when 'g0'
@@ -284,34 +403,22 @@ module Fm
 		when 'G'
 			@pwd.pos = @pwd.size - 1
 
-		when 'k'
+		when 'k', '<up>'
 			@pwd.pos -= 1
-			@pwd.pos = 0 if @pwd.pos < 0
 
-		when '<bs>', 'h', 'H'
-			enter_dir(@buffer=='H' ? '..' : @path[-2].path) unless @path.size == 1
+		when '<bs>', 'h', 'H', '<left>'
+			descend
 
 		when 'E'
-			cf = currentfile
+			cf = currentfile.path
 			unless cf.nil? or enter_dir_safely(cf)
 				closei
 				system VI % cf
 				starti
 			end
 
-		when '<cr>', 'l', ';', 'L'
-			cf = currentfile
-			unless cf.nil? or enter_dir_safely(cf)
-				handler, wait = getfilehandler(currentfile)
-				if handler
-					closei
-					system(handler)
-					if @buffer == 'L'
-						gets
-					end
-					starti
-				end
-			end
+		when '<cr>', 'l', ';', 'L', '<right>'
+			ascend(@buffer=='L')
 
 		when 'q', 'ZZ', "\004"
 			exit
@@ -320,4 +427,28 @@ module Fm
 
 		@buffer = '' unless @buffer == '' or @buffer =~ REGX
 	end
+	
+	def self.ascend(wait = false)
+		cf = currentfile
+		enter = enter_dir_safely(cf.path)
+		unless cf.nil? or enter
+			handler, wait = getfilehandler(currentfile)
+			if handler
+				closei
+				log handler
+				system(handler)
+				gets if wait
+				starti
+				return true
+			end
+		end
+		return false
+	end
+
+	def self.descend
+		unless @path.size == 1
+			enter_dir(@buffer=='H' ? '..' : @path[-2].path)
+		end
+	end
 end
+
diff --git a/code/old_fm.rb b/code/old_fm.rb
deleted file mode 100644
index 6d868ebb..00000000
--- a/code/old_fm.rb
+++ /dev/null
@@ -1,233 +0,0 @@
-class Directory
-	def initialize(path)
-		@path = path
-		@pos = 0
-		refresh
-	end
-
-	attr_reader(:path, :files)
-	attr_accessor(:pos)
-
-	def refresh()
-		@files = Dir::glob(File::join(path, '*')).sort!
-	end
-	def self.current()
-		Fm.current_dir()
-	end
-end
-
-module Fm
-	def self.initialize
-		@key = ''
-		@dirs = {}
-		@current_dir = ''
-		enter_dir(Dir.getwd)
-	end
-
-	def self.current_path() self.current_dir.path end
-
-	attr_reader(:dirs, :current_dir)
-
-#	{
-#		"/" => [ 2,
-#					 ["usr", "home", "root", "etc"] ],
-#		 ...
-#	}
-	
-
-	def self.getfilehandler(file)
-		bn = File.basename(file)
-		case bn
-		when /\.(avi|mpg|flv)$/
-			"mplayer #{file} >> /dev/null"
-		when /\.(jpe?g|png)$/
-			"feh '#{file}'"
-		when /\.m3u$/
-			"cmus-remote -c && cmus-remote -P #{file} && cmus-remote -C 'set play_library=false' && sleep 0.3 && cmus-remote -n"
-		end
-	end
-
-	def self.enter_dir(dir)
-		dir = File.expand_path(dir)
-		olddirs = @dirs.dup
-		@dirs = {}
-
-		cur = 0
-		got = ""
-		ary = dir.split('/')
-		if ary == []; ary = [''] end
-		["", *ary].each do |folder|
-			got = File.join(got, folder)
-			cur = olddirs.has_key?(got) ? olddirs[got][0] : 0
-			@dirs[got] = [cur, Dir.glob(File.join(got, '*')).sort]
-		end
-
-		# quick fix, sets the cursor correctly when entering ".."
-		if @dirs.size < olddirs.size
-			@dirs[@currentdir] = olddirs[@currentdir] 
-			@dirs[got][0] = @dirs[got][1].index(@currentdir) || 0
-		end
-
-#		log @dirs
-
-		@currentdir = got
-#		@dirs[dir] = Dir[File.join(dir, '*')]
-		Dir.chdir(got)
-		
-#		log(@dirs)
-	end
-
-	def self.cursor() @dirs[@currentdir][0] end
-	def self.cursor=(x) @dirs[@currentdir][0] = x end
-
-	def self.currentdir() @dirs[@currentdir][1] end
-
-	def self.currentfile
-		@dirs[@currentdir][1][@dirs[@currentdir][0] || 0]
-	end
-
-	def self.get_offset(dir, max)
-		pos = dir[0]
-		len = dir[1].size
-		max -= 2
-		if len <= max or pos < max/2
-			return 0
-		elsif pos > (len - max/2)
-			return len - max
-		else
-			return pos - max/2
-		end
-	end
-
-	def self.put_directory(c, d)
-		l = 0
-		unless d == nil
-			offset = get_offset(d, lines)
-			(lines - 1).times do |l|
-				lpo = l + offset
-				break if (f = d[1][lpo]) == nil
-
-				if File.symlink?(f)
-					color(3)
-				elsif File.directory?(f)
-					color(4)
-				elsif File.executable?(f)
-					color(2)
-				else
-					color(7)
-				end
-				puti l+1, c*@wid, File.basename(f).ljust(@wid-1)[0, @wid]
-			end
-		end
-
-		column_clear(c, l)
-	end
-
-	def self.column_clear(n, from=0)
-		(from -1).upto(lines) do |l|
-			puti l+2, (n * @wid), ' ' * @wid
-		end
-	end
-
-	def self.column_put_file(n, file)
-		m = lines
-		i = 0
-		File.open(file, 'r') do |f|
-			f.lines.each do |l|
-				puti i+1, n * @wid, l.gsub("\t","   ")[0, @wid-1].ljust(@wid-1)
-				i += 1
-				break if i == m
-			end
-		end
-		column_clear(n, i)
-	end
-
-	def self.draw
-		color 7
-		puti 0, 3, "pwd: #{@current_path}".ljust(cols)
-
-		if @dirs.size == 1
-			@temp = [nil, @dirs["/"]]
-		else
-			left = @dirs[@currentdir[0, @currentdir.rindex('/')]]
-			left ||= @dirs['/']
-			@temp = [left, @dirs[@currentdir]]
-		end
-
-		@wid = cols / 3
-		f = currentfile
-		begin
-			if File.directory?(f)
-				put_directory(2, [0, Dir.glob(File.join(f, '*')).sort])
-			else
-				column_put_file(2, currentfile)
-			end
-		rescue
-			column_clear(2)
-		end
-
-		2.times do |c|
-			put_directory(c, @temp[c])
-		end
-
-	
-		movi(self.cursor + 1 - get_offset(@dirs[@currentdir], lines), @wid)
-	end
-
-	# ALL combinations of multiple keys have to be in the COMBS array.
-	COMBS = %w(
-		gg
-	)
-	def self.main_loop
-		while true
-			draw
-
-			case @key << geti
-			when 'j'
-				self.cursor += 1
-				self.cursor = currentdir.size - 1 if self.cursor >= currentdir.size
-
-			when 'gg'
-				self.cursor = 0
-
-			when 'gh'
-				enter_dir('~')
-
-			when 'G'
-				self.cursor = currentdir.size - 1
-
-			when 'k'
-				self.cursor -= 1
-				self.cursor = 0 if self.cursor < 0
-
-			when '<bs>', 'h'
-				enter_dir('..') unless @dirs.size == 1
-
-			when '<cr>', 'l'
-				if File.directory?(currentfile || '')
-					begin
-						olddir = @currentdir
-						enter_dir(currentfile)
-					rescue Exception
-						enter_dir olddir
-					end
-				else
-					h = getfilehandler(currentfile)
-					h and system(h)
-				end
-
-			when 'q'
-				break
-			end
-
-			unless @key == '' or COMBS.select{ |x|
-				x.size != @key.size and x.size > @key.size
-			}.map{ |x|
-				x[0, @key.size]
-			}.include? @key
-				@key = ''
-			end
-		end
-	end
-end
-
diff --git a/code/types.rb b/code/types.rb
index 7fc614e9..0fc0bf7c 100644
--- a/code/types.rb
+++ b/code/types.rb
@@ -1,22 +1,56 @@
 module Fm
-	def self.getfilehandler(file)
-		bn = File.basename(file)
-		case bn
-		when /\.(avi|mpe?g|flv|mkv|ogm|mov|mp4|wmv|vob|php|divx?|mp3|ogg)$/i
-			return "mplayer -fs #{file.sh}", false
+	def self.getfilehandler_frompath(file)
+		case file
+		when /\.(part|avi|mpe?[g\d]|flv|fid|mkv|mov|wm[av]|vob|php|divx?|og[gmv])$/i
+			if file =~ /720p/
+				return "mplayer -vm sdl #{file.sh}", false
+			else
+				return "mplayer -fs #{file.sh}", false
+			end
+
+		when /\.part$/
+			test = getfilehandler_frompath($`)
+			if test
+				return test
+			end
+
+		when /\.(swc|smc)$/i
+			return "zsnes -ad sdl -u -o #{file.sh}"
+
+		when /\.(zip|rar|tar|gz|7z|jar|bz2)$/i
+			return "aunpack #{file.sh}", false
+
+		when "Makefile"
+			return "make"
+
 		when /\.(jpe?g|png)$/i
 			return "feh #{file.sh}", false
-		when /\.(pdf)$/i
+
+		when /\.html?$/i
+			return "firefox #{file.sh}"
+
+		when /\.pdf$/i
 			return "evince #{file.sh}"
-		when /\.(txt)$/i
+
+		when /\.txt$/i
 			return VI % file.sh
+
 		when /\.wav$/i
 			return "aplay -q #{file.sh}"
+
 		when /\.m3u$/i
 			return "cmus-remote -c && cmus-remote -P #{file} && cmus-remote -C 'set play_library=false' && sleep 0.3 && cmus-remote -n", false
+
+		end
+
+	end
+	def self.getfilehandler(file)
+		test = getfilehandler_frompath(file.basename)
+		if test
+			return test
 		end
 
-		if File.executable?(file)
+		if file.executable?
 			return "#{file.sh}", true
 		end
 
diff --git a/fm b/fm
index 7133192f..1129a9d4 100755
--- a/fm
+++ b/fm
@@ -1,4 +1,12 @@
-#!/usr/bin/ruby
+#!/usr/bin/ruby -Ku
+
+# Log details
+# 0 = do not log
+# 1 = log fatalities
+# 2 = log errors
+# 3 = log everything
+LOG_LEVEL = 3
+#LOG_LEVEL = 0
 
 def File::resolve_symlink( path = __FILE__ )
    path = readlink(path) while symlink?(path)
@@ -11,31 +19,54 @@ end
 
 $: << FM_DIR = File::dirname(File::resolve_symlink)
 
-require 'ftools'
+PID = Process.pid
+
+if ARGV.size > 0
+	pwd = ARGV.first
+	if pwd =~ /^file:\/\//
+		pwd = $'
+	end
+else
+	pwd = nil
+end
+
+#require 'ftools'
 require 'pp'
+require 'thread'
 
 require_from_here 'interface/ncurses.rb'
 require_from_here 'code/fm.rb'
 require_from_here 'code/keys.rb'
 require_from_here 'code/types.rb'
+require_from_here 'code/draw.rb'
 require_from_here 'code/extensions.rb'
+require_from_here 'code/debug.rb'
 include Interface
+include Debug
 
 ERROR_STREAM = File.open('/tmp/errorlog', 'a')
-def log(obj)
-	$stdout = ERROR_STREAM
-	pp obj
-	$stdout.flush
-	$stdout = STDOUT
-	obj
-end
 
-END {
+#def log(obj)
+#	$stdout = ERROR_STREAM
+#	pp caller
+#	pp obj
+#	$stdout.flush
+#	$stdout = STDOUT
+#	obj
+#end
+
+begin
+	Fm.initialize( pwd )
+	Fm.main_loop
+ensure
 	closei
 	Fm.dump
 	ERROR_STREAM.close
-}
 
-Fm.initialize
-Fm.main_loop
+	for thr in Thread.list
+		unless thr == Thread.current
+			thr.kill
+		end
+	end
+end
 
diff --git a/interface/ncurses.rb b/interface/ncurses.rb
index 0d2659e4..a383a570 100644
--- a/interface/ncurses.rb
+++ b/interface/ncurses.rb
@@ -9,8 +9,18 @@ module Interface
 			'<cr>'
 		when ?\b, Ncurses::KEY_BACKSPACE
 			'<bs>'
+		when Ncurses::KEY_RIGHT
+			'<right>'
+		when Ncurses::KEY_LEFT
+			'<left>'
+		when Ncurses::KEY_UP
+			'<up>'
+		when Ncurses::KEY_DOWN
+			'<down>'
 		when ?\e
 			'<esc>'
+#		when ?\s
+#			'<space>'
 		when ?\t
 			'<tab>'
 		when 32
@@ -18,6 +28,7 @@ module Interface
 		when 0..127
 			key.chr
 		else
+			log(key)
 			''
 		end
 	end
@@ -83,10 +94,9 @@ module Interface
 		Ncurses.start_color
 		Ncurses.use_default_colors
 
-#		Ncurses.cbreak
 		Ncurses.noecho
 		Ncurses.curs_set 0
-		Ncurses.halfdelay(1000)
+		Ncurses.halfdelay(10000)
 		@@colortable = []
 	end
 
@@ -97,15 +107,16 @@ module Interface
 		Ncurses.endwin
 	end
 
+	def cleari
+		Ncurses.mvaddstr(0, 0, ' ' * (lines * cols))
+	end
+
 	def geti
 		Interface::keytable(Ncurses.getch)
 	end
 
 	def set_title(x)
-#		closei
-		print "\e]2;#{x}\007"
-#		system('echo', '-n', '-e', '"\e2;' + x + '\007"')
-#		starti
+		print "\e]2;#{x}\b"
 	end
 
 	def lines
@@ -135,22 +146,53 @@ module Interface
 	end
 
 	def color(fg = -1, bg = -1)
-		Ncurses.color_set(get_color(fg,bg), nil)
+		if OPTIONS['color']
+			Ncurses.color_set(get_color(fg,bg), nil)
+		end
 	end
 
 	def color_at y, x=0, len=-1, fg=-1, bg=-1
-		if y < 0 then y += Ncurses.LINES end
-		Ncurses.mvchgat(y, x, len, 0, get_color(fg, bg), nil)
+		if OPTIONS['color']
+			if y < 0 then y += Ncurses.LINES end
+			Ncurses.mvchgat(y, x, len, 0, get_color(fg, bg), nil)
+		end
 	end
 
 	def color_bold_at y, x=0, len=-1, fg=-1, bg=-1
-		if y < 0 then y += Ncurses.LINES end
-		Ncurses.mvchgat(y, x, len, Ncurses::A_BOLD, get_color(fg, bg), nil)
+		if OPTIONS['color']
+			if y < 0 then y += Ncurses.LINES end
+			Ncurses.mvchgat(y, x, len, Ncurses::A_BOLD, get_color(fg, bg), nil)
+		end
 	end
 
 	def color_reverse_at y, x=0, len=-1, fg=-1, bg=-1
-		if y < 0 then y += Ncurses.LINES end
-		Ncurses.mvchgat(y, x, len, Ncurses::A_REVERSE, get_color(fg, bg), nil)
+		if OPTIONS['color']
+			if y < 0 then y += Ncurses.LINES end
+			Ncurses.mvchgat(y, x, len, Ncurses::A_REVERSE, get_color(fg, bg), nil)
+		else
+			Ncurses.mvchgat(y, x, len, Ncurses::A_REVERSE, 0, nil)
+		end
+	end
+
+#	runi(:command => String/Array, :wait=>false)
+#	runi('ä́', 'b', 'c')
+	def runi(hash, *args)
+		wait = false
+		if Array === hash
+			command = hash
+		elsif String === hash
+			command = [hash, *args]
+		else
+			command = hash[:command] or return false
+			wait = hash[:wait] if hash.has_key? :wait
+		end
+
+		closei
+
+		system(*command)
+		gets if wait
+
+		starti
 	end
 
 	def get_color(fg, bg)