lib/mix/complete.ex

defmodule Mix.Tasks.Complete do
  use Mix.Task

  @shortdoc "Cache tab completions for mix"
  def run(_) do
    loadpaths!()

    modules = load_tasks()
    aliases = load_aliases()

    docs = build_doc_list(modules, aliases)

    text =
      docs
      |> Enum.sort()
      |> Enum.map(fn {task, shortdoc} -> "#{task}\t#{shortdoc}" end)
      |> Enum.join("\n")

    # text = elem(System.shell("mix help --names"), 0)
    cache_dir =
      case System.get_env("XDG_CACHE_HOME") do
        nil -> Path.join([System.get_env("HOME"), ".cache"])
        "" -> Path.join([System.get_env("HOME"), ".cache"])
        xdg_cache_home -> xdg_cache_home
      end

    if File.exists?("mix.exs") do
      File.write(".mix_complete.cache", text)
      IO.puts(:stderr, "cache updated in project directory")
    else
      File.write(Path.join([cache_dir, ".mix_complete.cache"]), text)
      IO.puts(:stderr, "cache updated in #{cache_dir}")
    end
  end

  defp loadpaths! do
    args = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
    Mix.Task.run("loadpaths", args)
    Mix.Task.reenable("loadpaths")
    Mix.Task.reenable("deps.loadpaths")
  end

  defp load_tasks() do
    Enum.filter(Mix.Task.load_all(), &(Mix.Task.moduledoc(&1) != false))
  end

  defp load_aliases() do
    aliases = Mix.Project.config()[:aliases]

    Map.new(aliases, fn {alias_name, alias_tasks} -> {Atom.to_string(alias_name), alias_tasks} end)
  end

  defp build_doc_list(modules, aliases) do
    task_docs = build_task_doc_list(modules)
    alias_docs = build_alias_doc_list(aliases)
    task_docs ++ alias_docs
  end

  defp build_task_doc_list(modules) do
    Enum.reduce(modules, [], fn module, docs ->
      if doc = Mix.Task.shortdoc(module) do
        task = Mix.Task.task_name(module)
        [{task, doc} | docs]
      else
        docs
      end
    end)
  end

  defp build_alias_doc_list(aliases) do
    Enum.reduce(aliases, [], fn {alias_name, _task_name}, docs ->
      doc = "Alias defined in mix.exs"
      task = alias_name
      [{task, doc} | docs]
    end)
  end
end