ver-command_t 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ // Copyright 2010 Wincent Colaiuta. All rights reserved.
2
+ //
3
+ // Redistribution and use in source and binary forms, with or without
4
+ // modification, are permitted provided that the following conditions are met:
5
+ //
6
+ // 1. Redistributions of source code must retain the above copyright notice,
7
+ // this list of conditions and the following disclaimer.
8
+ // 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ // this list of conditions and the following disclaimer in the documentation
10
+ // and/or other materials provided with the distribution.
11
+ //
12
+ // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
13
+ // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
16
+ // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17
+ // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
18
+ // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
19
+ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
20
+ // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
21
+ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
+ // POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ #include <ruby.h>
25
+
26
+ // for compatibility with older versions of Ruby which don't declare RSTRING_PTR
27
+ #ifndef RSTRING_PTR
28
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
29
+ #endif
30
+
31
+ // for compatibility with older versions of Ruby which don't declare RSTRING_LEN
32
+ #ifndef RSTRING_LEN
33
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
34
+ #endif
35
+
36
+ // for compatibility with older versions of Ruby which don't declare RARRAY_PTR
37
+ #ifndef RARRAY_PTR
38
+ #define RARRAY_PTR(a) (RARRAY(a)->ptr)
39
+ #endif
40
+
41
+ // for compatibility with older versions of Ruby which don't declare RARRAY_LEN
42
+ #ifndef RARRAY_LEN
43
+ #define RARRAY_LEN(a) (RARRAY(a)->len)
44
+ #endif
45
+
46
+ // for compatibility with older versions of Ruby which don't declare RFLOAT_VALUE
47
+ #ifndef RFLOAT_VALUE
48
+ #define RFLOAT_VALUE(f) (RFLOAT(f)->value)
49
+ #endif
@@ -0,0 +1,34 @@
1
+ require 'command_t/finder'
2
+ class VER::Executor::ExCommandT < VER::Executor::Entry
3
+ def pwd
4
+ @pwd || Dir.pwd
5
+ end
6
+ attr_writer :pwd
7
+
8
+ def options
9
+ @options || {}
10
+ end
11
+ attr_writer :options
12
+
13
+ def finder
14
+ @finder ||= CommandT::Finder.new(pwd, options)
15
+ end
16
+
17
+ def choices(text)
18
+ finder.sorted_matches_for(text, options)
19
+ end
20
+
21
+ def action(selected)
22
+ if selected
23
+ throw :invalid if File.directory? path
24
+ VER.find_or_create_buffer(path)
25
+ else
26
+ tree.children.each do |child|
27
+ path = Array(child).first # if more is added later
28
+ next if File.directory? path
29
+ VER.find_or_create_buffer path
30
+ end
31
+ end
32
+ callback.destroy(false)
33
+ end
34
+ end
@@ -0,0 +1,51 @@
1
+ # Copyright 2010 Wincent Colaiuta. All rights reserved.
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice,
7
+ # this list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ #
12
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
13
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
16
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
18
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
19
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
20
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
21
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
+ # POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ require 'command_t/ext' # CommandT::Matcher
25
+ require 'command_t/scanner'
26
+
27
+ module CommandT
28
+ # Encapsulates a Scanner instance (which builds up a list of available files
29
+ # in a directory) and a Matcher instance (which selects from that list based
30
+ # on a search string).
31
+ class Finder
32
+ def initialize path = Dir.pwd, options = {}
33
+ @scanner = Scanner.new path, options
34
+ @matcher = Matcher.new @scanner, options
35
+ end
36
+
37
+ # Options:
38
+ # :limit (integer): limit the number of returned matches
39
+ def sorted_matches_for str, options = {}
40
+ @matcher.sorted_matches_for str, options
41
+ end
42
+
43
+ def flush
44
+ @scanner.flush
45
+ end
46
+
47
+ def path= path
48
+ @scanner.path = path
49
+ end
50
+ end # class Finder
51
+ end # CommandT
@@ -0,0 +1,89 @@
1
+ # Copyright 2010 Wincent Colaiuta. All rights reserved.
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice,
7
+ # this list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ #
12
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
13
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
16
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
18
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
19
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
20
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
21
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
+ # POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ module CommandT
25
+ # Reads the current directory recursively for the paths to all regular files.
26
+ class Scanner
27
+ class FileLimitExceeded < ::RuntimeError; end
28
+
29
+ def initialize path = Dir.pwd, options = {}
30
+ @path = path
31
+ @max_depth = options[:max_depth] || 15
32
+ @max_files = options[:max_files] || 10_000
33
+ @scan_dot_directories = options[:scan_dot_directories] || false
34
+ end
35
+
36
+ def paths
37
+ return @paths unless @paths.nil?
38
+ begin
39
+ @paths = []
40
+ @depth = 0
41
+ @files = 0
42
+ @prefix_len = @path.chomp('/').length
43
+ add_paths_for_directory @path, @paths
44
+ rescue FileLimitExceeded
45
+ end
46
+ @paths
47
+ end
48
+
49
+ def flush
50
+ @paths = nil
51
+ end
52
+
53
+ def path= str
54
+ if @path != str
55
+ @path = str
56
+ flush
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def path_excluded? path
63
+ path = path[(@prefix_len + 1)..-1]
64
+ false # until we find something
65
+ end
66
+
67
+ def add_paths_for_directory dir, accumulator
68
+ Dir.foreach(dir) do |entry|
69
+ next if ['.', '..'].include?(entry)
70
+ path = File.join(dir, entry)
71
+ unless path_excluded?(path)
72
+ if File.file?(path)
73
+ @files += 1
74
+ raise FileLimitExceeded if @files > @max_files
75
+ accumulator << path[@prefix_len + 1..-1]
76
+ elsif File.directory?(path)
77
+ next if @depth >= @max_depth
78
+ next if (entry.match(/\A\./) && !@scan_dot_directories)
79
+ @depth += 1
80
+ add_paths_for_directory path, accumulator
81
+ @depth -= 1
82
+ end
83
+ end
84
+ end
85
+ rescue Errno::EACCES
86
+ # skip over directories for which we don't have access
87
+ end
88
+ end # class Scanner
89
+ end # module CommandT
@@ -0,0 +1,75 @@
1
+ # Copyright 2010 Wincent Colaiuta. All rights reserved.
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice,
7
+ # this list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ #
12
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
13
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
16
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
18
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
19
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
20
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
21
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
+ # POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ module CommandT
25
+ # Convenience class for saving and restoring global settings.
26
+ class Settings
27
+ def initialize
28
+ save
29
+ end
30
+
31
+ def save
32
+ @timeoutlen = get_number 'timeoutlen'
33
+ @report = get_number 'report'
34
+ @sidescroll = get_number 'sidescroll'
35
+ @sidescrolloff = get_number 'sidescrolloff'
36
+ @equalalways = get_bool 'equalalways'
37
+ @hlsearch = get_bool 'hlsearch'
38
+ @insertmode = get_bool 'insertmode'
39
+ @showcmd = get_bool 'showcmd'
40
+ end
41
+
42
+ def restore
43
+ set_number 'timeoutlen', @timeoutlen
44
+ set_number 'report', @report
45
+ set_number 'sidescroll', @sidescroll
46
+ set_number 'sidescrolloff', @sidescrolloff
47
+ set_bool 'equalalways', @equalalways
48
+ set_bool 'hlsearch', @hlsearch
49
+ set_bool 'insertmode', @insertmode
50
+ set_bool 'showcmd', @showcmd
51
+ end
52
+
53
+ private
54
+
55
+ def get_number setting
56
+ ::VIM::evaluate("&#{setting}").to_i
57
+ end
58
+
59
+ def get_bool setting
60
+ ::VIM::evaluate("&#{setting}").to_i == 1
61
+ end
62
+
63
+ def set_number setting, value
64
+ ::VIM::set_option "#{setting}=#{value}"
65
+ end
66
+
67
+ def set_bool setting, value
68
+ if value
69
+ ::VIM::set_option setting
70
+ else
71
+ ::VIM::set_option "no#{setting}"
72
+ end
73
+ end
74
+ end # class Settings
75
+ end # module CommandT
@@ -0,0 +1,16 @@
1
+ # All the ver integration stuff is in here.
2
+ require 'ver'
3
+ require 'command_t/ex/command_t'
4
+
5
+ VER::Executor::ExLabel::COMPLETERS.merge!(
6
+ 'command_t' => :ExCommandT
7
+ )
8
+
9
+ VER::startup_hook do
10
+ module VER
11
+ minor_mode :open do
12
+ handler Methods::Control
13
+ map [:ex, :command_t], '<Control-t>'
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,72 @@
1
+ # Copyright 2010 Wincent Colaiuta. All rights reserved.
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice,
7
+ # this list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ #
12
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
13
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
16
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
18
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
19
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
20
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
21
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
+ # POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
25
+ require 'command_t/finder'
26
+
27
+ describe CommandT::Finder do
28
+ before :all do
29
+ @finder = CommandT::Finder.new File.join(File.dirname(__FILE__), '..', 'fixtures')
30
+ @all_fixtures = %w(
31
+ bar/abc
32
+ bar/xyz
33
+ baz
34
+ bing
35
+ foo/alpha/t1
36
+ foo/alpha/t2
37
+ foo/beta
38
+ )
39
+ end
40
+
41
+ describe 'sorted_matches_for method' do
42
+ it 'should return an empty array when no matches' do
43
+ @finder.sorted_matches_for('kung foo fighting').should == []
44
+ end
45
+
46
+ it 'should return all files when query string is empty' do
47
+ @finder.sorted_matches_for('').should == @all_fixtures
48
+ end
49
+
50
+ it 'should return files in alphabetical order when query string is empty' do
51
+ results = @finder.sorted_matches_for('')
52
+ results.should == results.sort
53
+ end
54
+
55
+ it 'should return matching files in score order' do
56
+ @finder.sorted_matches_for('ba').
57
+ should == %w(baz bar/abc bar/xyz foo/beta)
58
+ @finder.sorted_matches_for('a').
59
+ should == %w(baz bar/abc bar/xyz foo/alpha/t1 foo/alpha/t2 foo/beta)
60
+ end
61
+
62
+ it 'should obey the :limit option for empty search strings' do
63
+ @finder.sorted_matches_for('', :limit => 2).
64
+ should == %w(bar/abc bar/xyz)
65
+ end
66
+
67
+ it 'should obey the :limit option for non-empty search strings' do
68
+ @finder.sorted_matches_for('a', :limit => 3).
69
+ should == %w(baz bar/abc bar/xyz)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,236 @@
1
+ # Copyright 2010 Wincent Colaiuta. All rights reserved.
2
+ #
3
+ # Redistribution and use in source and binary forms, with or without
4
+ # modification, are permitted provided that the following conditions are met:
5
+ #
6
+ # 1. Redistributions of source code must retain the above copyright notice,
7
+ # this list of conditions and the following disclaimer.
8
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
9
+ # this list of conditions and the following disclaimer in the documentation
10
+ # and/or other materials provided with the distribution.
11
+ #
12
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
13
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
16
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
18
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
19
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
20
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
21
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
+ # POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
25
+ require 'command_t/ext'
26
+
27
+ describe CommandT::Match do
28
+ def match_for path, pattern
29
+ CommandT::Match.new path, pattern
30
+ end
31
+
32
+ it 'requires pattern to be lowercase' do
33
+ # this is an optimization: we ask our caller (the Matcher class) to
34
+ # downcase once before calling us, rather than downcase repeatedly
35
+ # during looping, recursion, and initialization of thousands of Match
36
+ # instances
37
+ match_for('foo', 'Foo').matches?.should == false
38
+ end
39
+
40
+ describe '#matches?' do
41
+ it 'returns false for non-matches' do
42
+ match_for('foo', 'bar').matches?.should == false
43
+ end
44
+
45
+ it 'returns true for matches' do
46
+ match_for('foo', 'foo').matches?.should == true
47
+ end
48
+
49
+ it 'returns true for empty search strings' do
50
+ match_for('foo', '').matches?.should == true
51
+ end
52
+
53
+ it 'returns false for overlength matches' do
54
+ match_for('foo', 'foo...').matches?.should == false
55
+ end
56
+ end
57
+
58
+ describe 'score method' do
59
+ it 'assigns a score of 1.0 for empty search string' do
60
+ match_for('foo', '').score.should == 1.0
61
+ end
62
+
63
+ it 'assigns a score of zero for a non-match' do
64
+ match_for('foo', 'bar').score.should == 0.0
65
+ end
66
+
67
+ it 'assigns a score of zero for an overlength match' do
68
+ match_for('foo', 'foo...').score.should == 0.0
69
+ end
70
+
71
+ it 'assigns perfect matches a score of one' do
72
+ match_for('foo', 'foo').score.should == 1.0
73
+ end
74
+
75
+ it 'assigns perfect but incomplete matches a score of less than one' do
76
+ match_for('foo', 'f').score.should < 1.0
77
+ end
78
+
79
+ it 'prioritizes matches with more matching characters' do
80
+ few_matches = match_for('foobar', 'fb')
81
+ many_matches = match_for('foobar', 'fbar')
82
+ many_matches.score.should > few_matches.score
83
+ end
84
+
85
+ it 'prioritizes shorter paths over longer ones' do
86
+ short_path = match_for('article.rb', 'art')
87
+ long_path = match_for('articles_controller_spec.rb', 'art')
88
+ short_path.score.should > long_path.score
89
+ end
90
+
91
+ it 'prioritizes matches after "/"' do
92
+ normal_match = match_for('fooobar', 'b')
93
+ special_match = match_for('foo/bar', 'b')
94
+ special_match.score.should > normal_match.score
95
+
96
+ # note that / beats _
97
+ normal_match = match_for('foo_bar', 'b')
98
+ special_match = match_for('foo/bar', 'b')
99
+ special_match.score.should > normal_match.score
100
+
101
+ # / also beats -
102
+ normal_match = match_for('foo-bar', 'b')
103
+ special_match = match_for('foo/bar', 'b')
104
+ special_match.score.should > normal_match.score
105
+
106
+ # and numbers
107
+ normal_match = match_for('foo9bar', 'b')
108
+ special_match = match_for('foo/bar', 'b')
109
+ special_match.score.should > normal_match.score
110
+
111
+ # and periods
112
+ normal_match = match_for('foo.bar', 'b')
113
+ special_match = match_for('foo/bar', 'b')
114
+ special_match.score.should > normal_match.score
115
+
116
+ # and spaces
117
+ normal_match = match_for('foo bar', 'b')
118
+ special_match = match_for('foo/bar', 'b')
119
+ special_match.score.should > normal_match.score
120
+ end
121
+
122
+ it 'prioritizes matches after "-"' do
123
+ normal_match = match_for('fooobar', 'b')
124
+ special_match = match_for('foo-bar', 'b')
125
+ special_match.score.should > normal_match.score
126
+
127
+ # - also beats .
128
+ normal_match = match_for('foo.bar', 'b')
129
+ special_match = match_for('foo-bar', 'b')
130
+ special_match.score.should > normal_match.score
131
+ end
132
+
133
+ it 'prioritizes matches after "_"' do
134
+ normal_match = match_for('fooobar', 'b')
135
+ special_match = match_for('foo_bar', 'b')
136
+ special_match.score.should > normal_match.score
137
+
138
+ # _ also beats .
139
+ normal_match = match_for('foo.bar', 'b')
140
+ special_match = match_for('foo_bar', 'b')
141
+ special_match.score.should > normal_match.score
142
+ end
143
+
144
+ it 'prioritizes matches after " "' do
145
+ normal_match = match_for('fooobar', 'b')
146
+ special_match = match_for('foo bar', 'b')
147
+ special_match.score.should > normal_match.score
148
+
149
+ # " " also beats .
150
+ normal_match = match_for('foo.bar', 'b')
151
+ special_match = match_for('foo bar', 'b')
152
+ special_match.score.should > normal_match.score
153
+ end
154
+
155
+ it 'prioritizes matches after numbers' do
156
+ normal_match = match_for('fooobar', 'b')
157
+ special_match = match_for('foo9bar', 'b')
158
+ special_match.score.should > normal_match.score
159
+
160
+ # numbers also beat .
161
+ normal_match = match_for('foo.bar', 'b')
162
+ special_match = match_for('foo9bar', 'b')
163
+ special_match.score.should > normal_match.score
164
+ end
165
+
166
+ it 'prioritizes matches after periods' do
167
+ normal_match = match_for('fooobar', 'b')
168
+ special_match = match_for('foo.bar', 'b')
169
+ special_match.score.should > normal_match.score
170
+ end
171
+
172
+ it 'prioritizes matching capitals following lowercase' do
173
+ normal_match = match_for('foobar', 'b')
174
+ special_match = match_for('fooBar', 'b')
175
+ special_match.score.should > normal_match.score
176
+ end
177
+
178
+ it 'prioritizes matches earlier in the string' do
179
+ early_match = match_for('**b*****', 'b')
180
+ late_match = match_for('******b*', 'b')
181
+ early_match.score.should > late_match.score
182
+ end
183
+
184
+ it 'prioritizes matches closer to previous matches' do
185
+ early_match = match_for('**bc****', 'bc')
186
+ late_match = match_for('**b***c*', 'bc')
187
+ early_match.score.should > late_match.score
188
+ end
189
+
190
+ it 'scores alternative matches of same path differently' do
191
+ # given path: app/controllers/articles_controller.rb
192
+ left_to_right_match = match_for('a**/****r******/**t*c***_*on*******.**', 'artcon')
193
+ best_match = match_for('***/***********/art*****_con*******.**', 'artcon')
194
+ best_match.score.should > left_to_right_match.score
195
+ end
196
+
197
+ it 'returns the best possible score among alternatives' do
198
+ # given path: app/controllers/articles_controller.rb
199
+ best_match = match_for('***/***********/art*****_con*******.**', 'artcon')
200
+ chosen_match = match_for('app/controllers/articles_controller.rb', 'artcon')
201
+ chosen_match.score.should == best_match.score
202
+ end
203
+
204
+ it 'provides intuitive results for "artcon" and "articles_controller"' do
205
+ low = match_for('app/controllers/heartbeat_controller.rb', 'artcon')
206
+ high = match_for('app/controllers/articles_controller.rb', 'artcon')
207
+ high.score.should > low.score
208
+ end
209
+
210
+ it 'provides intuitive results for "aca" and "a/c/articles_controller"' do
211
+ low = match_for 'app/controllers/heartbeat_controller.rb', 'aca'
212
+ high = match_for 'app/controllers/articles_controller.rb', 'aca'
213
+ best_match = match_for 'a**/c**********/a******************.**', 'aca'
214
+ high.score.should > low.score
215
+ high.score.should == best_match.score
216
+ end
217
+
218
+ it 'provides intuitive results for "d" and "doc/command-t.txt"' do
219
+ low = match_for 'TODO', 'd'
220
+ high = match_for 'doc/command-t.txt', 'd'
221
+ high.score.should > low.score
222
+ end
223
+
224
+ it 'provides intuitive results for "do" and "doc/command-t.txt"' do
225
+ low = match_for 'TODO', 'do'
226
+ high = match_for 'doc/command-t.txt', 'do'
227
+ high.score.should > low.score
228
+ end
229
+ end
230
+
231
+ describe 'to_s method' do
232
+ it 'returns the entire matched string' do
233
+ match_for('abc', 'abc').to_s.should == 'abc'
234
+ end
235
+ end
236
+ end