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
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# ==================================================================
|
|
3
|
+
# Author: Jamis Buck (jamis@jamisbuck.org)
|
|
4
|
+
# Date: 2008-10-09
|
|
5
|
+
#
|
|
6
|
+
# This file is in the public domain. Usage, modification, and
|
|
7
|
+
# redistribution of this file are unrestricted.
|
|
8
|
+
# ==================================================================
|
|
9
|
+
#++
|
|
10
|
+
|
|
11
|
+
# The "fuzzy" file finder provides a way for searching a directory
|
|
12
|
+
# tree with only a partial name. This is similar to the "cmd-T"
|
|
13
|
+
# feature in TextMate (http://macromates.com).
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
#
|
|
17
|
+
# finder = FuzzyFileFinder.new
|
|
18
|
+
# finder.search("app/blogcon") do |match|
|
|
19
|
+
# puts match[:highlighted_path]
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# In the above example, all files matching "app/blogcon" will be
|
|
23
|
+
# yielded to the block. The given pattern is reduced to a regular
|
|
24
|
+
# expression internally, so that any file that contains those
|
|
25
|
+
# characters in that order (even if there are other characters
|
|
26
|
+
# in between) will match.
|
|
27
|
+
#
|
|
28
|
+
# In other words, "app/blogcon" would match any of the following
|
|
29
|
+
# (parenthesized strings indicate how the match was made):
|
|
30
|
+
#
|
|
31
|
+
# * (app)/controllers/(blog)_(con)troller.rb
|
|
32
|
+
# * lib/c(ap)_(p)ool/(bl)ue_(o)r_(g)reen_(co)loratio(n)
|
|
33
|
+
# * test/(app)/(blog)_(con)troller_test.rb
|
|
34
|
+
#
|
|
35
|
+
# And so forth.
|
|
36
|
+
class FuzzyFileFinder
|
|
37
|
+
module Version
|
|
38
|
+
MAJOR = 1
|
|
39
|
+
MINOR = 0
|
|
40
|
+
TINY = 2
|
|
41
|
+
STRING = [MAJOR, MINOR, TINY].join(".")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# This is the exception that is raised if you try to scan a
|
|
45
|
+
# directory tree with too many entries. By default, a ceiling of
|
|
46
|
+
# 10,000 entries is enforced, but you can change that number via
|
|
47
|
+
# the +ceiling+ parameter to FuzzyFileFinder.new.
|
|
48
|
+
class TooManyEntries < RuntimeError; end
|
|
49
|
+
|
|
50
|
+
# Used internally to represent a run of characters within a
|
|
51
|
+
# match. This is used to build the highlighted version of
|
|
52
|
+
# a file name.
|
|
53
|
+
class CharacterRun < Struct.new(:string, :inside) #:nodoc:
|
|
54
|
+
def to_s
|
|
55
|
+
if inside
|
|
56
|
+
"(#{string})"
|
|
57
|
+
else
|
|
58
|
+
string
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
alias to_str to_s
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Used internally to represent a file within the directory tree.
|
|
65
|
+
class FileSystemEntry #:nodoc:
|
|
66
|
+
attr_reader :parent
|
|
67
|
+
attr_reader :name
|
|
68
|
+
|
|
69
|
+
def initialize(parent, name)
|
|
70
|
+
@parent = parent
|
|
71
|
+
@name = name
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def path
|
|
75
|
+
File.join(parent.name, name)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Used internally to represent a subdirectory within the directory
|
|
80
|
+
# tree.
|
|
81
|
+
class Directory #:nodoc:
|
|
82
|
+
attr_reader :name
|
|
83
|
+
|
|
84
|
+
def initialize(name, is_root=false)
|
|
85
|
+
@name = name
|
|
86
|
+
@is_root = is_root
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def root?
|
|
90
|
+
is_root
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# The roots directory trees to search.
|
|
95
|
+
attr_reader :roots
|
|
96
|
+
|
|
97
|
+
# The list of files beneath all +roots+
|
|
98
|
+
attr_reader :files
|
|
99
|
+
|
|
100
|
+
# The maximum number of files beneath all +roots+
|
|
101
|
+
attr_reader :ceiling
|
|
102
|
+
|
|
103
|
+
# The prefix shared by all +roots+.
|
|
104
|
+
attr_reader :shared_prefix
|
|
105
|
+
|
|
106
|
+
# Initializes a new FuzzyFileFinder. This will scan the
|
|
107
|
+
# given +directories+, using +ceiling+ as the maximum number
|
|
108
|
+
# of entries to scan. If there are more than +ceiling+ entries
|
|
109
|
+
# a TooManyEntries exception will be raised.
|
|
110
|
+
def initialize(directories=['.'], ceiling=10_000)
|
|
111
|
+
directories = Array(directories)
|
|
112
|
+
directories << "." if directories.empty?
|
|
113
|
+
|
|
114
|
+
# expand any paths with ~
|
|
115
|
+
root_dirnames = directories.map { |d| File.expand_path(d) }.select { |d| File.directory?(d) }.uniq
|
|
116
|
+
|
|
117
|
+
@roots = root_dirnames.map { |d| Directory.new(d, true) }
|
|
118
|
+
@shared_prefix = determine_shared_prefix
|
|
119
|
+
@shared_prefix_re = Regexp.new("^#{Regexp.escape(shared_prefix)}" + (shared_prefix.empty? ? "" : "/"))
|
|
120
|
+
|
|
121
|
+
@files = []
|
|
122
|
+
@ceiling = ceiling
|
|
123
|
+
|
|
124
|
+
rescan!
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Rescans the subtree. If the directory contents every change,
|
|
128
|
+
# you'll need to call this to force the finder to be aware of
|
|
129
|
+
# the changes.
|
|
130
|
+
def rescan!
|
|
131
|
+
@files.clear
|
|
132
|
+
roots.each { |root| follow_tree(root) }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Takes the given +pattern+ (which must be a string) and searches
|
|
136
|
+
# all files beneath +root+, yielding each match.
|
|
137
|
+
#
|
|
138
|
+
# +pattern+ is interpreted thus:
|
|
139
|
+
#
|
|
140
|
+
# * "foo" : look for any file with the characters 'f', 'o', and 'o'
|
|
141
|
+
# in its basename (discounting directory names). The characters
|
|
142
|
+
# must be in that order.
|
|
143
|
+
# * "foo/bar" : look for any file with the characters 'b', 'a',
|
|
144
|
+
# and 'r' in its basename (discounting directory names). Also,
|
|
145
|
+
# any successful match must also have at least one directory
|
|
146
|
+
# element matching the characters 'f', 'o', and 'o' (in that
|
|
147
|
+
# order.
|
|
148
|
+
# * "foo/bar/baz" : same as "foo/bar", but matching two
|
|
149
|
+
# directory elements in addition to a file name of "baz".
|
|
150
|
+
#
|
|
151
|
+
# Each yielded match will be a hash containing the following keys:
|
|
152
|
+
#
|
|
153
|
+
# * :path refers to the full path to the file
|
|
154
|
+
# * :directory refers to the directory of the file
|
|
155
|
+
# * :name refers to the name of the file (without directory)
|
|
156
|
+
# * :highlighted_directory refers to the directory of the file with
|
|
157
|
+
# matches highlighted in parentheses.
|
|
158
|
+
# * :highlighted_name refers to the name of the file with matches
|
|
159
|
+
# highlighted in parentheses
|
|
160
|
+
# * :highlighted_path refers to the full path of the file with
|
|
161
|
+
# matches highlighted in parentheses
|
|
162
|
+
# * :abbr refers to an abbreviated form of :highlighted_path, where
|
|
163
|
+
# path segments without matches are compressed to just their first
|
|
164
|
+
# character.
|
|
165
|
+
# * :score refers to a value between 0 and 1 indicating how closely
|
|
166
|
+
# the file matches the given pattern. A score of 1 means the
|
|
167
|
+
# pattern matches the file exactly.
|
|
168
|
+
def search(pattern, &block)
|
|
169
|
+
pattern.strip!
|
|
170
|
+
path_parts = pattern.split("/")
|
|
171
|
+
path_parts.push "" if pattern[-1,1] == "/"
|
|
172
|
+
|
|
173
|
+
file_name_part = path_parts.pop || ""
|
|
174
|
+
|
|
175
|
+
if path_parts.any?
|
|
176
|
+
path_regex_raw = "^(.*?)" + path_parts.map { |part| make_pattern(part) }.join("(.*?/.*?)") + "(.*?)$"
|
|
177
|
+
path_regex = Regexp.new(path_regex_raw, Regexp::IGNORECASE)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
file_regex_raw = "^(.*?)" << make_pattern(file_name_part) << "(.*)$"
|
|
181
|
+
file_regex = Regexp.new(file_regex_raw, Regexp::IGNORECASE)
|
|
182
|
+
|
|
183
|
+
path_matches = {}
|
|
184
|
+
files.each do |file|
|
|
185
|
+
path_match = match_path(file.parent, path_matches, path_regex, path_parts.length)
|
|
186
|
+
next if path_match[:missed]
|
|
187
|
+
|
|
188
|
+
match_file(file, file_regex, path_match, &block)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Takes the given +pattern+ (which must be a string, formatted as
|
|
193
|
+
# described in #search), and returns up to +max+ matches in an
|
|
194
|
+
# Array. If +max+ is nil, all matches will be returned.
|
|
195
|
+
def find(pattern, max=nil)
|
|
196
|
+
results = []
|
|
197
|
+
search(pattern) do |match|
|
|
198
|
+
results << match
|
|
199
|
+
break if max && results.length >= max
|
|
200
|
+
end
|
|
201
|
+
return results
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Displays the finder object in a sane, non-explosive manner.
|
|
205
|
+
def inspect #:nodoc:
|
|
206
|
+
"#<%s:0x%x roots=%s, files=%d>" % [self.class.name, object_id, roots.map { |r| r.name.inspect }.join(", "), files.length]
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
private
|
|
210
|
+
|
|
211
|
+
# Recursively scans +directory+ and all files and subdirectories
|
|
212
|
+
# beneath it, depth-first.
|
|
213
|
+
def follow_tree(directory)
|
|
214
|
+
Dir.entries(directory.name).each do |entry|
|
|
215
|
+
next if entry[0,1] == "."
|
|
216
|
+
raise TooManyEntries if files.length > ceiling
|
|
217
|
+
|
|
218
|
+
full = File.join(directory.name, entry)
|
|
219
|
+
|
|
220
|
+
if File.directory?(full)
|
|
221
|
+
follow_tree(Directory.new(full))
|
|
222
|
+
else
|
|
223
|
+
files.push(FileSystemEntry.new(directory, entry))
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Takes the given pattern string "foo" and converts it to a new
|
|
229
|
+
# string "(f)([^/]*?)(o)([^/]*?)(o)" that can be used to create
|
|
230
|
+
# a regular expression.
|
|
231
|
+
def make_pattern(pattern)
|
|
232
|
+
pattern = pattern.split(//)
|
|
233
|
+
pattern << "" if pattern.empty?
|
|
234
|
+
|
|
235
|
+
pattern.inject("") do |regex, character|
|
|
236
|
+
regex << "([^/]*?)" if regex.length > 0
|
|
237
|
+
regex << "(" << Regexp.escape(character) << ")"
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Given a MatchData object +match+ and a number of "inside"
|
|
242
|
+
# segments to support, compute both the match score and the
|
|
243
|
+
# highlighted match string. The "inside segments" refers to how
|
|
244
|
+
# many patterns were matched in this one match. For a file name,
|
|
245
|
+
# this will always be one. For directories, it will be one for
|
|
246
|
+
# each directory segment in the original pattern.
|
|
247
|
+
def build_match_result(match, inside_segments)
|
|
248
|
+
runs = []
|
|
249
|
+
inside_chars = total_chars = 0
|
|
250
|
+
match.captures.each_with_index do |capture, index|
|
|
251
|
+
if capture.length > 0
|
|
252
|
+
# odd-numbered captures are matches inside the pattern.
|
|
253
|
+
# even-numbered captures are matches between the pattern's elements.
|
|
254
|
+
inside = index % 2 != 0
|
|
255
|
+
|
|
256
|
+
total_chars += capture.gsub(%r(/), "").length # ignore '/' delimiters
|
|
257
|
+
inside_chars += capture.length if inside
|
|
258
|
+
|
|
259
|
+
if runs.last && runs.last.inside == inside
|
|
260
|
+
runs.last.string << capture
|
|
261
|
+
else
|
|
262
|
+
runs << CharacterRun.new(capture, inside)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Determine the score of this match.
|
|
268
|
+
# 1. fewer "inside runs" (runs corresponding to the original pattern)
|
|
269
|
+
# is better.
|
|
270
|
+
# 2. better coverage of the actual path name is better
|
|
271
|
+
|
|
272
|
+
inside_runs = runs.select { |r| r.inside }
|
|
273
|
+
run_ratio = inside_runs.length.zero? ? 1 : inside_segments / inside_runs.length.to_f
|
|
274
|
+
|
|
275
|
+
char_ratio = total_chars.zero? ? 1 : inside_chars.to_f / total_chars
|
|
276
|
+
|
|
277
|
+
score = run_ratio * char_ratio
|
|
278
|
+
|
|
279
|
+
return { :score => score, :result => runs.join }
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Match the given path against the regex, caching the result in +path_matches+.
|
|
283
|
+
# If +path+ is already cached in the path_matches cache, just return the cached
|
|
284
|
+
# value.
|
|
285
|
+
def match_path(path, path_matches, path_regex, path_segments)
|
|
286
|
+
return path_matches[path] if path_matches.key?(path)
|
|
287
|
+
|
|
288
|
+
matchable_name = path.name.sub(@shared_prefix_re, "")
|
|
289
|
+
|
|
290
|
+
if path_regex
|
|
291
|
+
match = matchable_name.match(path_regex)
|
|
292
|
+
|
|
293
|
+
path_matches[path] =
|
|
294
|
+
match && build_match_result(match, path_segments) ||
|
|
295
|
+
{ :score => 1, :result => matchable_name, :missed => true }
|
|
296
|
+
else
|
|
297
|
+
path_matches[path] = { :score => 1, :result => matchable_name }
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Match +file+ against +file_regex+. If it matches, yield the match
|
|
302
|
+
# metadata to the block.
|
|
303
|
+
def match_file(file, file_regex, path_match, &block)
|
|
304
|
+
if file_match = file.name.match(file_regex)
|
|
305
|
+
match_result = build_match_result(file_match, 1)
|
|
306
|
+
full_match_result = File.join(path_match[:result], match_result[:result])
|
|
307
|
+
abbr = File.join(path_match[:result].gsub(/[^\/]+/) { |m| m.index("(") ? m : m[0,1] }, match_result[:result])
|
|
308
|
+
|
|
309
|
+
result = { :path => file.path,
|
|
310
|
+
:abbr => abbr,
|
|
311
|
+
:directory => file.parent.name,
|
|
312
|
+
:name => file.name,
|
|
313
|
+
:highlighted_directory => path_match[:result],
|
|
314
|
+
:highlighted_name => match_result[:result],
|
|
315
|
+
:highlighted_path => full_match_result,
|
|
316
|
+
:score => path_match[:score] * match_result[:score] }
|
|
317
|
+
yield result
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def determine_shared_prefix
|
|
322
|
+
# the common case: if there is only a single root, then the entire
|
|
323
|
+
# name of the root is the shared prefix.
|
|
324
|
+
return roots.first.name if roots.length == 1
|
|
325
|
+
|
|
326
|
+
split_roots = roots.map { |root| root.name.split(%r{/}) }
|
|
327
|
+
segments = split_roots.map { |root| root.length }.max
|
|
328
|
+
master = split_roots.pop
|
|
329
|
+
|
|
330
|
+
segments.times do |segment|
|
|
331
|
+
if !split_roots.all? { |root| root[segment] == master[segment] }
|
|
332
|
+
return master[0,segment].join("/")
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# shouldn't ever get here, since we uniq the root list before
|
|
337
|
+
# calling this method, but if we do, somehow...
|
|
338
|
+
return roots.first.name
|
|
339
|
+
end
|
|
340
|
+
end
|
data/lib/ver/view.rb
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
module VER
|
|
2
|
+
class View < TkFrame
|
|
3
|
+
autoload :Entry, 'ver/view/entry'
|
|
4
|
+
autoload :List, 'ver/view/list'
|
|
5
|
+
|
|
6
|
+
attr_reader :layout, :text, :status
|
|
7
|
+
|
|
8
|
+
def initialize(layout, options = {})
|
|
9
|
+
super
|
|
10
|
+
@layout = layout
|
|
11
|
+
@text = @status = @ybar = @xbar = nil
|
|
12
|
+
setup
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# +-------+---+
|
|
16
|
+
# | | @ |
|
|
17
|
+
# | | y |
|
|
18
|
+
# | @text | b |
|
|
19
|
+
# | | a |
|
|
20
|
+
# | | r |
|
|
21
|
+
# +-------+---+
|
|
22
|
+
# | @xbar | |
|
|
23
|
+
# +-------+---+
|
|
24
|
+
# | @status |
|
|
25
|
+
# +-----------+
|
|
26
|
+
def setup
|
|
27
|
+
setup_text
|
|
28
|
+
# setup_scrollbars # enable if you really want some.
|
|
29
|
+
setup_status
|
|
30
|
+
setup_grid
|
|
31
|
+
setup_misc
|
|
32
|
+
setup_events
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def setup_text
|
|
36
|
+
font = VER.options[:font]
|
|
37
|
+
tab_width = font.measure('0') * 2
|
|
38
|
+
|
|
39
|
+
@text = VER::Text.new(
|
|
40
|
+
self,
|
|
41
|
+
autoseparators: true, # insert separators into the undo flow
|
|
42
|
+
borderwidth: 0,
|
|
43
|
+
exportselection: true, # copy into X11 buffer automatically
|
|
44
|
+
insertbackground: '#0f0', # initial value (hardcoded for control mode)
|
|
45
|
+
font: font,
|
|
46
|
+
insertofftime: 0, # blinking cursor be gone!
|
|
47
|
+
setgrid: true, # tell the wm that this is a griddy window
|
|
48
|
+
tabs: tab_width,
|
|
49
|
+
tabstyle: :wordprocessor,
|
|
50
|
+
undo: true, # enable undo capabilities
|
|
51
|
+
wrap: :word
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def setup_scrollbars
|
|
56
|
+
# vertical scrollbar
|
|
57
|
+
@ybar = Ttk::Scrollbar.new(self)
|
|
58
|
+
@text.yscrollbar(@ybar)
|
|
59
|
+
|
|
60
|
+
# horizontal scrollbar
|
|
61
|
+
@xbar = Ttk::Scrollbar.new(self)
|
|
62
|
+
@text.xscrollbar(@xbar)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def setup_status
|
|
66
|
+
@status = Status.new(self, font: VER.options[:font], takefocus: 0)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def setup_grid
|
|
70
|
+
TkGrid.grid @text, row: 0, column: 0, sticky: :nsew if @text
|
|
71
|
+
TkGrid.grid @ybar, row: 0, column: 1, sticky: :ns if @ybar
|
|
72
|
+
TkGrid.grid @xbar, row: 1, column: 0, sticky: :ew if @xbar
|
|
73
|
+
TkGrid.grid @status, row: 2, column: 0, sticky: :ew, columnspan: 2 if @status
|
|
74
|
+
|
|
75
|
+
TkGrid.columnconfigure self, 0, weight: 1
|
|
76
|
+
TkGrid.columnconfigure self, 1, weight: 0
|
|
77
|
+
TkGrid.rowconfigure self, 0, weight: 1
|
|
78
|
+
TkGrid.rowconfigure self, 1, weight: 0
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def setup_misc
|
|
82
|
+
@text.status = @status
|
|
83
|
+
@text.view = self
|
|
84
|
+
@text.start_control_mode
|
|
85
|
+
@status.mode = :status_query
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def setup_events
|
|
89
|
+
%w[Movement Modified Focus].each do |name|
|
|
90
|
+
@text.bind("<#{name}>"){|event| __send__("on_#{name.downcase}", event) }
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def open_path(path)
|
|
95
|
+
@text.open_path(path)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def open_empty
|
|
99
|
+
@text.open_empty
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# handling events
|
|
103
|
+
|
|
104
|
+
def on_movement(event)
|
|
105
|
+
@text.see :insert
|
|
106
|
+
@text.refresh_selection
|
|
107
|
+
@text.status_projection(@status)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def on_modified(event)
|
|
111
|
+
@text.see :insert
|
|
112
|
+
@text.refresh_highlight
|
|
113
|
+
@text.status_projection(@status)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def on_focus(event)
|
|
117
|
+
@text.set_window_title
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @text.bind '<Modified>', proc{|e| refresh; p :modified }
|
|
121
|
+
# @text.bind '<Undo>', proc{|e| refresh; p :undo }
|
|
122
|
+
# @text.bind '<Redo>', proc{|e| refresh; p :redo }
|
|
123
|
+
# @text.bind '<Copy>', proc{|e| p :copy }
|
|
124
|
+
# @text.bind '<Cut>', proc{|e| refresh; p :cut }
|
|
125
|
+
# @text.bind '<Paste>', proc{|e| refresh; p :paste }
|
|
126
|
+
# @text.bind '<PasteSelection>', proc{|e| refresh; p :paste_selection }
|
|
127
|
+
# @text.bind '<Movement>', proc{|e| p :movement }
|
|
128
|
+
|
|
129
|
+
def focus
|
|
130
|
+
text.focus
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def create
|
|
134
|
+
layout.create_view do |view|
|
|
135
|
+
view.open_empty
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def close
|
|
140
|
+
layout.close_view(self)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def focus_next
|
|
144
|
+
layout.focus_next(self)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def focus_prev
|
|
148
|
+
layout.focus_prev(self)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def destroy
|
|
152
|
+
[@text, @ybar, @xbar, @status].each do |widget|
|
|
153
|
+
widget.destroy if widget
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
super
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def filename
|
|
160
|
+
text.filename
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|