ver 2009.10.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +1404 -0
- data/MANIFEST +187 -0
- data/Rakefile +30 -0
- data/TODO +169 -0
- data/bin/ver +19 -0
- data/config/detect.rb +109 -0
- data/config/keymap/vim.rb +239 -0
- data/config/rc.rb +0 -0
- data/config/scratch +3 -0
- data/config/syntax/ANTLR.json +275 -0
- data/config/syntax/ASP VB.net.json +228 -0
- data/config/syntax/ASP.json +135 -0
- data/config/syntax/ActionScript.json +168 -0
- data/config/syntax/Ada.json +108 -0
- data/config/syntax/Apache.json +342 -0
- data/config/syntax/AppleScript.json +721 -0
- data/config/syntax/Bibtex.json +253 -0
- data/config/syntax/Blog (HTML).json +47 -0
- data/config/syntax/Blog (Markdown).json +50 -0
- data/config/syntax/Blog (Text).json +45 -0
- data/config/syntax/Blog (Textile).json +45 -0
- data/config/syntax/Bulletin Board.json +428 -0
- data/config/syntax/C++.json +323 -0
- data/config/syntax/C.json +694 -0
- data/config/syntax/CSS.json +346 -0
- data/config/syntax/DOT.json +79 -0
- data/config/syntax/Diff.json +136 -0
- data/config/syntax/Dylan.json +104 -0
- data/config/syntax/Eiffel.json +128 -0
- data/config/syntax/Erlang.json +1636 -0
- data/config/syntax/F-Script.json +137 -0
- data/config/syntax/FXScript.json +260 -0
- data/config/syntax/Gri.json +141 -0
- data/config/syntax/HTML (Mason).json +207 -0
- data/config/syntax/HTML (Rails).json +52 -0
- data/config/syntax/HTML (Tcl).json +42 -0
- data/config/syntax/HTML for ASP.net.json +736 -0
- data/config/syntax/HTML-ASP.json +45 -0
- data/config/syntax/HTML.json +614 -0
- data/config/syntax/Haskell.json +407 -0
- data/config/syntax/Inform.json +80 -0
- data/config/syntax/Ini.json +91 -0
- data/config/syntax/Io.json +142 -0
- data/config/syntax/Java.json +745 -0
- data/config/syntax/JavaProperties.json +42 -0
- data/config/syntax/JavaScript.json +446 -0
- data/config/syntax/LaTeX Beamer.json +65 -0
- data/config/syntax/LaTeX Log.json +88 -0
- data/config/syntax/LaTeX.json +962 -0
- data/config/syntax/Lighttpd.json +93 -0
- data/config/syntax/Lisp.json +101 -0
- data/config/syntax/Literate Haskell.json +55 -0
- data/config/syntax/Logtalk.json +289 -0
- data/config/syntax/Lua.json +146 -0
- data/config/syntax/M.json +744 -0
- data/config/syntax/MEL.json +161 -0
- data/config/syntax/MIPS.json +114 -0
- data/config/syntax/Mail.json +224 -0
- data/config/syntax/Makefile.json +66 -0
- data/config/syntax/Markdown.json +644 -0
- data/config/syntax/Modula-3.json +80 -0
- data/config/syntax/Movable Type.json +348 -0
- data/config/syntax/OCaml.json +1391 -0
- data/config/syntax/Objective-C++.json +21 -0
- data/config/syntax/OpenGL.json +24 -0
- data/config/syntax/PHP.json +2184 -0
- data/config/syntax/Pascal.json +128 -0
- data/config/syntax/Perl.json +2091 -0
- data/config/syntax/Plain text.json +49 -0
- data/config/syntax/Processing.json +188 -0
- data/config/syntax/Quake3 Config.json +54 -0
- data/config/syntax/R.json +157 -0
- data/config/syntax/Rez.json +137 -0
- data/config/syntax/Ruby on Rails.json +170 -0
- data/config/syntax/Ruby.json +1753 -0
- data/config/syntax/SQL (Rails).json +31 -0
- data/config/syntax/SQL.json +435 -0
- data/config/syntax/SWIG.json +96 -0
- data/config/syntax/Scheme.json +359 -0
- data/config/syntax/Shell-Unix-Generic.json +1198 -0
- data/config/syntax/Slate.json +265 -0
- data/config/syntax/Smarty.json +110 -0
- data/config/syntax/Standard ML.json +322 -0
- data/config/syntax/Subversion commit message.json +62 -0
- data/config/syntax/Tcl.json +278 -0
- data/config/syntax/TeX Math.json +83 -0
- data/config/syntax/TeX.json +157 -0
- data/config/syntax/Textile.json +273 -0
- data/config/syntax/Twiki.json +436 -0
- data/config/syntax/Vectorscript.json +97 -0
- data/config/syntax/XML strict.json +148 -0
- data/config/syntax/XML.json +301 -0
- data/config/syntax/XSL.json +96 -0
- data/config/syntax/YAML.json +293 -0
- data/config/syntax/iCalendar.json +51 -0
- data/config/syntax/reStructuredText.json +403 -0
- data/config/theme/Active4D.json +260 -0
- data/config/theme/All Hallow's Eve.json +171 -0
- data/config/theme/Amy.json +359 -0
- data/config/theme/BBEdit.json +269 -0
- data/config/theme/Bespin.json +322 -0
- data/config/theme/Blackboard.json +215 -0
- data/config/theme/BoysAndGirls01.json +156 -0
- data/config/theme/Brilliance Black.json +1695 -0
- data/config/theme/Brilliance Dull.json +1451 -0
- data/config/theme/Classic Modified.json +288 -0
- data/config/theme/Cobalt.json +345 -0
- data/config/theme/Cool Glow.json +215 -0
- data/config/theme/Dawn.json +258 -0
- data/config/theme/Eiffel.json +270 -0
- data/config/theme/Espresso Libre.json +247 -0
- data/config/theme/Fluidvision.json +272 -0
- data/config/theme/IDLE.json +159 -0
- data/config/theme/LAZY.json +178 -0
- data/config/theme/Mac Classic.json +277 -0
- data/config/theme/MagicWB (Amiga).json +231 -0
- data/config/theme/Merbivore Soft.json +181 -0
- data/config/theme/Merbivore.json +181 -0
- data/config/theme/Monokai.json +177 -0
- data/config/theme/Notepad2.json +166 -0
- data/config/theme/Pastels on Dark.json +437 -0
- data/config/theme/RubyBlue.json +226 -0
- data/config/theme/Sin City 2.json +361 -0
- data/config/theme/Slate.json +270 -0
- data/config/theme/Slush & Poppies.json +232 -0
- data/config/theme/SpaceCadet.json +143 -0
- data/config/theme/Sunburst.json +415 -0
- data/config/theme/Twilight BG FG.json +633 -0
- data/config/theme/Twilight.json +321 -0
- data/config/theme/Whys Poignant.json +119 -0
- data/config/theme/Zenburnesque.json +237 -0
- data/config/theme/barf.json +155 -0
- data/config/theme/fake.json +418 -0
- data/config/theme/happydeluxe.json +114 -0
- data/config/theme/iLife 05.json +393 -0
- data/config/theme/iPlastic.json +177 -0
- data/config/theme/mintBlue Dark.json +414 -0
- data/config/theme/mintBlue.json +415 -0
- data/config/theme/monoindustrial.json +276 -0
- data/config/theme/starlight.json +67 -0
- data/config/tutorial +74 -0
- data/config/welcome +115 -0
- data/help/index.verh +14 -0
- data/lib/ver.rb +156 -0
- data/lib/ver/entry.rb +97 -0
- data/lib/ver/keymap.rb +96 -0
- data/lib/ver/layout.rb +107 -0
- data/lib/ver/methods.rb +19 -0
- data/lib/ver/methods/completion.rb +116 -0
- data/lib/ver/methods/control.rb +340 -0
- data/lib/ver/methods/insert.rb +6 -0
- data/lib/ver/methods/move.rb +65 -0
- data/lib/ver/methods/search.rb +33 -0
- data/lib/ver/methods/select.rb +145 -0
- data/lib/ver/methods/views.rb +21 -0
- data/lib/ver/mode.rb +160 -0
- data/lib/ver/options.rb +207 -0
- data/lib/ver/plist.rb +106 -0
- data/lib/ver/status.rb +67 -0
- data/lib/ver/syntax.rb +68 -0
- data/lib/ver/syntax/detector.rb +53 -0
- data/lib/ver/syntax/processor.rb +48 -0
- data/lib/ver/text.rb +374 -0
- data/lib/ver/textpow.rb +357 -0
- data/lib/ver/theme.rb +162 -0
- data/lib/ver/vendor/fuzzy_file_finder.rb +340 -0
- data/lib/ver/view.rb +163 -0
- data/lib/ver/view/entry.rb +28 -0
- data/lib/ver/view/list.rb +137 -0
- data/lib/ver/view/list/buffer.rb +27 -0
- data/lib/ver/view/list/fuzzy_file_finder.rb +44 -0
- data/lib/ver/view/list/syntax.rb +13 -0
- data/lib/ver/view/list/theme.rb +13 -0
- data/spec/keymap.rb +224 -0
- data/tasks/bacon.rake +49 -0
- data/tasks/changelog.rake +18 -0
- data/tasks/gem.rake +22 -0
- data/tasks/gem_installer.rake +76 -0
- data/tasks/grancher.rake +12 -0
- data/tasks/install_dependencies.rake +6 -0
- data/tasks/manifest.rake +4 -0
- data/tasks/plist2json.rake +35 -0
- data/tasks/rcov.rake +18 -0
- data/tasks/release.rake +12 -0
- data/tasks/reversion.rake +8 -0
- data/tasks/syntax_list.rake +31 -0
- data/ver.gemspec +29 -0
- metadata +241 -0
data/lib/ver/plist.rb
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
|
|
3
|
+
module VER
|
|
4
|
+
module Plist
|
|
5
|
+
class XML
|
|
6
|
+
def initialize(xml)
|
|
7
|
+
@doc = Nokogiri(xml)
|
|
8
|
+
@plist = {}
|
|
9
|
+
@parsed = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def parse
|
|
13
|
+
@parsed || parse!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def parse!
|
|
17
|
+
dict = @doc.at('/plist/dict')
|
|
18
|
+
|
|
19
|
+
if dict
|
|
20
|
+
@parsed = handle_dict(dict)
|
|
21
|
+
else
|
|
22
|
+
raise ArgumentError, 'This is no XML plist'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def handle_array(array)
|
|
27
|
+
out = []
|
|
28
|
+
key = nil
|
|
29
|
+
|
|
30
|
+
array.children.each do |child|
|
|
31
|
+
child_name = child.name
|
|
32
|
+
next if child_name == 'text'
|
|
33
|
+
|
|
34
|
+
out.push(
|
|
35
|
+
case child_name
|
|
36
|
+
when 'key'
|
|
37
|
+
raise 'No key allows in array'
|
|
38
|
+
when 'array'; handle_array(child)
|
|
39
|
+
when 'dict'; handle_dict(child)
|
|
40
|
+
when 'false'; false
|
|
41
|
+
when 'integer', 'real'; child.inner_text.to_i
|
|
42
|
+
when 'string'; child.inner_text
|
|
43
|
+
when 'true'; true
|
|
44
|
+
else
|
|
45
|
+
# $unhandled[child_name] << child
|
|
46
|
+
raise "%p: %p is not handled" % [child_name, child]
|
|
47
|
+
end
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
out
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def handle_dict(dict)
|
|
55
|
+
out = {}
|
|
56
|
+
key = nil
|
|
57
|
+
|
|
58
|
+
dict.children.each do |child|
|
|
59
|
+
child_name = child.name
|
|
60
|
+
|
|
61
|
+
case child_name
|
|
62
|
+
when 'key'; key = child.inner_text
|
|
63
|
+
when 'text' # ignore
|
|
64
|
+
else
|
|
65
|
+
raise 'No key given' unless key
|
|
66
|
+
|
|
67
|
+
out[key] =
|
|
68
|
+
case child_name
|
|
69
|
+
when 'array'; handle_array(child)
|
|
70
|
+
when 'dict'; handle_dict(child)
|
|
71
|
+
when 'false'; false
|
|
72
|
+
when 'integer', 'real'; child.inner_text.to_i
|
|
73
|
+
when 'string'; child.inner_text
|
|
74
|
+
when 'true'; true
|
|
75
|
+
else
|
|
76
|
+
# $unhandled[child_name] << child
|
|
77
|
+
raise "%p: %p is not handled" % [child_name, child]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
key = nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
out
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def to_yaml
|
|
88
|
+
parse.to_yaml
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def to_json
|
|
92
|
+
parse.to_json
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def to_hash
|
|
96
|
+
parse
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
module_function
|
|
101
|
+
|
|
102
|
+
def parse_xml(filename)
|
|
103
|
+
XML.new(File.read(filename)).parse
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
data/lib/ver/status.rb
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module VER
|
|
2
|
+
# The status bar
|
|
3
|
+
class Status < VER::Entry
|
|
4
|
+
attr_accessor :keymap, :view
|
|
5
|
+
attr_reader :mode
|
|
6
|
+
|
|
7
|
+
def initialize(view, options = {})
|
|
8
|
+
options[:style] ||= self.class.obtain_style_name
|
|
9
|
+
super
|
|
10
|
+
self.view = view
|
|
11
|
+
|
|
12
|
+
keymap_name = VER.options.fetch(:keymap)
|
|
13
|
+
self.keymap = Keymap.get(name: keymap_name, receiver: self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def destroy
|
|
17
|
+
style_name = style
|
|
18
|
+
super
|
|
19
|
+
ensure
|
|
20
|
+
self.class.return_style_name(style_name)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def mode=(name)
|
|
24
|
+
@keymap.mode = @mode = name.to_sym
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def text
|
|
28
|
+
view.text
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def ask(question, &callback)
|
|
32
|
+
@question, @backup_value, @callback = question, value, callback
|
|
33
|
+
|
|
34
|
+
message @question
|
|
35
|
+
focus
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ask_submit
|
|
39
|
+
answer = value.sub(@question, '')
|
|
40
|
+
result = @callback.call(answer)
|
|
41
|
+
message result.inspect
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ask_abort
|
|
45
|
+
message @backup_value
|
|
46
|
+
text.focus
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def delete_char_left
|
|
50
|
+
return if @question.size == cursor
|
|
51
|
+
super
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def go_char_left
|
|
55
|
+
return if @question.size == cursor
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def go_word_left
|
|
60
|
+
if index = value.rindex(/.\b\s/, cursor - 1)
|
|
61
|
+
self.cursor = index
|
|
62
|
+
else
|
|
63
|
+
self.cursor = @question.size
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
data/lib/ver/syntax.rb
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module VER
|
|
2
|
+
class Syntax
|
|
3
|
+
autoload :Detector, 'ver/syntax/detector'
|
|
4
|
+
autoload :Processor, 'ver/syntax/processor'
|
|
5
|
+
|
|
6
|
+
def self.list
|
|
7
|
+
VER.loadpath.map{|path| Dir[(path/'syntax/*.json').to_s] }.flatten
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.from_filename(filename)
|
|
11
|
+
if syntax_name = Detector.detect(filename)
|
|
12
|
+
return new(syntax_name)
|
|
13
|
+
else
|
|
14
|
+
new 'Plain text'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.find(syntax_name)
|
|
19
|
+
VER.find_in_loadpath("syntax/#{syntax_name}.json")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.load(file)
|
|
23
|
+
raise(ArgumentError, "No path to syntax file given") unless file
|
|
24
|
+
Textpow::SyntaxNode.load(file)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.find_and_load(syntax_name)
|
|
28
|
+
load(find(syntax_name))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
attr_accessor :parser, :name
|
|
32
|
+
attr_reader :theme
|
|
33
|
+
|
|
34
|
+
def initialize(name, theme = nil)
|
|
35
|
+
@name = name
|
|
36
|
+
@first_highlight = true
|
|
37
|
+
@old_theme = nil
|
|
38
|
+
|
|
39
|
+
@parser = self.class.find_and_load(name)
|
|
40
|
+
@theme = theme || Theme.find_and_load(VER.options[:theme])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def highlight(textarea, code, lineno = 0)
|
|
44
|
+
if @old_theme
|
|
45
|
+
@old_theme.delete_tags_on(textarea)
|
|
46
|
+
@old_theme = nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if @first_highlight
|
|
50
|
+
@theme.create_tags_on(textarea)
|
|
51
|
+
@theme.apply_default_on(textarea)
|
|
52
|
+
@first_highlight = false
|
|
53
|
+
else
|
|
54
|
+
@theme.remove_tags_on(textarea)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
pr = Processor.new(textarea, @theme, lineno)
|
|
58
|
+
|
|
59
|
+
parser.parse(code, pr)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def theme=(theme)
|
|
63
|
+
@old_theme = @theme
|
|
64
|
+
@theme = theme
|
|
65
|
+
@first_highlight = true
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module VER
|
|
2
|
+
class Syntax
|
|
3
|
+
module Detector
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
EXTS_LIST = {}
|
|
7
|
+
HEAD_LIST = {}
|
|
8
|
+
|
|
9
|
+
def exts(name, exts)
|
|
10
|
+
EXTS_LIST[name] = exts
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def head(name, head)
|
|
14
|
+
HEAD_LIST[name] = head
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def names
|
|
18
|
+
[EXTS_LIST.keys, HEAD_LIST.keys].flatten.compact.sort
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def detect(filename, override_name = nil)
|
|
22
|
+
path = Pathname(filename.to_s)
|
|
23
|
+
path = path.readlink if path.symlink?
|
|
24
|
+
|
|
25
|
+
return unless path.file?
|
|
26
|
+
|
|
27
|
+
VER.load('detect')
|
|
28
|
+
|
|
29
|
+
override_name || detect_ext(path) || detect_head(path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def detect_ext(path)
|
|
33
|
+
basename = path.basename.to_s
|
|
34
|
+
return unless basename =~ /\./
|
|
35
|
+
|
|
36
|
+
EXTS_LIST.find do |name, exts|
|
|
37
|
+
exts.find do |ext|
|
|
38
|
+
return name if basename.end_with?(ext)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def detect_head(path)
|
|
44
|
+
line = path.open{|io| io.gets }
|
|
45
|
+
return unless line && line.valid_encoding?
|
|
46
|
+
|
|
47
|
+
HEAD_LIST.find do |name, head|
|
|
48
|
+
return name if line =~ head
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module VER
|
|
2
|
+
class Syntax
|
|
3
|
+
class Processor < Struct.new(:textarea, :theme, :lineno, :stack, :tags)
|
|
4
|
+
def start_parsing(syntax_name)
|
|
5
|
+
self.stack = []
|
|
6
|
+
self.tags = Hash.new{|h,k| h[k] = [] }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def end_parsing(syntax_name)
|
|
10
|
+
tags.each do |name, indices|
|
|
11
|
+
tag_name = theme.get(name) || name
|
|
12
|
+
textarea.fast_tag_add(tag_name, *indices)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
stack.clear
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def new_line(line)
|
|
19
|
+
self.lineno += 1
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def open_tag(name, pos)
|
|
23
|
+
stack << [name, pos]
|
|
24
|
+
|
|
25
|
+
if tag_name = theme.get(name)
|
|
26
|
+
if stack.size > 1
|
|
27
|
+
below = theme.get(stack[-2][0])
|
|
28
|
+
textarea.tag_raise(tag_name, below) rescue nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def close_tag(name, mark)
|
|
34
|
+
sname, pos = stack.pop
|
|
35
|
+
|
|
36
|
+
tags[name] << "#{lineno}.#{pos}" << "#{lineno}.#{mark}"
|
|
37
|
+
rescue RuntimeError => exception
|
|
38
|
+
# if you modify near the end of the textarea, sometimes the last tag
|
|
39
|
+
# cannot be closed because the contents of the textarea changed since
|
|
40
|
+
# the last highlight was issued.
|
|
41
|
+
# this will cause Tk to raise an error that doesn't have a message and
|
|
42
|
+
# is of no major consequences.
|
|
43
|
+
# We swallow that exception to avoid confusion.
|
|
44
|
+
raise exception unless exception.message.empty?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/ver/text.rb
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
module VER
|
|
2
|
+
class Text < Tk::Text
|
|
3
|
+
include Methods
|
|
4
|
+
|
|
5
|
+
MODE_CURSOR = {
|
|
6
|
+
:insert => {insertbackground: 'red', blockcursor: false},
|
|
7
|
+
:control => {insertbackground: 'green', blockcursor: true},
|
|
8
|
+
:select_char => {insertbackground: 'yellow', blockcursor: true},
|
|
9
|
+
:select_line => {insertbackground: 'yellow', blockcursor: true},
|
|
10
|
+
:select_block => {insertbackground: 'yellow', blockcursor: true},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
attr_accessor :keymap, :view, :status, :filename
|
|
14
|
+
|
|
15
|
+
def initialize(view, options = {})
|
|
16
|
+
super
|
|
17
|
+
self.view = view
|
|
18
|
+
|
|
19
|
+
keymap_name = VER.options.fetch(:keymap)
|
|
20
|
+
self.keymap = Keymap.get(name: keymap_name, receiver: self)
|
|
21
|
+
|
|
22
|
+
@selection_start = @highlight_thread = nil
|
|
23
|
+
@pristine = true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def short_filename
|
|
27
|
+
filename.sub(Dir.pwd + '/', '') if filename
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def open_path(path)
|
|
31
|
+
@filename = Pathname(path.to_s).expand_path
|
|
32
|
+
|
|
33
|
+
begin
|
|
34
|
+
self.value = @filename.read
|
|
35
|
+
status.message "Opened #{short_filename}"
|
|
36
|
+
rescue Errno::ENOENT
|
|
37
|
+
clear
|
|
38
|
+
status.message "Create #{short_filename}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
after_open
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def open_empty
|
|
45
|
+
clear
|
|
46
|
+
status.message "[No File]"
|
|
47
|
+
after_open
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def after_open
|
|
51
|
+
VER.opened_file(self)
|
|
52
|
+
|
|
53
|
+
edit_reset
|
|
54
|
+
|
|
55
|
+
if @pristine
|
|
56
|
+
Thread.new do
|
|
57
|
+
wait_visibility
|
|
58
|
+
setup_highlight
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
setup_highlight
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
focus
|
|
65
|
+
mark_set :insert, '0.0'
|
|
66
|
+
|
|
67
|
+
@pristine = false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def layout
|
|
71
|
+
view.layout
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def quit
|
|
75
|
+
Tk.exit
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def insert_index
|
|
79
|
+
index(:insert).split('.').map(&:to_i)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def end_index
|
|
83
|
+
index(:end).split('.').map(&:to_i)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# lines start from 1
|
|
87
|
+
# end is maximum lines + 1
|
|
88
|
+
def status_projection(into)
|
|
89
|
+
format = "%s %d,%d %d%% [%s]"
|
|
90
|
+
|
|
91
|
+
insert_y, insert_x = insert_index
|
|
92
|
+
end_y, end_x = end_index
|
|
93
|
+
|
|
94
|
+
percent = (100.0 / (end_y - 2)) * (insert_y - 1)
|
|
95
|
+
percent = 100.0 if percent.nan?
|
|
96
|
+
|
|
97
|
+
additional = [keymap.mode]
|
|
98
|
+
syntax_name = @syntax.name if @syntax
|
|
99
|
+
additional << syntax_name if syntax_name
|
|
100
|
+
|
|
101
|
+
values = [
|
|
102
|
+
short_filename,
|
|
103
|
+
insert_y, insert_x,
|
|
104
|
+
percent,
|
|
105
|
+
additional.join(' | '),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
into.value = format % values
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
TAG_ALL_MATCHING_OPTIONS = {
|
|
112
|
+
foreground: '#f00',
|
|
113
|
+
background: '#00f',
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
def tag_all_matching(name, regexp, options = {})
|
|
117
|
+
name = name.to_s
|
|
118
|
+
|
|
119
|
+
if tag_exists?(name)
|
|
120
|
+
tag_remove(name, '0.0', 'end')
|
|
121
|
+
else
|
|
122
|
+
options = TAG_ALL_MATCHING_OPTIONS.merge(options)
|
|
123
|
+
TktNamedTag.new(self, name, options)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
start = '0.0'
|
|
127
|
+
while result = search_with_length(regexp, start, 'end - 1 chars')
|
|
128
|
+
pos, len, match = result
|
|
129
|
+
break if !result || len == 0
|
|
130
|
+
|
|
131
|
+
start = "#{pos} + #{len} chars"
|
|
132
|
+
tag_add name, pos, start
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def tag_exists?(given_path)
|
|
137
|
+
list = tk_split_simplelist(tk_send_without_enc('tag', 'names', None), false, true)
|
|
138
|
+
list.include?(given_path)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Wrap Tk methods to behave as we want and to generate events
|
|
142
|
+
def mark_set(mark_name, index)
|
|
143
|
+
super
|
|
144
|
+
|
|
145
|
+
return unless mark_name == :insert
|
|
146
|
+
|
|
147
|
+
Tk.event_generate(self, '<Movement>')
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def refresh_selection
|
|
151
|
+
return unless start = @selection_start
|
|
152
|
+
|
|
153
|
+
now = index(:insert).split('.').map(&:to_i)
|
|
154
|
+
left, right = [start, now].sort.map{|pos| pos.join('.') }
|
|
155
|
+
tag_remove :sel, '0.0', 'end'
|
|
156
|
+
|
|
157
|
+
case keymap.mode
|
|
158
|
+
when :select_char
|
|
159
|
+
tag_add :sel, left, "#{right} + 1 chars"
|
|
160
|
+
when :select_line
|
|
161
|
+
tag_add :sel, "#{left} linestart", "#{right} lineend + 1 chars"
|
|
162
|
+
when :select_block
|
|
163
|
+
ly, lx = left.split('.').map(&:to_i)
|
|
164
|
+
ry, rx = right.split('.').map(&:to_i)
|
|
165
|
+
|
|
166
|
+
from_y, to_y = [ly, ry].sort
|
|
167
|
+
from_x, to_x = [lx, rx].sort
|
|
168
|
+
|
|
169
|
+
from_y.upto to_y do |y|
|
|
170
|
+
tag_add :sel, "#{y}.#{from_x}", "#{y}.#{to_x + 1}"
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# fix the ruby definition of delete, Tk allows more than 2 indices
|
|
176
|
+
def delete(*args)
|
|
177
|
+
if args.size > 2
|
|
178
|
+
deleted = args.each_slice(2).map{|left, right| get(left, right) }
|
|
179
|
+
else
|
|
180
|
+
deleted = get(*args)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
copy(deleted)
|
|
184
|
+
|
|
185
|
+
tk_send_without_enc('delete', *args)
|
|
186
|
+
|
|
187
|
+
touch!
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def insert(*args)
|
|
191
|
+
super
|
|
192
|
+
touch!
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def setup_highlight
|
|
196
|
+
return unless filename
|
|
197
|
+
return unless @syntax = Syntax.from_filename(filename)
|
|
198
|
+
|
|
199
|
+
@highlight_thread = create_highlight_thread
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def create_highlight_thread
|
|
203
|
+
@highlight_thread = Thread.new{
|
|
204
|
+
this = Thread.current
|
|
205
|
+
this[:pending] = 1
|
|
206
|
+
|
|
207
|
+
loop do
|
|
208
|
+
if this[:pending] > 0
|
|
209
|
+
while this[:pending] > 0
|
|
210
|
+
this[:pending] -= 1
|
|
211
|
+
sleep 0.2
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
refresh_highlight!
|
|
215
|
+
else
|
|
216
|
+
sleep 0.5
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
}
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def refresh_highlight(lineno = 0)
|
|
223
|
+
return unless @highlight_thread
|
|
224
|
+
@highlight_thread[:pending] += 1
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def caret(keys=nil)
|
|
228
|
+
if keys
|
|
229
|
+
tk_call_without_enc('tk', 'caret', self, *hash_kv(keys))
|
|
230
|
+
self
|
|
231
|
+
else
|
|
232
|
+
lst = tk_split_list(tk_call_without_enc('tk', 'caret', self))
|
|
233
|
+
info = {}
|
|
234
|
+
while key = lst.shift
|
|
235
|
+
info[key[1..-1]] = lst.shift
|
|
236
|
+
end
|
|
237
|
+
info
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def focus
|
|
242
|
+
super
|
|
243
|
+
Tk.event_generate(self, '<Focus>')
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def fast_tag_add(tag, *indices)
|
|
247
|
+
tk_send_without_enc('tag', 'add', _get_eval_enc_str(tag), *indices)
|
|
248
|
+
self
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def set_window_title
|
|
252
|
+
if filename
|
|
253
|
+
home = Pathname(ENV['HOME'])
|
|
254
|
+
dir, file = filename.split
|
|
255
|
+
dir_relative_to_home = dir.relative_path_from(home)
|
|
256
|
+
|
|
257
|
+
if dir_relative_to_home.to_s.start_with?('../')
|
|
258
|
+
title = "#{file} (#{dir}) - VER"
|
|
259
|
+
else
|
|
260
|
+
title = "#{file} (#{dir_relative_to_home}) - VER"
|
|
261
|
+
end
|
|
262
|
+
else
|
|
263
|
+
title = "[No Name] - VER"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
VER.root['title'] = title
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
private
|
|
270
|
+
|
|
271
|
+
def refresh_highlight!
|
|
272
|
+
tag_all_matching('trailing_whitespace', /[ \t]+$/, foreground: '#000', background: '#f00')
|
|
273
|
+
@syntax.highlight(self, value, lineno = 0)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def touch!
|
|
277
|
+
Tk.event_generate(self, '<Modified>')
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def copy(text)
|
|
281
|
+
if text.respond_to?(:to_str)
|
|
282
|
+
copy_string(text)
|
|
283
|
+
elsif text.respond_to?(:to_ary)
|
|
284
|
+
copy_array(text)
|
|
285
|
+
else
|
|
286
|
+
copy_fallback(text)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def copy_string(text)
|
|
291
|
+
TkClipboard.set(text = text.to_str)
|
|
292
|
+
|
|
293
|
+
copy_message text.count("\n"), text.size
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def copy_array(text)
|
|
297
|
+
TkClipboard.set(text, type: Array)
|
|
298
|
+
|
|
299
|
+
copy_message text.size, text.reduce(0){|s,v| s + v.size }
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def copy_fallback(text)
|
|
303
|
+
TkClipboard.set(text)
|
|
304
|
+
|
|
305
|
+
status.message "Copied unkown entity of class %p" % [text.class]
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def copy_message(lines, chars)
|
|
309
|
+
lines_desc = lines == 1 ? 'line' : 'lines'
|
|
310
|
+
chars_desc = chars == 1 ? 'character' : 'characters'
|
|
311
|
+
|
|
312
|
+
msg = "copied %d %s of %d %s" % [lines, lines_desc, chars, chars_desc]
|
|
313
|
+
status.message msg
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def paste_continous(text)
|
|
317
|
+
if text =~ /\n/
|
|
318
|
+
mark_set :insert, 'insert lineend'
|
|
319
|
+
insert :insert, "\n"
|
|
320
|
+
insert :insert, text.chomp
|
|
321
|
+
else
|
|
322
|
+
insert :insert, text
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def paste_tk_array(tk_array)
|
|
327
|
+
chunks = Tk.send(:simplelist, tk_array)
|
|
328
|
+
|
|
329
|
+
insert_y, insert_x = index(:insert).split('.').map(&:to_i)
|
|
330
|
+
|
|
331
|
+
chunks.each_with_index do |chunk, idx|
|
|
332
|
+
y = insert_y + idx
|
|
333
|
+
insert "#{y}.#{insert_x}", chunk
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def mode=(name)
|
|
338
|
+
keymap.mode = mode = name.to_sym
|
|
339
|
+
|
|
340
|
+
cursor = MODE_CURSOR[mode]
|
|
341
|
+
configure cursor
|
|
342
|
+
|
|
343
|
+
return unless status && color = cursor[:insertbackground]
|
|
344
|
+
style = status.style
|
|
345
|
+
Tk::Tile::Style.configure style, fieldbackground: color
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def load_theme(name)
|
|
349
|
+
return unless @syntax
|
|
350
|
+
return unless found = Theme.find(name)
|
|
351
|
+
|
|
352
|
+
@syntax.theme = Theme.load(found)
|
|
353
|
+
refresh_highlight
|
|
354
|
+
|
|
355
|
+
status.message "Theme #{found} loaded"
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def load_syntax(name)
|
|
359
|
+
return unless @syntax
|
|
360
|
+
return unless found = Syntax.find(name)
|
|
361
|
+
|
|
362
|
+
theme = @syntax.theme
|
|
363
|
+
@syntax = Syntax.new(name, theme)
|
|
364
|
+
refresh_highlight
|
|
365
|
+
|
|
366
|
+
status.message "Syntax #{found} loaded"
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def clear_selection
|
|
370
|
+
@selection_start = nil
|
|
371
|
+
tag_remove :sel, '0.0', 'end'
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|