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.
Files changed (188) hide show
  1. data/CHANGELOG +1404 -0
  2. data/MANIFEST +187 -0
  3. data/Rakefile +30 -0
  4. data/TODO +169 -0
  5. data/bin/ver +19 -0
  6. data/config/detect.rb +109 -0
  7. data/config/keymap/vim.rb +239 -0
  8. data/config/rc.rb +0 -0
  9. data/config/scratch +3 -0
  10. data/config/syntax/ANTLR.json +275 -0
  11. data/config/syntax/ASP VB.net.json +228 -0
  12. data/config/syntax/ASP.json +135 -0
  13. data/config/syntax/ActionScript.json +168 -0
  14. data/config/syntax/Ada.json +108 -0
  15. data/config/syntax/Apache.json +342 -0
  16. data/config/syntax/AppleScript.json +721 -0
  17. data/config/syntax/Bibtex.json +253 -0
  18. data/config/syntax/Blog (HTML).json +47 -0
  19. data/config/syntax/Blog (Markdown).json +50 -0
  20. data/config/syntax/Blog (Text).json +45 -0
  21. data/config/syntax/Blog (Textile).json +45 -0
  22. data/config/syntax/Bulletin Board.json +428 -0
  23. data/config/syntax/C++.json +323 -0
  24. data/config/syntax/C.json +694 -0
  25. data/config/syntax/CSS.json +346 -0
  26. data/config/syntax/DOT.json +79 -0
  27. data/config/syntax/Diff.json +136 -0
  28. data/config/syntax/Dylan.json +104 -0
  29. data/config/syntax/Eiffel.json +128 -0
  30. data/config/syntax/Erlang.json +1636 -0
  31. data/config/syntax/F-Script.json +137 -0
  32. data/config/syntax/FXScript.json +260 -0
  33. data/config/syntax/Gri.json +141 -0
  34. data/config/syntax/HTML (Mason).json +207 -0
  35. data/config/syntax/HTML (Rails).json +52 -0
  36. data/config/syntax/HTML (Tcl).json +42 -0
  37. data/config/syntax/HTML for ASP.net.json +736 -0
  38. data/config/syntax/HTML-ASP.json +45 -0
  39. data/config/syntax/HTML.json +614 -0
  40. data/config/syntax/Haskell.json +407 -0
  41. data/config/syntax/Inform.json +80 -0
  42. data/config/syntax/Ini.json +91 -0
  43. data/config/syntax/Io.json +142 -0
  44. data/config/syntax/Java.json +745 -0
  45. data/config/syntax/JavaProperties.json +42 -0
  46. data/config/syntax/JavaScript.json +446 -0
  47. data/config/syntax/LaTeX Beamer.json +65 -0
  48. data/config/syntax/LaTeX Log.json +88 -0
  49. data/config/syntax/LaTeX.json +962 -0
  50. data/config/syntax/Lighttpd.json +93 -0
  51. data/config/syntax/Lisp.json +101 -0
  52. data/config/syntax/Literate Haskell.json +55 -0
  53. data/config/syntax/Logtalk.json +289 -0
  54. data/config/syntax/Lua.json +146 -0
  55. data/config/syntax/M.json +744 -0
  56. data/config/syntax/MEL.json +161 -0
  57. data/config/syntax/MIPS.json +114 -0
  58. data/config/syntax/Mail.json +224 -0
  59. data/config/syntax/Makefile.json +66 -0
  60. data/config/syntax/Markdown.json +644 -0
  61. data/config/syntax/Modula-3.json +80 -0
  62. data/config/syntax/Movable Type.json +348 -0
  63. data/config/syntax/OCaml.json +1391 -0
  64. data/config/syntax/Objective-C++.json +21 -0
  65. data/config/syntax/OpenGL.json +24 -0
  66. data/config/syntax/PHP.json +2184 -0
  67. data/config/syntax/Pascal.json +128 -0
  68. data/config/syntax/Perl.json +2091 -0
  69. data/config/syntax/Plain text.json +49 -0
  70. data/config/syntax/Processing.json +188 -0
  71. data/config/syntax/Quake3 Config.json +54 -0
  72. data/config/syntax/R.json +157 -0
  73. data/config/syntax/Rez.json +137 -0
  74. data/config/syntax/Ruby on Rails.json +170 -0
  75. data/config/syntax/Ruby.json +1753 -0
  76. data/config/syntax/SQL (Rails).json +31 -0
  77. data/config/syntax/SQL.json +435 -0
  78. data/config/syntax/SWIG.json +96 -0
  79. data/config/syntax/Scheme.json +359 -0
  80. data/config/syntax/Shell-Unix-Generic.json +1198 -0
  81. data/config/syntax/Slate.json +265 -0
  82. data/config/syntax/Smarty.json +110 -0
  83. data/config/syntax/Standard ML.json +322 -0
  84. data/config/syntax/Subversion commit message.json +62 -0
  85. data/config/syntax/Tcl.json +278 -0
  86. data/config/syntax/TeX Math.json +83 -0
  87. data/config/syntax/TeX.json +157 -0
  88. data/config/syntax/Textile.json +273 -0
  89. data/config/syntax/Twiki.json +436 -0
  90. data/config/syntax/Vectorscript.json +97 -0
  91. data/config/syntax/XML strict.json +148 -0
  92. data/config/syntax/XML.json +301 -0
  93. data/config/syntax/XSL.json +96 -0
  94. data/config/syntax/YAML.json +293 -0
  95. data/config/syntax/iCalendar.json +51 -0
  96. data/config/syntax/reStructuredText.json +403 -0
  97. data/config/theme/Active4D.json +260 -0
  98. data/config/theme/All Hallow's Eve.json +171 -0
  99. data/config/theme/Amy.json +359 -0
  100. data/config/theme/BBEdit.json +269 -0
  101. data/config/theme/Bespin.json +322 -0
  102. data/config/theme/Blackboard.json +215 -0
  103. data/config/theme/BoysAndGirls01.json +156 -0
  104. data/config/theme/Brilliance Black.json +1695 -0
  105. data/config/theme/Brilliance Dull.json +1451 -0
  106. data/config/theme/Classic Modified.json +288 -0
  107. data/config/theme/Cobalt.json +345 -0
  108. data/config/theme/Cool Glow.json +215 -0
  109. data/config/theme/Dawn.json +258 -0
  110. data/config/theme/Eiffel.json +270 -0
  111. data/config/theme/Espresso Libre.json +247 -0
  112. data/config/theme/Fluidvision.json +272 -0
  113. data/config/theme/IDLE.json +159 -0
  114. data/config/theme/LAZY.json +178 -0
  115. data/config/theme/Mac Classic.json +277 -0
  116. data/config/theme/MagicWB (Amiga).json +231 -0
  117. data/config/theme/Merbivore Soft.json +181 -0
  118. data/config/theme/Merbivore.json +181 -0
  119. data/config/theme/Monokai.json +177 -0
  120. data/config/theme/Notepad2.json +166 -0
  121. data/config/theme/Pastels on Dark.json +437 -0
  122. data/config/theme/RubyBlue.json +226 -0
  123. data/config/theme/Sin City 2.json +361 -0
  124. data/config/theme/Slate.json +270 -0
  125. data/config/theme/Slush & Poppies.json +232 -0
  126. data/config/theme/SpaceCadet.json +143 -0
  127. data/config/theme/Sunburst.json +415 -0
  128. data/config/theme/Twilight BG FG.json +633 -0
  129. data/config/theme/Twilight.json +321 -0
  130. data/config/theme/Whys Poignant.json +119 -0
  131. data/config/theme/Zenburnesque.json +237 -0
  132. data/config/theme/barf.json +155 -0
  133. data/config/theme/fake.json +418 -0
  134. data/config/theme/happydeluxe.json +114 -0
  135. data/config/theme/iLife 05.json +393 -0
  136. data/config/theme/iPlastic.json +177 -0
  137. data/config/theme/mintBlue Dark.json +414 -0
  138. data/config/theme/mintBlue.json +415 -0
  139. data/config/theme/monoindustrial.json +276 -0
  140. data/config/theme/starlight.json +67 -0
  141. data/config/tutorial +74 -0
  142. data/config/welcome +115 -0
  143. data/help/index.verh +14 -0
  144. data/lib/ver.rb +156 -0
  145. data/lib/ver/entry.rb +97 -0
  146. data/lib/ver/keymap.rb +96 -0
  147. data/lib/ver/layout.rb +107 -0
  148. data/lib/ver/methods.rb +19 -0
  149. data/lib/ver/methods/completion.rb +116 -0
  150. data/lib/ver/methods/control.rb +340 -0
  151. data/lib/ver/methods/insert.rb +6 -0
  152. data/lib/ver/methods/move.rb +65 -0
  153. data/lib/ver/methods/search.rb +33 -0
  154. data/lib/ver/methods/select.rb +145 -0
  155. data/lib/ver/methods/views.rb +21 -0
  156. data/lib/ver/mode.rb +160 -0
  157. data/lib/ver/options.rb +207 -0
  158. data/lib/ver/plist.rb +106 -0
  159. data/lib/ver/status.rb +67 -0
  160. data/lib/ver/syntax.rb +68 -0
  161. data/lib/ver/syntax/detector.rb +53 -0
  162. data/lib/ver/syntax/processor.rb +48 -0
  163. data/lib/ver/text.rb +374 -0
  164. data/lib/ver/textpow.rb +357 -0
  165. data/lib/ver/theme.rb +162 -0
  166. data/lib/ver/vendor/fuzzy_file_finder.rb +340 -0
  167. data/lib/ver/view.rb +163 -0
  168. data/lib/ver/view/entry.rb +28 -0
  169. data/lib/ver/view/list.rb +137 -0
  170. data/lib/ver/view/list/buffer.rb +27 -0
  171. data/lib/ver/view/list/fuzzy_file_finder.rb +44 -0
  172. data/lib/ver/view/list/syntax.rb +13 -0
  173. data/lib/ver/view/list/theme.rb +13 -0
  174. data/spec/keymap.rb +224 -0
  175. data/tasks/bacon.rake +49 -0
  176. data/tasks/changelog.rake +18 -0
  177. data/tasks/gem.rake +22 -0
  178. data/tasks/gem_installer.rake +76 -0
  179. data/tasks/grancher.rake +12 -0
  180. data/tasks/install_dependencies.rake +6 -0
  181. data/tasks/manifest.rake +4 -0
  182. data/tasks/plist2json.rake +35 -0
  183. data/tasks/rcov.rake +18 -0
  184. data/tasks/release.rake +12 -0
  185. data/tasks/reversion.rake +8 -0
  186. data/tasks/syntax_list.rake +31 -0
  187. data/ver.gemspec +29 -0
  188. 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