Created
January 24, 2021 15:31
-
-
Save mg979/07a8d24a5d2101ffcd2edfefd45ac028 to your computer and use it in GitHub Desktop.
Score fuzzy matches
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
M = {} | |
function M.score() | |
local lines = {} | |
local self = function(x, ...) return vim.fn.get(vim.fn.eval('self'), x, ...) end | |
local re = function(x, ...) return vim.fn.get(vim.fn.eval('self.re'), x, ...) end | |
local find = string.find | |
local vmatch = vim.fn.match | |
local vmatchend = vim.fn.matchend | |
local fileFinder = self('file_finder') == 1 | |
local slash = vim.fn.has('win32') == 1 and '.*[\\/]' or '.*/' | |
local longFirst = self('score_longest_first', 1) == 1 | |
-- the only option that we consider when evaluating score is the case | |
local filter = table.concat(self('filter')) | |
local case = vim.fn.matchstr(re('opts'), '\\[cC]') | |
local opts = '\\V' .. case .. re('pre') | |
local post = re('post') | |
local smartcase = not find(opts, '\\[cC]') and not find(filter, '%u') | |
local pattern = opts .. re('pattern') .. post -- the basic pattern | |
local wpattern = opts .. '\\<' .. re('pattern') .. post -- the pattern at begin of word | |
local xpattern = opts .. filter .. post -- the exact pattern | |
local fromCol = string.match(re('opts'), '\\%%>(%d+)c') | |
fromCol = fromCol and tonumber(fromCol) or 0 | |
local filterLen = #filter | |
local Count = 0 | |
local fastScore = filterLen < 3 and vim.fn.line('$') > 1000 | |
-- used to search for case sensitive exact matches | |
local caseAfterSlash = not self('stop_at_tab') and string.find or | |
function(v, p, ...) local s = ... and ... - 1 or 0; return vmatch(v, '\\C' .. p, s) end | |
local sameCase = not self('stop_at_tab') and string.find or | |
function(v, p, ...) local s = ... and ... - 1 or 0; return vmatch(v, '\\C' .. p, s) ~= -1 end | |
for _,v in ipairs(vim.fn.getline(1, '$')) do | |
local firstMatch = vmatch(v, pattern, fromCol) | |
local matchLength = fastScore and 0 or vmatchend(v, pattern) - filterLen | |
local perfectMatch = not fastScore and vmatch(v, xpattern, fromCol) >= 0 | |
-- if longFirst, the farthest the first match, the higher the score | |
-- the longer the match, the less exact it is, so subtract it from score | |
local score = ( longFirst and firstMatch or -firstMatch ) - matchLength | |
local lastSlash, afterSlash, beginOfLine, beginOfBasename, beginOfWord, exactAfterSlash | |
-- remember that vim matches are 0-indexed, lua's are 1-indexed | |
if fileFinder then | |
_, lastSlash = find(v, slash) | |
afterSlash = lastSlash and lastSlash < firstMatch | |
beginOfLine = not lastSlash and firstMatch == fromCol | |
beginOfBasename = lastSlash and | |
vmatch(v, pattern, lastSlash - 1) == lastSlash | |
beginOfWord = not fastScore and | |
not beginOfLine and | |
not beginOfBasename and | |
vmatch(v, wpattern, fromCol) > fromCol | |
exactAfterSlash = perfectMatch and | |
beginOfBasename and | |
vmatch(v, xpattern, lastSlash - 1) == lastSlash | |
else | |
beginOfLine = firstMatch == fromCol | |
beginOfWord = not beginOfLine and | |
vmatch(v, wpattern, fromCol) > fromCol | |
end | |
-- prefer exact case if smartcase | |
if smartcase and not fastScore then | |
if exactAfterSlash then if caseAfterSlash(v, filter, lastSlash) ~= lastSlash | |
then score = score - 100 end | |
elseif perfectMatch then if not sameCase(v, filter, fromCol + 1) | |
then score = score - 75 end | |
elseif vmatch(v, '\\C' .. pattern, fromCol) == -1 | |
then score = score - 50 end | |
end | |
if perfectMatch then score = score + 200 end -- perfect filter match increases score | |
if exactAfterSlash then score = score + 100 end -- perfect match at start of basename | |
if beginOfBasename then score = score + 100 end -- match at start of basename | |
if not lastSlash then score = score + 50 end -- no slashes, file is in current directory | |
if beginOfLine then score = score + 50 end -- match at start of line | |
if afterSlash then score = score + 20 end -- match starts after last slash | |
if beginOfWord then score = score + 20 end -- match at start of word | |
Count = Count + 1 | |
lines[Count] = {v, score} | |
end | |
-- sort by score | |
table.sort(lines, function(a, b) return a[2] > b[2] end ) | |
-- update buffer | |
vim.fn.execute('%d _') | |
for i,line in ipairs(lines) do | |
vim.fn.setline(i, line[1]) | |
end | |
end | |
return M |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment