@@ -5,43 +5,103 @@ local libuv = require "fzf-lua.libuv"
55local base64 = require " fzf-lua.lib.base64"
66local serpent = require " fzf-lua.lib.serpent"
77
8- local M = {}
8+ --- @class fzf-lua.lru
9+ --- @field max_size integer
10+ --- @field max_id integer
11+ --- @field last_id integer
12+ --- @field mru integer[]
13+ --- @field store any[]
14+ --- @field lookup table<string , integer>
15+ local LRU = {}
16+
17+ function LRU :new (size )
18+ local obj = {
19+ max_size = size ,
20+ -- Arbitrary, should be big enough to assert
21+ -- when accessing an evicted item
22+ max_id = size * 20 ,
23+ last_id = 0 ,
24+ mru = {},
25+ store = {},
26+ lookup = {},
27+ }
28+ setmetatable (obj , self )
29+ self .__index = self
30+ return obj
31+ end
932
10- -- Circular buffer used to store registered function IDs
11- -- set max length to 10, ATM most actions used by a single
12- -- provider are 2 (`live_grep` with `multiprocess=false`)
13- -- and 4 (`git_status` with preview and 3 reload binds)
14- -- we can always increase if we need more
15- local _MAX_LEN = 50
16- local _index = 0
17- local _registry = {}
18- local _protected = {}
33+ --- @param value any
34+ --- @return integer , integer new element id and evicted (if evicted )
35+ function LRU :set (value )
36+ local store_idx , evicted_id
37+ local id = self .last_id + 1
38+ if id > self .max_id then id = 1 end
39+ if # self .store >= self .max_size then
40+ -- Remove the last element in the MRU table
41+ evicted_id = tostring (table.remove (self .mru ))
42+ store_idx = self .lookup [evicted_id ]
43+ self .lookup [evicted_id ] = nil
44+ else
45+ store_idx = # self .store + 1
46+ end
47+ -- New id is always at the top of the MRU
48+ table.insert (self .mru , 1 , id )
49+ self .store [store_idx ] = value
50+ self .lookup [tostring (id )] = store_idx
51+ self .last_id = id
52+ return id , tonumber (evicted_id )
53+ end
1954
20- function M .register_func (fn )
21- repeat
22- _index = _index % _MAX_LEN + 1
23- until not _protected [_index ]
24- _registry [_index ] = fn
25- return _index
55+ --- @return integer
56+ function LRU :len ()
57+ return # self .store
2658end
2759
28- function M .get_func (id )
29- return _registry [id ]
60+ --- @param id integer
61+ --- @return any
62+ function LRU :get (id )
63+ local store_idx = self .lookup [tostring (id )]
64+ assert (store_idx , string.format (" get nonexistent id %d" , id ))
65+ self .mru = (function ()
66+ -- Remove all previous occurances of the id in the MRU
67+ -- and insert the current id at the top
68+ -- TODO: more efficient way?
69+ local ret = {}
70+ table.insert (ret , id )
71+ for _ , x in ipairs (self .mru ) do
72+ if x ~= id then
73+ table.insert (ret , x )
74+ end
75+ end
76+ return ret
77+ end )()
78+ return self .store [store_idx ]
3079end
3180
32- function M .set_protected (id )
33- _protected [id ] = true
34- assert (_MAX_LEN > utils .tbl_count (_protected ))
81+ -- Cache should be able to hold all function callbacks of a single picker
82+ -- max cache size of 50 should be more than enough, we don't want it to be
83+ -- too big as this will prevent clearing of referecnces to "opts" which
84+ -- prevents garabage collection from freeing the resources
85+ local function new_cache () return LRU :new (50 ) end
86+ local _cache = new_cache ()
87+
88+ local M = {}
89+
90+ -- Export LRU for testing
91+ M .LRU = LRU
92+
93+ function M .register_func (fn )
94+ return (_cache :set (fn ))
3595end
3696
37- function M .clear_protected ( )
38- _protected = {}
97+ function M .get_func ( id )
98+ return _cache : get ( id )
3999end
40100
41- function M . clear_registry ()
42- _index = 0
43- _registry = {}
44- _protected = {}
101+ -- NOTE: CI ONLY - DO NOT USE
102+ -- new cache is equal to cleared cache
103+ function M . clear_cache ()
104+ _cache = new_cache ()
45105end
46106
47107-- creates a new address to listen to messages from actions. This is important
246306--- Fzf field index expression, e.g. "{+}" (selected), "{q}" (query)
247307--- @param fzf_field_index string ?
248308--- @return string , integer ?
249- M .stringify = function (contents , opts , fzf_field_index , protect )
309+ M .stringify = function (contents , opts , fzf_field_index )
250310 assert (contents , " must supply contents" )
251311
252312 -- TODO: should we let this assert?
@@ -422,27 +482,19 @@ M.stringify = function(contents, opts, fzf_field_index, protect)
422482 end
423483 end , fzf_field_index or " " , opts .debug )
424484
425- -- "protect" a function ptr, cleared when opening a new picker in `core.fzf()`
426- -- protected functions include init (content) commands and acrions (reload
427- -- and execute-silent), previewer trigger commands are excluded (zero event
428- -- and preview callback)
429- if protect then
430- M .set_protected (id )
431- end
432-
433485 return cmd , id
434486end
435487
436- M .stringify_cmd = function (fn , opts , fzf_field_index , protect )
488+ M .stringify_cmd = function (fn , opts , fzf_field_index )
437489 assert (type (fn ) == " function" , " fn must be of type function" )
438490 return M .stringify (fn , {
439491 __stringify_cmd = true ,
440492 PidObject = utils .pid_object (" __stringify_cmd_pid" , opts ),
441493 debug = opts .debug ,
442- }, fzf_field_index , protect )
494+ }, fzf_field_index )
443495end
444496
445- M .stringify_data = function (fn , opts , fzf_field_index , protect )
497+ M .stringify_data = function (fn , opts , fzf_field_index )
446498 assert (type (fn ) == " function" , " fn must be of type function" )
447499 return M .stringify (function (cb , _ , ...)
448500 local ret = fn (... )
@@ -454,7 +506,7 @@ M.stringify_data = function(fn, opts, fzf_field_index, protect)
454506 cb (tostring (ret ))
455507 end
456508 cb (nil )
457- end , { debug = opts .debug }, fzf_field_index , protect )
509+ end , { debug = opts .debug }, fzf_field_index )
458510end
459511
460512return M
0 commit comments