Skip to content

Commit 8eba686

Browse files
committed
Improve search results order in Seal filesearch plugin
Spotlight results naturally put first the items that were recently accessed. However it doesn't put first the item that really contain the word as it (not as a sub-part of the word) whereas it is often the ones you want first typically. We emulate that behaviour by re-ordering the results to boost the item that contains whole words.
1 parent cf679ff commit 8eba686

File tree

1 file changed

+56
-3
lines changed

1 file changed

+56
-3
lines changed

Source/Seal.spoon/seal_filesearch.lua

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ EMPTY_QUERY = ".*"
5555

5656
local log = hs.logger.new('seal_filesearch', 'info')
5757

58+
---
59+
-- Generic helper functions
60+
---
61+
62+
local imapWithIndex = function(t, mapFn)
63+
local mappedTable = {}
64+
for i, v in ipairs(t) do
65+
mappedTable[#mappedTable + 1] = mapFn(v, i)
66+
end
67+
return mappedTable
68+
end
69+
70+
local containsWholeWord = function(s, word)
71+
local pattern = "%f[%w]" .. string.lower(word) .. "%f[%W]"
72+
return string.find(string.lower(s), pattern)
73+
end
74+
75+
local containsCamelCaseWholeWord = function(s, word)
76+
local camelCaseWord = word:sub(1, 1):upper() .. word:sub(2):lower()
77+
local pattern = "%f[%u]" .. camelCaseWord .. "(%f[%u%z])"
78+
return string.find(s, pattern)
79+
end
80+
5881
---
5982
-- Spotlight Helper class
6083
---
@@ -151,7 +174,7 @@ end
151174
-- Private functions
152175
---
153176

154-
local convertSpotlightResultToQueryResult = function(item)
177+
local convertSpotlightResultToQueryResult = function(item, index)
155178
local icon = hs.image.iconForFile(item.kMDItemPath)
156179
local bundleID = item.kMDItemCFBundleIdentifier
157180
if (not icon) and (bundleID) then
@@ -164,13 +187,43 @@ local convertSpotlightResultToQueryResult = function(item)
164187
uuid = obj.__name .. "__" .. (bundleID or item.kMDItemDisplayName),
165188
plugin = obj.__name,
166189
type = "open",
167-
image = icon
190+
image = icon,
191+
index = index
168192
}
169193
end
170194

195+
-- This function returns a sort function suitable for table.sort
196+
-- that will boost the search results that:
197+
-- * contains the whole word (not part of another word)
198+
-- * contains the word in CamelCase
199+
local buildBoostResultsSortForQuery = function(query)
200+
local queryWords = hs.fnutils.split(query, "%s+")
201+
local scoreItem = function(item)
202+
local score = 0
203+
for _, word in ipairs(queryWords) do
204+
if containsWholeWord(item.text, word) or containsCamelCaseWholeWord(item.text, word) then
205+
score = score + 1
206+
end
207+
end
208+
return score
209+
end
210+
211+
return function(itemA, itemB)
212+
local scoreA = scoreItem(itemA)
213+
local scoreB = scoreItem(itemB)
214+
if scoreA ~= scoreB then
215+
return scoreA > scoreB
216+
else
217+
-- we just preserve the existing order otherwise
218+
return itemA.index < itemB.index
219+
end
220+
end
221+
end
222+
171223
local handleFileSearchResults = function(query, searchResults)
172224
if query == obj.currentQuery then
173-
obj.currentQueryResults = hs.fnutils.map(searchResults, convertSpotlightResultToQueryResult)
225+
obj.currentQueryResults = imapWithIndex(searchResults, convertSpotlightResultToQueryResult)
226+
table.sort(obj.currentQueryResults, buildBoostResultsSortForQuery(query))
174227
obj.seal.chooser:refreshChoicesCallback()
175228
end
176229
end

0 commit comments

Comments
 (0)