ver-command_t 0.1.0
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/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
|