diff --git a/README.md b/README.md index 113e133..b1146ea 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ in the same line). Using treesitter for file preview highlighter etc - LSP Code Action, Code Lens, Code lens action +- LRU cache for treesitter nodes + # Why a new plugin I'd like to go beyond what the system is providing. diff --git a/lua/navigator/gui.lua b/lua/navigator/gui.lua index c3feba8..5736271 100644 --- a/lua/navigator/gui.lua +++ b/lua/navigator/gui.lua @@ -127,7 +127,7 @@ function M.new_list_view(opts) if opts.rawdata then data = items else - log(items) + trace(items) data = require"navigator.render".prepare_for_render(items, opts) end diff --git a/lua/navigator/lru.lua b/lua/navigator/lru.lua new file mode 100644 index 0000000..2e79a51 --- /dev/null +++ b/lua/navigator/lru.lua @@ -0,0 +1,150 @@ +-- lua-lru, LRU cache in Lua +-- Copyright (c) 2015 Boris Nagaev +-- See the LICENSE file for terms of use. +local lru = {} + +function lru.new(max_size, max_bytes) + + assert(max_size >= 1, "max_size must be >= 1") + assert(not max_bytes or max_bytes >= 1, "max_bytes must be >= 1") + + -- current size + local size = 0 + local bytes_used = 0 + + -- map is a hash map from keys to tuples + -- tuple: value, prev, next, key + -- prev and next are pointers to tuples + local map = {} + + -- indices of tuple + local VALUE = 1 + local PREV = 2 + local NEXT = 3 + local KEY = 4 + local BYTES = 5 + + -- newest and oldest are ends of double-linked list + local newest = nil -- first + local oldest = nil -- last + + local removed_tuple -- created in del(), removed in set() + + -- remove a tuple from linked list + local function cut(tuple) + local tuple_prev = tuple[PREV] + local tuple_next = tuple[NEXT] + tuple[PREV] = nil + tuple[NEXT] = nil + if tuple_prev and tuple_next then + tuple_prev[NEXT] = tuple_next + tuple_next[PREV] = tuple_prev + elseif tuple_prev then + -- tuple is the oldest element + tuple_prev[NEXT] = nil + oldest = tuple_prev + elseif tuple_next then + -- tuple is the newest element + tuple_next[PREV] = nil + newest = tuple_next + else + -- tuple is the only element + newest = nil + oldest = nil + end + end + + -- insert a tuple to the newest end + local function setNewest(tuple) + if not newest then + newest = tuple + oldest = tuple + else + tuple[NEXT] = newest + newest[PREV] = tuple + newest = tuple + end + end + + local function del(key, tuple) + map[key] = nil + cut(tuple) + size = size - 1 + bytes_used = bytes_used - (tuple[BYTES] or 0) + removed_tuple = tuple + end + + -- removes elemenets to provide enough memory + -- returns last removed element or nil + local function makeFreeSpace(bytes) + while size + 1 > max_size or (max_bytes and bytes_used + bytes > max_bytes) do + assert(oldest, "not enough storage for cache") + del(oldest[KEY], oldest) + end + end + + local function get(_, key) + local tuple = map[key] + if not tuple then + return nil + end + cut(tuple) + setNewest(tuple) + return tuple[VALUE] + end + + local function set(_, key, value, bytes) + local tuple = map[key] + if tuple then + del(key, tuple) + end + if value ~= nil then + -- the value is not removed + bytes = max_bytes and (bytes or #value) or 0 + makeFreeSpace(bytes) + local tuple1 = removed_tuple or {} + map[key] = tuple1 + tuple1[VALUE] = value + tuple1[KEY] = key + tuple1[BYTES] = max_bytes and bytes + size = size + 1 + bytes_used = bytes_used + bytes + setNewest(tuple1) + else + assert(key ~= nil, "Key may not be nil") + end + removed_tuple = nil + end + + local function delete(_, key) + return set(_, key, nil) + end + + local function mynext(_, prev_key) + local tuple + if prev_key then + tuple = map[prev_key][NEXT] + else + tuple = newest + end + if tuple then + return tuple[KEY], tuple[VALUE] + else + return nil + end + end + + -- returns iterator for keys and values + local function lru_pairs() + return mynext, nil, nil + end + + local mt = { + __index = {get = get, set = set, delete = delete, pairs = lru_pairs}, + __pairs = lru_pairs + } + + return setmetatable({}, mt) +end + +return lru diff --git a/lua/navigator/lspwrapper.lua b/lua/navigator/lspwrapper.lua index 2751733..949ba34 100644 --- a/lua/navigator/lspwrapper.lua +++ b/lua/navigator/lspwrapper.lua @@ -1,4 +1,5 @@ local M = {} + local util = require "navigator.util" local gutil = require "guihua.util" local lsp = require "vim.lsp" @@ -14,9 +15,8 @@ local is_win = vim.loop.os_uname().sysname:find("Windows") local path_sep = require"navigator.util".path_sep() local path_cur = require"navigator.util".path_cur() cwd = gutil.add_pec(cwd) -ts_nodes = {} -ts_nodes_time = {} - +local ts_nodes = require('navigator.lru').new(1000, 1024 * 1024) +local ts_nodes_time = require('navigator.lru').new(1000) local TS_analysis_enabled = require"navigator".config_values().treesitter_analysis -- extract symbol from range @@ -167,13 +167,18 @@ local function ts_functions(uri) local bufnr = vim.uri_to_bufnr(uri) local x = os.clock() trace(ts_nodes) - if ts_nodes[uri] ~= nil then - local t = ts_nodes_time[uri] + local tsnodes = ts_nodes:get(uri) + if tsnodes ~= nil then + log("get data from cache") + local t = ts_nodes_time:get(uri) or 0 local fname = vim.uri_to_fname(uri) local modified = vim.fn.getftime(fname) if modified <= t then trace(t, modified) - return ts_nodes[uri] + return tsnodes + else + ts_nodes:delete(uri) + ts_nodes_time:delete(uri) end end local unload = false @@ -189,9 +194,9 @@ local function ts_functions(uri) trace(cmd) -- vim.cmd(cmd) -- todo: not sure if it is needed end - ts_nodes[uri] = funcs - ts_nodes_time[uri] = os.time() - trace(funcs, ts_nodes) + ts_nodes:set(uri, funcs) + ts_nodes_time:set(uri, os.time()) + trace(funcs, ts_nodes:get(uri)) trace(string.format("elapsed time: %.4f\n", os.clock() - x)) -- how long it tooks return funcs end @@ -261,7 +266,7 @@ function M.locations_to_items(locations) end) local uri_def = {} - log(locations) + trace(locations) for i, loc in ipairs(locations) do local funcs = nil local item = lsp.util.locations_to_items({loc})[1] diff --git a/lua/navigator/treesitter.lua b/lua/navigator/treesitter.lua index 38c0df9..55c117e 100644 --- a/lua/navigator/treesitter.lua +++ b/lua/navigator/treesitter.lua @@ -2,6 +2,7 @@ -- to fit in navigator.lua local gui = require "navigator.gui" local fn = vim.fn +local lru = require('navigator.lru').new(500, 1024 * 1024) local ok, ts_locals = pcall(require, "nvim-treesitter.locals") @@ -235,19 +236,41 @@ function M.goto_previous_usage(bufnr) return M.goto_adjacent_usage(bufnr, -1) end +local function key(fname, filter) + return fname .. vim.inspect(filter) +end + local function get_all_nodes(bufnr, filter, summary) - trace(bufnr, filter, summary) - bufnr = bufnr or 0 - summary = summary or false - if not parsers.has_parser() then - print("ts not loaded") - end local fname = vim.fn.expand("%:p:f") local uri = vim.uri_from_fname(fname) if bufnr ~= 0 then uri = vim.uri_from_bufnr(bufnr) fname = vim.uri_to_fname(uri) end + + local ftime = vim.fn.getftime(fname) + + local hash = key(fname, filter) + + local result = lru:get(hash) + if result ~= nil and result.ftime == ftime then + log("get data from cache") + return result.nodes, result.length + end + + if result ~= nil and result.ftime ~= ftime then + lru:delete(hash) + end + + trace(bufnr, filter, summary) + if not bufnr then + print("get_all_node invalide bufnr") + end + summary = summary or false + if not parsers.has_parser() then + print("ts not loaded") + end + path_sep = require"navigator.util".path_sep() path_cur = require"navigator.util".path_cur() local display_filename = fname:gsub(cwd .. path_sep, path_cur, 1) @@ -366,6 +389,8 @@ local function get_all_nodes(bufnr, filter, summary) end end trace(all_nodes) + local nd = {nodes = all_nodes, ftime = vim.fn.getftime(fname), length = length} + lru:set(hash, nd) return all_nodes, length end