ver-command_t 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +12 -0
- data/ext/command_t/depend +24 -0
- data/ext/command_t/ext.c +65 -0
- data/ext/command_t/ext.h +36 -0
- data/ext/command_t/ext.so +0 -0
- data/ext/command_t/extconf.rb +32 -0
- data/ext/command_t/match.c +189 -0
- data/ext/command_t/match.h +29 -0
- data/ext/command_t/matcher.c +164 -0
- data/ext/command_t/matcher.h +30 -0
- data/ext/command_t/mkmf.log +20 -0
- data/ext/command_t/ruby_compat.h +49 -0
- data/lib/command_t/ex/command_t.rb +34 -0
- data/lib/command_t/finder.rb +51 -0
- data/lib/command_t/scanner.rb +89 -0
- data/lib/command_t/settings.rb +75 -0
- data/lib/ver-command_t.rb +16 -0
- data/spec/command_t/finder_spec.rb +72 -0
- data/spec/command_t/match_spec.rb +236 -0
- data/spec/command_t/matcher_spec.rb +76 -0
- data/spec/command_t/scanner_spec.rb +73 -0
- data/spec/fixtures/bar/abc +1 -0
- data/spec/fixtures/bar/xyz +1 -0
- data/spec/fixtures/baz +1 -0
- data/spec/fixtures/bing +1 -0
- data/spec/fixtures/foo/alpha/t1 +1 -0
- data/spec/fixtures/foo/alpha/t2 +1 -0
- data/spec/fixtures/foo/beta +1 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/vim_formatter.rb +41 -0
- metadata +107 -0
@@ -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
|