From 8783bea06e1e0dfa8dfd4834058923088471d832 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Mon, 11 Jan 2021 13:29:37 -0500 Subject: [PATCH] feat: quickfix (#293) * feat: quickfix (not implemented) * [WIP]: Wed 09 Dec 2020 11:11:30 PM EST * somewhat working linked list impl * getting closer * might be working * might be working for real * works and implemented basic example * dont forget to close prompt * fix descending and add more tests * test fixes * fix test * more logging * Fix some more tests * Fix logging messing up tests * fix: lint * fix: multi select stuffs --- lua/telescope/actions/init.lua | 49 ++++ lua/telescope/algos/linked_list.lua | 222 +++++++++++++++ lua/telescope/config.lua | 4 +- lua/telescope/entry_manager.lua | 255 +++++++++++------- lua/telescope/pickers.lua | 136 +++++++--- lua/telescope/pickers/_test.lua | 191 +++++++++---- lua/telescope/pickers/_test_helpers.lua | 12 +- lua/telescope/pickers/highlights.lua | 23 ++ lua/telescope/pickers/scroller.lua | 103 ++++--- lua/tests/automated/entry_manager_spec.lua | 142 ++++++++++ lua/tests/automated/linked_list_spec.lua | 133 +++++++++ .../automated/pickers/find_files_spec.lua | 109 +++++--- .../automated/pickers/scrolling_spec.lua | 9 + lua/tests/automated/scroller_spec.lua | 11 +- lua/tests/automated/telescope_spec.lua | 99 ------- lua/tests/pickers/find_files__readme.lua | 3 +- ...find_files__scrolling_descending_cycle.lua | 14 + lua/tests/pickers/find_files__with_ctrl_n.lua | 2 +- scratch/nvim_ffi.lua | 4 +- 19 files changed, 1152 insertions(+), 369 deletions(-) create mode 100644 lua/telescope/algos/linked_list.lua create mode 100644 lua/tests/automated/entry_manager_spec.lua create mode 100644 lua/tests/automated/linked_list_spec.lua create mode 100644 lua/tests/automated/pickers/scrolling_spec.lua create mode 100644 lua/tests/pickers/find_files__scrolling_descending_cycle.lua diff --git a/lua/telescope/actions/init.lua b/lua/telescope/actions/init.lua index 6ffb3ef..f4c3b08 100644 --- a/lua/telescope/actions/init.lua +++ b/lua/telescope/actions/init.lua @@ -38,6 +38,16 @@ function actions.add_selection(prompt_bufnr) current_picker:add_selection(current_picker:get_selection_row()) end +function actions.remove_selection(prompt_bufnr) + local current_picker = actions.get_current_picker(prompt_bufnr) + current_picker:remove_selection(current_picker:get_selection_row()) +end + +function actions.toggle_selection(prompt_bufnr) + local current_picker = actions.get_current_picker(prompt_bufnr) + current_picker:toggle_selection(current_picker:get_selection_row()) +end + --- Get the current entry function actions.get_selected_entry() return state.get_global_key('selected_entry') @@ -273,6 +283,45 @@ actions.git_staging_toggle = function(prompt_bufnr) require('telescope.builtin').git_status() end +local entry_to_qf = function(entry) + return { + bufnr = entry.bufnr, + filename = entry.filename, + lnum = entry.lnum, + col = entry.col, + text = entry.value, + } +end + +actions.send_selected_to_qflist = function(prompt_bufnr) + local picker = actions.get_current_picker(prompt_bufnr) + + local qf_entries = {} + for entry in pairs(picker.multi_select) do + table.insert(qf_entries, entry_to_qf(entry)) + end + + actions.close(prompt_bufnr) + + vim.fn.setqflist(qf_entries, 'r') + vim.cmd [[copen]] +end + +actions.send_to_qflist = function(prompt_bufnr) + local picker = actions.get_current_picker(prompt_bufnr) + local manager = picker.manager + + local qf_entries = {} + for entry in manager:iter() do + table.insert(qf_entries, entry_to_qf(entry)) + end + + actions.close(prompt_bufnr) + + vim.fn.setqflist(qf_entries, 'r') + vim.cmd [[copen]] +end + -- ================================================== -- Transforms modules and sets the corect metatables. -- ================================================== diff --git a/lua/telescope/algos/linked_list.lua b/lua/telescope/algos/linked_list.lua new file mode 100644 index 0000000..cf1c47d --- /dev/null +++ b/lua/telescope/algos/linked_list.lua @@ -0,0 +1,222 @@ + +local LinkedList = {} +LinkedList.__index = LinkedList + +function LinkedList:new(opts) + opts = opts or {} + local track_at = opts.track_at + + return setmetatable({ + size = 0, + head = false, + tail = false, + + -- track_at: Track at can track a particular node + -- Use to keep a node tracked at a particular index + -- This greatly decreases looping for checking values at this location. + track_at = track_at, + _tracked_node = nil, + tracked = nil, + }, self) +end + +function LinkedList:_increment() + self.size = self.size + 1 + return self.size +end + +local create_node = function(item) + return { + item = item + } +end + +function LinkedList:append(item) + local final_size = self:_increment() + + local node = create_node(item) + + if not self.head then + self.head = node + end + + if self.tail then + self.tail.next = node + node.prev = self.tail + end + + self.tail = node + + if self.track_at then + if final_size == self.track_at then + self.tracked = item + self._tracked_node = node + end + end +end + +function LinkedList:prepend(item) + local final_size = self:_increment() + local node = create_node(item) + + if not self.tail then + self.tail = node + end + + if self.head then + self.head.prev = node + node.next = self.head + end + + self.head = node + + if self.track_at then + if final_size == self.track_at then + self._tracked_node = self.tail + elseif final_size > self.track_at then + self._tracked_node = self._tracked_node.prev + else + return + end + + self.tracked = self._tracked_node.item + end +end + +-- [a, b, c] +-- b.prev = a +-- b.next = c +-- +-- a.next = b +-- c.prev = c +-- +-- insert d after b +-- [a, b, d, c] +-- +-- b.next = d +-- b.prev = a +-- +-- Place "item" after "node" (which is at index `index`) +function LinkedList:place_after(index, node, item) + local new_node = create_node(item) + + assert(node.prev ~= node) + assert(node.next ~= node) + local final_size = self:_increment() + + -- Update tail to be the next node. + if self.tail == node then + self.tail = new_node + end + + new_node.prev = node + new_node.next = node.next + + node.next = new_node + + if new_node.prev then + new_node.prev.next = new_node + end + + if new_node.next then + new_node.next.prev = new_node + end + + + if self.track_at then + if index == self.track_at then + self._tracked_node = new_node + elseif index < self.track_at then + if final_size == self.track_at then + self._tracked_node = self.tail + elseif final_size > self.track_at then + self._tracked_node = self._tracked_node.prev + else + return + end + end + + self.tracked = self._tracked_node.item + end +end + +function LinkedList:place_before(index, node, item) + local new_node = create_node(item) + + assert(node.prev ~= node) + assert(node.next ~= node) + local final_size = self:_increment() + + -- Update head to be the node we are inserting. + if self.head == node then + self.head = new_node + end + + new_node.prev = node.prev + new_node.next = node + + node.prev = new_node + -- node.next = node.next + + if new_node.prev then + new_node.prev.next = new_node + end + + if new_node.next then + new_node.next.prev = new_node + end + + + if self.track_at then + if index == self.track_at - 1 then + self._tracked_node = node + elseif index < self.track_at then + if final_size == self.track_at then + self._tracked_node = self.tail + elseif final_size > self.track_at then + self._tracked_node = self._tracked_node.prev + else + return + end + end + + self.tracked = self._tracked_node.item + end +end + + +-- Do you even do this in linked lists...? +-- function LinkedList:remove(item) +-- end + +function LinkedList:iter() + local current_node = self.head + + return function() + local node = current_node + if not node then + return nil + end + + current_node = current_node.next + return node.item + end +end + +function LinkedList:ipairs() + local index = 0 + local current_node = self.head + + return function() + local node = current_node + if not node then + return nil + end + + current_node = current_node.next + index = index + 1 + return index, node.item, node + end +end + +return LinkedList diff --git a/lua/telescope/config.lua b/lua/telescope/config.lua index eb527eb..6403a54 100644 --- a/lua/telescope/config.lua +++ b/lua/telescope/config.lua @@ -34,7 +34,7 @@ function config.set_defaults(defaults) set("sorting_strategy", "descending") set("selection_strategy", "reset") - set("scroll_strategy", nil) + set("scroll_strategy", "cycle") set("layout_strategy", "horizontal") set("layout_defaults", {}) @@ -53,7 +53,7 @@ function config.set_defaults(defaults) set("borderchars", { '─', '│', '─', '│', '╭', '╮', '╯', '╰'}) set("get_status_text", function(self) - return string.format("%s / %s", self.stats.processed - self.stats.filtered, self.stats.processed) + return string.format("%s / %s", (self.stats.processed or 0) - (self.stats.filtered or 0), self.stats.processed) end) -- Builtin configuration diff --git a/lua/telescope/entry_manager.lua b/lua/telescope/entry_manager.lua index 237f0e5..c7350cb 100644 --- a/lua/telescope/entry_manager.lua +++ b/lua/telescope/entry_manager.lua @@ -1,131 +1,192 @@ local log = require("telescope.log") +local LinkedList = require('telescope.algos.linked_list') + +--[[ + +OK, new idea. +We can do linked list here. +To convert at the end to quickfix, just run the list. +... + +start node +end node + +if past loop of must have scores, + then we can just add to end node and shift end node to current node. + etc. + + + always inserts a row, because we clear everything before? + + can also optimize by keeping worst acceptable score around. + +--]] + local EntryManager = {} EntryManager.__index = EntryManager -function EntryManager:new(max_results, set_entry, info) +function EntryManager:new(max_results, set_entry, info, id) log.trace("Creating entry_manager...") info = info or {} info.looped = 0 info.inserted = 0 + info.find_loop = 0 -- state contains list of - -- { - -- score = ... - -- line = ... - -- metadata ? ... - -- } - local entry_state = {} - + -- { entry, score } + -- Stored directly in a table, accessed as [1], [2] set_entry = set_entry or function() end return setmetatable({ - set_entry = set_entry, - max_results = max_results, - worst_acceptable_score = math.huge, - - entry_state = entry_state, + id = id, + linked_states = LinkedList:new { track_at = max_results }, info = info, - - num_results = function() - return #entry_state - end, - - get_ordinal = function(em, index) - return em:get_entry(index).ordinal - end, - - get_entry = function(_, index) - return (entry_state[index] or {}).entry - end, - - get_score = function(_, index) - return (entry_state[index] or {}).score - end, - - find_entry = function(_, entry) - if entry == nil then - return nil - end - - for k, v in ipairs(entry_state) do - local existing_entry = v.entry - - -- FIXME: This has the problem of assuming that display will not be the same for two different entries. - if existing_entry == entry then - return k - end - end - - return nil - end, - - _get_state = function() - return entry_state - end, + max_results = max_results, + set_entry = set_entry, + worst_acceptable_score = math.huge, }, self) end -function EntryManager:should_save_result(index) - return index <= self.max_results +function EntryManager:num_results() + return self.linked_states.size +end + +function EntryManager:get_container(index) + local count = 0 + for val in self.linked_states:iter() do + count = count + 1 + + if count == index then + return val + end + end + + return {} +end + +function EntryManager:get_entry(index) + return self:get_container(index)[1] +end + +function EntryManager:get_score(index) + return self:get_container(index)[2] +end + +function EntryManager:get_ordinal(index) + return self:get_entry(index).ordinal +end + +function EntryManager:find_entry(entry) + local info = self.info + + local count = 0 + for container in self.linked_states:iter() do + count = count + 1 + + if container[1] == entry then + info.find_loop = info.find_loop + count + + return count + end + end + + info.find_loop = info.find_loop + count + return nil +end + +function EntryManager:_update_score_from_tracked() + local linked = self.linked_states + + if linked.tracked then + self.worst_acceptable_score = math.min(self.worst_acceptable_score, linked.tracked[2]) + end +end + +function EntryManager:_insert_container_before(picker, index, linked_node, new_container) + self.linked_states:place_before(index, linked_node, new_container) + self.set_entry(picker, index, new_container[1], new_container[2], true) + + self:_update_score_from_tracked() +end + +function EntryManager:_insert_container_after(picker, index, linked_node, new_container) + self.linked_states:place_after(index, linked_node, new_container) + self.set_entry(picker, index, new_container[1], new_container[2], true) + + self:_update_score_from_tracked() +end + +function EntryManager:_append_container(picker, new_container, should_update) + self.linked_states:append(new_container) + self.worst_acceptable_score = math.min(self.worst_acceptable_score, new_container[2]) + + if should_update then + self.set_entry(picker, self.linked_states.size, new_container[1], new_container[2]) + end end function EntryManager:add_entry(picker, score, entry) - score = score or 0 - - if score >= self.worst_acceptable_score then - return - end - - for index, item in ipairs(self.entry_state) do - self.info.looped = self.info.looped + 1 - - if item.score > score then - return self:insert(picker, index, { - score = score, - entry = entry, - }) - end - - -- Don't add results that are too bad. - if not self:should_save_result(index) then + if picker and picker.id then + if picker.request_number ~= self.id then + error("ADDING ENTRY TOO LATE!") return end end - return self:insert(picker, { - score = score, - entry = entry, - }) -end + score = score or 0 -function EntryManager:insert(picker, index, entry) - if entry == nil then - entry = index - index = #self.entry_state + 1 + local max_res = self.max_results + local worst_score = self.worst_acceptable_score + local size = self.linked_states.size + + local info = self.info + info.maxed = info.maxed or 0 + + local new_container = { entry, score, } + + -- Short circuit for bad scores -- they never need to be displayed. + -- Just save them and we'll deal with them later. + if score >= worst_score then + return self.linked_states:append(new_container) end - -- To insert something, we place at the next available index (or specified index) - -- and then shift all the corresponding items one place. - local next_entry, last_score - repeat - self.info.inserted = self.info.inserted + 1 - next_entry = self.entry_state[index] - - self.set_entry(picker, index, entry.entry, entry.score) - self.entry_state[index] = entry - - last_score = entry.score - - index = index + 1 - entry = next_entry - until not next_entry or not self:should_save_result(index) - - if not self:should_save_result(index) then - self.worst_acceptable_score = last_score + -- Short circuit for first entry. + if size == 0 then + self.linked_states:prepend(new_container) + self.set_entry(picker, 1, entry, score) + return end + + for index, container, node in self.linked_states:ipairs() do + info.looped = info.looped + 1 + + if container[2] > score then + -- print("Inserting: ", picker, index, node, new_container) + return self:_insert_container_before(picker, index, node, new_container) + end + + -- Don't add results that are too bad. + if index >= max_res then + info.maxed = info.maxed + 1 + return self:_append_container(picker, new_container, false) + end + end + + if self.linked_states.size >= max_res then + self.worst_acceptable_score = math.min(self.worst_acceptable_score, score) + end + + return self:_insert_container_after(picker, size + 1, self.linked_states.tail, new_container) end +function EntryManager:iter() + return coroutine.wrap(function() + for val in self.linked_states:iter() do + coroutine.yield(val[1]) + end + end) +end return EntryManager diff --git a/lua/telescope/pickers.lua b/lua/telescope/pickers.lua index 67553c2..d129737 100644 --- a/lua/telescope/pickers.lua +++ b/lua/telescope/pickers.lua @@ -221,14 +221,17 @@ function Picker:is_done() end function Picker:clear_extra_rows(results_bufnr) - if self:is_done() then return end + if self:is_done() then + log.trace("Not clearing due to being already complete") + return + end if not vim.api.nvim_buf_is_valid(results_bufnr) then log.debug("Invalid results_bufnr for clearing:", results_bufnr) return end - local worst_line + local worst_line, ok, msg if self.sorting_strategy == 'ascending' then local num_results = self.manager:num_results() worst_line = self.max_results - num_results @@ -237,7 +240,7 @@ function Picker:clear_extra_rows(results_bufnr) return end - pcall(vim.api.nvim_buf_set_lines, results_bufnr, num_results, self.max_results, false, {}) + ok, msg = pcall(vim.api.nvim_buf_set_lines, results_bufnr, num_results, -1, false, {}) else worst_line = self:get_row(self.manager:num_results()) if worst_line <= 0 then @@ -245,10 +248,14 @@ function Picker:clear_extra_rows(results_bufnr) end local empty_lines = utils.repeated_table(worst_line, "") - pcall(vim.api.nvim_buf_set_lines, results_bufnr, 0, worst_line, false, empty_lines) + ok, msg = pcall(vim.api.nvim_buf_set_lines, results_bufnr, 0, worst_line, false, empty_lines) end - log.trace("Clearing:", worst_line) + if not ok then + log.debug(msg) + end + + log.debug("Clearing:", worst_line) end function Picker:highlight_displayed_rows(results_bufnr, prompt) @@ -296,6 +303,9 @@ function Picker:highlight_one_row(results_bufnr, prompt, display, row) ) end end + + local entry = self.manager:get_entry(self:get_index(row)) + self.highlighter:hi_multiselect(row, entry) end function Picker:can_select_row(row) @@ -416,7 +426,9 @@ function Picker:find() local debounced_status = debounce.throttle_leading(update_status, 50) + self.request_number = 0 local on_lines = function(_, _, _, first_line, last_line) + self.request_number = self.request_number + 1 self:_reset_track() if not vim.api.nvim_buf_is_valid(prompt_bufnr) then @@ -424,6 +436,9 @@ function Picker:find() return end + if not first_line then first_line = 0 end + if not last_line then last_line = 1 end + if first_line > 0 or last_line > 1 then log.debug("ON_LINES: Bad range", first_line, last_line) return @@ -434,13 +449,8 @@ function Picker:find() self.sorter:_start(prompt) end - -- TODO: Statusbar possibilities here. - -- vim.api.nvim_buf_set_virtual_text(prompt_bufnr, 0, 1, { {"hello", "Error"} }, {}) - -- TODO: Entry manager should have a "bulk" setter. This can prevent a lot of redraws from display - - self.manager = EntryManager:new(self.max_results, self.entry_adder) - -- self.manager = EntryManager:new(self.max_results, self.entry_adder, self.stats) + self.manager = EntryManager:new(self.max_results, self.entry_adder, self.stats, self.request_number) local process_result = function(entry) if self:is_done() then return end @@ -690,24 +700,48 @@ end function Picker:add_selection(row) local entry = self.manager:get_entry(self:get_index(row)) self.multi_select[entry] = true + + self.highlighter:hi_multiselect(row, entry) +end + +function Picker:add_selection(row) + local entry = self.manager:get_entry(self:get_index(row)) + self.multi_select[entry] = true + + self.highlighter:hi_multiselect(row, entry) +end + +function Picker:remove_selection(row) + local entry = self.manager:get_entry(self:get_index(row)) + self.multi_select[entry] = nil + + self.highlighter:hi_multiselect(row, entry) +end + +function Picker:toggle_selection(row) + local entry = self.manager:get_entry(self:get_index(row)) + + if self.multi_select[entry] then + self:remove_selection(row) + else + self:add_selection(row) + end end function Picker:display_multi_select(results_bufnr) - if true then return end - - -- for entry, _ in pairs(self.multi_select) do - -- local index = self.manager:find_entry(entry) - -- if index then - -- vim.api.nvim_buf_add_highlight( - -- results_bufnr, - -- ns_telescope_selection, - -- "TelescopeMultiSelection", - -- self:get_row(index), - -- 0, - -- -1 - -- ) - -- end - -- end + for entry, _ in pairs(self.multi_select) do + local index = self.manager:find_entry(entry) + if index then + vim.api.nvim_buf_add_highlight( + results_bufnr, + a.nvim_create_namespace('telescope_selection'), + "TelescopeMultiSelection", + self:get_row(index), + 0, + -1 + ) + end + end end function Picker:reset_selection() @@ -718,25 +752,30 @@ function Picker:reset_selection() end function Picker:set_selection(row) - -- TODO: Loop around behavior? - -- TODO: Scrolling past max results row = self.scroller(self.max_results, self.manager:num_results(), row) if not self:can_select_row(row) then -- If the current selected row exceeds number of currently displayed - -- elements we have to reset it. Affectes sorting_strategy = 'row'. + -- elements we have to reset it. Affects sorting_strategy = 'row'. if not self:can_select_row(self:get_selection_row()) then row = self:get_row(self.manager:num_results()) else - log.debug("Cannot select row:", row, self.manager:num_results(), self.max_results) + log.trace("Cannot select row:", row, self.manager:num_results(), self.max_results) return end end - -- local entry = self.manager:get_entry(self.max_results - row + 1) local entry = self.manager:get_entry(self:get_index(row)) local status = state.get_status(self.prompt_bufnr) - local results_bufnr = status.results_bufnr + local results_bufnr = self.results_bufnr + + if row > a.nvim_buf_line_count(results_bufnr) then + error(string.format( + "Should not be possible to get row this large %s %s", + row, + a.nvim_buf_line_count(results_bufnr) + )) + end state.set_global_key("selected_entry", entry) @@ -764,6 +803,7 @@ function Picker:set_selection(row) self.highlighter:hi_display(self._selection_row, ' ', display_highlights) self.highlighter:hi_sorter(self._selection_row, prompt, display) + self.highlighter:hi_multiselect(self._selection_row, self._selection_entry) end end @@ -785,9 +825,7 @@ function Picker:set_selection(row) self.highlighter:hi_selection(row, caret) self.highlighter:hi_display(row, ' ', display_highlights) self.highlighter:hi_sorter(row, prompt, display) - - -- TODO: Actually implement this for real TJ, don't leave around half implemented code plz :) - -- self:display_multi_select(results_bufnr) + self.highlighter:hi_multiselect(row, entry) end) if not set_ok then @@ -814,7 +852,7 @@ function Picker:set_selection(row) end -function Picker:entry_adder(index, entry, score) +function Picker:entry_adder(index, entry, score, insert) local row = self:get_row(index) -- If it's less than 0, then we don't need to show it at all. @@ -838,17 +876,39 @@ function Picker:entry_adder(index, entry, score) self:_increment("displayed") -- TODO: Don't need to schedule this if we schedule the adder. + local offset = insert and 0 or 1 + local scheduled_request = self.request_number vim.schedule(function() if not vim.api.nvim_buf_is_valid(self.results_bufnr) then log.debug("ON_ENTRY: Invalid buffer") return end - local set_ok = pcall(vim.api.nvim_buf_set_lines, self.results_bufnr, row, row + 1, false, {display}) + if self.request_number ~= scheduled_request then + log.debug("Cancelling request number:", self.request_number, " // ", scheduled_request) + return + end + + local line_count = vim.api.nvim_buf_line_count(self.results_bufnr) + if row > line_count then + return + end + + if insert then + if self.sorting_strategy == 'descending' then + vim.api.nvim_buf_set_lines(self.results_bufnr, 0, 1, false, {}) + end + end + + local set_ok, msg = pcall(vim.api.nvim_buf_set_lines, self.results_bufnr, row, row + offset, false, {display}) if set_ok and display_highlights then self.highlighter:hi_display(row, prefix, display_highlights) end + if not set_ok then + log.debug("Failed to set lines...", msg) + end + -- This pretty much only fails when people leave newlines in their results. -- So we'll clean it up for them if it fails. if not set_ok and display:find("\n") then @@ -893,7 +953,7 @@ function Picker:_track(key, func, ...) end function Picker:_increment(key) - self.stats[key] = self.stats[key] + 1 + self.stats[key] = (self.stats[key] or 0) + 1 end diff --git a/lua/telescope/pickers/_test.lua b/lua/telescope/pickers/_test.lua index ddd245a..1ab5a97 100644 --- a/lua/telescope/pickers/_test.lua +++ b/lua/telescope/pickers/_test.lua @@ -1,10 +1,14 @@ local assert = require('luassert') local builtin = require('telescope.builtin') +local log = require('telescope.log') local Job = require("plenary.job") +local Path = require("plenary.path") local tester = {} +tester.debug = false + local replace_terms = function(input) return vim.api.nvim_replace_termcodes(input, true, false, true) end @@ -15,7 +19,53 @@ local nvim_feed = function(text, feed_opts) vim.api.nvim_feedkeys(text, feed_opts, true) end -tester.picker_feed = function(input, test_cases, debug) +local writer = function(val) + if type(val) == "table" then + val = vim.fn.json_encode(val) .. "\n" + end + + if tester.debug then + print(val) + else + io.stderr:write(val) + end +end + +local execute_test_case = function(location, key, spec) + local ok, actual = pcall(spec[2]) + + if not ok then + writer { + location = 'Error: ' .. location, + case = key, + expected = 'To succeed and return: ' .. tostring(spec[1]), + actual = actual, + + _type = spec._type, + } + else + writer { + location = location, + case = key, + expected = spec[1], + actual = actual, + + _type = spec._type, + } + end +end + +local end_test_cases = function() + vim.cmd [[qa!]] +end + +local invalid_test_case = function(k) + writer { case = k, expected = '', actual = k } + + end_test_cases() +end + +tester.picker_feed = function(input, test_cases) input = replace_terms(input) return coroutine.wrap(function() @@ -28,75 +78,66 @@ tester.picker_feed = function(input, test_cases, debug) if string.match(char, "%g") then coroutine.yield() end + + if tester.debug then + vim.wait(200) + end end - vim.wait(10, function() end) + vim.wait(10) - local timer = vim.loop.new_timer() - timer:start(20, 0, vim.schedule_wrap(function() - if test_cases.post_close then - for k, v in ipairs(test_cases.post_close) do - io.stderr:write(vim.fn.json_encode({ case = k, expected = v[1], actual = v[2]() })) - io.stderr:write("\n") + if tester.debug then + coroutine.yield() + end + + vim.defer_fn(function() + if test_cases.post_typed then + for k, v in ipairs(test_cases.post_typed) do + execute_test_case('post_typed', k, v) end end - if debug then + nvim_feed(replace_terms(""), "") + end, 20) + + vim.defer_fn(function() + if test_cases.post_close then + for k, v in ipairs(test_cases.post_close) do + execute_test_case('post_close', k, v) + end + end + + if tester.debug then return end - vim.defer_fn(function() - vim.cmd [[qa!]] - end, 10) - end)) + vim.defer_fn(end_test_cases, 20) + end, 40) - if not debug then - vim.schedule(function() - if test_cases.post_typed then - for k, v in ipairs(test_cases.post_typed) do - io.stderr:write(vim.fn.json_encode({ case = k, expected = v[1], actual = v[2]() })) - io.stderr:write("\n") - end - end - - nvim_feed(replace_terms(""), "") - end) - end coroutine.yield() end) end --- local test_cases = { --- post_typed = { --- }, --- post_close = { --- { "README.md", function() return "README.md" end }, --- }, --- } - local _VALID_KEYS = { post_typed = true, post_close = true, } -tester.builtin_picker = function(key, input, test_cases, opts) +tester.builtin_picker = function(builtin_key, input, test_cases, opts) opts = opts or {} - local debug = opts.debug or false + tester.debug = opts.debug or false for k, _ in pairs(test_cases) do if not _VALID_KEYS[k] then - -- TODO: Make an error type for the json protocol. - io.stderr:write(vim.fn.json_encode({ case = k, expected = '', actual = k })) - io.stderr:write("\n") - vim.cmd [[qa!]] + return invalid_test_case(k) end end opts.on_complete = { - tester.picker_feed(input, test_cases, debug) + tester.picker_feed(input, test_cases), } - builtin[key](opts) + builtin[builtin_key](opts) end local get_results_from_file = function(file) @@ -107,11 +148,14 @@ local get_results_from_file = function(file) '-u', 'scripts/minimal_init.vim', '-c', - 'luafile ' .. file + string.format( + [[lua require("telescope.pickers._test")._execute("%s")]], + file + ), }, } - j:sync() + j:sync(1000) local results = j:stderr_result() local result_table = {} @@ -122,10 +166,27 @@ local get_results_from_file = function(file) return result_table end + +local asserters = { + _default = assert.are.same, + + are = assert.are.same, + are_not = assert.are_not.same, +} + + local check_results = function(results) -- TODO: We should get all the test cases here that fail, not just the first one. for _, v in ipairs(results) do - assert.are.same(v.expected, v.actual) + local assertion = asserters[v._type or 'default'] + + assertion( + v.expected, + v.actual, + string.format("Test Case: %s // %s", + v.location, + v.case) + ) end end @@ -144,14 +205,52 @@ tester.run_string = function(contents) vim.fn.delete(tempname) check_results(result_table) - -- assert.are.same(result_table.expected, result_table.actual) end tester.run_file = function(filename) local file = './lua/tests/pickers/' .. filename .. '.lua' + if not Path:new(file):exists() then + assert.are.same("", file) + end + local result_table = get_results_from_file(file) - assert.are.same(result_table.expected, result_table.actual) + + check_results(result_table) end +tester.not_ = function(val) + val._type = 'are_not' + return val +end + +tester._execute = function(filename) + -- Important so that the outputs don't get mixed + log.use_console = false + + vim.cmd(string.format("luafile %s", filename)) + + local f = loadfile(filename) + if not f then + writer { + location = 'Error: ' .. filename, + case = filename, + expected = 'To succeed', + actual = nil, + } + end + + local ok, msg = pcall(f) + if not ok then + writer { + location = "Error: " .. msg, + case = msg, + expected = msg, + } + end + + end_test_cases() +end + + return tester diff --git a/lua/telescope/pickers/_test_helpers.lua b/lua/telescope/pickers/_test_helpers.lua index a430fae..63d1a0f 100644 --- a/lua/telescope/pickers/_test_helpers.lua +++ b/lua/telescope/pickers/_test_helpers.lua @@ -22,9 +22,15 @@ test_helpers.get_results = function() return vim.api.nvim_buf_get_lines(test_helpers.get_results_bufnr(), 0, -1, false) end -test_helpers.get_last_result = function() +test_helpers.get_best_result = function() local results = test_helpers.get_results() - return results[#results] + local picker = test_helpers.get_picker () + + if picker.sorting_strategy == 'ascending' then + return results[1] + else + return results[#results] + end end test_helpers.get_selection = function() @@ -41,7 +47,7 @@ test_helpers.make_globals = function() GetPrompt = test_helpers.get_prompt -- luacheck: globals GetPrompt GetResults = test_helpers.get_results -- luacheck: globals GetResults - GetLastResult = test_helpers.get_last_result -- luacheck: globals GetLastResult + GetBestResult = test_helpers.get_best_result -- luacheck: globals GetBestResult GetSelection = test_helpers.get_selection -- luacheck: globals GetSelection GetSelectionValue = test_helpers.get_selection_value -- luacheck: globals GetSelectionValue diff --git a/lua/telescope/pickers/highlights.lua b/lua/telescope/pickers/highlights.lua index 23e796e..62ffb4b 100644 --- a/lua/telescope/pickers/highlights.lua +++ b/lua/telescope/pickers/highlights.lua @@ -3,6 +3,7 @@ local a = vim.api local highlights = {} local ns_telescope_selection = a.nvim_create_namespace('telescope_selection') +local ns_telescope_multiselection = a.nvim_create_namespace('telescope_mulitselection') local ns_telescope_entry = a.nvim_create_namespace('telescope_entry') local Highlighter = {} @@ -75,6 +76,28 @@ function Highlighter:hi_selection(row, caret) ) end +function Highlighter:hi_multiselect(row, entry) + local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr") + + if self.picker.multi_select[entry] then + vim.api.nvim_buf_add_highlight( + results_bufnr, + ns_telescope_multiselection, + "TelescopeMultiSelection", + row, + 0, + -1 + ) + else + vim.api.nvim_buf_clear_namespace( + results_bufnr, + ns_telescope_multiselection, + row, + row + 1 + ) + end +end + highlights.new = function(...) return Highlighter:new(...) end diff --git a/lua/telescope/pickers/scroller.lua b/lua/telescope/pickers/scroller.lua index 324627b..3169c4b 100644 --- a/lua/telescope/pickers/scroller.lua +++ b/lua/telescope/pickers/scroller.lua @@ -1,56 +1,75 @@ local scroller = {} -local calc_count_fn = function(sorting_strategy) - if sorting_strategy == 'ascending' then - return function(a, b) return math.min(a, b) end - else - return function(a, b, row) - if a == b or not row then - return math.max(a, b) - else - local x = a - b - if row < x then - return math.max(a, b) - 1, true - elseif row == a then - return x, true - else - return math.max(a, b) - end +local range_calculators = { + ascending = function(max_results, num_results) + return 0, math.min(max_results, num_results) + end, + + descending = function(max_results, num_results) + return math.max(max_results - num_results, 0), max_results + end, +} + +local scroll_calculators = { + cycle = function(range_fn) + return function(max_results, num_results, row) + local start, finish = range_fn(max_results, num_results) + + if row >= finish then + return start + elseif row < start then + return finish - 1 end + + return row end + end, + + limit = function(range_fn) + return function(max_results, num_results, row) + local start, finish = range_fn(max_results, num_results) + + if row >= finish then + return finish - 1 + elseif row < start then + return start + end + + return row + end + end, +} + +scroller.create = function(scroll_strategy, sorting_strategy) + local range_fn = range_calculators[sorting_strategy] + if not range_fn then + error(debug.traceback("Unknown sorting strategy: " .. sorting_strategy)) end -end -scroller.create = function(strategy, sorting_strategy) - local calc_count = calc_count_fn(sorting_strategy) + local scroll_fn = scroll_calculators[scroll_strategy] + if not scroll_fn then + error(debug.traceback("Unknown scroll strategy: " .. (scroll_strategy or ''))) + end - if strategy == 'cycle' then - return function(max_results, num_results, row) - local count, b = calc_count(max_results, num_results, row) - if b then return count end + local calculator = scroll_fn(range_fn) + return function(max_results, num_results, row) + local result = calculator(max_results, num_results, row) - if row >= count then - return 0 - elseif row < 0 then - return count - 1 - end - - return row + if result < 0 then + error(string.format( + "Must never return a negative row: { result = %s, args = { %s %s %s } }", + result, max_results, num_results, row + )) end - elseif strategy == 'limit' or strategy == nil then - return function(max_results, num_results, row) - local count = calc_count(max_results, num_results) - if row >= count then - return count - 1 - elseif row < 0 then - return 0 - end - - return row + if result >= max_results then + error(string.format( + "Must never exceed max results: { result = %s, args = { %s %s %s } }", + result, max_results, num_results, row + )) end - else - error("Unsupported strategy: " .. strategy) + + return result end end diff --git a/lua/tests/automated/entry_manager_spec.lua b/lua/tests/automated/entry_manager_spec.lua new file mode 100644 index 0000000..cc59ebd --- /dev/null +++ b/lua/tests/automated/entry_manager_spec.lua @@ -0,0 +1,142 @@ +local EntryManager = require('telescope.entry_manager') + +local eq = assert.are.same + +describe('process_result', function() + it('works with one entry', function() + local manager = EntryManager:new(5, nil) + + manager:add_entry(nil, 1, "hello") + + eq(1, manager:get_score(1)) + end) + + it('works with two entries', function() + local manager = EntryManager:new(5, nil) + + manager:add_entry(nil, 1, "hello") + manager:add_entry(nil, 2, "later") + + eq(2, manager.linked_states.size) + + eq("hello", manager:get_entry(1)) + eq("later", manager:get_entry(2)) + end) + + it('calls functions when inserting', function() + local called_count = 0 + local manager = EntryManager:new(5, function() called_count = called_count + 1 end) + + assert(called_count == 0) + manager:add_entry(nil, 1, "hello") + assert(called_count == 1) + end) + + it('calls functions when inserting twice', function() + local called_count = 0 + local manager = EntryManager:new(5, function() called_count = called_count + 1 end) + + assert(called_count == 0) + manager:add_entry(nil, 1, "hello") + manager:add_entry(nil, 2, "world") + assert(called_count == 2) + end) + + it('correctly sorts lower scores', function() + local called_count = 0 + local manager = EntryManager:new(5, function() called_count = called_count + 1 end) + manager:add_entry(nil, 5, "worse result") + manager:add_entry(nil, 2, "better result") + + eq("better result", manager:get_entry(1)) + eq("worse result", manager:get_entry(2)) + + eq(2, called_count) + end) + + it('respects max results', function() + local called_count = 0 + local manager = EntryManager:new(1, function() called_count = called_count + 1 end) + manager:add_entry(nil, 2, "better result") + manager:add_entry(nil, 5, "worse result") + + eq("better result", manager:get_entry(1)) + eq(1, called_count) + end) + + it('should allow simple entries', function() + local manager = EntryManager:new(5) + + local counts_executed = 0 + manager:add_entry(nil, 1, setmetatable({}, { + __index = function(t, k) + local val = nil + if k == "ordinal" then + counts_executed = counts_executed + 1 + + -- This could be expensive, only call later + val = "wow" + end + + rawset(t, k, val) + return val + end, + })) + + eq("wow", manager:get_ordinal(1)) + eq("wow", manager:get_ordinal(1)) + eq("wow", manager:get_ordinal(1)) + + eq(1, counts_executed) + end) + + it('should not loop a bunch', function() + local info = {} + local manager = EntryManager:new(5, nil, info) + manager:add_entry(nil, 4, "better result") + manager:add_entry(nil, 3, "better result") + manager:add_entry(nil, 2, "better result") + + -- Loops once to find 3 < 4 + -- Loops again to find 2 < 3 + eq(2, info.looped) + end) + + it('should not loop a bunch, part 2', function() + local info = {} + local manager = EntryManager:new(5, nil, info) + manager:add_entry(nil, 4, "better result") + manager:add_entry(nil, 2, "better result") + manager:add_entry(nil, 3, "better result") + + -- Loops again to find 2 < 4 + -- Loops once to find 3 > 2 + -- but less than 4 + eq(3, info.looped) + end) + + it('should update worst score in all append case', function() + local manager = EntryManager:new(2, nil) + manager:add_entry(nil, 2, "result 2") + manager:add_entry(nil, 3, "result 3") + manager:add_entry(nil, 4, "result 4") + + eq(3, manager.worst_acceptable_score) + end) + + it('should update worst score in all prepend case', function() + local called_count = 0 + local manager = EntryManager:new(2, function() called_count = called_count + 1 end) + manager:add_entry(nil, 5, "worse result") + manager:add_entry(nil, 4, "less worse result") + manager:add_entry(nil, 2, "better result") + + -- Once for insert 5 + -- Once for prepend 4 + -- Once for prepend 2 + eq(3, called_count) + + eq("better result", manager:get_entry(1)) + eq(4, manager.worst_acceptable_score) + end) +end) diff --git a/lua/tests/automated/linked_list_spec.lua b/lua/tests/automated/linked_list_spec.lua new file mode 100644 index 0000000..49c035a --- /dev/null +++ b/lua/tests/automated/linked_list_spec.lua @@ -0,0 +1,133 @@ +local LinkedList = require('telescope.algos.linked_list') + +describe('LinkedList', function() + it('can create a list', function() + local l = LinkedList:new() + + assert.are.same(0, l.size) + end) + + it('can add a single entry to the list', function() + local l = LinkedList:new() + l:append('hello') + + assert.are.same(1, l.size) + end) + + it('can iterate over one item', function() + local l = LinkedList:new() + l:append('hello') + + for val in l:iter() do + assert.are.same('hello', val) + end + end) + + it('iterates in order', function() + local l = LinkedList:new() + l:append('hello') + l:append('world') + + local x = {} + for val in l:iter() do + table.insert(x, val) + end + + assert.are.same({'hello', 'world'}, x) + end) + + it('iterates in order, for prepend', function() + local l = LinkedList:new() + l:prepend('world') + l:prepend('hello') + + local x = {} + for val in l:iter() do + table.insert(x, val) + end + + assert.are.same({'hello', 'world'}, x) + end) + + it('iterates in order, for combo', function() + local l = LinkedList:new() + l:prepend('world') + l:prepend('hello') + l:append('last') + l:prepend('first') + + local x = {} + for val in l:iter() do + table.insert(x, val) + end + + assert.are.same({'first', 'hello', 'world', 'last'}, x) + assert.are.same(#x, l.size) + end) + + it('has ipairs', function() + local l = LinkedList:new() + l:prepend('world') + l:prepend('hello') + l:append('last') + l:prepend('first') + + local x = {} + for v in l:iter() do + table.insert(x, v) + end + assert.are.same({'first', 'hello', 'world', 'last'}, x) + + local expected = {} + for i, v in ipairs(x) do + table.insert(expected, {i, v}) + end + + local actual = {} + for i, v in l:ipairs() do + table.insert(actual, {i, v}) + end + + assert.are.same(expected, actual) + end) + + describe('track_at', function() + it('should update tracked when only appending', function() + local l = LinkedList:new { track_at = 2 } + l:append("first") + l:append("second") + l:append("third") + + assert.are.same("second", l.tracked) + end) + + it('should update tracked when first some prepend and then append', function() + local l = LinkedList:new { track_at = 2 } + l:prepend("first") + l:append("second") + l:append("third") + + assert.are.same("second", l.tracked) + end) + + it('should update when only prepending', function() + local l = LinkedList:new { track_at = 2 } + l:prepend("third") + l:prepend("second") + l:prepend("first") + + assert.are.same("second", l.tracked) + end) + + it('should update when lots of prepend and append', function() + local l = LinkedList:new { track_at = 2 } + l:prepend("third") + l:prepend("second") + l:prepend("first") + l:append("fourth") + l:prepend("zeroth") + + assert.are.same("first", l.tracked) + end) + end) +end) diff --git a/lua/tests/automated/pickers/find_files_spec.lua b/lua/tests/automated/pickers/find_files_spec.lua index c5cf50e..b3af1d6 100644 --- a/lua/tests/automated/pickers/find_files_spec.lua +++ b/lua/tests/automated/pickers/find_files_spec.lua @@ -2,6 +2,10 @@ require('plenary.reload').reload_module('telescope') local tester = require('telescope.pickers._test') +local disp = function(val) + return vim.inspect(val, { newline = " ", indent = "" }) +end + describe('builtin.find_files', function() it('should find the readme', function() tester.run_file('find_files__readme') @@ -11,45 +15,76 @@ describe('builtin.find_files', function() tester.run_file('find_files__with_ctrl_n') end) - it('should not display devicons when disabled', function() - tester.run_string [[ - tester.builtin_picker('find_files', 'README.md', { - post_typed = { - { "> README.md", GetPrompt }, - { "> README.md", GetLastResult }, - }, - post_close = { - { 'README.md', GetFile }, - { 'README.md', GetFile }, - } - }, { - disable_devicons = true, - sorter = require('telescope.sorters').get_fzy_sorter(), - }) - ]] - end) + for _, configuration in ipairs { + { sorting_strategy = 'descending', }, + { sorting_strategy = 'ascending', }, + } do + it('should not display devicons when disabled: ' .. disp(configuration), function() + tester.run_string(string.format([[ + local max_results = 5 - it('use devicons, if it has it when enabled', function() - if not pcall(require, 'nvim-web-devicons') then - return - end + tester.builtin_picker('find_files', 'README.md', { + post_typed = { + { "> README.md", GetPrompt }, + { "> README.md", GetBestResult }, + }, + post_close = { + { 'README.md', GetFile }, + { 'README.md', GetFile }, + } + }, vim.tbl_extend("force", { + disable_devicons = true, + sorter = require('telescope.sorters').get_fzy_sorter(), + results_height = max_results, + layout_strategy = 'center', + }, vim.fn.json_decode([==[%s]==]))) + ]], vim.fn.json_encode(configuration))) + end) - tester.run_string [[ - tester.builtin_picker('find_files', 'README.md', { - post_typed = { - { "> README.md", GetPrompt }, - { ">  README.md", GetLastResult } - }, - post_close = { - { 'README.md', GetFile }, - { 'README.md', GetFile }, - } - }, { - disable_devicons = false, - sorter = require('telescope.sorters').get_fzy_sorter(), - }) - ]] - end) + it('should only save one line for ascending, but many for descending', function() + local expected + if configuration.sorting_strategy == 'descending' then + expected = 5 + else + expected = 1 + end + + tester.run_string(string.format([[ + tester.builtin_picker('find_files', 'README.md', { + post_typed = { + { %s, function() return #GetResults() end }, + }, + }, vim.tbl_extend("force", { + disable_devicons = true, + sorter = require('telescope.sorters').get_fzy_sorter(), + results_height = 5, + layout_strategy = 'center', + }, vim.fn.json_decode([==[%s]==]))) + ]], expected, vim.fn.json_encode(configuration))) + end) + + it('use devicons, if it has it when enabled', function() + if not pcall(require, 'nvim-web-devicons') then + return + end + + tester.run_string(string.format([[ + tester.builtin_picker('find_files', 'README.md', { + post_typed = { + { "> README.md", GetPrompt }, + { ">  README.md", GetBestResult } + }, + post_close = { + { 'README.md', GetFile }, + { 'README.md', GetFile }, + } + }, vim.tbl_extend("force", { + disable_devicons = false, + sorter = require('telescope.sorters').get_fzy_sorter(), + }, vim.fn.json_decode([==[%s]==]))) + ]], vim.fn.json_encode(configuration))) + end) + end it('should find the readme, using lowercase', function() tester.run_string [[ diff --git a/lua/tests/automated/pickers/scrolling_spec.lua b/lua/tests/automated/pickers/scrolling_spec.lua new file mode 100644 index 0000000..ab82378 --- /dev/null +++ b/lua/tests/automated/pickers/scrolling_spec.lua @@ -0,0 +1,9 @@ +require('plenary.reload').reload_module('telescope') + +local tester = require('telescope.pickers._test') + +describe('scrolling strategies', function() + it('should handle cycling for full list', function() + tester.run_file [[find_files__scrolling_descending_cycle]] + end) +end) diff --git a/lua/tests/automated/scroller_spec.lua b/lua/tests/automated/scroller_spec.lua index f47edda..dcc55a0 100644 --- a/lua/tests/automated/scroller_spec.lua +++ b/lua/tests/automated/scroller_spec.lua @@ -99,7 +99,16 @@ describe('scroller', function() it('should stay at current results when current results is less than max_results', function() local current = 5 - eq(current - 1, limit_scroller(max_results, current, 4)) + eq(max_results - current, limit_scroller(max_results, current, 4)) + end) + end) + + describe('https://github.com/nvim-telescope/telescope.nvim/pull/293#issuecomment-751463224', function() + it('should handle having many more results than necessary', function() + local scroller = p_scroller.create('cycle', 'descending') + + -- 23 112 23 + eq(0, scroller(23, 112, 23)) end) end) end) diff --git a/lua/tests/automated/telescope_spec.lua b/lua/tests/automated/telescope_spec.lua index bd9203c..3e9f0cb 100644 --- a/lua/tests/automated/telescope_spec.lua +++ b/lua/tests/automated/telescope_spec.lua @@ -4,8 +4,6 @@ local log = require('telescope.log') log.level = 'info' -- log.use_console = false -local EntryManager = require('telescope.entry_manager') - --[[ lua RELOAD('plenary'); require("plenary.test_harness"):test_directory("busted", "./tests/automated") --]] @@ -16,103 +14,6 @@ describe('Picker', function() assert(true) end) end) - - describe('process_result', function() - it('works with one entry', function() - local manager = EntryManager:new(5, nil) - - manager:add_entry(nil, 1, "hello") - - assert.are.same(1, manager:get_score(1)) - end) - - it('works with two entries', function() - local manager = EntryManager:new(5, nil) - - manager:add_entry(nil, 1, "hello") - manager:add_entry(nil, 2, "later") - - assert.are.same("hello", manager:get_entry(1)) - assert.are.same("later", manager:get_entry(2)) - end) - - it('calls functions when inserting', function() - local called_count = 0 - local manager = EntryManager:new(5, function() called_count = called_count + 1 end) - - assert(called_count == 0) - manager:add_entry(nil, 1, "hello") - assert(called_count == 1) - end) - - it('calls functions when inserting twice', function() - local called_count = 0 - local manager = EntryManager:new(5, function() called_count = called_count + 1 end) - - assert(called_count == 0) - manager:add_entry(nil, 1, "hello") - manager:add_entry(nil, 2, "world") - assert(called_count == 2) - end) - - it('correctly sorts lower scores', function() - local called_count = 0 - local manager = EntryManager:new(5, function() called_count = called_count + 1 end) - manager:add_entry(nil, 5, "worse result") - manager:add_entry(nil, 2, "better result") - - assert.are.same("better result", manager:get_entry(1)) - assert.are.same("worse result", manager:get_entry(2)) - - -- once to insert "worse" - -- once to insert "better" - -- and then to move "worse" - assert.are.same(3, called_count) - end) - - it('respects max results', function() - local called_count = 0 - local manager = EntryManager:new(1, function() called_count = called_count + 1 end) - manager:add_entry(nil, 2, "better result") - manager:add_entry(nil, 5, "worse result") - - assert.are.same("better result", manager:get_entry(1)) - assert.are.same(1, called_count) - end) - - -- TODO: We should decide if we want to add this or not. - -- it('should handle no scores', function() - -- local manager = EntryManager:new(5, nil) - - -- manager:add_entry(nil, - -- end) - - it('should allow simple entries', function() - local manager = EntryManager:new(5) - - local counts_executed = 0 - manager:add_entry(nil, 1, setmetatable({}, { - __index = function(t, k) - local val = nil - if k == "ordinal" then - counts_executed = counts_executed + 1 - - -- This could be expensive, only call later - val = "wow" - end - - rawset(t, k, val) - return val - end, - })) - - assert.are.same("wow", manager:get_ordinal(1)) - assert.are.same("wow", manager:get_ordinal(1)) - assert.are.same("wow", manager:get_ordinal(1)) - - assert.are.same(1, counts_executed) - end) - end) end) describe('Sorters', function() diff --git a/lua/tests/pickers/find_files__readme.lua b/lua/tests/pickers/find_files__readme.lua index 620c3be..534ee3e 100644 --- a/lua/tests/pickers/find_files__readme.lua +++ b/lua/tests/pickers/find_files__readme.lua @@ -1,7 +1,8 @@ local tester = require('telescope.pickers._test') +local helper = require('telescope.pickers._test_helpers') tester.builtin_picker('find_files', 'README.md', { post_close = { - {'README.md', function() return vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ":.") end }, + {'README.md', helper.get_file }, } }) diff --git a/lua/tests/pickers/find_files__scrolling_descending_cycle.lua b/lua/tests/pickers/find_files__scrolling_descending_cycle.lua new file mode 100644 index 0000000..7740003 --- /dev/null +++ b/lua/tests/pickers/find_files__scrolling_descending_cycle.lua @@ -0,0 +1,14 @@ +require('plenary.reload').reload_module('plenary') +require('plenary.reload').reload_module('telescope') + +local tester = require('telescope.pickers._test') +local helper = require('telescope.pickers._test_helpers') + +tester.builtin_picker('find_files', 'telescope', { + post_close = { + tester.not_ { 'plugin/telescope.vim', helper.get_file }, + }, +}, { + sorting_strategy = "descending", + scroll_strategy = "cycle", +}) diff --git a/lua/tests/pickers/find_files__with_ctrl_n.lua b/lua/tests/pickers/find_files__with_ctrl_n.lua index 78a31fd..5d93f60 100644 --- a/lua/tests/pickers/find_files__with_ctrl_n.lua +++ b/lua/tests/pickers/find_files__with_ctrl_n.lua @@ -3,7 +3,7 @@ local helper = require('telescope.pickers._test_helpers') tester.builtin_picker('find_files', 'fixtures/file', { post_close = { - { 'lua/tests/fixtures/file_2.txt', helper.get_file } + { 'lua/tests/fixtures/file_abc.txt', helper.get_selection_value }, }, }) diff --git a/scratch/nvim_ffi.lua b/scratch/nvim_ffi.lua index 3a48ada..6fc01c1 100644 --- a/scratch/nvim_ffi.lua +++ b/scratch/nvim_ffi.lua @@ -2,8 +2,8 @@ local ffi = require("ffi") -- ffi.load("/home/tj/build/neovim/build/include/eval/funcs.h.generated.h") ffi.cdef [[ -typedef unsigned char char_u; -char_u *shorten_dir(char_u *str); + typedef unsigned char char_u; + char_u *shorten_dir(char_u *str); ]] local text = "scratch/file.lua"