diff options
Diffstat (limited to 'code')
-rw-r--r-- | code/debug.rb | 67 | ||||
-rw-r--r-- | code/draw.rb | 265 | ||||
-rw-r--r-- | code/extensions.rb | 487 | ||||
-rw-r--r-- | code/fm.rb | 393 | ||||
-rw-r--r-- | code/keys.rb | 321 | ||||
-rw-r--r-- | code/old_fm.rb | 233 | ||||
-rw-r--r-- | code/types.rb | 50 |
7 files changed, 1195 insertions, 621 deletions
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 |