Headings Picker

Hey there,

originating from my thread Headings chooser possible as a plugin? I finally made it and came up with my first plugin:

Basically you can have a picker (by default opened with Ctrl+r) for headings of the active page, similar to how Sublime Text can list markdown headings this way.

Maybe this plugin is useful for others as well. If you have improvement ideas, feel free to share the ideas here. Just keep in mind that this is my first plugin and I basically have no experience really so far! :smiley:

11 Likes

Well, this might be something I never want to miss again. Simple way to improve the navigation within the page.

Thank you for creating and sharing this plug!

Cheers
CF

1 Like

Thanks for your positive feedback. I am glad you find it useful as well! (=

Very nice! Instead of using separate daily notes, I have a long “journal” page where I add bullet notes Logseq-style, separated by headings with today’s date. This makes it easy to jump to different dates in it. Thank a lot for this!

1 Like

I am happy to read that this little plugin is useful to you, too. Cool! (=

1 Like

Space-lua version,


function headingsPicker(options)

  local text = editor.getText()
  local pageName = editor.getCurrentPage()
  local parsedMarkdown = markdown.parseMarkdown(text)

  -- Collect all headers
  local headers = {}
  for topLevelChild in parsedMarkdown.children do
    if topLevelChild.type then
      local headerLevel = string.match(topLevelChild.type, "^ATXHeading(%d+)")
      if headerLevel then
        local text = ""
        table.remove(topLevelChild.children, 1)
        for child in topLevelChild.children do
          text = text .. string.trim(markdown.renderParseTree(child))
        end

        if text != "" then
          table.insert(headers, {
            name = string.rep("⠀⠀", headerLevel-1) .. " 🔹 " .. text,
            pos = topLevelChild.from,
            description = "",
          })
        end
      end
    end
  end

    local result = editor.filterBox("Select:", headers, "Headers")

    if result and result.pos then
      editor.moveCursor(result.pos, true)
    end
end


command.define {
  name = "Pick Headings",
  key = "Ctrl-h",
  run = function() headingsPicker({}) end
}

7 Likes

these are those things why i love silverbullet so much. you don’t know you need a feature until you try it out. And after you tried it out, you cant live without it. This Plug/Lua-script is exactly like that. Thank you both for making this!

1 Like

That’s very useful, thanks a lot! I used the Space-Lua version and renamed the command to “Navigate: Heading Picker” so it goes better the other location pickers.

2 Likes

Pick Headings with CMD-Tree UI

Yet another Navigate: Heading Picker -_-||

-- Pick Headings with CMD-like Tree UI
local function headingsPicker(options)
  local text = editor.getText()
  local parsed = markdown.parseMarkdown(text)

  local nodes = {}

  local function detect_level(node)
    if node.tag then
      local m = string.match(node.tag, "ATXHeading%s*(%d+)")
      if m then return tonumber(m) end
    end
    if node.type then
      local m = string.match(node.type, "ATXHeading%s*(%d+)") or string.match(node.type, "Heading(%d+)")
      if m then return tonumber(m) end
    end
    return nil
  end

  local function node_pos(node)
    return node.from or node.pos or node.name
  end

  for _, n in ipairs(parsed.children or {}) do
    local level = detect_level(n)
    if level then
      local children = {}
      if n.children then
        for i, c in ipairs(n.children) do
          if i > 1 then table.insert(children, c) end
        end
      end

      local parts = {}
      for _, c in ipairs(children) do
        local rendered = markdown.renderParseTree(c)
        if rendered and rendered ~= "" then
          table.insert(parts, string.trim(rendered))
        end
      end
      local title = table.concat(parts, "")

      if title ~= "" then
        table.insert(nodes, {
          level = level,
          text  = title,
          pos   = node_pos(n)
        })
      end
    end
  end

  if #nodes == 0 then
    editor.flashNotification("No headings found")
    return
  end

  local last_flags = {}
  for i = 1, #nodes do
    local L = nodes[i].level
    local is_last = true
    for j = i + 1, #nodes do
      if nodes[j].level <= L then
        if nodes[j].level == L then
          is_last = false
        else
          is_last = true
        end
        break
      end
    end
    last_flags[i] = is_last
  end

  local VERT = "│   "
  local BLNK = "   "
  local TEE  = "├─── "
  local ELB  = "└─── "

  local items = {}
  local stack = {}

  for i = 1, #nodes do
    local L = nodes[i].level
    local is_last = last_flags[i]

    while #stack >= L do table.remove(stack) end

    local prefix = ""
    for d = 1, #stack do
      prefix = prefix .. (stack[d].last and BLNK or VERT)
    end
    
    for d = #stack + 1, L - 1 do
      prefix = prefix .. BLNK
    end

    local elbow = is_last and ELB or TEE
    local label = prefix .. elbow .. nodes[i].text

    table.insert(items, {
      name = label,
      description = "",
      pos = nodes[i].pos
    })

    table.insert(stack, { level = L, last = is_last })
  end

  local result = editor.filterBox("Heading Search:", items, "Select a Header")
  local page = editor.getCurrentPage()

  if result and result.selected and result.selected.value then
    local item = result.selected.value
    if item.pos then editor.navigate({ page = page, pos = item.pos }) end
  elseif result and result.pos then
    editor.navigate({ page = page, pos = result.pos })
  end
end

command.define({
  name = "Navigate: Heading Picker",
  key = "Ctrl-Shift-h",
  run = function() headingsPicker({}) end
})

Yet another Navigate: Tag Picker -_-||

2 Likes

Page + Header Picker

aka: Tree-Tree Picker

Funny, ha? This reveals the fact that these 2 structures are equivalent:
pure outlines[1] = (out-page) dirs + (in-page) headings[2]

Step 1: Pick Page-Tree

Step 2: Pick Header-Tree

Esc to fall back to Step 1: Pick Page-Tree

Code

local pageTreePicker 

local function pickHeadings(pageName)
  local text = space.readPage(pageName)
  if not text then
    editor.flashNotification("Could not read page: " .. pageName)
    return
  end

  local parsed = markdown.parseMarkdown(text)
  local nodes = {}

  local function detect_level(node)
    if node.tag then
      local m = string.match(node.tag, "ATXHeading%s*(%d+)")
      if m then return tonumber(m) end
    end
    if node.type then
      local m = string.match(node.type, "ATXHeading%s*(%d+)") or string.match(node.type, "Heading(%d+)")
      if m then return tonumber(m) end
    end
    return nil
  end

  local function node_pos(node)
    return node.from or node.pos or node.name
  end

  for _, n in ipairs(parsed.children or {}) do
    local level = detect_level(n)
    if level then
      local children = {}
      if n.children then
        for i, c in ipairs(n.children) do
          if i > 1 then table.insert(children, c) end
        end
      end

      local parts = {}
      for _, c in ipairs(children) do
        local rendered = markdown.renderParseTree(c)
        if rendered and rendered ~= "" then
          table.insert(parts, string.trim(rendered))
        end
      end
      local title = table.concat(parts, "")

      if title ~= "" then
        table.insert(nodes, {
          level = level,
          text  = title,
          pos   = node_pos(n)
        })
      end
    end
  end

  if #nodes == 0 then
    editor.navigate({ page = pageName })
    editor.invokeCommand("Navigate: Center Cursor")
    return
  end
  
  local min_level = 10
  for _, n in ipairs(nodes) do
    if n.level < min_level then min_level = n.level end
  end

  local last_flags = {}
  for i = 1, #nodes do
    local L = nodes[i].level
    local is_last = true
    for j = i + 1, #nodes do
      if nodes[j].level <= L then
        if nodes[j].level == L then
          is_last = false
        else
          is_last = true
        end
        break
      end
    end
    last_flags[i] = is_last
  end

  local VERT = "│   "
  local BLNK = "   "
  local TEE  = "├─── "
  local ELB  = "└─── "

  local items = {}
  local stack = {} 

  table.insert(items, {
    name = ".",
    -- description = "Go to page root",
    pos = 0
  })

  for i = 1, #nodes do
    local L = nodes[i].level - min_level + 1
    local is_last = last_flags[i]

    while #stack > 0 and stack[#stack].level >= L do 
      table.remove(stack) 
    end

    local prefix = ""
    for d = 1, #stack do
      prefix = prefix .. (stack[d].last and BLNK or VERT)
    end
    
    for d = #stack + 1, L - 1 do
      prefix = prefix .. BLNK
    end

    local elbow = is_last and ELB or TEE
    local label = prefix .. elbow .. nodes[i].text

    table.insert(items, {
      name = label,
      description = "", 
      pos = nodes[i].pos
    })

    table.insert(stack, { level = L, last = is_last })
  end

  local result = editor.filterBox(pageName .. "#", items, "Select a Header...", "Heading Picker")

  if result then
    local pos = result.pos
    if not pos and result.value and result.value.pos then
        pos = result.value.pos
    end
    
    if pos == 0 then
        editor.navigate({ page = pageName })
    elseif pos then
        editor.navigate({ page = pageName, pos = pos })
    end
    editor.invokeCommand("Navigate: Center Cursor")
  else
    return pageTreePicker()
  end
end

pageTreePicker = function()
  local pages = space.listPages()
  
  local nodes = {}

  local function parse_page_info(page_name)
    local level = 1
    for _ in string.gmatch(page_name, "/") do
      level = level + 1
    end
    local text = page_name:match(".*/(.*)") or page_name
    return level, text
  end

  for _, page in ipairs(pages) do
    local level, text = parse_page_info(page.name)
    table.insert(nodes, {
      level = level,
      text  = text,
      pos   = page.name
    })
  end

  if #nodes == 0 then
    editor.flashNotification("No pages found")
    return
  end

  local last_flags = {}
  for i = 1, #nodes do
    local L = nodes[i].level
    local is_last = true
    
    for j = i + 1, #nodes do
      local next_L = nodes[j].level
      
      if next_L == L then
        is_last = false
        break
      elseif next_L < L then
        is_last = true
        break
      end
    end
    last_flags[i] = is_last
  end

  local VERT = "│   "
  local BLNK = "   "
  local TEE  = "├─── "
  local ELB  = "└─── "

  local items = {}
  local stack = {}

  for i = 1, #nodes do
    local L = nodes[i].level
    local is_last = last_flags[i]

    while #stack >= L do 
      table.remove(stack) 
    end

    local prefix = ""
    for d = 1, #stack do
      prefix = prefix .. (stack[d].last and BLNK or VERT)
    end
    
    for d = #stack + 1, L - 1 do
      prefix = prefix .. BLNK
    end

    local elbow = is_last and ELB or TEE
    local label = prefix .. elbow .. nodes[i].text

    table.insert(items, {
      name = label,
      description = nodes[i].pos,
      value = nodes[i].pos
    })

    table.insert(stack, { level = L, last = is_last })
  end

  local result = editor.filterBox("Pick:", items, "Select a Page...", "Page Tree")

  if result then
    local page_name = result.value or result
    if type(page_name) == "table" and page_name.value then
        page_name = page_name.value
    end
    
    if page_name then
        pickHeadings(page_name)
    end
  end
end

command.define({
  name = "Navigate: Page Tree & Heading Picker",
  key = "Shift-Alt-e",
  run = function() pageTreePicker() end
})

  1. Tana, Workflowy, RoamResearch, RoamEdit… ↩︎

  2. SilverBullet, SiYuan, Obsidian… ↩︎

3 Likes

Well done ! Navigating between pages and then between sections of the chosen page is a great feature! :grinning_face:

1 Like

Tree-Tree Picker

  1. fix aspiring folder causing wrong indent
    • now aspiring folder has a / at its end

  1. add full-path description to the 2nd Heading Picker, to make it searchable through its parent headings (may be a bit of visual noise, you can comment out the description)

local pageTreePicker 

local function pickHeadings(pageName)
  local text = space.readPage(pageName)
  if not text then
    editor.flashNotification("Could not read page: " .. pageName)
    return
  end

  local parsed = markdown.parseMarkdown(text)
  local nodes = {}

  local function detect_level(node)
    if node.tag then
      local m = string.match(node.tag, "ATXHeading%s*(%d+)")
      if m then return tonumber(m) end
    end
    if node.type then
      local m = string.match(node.type, "ATXHeading%s*(%d+)") or string.match(node.type, "Heading(%d+)")
      if m then return tonumber(m) end
    end
    return nil
  end

  local function node_pos(node)
    return node.from or node.pos or node.name
  end

  for _, n in ipairs(parsed.children or {}) do
    local level = detect_level(n)
    if level then
      local children = {}
      if n.children then
        for i, c in ipairs(n.children) do
          if i > 1 then table.insert(children, c) end
        end
      end

      local parts = {}
      for _, c in ipairs(children) do
        local rendered = markdown.renderParseTree(c)
        if rendered and rendered ~= "" then
          table.insert(parts, string.trim(rendered))
        end
      end
      local title = table.concat(parts, "")

      if title ~= "" then
        table.insert(nodes, {
          level = level,
          text  = title,
          pos   = node_pos(n)
        })
      end
    end
  end

  if #nodes == 0 then
    editor.navigate({ page = pageName })
    editor.invokeCommand("Navigate: Center Cursor")
    return
  end
  
  local min_level = 10
  for _, n in ipairs(nodes) do
    if n.level < min_level then min_level = n.level end
  end

  local last_flags = {}
  for i = 1, #nodes do
    local L = nodes[i].level
    local is_last = true
    for j = i + 1, #nodes do
      if nodes[j].level <= L then
        if nodes[j].level == L then
          is_last = false
        else
          is_last = true
        end
        break
      end
    end
    last_flags[i] = is_last
  end

  local VERT = "│   "
  local BLNK = "   "
  local TEE  = "├─── "
  local ELB  = "└─── "

  local items = {}
  local stack = {} 

  table.insert(items, {
    name = ".",
    description = pageName,
    pos = 0
  })

  for i = 1, #nodes do
    local L = nodes[i].level - min_level + 1
    local is_last = last_flags[i]

    while #stack > 0 and stack[#stack].level >= L do 
      table.remove(stack) 
    end

    local prefix = ""
    for d = 1, #stack do
      prefix = prefix .. (stack[d].last and BLNK or VERT)
    end
    
    for d = #stack + 1, L - 1 do
      prefix = prefix .. BLNK
    end

    local path_parts = {}
    for _, s in ipairs(stack) do
      table.insert(path_parts, s.text)
    end
    table.insert(path_parts, nodes[i].text)
    local full_path = table.concat(path_parts, " > ")

    local elbow = is_last and ELB or TEE
    local label = prefix .. elbow .. nodes[i].text

    table.insert(items, {
      name = label,
      description = full_path, 
      pos = nodes[i].pos
    })

    table.insert(stack, { level = L, last = is_last, text = nodes[i].text })
  end

  local result = editor.filterBox(pageName .. "#", items, "Select a Header...", "Heading Picker")

  if result then
    local pos = result.pos
    if not pos and result.value and result.value.pos then
        pos = result.value.pos
    end
    
    if pos == 0 then
        editor.navigate({ page = pageName })
    elseif pos then
        editor.navigate({ page = pageName, pos = pos })
    end
    editor.invokeCommand("Navigate: Center Cursor")
  else
    return pageTreePicker()
  end
end

pageTreePicker = function()
  local pages = space.listPages()
  
  local path_map = {}
  local real_pages = {}
  
  for _, page in ipairs(pages) do
    real_pages[page.name] = true
  end

  for _, page in ipairs(pages) do
    local parts = {}
    for part in string.gmatch(page.name, "[^/]+") do
      table.insert(parts, part)
      local current_path = table.concat(parts, "/")
      
      if not path_map[current_path] then
        path_map[current_path] = {
          name = current_path,
          text = part,
          level = #parts,
          is_real = false
        }
      end
    end
  end

  for path, _ in pairs(real_pages) do
    if path_map[path] then
      path_map[path].is_real = true
    end
  end

  local nodes = {}
  for _, node in pairs(path_map) do
    table.insert(nodes, node)
  end

  table.sort(nodes, function(a, b) 
    return a.name < b.name 
  end)

  if #nodes == 0 then
    editor.flashNotification("No pages found")
    return
  end

  local last_flags = {}
  for i = 1, #nodes do
    local L = nodes[i].level
    local is_last = true
    
    for j = i + 1, #nodes do
      local next_L = nodes[j].level
      
      if next_L == L then
        is_last = false
        break
      elseif next_L < L then
        is_last = true
        break
      end
    end
    last_flags[i] = is_last
  end

  local VERT = "│   "
  local BLNK = "   "
  local TEE  = "├─── "
  local ELB  = "└─── "

  local items = {}
  local stack = {}

  for i = 1, #nodes do
    local L = nodes[i].level
    local is_last = last_flags[i]

    while #stack >= L do 
      table.remove(stack) 
    end

    local prefix = ""
    for d = 1, #stack do
      prefix = prefix .. (stack[d].last and BLNK or VERT)
    end
    
    for d = #stack + 1, L - 1 do
      prefix = prefix .. BLNK
    end

    local elbow = is_last and ELB or TEE
    
    local display_text = nodes[i].text
    local desc = nodes[i].name
    
    if not nodes[i].is_real then
        display_text = display_text .. "/"
        desc = desc .. "/"
    end

    local label = prefix .. elbow .. display_text

    table.insert(items, {
      name = label,
      description = desc,
      value = { 
          page = nodes[i].name, 
          is_real = nodes[i].is_real 
      }
    })

    table.insert(stack, { level = L, last = is_last })
  end

  local result = editor.filterBox("Pick:", items, "Select a Page...", "Page Tree")

  if result then
    local selection = result.value or result
    
    if type(selection) ~= "table" then
       if selection then pickHeadings(selection) end
       return
    end

    local page_name = selection.page
    local is_real = selection.is_real

    if page_name then
        if is_real then
            pickHeadings(page_name)
        else
            editor.flashNotification("Folder selected. Creating page: " .. page_name)
            editor.navigate({ page = page_name })
        end
    end
  end
end

command.define({
  name = "Navigate: Tree-Tree Picker",
  key = "Shift-Alt-e",
  run = function() pageTreePicker() end
})

Giant-Tree Picker

it unified 2 tree-picker in one.
(might be slow but gives you a full view all at once…)

local function getPageHeadings(pageName)
  local text = space.readPage(pageName)
  if not text then return {} end

  local nodes = {}
  local in_code_block = false
  local current_pos = 0
  
  for line, newline in string.gmatch(text, "([^\r\n]*)(\r?\n?)") do
    if line == "" and newline == "" then break end

    if line:match("^```") then 
      in_code_block = not in_code_block 
    end

    if not in_code_block then
      local hashes, title = line:match("^(#+)%s+(.*)")
      if hashes then
        title = title:match("^(.-)%s*$")
        table.insert(nodes, {
          level = #hashes,
          text  = title,
          pos   = current_pos
        })
      end
    end

    current_pos = current_pos + #line + #newline
  end
  
  return nodes
end

local function unifiedTreePicker()
  local pages = space.listPages()
  local path_map = {}
  local real_pages = {}
  
  for _, page in ipairs(pages) do
    real_pages[page.name] = true
  end

  for _, page in ipairs(pages) do
    local parts = {}
    for part in string.gmatch(page.name, "[^/]+") do
      table.insert(parts, part)
      local current_path = table.concat(parts, "/")
      
      if not path_map[current_path] then
        path_map[current_path] = {
          name = current_path,
          text = part,
          level = #parts,
          is_real = false,
          type = "folder"
        }
      end
    end
  end

  for path, _ in pairs(real_pages) do
    if path_map[path] then
      path_map[path].is_real = true
      path_map[path].type = "page"
    end
  end

  local sorted_nodes = {}
  for _, node in pairs(path_map) do
    table.insert(sorted_nodes, node)
  end

  table.sort(sorted_nodes, function(a, b) 
    return a.name < b.name 
  end)

  if #sorted_nodes == 0 then
    editor.flashNotification("No pages found")
    return
  end

  local final_nodes = {}
  
  for _, node in ipairs(sorted_nodes) do
    table.insert(final_nodes, node)
    
    if node.is_real then
      local headings = getPageHeadings(node.name)
      
      if #headings > 0 then
        local min_level = 10
        for _, h in ipairs(headings) do
          if h.level < min_level then min_level = h.level end
        end

        local heading_stack = {}

        for _, h in ipairs(headings) do
          while #heading_stack > 0 and heading_stack[#heading_stack].level >= h.level do
            table.remove(heading_stack)
          end
          
          table.insert(heading_stack, {level = h.level, text = h.text})

          local path_parts = { node.name }
          for _, stack_item in ipairs(heading_stack) do
            table.insert(path_parts, stack_item.text)
          end
          local full_path_desc = table.concat(path_parts, ">")

          local relative_level = h.level - min_level + 1
          local absolute_level = node.level + relative_level
          
          table.insert(final_nodes, {
            name = node.name,
            text = h.text,
            level = absolute_level,
            is_real = false,
            type = "heading",
            pos = h.pos,
            page_name = node.name,
            full_desc = full_path_desc
          })
        end
      end
    end
  end

  local last_flags = {}
  for i = 1, #final_nodes do
    local L = final_nodes[i].level
    local is_last = true
    
    for j = i + 1, #final_nodes do
      local next_L = final_nodes[j].level
      
      if next_L == L then
        is_last = false
        break
      elseif next_L < L then
        is_last = true
        break
      end
    end
    last_flags[i] = is_last
  end

  local VERT = "│   "
  local BLNK = "   "
  local TEE  = "├─── "
  local ELB  = "└─── "

  local items = {}
  local stack = {}

  for i = 1, #final_nodes do
    local node = final_nodes[i]
    local L = node.level
    local is_last = last_flags[i]

    while #stack >= L do 
      table.remove(stack) 
    end

    local prefix = ""
    for d = 1, #stack do
      prefix = prefix .. (stack[d].last and BLNK or VERT)
    end
    
    for d = #stack + 1, L - 1 do
      prefix = prefix .. BLNK
    end

    local elbow = is_last and ELB or TEE
    
    local display_text = node.text
    local desc = ""
    
    if node.type == "folder" then
        display_text = display_text .. "/"
        desc = node.name .. "/"
    elseif node.type == "page" then
        desc = node.name
    elseif node.type == "heading" then
        desc = node.full_desc
    end

    local label = prefix .. elbow .. display_text

    table.insert(items, {
      name = label,
      description = desc,
      value = { 
          page = node.page_name or node.name, 
          pos = node.pos,
          type = node.type
      }
    })

    table.insert(stack, { level = L, last = is_last })
  end

  local result = editor.filterBox("Jump to:", items, "Select Page or Heading...", "Unified Tree")

  if result then
    local selection = result.value or result
    
    if type(selection) ~= "table" then return end

    local page_name = selection.page
    local pos = selection.pos
    local node_type = selection.type

    if node_type == "folder" then
        editor.flashNotification("Folder selected. Creating/Going to page: " .. page_name)
        editor.navigate({ page = page_name })
    elseif node_type == "page" or node_type == "heading" then
        if pos and pos > 0 then
            editor.navigate({ page = page_name, pos = pos })
        else
            editor.navigate({ page = page_name })
        end
        editor.invokeCommand("Navigate: Center Cursor")
    end
  end
end

command.define({
  name = "Navigate: Unified Tree Picker",
  key = "Shift-Alt-e",
  run = function() unifiedTreePicker() end
})
1 Like
  • fix edge cases:

6 → 4 → 2 level,
2 → 4 → 6 level,
2 → 4 → 2 level,
6 → 4 → 6 level

header structures now should display correctly.

  • lua Query to enhance speed (especially for the Giant-Tree Picker)

Heading Picker

command.define({
  name = "Navigate: Heading Picker",
  key = "Ctrl-Shift-h",
  run = function()
    local headers = query[[
      from index.tag "header"
      where _.page == editor.getCurrentPage()
      order by _.pos
    ]]

    if #headers == 0 then
      editor.flashNotification("No headings found")
      return
    end

    local min_level = 10
    for _, h in ipairs(headers) do
      if h.level < min_level then min_level = h.level end
    end

    local items = {}
    local stack = {}
    
    local VERT = "│   "
    local BLNK = "   "
    local TEE  = "├─── "
    local ELB  = "└─── "

    for i, h in ipairs(headers) do
      local is_last = true
      for j = i + 1, #headers do
        if headers[j].level <= h.level then
          if headers[j].level == h.level then is_last = false end
          break
        end
      end

      local rel_level = h.level - min_level + 1
      while #stack > 0 and stack[#stack].level >= rel_level do
        table.remove(stack)
      end

      local prefix = ""
      for _, s in ipairs(stack) do
        prefix = prefix .. (s.last and BLNK or VERT)
      end
      for k = #stack + 1, rel_level - 1 do
        local has_deeper = false
        for j = i + 1, #headers do
          local target_level = min_level + k - 1
          if headers[j].level == target_level then
            has_deeper = true
            break
          elseif headers[j].level < target_level then
            break
          end
        end
        prefix = prefix .. (has_deeper and VERT or BLNK)
      end

      table.insert(items, {
        name = prefix .. (is_last and ELB or TEE) .. h.name,
        ref  = h.ref
      })

      table.insert(stack, { level = rel_level, last = is_last })
    end

    local selection = editor.filterBox("🤏 Pick", items, "Select a Header...", "a Header")
    if selection then
      editor.navigate(selection.ref)
      editor.invokeCommand("Navigate: Center Cursor")
    end
  end
})

Tree-Tree Picker

(Page Picker → Heading Picker)

local pageTreePicker

local VERT = "│   "
local BLNK = "   "
local TEE  = "├─── "
local ELB  = "└─── "

local function pickHeadings(pageName)
  local text = space.readPage(pageName)
  if not text then
    editor.flashNotification("Could not read page: " .. pageName)
    return
  end

  local nodes = query[[
      from index.tag "header"
      where _.page == pageName
      order by _.pos
    ]]

  if #nodes == 0 then
    editor.navigate({ page = pageName })
    editor.invokeCommand("Navigate: Center Cursor")
    return
  end

  local min_level = 10
  for _, n in ipairs(nodes) do
    if n.level < min_level then min_level = n.level end
  end

  local last_flags = {}
  for i = 1, #nodes do
    local L = nodes[i].level
    local is_last = true
    for j = i + 1, #nodes do
      if nodes[j].level <= L then
        if nodes[j].level == L then
          is_last = false
        else
          is_last = true
        end
        break
      end
    end
    last_flags[i] = is_last
  end

  local items = {}
  local stack = {}

  table.insert(items, {
    name        = ".",
    description = pageName,
    pos         = 0,
  })

  for i = 1, #nodes do
    local node = nodes[i]
    local L = node.level - min_level + 1
    local is_last = last_flags[i]

    while #stack > 0 and stack[#stack].level >= L do
      table.remove(stack)
    end

    local prefix = ""
    for d = 1, #stack do
      prefix = prefix .. (stack[d].last and BLNK or VERT)
    end

    for k = #stack + 1, L - 1 do
      local has_deeper = false
      for j = i + 1, #nodes do
        local next_L = nodes[j].level - min_level + 1
        if next_L == k then
          has_deeper = true
          break
        elseif next_L < k then
          break
        end
      end
      prefix = prefix .. (has_deeper and VERT or BLNK)
    end

    local path_parts = {}
    for _, s in ipairs(stack) do
      table.insert(path_parts, s.text)
    end
    table.insert(path_parts, node.name)
    local full_path = table.concat(path_parts, " > ")

    local elbow = is_last and ELB or TEE
    local label = prefix .. elbow .. node.name

    table.insert(items, {
      name        = label,
      description = full_path,
      pos         = node.pos,
    })

    table.insert(stack, { level = L, last = is_last, text = node.name })
  end

  local result = editor.filterBox(pageName .. "#", items, "Select a Header...", "Heading Picker")

  if result then
    local pos = result.pos
    if not pos and result.value and result.value.pos then
      pos = result.value.pos
    end

    if pos == 0 then
      editor.navigate({ page = pageName })
    elseif pos then
      editor.navigate({ page = pageName, pos = pos })
    end
    editor.invokeCommand("Navigate: Center Cursor")
  else
    return pageTreePicker()
  end
end

pageTreePicker = function()
  local pages = space.listPages()

  local path_map  = {}
  local real_pages = {}

  for _, page in ipairs(pages) do
    real_pages[page.name] = true
  end

  for _, page in ipairs(pages) do
    local parts = {}
    for part in string.gmatch(page.name, "[^/]+") do
      table.insert(parts, part)
      local current_path = table.concat(parts, "/")

      if not path_map[current_path] then
        path_map[current_path] = {
          name    = current_path,
          text    = part,
          level   = #parts,
          is_real = false,
        }
      end
    end
  end

  for path, _ in pairs(real_pages) do
    if path_map[path] then
      path_map[path].is_real = true
    end
  end

  local nodes = {}
  for _, node in pairs(path_map) do
    table.insert(nodes, node)
  end

  table.sort(nodes, function(a, b)
    return a.name < b.name
  end)

  if #nodes == 0 then
    editor.flashNotification("No pages found")
    return
  end

  local last_flags = {}
  for i = 1, #nodes do
    local L = nodes[i].level
    local is_last = true

    for j = i + 1, #nodes do
      local next_L = nodes[j].level

      if next_L == L then
        is_last = false
        break
      elseif next_L < L then
        is_last = true
        break
      end
    end
    last_flags[i] = is_last
  end

  local items = {}
  local stack = {}

  for i = 1, #nodes do
    local node = nodes[i]
    local L = node.level
    local is_last = last_flags[i]

    while #stack >= L do
      table.remove(stack)
    end

    local prefix = ""
    for d = 1, #stack do
      prefix = prefix .. (stack[d].last and BLNK or VERT)
    end

    for _ = #stack + 1, L - 1 do
      prefix = prefix .. BLNK
    end

    local elbow = is_last and ELB or TEE

    local display_text = node.text
    local desc = node.name

    if not node.is_real then
      display_text = display_text .. "/"
      desc = desc .. "/"
    end

    local label = prefix .. elbow .. display_text

    table.insert(items, {
      name        = label,
      description = desc,
      value       = {
        page    = node.name,
        is_real = node.is_real,
      },
    })

    table.insert(stack, { level = L, last = is_last })
  end

  local result = editor.filterBox("🤏 Pick:", items, "Select a Page...", "Page Tree")

  if result then
    local selection = result.value or result

    if type(selection) ~= "table" then
      if selection then pickHeadings(selection) end
      return
    end

    local page_name = selection.page
    local is_real   = selection.is_real

    if page_name then
      if is_real then
        pickHeadings(page_name)
      else
        editor.flashNotification("Folder selected. Creating page: " .. page_name)
        editor.navigate({ page = page_name })
      end
    end
  end
end

command.define({
  name = "Navigate: Tree-Tree Picker",
  key  = "Shift-Alt-e",
  run  = function() pageTreePicker() end,
})

Giant-Tree Picker

(Page-Heading Picker)

local VERT = "│   "
local BLNK = "   "
local TEE  = "├─── "
local ELB  = "└─── "

local function unifiedTreePicker()
  local pages = space.listPages()
  local path_map = {}
  local real_pages = {}

  for _, page in ipairs(pages) do
    real_pages[page.name] = true
  end

  for _, page in ipairs(pages) do
    local parts = {}
    for part in string.gmatch(page.name, "[^/]+") do
      table.insert(parts, part)
      local current_path = table.concat(parts, "/")
      if not path_map[current_path] then
        path_map[current_path] = {
          name = current_path,
          text = part,
          level = #parts,
          is_real = false,
          type = "folder"
        }
      end
    end
  end

  for path, _ in pairs(real_pages) do
    if path_map[path] then
      path_map[path].is_real = true
      path_map[path].type = "page"
    end
  end

  local sorted_nodes = {}
  for _, node in pairs(path_map) do
    table.insert(sorted_nodes, node)
  end

  table.sort(sorted_nodes, function(a, b)
    return a.name < b.name
  end)

  if #sorted_nodes == 0 then
    editor.flashNotification("No pages found")
    return
  end

  local all_headers = query[[
    from index.tag "header"
    order by _.page, _.pos
  ]]

  local headers_by_page = {}
  for _, h in ipairs(all_headers or {}) do
    local p = h.page
    if p and h.name and h.name ~= "" then
      local bucket = headers_by_page[p]
      if not bucket then
        bucket = {}
        headers_by_page[p] = bucket
      end
      table.insert(bucket, {
        level = h.level or 1,
        text  = h.name,
        pos   = h.pos or 0
      })
    end
  end

  local final_nodes = {}

  for _, node in ipairs(sorted_nodes) do
    table.insert(final_nodes, node)

    if node.is_real then
      local headings = headers_by_page[node.name]

      if headings and #headings > 0 then
        local min_level = 10
        for _, h in ipairs(headings) do
          if h.level and h.level < min_level then
            min_level = h.level
          end
        end

        local heading_stack = {}

        for _, h in ipairs(headings) do
          local hlevel = h.level or min_level
          while #heading_stack > 0 and heading_stack[#heading_stack].level >= hlevel do
            table.remove(heading_stack)
          end

          table.insert(heading_stack, { level = hlevel, text = h.text })

          local path_parts = { node.name }
          for _, stack_item in ipairs(heading_stack) do
            table.insert(path_parts, stack_item.text)
          end
          local full_path_desc = table.concat(path_parts, ">")

          local relative_level = hlevel - min_level + 1
          local absolute_level = node.level + relative_level

          table.insert(final_nodes, {
            name      = node.name,
            text      = h.text,
            level     = absolute_level,
            is_real   = false,
            type      = "heading",
            pos       = h.pos,
            page_name = node.name,
            full_desc = full_path_desc
          })
        end
      end
    end
  end

  local last_flags = {}
  local total = #final_nodes

  for i = 1, total do
    local L = final_nodes[i].level
    local is_last = true

    for j = i + 1, total do
      local next_L = final_nodes[j].level
      if next_L == L then
        is_last = false
        break
      elseif next_L < L then
        is_last = true
        break
      end
    end

    last_flags[i] = is_last
  end

  local items = {}
  local stack = {}

  for i = 1, total do
    local node   = final_nodes[i]
    local L      = node.level
    local is_last = last_flags[i]

    while #stack > 0 and stack[#stack].level >= L do
      table.remove(stack)
    end

    local prefix = ""
    for d = 1, #stack do
      prefix = prefix .. (stack[d].last and BLNK or VERT)
    end

    for k = #stack + 1, L - 1 do
      local has_deeper = false
      for j = i + 1, total do
        local next_L = final_nodes[j].level
        if next_L == k then
          has_deeper = true
          break
        elseif next_L < k then
          break
        end
      end
      prefix = prefix .. (has_deeper and VERT or BLNK)
    end

    local elbow = is_last and ELB or TEE

    local display_text = node.text
    local desc = ""

    if node.type == "folder" then
      display_text = display_text .. "/"
      desc = node.name .. "/"
    elseif node.type == "page" then
      desc = node.name
    elseif node.type == "heading" then
      desc = node.full_desc
    end

    local label = prefix .. elbow .. display_text

    table.insert(items, {
      name        = label,
      description = desc,
      value       = {
        page = node.page_name or node.name,
        pos  = node.pos,
        type = node.type
      }
    })

    table.insert(stack, { level = L, last = is_last })
  end

  local result = editor.filterBox("🤏 Pick:", items, "Select Page or Heading...", "Unified Tree")

  if result then
    local selection = result.value or result
    if type(selection) ~= "table" then
      return
    end

    local page_name = selection.page
    local pos       = selection.pos
    local node_type = selection.type

    if node_type == "folder" then
      editor.flashNotification("Folder selected. Creating/Going to page: " .. page_name)
      editor.navigate({ page = page_name })
    elseif node_type == "page" or node_type == "heading" then
      if pos and pos > 0 then
        editor.navigate({ page = page_name, pos = pos })
      else
        editor.navigate({ page = page_name })
      end
      editor.invokeCommand("Navigate: Center Cursor")
    end
  end
end

command.define({
  name = "Navigate: Tree-Tree Picker",
  key  = "Shift-Alt-e",
  run  = function() unifiedTreePicker() end
})