Skip to content

Instantly share code, notes, and snippets.

@ajitid
Last active January 13, 2026 10:10
Show Gist options
  • Select an option

  • Save ajitid/978ca82799ffb979779afba19106ab43 to your computer and use it in GitHub Desktop.

Select an option

Save ajitid/978ca82799ffb979779afba19106ab43 to your computer and use it in GitHub Desktop.
Silverbullet global search

Not queries and Or queries are not supported.

Based on this

search term:       do this `that too`
would search for:  "do" and "this" and "that too"

Folder filter syntax:
  in:visuals/touchdesigner myterm        -> search "myterm" in folder "visuals/touchdesigner"
  in:"visuals/touch designer" myterm     -> search "myterm" in folder with spaces
  myterm in:folder                       -> folder can also be at the end
-- ============================================
-- Configuration
-- ============================================
local config = {
showParentHeaders = false,
contextLength = 50
}
-- Highlight styles
local highlightStyles = {
function(kw) return "==" .. kw .. "==" end,
function(kw) return "==`" .. kw .. "`==" end,
function(kw) return "`" .. kw .. "`" end,
function(kw) return "**==" .. kw .. "==**" end,
function(kw) return "**`" .. kw .. "`**" end,
function(kw) return "*==`" .. kw .. "`==*" end,
function(kw) return "*`" .. kw .. "`*" end,
function(kw) return "**" .. kw .. "**" end,
function(kw) return "*" .. kw .. "*" end,
function(kw) return "*==" .. kw .. "==*" end,
}
-- ============================================
-- Utilities
-- ============================================
local function cleanContext(s)
if not s then return "" end
return string.gsub(s, "[\r\n]+", " ↩ ")
end
local function headingPrefix(depth)
if depth > 6 then depth = 6 end
return string.rep("#", depth) .. " "
end
local function isCaseSensitive(word)
return string.find(word, "%u") ~= nil
end
-- Normalize folder path: remove trailing slash if present
local function normalizeFolder(folder)
if not folder or folder == "" then return "" end
if string.sub(folder, -1) == "/" then
return string.sub(folder, 1, -2)
end
return folder
end
-- Check if a page is within the specified folder
local function isInFolder(pageName, folder)
if folder == "" then return true end
-- Page must start with "folder/" to be inside the folder
return string.sub(pageName, 1, #folder + 1) == folder .. "/"
end
-- Parse in:folder or in:"folder with spaces" from input
-- Returns: folder, remainingInput
local function parseInFolder(input)
-- Try to match in:"quoted folder" at the START
local folder, rest = string.match(input, '^in:"([^"]+)"%s*(.*)')
if folder then
return normalizeFolder(folder), rest
end
-- Try to match in:folder (no spaces) at the START
folder, rest = string.match(input, '^in:(%S+)%s*(.*)')
if folder then
return normalizeFolder(folder), rest
end
-- Check if in:"quoted" is elsewhere in the input
folder = string.match(input, '%s+in:"([^"]+)"')
if folder then
local remaining = string.gsub(input, '%s+in:"[^"]+"', '')
return normalizeFolder(folder), remaining
end
-- Check if in:folder is elsewhere in the input
folder = string.match(input, '%s+in:(%S+)')
if folder then
local remaining = string.gsub(input, '%s+in:%S+', '')
return normalizeFolder(folder), remaining
end
return "", input
end
local function smartHighlight(text, keyword, highlightFn)
if isCaseSensitive(keyword) then
return string.gsub(text, keyword, highlightFn(keyword), 1)
end
local lowerText = string.lower(text)
local lowerKw = string.lower(keyword)
local pos = string.find(lowerText, lowerKw, 1, true)
if not pos then return text end
return
string.sub(text, 1, pos - 1) ..
highlightFn(string.sub(text, pos, pos + #keyword - 1)) ..
string.sub(text, pos + #keyword)
end
local function buildHierarchicalHeaders(pageName, existingPaths)
local output = {}
local parts = {}
for part in string.gmatch(pageName, "[^/]+") do
table.insert(parts, part)
end
local currentPath = ""
for i, part in ipairs(parts) do
if i > 1 then currentPath = currentPath .. "/" end
currentPath = currentPath .. part
if not existingPaths[currentPath] then
existingPaths[currentPath] = true
if i == #parts then
table.insert(output, headingPrefix(i) .. "[[" .. pageName .. "]]")
elseif config.showParentHeaders then
table.insert(output, headingPrefix(i) .. part)
end
end
end
return output
end
local function parseKeywords(input)
local keywords = {}
local i = 1
local len = #input
while i <= len do
while i <= len and string.sub(input, i, i):match("%s") do
i = i + 1
end
if i > len then break end
local char = string.sub(input, i, i)
if char == "`" then
local closePos = string.find(input, "`", i + 1, true)
if closePos then
table.insert(keywords, string.sub(input, i + 1, closePos - 1))
i = closePos + 1
else
table.insert(keywords, string.sub(input, i + 1))
break
end
else
local wordEnd = i
while wordEnd <= len do
local c = string.sub(input, wordEnd, wordEnd)
if c:match("%s") or c == "`" then break end
wordEnd = wordEnd + 1
end
table.insert(keywords, string.sub(input, i, wordEnd - 1))
i = wordEnd
end
end
return keywords
end
-- ============================================
-- Search helpers
-- ============================================
local function findAllPositions(content, keyword)
local positions = {}
local start = 1
while true do
local pos = string.find(content, keyword, start, true)
if not pos then break end
table.insert(positions, { pos = pos, endPos = pos + #keyword - 1 })
start = pos + 1
end
return positions
end
local function findSingleKeywordMatches(content, keyword, ctxLen)
local searchContent = isCaseSensitive(keyword)
and content
or string.lower(content)
local searchKw = isCaseSensitive(keyword)
and keyword
or string.lower(keyword)
local matches = {}
local len = #content
local positions = findAllPositions(searchContent, searchKw)
for _, p in ipairs(positions) do
local prefixStart = math.max(1, p.pos - ctxLen)
local suffixEnd = math.min(len, p.endPos + ctxLen)
table.insert(matches, {
prefix = cleanContext(string.sub(content, prefixStart, p.pos - 1)),
suffix = cleanContext(string.sub(content, p.endPos + 1, suffixEnd)),
keyword = keyword,
pos = p.pos
})
end
return matches
end
local function findMultiKeywordMatches(content, keywords, ctxLen)
local matches = {}
local len = #content
local searchContents = {}
for i, kw in ipairs(keywords) do
searchContents[i] = isCaseSensitive(kw)
and content
or string.lower(content)
end
local firstKw = keywords[1]
local firstSearchKw = isCaseSensitive(firstKw)
and firstKw
or string.lower(firstKw)
local anchors = findAllPositions(searchContents[1], firstSearchKw)
for _, anchor in ipairs(anchors) do
local windowStart = math.max(1, anchor.pos - ctxLen)
local windowEnd = math.min(len, anchor.endPos + ctxLen)
local ok = true
for i = 2, #keywords do
local window = string.sub(searchContents[i], windowStart, windowEnd)
local kw = isCaseSensitive(keywords[i])
and keywords[i]
or string.lower(keywords[i])
if not string.find(window, kw, 1, true) then
ok = false
break
end
end
if ok then
local snippet = cleanContext(string.sub(content, windowStart, windowEnd))
for i = #keywords, 1, -1 do
local styleIndex = ((i - 1) % #highlightStyles) + 1
snippet = smartHighlight(
snippet,
keywords[i],
highlightStyles[styleIndex]
)
end
table.insert(matches, { snippet = snippet, pos = windowStart })
end
end
return matches
end
-- ============================================
-- Core search
-- ============================================
local function searchGlobalOptimized(keywordInput)
-- Parse in:folder syntax from input
local folder, remainingInput = parseInFolder(keywordInput)
local keywords = parseKeywords(remainingInput)
if #keywords == 0 then return nil, 0, 0, {}, "" end
local results = {}
local matchCount = 0
local pageCount = 0
local existingPaths = {}
folder = normalizeFolder(folder)
for _, page in ipairs(space.listPages()) do
if not string.find(page.name, "^search:") and isInFolder(page.name, folder) then
local content = space.readPage(page.name)
if content then
local pageMatches = {}
if #keywords == 1 then
local matches = findSingleKeywordMatches(
content,
keywords[1],
config.contextLength
)
for i, m in ipairs(matches) do
-- Use character position (0-based) instead of line/column
local link = string.format("[[%s@%d|↗]]", page.name, m.pos - 1)
table.insert(pageMatches,
string.format("%d. …%s%s%s… %s", i, m.prefix, highlightStyles[1](m.keyword), m.suffix, link)
)
end
else
local matches = findMultiKeywordMatches(
content,
keywords,
config.contextLength
)
for i, m in ipairs(matches) do
-- Use character position (0-based) instead of line/column
local link = string.format("[[%s@%d|↗]]", page.name, m.pos - 1)
table.insert(pageMatches,
string.format("%d. …%s… %s", i, m.snippet, link)
)
end
end
if #pageMatches > 0 then
pageCount = pageCount + 1
for _, h in ipairs(buildHierarchicalHeaders(page.name, existingPaths)) do
table.insert(results, h)
end
for _, m in ipairs(pageMatches) do
table.insert(results, m)
matchCount = matchCount + 1
end
table.insert(results, "")
end
end
end
end
return results, matchCount, pageCount, keywords, folder
end
-- ============================================
-- UI Helpers
-- ============================================
local function buildKeywordLegend(keywords)
local out = {}
for i, kw in ipairs(keywords) do
out[i] = highlightStyles[((i - 1) % #highlightStyles) + 1](kw)
end
return table.concat(out, " AND ")
end
-- ============================================
-- Virtual Page Definition
-- ============================================
virtualPage.define {
pattern = "search:(.+)",
run = function(keywordInput)
keywordInput = keywordInput:trim()
local results, matchCount, pageCount, keywords, folder =
searchGlobalOptimized(keywordInput)
local folderInfo = folder ~= "" and (" | Folder: `" .. folder .. "`") or ""
local output = {
"# 🔍 Search Results",
string.format(
"> Keywords: %s | Matches: %d | Pages: %d%s",
buildKeywordLegend(keywords),
matchCount,
pageCount,
folderInfo
),
""
}
if matchCount == 0 then
table.insert(output, "😔 **No results found**")
else
for _, line in ipairs(results) do
table.insert(output, line)
end
end
return table.concat(output, "\n")
end
}
-- ============================================
-- Command
-- ============================================
command.define {
name = "Search: Find in files",
run = function()
local initialValue = ""
local currentPage = editor.getCurrentPage()
if currentPage then
local existingQuery = string.match(currentPage, "^search:(.+)")
if existingQuery then
initialValue = existingQuery
end
end
local keyword = editor.prompt(
"Find in files",
initialValue
)
if keyword and keyword:trim() ~= "" then
editor.navigate("search:" .. keyword:trim())
end
end,
key = "Ctrl-Shift-f",
mac = "Cmd-Shift-f",
priority = 1,
}
-- search term: do this `that too`
-- would search for: "do" and "this" and "that too"
--
-- Folder filter syntax:
-- in:visuals/touchdesigner myterm -> search "myterm" in folder "visuals/touchdesigner"
-- in:"visuals/touch designer" myterm -> search "myterm" in folder with spaces
-- myterm in:folder -> folder can also be at the end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment