HackTree.define do
  desc <<-EOT
    List groups/hacks globally
    
    Examples:

      >> c.ls /^ls$/
      ls               # List groups/hacks globally

      >> c.ls /he/
      hello            # Say hello to the world or to a specific person

      >> c.ls /person/
      hello            # Say hello to the world or to a specific person

      >> c.ls "dsl"
      dsl/             # Get domain-specific language contexts
      dsl.gemfile      # Get Bundler `Gemfile` context
      dsl.rspec        # Get RSpec specfile context

      >> c.ls "dsl.r"
      dsl.rspec        # Get RSpec specfile context
  EOT
  hack :ls do |filter = nil|
    nodes = @nodes

    # Apply filter.
    if filter
      re = case filter
      when Regexp
        filter
      when String
        Regexp.compile(Regexp.escape(filter))
      else
        raise ArgumentError, "Unsupported filter #{filter.inspect}"
      end

      nodes = nodes.select do |r|
        [
          # Logical order.
          r.global_name =~ re,
          r.brief_desc && r.brief_desc =~ re,

          # Might some time include search in `full_desc` as an option.
          # Must not be enabled by default since it returns too many results.
          #r.full_desc && r.full_desc =~ re,
        ].any?
      end
    end

    nodes = nodes.sort_by do |node|
      [
        node.is_a?(::HackTree::Node::Group) ? 0 : 1,    # Groups first.
        node.global_name,
      ]
    end

    # Compute name alignment width.
    names = nodes.map {|node| ::HackTree::Tools.format_node_name(node)}
    name_align = ::HackTree::Tools.compute_name_align(names, @conf.global_name_align)

    nodes.each do |node|
      brief_desc = node.brief_desc || ::HackTree.conf.brief_desc_stub

      fmt = "%-#{name_align}s%s"

      ::Kernel.puts(fmt % [
        ::HackTree::Tools.format_node_name(node, node.global_name),
        brief_desc ? " # #{brief_desc}" : "",
      ])
    end # nodes.each

    nil
  end
end