lib/ver.rb in ver-2009.10.14 vs lib/ver.rb in ver-2009.11.28
- old
+ new
@@ -1,125 +1,227 @@
# Well begun is half done.
# -- Aristotle
-# TODO: remove before release
-$LOAD_PATH.unshift File.expand_path('../', __FILE__)
+# lazy stdlib
+autoload :Benchmark, 'benchmark'
+autoload :FileUtils, 'fileutils'
-# stdlib
-require 'tk'
-require 'benchmark'
+# 3rd party
+require 'eventmachine'
+
+# eager stdlib
require 'digest/sha1'
-require 'fileutils'
require 'json'
-require 'pathname'
-require 'pp'
+# require 'pp'
require 'securerandom'
require 'set'
-require 'tmpdir'
-class Pathname
- alias / join
-
- def cp(dest)
- FileUtils.copy_file(expand_path.to_s, dest.to_s, preserve = true)
- end
-
- def =~(regexp)
- to_s =~ regexp
- end
-end
-
module VER
- autoload :BufferListView, 'ver/view/buffer_list_view'
autoload :Entry, 'ver/entry'
- autoload :FuzzyFileFinderView, 'ver/view/fuzzy_file_finder_view'
+ autoload :Help, 'ver/help'
+ autoload :Font, 'ver/font'
+ autoload :HoverCompletion, 'ver/hover_completion'
autoload :Keymap, 'ver/keymap'
autoload :Layout, 'ver/layout'
- autoload :ListView, 'ver/view/list_view'
autoload :Methods, 'ver/methods'
autoload :Mode, 'ver/mode'
autoload :Status, 'ver/status'
- autoload :SyntaxListView, 'ver/view/syntax_list_view'
autoload :Syntax, 'ver/syntax'
autoload :Text, 'ver/text'
- autoload :Textpow, 'ver/textpow'
+ autoload :Textpow, 'ver/vendor/textpow'
+ autoload :Levenshtein, 'ver/vendor/levenshtein'
autoload :Theme, 'ver/theme'
- autoload :ThemeListView, 'ver/view/theme_list_view'
autoload :View, 'ver/view'
- home_conf_dir = Pathname('~/.config/ver').expand_path
- core_conf_dir = Pathname(File.expand_path('../../config/', __FILE__))
+ require 'ver/options'
+ @options = Options.new(:ver)
- # poor man's option system
- # p Tk::Tile.themes # a list of available themes
- # Linux themes: "classic", "default", "clam", "alt"
- OPTIONS = {
- font: TkFont.new(family: 'Terminus', size: 9),
- tk_theme: 'clam',
- theme: 'Blackboard',
- keymap: 'vim',
- global_quit: 'Control-q',
- home_conf_dir: home_conf_dir,
- core_conf_dir: core_conf_dir,
- loadpath: [home_conf_dir, core_conf_dir],
- }
-
class << self
attr_reader :root, :layout, :status, :paths, :options
end
+ options.dsl do
+ o "Default Font for all widgets",
+ :font, "TkFixedFont 10"
+
+ o "Internal:External encoding",
+ :encoding, "UTF-8:UTF-8"
+
+ o "Tk Tile Theme",
+ :tk_theme, 'clam'
+
+ o "Syntax highlighting theme",
+ :theme, "Blackboard"
+
+ o "Keymap used",
+ :keymap, 'vim'
+
+ o "Expand all tabs into spaces",
+ :expandtab, true
+
+ o "Use automatic indentation",
+ :autoindent, true
+
+ o "Number of spaces used in autoindent",
+ :shiftwidth, 2
+
+ o "Number of spaces a tab stands for",
+ :tabstop, 8
+
+ o "Number of characters after which wrap commands will wrap",
+ :textwidth, 80
+
+ o "In case of a total failure, this key binding should bail you out",
+ :emergency_exit, "<Control-q>"
+
+ o "Fork off on startup to avoid dying with the terminal",
+ :fork, true
+
+ o "Milliseconds that the cursor is visible when blinking",
+ :insertontime, 500
+
+ o "Milliseconds that the cursor is invisible when blinking",
+ :insertofftime, 0
+
+ o "Width of one tab in pixel",
+ :tabs, 10
+
+ o "Default filetype if no matching syntax can be found",
+ :filetype, "Plain Text"
+
+ o "Location of personal configuration",
+ :home_conf_dir, Pathname('~/.config/ver').expand_path
+
+ o "Location of system-wide configuration",
+ :core_conf_dir, Pathname(File.expand_path('../../config/', __FILE__))
+
+ o "Locations where we look for configuration",
+ :loadpath, [home_conf_dir, core_conf_dir]
+ end
+
module_function
def loadpath
- options[:loadpath]
+ options.loadpath
end
def run(given_options = {})
- @options = OPTIONS.merge(given_options)
+ @options.merge!(given_options)
- setup
- first_startup unless options[:home_conf_dir].directory?
+ setup_tk
+
+ if Tk::RUN_EVENTLOOP_ON_MAIN_THREAD
+ run_aqua
+ else
+ run_x11
+ end
+ rescue => exception
+ VER.error(exception)
+ exit
+ end
+
+ def run_aqua
+ run_core
+ EM.run{ Tk.mainloop }
+ end
+
+ def run_x11
+ EM.run do
+ EM.defer do
+ run_core
+ Tk.mainloop
+ end
+ end
+ end
+
+ def run_core
+ first_startup unless options.home_conf_dir.directory?
load 'rc'
+ sanitize_options
+ setup_widgets
open_argv || open_welcome
emergency_bindings
-
- rescue => exception
- puts "#{exception.class}: #{exception}", *exception.backtrace
- Tk.exit
- else
- Tk.mainloop
end
- def setup
+ def setup_tk
+ require 'ffi-tk'
Thread.abort_on_exception = true
+ end
- Tk::Tile.set_theme options[:tk_theme]
+ def setup_widgets
+ Tk::Tile.set_theme options.tk_theme
@paths = Set.new
- @root = TkRoot.new
+ @root = Tk.root
+ @root.wm_geometry = '160x80'
@layout = Layout.new(@root)
@layout.strategy = Layout::VerticalTiling
+ @status = Entry.new(@root, font: options.font)
+ @status.insert :end, 'For information about VER, type F1'
+ @status.pack(fill: :x)
end
+ def sanitize_options
+ font = options.font
+
+ unless font.respond_to?(:measure)
+ font = Tk::Font.new(font)
+ actual_hash = font.actual_hash
+ options.font = Font.cache[actual_hash] = font
+ end
+
+ tabs = font.measure('0') * options.tabstop
+ options.tabs = tabs
+
+ encoding = options[:encoding]
+ unless encoding.is_a?(Encoding)
+ external, internal = encoding.to_s.split(':', 2)
+
+ Encoding.default_external = Encoding.find(external) if external
+ Encoding.default_internal = Encoding.find(internal) if internal
+ end
+
+ # We supply a reasonable default in case the platform doesn't have the theme
+ # wished for.
+ unless Tk::Tile::Style.theme_names.include?(options.tk_theme)
+ options.tk_theme = 'default'
+ end
+
+ letter = /[\w\n.-]/
+ space = /[^\w.-]/
+
+ # make sure Tcl already has the vars set
+ Tk.interp.eval('catch {tcl_endOfWord}')
+ Tk.execute('set', 'tcl_wordchars', letter)
+ Tk.execute('set', 'tcl_nonwordchars', space)
+ end
+
def first_startup
- home, core = options.values_at(:home_conf_dir, :core_conf_dir)
+ home, core = options.home_conf_dir, options.core_conf_dir
home.mkpath
(core/'rc.rb').cp(home/'rc.rb')
(core/'scratch').cp(home/'scratch')
(core/'tutorial').cp(home/'tutorial')
(core/'welcome').cp(home/'welcome')
end
+ def exit
+ Tk.exit rescue nil
+ EM.stop rescue nil
+ Kernel.exit
+ end
+
def load(name)
loadpath.each do |path|
file = File.join(path, name)
begin
require(file)
return
- rescue LoadError
+ rescue LoadError, TypeError => ex
+ # TypeError happens on JRuby sometimes...
end
end
end
def find_in_loadpath(basename)
@@ -130,27 +232,136 @@
nil
end
def open_argv
- ARGV.each{|arg|
+ argv = ARGV.dup
+ any = false
+
+ while arg = argv.shift
layout.create_view do |view|
- view.open_path(arg)
+ if argv.first =~ /\+\d+/
+ line = argv.shift.to_i
+ view.open_path(arg, line)
+ else
+ view.open_path(arg)
+ end
+
+ any = true
end
- }.any?
+ end
+
+ any
end
def open_welcome
layout.create_view do |view|
welcome = find_in_loadpath('welcome')
view.open_path(welcome)
end
end
def emergency_bindings
- Tk.bind :all, options[:global_quit], proc{ exit }
+ Tk::Bind.bind(:all, options.emergency_exit){ exit }
end
def opened_file(text)
@paths << text.filename
+ end
+
+ def error(exception)
+ @status.value = exception.message if @status
+ error_tree(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.
+ end
+
+ def error_tree(exception)
+ @tree ||= Tk::Tile::Treeview.new(@root)
+ @tree.clear
+
+ @tree.configure(
+ columns: %w[line method],
+ displaycolumns: %w[line method]
+ )
+ @tree.heading('#0', text: 'File')
+ @tree.heading('line', text: 'Line')
+ @tree.heading('method', text: 'Method')
+ @tree.tag_configure('error', background: '#f88')
+ @tree.tag_configure('backtrace', background: '#8f8')
+
+ context_size = 7
+ frames = {}
+ error_tags = ['error']
+ backtrace_tags = ['backtrace']
+
+ # from Rack::ShowExceptions
+ exception.backtrace.each do |line|
+ next unless line =~ /(.*?):(\d+)(:in `(.*)')?/
+ filename, lineno, function = $1, $2.to_i, $4
+
+ item = @tree.insert(nil, :end,
+ text: filename, values: [lineno, function], tags: error_tags)
+
+ begin
+ lines = ::File.readlines(filename)
+ _lineno = lineno - 1
+
+ first_lineno = [_lineno - context_size, 0].max
+ last_lineno = [_lineno + context_size, lines.size].min
+ context = lines[first_lineno..last_lineno]
+
+ frames[item.id] = {
+ filename: filename,
+ lineno: lineno,
+ function: function,
+ first_lineno: first_lineno,
+ last_lineno: last_lineno,
+ context: context,
+ }
+ rescue => ex
+ puts ex, ex.backtrace
+ end
+ end
+
+ @tree.focus
+ @tree.pack expand: true, fill: :both
+
+ @tree.bind('<<TreeviewOpen>>'){|event|
+ begin
+ item = @tree.focus_item
+ frame = frames[item.id]
+
+ case frame
+ when Hash
+ filename, lineno, first_lineno, context =
+ frame.values_at(:filename, :lineno, :first_lineno, :context)
+
+ context.each_with_index{|line, idx|
+ line_lineno = first_lineno + idx + 1
+ tags = line_lineno == lineno ? error_tags : backtrace_tags
+ line_item = item.insert(:end,
+ text: line, values: [line_lineno], tags: tags)
+ frames[line_item.id] = [filename, lineno]
+ }
+ when Array
+ filename, lineno = frame
+ @layout.views.first.find_or_create(filename, lineno){|view|
+ @tree.pack_forget
+ }
+ end
+ rescue => ex
+ puts ex, ex.backtrace
+ end
+ }
+ @tree.bind('<Escape>'){
+ @tree.pack_forget
+ @layout.views.first.focus
+ }
end
end