bond 0.4.2-java
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemspec +28 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.rdoc +91 -0
- data/LICENSE.txt +22 -0
- data/README.rdoc +242 -0
- data/Rakefile +47 -0
- data/lib/bond.rb +127 -0
- data/lib/bond/agent.rb +108 -0
- data/lib/bond/completion.rb +16 -0
- data/lib/bond/completions/activerecord.rb +12 -0
- data/lib/bond/completions/array.rb +1 -0
- data/lib/bond/completions/bond.rb +6 -0
- data/lib/bond/completions/hash.rb +3 -0
- data/lib/bond/completions/kernel.rb +15 -0
- data/lib/bond/completions/module.rb +10 -0
- data/lib/bond/completions/object.rb +21 -0
- data/lib/bond/completions/struct.rb +1 -0
- data/lib/bond/input.rb +28 -0
- data/lib/bond/m.rb +146 -0
- data/lib/bond/mission.rb +151 -0
- data/lib/bond/missions/anywhere_mission.rb +15 -0
- data/lib/bond/missions/default_mission.rb +21 -0
- data/lib/bond/missions/method_mission.rb +197 -0
- data/lib/bond/missions/object_mission.rb +44 -0
- data/lib/bond/missions/operator_method_mission.rb +27 -0
- data/lib/bond/rc.rb +48 -0
- data/lib/bond/readline.rb +38 -0
- data/lib/bond/readlines/jruby.rb +13 -0
- data/lib/bond/readlines/rawline.rb +15 -0
- data/lib/bond/readlines/ruby.rb +9 -0
- data/lib/bond/search.rb +74 -0
- data/lib/bond/version.rb +3 -0
- data/test/agent_test.rb +235 -0
- data/test/anywhere_mission_test.rb +34 -0
- data/test/bond_test.rb +141 -0
- data/test/completion_test.rb +148 -0
- data/test/completions_test.rb +98 -0
- data/test/deps.rip +4 -0
- data/test/m_test.rb +34 -0
- data/test/method_mission_test.rb +246 -0
- data/test/mission_test.rb +51 -0
- data/test/object_mission_test.rb +59 -0
- data/test/operator_method_mission_test.rb +66 -0
- data/test/search_test.rb +140 -0
- data/test/test_helper.rb +69 -0
- metadata +167 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
# A mission which completes anywhere i.e. even after non word break characters
|
2
|
+
# such as '[' or '}'. With options :prefix and :anywhere, this mission matches
|
3
|
+
# on the following regexp condition /:prefix?(:anywhere)$/ and passes the first
|
4
|
+
# capture group to the mission action.
|
5
|
+
class Bond::AnywhereMission < Bond::Mission
|
6
|
+
def initialize(options={}) #@private
|
7
|
+
options[:on] = Regexp.new("#{options[:prefix]}(#{options[:anywhere]})$")
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def after_match(input) #@private
|
12
|
+
@completion_prefix = input.to_s.sub(/#{Regexp.escape(@matched[1])}$/, '')
|
13
|
+
create_input @matched[1]
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This is the mission called when none of the others match.
|
2
|
+
class Bond::DefaultMission < Bond::Mission
|
3
|
+
ReservedWords = [
|
4
|
+
"BEGIN", "END", "alias", "and", "begin", "break", "case", "class", "def", "defined", "do", "else", "elsif", "end", "ensure",
|
5
|
+
"false", "for", "if", "in", "module", "next", "nil", "not", "or", "redo", "rescue", "retry", "return", "self", "super",
|
6
|
+
"then", "true", "undef", "unless", "until", "when", "while", "yield"
|
7
|
+
]
|
8
|
+
|
9
|
+
|
10
|
+
# Default action which generates methods, private methods, reserved words, local variables and constants.
|
11
|
+
def self.completions(input=nil)
|
12
|
+
Bond::Mission.current_eval("methods | private_methods | local_variables | " +
|
13
|
+
"self.class.constants | instance_variables") | ReservedWords
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(options={}) #@private
|
17
|
+
options[:action] ||= self.class.method(:completions)
|
18
|
+
super
|
19
|
+
end
|
20
|
+
def default_on; end #@private
|
21
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module Bond
|
2
|
+
# A mission which completes arguments for any module/class method that isn't an operator method.
|
3
|
+
# To create this mission or OperatorMethodMission, :method or :methods must be passed to Bond.complete.
|
4
|
+
# A completion for a given module/class effects any object that has it as an ancestor. If an object
|
5
|
+
# has two ancestors that have completions for the same method, the ancestor closer to the object is
|
6
|
+
# picked. For example, if Array#collect and Enumerable#collect have completions, argument completion on
|
7
|
+
# '[].collect ' would use Array#collect.
|
8
|
+
#
|
9
|
+
# ==== Bond.complete Options:
|
10
|
+
# [:action] If a string, value is assumed to be a :method and that method's action is copied.
|
11
|
+
# Otherwise defaults to normal :action behavior.
|
12
|
+
# [:search] If :action is a :method string, defaults to copying its search.
|
13
|
+
# Otherwise defaults to normal :search behavior.
|
14
|
+
# [:name, :place] These options aren't supported by a MethodMission/OperatorMethodMission completion.
|
15
|
+
# ==== Examples:
|
16
|
+
# Bond.complete(:methods => %w{delete index rindex}, :class => "Array#") {|e| e.object }
|
17
|
+
# Bond.complete(:method => "Hash#index") {|e| e.object.values }
|
18
|
+
#
|
19
|
+
# ==== Argument Format
|
20
|
+
# All method arguments can autocomplete as symbols or strings and the first argument can be prefixed
|
21
|
+
# with '(':
|
22
|
+
# >> Bond.complete(:method => 'example') { %w{some example eh} }
|
23
|
+
# => true
|
24
|
+
# >> example '[TAB]
|
25
|
+
# eh example some
|
26
|
+
# >> example :[TAB]
|
27
|
+
# :eh :example :some
|
28
|
+
#
|
29
|
+
# >> example("[TAB]
|
30
|
+
# eh example some
|
31
|
+
#
|
32
|
+
# ==== Multiple Arguments
|
33
|
+
# Every time a comma appears after a method, Bond starts a new completion. This allows a method to
|
34
|
+
# complete multiple arguments as well as complete keys for a hash. *Each* argument can be have a unique
|
35
|
+
# set of completions since a completion action is aware of what argument it is currently completing:
|
36
|
+
# >> Bond.complete(:method => 'FileUtils.chown') {|e|
|
37
|
+
# e.argument > 3 ? %w{noop verbose} : %w{root admin me} }
|
38
|
+
# => true
|
39
|
+
# >> FileUtils.chown 'r[TAB]
|
40
|
+
# >> FileUtils.chown 'root'
|
41
|
+
# >> FileUtils.chown 'root', 'a[TAB]
|
42
|
+
# >> FileUtils.chown 'root', 'admin'
|
43
|
+
# >> FileUtils.chown 'root', 'admin', 'some_file', :v[TAB]
|
44
|
+
# >> FileUtils.chown 'root', 'admin', 'some_file', :verbose
|
45
|
+
# >> FileUtils.chown 'root', 'admin', 'some_file', :verbose => true
|
46
|
+
#
|
47
|
+
# ==== Developer Notes
|
48
|
+
# Unlike other missions, creating these missions with Bond.complete doesn't add more completion rules
|
49
|
+
# for an Agent to look through. Instead, all :method(s) completions are handled by one MethodMission
|
50
|
+
# object which looks them up with its own hashes. In the same way, all operator methods are
|
51
|
+
# handled by one OperatorMethodMission object.
|
52
|
+
class MethodMission < Bond::Mission
|
53
|
+
class<<self
|
54
|
+
# Hash of instance method completions which maps methods to hashes of modules to arrays ([action, search])
|
55
|
+
attr_accessor :actions
|
56
|
+
# Same as :actions but for class methods
|
57
|
+
attr_accessor :class_actions
|
58
|
+
# Stores last search result from MethodMission.find
|
59
|
+
attr_accessor :last_find
|
60
|
+
# Stores class from last search in MethodMission.find
|
61
|
+
attr_accessor :last_class
|
62
|
+
|
63
|
+
# Creates a method action given the same options as Bond.complete
|
64
|
+
def create(options)
|
65
|
+
if options[:action].is_a?(String)
|
66
|
+
klass, klass_meth = split_method(options[:action])
|
67
|
+
if (arr = (current_actions(options[:action])[klass_meth] || {})[klass])
|
68
|
+
options[:action], options[:search] = [arr[0], options[:search] || arr[1]]
|
69
|
+
else
|
70
|
+
raise InvalidMissionError, "string :action"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
raise InvalidMissionError, "array :method" if options[:method].is_a?(Array)
|
75
|
+
meths = options[:methods] || Array(options[:method])
|
76
|
+
raise InvalidMissionError, "non-string :method(s)" unless meths.all? {|e| e.is_a?(String) }
|
77
|
+
if options[:class].is_a?(String)
|
78
|
+
options[:class] << '#' unless options[:class][/[#.]$/]
|
79
|
+
meths.map! {|e| options[:class] + e }
|
80
|
+
end
|
81
|
+
|
82
|
+
meths.each {|meth|
|
83
|
+
klass, klass_meth = split_method(meth)
|
84
|
+
(current_actions(meth)[klass_meth] ||= {})[klass] = [options[:action], options[:search]].compact
|
85
|
+
}
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
# Resets all instance and class method actions.
|
90
|
+
def reset
|
91
|
+
@actions = {}
|
92
|
+
@class_actions = {}
|
93
|
+
end
|
94
|
+
|
95
|
+
# Lists method names
|
96
|
+
def action_methods
|
97
|
+
(actions.keys + class_actions.keys).uniq
|
98
|
+
end
|
99
|
+
|
100
|
+
# Lists full method names, prefixed with class/module
|
101
|
+
def all_methods
|
102
|
+
(class_actions.map {|m,h| h.map {|k,v| "#{k}.#{m}" } } +
|
103
|
+
actions.map {|m,h| h.map {|k,v| "#{k}##{m}" } }).flatten.sort
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the first completion by looking up the object's ancestors and finding the closest
|
107
|
+
# one that has a completion definition for the given method. Completion is returned
|
108
|
+
# as an array containing action proc and optional search to go with it.
|
109
|
+
def find(obj, meth)
|
110
|
+
last_find = find_with(obj, meth, :<=, @class_actions) if obj.is_a?(Module)
|
111
|
+
last_find = find_with(obj, meth, :is_a?, @actions) unless last_find
|
112
|
+
@last_class = last_find.is_a?(Array) ? last_find[0] : nil
|
113
|
+
@last_find = last_find ? last_find[1] : last_find
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns a constant like Module#const_get no matter what namespace it's nested in.
|
117
|
+
# Returns nil if the constant is not found.
|
118
|
+
def any_const_get(name)
|
119
|
+
return name if name.is_a?(Module)
|
120
|
+
klass = Object
|
121
|
+
name.split('::').each {|e| klass = klass.const_get(e) }
|
122
|
+
klass
|
123
|
+
rescue
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
|
127
|
+
protected
|
128
|
+
def current_actions(meth)
|
129
|
+
meth.include?('.') ? @class_actions : @actions
|
130
|
+
end
|
131
|
+
|
132
|
+
def split_method(meth)
|
133
|
+
meth = "Kernel##{meth}" if !meth.to_s[/[.#]/]
|
134
|
+
meth.split(/[.#]/,2)
|
135
|
+
end
|
136
|
+
|
137
|
+
def find_with(obj, meth, find_meth, actions)
|
138
|
+
(actions[meth] || {}).select {|k,v| get_class(k) }.
|
139
|
+
sort {|a,b| get_class(a[0]) <=> get_class(b[0]) || -1 }.
|
140
|
+
find {|k,v| obj.send(find_meth, get_class(k)) }
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_class(klass)
|
144
|
+
(@klasses ||= {})[klass] ||= any_const_get(klass)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
self.reset
|
149
|
+
OBJECTS = Mission::OBJECTS + %w{\S*?}
|
150
|
+
CONDITION = %q{(OBJECTS)\.?(METHODS)(?:\s+|\()(['":])?(.*)$}
|
151
|
+
|
152
|
+
def match_message #@private
|
153
|
+
"Matches completion for method '#{@meth}' in '#{MethodMission.last_class}'."
|
154
|
+
end
|
155
|
+
|
156
|
+
protected
|
157
|
+
def do_match(input)
|
158
|
+
(@on = default_on) && super && eval_object(@matched[1] ? @matched[1] : 'self') &&
|
159
|
+
MethodMission.find(@evaled_object, @meth = matched_method)
|
160
|
+
end
|
161
|
+
|
162
|
+
def default_on
|
163
|
+
Regexp.new condition_with_objects.sub('METHODS',Regexp.union(*current_methods).to_s)
|
164
|
+
end
|
165
|
+
|
166
|
+
def current_methods
|
167
|
+
self.class.action_methods - OPERATORS
|
168
|
+
end
|
169
|
+
|
170
|
+
def default_action
|
171
|
+
MethodMission.last_find[0]
|
172
|
+
end
|
173
|
+
|
174
|
+
def matched_method
|
175
|
+
@matched[2]
|
176
|
+
end
|
177
|
+
|
178
|
+
def set_action_and_search
|
179
|
+
@action = default_action
|
180
|
+
@search = MethodMission.last_find[1] || Search.default_search
|
181
|
+
end
|
182
|
+
|
183
|
+
def after_match(input)
|
184
|
+
set_action_and_search
|
185
|
+
@completion_prefix, typed = @matched[3], @matched[-1]
|
186
|
+
input_options = {:object => @evaled_object, :argument => 1+typed.count(','),
|
187
|
+
:arguments => (@completion_prefix.to_s+typed).split(/\s*,\s*/) }
|
188
|
+
if typed.to_s.include?(',') && (match = typed.match(/(.*?\s*)([^,]*)$/))
|
189
|
+
typed = match[2]
|
190
|
+
typed.sub!(/^(['":])/,'')
|
191
|
+
@completion_prefix = typed.empty? ? '' : "#{@matched[3]}#{match[1]}#{$1}"
|
192
|
+
end
|
193
|
+
create_input typed, input_options
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# A mission which completes an object's methods. For this mission to match, the
|
2
|
+
# condition must match and the current object must have an ancestor that matches
|
3
|
+
# :object. Note: To access to the current object being completed on within an
|
4
|
+
# action, use the input's object attribute.
|
5
|
+
#
|
6
|
+
# ==== Bond.complete Options:
|
7
|
+
# [:action] If an action is not specified, the default action is to complete an
|
8
|
+
# object's non-operator methods.
|
9
|
+
#
|
10
|
+
# ===== Example:
|
11
|
+
# Bond.complete(:object => 'ActiveRecord::Base') {|input| input.object.class.instance_methods(false) }
|
12
|
+
class Bond::ObjectMission < Bond::Mission
|
13
|
+
OBJECTS = %w<\S+> + Bond::Mission::OBJECTS
|
14
|
+
CONDITION = '(OBJECTS)\.(\w*(?:\?|!)?)$'
|
15
|
+
def initialize(options={}) #@private
|
16
|
+
@object_condition = /^#{options[:object]}$/
|
17
|
+
options[:on] ||= Regexp.new condition_with_objects
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def match_message #@private
|
22
|
+
"Matches completion for object with ancestor matching #{@object_condition.inspect}."
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
def unique_id
|
27
|
+
"#{@object_condition.inspect}+#{@on.inspect}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def do_match(input)
|
31
|
+
super && eval_object(@matched[1]) && @evaled_object.class.respond_to?(:ancestors) &&
|
32
|
+
@evaled_object.class.ancestors.any? {|e| e.to_s =~ @object_condition }
|
33
|
+
end
|
34
|
+
|
35
|
+
def after_match(input)
|
36
|
+
@completion_prefix = @matched[1] + "."
|
37
|
+
@action ||= lambda {|e| default_action(e.object) }
|
38
|
+
create_input @matched[2], :object => @evaled_object
|
39
|
+
end
|
40
|
+
|
41
|
+
def default_action(obj)
|
42
|
+
obj.methods.map {|e| e.to_s} - OPERATORS
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Bond
|
2
|
+
# A mission which completes arguments for any module/class method that is an
|
3
|
+
# operator i.e. '>' or '*'. Takes same Bond.complete options as
|
4
|
+
# MethodMission. The only operator method this mission doesn't complete is
|
5
|
+
# '[]='. The operator '[]' should cover the first argument completion of '[]='
|
6
|
+
# anyways.
|
7
|
+
class OperatorMethodMission < MethodMission
|
8
|
+
OPERATORS = Mission::OPERATORS - ["[]", "[]="]
|
9
|
+
OBJECTS = Mission::OBJECTS + %w{\S+}
|
10
|
+
CONDITION = %q{(OBJECTS)\s*(METHODS)\s*(['":])?(.*)$}
|
11
|
+
|
12
|
+
protected
|
13
|
+
def current_methods
|
14
|
+
(OPERATORS & MethodMission.action_methods) + ['[']
|
15
|
+
end
|
16
|
+
|
17
|
+
def matched_method
|
18
|
+
{'['=>'[]'}[@matched[2]] || @matched[2]
|
19
|
+
end
|
20
|
+
|
21
|
+
def after_match(input)
|
22
|
+
set_action_and_search
|
23
|
+
@completion_prefix, typed = input.to_s.sub(/#{Regexp.quote(@matched[-1])}$/, ''), @matched[-1]
|
24
|
+
create_input typed, :object => @evaled_object, :argument => 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/bond/rc.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Bond
|
2
|
+
# Namespace in which completion files, ~/.bondrc and ~/.bond/completions/*.rb, are evaluated. Methods in this module
|
3
|
+
# and Search are the DSL in completion files and can be used within completion actions.
|
4
|
+
#
|
5
|
+
# === Example ~/.bondrc
|
6
|
+
# # complete arguments for any object's :respond_to?
|
7
|
+
# complete(:method => "Object#respond_to?") {|e| e.object.methods }
|
8
|
+
# # complete arguments for any module's :public
|
9
|
+
# complete(:method => "Module#public") {|e| e.object.instance_methods }
|
10
|
+
#
|
11
|
+
# # Share generate_tags action across completions
|
12
|
+
# complete(:method => "edit_tags", :action => :generate_tags)
|
13
|
+
# complete(:method => "delete_tags", :search => false) {|e| generate_tags(e).grep(/#{e}/i) }
|
14
|
+
#
|
15
|
+
# def generate_tags(input)
|
16
|
+
# ...
|
17
|
+
# end
|
18
|
+
module Rc
|
19
|
+
extend self, Search
|
20
|
+
|
21
|
+
# See {Bond#complete}
|
22
|
+
def complete(*args, &block); M.complete(*args, &block); end
|
23
|
+
# See {Bond#recomplete}
|
24
|
+
def recomplete(*args, &block); M.recomplete(*args, &block); end
|
25
|
+
|
26
|
+
# Action method with search which returns array of files that match current input.
|
27
|
+
def files(input)
|
28
|
+
(::Readline::FILENAME_COMPLETION_PROC.call(input) || []).map {|f|
|
29
|
+
f =~ /^~/ ? File.expand_path(f) : f
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Helper method which returns objects of a given class.
|
34
|
+
def objects_of(klass)
|
35
|
+
object = []
|
36
|
+
ObjectSpace.each_object(klass) {|e| object.push(e) }
|
37
|
+
object
|
38
|
+
end
|
39
|
+
|
40
|
+
# Calls eval with Mission.current_eval, rescuing any exceptions to return nil.
|
41
|
+
# If Bond.config[:debug] is true, exceptions are raised again.
|
42
|
+
def eval(str)
|
43
|
+
Mission.current_eval(str)
|
44
|
+
rescue Exception
|
45
|
+
raise if Bond.config[:debug]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# This is the default readline plugin for Bond. A valid plugin must be an object
|
2
|
+
# that responds to methods setup and line_buffer as described below.
|
3
|
+
class Bond::Readline
|
4
|
+
DefaultBreakCharacters = " \t\n\"\\'`><=;|&{("
|
5
|
+
|
6
|
+
# Loads the readline-like library and sets the completion_proc to the given agent.
|
7
|
+
def self.setup(agent)
|
8
|
+
readline_setup
|
9
|
+
|
10
|
+
# Reinforcing irb defaults
|
11
|
+
Readline.completion_append_character = nil
|
12
|
+
if Readline.respond_to?("basic_word_break_characters=")
|
13
|
+
Readline.basic_word_break_characters = DefaultBreakCharacters
|
14
|
+
end
|
15
|
+
|
16
|
+
Readline.completion_proc = agent
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.readline_setup
|
20
|
+
require 'readline'
|
21
|
+
load_extension unless Readline.respond_to?(:line_buffer)
|
22
|
+
if (Readline::VERSION rescue nil).to_s[/editline/i]
|
23
|
+
puts "Bond has detected EditLine and may not work with it." +
|
24
|
+
" See the README's Limitations section."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.load_extension
|
29
|
+
require 'readline_line_buffer'
|
30
|
+
rescue LoadError
|
31
|
+
$stderr.puts "Bond Error: Failed to load readline_line_buffer. Ensure that it exists and was built correctly."
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns full line of what the user has typed.
|
35
|
+
def self.line_buffer
|
36
|
+
Readline.line_buffer
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Readline for Jruby
|
2
|
+
class Bond::Jruby < Bond::Readline
|
3
|
+
def self.readline_setup
|
4
|
+
require 'readline'
|
5
|
+
require 'jruby'
|
6
|
+
class << Readline
|
7
|
+
ReadlineExt = org.jruby.ext.Readline
|
8
|
+
def line_buffer
|
9
|
+
ReadlineExt.s_get_line_buffer(JRuby.runtime.current_context, JRuby.reference(self))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# A pure ruby readline which requires {rawline}[http://github.com/h3rald/rawline].
|
2
|
+
class Bond::Rawline < Bond::Readline
|
3
|
+
def self.setup(agent)
|
4
|
+
require 'rawline'
|
5
|
+
Rawline.completion_append_character = nil
|
6
|
+
Rawline.basic_word_break_characters= " \t\n\"\\'`><;|&{("
|
7
|
+
Rawline.completion_proc = agent
|
8
|
+
rescue LoadError
|
9
|
+
abort "Bond Error: rawline gem is required for this readline plugin -> gem install rawline"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.line_buffer
|
13
|
+
Rawline.editor.line.text
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# A pure ruby readline which requires {rb-readline}[https://github.com/luislavena/rb-readline].
|
2
|
+
class Bond::Ruby < Bond::Readline
|
3
|
+
def self.readline_setup
|
4
|
+
require 'rb-readline'
|
5
|
+
rescue LoadError
|
6
|
+
abort "Bond Error: rb-readline gem is required for this readline plugin" +
|
7
|
+
" -> gem install rb-readline"
|
8
|
+
end
|
9
|
+
end
|
data/lib/bond/search.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module Bond
|
2
|
+
# Contains search methods used to filter possible completions given what the user has typed for that completion.
|
3
|
+
# For a search method to be used by Bond.complete it must end in '_search' and take two arguments: the Input
|
4
|
+
# string and an array of possible completions.
|
5
|
+
#
|
6
|
+
# ==== Creating a search method
|
7
|
+
# Say you want to create a custom search which ignores completions containing '-'.
|
8
|
+
# In a completion file under Rc namespace, define this method:
|
9
|
+
# def ignore_hyphen_search(input, list)
|
10
|
+
# normal_search(input, list.select {|e| e !~ /-/ })
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Now you can pass this custom search to any complete() as :search => :ignore_hyphen
|
14
|
+
module Search
|
15
|
+
class<<self
|
16
|
+
# Default search used across missions, set by Bond.config[:default_search]
|
17
|
+
attr_accessor :default_search
|
18
|
+
end
|
19
|
+
|
20
|
+
# Searches completions from the beginning of the string.
|
21
|
+
def normal_search(input, list)
|
22
|
+
list.grep(/^#{Regexp.escape(input)}/)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Searches completions anywhere in the string.
|
26
|
+
def anywhere_search(input, list)
|
27
|
+
list.grep(/#{Regexp.escape(input)}/)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Searches completions from the beginning and ignores case.
|
31
|
+
def ignore_case_search(input, list)
|
32
|
+
list.grep(/^#{Regexp.escape(input)}/i)
|
33
|
+
end
|
34
|
+
|
35
|
+
# A normal_search which also provides aliasing of underscored words.
|
36
|
+
# For example 'some_dang_long_word' can be specified as 's_d_l_w'. Aliases can be any unique string
|
37
|
+
# at the beginning of an underscored word. For example, to choose the first completion between 'so_long'
|
38
|
+
# and 'so_larger', type 's_lo'.
|
39
|
+
def underscore_search(input, list)
|
40
|
+
if input[/_([^_]+)$/]
|
41
|
+
regex = input.split('_').map {|e| Regexp.escape(e) }.join("([^_]+)?_")
|
42
|
+
list.select {|e| e =~ /^#{regex}/ }
|
43
|
+
else
|
44
|
+
normal_search(input, list)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Default search across missions to be invoked by a search that wrap another search i.e. files_search.
|
49
|
+
def default_search(input, list)
|
50
|
+
send("#{Search.default_search}_search", input, list)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Does default_search on the given paths but only returns ones that match the input's current
|
54
|
+
# directory depth, determined by '/'. For example if a user has typed 'irb/c', this search returns
|
55
|
+
# matching paths that are one directory deep i.e. 'irb/cmd/ irb/completion.rb irb/context.rb'.
|
56
|
+
def files_search(input, list)
|
57
|
+
incremental_filter(input, list, '/')
|
58
|
+
end
|
59
|
+
|
60
|
+
# Does the same as files_search but for modules. A module depth is delimited by '::'.
|
61
|
+
def modules_search(input, list)
|
62
|
+
incremental_filter(input, list, '::')
|
63
|
+
end
|
64
|
+
|
65
|
+
# Used by files_search and modules_search.
|
66
|
+
def incremental_filter(input, list, delim)
|
67
|
+
i = 0; input.gsub(delim) {|e| i+= 1 }
|
68
|
+
delim_chars = delim.split('').uniq.join('')
|
69
|
+
current_matches, future_matches = default_search(input, list).partition {|e|
|
70
|
+
e[/^[^#{delim_chars}]*(#{delim}[^#{delim_chars}]+){0,#{i}}$/] }
|
71
|
+
(current_matches + future_matches.map {|e| e[/^(([^#{delim_chars}]*#{delim}){0,#{i+1}})/, 1] }).uniq
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|