Skip to content

Commit 61e63a1

Browse files
Resolve errors
1 parent e83d6cd commit 61e63a1

File tree

4 files changed

+441
-64
lines changed

4 files changed

+441
-64
lines changed

src/OriginalREPLCompletions.jl

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
#=
2+
MIT License
3+
4+
Copyright (c) 2009-2024: Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors: https://github.com/JuliaLang/julia/contributors
5+
6+
Permission is hereby granted, free of charge, to any person obtaining
7+
a copy of this software and associated documentation files (the
8+
"Software"), to deal in the Software without restriction, including
9+
without limitation the rights to use, copy, modify, merge, publish,
10+
distribute, sublicense, and/or sell copies of the Software, and to
11+
permit persons to whom the Software is furnished to do so, subject to
12+
the following conditions:
13+
14+
The above copyright notice and this permission notice shall be
15+
included in all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24+
25+
end of terms and conditions
26+
27+
Please see [THIRDPARTY.md](./THIRDPARTY.md) for license information for other software used in this project.
28+
=#
29+
30+
module OriginalREPLCompletions
31+
32+
using REPL.REPLCompletions: Completion, Completions, PackageCompletion, PathCompletion
33+
using REPL.REPLCompletions:
34+
dict_identifier_key,
35+
bslash_completions,
36+
complete_keyword_argument,
37+
non_identifier_chars,
38+
afterusing,
39+
complete_identifiers!,
40+
identify_possible_method_completion,
41+
complete_any_methods,
42+
module_filter,
43+
_readdirx,
44+
project_deps_get_completion_candidates,
45+
is_broadcasting_expr,
46+
complete_methods,
47+
complete_expanduser,
48+
complete_path,
49+
find_dict_matches
50+
51+
# This is the original implementation of REPL.REPLCompletions.completions function.
52+
function _completions(
53+
string::String,
54+
pos::Int,
55+
context_module::Module = Main,
56+
shift::Bool = true,
57+
hint::Bool = false,
58+
)
59+
# First parse everything up to the current position
60+
partial = string[1:pos]
61+
inc_tag = Base.incomplete_tag(Meta.parse(partial, raise = false, depwarn = false))
62+
63+
# ?(x, y)TAB lists methods you can call with these objects
64+
# ?(x, y TAB lists methods that take these objects as the first two arguments
65+
# MyModule.?(x, y)TAB restricts the search to names in MyModule
66+
rexm = match(r"(\w+\.|)\?\((.*)$", partial)
67+
if !isnothing(rexm)
68+
# Get the module scope
69+
if isempty(rexm.captures[1])
70+
callee_module = context_module
71+
else
72+
modname = Symbol(rexm.captures[1][1:end-1])
73+
if isdefined(context_module, modname)
74+
callee_module = getfield(context_module, modname)
75+
if !isa(callee_module, Module)
76+
callee_module = context_module
77+
end
78+
else
79+
callee_module = context_module
80+
end
81+
end
82+
moreargs = !endswith(rexm.captures[2], ')')
83+
callstr = "_(" * rexm.captures[2]
84+
if moreargs
85+
callstr *= ')'
86+
end
87+
ex_org = Meta.parse(callstr, raise = false, depwarn = false)
88+
if isa(ex_org, Expr)
89+
return complete_any_methods(
90+
ex_org,
91+
callee_module::Module,
92+
context_module,
93+
moreargs,
94+
shift,
95+
),
96+
(0:length(rexm.captures[1])+1) .+ rexm.offset,
97+
false
98+
end
99+
end
100+
101+
# if completing a key in a Dict
102+
identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
103+
if identifier !== nothing
104+
matches = find_dict_matches(identifier, partial_key)
105+
length(matches) == 1 &&
106+
(lastindex(string) <= pos || string[nextind(string, pos)] != ']') &&
107+
(matches[1] *= ']')
108+
length(matches) > 0 && return Completion[
109+
DictCompletion(identifier, match) for match in sort!(matches)
110+
],
111+
loc::Int:pos,
112+
true
113+
end
114+
115+
ffunc = Returns(true)
116+
suggestions = Completion[]
117+
118+
# Check if this is a var"" string macro that should be completed like
119+
# an identifier rather than a string.
120+
# TODO: It would be nice for the parser to give us more information here
121+
# so that we can lookup the macro by identity rather than pattern matching
122+
# its invocation.
123+
varrange = findprev("var\"", string, pos)
124+
125+
expanded = nothing
126+
was_expanded = false
127+
128+
if varrange !== nothing
129+
ok, ret = bslash_completions(string, pos)
130+
ok && return ret
131+
startpos = first(varrange) + 4
132+
dotpos = something(findprev(isequal('.'), string, first(varrange) - 1), 0)
133+
name = string[startpos:pos]
134+
return complete_identifiers!(
135+
Completion[],
136+
ffunc,
137+
context_module,
138+
string,
139+
name,
140+
pos,
141+
dotpos,
142+
startpos,
143+
)
144+
elseif inc_tag === :cmd
145+
# TODO: should this call shell_completions instead of partially reimplementing it?
146+
let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse
147+
startpos = nextind(partial, reverseind(partial, m.offset))
148+
r = startpos:pos
149+
scs::String = string[r]
150+
151+
expanded = complete_expanduser(scs, r)
152+
was_expanded = expanded[3]
153+
if was_expanded
154+
scs = (only(expanded[1])::PathCompletion).path
155+
# If tab press, ispath and user expansion available, return it now
156+
# otherwise see if we can complete the path further before returning with expanded ~
157+
!hint && ispath(scs) && return expanded::Completions
158+
end
159+
160+
path::String = replace(scs, r"(\\+)\g1(\\?)`" => "\1\2`") # fuzzy unescape_raw_string: match an even number of \ before ` and replace with half as many
161+
# This expansion with "\\ "=>' ' replacement and shell_escape=true
162+
# assumes the path isn't further quoted within the cmd backticks.
163+
path = replace(path, r"\\ " => " ", r"\$" => "\$") # fuzzy shell_parse (reversed by shell_escape_posixly)
164+
paths, dir, success =
165+
complete_path(path, shell_escape = true, raw_escape = true)
166+
167+
if success && !isempty(dir)
168+
let dir = do_raw_escape(do_shell_escape(dir))
169+
# if escaping of dir matches scs prefix, remove that from the completions
170+
# otherwise make it the whole completion
171+
if endswith(dir, "/") && startswith(scs, dir)
172+
r = (startpos+sizeof(dir)):pos
173+
elseif startswith(scs, dir * "/")
174+
r = nextind(string, startpos + sizeof(dir)):pos
175+
else
176+
map!(paths, paths) do c::PathCompletion
177+
p = dir * "/" * c.path
178+
was_expanded && (p = contractuser(p))
179+
return PathCompletion(p)
180+
end
181+
end
182+
end
183+
end
184+
if isempty(paths) && !hint && was_expanded
185+
# if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
186+
return expanded::Completions
187+
else
188+
return sort!(paths, by = p -> p.path), r::UnitRange{Int}, success
189+
end
190+
end
191+
elseif inc_tag === :string
192+
# Find first non-escaped quote
193+
let m = match(r"\"(?!\\)", reverse(partial))
194+
startpos = nextind(partial, reverseind(partial, m.offset))
195+
r = startpos:pos
196+
scs::String = string[r]
197+
198+
expanded = complete_expanduser(scs, r)
199+
was_expanded = expanded[3]
200+
if was_expanded
201+
scs = (only(expanded[1])::PathCompletion).path
202+
# If tab press, ispath and user expansion available, return it now
203+
# otherwise see if we can complete the path further before returning with expanded ~
204+
!hint && ispath(scs) && return expanded::Completions
205+
end
206+
207+
path = try
208+
unescape_string(replace(scs, "\\\$" => "\$"))
209+
catch ex
210+
ex isa ArgumentError || rethrow()
211+
nothing
212+
end
213+
if !isnothing(path)
214+
paths, dir, success = complete_path(path::String, string_escape = true)
215+
216+
if close_path_completion(dir, paths, path, pos)
217+
p = (paths[1]::PathCompletion).path * "\""
218+
hint && was_expanded && (p = contractuser(p))
219+
paths[1] = PathCompletion(p)
220+
end
221+
222+
if success && !isempty(dir)
223+
let dir = do_string_escape(dir)
224+
# if escaping of dir matches scs prefix, remove that from the completions
225+
# otherwise make it the whole completion
226+
if endswith(dir, "/") && startswith(scs, dir)
227+
r = (startpos+sizeof(dir)):pos
228+
elseif startswith(scs, dir * "/") && dir != dirname(homedir())
229+
was_expanded && (dir = contractuser(dir))
230+
r = nextind(string, startpos + sizeof(dir)):pos
231+
else
232+
map!(paths, paths) do c::PathCompletion
233+
p = dir * "/" * c.path
234+
hint && was_expanded && (p = contractuser(p))
235+
return PathCompletion(p)
236+
end
237+
end
238+
end
239+
end
240+
241+
# Fallthrough allowed so that Latex symbols can be completed in strings
242+
if success
243+
return sort!(paths, by = p -> p.path), r::UnitRange{Int}, success
244+
elseif !hint && was_expanded
245+
# if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
246+
return expanded::Completions
247+
end
248+
end
249+
end
250+
end
251+
# if path has ~ and we didn't find any paths to complete just return the expanded path
252+
was_expanded && return expanded::Completions
253+
254+
ok, ret = bslash_completions(string, pos)
255+
ok && return ret
256+
257+
# Make sure that only bslash_completions is working on strings
258+
inc_tag === :string && return Completion[], 0:-1, false
259+
if inc_tag === :other
260+
frange, ex, wordrange, method_name_end =
261+
identify_possible_method_completion(partial, pos)
262+
if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
263+
if ex.head === :call
264+
return complete_methods(ex, context_module, shift),
265+
first(frange):method_name_end,
266+
false
267+
elseif is_broadcasting_expr(ex)
268+
return complete_methods(ex, context_module, shift),
269+
first(frange):(method_name_end-1),
270+
false
271+
end
272+
end
273+
elseif inc_tag === :comment
274+
return Completion[], 0:-1, false
275+
end
276+
277+
# Check whether we can complete a keyword argument in a function call
278+
kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module)
279+
isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
280+
281+
dotpos = something(findprev(isequal('.'), string, pos), 0)
282+
startpos =
283+
nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
284+
# strip preceding ! operator
285+
if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
286+
startpos += length(m.match)
287+
end
288+
289+
name = string[max(startpos, dotpos + 1):pos]
290+
comp_keywords = !isempty(name) && startpos > dotpos
291+
if afterusing(string, startpos)
292+
# We're right after using or import. Let's look only for packages
293+
# and modules we can reach from here
294+
295+
# If there's no dot, we're in toplevel, so we should
296+
# also search for packages
297+
s = string[startpos:pos]
298+
if dotpos <= startpos
299+
for dir in Base.load_path()
300+
if basename(dir) in Base.project_names && isfile(dir)
301+
append!(suggestions, project_deps_get_completion_candidates(s, dir))
302+
end
303+
isdir(dir) || continue
304+
for entry in _readdirx(dir)
305+
pname = entry.name
306+
if pname[1] != '.' &&
307+
pname != "METADATA" &&
308+
pname != "REQUIRE" &&
309+
startswith(pname, s)
310+
# Valid file paths are
311+
# <Mod>.jl
312+
# <Mod>/src/<Mod>.jl
313+
# <Mod>.jl/src/<Mod>.jl
314+
if isfile(entry)
315+
endswith(pname, ".jl") && push!(
316+
suggestions,
317+
PackageCompletion(pname[1:prevind(pname, end - 2)]),
318+
)
319+
else
320+
mod_name = if endswith(pname, ".jl")
321+
pname[1:prevind(pname, end - 2)]
322+
else
323+
pname
324+
end
325+
if isfile(joinpath(entry, "src", "$mod_name.jl"))
326+
push!(suggestions, PackageCompletion(mod_name))
327+
end
328+
end
329+
end
330+
end
331+
end
332+
end
333+
ffunc = module_filter
334+
comp_keywords = false
335+
end
336+
337+
startpos == 0 && (pos = -1)
338+
dotpos < startpos && (dotpos = startpos - 1)
339+
return complete_identifiers!(
340+
suggestions,
341+
ffunc,
342+
context_module,
343+
string,
344+
name,
345+
pos,
346+
dotpos,
347+
startpos;
348+
comp_keywords,
349+
)
350+
end
351+
352+
end # module OriginalREPLCompletions

0 commit comments

Comments
 (0)