1defmodule IEx.Autocomplete do 2 @moduledoc false 3 4 @doc """ 5 Provides one helper function that is injected into connecting 6 remote nodes to properly handle autocompletion. 7 """ 8 def remsh(node) do 9 fn e -> 10 case :rpc.call(node, IEx.Autocomplete, :expand, [e, IEx.Broker.shell()]) do 11 {:badrpc, _} -> {:no, '', []} 12 r -> r 13 end 14 end 15 end 16 17 @doc """ 18 The expansion logic. 19 20 Some of the expansion has to be use the current shell 21 environment, which is found via the broker. 22 """ 23 def expand(code, shell \\ IEx.Broker.shell()) do 24 code = Enum.reverse(code) 25 helper = get_helper(code) 26 27 case Code.cursor_context(code) do 28 {:alias, alias} -> 29 expand_aliases(List.to_string(alias), shell) 30 31 {:unquoted_atom, unquoted_atom} -> 32 expand_erlang_modules(List.to_string(unquoted_atom)) 33 34 expansion when helper == ?b -> 35 expand_typespecs(expansion, shell, &get_module_callbacks/1) 36 37 expansion when helper == ?t -> 38 expand_typespecs(expansion, shell, &get_module_types/1) 39 40 {:dot, path, hint} -> 41 expand_dot(path, List.to_string(hint), shell) 42 43 {:dot_arity, path, hint} -> 44 expand_dot(path, List.to_string(hint), shell) 45 46 {:dot_call, path, hint} -> 47 expand_dot_call(path, List.to_atom(hint), shell) 48 49 :expr -> 50 expand_local_or_var("", shell) 51 52 {:local_or_var, local_or_var} -> 53 expand_local_or_var(List.to_string(local_or_var), shell) 54 55 {:local_arity, local} -> 56 expand_local(List.to_string(local), shell) 57 58 {:local_call, local} -> 59 expand_local_call(List.to_atom(local), shell) 60 61 # {:module_attribute, charlist} 62 # :none 63 _ -> 64 no() 65 end 66 end 67 68 defp get_helper(expr) do 69 with [helper | rest] when helper in 'bt' <- expr, 70 [space_or_paren, char | _] <- squeeze_spaces(rest), 71 true <- 72 space_or_paren in ' (' and 73 (char in ?A..?Z or char in ?a..?z or char in ?0..?9 or char in '_:') do 74 helper 75 else 76 _ -> nil 77 end 78 end 79 80 defp squeeze_spaces(' ' ++ rest), do: squeeze_spaces([?\s | rest]) 81 defp squeeze_spaces(rest), do: rest 82 83 @doc false 84 def exports(mod) do 85 if Code.ensure_loaded?(mod) and function_exported?(mod, :__info__, 1) do 86 mod.__info__(:macros) ++ (mod.__info__(:functions) -- [__info__: 1]) 87 else 88 mod.module_info(:exports) -- [module_info: 0, module_info: 1] 89 end 90 end 91 92 ## Typespecs 93 94 defp expand_typespecs({:dot, path, hint}, shell, fun) do 95 hint = List.to_string(hint) 96 97 case expand_dot_path(path, shell) do 98 {:ok, mod} when is_atom(mod) -> 99 mod 100 |> fun.() 101 |> match_module_funs(hint) 102 |> format_expansion(hint) 103 104 _ -> 105 no() 106 end 107 end 108 109 defp expand_typespecs(_, _, _), do: no() 110 111 ## Expand call 112 113 defp expand_local_call(fun, shell) do 114 imports_from_env(shell) 115 |> Enum.filter(fn {_, funs} -> List.keymember?(funs, fun, 0) end) 116 |> Enum.flat_map(fn {module, _} -> get_signatures(fun, module) end) 117 |> expand_signatures(shell) 118 end 119 120 defp expand_dot_call(path, fun, shell) do 121 case expand_dot_path(path, shell) do 122 {:ok, mod} when is_atom(mod) -> get_signatures(fun, mod) |> expand_signatures(shell) 123 _ -> no() 124 end 125 end 126 127 defp get_signatures(name, module) when is_atom(module) do 128 with docs when is_list(docs) <- get_docs(module, [:function, :macro], name) do 129 Enum.map(docs, fn {_, _, signatures, _, _} -> Enum.join(signatures, " ") end) 130 else 131 _ -> [] 132 end 133 end 134 135 defp expand_signatures([_ | _] = signatures, _shell) do 136 [head | tail] = Enum.sort(signatures, &(String.length(&1) <= String.length(&2))) 137 if tail != [], do: IO.write("\n" <> (tail |> Enum.reverse() |> Enum.join("\n"))) 138 yes("", [head]) 139 end 140 141 defp expand_signatures([], shell), do: expand_local_or_var("", shell) 142 143 ## Expand dot 144 145 defp expand_dot(path, hint, shell) do 146 case expand_dot_path(path, shell) do 147 {:ok, mod} when is_atom(mod) and hint == "" -> expand_aliases(mod, "", [], true) 148 {:ok, mod} when is_atom(mod) -> expand_require(mod, hint) 149 {:ok, map} when is_map(map) -> expand_map_field_access(map, hint) 150 _ -> no() 151 end 152 end 153 154 defp expand_dot_path({:var, var}, shell) do 155 value_from_binding({List.to_atom(var), [], nil}, shell) 156 end 157 158 defp expand_dot_path({:alias, var}, shell) do 159 var |> List.to_string() |> String.split(".") |> value_from_alias(shell) 160 end 161 162 defp expand_dot_path({:unquoted_atom, var}, _shell) do 163 {:ok, List.to_atom(var)} 164 end 165 166 defp expand_dot_path({:module_attribute, _}, _shell) do 167 :error 168 end 169 170 defp expand_dot_path({:dot, parent, call}, shell) do 171 case expand_dot_path(parent, shell) do 172 {:ok, %{} = map} -> Map.fetch(map, List.to_atom(call)) 173 _ -> :error 174 end 175 end 176 177 defp expand_map_field_access(map, hint) do 178 case match_map_fields(map, hint) do 179 [%{kind: :map_key, name: ^hint, value_is_map: false}] -> no() 180 map_fields when is_list(map_fields) -> format_expansion(map_fields, hint) 181 end 182 end 183 184 defp expand_require(mod, hint) do 185 format_expansion(match_module_funs(get_module_funs(mod), hint), hint) 186 end 187 188 ## Expand local or var 189 190 defp expand_local_or_var(hint, shell) do 191 format_expansion(match_var(hint, shell) ++ match_local(hint, shell), hint) 192 end 193 194 defp expand_local(hint, shell) do 195 format_expansion(match_local(hint, shell), hint) 196 end 197 198 defp match_local(hint, shell) do 199 imports = imports_from_env(shell) |> Enum.flat_map(&elem(&1, 1)) 200 module_funs = get_module_funs(Kernel.SpecialForms) 201 match_module_funs(imports ++ module_funs, hint) 202 end 203 204 defp match_var(hint, shell) do 205 variables_from_binding(hint, shell) 206 |> Enum.sort() 207 |> Enum.map(&%{kind: :variable, name: &1}) 208 end 209 210 ## Erlang modules 211 212 defp expand_erlang_modules(hint) do 213 format_expansion(match_erlang_modules(hint), hint) 214 end 215 216 defp match_erlang_modules(hint) do 217 for mod <- match_modules(hint, true), usable_as_unquoted_module?(mod) do 218 %{kind: :module, name: mod, type: :erlang} 219 end 220 end 221 222 ## Elixir modules 223 224 defp expand_aliases(all, shell) do 225 case String.split(all, ".") do 226 [hint] -> 227 aliases = match_aliases(hint, shell) 228 expand_aliases(Elixir, hint, aliases, false) 229 230 parts -> 231 hint = List.last(parts) 232 list = Enum.take(parts, length(parts) - 1) 233 234 case value_from_alias(list, shell) do 235 {:ok, alias} -> expand_aliases(alias, hint, [], false) 236 :error -> no() 237 end 238 end 239 end 240 241 defp expand_aliases(mod, hint, aliases, include_funs?) do 242 aliases 243 |> Kernel.++(match_elixir_modules(mod, hint)) 244 |> Kernel.++(if include_funs?, do: match_module_funs(get_module_funs(mod), hint), else: []) 245 |> format_expansion(hint) 246 end 247 248 defp value_from_alias([name | rest], shell) when is_binary(name) do 249 name = String.to_atom(name) 250 251 case Keyword.fetch(aliases_from_env(shell), Module.concat(Elixir, name)) do 252 {:ok, name} when rest == [] -> {:ok, name} 253 {:ok, name} -> {:ok, Module.concat([name | rest])} 254 :error -> {:ok, Module.concat([name | rest])} 255 end 256 end 257 258 defp value_from_alias([_ | _], _) do 259 :error 260 end 261 262 defp match_aliases(hint, shell) do 263 for {alias, _mod} <- aliases_from_env(shell), 264 [name] = Module.split(alias), 265 String.starts_with?(name, hint) do 266 %{kind: :module, type: :alias, name: name} 267 end 268 end 269 270 defp match_elixir_modules(module, hint) do 271 name = Atom.to_string(module) 272 depth = length(String.split(name, ".")) + 1 273 base = name <> "." <> hint 274 275 for mod <- match_modules(base, module == Elixir), 276 parts = String.split(mod, "."), 277 depth <= length(parts), 278 name = Enum.at(parts, depth - 1), 279 valid_alias_piece?("." <> name), 280 uniq: true, 281 do: %{kind: :module, type: :elixir, name: name} 282 end 283 284 defp valid_alias_piece?(<<?., char, rest::binary>>) when char in ?A..?Z, 285 do: valid_alias_rest?(rest) 286 287 defp valid_alias_piece?(_), do: false 288 289 defp valid_alias_rest?(<<char, rest::binary>>) 290 when char in ?A..?Z 291 when char in ?a..?z 292 when char in ?0..?9 293 when char == ?_, 294 do: valid_alias_rest?(rest) 295 296 defp valid_alias_rest?(<<>>), do: true 297 defp valid_alias_rest?(rest), do: valid_alias_piece?(rest) 298 299 ## Formatting 300 301 defp format_expansion([], _) do 302 no() 303 end 304 305 defp format_expansion([uniq], hint) do 306 case to_hint(uniq, hint) do 307 "" -> yes("", to_uniq_entries(uniq)) 308 hint -> yes(hint, []) 309 end 310 end 311 312 defp format_expansion([first | _] = entries, hint) do 313 binary = Enum.map(entries, & &1.name) 314 length = byte_size(hint) 315 prefix = :binary.longest_common_prefix(binary) 316 317 if prefix in [0, length] do 318 yes("", Enum.flat_map(entries, &to_entries/1)) 319 else 320 yes(binary_part(first.name, prefix, length - prefix), []) 321 end 322 end 323 324 defp yes(hint, entries) do 325 {:yes, String.to_charlist(hint), Enum.map(entries, &String.to_charlist/1)} 326 end 327 328 defp no do 329 {:no, '', []} 330 end 331 332 ## Helpers 333 334 defp usable_as_unquoted_module?(name) do 335 # Conversion to atom is not a problem because 336 # it is only called with existing modules names. 337 Code.Identifier.classify(String.to_atom(name)) != :other 338 end 339 340 defp match_modules(hint, root) do 341 get_modules(root) 342 |> Enum.sort() 343 |> Enum.dedup() 344 |> Enum.drop_while(&(not String.starts_with?(&1, hint))) 345 |> Enum.take_while(&String.starts_with?(&1, hint)) 346 end 347 348 defp get_modules(true) do 349 ["Elixir.Elixir"] ++ get_modules(false) 350 end 351 352 defp get_modules(false) do 353 modules = Enum.map(:code.all_loaded(), &Atom.to_string(elem(&1, 0))) 354 355 case :code.get_mode() do 356 :interactive -> modules ++ get_modules_from_applications() 357 _otherwise -> modules 358 end 359 end 360 361 defp get_modules_from_applications do 362 for [app] <- loaded_applications(), 363 {:ok, modules} = :application.get_key(app, :modules), 364 module <- modules do 365 Atom.to_string(module) 366 end 367 end 368 369 defp loaded_applications do 370 # If we invoke :application.loaded_applications/0, 371 # it can error if we don't call safe_fixtable before. 372 # Since in both cases we are reaching over the 373 # application controller internals, we choose to match 374 # for performance. 375 :ets.match(:ac_tab, {{:loaded, :"$1"}, :_}) 376 end 377 378 defp match_module_funs(funs, hint) do 379 for {fun, arity} <- funs, name = Atom.to_string(fun), String.starts_with?(name, hint) do 380 %{ 381 kind: :function, 382 name: name, 383 arity: arity 384 } 385 end 386 |> Enum.sort_by(&{&1.name, &1.arity}) 387 end 388 389 defp match_map_fields(map, hint) do 390 for {key, value} when is_atom(key) <- Map.to_list(map), 391 key = Atom.to_string(key), 392 String.starts_with?(key, hint) do 393 %{kind: :map_key, name: key, value_is_map: is_map(value)} 394 end 395 |> Enum.sort_by(& &1.name) 396 end 397 398 defp get_module_funs(mod) do 399 cond do 400 not ensure_loaded?(mod) -> 401 [] 402 403 docs = get_docs(mod, [:function, :macro]) -> 404 exports(mod) 405 |> Kernel.--(default_arg_functions_with_doc_false(docs)) 406 |> Enum.reject(&hidden_fun?(&1, docs)) 407 408 true -> 409 exports(mod) 410 end 411 end 412 413 defp get_module_types(mod) do 414 if ensure_loaded?(mod) do 415 case Code.Typespec.fetch_types(mod) do 416 {:ok, types} -> 417 for {kind, {name, _, args}} <- types, 418 kind in [:type, :opaque] do 419 {name, length(args)} 420 end 421 422 :error -> 423 [] 424 end 425 else 426 [] 427 end 428 end 429 430 defp get_module_callbacks(mod) do 431 if ensure_loaded?(mod) do 432 case Code.Typespec.fetch_callbacks(mod) do 433 {:ok, callbacks} -> 434 for {name_arity, _} <- callbacks do 435 {_kind, name, arity} = IEx.Introspection.translate_callback_name_arity(name_arity) 436 437 {name, arity} 438 end 439 440 :error -> 441 [] 442 end 443 else 444 [] 445 end 446 end 447 448 defp get_docs(mod, kinds, fun \\ nil) do 449 case Code.fetch_docs(mod) do 450 {:docs_v1, _, _, _, _, _, docs} -> 451 if is_nil(fun) do 452 for {{kind, _, _}, _, _, _, _} = doc <- docs, kind in kinds, do: doc 453 else 454 for {{kind, ^fun, _}, _, _, _, _} = doc <- docs, kind in kinds, do: doc 455 end 456 457 {:error, _} -> 458 nil 459 end 460 end 461 462 defp default_arg_functions_with_doc_false(docs) do 463 for {{_, fun_name, arity}, _, _, :hidden, %{defaults: count}} <- docs, 464 new_arity <- (arity - count)..arity, 465 do: {fun_name, new_arity} 466 end 467 468 defp hidden_fun?({name, arity}, docs) do 469 case Enum.find(docs, &match?({{_, ^name, ^arity}, _, _, _, _}, &1)) do 470 nil -> hd(Atom.to_charlist(name)) == ?_ 471 {_, _, _, %{}, _} -> false 472 {_, _, _, _, _} -> true 473 end 474 end 475 476 defp ensure_loaded?(Elixir), do: false 477 defp ensure_loaded?(mod), do: Code.ensure_loaded?(mod) 478 479 ## Ad-hoc conversions 480 481 defp to_entries(%{kind: kind, name: name}) 482 when kind in [:map_key, :module, :variable] do 483 [name] 484 end 485 486 defp to_entries(%{kind: :function, name: name, arity: arity}) do 487 ["#{name}/#{arity}"] 488 end 489 490 defp to_uniq_entries(%{kind: kind}) 491 when kind in [:map_key, :module, :variable] do 492 [] 493 end 494 495 defp to_uniq_entries(%{kind: :function} = fun) do 496 to_entries(fun) 497 end 498 499 defp to_hint(%{kind: :module, name: name}, hint) when name == hint do 500 format_hint(name, name) <> "." 501 end 502 503 defp to_hint(%{kind: :map_key, name: name, value_is_map: true}, hint) when name == hint do 504 format_hint(name, hint) <> "." 505 end 506 507 defp to_hint(%{kind: kind, name: name}, hint) 508 when kind in [:function, :map_key, :module, :variable] do 509 format_hint(name, hint) 510 end 511 512 defp format_hint(name, hint) do 513 hint_size = byte_size(hint) 514 binary_part(name, hint_size, byte_size(name) - hint_size) 515 end 516 517 ## Evaluator interface 518 519 defp imports_from_env(shell) do 520 with {evaluator, server} <- IEx.Broker.evaluator(shell), 521 env_fields = IEx.Evaluator.fields_from_env(evaluator, server, [:functions, :macros]), 522 %{functions: funs, macros: macros} <- env_fields do 523 funs ++ macros 524 else 525 _ -> [] 526 end 527 end 528 529 defp aliases_from_env(shell) do 530 with {evaluator, server} <- IEx.Broker.evaluator(shell), 531 %{aliases: aliases} <- IEx.Evaluator.fields_from_env(evaluator, server, [:aliases]) do 532 aliases 533 else 534 _ -> [] 535 end 536 end 537 538 defp variables_from_binding(hint, shell) do 539 with {evaluator, server} <- IEx.Broker.evaluator(shell) do 540 IEx.Evaluator.variables_from_binding(evaluator, server, hint) 541 else 542 _ -> [] 543 end 544 end 545 546 defp value_from_binding(ast_node, shell) do 547 with {evaluator, server} <- IEx.Broker.evaluator(shell), 548 {var, map_key_path} <- extract_from_ast(ast_node, []) do 549 IEx.Evaluator.value_from_binding(evaluator, server, var, map_key_path) 550 else 551 _ -> :error 552 end 553 end 554 555 defp extract_from_ast(var_name, acc) when is_atom(var_name) do 556 {var_name, acc} 557 end 558 559 defp extract_from_ast({var_name, _, nil}, acc) when is_atom(var_name) do 560 {var_name, acc} 561 end 562 563 defp extract_from_ast({{:., _, [ast_node, fun]}, _, []}, acc) when is_atom(fun) do 564 extract_from_ast(ast_node, [fun | acc]) 565 end 566 567 defp extract_from_ast(_ast_node, _acc) do 568 :error 569 end 570end 571