lib/ver.rb in ver-2010.02 vs lib/ver.rb in ver-2010.08

- old
+ new

@@ -6,42 +6,75 @@ autoload :FileUtils, 'fileutils' autoload :Tempfile, 'tempfile' # eager stdlib require 'digest/sha1' +require 'forwardable' +require 'logger' require 'securerandom' require 'set' +require 'tmpdir' + +# vendor stuff, extensions and fixes. require 'ver/vendor/better_pp_hash' require 'ver/vendor/pathname' require 'ver/vendor/sized_array' +require 'ver/vendor/json_store' +# 3rd party dependencies +require 'ffi' + +# Small helper method that equivalent to Kernel#p but writes to log. +# Please use this for debugging, log is at /tmp/ver/log/all.log. +# +# Returns the arguments given just like Kernel#p. +# +# Logger takes care of calling #inspect on everything that's not a String. +# We use debug level for this. +def l(arg = nil, *args) + if args.empty? + VER.log.debug(arg) + arg + else + VER.log.debug(arg, *args) + [arg, *args] + end +end + # This is the doc for VER module VER autoload :Action, 'ver/action' autoload :Bookmarks, 'ver/methods/bookmark' autoload :Buffer, 'ver/buffer' + autoload :Clipboard, 'ver/clipboard' autoload :Entry, 'ver/entry' + autoload :EvalCompleter, 'ver/vendor/eval_completer' + autoload :Event, 'ver/event' autoload :ExceptionView, 'ver/exception_view' autoload :Executor, 'ver/executor' autoload :Font, 'ver/font' autoload :Help, 'ver/help' autoload :HoverCompletion, 'ver/hover_completion' - autoload :KEYSYMS, 'ver/keysyms' autoload :Keymap, 'ver/keymap' autoload :Keymapped, 'ver/keymap/keymapped' autoload :Levenshtein, 'ver/vendor/levenshtein' autoload :Methods, 'ver/methods' autoload :MiniBuffer, 'ver/minibuffer' + autoload :ModeResolving, 'ver/mode_resolving' autoload :NotebookLayout, 'ver/layout/notebook' autoload :PanedLayout, 'ver/layout/paned' - autoload :SYMKEYS, 'ver/keysyms' + autoload :Platform, 'ver/platform' + autoload :Register, 'ver/register' + autoload :RegisterList, 'ver/register' autoload :Status, 'ver/status' + autoload :Struct, 'ver/struct' autoload :Syntax, 'ver/syntax' autoload :Text, 'ver/text' autoload :Textpow, 'ver/vendor/textpow' autoload :Theme, 'ver/theme' autoload :TilingLayout, 'ver/layout/tiling' + autoload :ToplevelLayout, 'ver/layout/toplevel' autoload :Treeview, 'ver/treeview' autoload :Undo, 'ver/undo' autoload :WidgetEvent, 'ver/widget_event' autoload :WidgetMajorMode, 'ver/widget_major_mode' @@ -53,27 +86,33 @@ class << self attr_reader(:ctag_stack, :keymap, :style_name_pools, :style_name_register, :bookmarks, :buffers, :layout, :options, :paths, :root, :status, :minibuf) - attr_accessor :layout_class + attr_accessor :layout_class, :log end # the rest of the options are in config/rc.rb options.dsl do o "Fork off on startup to avoid dying with the terminal", - :fork, true + :fork, Platform.unix? + o "Start hidden, useful for specs", + :hidden, false + o "Use EventMachine inside VER, at the moment only for the console", :eventmachine, false o "Internal:External encoding", :encoding, "UTF-8:UTF-8" o "Keymap used", :keymap, 'vim' + o "Load personal rc.rb", + :load_rc, true + o "Width of one tab in pixel", :tabs, 10 o "Minimum size of search term to start incremental highlighting", :search_incremental_min, 1 @@ -87,36 +126,48 @@ o "Locations where we look for configuration", :loadpath, [home_conf_dir, core_conf_dir] o "Name under which the session is stored (nil means to keep no session)", :session, nil + + o "Open welcome file on startup without parameters", + :welcome, true end module_function def loadpath options.loadpath end def run(given_options = {}, &block) + # we fork, redirect all output to log files. + log_dir = Pathname.tmpdir/'ver/log' + log_dir.mkpath + + self.log = Logger.new(log_dir/'all.log', 10, 1 << 20) + + touch setup_tk run_startup(given_options) pp options if $DEBUG run_maybe_forking do + Event.load! options.eventmachine ? run_em(&block) : run_noem(&block) end rescue => exception - VER.error(exception) + error(exception) exit end def run_maybe_forking(&block) return yield unless options.fork fork do - trap(:HUP){ 'terminal disconnected' } + [$stdout, $stderr].each{|io| io.reopen('/dev/null') } + trap(:HUP){ l('terminal lost') } yield end end def run_em(&block) @@ -147,19 +198,24 @@ first_startup unless options.home_conf_dir.directory? @startup_hooks = [] @paths = Set.new @cancel_blocks = {} + @repeat_blocks = {} @load_plugins = Set.new @exception_view = nil @bookmarks = Bookmarks.new @ctag_stack = [] @style_name_register = [] @style_name_pools = {} - @buffers = {} + @buffers = Set.new - load 'rc' + if given_options[:load_rc] != false + load 'rc' + else + require(options.core_conf_dir/'rc') + end @options.merge!(given_options) end def run_core sanitize_options @@ -169,37 +225,50 @@ emergency_bindings load_plugins run_startup_hooks end - def when_inactive_for(ms) + def when_inactive_for(ms, repetitions = 1) block = lambda{ if @cancel_blocks[block] @cancel_blocks.delete(block) else - - if Tk.root.tk_inactive > ms - yield - Tk.root.tk_inactive('reset') - Tk::After.ms(ms, &block) + if inactive > ms + if @repeat_blocks[block] < repetitions + @repeat_blocks[block] += 1 + yield + Tk::After.ms(ms, &block) + else + Tk::After.ms(ms, &block) + end else + @repeat_blocks[block] = 0 Tk::After.ms(ms, &block) end end } @cancel_blocks[block] = false + @repeat_blocks[block] = 0 Tk::After.idle(&block) block end + def touch + @mtime = Time.now + end + + def inactive + (Time.now - @mtime) * 1000 + end + def defer Tk::After.idle do begin yield rescue Exception => ex - VER.error(ex) + error(ex) end end end def cancel_block(block) @@ -250,34 +319,25 @@ Thread.abort_on_exception = true end def setup_widgets Tk::Tile.set_theme options.tk_theme - - @root = Tk.root - @root.wm_geometry = '160x80' - Tk::Tile::Style.configure('Label', font: options.font, sticky: :sw) - # Tk::Tile::Style.configure('TLabelframe', background: '#f00') + @root = Tk.root setup_layout - load("keymap/#{options.keymap}.rb") + @minibuf = MiniBuffer.new(@root) + @minibuf.pack expand: true, fill: :both - @minibuf = MiniBuffer.new(@layout) - @layout.configure( - labelwidget: minibuf, - labelanchor: :sw - ) - - [:Messages, :Scratch].each do |name| - defer{ Buffer[name].hide } - end + # [:Messages, :Scratch, :Completions].each do |name| + # Buffer[name].hide + # end end def setup_layout - @layout = (layout_class || PanedLayout).new(root) + @layout = (layout_class || ToplevelLayout).new(root) end def sanitize_options font = options.font @@ -324,11 +384,11 @@ end def exit store_session @cancel_blocks.keys.each{|key| @cancel_blocks[key] = true } - Tk.exit rescue nil + Tk.exit EM.stop rescue nil Kernel.exit end def load(name) @@ -370,70 +430,71 @@ opened_any end def open_welcome + return unless options.welcome if welcome = find_in_loadpath('welcome') find_or_create_buffer(welcome) true end end def open_session return unless session_base = options.session - basename = "#{session_base}.session.rb" + basename = "sessions/#{session_base}.json" return unless file = find_in_loadpath(basename) - begin - session = eval(File.read(file)) - rescue SyntaxError => ex - puts "#{ex.class}: #{ex}", *ex.backtrace - return - end + JSON::Store.new(file, true).transaction do |session| + session['bookmarks'].each do |raw_bm| + bm = Bookmarks::Bookmark.new + bm.name = raw_bm['name'] + bm.file = Pathname(raw_bm['file']) + bm.index = raw_bm['index'] + l "Loaded %p" % [bm] + bookmarks << bm + end - session[:bookmarks].each do |raw_bm| - bm = Bookmarks::Bookmark.new - bm.name = raw_bm[:name] - bm.file = Pathname(raw_bm[:file]) - bm.index = raw_bm[:index] - bookmarks << bm + session['buffers'].each do |buffer| + l "Loading Buffer: %p" % [buffer] + find_or_create_buffer(buffer['filename'], *buffer['insert']) + end end - - session[:buffers].each do |buffer| - find_or_create_buffer(buffer[:filename], *buffer[:insert]) - end end + # FIXME: there are no buffers anymore when this is called. def store_session return unless session_base = VER.options.session - basename = "#{session_base}.session.rb" + basename = "sessions/#{session_base}.json" session_path = loadpath.first/basename + session_path.parent.mkpath - session = {buffers: [], bookmarks: []} - buffers.each do |name, buffer| - session[:buffers] << { - name: name.to_s, - filename: buffer.filename.to_s, - insert: buffer.text.index(:insert).split, - } - end + JSON::Store.new(session_path.to_s, true).transaction do |session| + buffers = self.buffers.map do |buffer| + l buffer: buffer + { 'name' => buffer.name.to_s, + 'filename' => buffer.filename.to_s, + 'insert' => buffer.at_insert.to_a, } + end + l "Storing Buffers: %p" % [buffers] + session['buffers'] = buffers - bookmarks.each do |bm| - session[:bookmarks] << { - name: bm.name, - file: bm.file.to_s, - index: bm.index, - } + bookmarks = self.bookmarks.map do |bm| + l bookmark: bm + { 'name' => bm.name, + 'file' => bm.file.to_s, + 'index' => bm.index, } + end + l "Storing Bookmarks: %p" % [bookmarks] + session['bookmarks'] = bookmarks end - - session_path.open('w+:UTF-8') do |io| - io.write(session.pretty_inspect) - end end def find_or_create_buffer(file, line = nil, column = nil, &block) - Buffer.find_or_create(file, line, column, &block) + buffer = Buffer.find_or_create(file, line, column, &block) + buffer.show + buffer end def emergency_bindings Tk::Bind.bind(:all, options.emergency_exit){ exit } end @@ -466,10 +527,11 @@ return name end def return_style_name(style_name) + return unless style_name id, widget_name, widget_class = style_name.split('.') suffix = "#{widget_name}.#{widget_class}" style_name_pools[suffix] << style_name end @@ -492,23 +554,45 @@ out.each do |pair| puts("VER.options.%s = %p" % pair) end end - def error(exception) - @status.value = exception.message if @status - exception_view(exception) if @root - $stderr.puts("#{exception.class}: #{exception}", *exception.backtrace) - rescue Errno::EIO - # The original terminal has disappeared, the $stderr pipe was closed on the - # other side. - [$stderr, $stdout, $stdin].each(&:close) - rescue IOError - # Our pipes are closed, maybe put some output to a file logger here, or display - # in a nicer way, maybe let it bubble up to Tk handling. + # low-level information for developers + def debug(*args) + log.debug(*args) + p(*args) end - def exception_view(exception) - @exception_view ||= ExceptionView.new(root) - @exception_view.show(exception) + # generic (useful) information about system operation + def info(*args) + log.info(*args) + p(:info, *args) + end + + # a warning + def warn(*args) + log.warn(*args) + p(:warn, *args) + end + + # a handleable error condition + def error(*args) + log.error(*args) + if args.size == 1 + arg = args.first + case arg + when Exception + puts "#{arg.class}: #{arg}", *arg.backtrace + else + p(:error, *args) + end + else + p(:error, *args) + end + end + + # an unhandleable error that results in a program crash + def fatal(*args) + log.fatal(*args) + p(:fatal, *args) end end