diff --git a/README.md b/README.md index 4af7c7f..7a6fa09 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ use { -- pytrize {{{ config = 'require("pytrize").setup()', } -- }}} ``` -Requires [`plenary.nvim`](https://github.com/nvim-lua/plenary.nvim). +Requires [`plenary.nvim`](https://github.com/nvim-lua/plenary.nvim) and [`telescope.nvim`](https://github.com/nvim-telescope/telescope.nvim). ## Configuration `require("pytrize").setup` takes an optional table of settings which currently have the default values: diff --git a/lua/pytrize/call_spec.lua b/lua/pytrize/call_spec.lua index 5dd6454..9b031e4 100644 --- a/lua/pytrize/call_spec.lua +++ b/lua/pytrize/call_spec.lua @@ -4,7 +4,7 @@ local ts = vim.treesitter local ts_query = ts.query local parse_query = ts_query.parse or ts_query.parse_query -local warn = require('pytrize.warn').warn +local notify = require('pytrize.notify') local tbls = require('pytrize.tables') local get_root = function(bufnr) @@ -103,7 +103,7 @@ local get_entry = function(entry_idx, entry_node, params, bufnr) local item_nodes = get_named_children(entry_node) if #params ~= #item_nodes then -- TODO warn here? - -- warn(string.format( + -- notify.warn(string.format( -- 'number of items in entry tuple differ from number of params, %d items and %d params (line %d in %s)', -- #item_nodes, -- #params, @@ -146,7 +146,7 @@ M.get_calls = function(bufnr) local decorated_definition = call:parent():parent() if decorated_definition:type() ~= 'decorated_definition' then local row = call:start() - warn(string.format( + notify.warn(string.format( "couldn't parse params (line %d)\n expected `decorated_definition`\n got `%s`", row, decorated_definition:type() @@ -160,7 +160,7 @@ M.get_calls = function(bufnr) local params_node = arguments:child(1) if params_node:type() ~= 'string' then local row = call:start() - warn(string.format( + notify.warn(string.format( "couldn't parse params (line %d)\n expected `string`\n got `%s`", row, params_node:type() diff --git a/lua/pytrize/init.lua b/lua/pytrize/init.lua index 5ff93e4..d9d905c 100644 --- a/lua/pytrize/init.lua +++ b/lua/pytrize/init.lua @@ -9,6 +9,19 @@ local function setup_commands() vim.cmd('command PytrizeJumpFixture lua require("pytrize.api").jump_fixture()') end + +local warm_up_cache = require'pytrize.jump'.warm_up_cache + +local function create_autocmd() + vim.api.nvim_create_autocmd({"BufRead", "BufWrite", "BufEnter"}, { + pattern = "*/tests*/*.py", + callback = function(args) + warm_up_cache():start() + end, + }) +end + + M.setup = function(opts) if opts == nil then opts = {} @@ -17,6 +30,9 @@ M.setup = function(opts) if not settings.settings.no_commands then setup_commands() end + if not settings.settings.no_autocmds then + create_autocmd() + end end return M diff --git a/lua/pytrize/input/init.lua b/lua/pytrize/input/init.lua index 87b3bd4..ec47bba 100644 --- a/lua/pytrize/input/init.lua +++ b/lua/pytrize/input/init.lua @@ -1,7 +1,7 @@ -- local M = {} -- -- local settings = require('pytrize.settings').settings --- local warn = require('pytrize.warn').warn +-- local notify = require('pytrize.notify') -- -- local function get_nui_handler() -- return require('pytrize.input.nui').load() @@ -29,7 +29,7 @@ -- -- local function get_input_handler() -- if handler_getters[settings.preferred_input] == nil then --- warn(string.format('unknown input choice "%s"', settings.preferred_input)) +-- notify.warn(string.format('unknown input choice "%s"', settings.preferred_input)) -- return -- end -- local handler = handler_getters[settings.preferred_input]() diff --git a/lua/pytrize/jump/fixture.lua b/lua/pytrize/jump/fixture.lua index e8c0051..c0130d1 100644 --- a/lua/pytrize/jump/fixture.lua +++ b/lua/pytrize/jump/fixture.lua @@ -3,8 +3,16 @@ local M = {} local Job = require('plenary.job') local Path = require('plenary.path') -local warn = require('pytrize.warn').warn +local notify = require('pytrize.notify') local open_file = require('pytrize.jump.util').open_file +local settings = require('pytrize.settings') + +local conf = require('telescope.config').values +local pickers = require('telescope.pickers') +local finders = require('telescope.finders') +local sorters = require('telescope.sorters') +local actions = require('telescope.actions') +local action_state = require('telescope.actions.state') local function normal(cmd) vim.cmd(string.format('normal! %s', cmd)) @@ -19,55 +27,135 @@ local function get_word_under_cursor() end local function parse_raw_fixture_output(cwd, lines) - local fixtures = {} + local fixtures = setmetatable({}, M.meta_nested_sized()) local pattern = '^([%w_]*) .*%-%- (%S*):(%d*)$' for _, line in ipairs(lines) do local i, _, fixture, file, linenr = string.find(line, pattern) if i ~= nil then - fixtures[fixture] = { - file = cwd / file, - linenr = tonumber(linenr), - } + file = cwd / file + linenr = tonumber(linenr) + fixtures[fixture][file:normalize()] = linenr end end return fixtures end local function get_cwd() - return Path:new(vim.api.nvim_buf_get_name(0)):parent() + return Path:new(vim.fn.getcwd()) end local function lookup_fixtures(callback) local cwd = get_cwd() - Job:new({ + local current_file_path = Path.new(vim.fn.expand("%:p")) + return Job:new({ command = 'pytest', - args = {'--fixtures', '-v'}, - cwd = tostring(cwd), + args = {'--fixtures', '-v', current_file_path}, + cwd = vim.fn.getcwd(), on_exit = vim.schedule_wrap(function(j, return_val) if return_val == 0 then local fixtures = parse_raw_fixture_output(cwd, j:result()) callback(fixtures) else - warn(string.format('failed to query fixtures: %s', table.concat(j:result(), '\n'))) + notify.err( + string.format( + 'failed to query fixtures, pytest response code: %d, result: %s, stderr: %s', + return_val, + table.concat(j:result(), '\n'), + table.concat(j:stderr_result(), '\n') + ) + ) end end), - }):sync() + }) +end + +M.meta_nested_sized = function () + return { + __index = function (self1, key1) + local new_entry = {} + rawset(self1, key1, new_entry) + return new_entry + end, + } +end + +local len = function(t) + local count = 0 + for _ in pairs(t) do + count = count + 1 + end + return count +end + +M.fixtures_cache = setmetatable({}, M.meta_nested_sized()) + +M.fixtures_cache.update = function(opts) + for fixture, locations in pairs(opts) do + for file, linenr in pairs(locations) do + M.fixtures_cache[fixture][file] = linenr + end + end +end + +M.warm_up_cache = function() + return lookup_fixtures(function(fixtures) + M.fixtures_cache.update(fixtures) + end) +end + +M._to_declaration = function(fixture, file, linenr) + open_file(tostring(file)) + vim.api.nvim_win_set_cursor(0, {linenr, 0}) + vim.fn.search(fixture) end M.to_declaration = function() local fixture = get_word_under_cursor() - lookup_fixtures(function(fixtures) - local fixture_location = fixtures[fixture] - if fixture_location == nil then - warn(string.format('fixture "%s" not found', fixture)) + if settings.settings.no_autocmds then + M.warm_up_cache():sync() + end + local locations = M.fixtures_cache[fixture] + if len(locations) > 0 then + if len(locations) == 1 then + for file, linenr in pairs(locations) do + M._to_declaration(fixture, file, linenr) + return + end else - local file = fixture_location.file - local linenr = fixture_location.linenr - open_file(tostring(file)) - vim.api.nvim_win_set_cursor(0, {linenr, 0}) - vim.fn.search(fixture) + local entries = {} + for path, linenr in pairs(locations) do + table.insert(entries, {path = path, linenr = linenr}) + end + pickers.new({}, { + prompt_title = 'Ambiguous fixture name, please choose a file', + finder = finders.new_table { + results = entries, + entry_maker = function(entry) + return { + path = entry.path, + linenr = entry.linenr, + value = tostring(entry.path), + display = entry.path .. ':' .. entry.linenr, + ordinal = entry.path .. ':' .. entry.linenr, + } + end, + }, + sorter = sorters.get_generic_fuzzy_sorter(), + attach_mappings = function(prompt_bufnr, map) + actions.select_default:replace(function() + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + M._to_declaration(fixture, selection.path, selection.linenr) + end) + return true + end, + previewer = conf.file_previewer({}), + }):find() + return end - end) + else + notify.warn(string.format('fixture "%s" not found', fixture)) + end end return M diff --git a/lua/pytrize/jump/init.lua b/lua/pytrize/jump/init.lua index dfe228d..40d6142 100644 --- a/lua/pytrize/jump/init.lua +++ b/lua/pytrize/jump/init.lua @@ -5,5 +5,6 @@ local fixture = require('pytrize.jump.fixture') M.to_param_declaration = param.to_declaration M.to_fixture_declaration = fixture.to_declaration +M.warm_up_cache = fixture.warm_up_cache return M diff --git a/lua/pytrize/jump/param.lua b/lua/pytrize/jump/param.lua index aed1226..e55b91a 100644 --- a/lua/pytrize/jump/param.lua +++ b/lua/pytrize/jump/param.lua @@ -5,7 +5,7 @@ local nids = require('pytrize.nodeids') local tbls = require('pytrize.tables') local paths = require('pytrize.paths') -- local prompt_files = require('pytrize.input').prompt_files -local warn = require('pytrize.warn').warn +local notify = require('pytrize.notify') local open_file = require('pytrize.jump.util').open_file local get_nodeids_path = require('pytrize.paths').get_nodeids_path local min = require('pytrize.utils').min @@ -29,7 +29,7 @@ local function query_file(func_name, callback) end end if #files == 0 then - warn(string.format( + notify.warn(string.format( 'could not find the file for function `%s` when looking in %s, did you run the test?', func_name, get_nodeids_path(rootdir) @@ -50,7 +50,7 @@ local function jump_to_nodeid_at_cursor(callback) local line = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, 0)[1] local i, _ = string.find(line, '%S*:?:?test_%w+%[.*') -- TODO how to check for zero or two :? if i == nil then - warn("no nodeid under cursor") + notify.warn("no nodeid under cursor") return end local nodeid = nids.parse_raw(line:sub(i)) @@ -58,7 +58,7 @@ local function jump_to_nodeid_at_cursor(callback) local param_position = pattern_position - nodeid.param_start_idx + 1 -- cursor relative to params param_position = min(max(1, param_position), nodeid.params:len()) -- restrict it to be inside if nodeid == nil then - warn("couldn't parse nodeid under cursor") + notify.warn("couldn't parse nodeid under cursor") return end if nodeid.file == nil then @@ -83,7 +83,7 @@ end -- end -- end -- if found_call_spec == nil then --- warn("couldn't find the declaration with param " .. param) +-- notify.warn("couldn't find the declaration with param " .. param) -- return -- end -- return found_call_spec @@ -104,7 +104,7 @@ end -- local max = tbls.max_length(param_values[call_spec.func_name]) -- while true do -- if list_idx > max then --- warn("couldn't find the declaration matching id " .. param_id) +-- notify.warn("couldn't find the declaration matching id " .. param_id) -- return -- end -- local pid = params.get_id(param_values[call_spec.func_name], call_spec.params, list_idx) @@ -137,7 +137,7 @@ M.to_declaration = function() local bufnr = 0 local original_buffer = vim.api.nvim_buf_get_name(bufnr) if vim.fn.filereadable(nodeid.file) == 0 then - warn(string.format('file `%s` is not readable', nodeid.file)) + notify.warn(string.format('file `%s` is not readable', nodeid.file)) return end open_file(nodeid.file) @@ -166,7 +166,7 @@ M.to_declaration = function() end end end - warn(string.format( + notify.warn(string.format( 'could not find the id `%s` of `%s` in file `%s`', nodeid.params, nodeid.func_name, diff --git a/lua/pytrize/nodeids.lua b/lua/pytrize/nodeids.lua index 228ee63..1d5d9d4 100644 --- a/lua/pytrize/nodeids.lua +++ b/lua/pytrize/nodeids.lua @@ -1,7 +1,7 @@ local M = {} local split_once = require('pytrize.strings').split_once -local warn = require('pytrize.warn').warn +local notify = require('pytrize.notify') local get_nodeids_path = require('pytrize.paths').get_nodeids_path local function get_raw_nodeids(rootdir) @@ -44,7 +44,7 @@ M.get = function(rootdir) local nodeid = M.parse_raw(raw_nodeid) if nodeid ~= nil then if nodeid.file == nil then - warn('node id has no file') + notify.warn('node id has no file') return {} end if nodeids[nodeid.file] == nil then diff --git a/lua/pytrize/notify.lua b/lua/pytrize/notify.lua new file mode 100644 index 0000000..0374ff4 --- /dev/null +++ b/lua/pytrize/notify.lua @@ -0,0 +1,14 @@ +local M = {} + +M.warn = function(msg) + msg = vim.fn.escape(msg, '"'):gsub('\\n', '\n') + -- vim.cmd(string.format('echohl WarningMsg | echo "Pytrize Warning: %s" | echohl None', msg)) + vim.notify(string.format("Pytrize Warning: %s", msg), vim.log.levels.WARN) +end + +M.err = function(msg) + msg = vim.fn.escape(msg, '"'):gsub('\\n', '\n') + vim.notify(string.format("Pytrize Error: %s", msg), vim.log.levels.ERROR) +end + +return M diff --git a/lua/pytrize/params.lua b/lua/pytrize/params.lua index 0469fbd..ecf6bb6 100644 --- a/lua/pytrize/params.lua +++ b/lua/pytrize/params.lua @@ -2,7 +2,7 @@ -- -- local paths = require('pytrize.paths') -- local nids = require('pytrize.nodeids') --- local warn = require('pytrize.warn').warn +-- local notify = require('pytrize.warn') -- -- M.get_values = function(param_order, bufnr) -- local rootdir, file = paths.split_at_root(vim.api.nvim_buf_get_name(bufnr)) @@ -11,7 +11,7 @@ -- end -- local nodeids = nids.get(rootdir) -- if nodeids[file] == nil then --- warn("no pytest cache for file " .. file .. " at root dir " .. rootdir) +-- notify.warn("no pytest cache for file " .. file .. " at root dir " .. rootdir) -- return -- end -- local values_per_func = {} diff --git a/lua/pytrize/paths.lua b/lua/pytrize/paths.lua index ab0cac0..cefc096 100644 --- a/lua/pytrize/paths.lua +++ b/lua/pytrize/paths.lua @@ -1,6 +1,6 @@ local M = {} -local warn = require('pytrize.warn').warn +local notify = require('pytrize.notify') local function is_root_dir(dir) return vim.fn.finddir('.pytest_cache', dir) ~= '' @@ -25,7 +25,7 @@ M.split_at_root = function(file) return dir, join_path(rel_file_fragments) end end - warn("couldn't find the pytest root dir") + notify.warn("couldn't find the pytest root dir") end M.get_nodeids_path = function(rootdir) diff --git a/lua/pytrize/settings.lua b/lua/pytrize/settings.lua index 8f2c191..8f05485 100644 --- a/lua/pytrize/settings.lua +++ b/lua/pytrize/settings.lua @@ -1,18 +1,19 @@ local M = {} -local warn = require('pytrize.warn').warn +local notify = require('pytrize.notify') -- defaults M.settings = { no_commands = false, + no_autocmds = false, highlight = 'LineNr', - -- preferred_input = 'telescope', + preferred_input = 'telescope', } M.update = function(opts) for k, v in pairs(opts) do if M.settings[k] == nil then - warn("unexpected setting " .. k) + notify.warn("unexpected setting " .. k) else M.settings[k] = v end diff --git a/lua/pytrize/warn.lua b/lua/pytrize/warn.lua deleted file mode 100644 index 02468b1..0000000 --- a/lua/pytrize/warn.lua +++ /dev/null @@ -1,9 +0,0 @@ -local M = {} - -M.warn = function(msg) - msg = vim.fn.escape(msg, '"'):gsub('\\n', '\n') - -- vim.cmd(string.format('echohl WarningMsg | echo "Pytrize Warning: %s" | echohl None', msg)) - vim.notify(vim.split(string.format("Pytrize Warning: %s", msg), '\n'), vim.log.levels.WARN) -end - -return M