diff options
Diffstat (limited to 'code/fm')
-rw-r--r-- | code/fm/fm.rb | 289 | ||||
-rw-r--r-- | code/fm/keys.rb | 589 | ||||
-rw-r--r-- | code/fm/types.rb | 151 |
3 files changed, 1029 insertions, 0 deletions
diff --git a/code/fm/fm.rb b/code/fm/fm.rb new file mode 100644 index 00000000..81b868ca --- /dev/null +++ b/code/fm/fm.rb @@ -0,0 +1,289 @@ +OPTIONS = { + 'hidden' => false, + 'sort' => :name, + 'dir_first' => true, + 'sort_reverse' => false, + 'color' => true, + 'filepreview' => true, +} + +class Void + oldv, $-v = $-v, nil + + for method in instance_methods + remove_method(method) rescue nil + end + + def self.method_missing(*a) end + + $-v = oldv +end + +module Fm + extend self + COPY_PRIORITY = -2 + + COLUMNS = 4 + 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 = nil + @search_string = '' + @copy = [] + @ignore_until = nil + @trash = File.expand_path('~/.trash') + 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)) rescue nil + if Hash === loaded + @memory.update(loaded) + 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 + +# for i in 1..20 +# eval "Signal.trap(#{i}) do +# log #{i} +# exit if #{i} == 9 end" +# end + + boot_up(pwd) + end + + attr_reader(:dirs, :pwd) + + def pwd() @pwd end + + def refresh() + begin + @pwd.refresh + draw + rescue + end + end + + def boot_up(pwd=nil) + pwd ||= @pwd.path || Dir.getwd + Scheduler.reset + + @dirs = Hash.new() do |hash, key| + hash[key] = newdir = Directory.new(key) +# newdir.schedule + newdir + end + + @path = [@dirs['/']] + enter_dir(pwd) + + Scheduler.run + end + + def lines + Interface::lines - @bars.size + end + + def dump + remember_dir + dumped = Marshal.dump(@memory) + File.open(@fmrc, 'w') do |f| + f.write(dumped) + end + end + + def on_interrupt + @buffer = '' + sleep 0.2 + end + + def main_loop + bool = false + 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 + log "drawing" + draw() + rescue Interrupt + on_interrupt +# rescue Exception +# log($!) +# log(caller) + end + + begin +# unless bool +# bool = true + key = geti +# else +# key = geti +# key = 'j' +# end +# @mutex.synchronize { + press(key) +# } + rescue Interrupt + on_interrupt + end + end + end + + def current_path() @pwd.path end + + def reset_title() set_title("fm: #{@pwd.path}") end + + def 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 + log("NIGGER" * 100) + log($!) + log(caller) + enter_dir(olddir) + return false + end + end + end + + def enter_dir(dir) + @pwd.restore if @pwd + @marked = [] + dir = File.expand_path(dir) + + oldpath = @path.dup + + # NOTE: @dirs[unknown] is not nil but Directory.new(unknown) + @path = [@dirs['/']] + unless dir == '/' + dir.slice(0) + accumulated = '/' + for part in dir.split('/') + unless part.empty? + accumulated = File.join(accumulated, part) + @path << @dirs[accumulated] + end + end + end + + @pwd = @path.last + @pwd.pos = @pwd.pos + + @pwd.files_raw.dup.each do |x| + @dirs[x] if File.directory?(x) + end + + reset_title() + + if @path.size < oldpath.size + @pwd.pos = @pwd.files_raw.index(oldpath.last.path) || 0 + end + + i = 0 + + @path.each_with_index do |p, i| + p.schedule + unless i == @path.size - 1 + p.pointed_file = @path[i+1].path + end + end + + Dir.chdir(@pwd.path) + end + + def currentfile() @pwd.files[@pwd.pos] end + def selection() + if @marked.empty? + [currentfile] + else + @marked.dup + end + end + + def move_to_trash!(fn) + unless File.exists?(@trash) + Dir.mkdir(@trash) + end + new_path = File.join(@trash, fn.basename) + + Action.move(fn, new_path) + + return new_path + end + + def move_to_trash(file) + unless file + return + end + + if String === file + file = Directory::Entry.new(file) + end + + 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 + if !file.in?(@trash) and file.size > 0 + return move_to_trash!(file) + else + file.delete! + end + end + end + return nil + end + + def 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 bar_del(bar) + @bars.delete(bar) + if @bars.empty? + @bars_thread.kill + @bars_thread = nil + end + end +end + diff --git a/code/fm/keys.rb b/code/fm/keys.rb new file mode 100644 index 00000000..bca4f4cd --- /dev/null +++ b/code/fm/keys.rb @@ -0,0 +1,589 @@ +module Fm + # ALL combinations of multiple keys (but without the last letter) + # or regexps which match combinations need to be in here! + COMBS = %w( + g d df y c Z delet cu + ter ta S ? ?g ?f :q + + /[m`']/ /[fF/!].*/ + /[ri]\d*\w*[^ri]/ + /(cw|cd|mv).*/ + /b(l(o(c(k(.*)?)?)?)?)?/ + /m(k(d(i(r(.*)?)?)?)?)?/ + /r(e(n(a(m(e(.*)?)?)?)?)?)?/ + ) + + # Create a regular expression which detects these combos + ary = [] + for token in COMBS + if token =~ /^\/(.*)\/$/ + ary << $1 + elsif token.size > 0 + ary << token.each_char.map {|t| + if t == '?' + t = '\?' + end + + "(?:#{t}" + }.join + + (')?' * (token.size - 1)) + ')' + end + end + REGX = Regexp.new('^(?:' + ary.uniq.join('|') + ')$') + + def self.ignore_keys_for(t) + @ignore_until = Time.now + t + end + + def self.search(str, offset=0, backwards=false) + begin + rx = Regexp.new(str, Regexp::IGNORECASE) + rescue + return false + end + + ary = @pwd.files_raw.dup + ary.wrap(@pwd.pos + offset) + + ary.reverse! if backwards + + for f in ary + g = File.basename(f) + if g =~ rx + @pwd.pointed_file = f + break + end + end + end + + def self.find_newest() + newest = nil + for f in @pwd.files + if newest.nil? or newest.ctime < f.ctime + newest = f + end + end + @pwd.pointed_file = newest.path + end + + def self.hints(str) + begin + rx = Regexp.new(str, Regexp::IGNORECASE) + rescue + return false + end + + ary = @pwd.files_raw.dup + ary.wrap(@pwd.pos) + + n = 0 + pointed = false + for f in ary + g = File.basename(f) + if g =~ rx + unless pointed + log "point at #{f}" + @pwd.pointed_file = f + pointed = true + end + n += 1 + end + end + + return n + end + + def self.remember_dir + @memory["`"] = @memory["'"] = @pwd.path + end + + def self.press(key) + return if @ignore_until and Time.now < @ignore_until + + @ignore_until = nil + + if key == '<bs>' + if @buffer.empty? + @buffer = key + elsif @buffer == 'F' + descend + elsif @buffer[-1] == ?> + @buffer.slice!(/(<.*)?>$/) + else + @buffer.slice!(-1) + end + elsif key == '<c-u>' + @buffer = '' + else + @buffer << key + end + + case @buffer + when '<redraw>' + closei + starti + + when 'j', '<down>' + @pwd.pos += 1 + + when 's' + closei + system('clear') + ls = ['ls'] + ls << '--color=auto' if OPTIONS['color'] + ls << '--group-directories-first' if OPTIONS['color'] + system(*ls) + system('bash') + @pwd.schedule + starti + + + when /^S(.)$/ + OPTIONS['sort_reverse'] = $1.ord.between?(65, 90) + + case $1 + when 'n' + OPTIONS['sort'] = :name + when 'e' + OPTIONS['sort'] = :ext + when 't' + OPTIONS['sort'] = :type + when 's' + OPTIONS['sort'] = :size + when 'm' + OPTIONS['sort'] = :mtime + when 'c' + OPTIONS['sort'] = :ctime + end + @pwd.schedule + + when 'tar' + closei + system('tar', 'cvvf', 'pack.tar', *selection.map{|x| x.basename}) + @pwd.refresh! + starti + + when 'R' + @pwd.refresh! + + when 'a' + Process.kill('INT', Process.pid) +# Process.kill('INT', Process.pid) + + when 'x' + @bars.first.kill unless @bars.empty? + + when 'X' + @bars.last.kill unless @bars.empty? + + when 'J' + @pwd.pos += lines/2 + + when 'K' + @pwd.pos -= lines/2 + + when 'cp', 'yy' + @copy = selection + @cut = false + + when 'cut' + @copy = selection + @cut = true + + when 'n' + if @search_string.empty? + find_newest + else + search(@search_string, 1) + end + + when 'N' + search(@search_string, 0, true) + +# 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 'A' + @buffer = "cw #{currentfile.name}" + + when /^f(.+)$/ + str = $1 + if str =~ /^\s?(.*)(L|;|<cr>|<esc>)$/ + @buffer = '' + @search_string = $1 + press('l') if $2 == ';' or $2 == 'L' + else + test = hints(str) + if test == 1 + @buffer = '' + press('l') + ignore_keys_for 0.5 + elsif test == 0 + @buffer = '' + ignore_keys_for 1 + end + end + + when /^\/(.+)$/ + str = $1 + if str =~ /^\s?(.*)(L|;|<cr>|<esc>)$/ + @buffer = '' + @search_string = $1 + press('l') if $2 == ';' or $2 == 'L' + else + search(str) + end + + when /^mkdir(.*)$/ + str = $1 + if str =~ /^\s?(.*)(<cr>|<esc>)$/ + @buffer = '' + if $2 == '<cr>' + closei + system('mkdir', $1) + starti + @pwd.schedule + end + end + + when /^block.*stop$/ + @buffer = '' + + when /^!(.+)$/ + str = $1 + if str =~ /^(\!?)(.*)(<cr>|<esc>)$/ + @buffer = '' + if $3 == '<cr>' + closei + system("bash", "-c", $2) + gets unless $1.empty? + starti + @pwd.schedule + end + end + + when /^cd(.+)$/ + str = $1 + if str =~ /^\s?(.*)(<cr>|<esc>)$/ + @buffer = '' + if $2 == '<cr>' + remember_dir + enter_dir_safely($1) + end + end + + when /^(mv|cw|rename)(.+)$/ + str = $2 + if $1 == 'mv' + if str =~ /['`"]([\w\d])/ + if path = @memory[$1] + str = '' + @buffer.clear + if File.exists?(path) and File.directory?(path) + Action.move(selection, path) + end + end + end + end + log str + if str =~ /^\s?(.*)(<cr>|<esc>)$/ + @buffer = '' + if $2 == '<cr>' + files = selection + if files.size == 1 + fn = $1 + log "!!! #{fn}" + unless fn.include? '.' + if ext = files.first.basename.from_last('.') + fn << ".#{ext}" + end + log "??? #{ext}" + end + Action.move(files, fn) + @pwd.refresh! + @pwd.find_file(fn) + else + Action.move(files, $1) + @pwd.refresh! + end + end + end + + when 'tc' + OPTIONS['color'] ^= true + + when 'tf' + OPTIONS['filepreview'] ^= true + + when 'th' + OPTIONS['hidden'] ^= true + @pwd.refresh! + + when 'td' + OPTIONS['dir_first'] ^= true + @pwd.schedule + + when 'delete' + files = selection + @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' + if @cut + Action.move(@copy, @pwd.path) + @cut = false + else + Action.copy(@copy, @pwd.path) + end + @pwd.refresh! + if @copy.size == 1 + @pwd.find_file(@copy[0].basename) + end + + when /^[`'](.)$/ + if dir = @memory[$1] and not @pwd.path == dir + remember_dir + enter_dir_safely(dir) + end + + when '<s-tab>' + if dir = @memory['`'] and not @pwd.path == dir + remember_dir + enter_dir_safely(dir) + end + + when '<tab>' + if dir = @memory['9'] and dir != '/' + unless @pwd.path == dir + enter_dir_safely(dir) + end + elsif 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) + if new_path + new_path = Directory::Entry.new(new_path) + new_path.get_data + @copy = [new_path] + @cut = false + end + @pwd.schedule + + when 'dD', 'dfd' + cf = currentfile + if cf and cf.exists? + cf.delete! + @pwd.schedule + end + + when 'term' + fork do exec 'x-terminal-emulator' end + + when 'g0' + remember_dir + enter_dir('/') + + when 'gh' + remember_dir + enter_dir('~') + + when 'gu' + remember_dir + enter_dir('/usr') + + when 'ge' + remember_dir + enter_dir('/etc') + + when 'gm' + remember_dir + enter_dir('/media') + + when 'gt' + remember_dir + enter_dir('~/.trash') + + when 'G' + @pwd.pos = @pwd.size - 1 + + when 'k', '<up>' + @pwd.pos -= 1 + + when '<bs>', 'h', 'H', '<left>' + descend + + when 'E' + cf = currentfile.path + unless cf.nil? or enter_dir_safely(cf) + closei + system VI % cf + starti + end + + when '<cr>', 'l', ';', 'L', '<right>' + ascend(@buffer=='L', @buffer=='l') + + # a = run all + # d or e = detach + # t = run in a terminal + # w = wait for <enter> after execution + # capital letter inverts + when /^[ri](\d*)([adetw]*)[ri]$/ + if $2.empty? + f = @marked.empty?? currentfile : @marked.first + flags = get_default_flags(f) + else + flags = $2 + end + opt = OpenStruct.new + opt.newway = true + + opt.mode = $1.to_i unless $1.empty? + + # Set options based on flags + + if flags =~ /a/ + opt.all = true + end + if flags =~ /[de]/ + opt.detach = true + end + if flags =~ /t/ + opt.new_term = true + opt.detach = true + end + if flags =~ /w/ + opt.wait = true + end + + if flags =~ /A/ + opt.all = false + end + if flags =~ /[DE]/ + opt.detach = false + end + if flags =~ /T/ + opt.new_term = false + end + if flags =~ /W/ + opt.wait = false + end + + Action.run(opt.__table__) + +# when 'ra' +# unless File.directory?(currentfile.path) +# Action.run(:all=>true) +# end + + when 'ZZ', '<c-d>', ':q<cr>', 'Q' + exit + + when '<c-r>' + Fm.boot_up + + when "-", "=" + val = "2#{key=='-' ? '-' : '+'}" + system("amixer", "-q", "set", "PCM", val, "unmute") + + else +# log key.ord + + end + + @buffer = '' unless @buffer == '' or @buffer =~ REGX + end + + def self.ascend(wait = false, all=false) + if all and !@marked.empty? + closei + system(*['mplayer', '-fs', *@marked.map{|x| x.path}]) + starti + return true + else + cf = currentfile + enter = enter_dir_safely(cf.path) + unless enter + return Action.run(:detach=>false) + end + return false + end + end + + def self.descend + unless @path.size == 1 + enter_dir(@buffer=='H' ? '..' : @path[-2].path) + end + end +end + diff --git a/code/fm/types.rb b/code/fm/types.rb new file mode 100644 index 00000000..2c21c214 --- /dev/null +++ b/code/fm/types.rb @@ -0,0 +1,151 @@ +module Fm + MIMETYPES = Marshal.load(File.read( + File.join(MYDIR, 'data', 'mime.dat'))) + + def self.get_default_flags(file) + case file.mimetype + when /^(?:image|video)\//; 'd' + when 'application/pdf'; 'd' + else '' end + end + + def self.filehandler(files, hash) + str = files.map{|x| x.sh}.join(' ') + type = files.first.mimetype + name = files.first.basename + mode = hash.mode + + use = lambda do |sym| + hash.exec = App.send(sym, mode, name, str, files) + end + + case type + when /^(video|audio)\// + use.call :mplayer + when "application/pdf" + use.call :evince + when /^(image)\// + use.call :image + else + case name + when /\.(swc|smc)/ + use.call :zsnes + end + end + + return hash + end + + module App + def image(mode, name, str, file) + case mode + when 4; "feh --bg-scale #{str}" + when 5; "feh --bg-tile #{str}" + when 6; "feh --bg-center #{str}" + when 2; "gimp #{str}" + when 1; "feh -F #{str}" + else "feh #{str}" + end + end + def evince(mode, name, str, file) + "evince #{str}" + end + def mplayer(mode, name, str, files) + rest = name, str, files + + case mode + when nil + if name =~ /720p/ + mplayer(1, *rest) + else + mplayer(0, *rest) + end + when 0 + return "mplayer -fs -sid 0 #{str}" + when 1 + return "mplayer -vm sdl -sid 0 #{str}" + end + end + def zsnes(mode, name, str, files) + case mode + when 1 + return "zsnes -ad sdl -o #{str}" + else + return "zsnes -ad sdl -u -o #{str}" + end + end + + module_function(*%w{ + mplayer zsnes evince image + }) + end + + def self.getfilehandler_frompath(*files) + file = files.first + n = files.size + 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 /\.java$/ + return "javac #{file.sh}", true + + when /\.class$/ + return log "java #{file.sh.before_last('.')}" + + 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|gif)$/i + return "feh #{file.sh}", false + + when /\.(html?|swf)$/i + return "firefox #{file.sh}" + + when /\.pdf$/i + return "evince #{file.sh}" + + when /\.txt$/i + return VI % file.sh + + when /\.wav$/i + return "aplay -q #{file.sh}" + + when /\.m3u$/i +# return "mpc play #{File.expand_path(file)[13..-1].sh}", false + return "/home/hut/bin/loadplaylist.rb #{file.sh}" +# 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? + return "#{file.sh}", true + end + + return VI % file.sh + end +end + |