forked from JuliaLang/julia
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathREPLCompletions.jl
268 lines (244 loc) · 8.98 KB
/
REPLCompletions.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
module REPLCompletions
export completions, shell_completions, latex_completions
using Base.Meta
function completes_global(x, name)
return beginswith(x, name) && !('#' in x)
end
function filtered_mod_names(ffunc::Function, mod::Module, name::String, all::Bool=false, imported::Bool=false)
ssyms = names(mod, all, imported)
filter!(ffunc, ssyms)
syms = UTF8String[string(s) for s in ssyms]
filter!(x->completes_global(x, name), syms)
end
# REPL Symbol Completions
function complete_symbol(sym, ffunc)
# Find module
strs = split(sym, '.')
# Maybe be smarter in the future
context_module = Main
mod = context_module
lookup_module = true
t = Union()
for name in strs[1:(end-1)]
s = symbol(name)
if lookup_module
# If we're considering A.B.C where B doesn't exist in A, give up
isdefined(mod, s) || return UTF8String[]
b = mod.(s)
if isa(b, Module)
mod = b
elseif Base.isstructtype(typeof(b))
lookup_module = false
t = typeof(b)
else
# A.B.C where B is neither a type nor a
# module. Will have to be revisited if
# overloading is allowed
return UTF8String[]
end
else
# We're now looking for a type
fields = t.names
found = false
for i in 1:length(fields)
s == fields[i] || continue
t = t.types[i]
Base.isstructtype(t) || return UTF8String[]
found = true
break
end
#Same issue as above, but with types instead of modules
found || return UTF8String[]
end
end
name = strs[end]
suggestions = UTF8String[]
if lookup_module
# We will exclude the results that the user does not want, as well
# as excluding Main.Main.Main, etc., because that's most likely not what
# the user wants
p = s->(ffunc(mod, s) && s != module_name(mod))
# Looking for a binding in a module
if mod == context_module
# Also look in modules we got through `using`
mods = ccall(:jl_module_usings, Any, (Any,), Main)
for m in mods
append!(suggestions, filtered_mod_names(p, m, name))
end
append!(suggestions, filtered_mod_names(p, mod, name, true, true))
else
append!(suggestions, filtered_mod_names(p, mod, name, true, false))
end
else
# Looking for a member of a type
fields = t.names
for field in fields
s = string(field)
if beginswith(s, name)
push!(suggestions, s)
end
end
end
suggestions
end
function complete_keyword(s::ByteString)
const sorted_keywords = [
"abstract", "baremodule", "begin", "bitstype", "break", "catch", "ccall",
"const", "continue", "do", "else", "elseif", "end", "export", "finally",
"for", "function", "global", "if", "immutable", "import", "importall",
"let", "local", "macro", "module", "quote", "return", "try", "type",
"typealias", "using", "while"]
r = searchsorted(sorted_keywords, s)
i = first(r)
n = length(sorted_keywords)
while i <= n && beginswith(sorted_keywords[i],s)
r = first(r):i
i += 1
end
sorted_keywords[r]
end
function complete_path(path::String, pos)
dir, prefix = splitdir(path)
local files
try
if length(dir) == 0
files = readdir()
elseif isdir(dir)
files = readdir(dir)
else
return UTF8String[], 0:-1, false
end
catch
return UTF8String[], 0:-1, false
end
matches = UTF8String[]
for file in files
if beginswith(file, prefix)
id = try isdir(joinpath(dir, file)) catch; false end
push!(matches, id ? joinpath(file,"") : file)
end
end
matches, (nextind(path, pos-sizeof(prefix))):pos, length(matches) > 0
end
function complete_methods(input::String)
tokens = split(input, '.')
fn = Main
for token in tokens
sym = symbol(token)
isdefined(fn, sym) || return UTF8String[]
fn = fn.(sym)
end
isgeneric(fn) || return UTF8String[]
UTF8String[string(m) for m in methods(fn)]
end
include("latex_symbols.jl")
const non_identifier_chars = [" \t\n\r\"\\'`\$><=:;|&{}()[],+-*/?%^~"...]
const non_filename_chars = [" \t\n\r\"\\'`@\$><=;|&{("...]
const whitespace_chars = [" \t\n\r"...]
# Aux function to detect whether we're right after a
# using or import keyword
function afterusing(string::ByteString, startpos::Int)
(isempty(string) || startpos == 0) && return false
str = string[1:prevind(string,startpos)]
isempty(str) && return false
rstr = reverse(str)
r = search(rstr, r"\s(gnisu|tropmi)\b")
isempty(r) && return false
fr = chr2ind(str, length(str)-ind2chr(rstr, last(r))+1)
return ismatch(r"^\b(using|import)\s*(\w+\s*,\s*)*\w*$", str[fr:end])
end
function latex_completions(string, pos)
slashpos = rsearch(string, '\\', pos)
if rsearch(string, whitespace_chars, pos) < slashpos
# latex symbol substitution
s = string[slashpos:pos]
latex = get(latex_symbols, s, "")
if !isempty(latex) # complete an exact match
return (true, ([latex], slashpos:pos, true))
else
# return possible matches; these cannot be mixed with regular
# Julian completions as only latex symbols contain the leading \
latex_names = filter(k -> beginswith(k, s), keys(latex_symbols))
return (true, (sort!(collect(latex_names)), slashpos:pos, true))
end
end
return (false, (UTF8String[], 0:-1, false))
end
function completions(string, pos)
# First parse everything up to the current position
partial = string[1:pos]
inc_tag = Base.incomplete_tag(parse(partial , raise=false))
if inc_tag in [:cmd, :string]
startpos = nextind(partial, rsearch(partial, non_filename_chars, pos))
r = startpos:pos
paths, r, success = complete_path(string[r], pos)
if inc_tag == :string &&
length(paths) == 1 && # Only close if there's a single choice,
!isdir(string[startpos:start(r)-1] * paths[1]) && # except if it's a directory
(length(string) <= pos || string[pos+1] != '"') # or there's already a " at the cursor.
paths[1] *= "\""
end
return sort(paths), r, success
end
ok, ret = latex_completions(string, pos)
ok && return ret
if inc_tag == :other && string[pos] == '('
endpos = prevind(string, pos)
startpos = nextind(string, rsearch(string, non_identifier_chars, endpos))
return complete_methods(string[startpos:endpos]), startpos:endpos, false
elseif inc_tag == :comment
return UTF8String[], 0:-1, false
end
dotpos = rsearch(string, '.', pos)
startpos = nextind(string, rsearch(string, non_identifier_chars, pos))
ffunc = (mod,x)->true
suggestions = UTF8String[]
comp_keywords = true
if afterusing(string, startpos)
# We're right after using or import. Let's look only for packages
# and modules we can reach from here
# If there's no dot, we're in toplevel, so we should
# also search for packages
s = string[startpos:pos]
if dotpos <= startpos
append!(suggestions, filter(readdir(Pkg.dir())) do pname
pname[1] != '.' &&
pname != "METADATA" &&
pname != "REQUIRE" &&
beginswith(pname, s)
end)
end
ffunc = (mod,x)->(isdefined(mod, x) && isa(mod.(x), Module))
comp_keywords = false
end
startpos == 0 && (pos = -1)
dotpos <= startpos && (dotpos = startpos - 1)
s = string[startpos:pos]
comp_keywords && append!(suggestions, complete_keyword(s))
append!(suggestions, complete_symbol(s, ffunc))
return sort(unique(suggestions)), (dotpos+1):pos, true
end
function shell_completions(string, pos)
# First parse everything up to the current position
scs = string[1:pos]
local args, last_parse
try
args, last_parse = Base.shell_parse(scs, true)
catch
return UTF8String[], 0:-1, false
end
# Now look at the last thing we parsed
isempty(args.args[end].args) && return UTF8String[], 0:-1, false
arg = args.args[end].args[end]
if isa(arg,String)
# Treat this as a path (perhaps give a list of comands in the future as well?)
return complete_path(arg, pos)
elseif isexpr(arg, :escape) && (isexpr(arg.args[1], :incomplete) || isexpr(arg.args[1], :error))
r = first(last_parse):prevind(last_parse, last(last_parse))
partial = scs[r]
ret, range = completions(partial, endof(partial))
range += first(r) - 1
return ret, range, true
end
end
end # module