Define fallback font for missing glyphs in LuaLaTeX

While this doesn't use fontspec, you can use Ulrike Fischer's experimental combofont package. This uses luaotfload's built-in support for combining fonts:

\documentclass{article}
\usepackage{verbatim}
\usepackage{combofont}
% Define a combofont for every font in the family
\setupcombofont{hack-symbola-regular}{
  {Hack:\combodefaultfeat} at #1pt,
  {Symbola:\combodefaultfeat} at #1pt
}{
  {},
  fallback
}

\setupcombofont{hack-symbola-bold}{
  {Hack/B:\combodefaultfeat} at #1pt,
  {Symbola:\combodefaultfeat} at #1pt
}{
  {},
  fallback
}

\setupcombofont{hack-symbola-italic}{
  {Hack/I:\combodefaultfeat} at #1pt,
  {Symbola:\combodefaultfeat} at #1pt
}{
  {},
  fallback
}

\setupcombofont{hack-symbola-bolditalic}{
  {Hack/BI:\combodefaultfeat} at #1pt,
  {Symbola:\combodefaultfeat} at #1pt
}{
  {},
  fallback
}

% Now set up the family
\DeclareFontFamily{TU}{hack-symbola}{}
\DeclareFontShape{TU}{hack-symbola}{m}{n} {<->combo*hack-symbola-regular}{}
\DeclareFontShape{TU}{hack-symbola}{bx}{n} {<->combo*hack-symbola-bold}{}
\DeclareFontShape{TU}{hack-symbola}{m}{it} {<->combo*hack-symbola-italic}{}
\DeclareFontShape{TU}{hack-symbola}{bx}{it} {<->combo*hack-symbola-bolditalic}{}
\renewcommand \ttdefault {hack-symbola}

\begin{document}
\begin{verbatim}
-  project-root
    -  .gitignore (sub)
    -  README.md (sub)
\end{verbatim}
\end{document}

This sets up the entire family of Hack, if you only need the regular one (This is the only one used by verbatim as far as I know) you can make it much shorter:

\documentclass{article}
\usepackage{verbatim}
\usepackage{combofont}
% Define a combofont for every font in the family
\setupcombofont{hack-symbola-regular}{
  {Hack:\combodefaultfeat} at #1pt,
  {Symbola:\combodefaultfeat} at #1pt
}{
  {},
  fallback
}

% Now set up the family
\DeclareFontFamily{TU}{hack-symbola}{}
\DeclareFontShape{TU}{hack-symbola}{m}{n} {<->combo*hack-symbola-regular}{}
\renewcommand \ttdefault {hack-symbola}

\begin{document}
\begin{verbatim}
-  project-root
    -  .gitignore (sub)
    -  README.md (sub)
\end{verbatim}
\end{document}

enter image description here


This is what I could come up with:

\documentclass{article}
\usepackage{fontspec}
\setmonofont{Hack}
\usepackage{verbatim}

\newfontface\Symbola{Symbola}
{\Symbola\global\expandafter\let\expandafter\fallbackfont\the\font}
\usepackage{luacode}
\begin{luacode}
local fontcharacters = { }
local nullfont       = 0
local glyph_t        = nodes.nodecodes.glyph
local fallbackfont   = font.id("fallbackfont")
table.setmetatableindex(fontcharacters, function (t, k)
  if k == true then
    return fontcharacters[currentfont()]
  else
    local tfmdata = fonts.hashes.identifiers[k]
    if not tfmdata then --- unsafe
      tfmdata = font.fonts[k]
      if not (tfmdata and type (tfmdata) == "table") then
        return false
      end
    end
    local characters = tfmdata.characters
    t[k] = characters
    return characters
  end
end)
local nodeprocessor = function (head)
  local lastfont, characters = nil, nil
  for n in node.traverse_id(glyph_t, head) do
    local currfont = n.font
    local char = n.char
    if currfont ~= lastfont and currfont ~= nullfont then
      characters = fontcharacters[currfont]
    end
    if characters ~= false then
      lastfont = currfont
      if not characters[char] then
        n.font = fallbackfont
        if fontcharacters[fallbackfont][char] then
          node.remove(head,node.next(n)) -- remove .notdef char (new in luaotfload 2.98)
        end
      end
    end
  end
  return head, false
end
luatexbase.add_to_callback("pre_linebreak_filter", nodeprocessor, "replace missing glyphs")
\end{luacode}

\begin{document}
\begin{verbatim}
-  project-root
    -  .gitignore (sub)
    -  README.md (sub)
\end{verbatim}
\end{document}

result