bond 0.4.2-java
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/.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
|