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" (\\ +)\g 1(\\ ?)`" => " \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