feat(fps): Add refresh style display for telescope

This commit is contained in:
TJ DeVries
2021-11-22 12:18:28 -05:00
parent e6b69b1488
commit 26b357789b
33 changed files with 1550 additions and 1597 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -155,35 +155,19 @@ end
actions.select_all = function(prompt_bufnr)
local current_picker = action_state.get_current_picker(prompt_bufnr)
action_utils.map_entries(prompt_bufnr, function(entry, _, row)
if not current_picker._multi:is_selected(entry) then
current_picker._multi:add(entry)
if current_picker:can_select_row(row) then
local caret = current_picker:update_prefix(entry, row)
if current_picker._selection_entry == entry and current_picker._selection_row == row then
current_picker.highlighter:hi_selection(row, caret:match "(.*%S)")
end
current_picker.highlighter:hi_multiselect(row, current_picker._multi:is_selected(entry))
end
end
current_picker._multi:add(entry)
end)
current_picker:get_status_updater(current_picker.prompt_win, current_picker.prompt_bufnr)()
current_picker:_redraw(true)
end
--- Drop all entries from the current multi selection.
---@param prompt_bufnr number: The prompt bufnr
actions.drop_all = function(prompt_bufnr)
local current_picker = action_state.get_current_picker(prompt_bufnr)
action_utils.map_entries(prompt_bufnr, function(entry, _, row)
action_utils.map_entries(prompt_bufnr, function(entry)
current_picker._multi:drop(entry)
if current_picker:can_select_row(row) then
local caret = current_picker:update_prefix(entry, row)
if current_picker._selection_entry == entry and current_picker._selection_row == row then
current_picker.highlighter:hi_selection(row, caret:match "(.*%S)")
end
current_picker.highlighter:hi_multiselect(row, current_picker._multi:is_selected(entry))
end
end)
current_picker:get_status_updater(current_picker.prompt_win, current_picker.prompt_bufnr)()
current_picker:_redraw(true)
end
--- Toggle multi selection for all entries.
@@ -191,17 +175,10 @@ end
---@param prompt_bufnr number: The prompt bufnr
actions.toggle_all = function(prompt_bufnr)
local current_picker = action_state.get_current_picker(prompt_bufnr)
action_utils.map_entries(prompt_bufnr, function(entry, _, row)
action_utils.map_entries(prompt_bufnr, function(entry)
current_picker._multi:toggle(entry)
if current_picker:can_select_row(row) then
local caret = current_picker:update_prefix(entry, row)
if current_picker._selection_entry == entry and current_picker._selection_row == row then
current_picker.highlighter:hi_selection(row, caret:match "(.*%S)")
end
current_picker.highlighter:hi_multiselect(row, current_picker._multi:is_selected(entry))
end
end)
current_picker:get_status_updater(current_picker.prompt_win, current_picker.prompt_bufnr)()
current_picker:_redraw(true)
end
--- Scroll the preview window up

View File

@@ -193,23 +193,16 @@ action_set.scroll_previewer = function(prompt_bufnr, direction)
end
--- Scrolls the results up or down.
--- Defaults to a half page scroll, but can be overridden using the `scroll_speed`
--- option in `layout_config`. See |telescope.layout| for more details.
--- Moves `count` movements.
---@param prompt_bufnr number: The prompt bufnr
---@param direction number: The direction of the scrolling
-- Valid directions include: "1", "-1"
action_set.scroll_results = function(prompt_bufnr, direction)
local status = state.get_status(prompt_bufnr)
local default_speed = vim.api.nvim_win_get_height(status.results_win) / 2
local speed = status.picker.layout_config.scroll_speed or default_speed
local count = vim.v.count
count = count == 0 and 1 or count
count = a.nvim_get_mode().mode == "n" and count or 1
local input = direction > 0 and [[]] or [[]]
vim.api.nvim_win_call(status.results_win, function()
vim.cmd([[normal! ]] .. math.floor(speed) .. input)
end)
action_set.shift_selection(prompt_bufnr, math.floor(speed) * direction)
action_state.get_current_picker(prompt_bufnr):shift_offset(count * direction)
end
-- ==================================================

View File

@@ -1,28 +1,15 @@
local log = require "telescope.log"
local LinkedList = require "telescope.algos.linked_list"
local EntryManager = {}
EntryManager.__index = EntryManager
function EntryManager:new(max_results, set_entry, info)
log.trace "Creating entry_manager..."
info = info or {}
info.looped = 0
info.inserted = 0
info.find_loop = 0
-- state contains list of
-- { entry, score }
-- Stored directly in a table, accessed as [1], [2]
set_entry = set_entry or function() end
function EntryManager:new(num_sorted)
num_sorted = num_sorted or 500
return setmetatable({
linked_states = LinkedList:new { track_at = max_results },
info = info,
max_results = max_results,
set_entry = set_entry,
dirty = true,
linked_states = LinkedList:new { track_at = num_sorted },
num_sorted = num_sorted,
worst_acceptable_score = math.huge,
}, self)
end
@@ -31,12 +18,12 @@ function EntryManager:num_results()
return self.linked_states.size
end
function EntryManager:get_container(index)
function EntryManager:get_container(offset, index)
local count = 0
for val in self.linked_states:iter() do
count = count + 1
if count == index then
if count == offset + index then
return val
end
end
@@ -44,33 +31,28 @@ function EntryManager:get_container(index)
return {}
end
function EntryManager:get_entry(index)
return self:get_container(index)[1]
function EntryManager:get_entry(offset, index)
return self:get_container(offset, index)[1]
end
function EntryManager:get_score(index)
return self:get_container(index)[2]
function EntryManager:get_score(offset, index)
return self:get_container(offset, index)[2]
end
function EntryManager:get_ordinal(index)
return self:get_entry(index).ordinal
function EntryManager:get_ordinal(offset, index)
return self:get_entry(offset, 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
@@ -84,14 +66,12 @@ 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
@@ -99,22 +79,15 @@ 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, prompt)
score = score or 0
local max_res = self.max_results
local num_sorted = self.num_sorted
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.
@@ -123,16 +96,15 @@ function EntryManager:add_entry(picker, score, entry, prompt)
return self.linked_states:append(new_container)
end
self.dirty = true
-- 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
return self:_insert_container_before(picker, index, node, new_container)
end
@@ -142,13 +114,12 @@ function EntryManager:add_entry(picker, score, entry, prompt)
end
-- Don't add results that are too bad.
if index >= max_res then
info.maxed = info.maxed + 1
if index >= num_sorted then
return self:_append_container(picker, new_container, false)
end
end
if self.linked_states.size >= max_res then
if self.linked_states.size >= num_sorted then
self.worst_acceptable_score = math.min(self.worst_acceptable_score, score)
end
@@ -165,4 +136,27 @@ function EntryManager:iter()
end
end
function EntryManager:window(start, finish)
local results = {}
local idx = 0
for val in self.linked_states:iter() do
idx = idx + 1
if val == nil then
return
end
if idx >= start then
table.insert(results, val[1])
end
if idx >= finish then
break
end
end
return results
end
return EntryManager

View File

@@ -63,6 +63,8 @@ mappings.default_mappings = config.values.default_mappings
["<C-u>"] = actions.preview_scrolling_up,
["<C-d>"] = actions.preview_scrolling_down,
["<C-e>"] = actions.results_scrolling_down,
["<C-y>"] = actions.results_scrolling_up,
["<PageUp>"] = actions.results_scrolling_up,
["<PageDown>"] = actions.results_scrolling_down,

File diff suppressed because it is too large Load Diff

View File

@@ -1,244 +0,0 @@
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
local nvim_feed = function(text, feed_opts)
feed_opts = feed_opts or "m"
vim.api.nvim_feedkeys(text, feed_opts, true)
end
local writer = function(val)
if type(val) == "table" then
val = vim.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 = "<a valid key>", actual = k }
end_test_cases()
end
tester.picker_feed = function(input, test_cases)
input = replace_terms(input)
return coroutine.wrap(function()
for i = 1, #input do
local char = input:sub(i, i)
nvim_feed(char, "")
-- TODO: I'm not 100% sure this is a hack or not...
-- it's possible these characters could still have an on_complete... but i'm not sure.
if string.match(char, "%g") then
coroutine.yield()
end
if tester.debug then
vim.wait(200)
end
end
vim.wait(10)
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
nvim_feed(replace_terms "<CR>", "")
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(end_test_cases, 20)
end, 40)
coroutine.yield()
end)
end
local _VALID_KEYS = {
post_typed = true,
post_close = true,
}
tester.builtin_picker = function(builtin_key, input, test_cases, opts)
opts = opts or {}
tester.debug = opts.debug or false
for k, _ in pairs(test_cases) do
if not _VALID_KEYS[k] then
return invalid_test_case(k)
end
end
opts.on_complete = {
tester.picker_feed(input, test_cases),
}
builtin[builtin_key](opts)
end
local get_results_from_file = function(file)
local j = Job:new {
command = "nvim",
args = {
"--noplugin",
"-u",
"scripts/minimal_init.vim",
"-c",
string.format([[lua require("telescope.pickers._test")._execute("%s")]], file),
},
}
j:sync(10000)
local results = j:stderr_result()
local result_table = {}
for _, v in ipairs(results) do
table.insert(result_table, vim.json.decode(v))
end
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
local assertion = asserters[v._type or "default"]
assertion(v.expected, v.actual, string.format("Test Case: %s // %s", v.location, v.case))
end
end
tester.run_string = function(contents)
local tempname = vim.fn.tempname()
contents = [[
local tester = require('telescope.pickers._test')
local helper = require('telescope.pickers._test_helpers')
helper.make_globals()
]] .. contents
vim.fn.writefile(vim.split(contents, "\n"), tempname)
local result_table = get_results_from_file(tempname)
vim.fn.delete(tempname)
check_results(result_table)
end
tester.run_file = function(filename)
local file = "./lua/tests/pickers/" .. filename .. ".lua"
if not Path:new(file):exists() then
assert.are.same("<An existing file>", file)
end
local result_table = get_results_from_file(file)
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

View File

@@ -2,8 +2,11 @@ local a = vim.api
local log = require "telescope.log"
local conf = require("telescope.config").values
local strdisplaywidth = require("plenary.strings").strdisplaywidth
local highlights = {}
local ns_telescope_matching = a.nvim_create_namespace "telescope_matching"
local ns_telescope_selection = a.nvim_create_namespace "telescope_selection"
local ns_telescope_multiselection = a.nvim_create_namespace "telescope_multiselection"
local ns_telescope_entry = a.nvim_create_namespace "telescope_entry"
@@ -14,10 +17,43 @@ Highlighter.__index = Highlighter
function Highlighter:new(picker)
return setmetatable({
picker = picker,
offset = picker._prefix_width,
}, self)
end
function Highlighter:hi_display(row, prefix, display_highlights)
local DISPLAY_HIGHLIGHTS_PRIORITY = 110
local SORTER_HIGHLIGHTS_PRIORITY = 120
local SELECTION_HIGHLIGHTS_PRIORITY = 130
function Highlighter:highlight(row, opts)
assert(row, "Must pass a row")
local picker = self.picker
local entry = opts.entry or picker:_get_entry_from_row(row)
local prompt = opts.prompt or picker:_get_prompt()
local is_selected = opts.is_selected or (picker._selection_row == row)
local is_multi_selected = opts.is_multi_selected or picker:is_multi_selected(entry)
if is_selected then
self:hi_selection(row)
end
if not opts.skip_display then
local display = opts.display
local display_highlights = opts.display_highlights
if not display then
display, display_highlights = picker:_resolve_entry_display(entry)
end
self:hi_display(row, display_highlights)
self:hi_sorter(row, prompt, display)
end
self:hi_multiselect(row, is_multi_selected)
end
function Highlighter:hi_display(row, display_highlights)
-- This is the bug that made my highlight fixes not work.
-- We will leave the solution commented, so the test fails.
if not display_highlights or vim.tbl_isempty(display_highlights) then
@@ -25,95 +61,143 @@ function Highlighter:hi_display(row, prefix, display_highlights)
end
local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr")
if not a.nvim_buf_is_valid(results_bufnr) then
return
end
a.nvim_buf_clear_namespace(results_bufnr, ns_telescope_entry, row, row + 1)
local len_prefix = #prefix
for _, hl_block in ipairs(display_highlights) do
a.nvim_buf_add_highlight(
results_bufnr,
ns_telescope_entry,
hl_block[2],
row,
len_prefix + hl_block[1][1],
len_prefix + hl_block[1][2]
)
a.nvim_buf_set_extmark(results_bufnr, ns_telescope_entry, row, self.offset + hl_block[1][1], {
end_col = self.offset + hl_block[1][2],
hl_group = hl_block[2],
priority = DISPLAY_HIGHLIGHTS_PRIORITY,
strict = false,
})
end
end
function Highlighter:clear_display()
function Highlighter:clear()
if
not self
or not self.picker
or not self.picker.results_bufnr
or not vim.api.nvim_buf_is_valid(self.picker.results_bufnr)
or not a.nvim_buf_is_valid(self.picker.results_bufnr)
then
return
end
a.nvim_buf_clear_namespace(self.picker.results_bufnr, ns_telescope_entry, 0, -1)
a.nvim_buf_clear_namespace(self.picker.results_bufnr, ns_telescope_matching, 0, -1)
end
function Highlighter:hi_sorter(row, prompt, display)
local picker = self.picker
local sorter = picker.sorter
if not picker.sorter or not picker.sorter.highlighter then
return
end
local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr")
picker:highlight_one_row(results_bufnr, prompt, display, row)
if not a.nvim_buf_is_valid(results_bufnr) then
return
end
local sorter_highlights = sorter:highlighter(prompt, display)
if sorter_highlights then
for _, hl in ipairs(sorter_highlights) do
local highlight, start, finish
if type(hl) == "table" then
highlight = hl.highlight or "TelescopeMatching"
start = hl.start
finish = hl.finish or hl.start
elseif type(hl) == "number" then
highlight = "TelescopeMatching"
start = hl
finish = hl
else
error "Invalid higlighter fn"
end
a.nvim_buf_set_extmark(results_bufnr, ns_telescope_matching, row, start + self.offset - 1, {
end_col = self.offset + finish,
hl_group = highlight,
priority = SORTER_HIGHLIGHTS_PRIORITY,
strict = false,
})
end
end
end
function Highlighter:hi_selection(row, caret)
caret = vim.F.if_nil(caret, "")
function Highlighter:hi_selection(row)
local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr")
if not a.nvim_buf_is_valid(results_bufnr) then
return
end
a.nvim_buf_clear_namespace(results_bufnr, ns_telescope_selection, 0, -1)
a.nvim_buf_add_highlight(results_bufnr, ns_telescope_selection, "TelescopeSelectionCaret", row, 0, #caret)
a.nvim_buf_set_extmark(
results_bufnr,
ns_telescope_selection,
row,
#caret,
{ end_line = row + 1, hl_eol = conf.hl_result_eol, hl_group = "TelescopeSelection" }
)
-- If there isn't anything _on_ the line, then it's some edge case with
-- loading the buffer or something like that.
--
-- We can just skip and we'll get the updates later.
if a.nvim_buf_get_lines(results_bufnr, row, row + 1, false)[1] == "" then
return
end
-- TODO: Someone will complain about the highlighting here I'm sure.
-- I don't know what to tell them except what are you doing w/ highlighting
local caret = self.picker.selection_caret
local offset = self.offset
-- Highlight the caret
a.nvim_buf_set_extmark(results_bufnr, ns_telescope_selection, row, 0, {
virt_text = { { caret, "TelescopeSelectionCaret" } },
virt_text_pos = "overlay",
end_col = offset,
hl_group = "TelescopeSelectionCaret",
priority = SELECTION_HIGHLIGHTS_PRIORITY,
strict = true,
})
-- Highlight the text after the caret
a.nvim_buf_set_extmark(results_bufnr, ns_telescope_selection, row, offset, {
end_line = row + 1,
hl_eol = conf.hl_result_eol,
hl_group = "TelescopeSelection",
priority = SELECTION_HIGHLIGHTS_PRIORITY,
})
end
function Highlighter:hi_multiselect(row, is_selected)
local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr")
if not a.nvim_buf_is_valid(results_bufnr) then
return
end
a.nvim_buf_clear_namespace(results_bufnr, ns_telescope_multiselection, row, row + 1)
local line = a.nvim_buf_get_lines(results_bufnr, row, row + 1, false)[1]
if not line then
return
end
if is_selected then
vim.api.nvim_buf_add_highlight(results_bufnr, ns_telescope_multiselection, "TelescopeMultiSelection", row, 0, -1)
if self.picker.multi_icon then
local line = vim.api.nvim_buf_get_lines(results_bufnr, row, row + 1, false)[1]
local pos = line:find(self.picker.multi_icon)
if pos and pos <= math.max(#self.picker.selection_caret, #self.picker.entry_prefix) then
vim.api.nvim_buf_add_highlight(
results_bufnr,
ns_telescope_multiselection,
"TelescopeMultiIcon",
row,
pos - 1,
pos - 1 + #self.picker.multi_icon
)
end
end
else
local existing_marks = vim.api.nvim_buf_get_extmarks(
results_bufnr,
ns_telescope_multiselection,
{ row, 0 },
{ row, -1 },
{}
)
a.nvim_buf_set_extmark(results_bufnr, ns_telescope_multiselection, row, self.offset, {
end_col = #line,
hl_group = "TelescopeMultiSelection",
})
-- This is still kind of weird to me, since it seems like I'm erasing stuff
-- when I shouldn't... Perhaps it's about the gravity of the extmark?
if #existing_marks > 0 then
log.trace("Clearing highlight multi select row: ", row)
vim.api.nvim_buf_clear_namespace(results_bufnr, ns_telescope_multiselection, row, row + 1)
-- TEST WITH MULTI-BYTE CHARS
if self.picker.multi_icon and self.offset > 0 then
local icon = self.picker.multi_icon
local cols = strdisplaywidth(icon)
a.nvim_buf_set_extmark(results_bufnr, ns_telescope_multiselection, row, self.offset - cols, {
end_col = self.offset,
virt_text = { { self.picker.multi_icon, "TelescopeMultiIcon" } },
virt_text_pos = "overlay",
})
end
end
end

View File

@@ -0,0 +1,18 @@
local M = {}
M.set_prompt = function(picker)
self._current_prefix_hl_group = hl_group or nil
if self.prompt_prefix ~= "" then
vim.api.nvim_buf_add_highlight(
self.prompt_bufnr,
ns_telescope_prompt_prefix,
self._current_prefix_hl_group or "TelescopePromptPrefix",
0,
0,
strdisplaywidth(self.prompt_prefix)
)
end
end
return M

View File

@@ -40,7 +40,7 @@ local scroll_calculators = {
end,
}
scroller.create = function(scroll_strategy, sorting_strategy)
scroller.new = 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))

View File

@@ -0,0 +1,116 @@
local assert = require "luassert"
local Path = require "plenary.path"
local tester = {}
tester.debug = false
local get_results_from_contents = function(content)
local nvim = vim.fn.jobstart(
{ "nvim", "--noplugin", "-u", "scripts/minimal_init.vim", "--headless", "--embed" },
{ rpc = true }
)
local result = vim.fn.rpcrequest(nvim, "nvim_exec_lua", content, {})
assert.are.same(true, result[1], vim.inspect(result))
local count = 0
while
vim.fn.rpcrequest(nvim, "nvim_exec_lua", "return require('telescope.testharness.runner').state.done", {}) ~= true
do
count = count + 1
vim.wait(100)
-- TODO: Could maybe wait longer, but it's annoying to wait if the test is going to timeout.
if count > 100 then
break
end
end
local state = vim.fn.rpcrequest(nvim, "nvim_exec_lua", "return require('telescope.testharness.runner').state", {})
vim.fn.jobstop(nvim)
assert.are.same(true, state.done, vim.inspect(state))
local result_table = {}
for _, v in ipairs(state.results) do
table.insert(result_table, v)
end
return result_table, state
end
local check_results = function(results, state)
assert(state, "Must pass state")
for _, v in ipairs(results) do
local assertion
if not v._type or v._type == "are" or v._type == "_default" then
assertion = assert.are.same
else
assertion = assert.are_not.same
end
-- TODO: I think it would be nice to be able to see the state,
-- but it clutters up the test output so much here.
--
-- So we would have to consider how to do that I think.
assertion(v.expected, v.actual, string.format("Test Case: %s // %s", v.location, v.case))
end
end
tester.run_string = function(contents)
contents = [[
return (function()
local runner = require('telescope.testharness.runner')
local helper = require('telescope.testharness.helpers')
helper.make_globals()
local ok, msg = pcall(function()
runner.log("Loading Test")
]] .. contents .. [[
end)
return {ok, msg or runner.state}
end)()
]]
check_results(get_results_from_contents(contents))
end
tester.run_file = function(filename)
local file = "./lua/tests/pickers/" .. filename .. ".lua"
local path = Path:new(file)
if not path:exists() then
assert.are.same("<An existing file>", file)
end
local contents = string.format(
[[
return (function()
local runner = require('telescope.testharness.runner')
local helper = require('telescope.testharness.helpers')
helper.make_globals()
local ok, msg = pcall(function()
runner.log("Loading Test")
return loadfile("%s")()
end)
return {ok, msg or runner.state}
end)()
]],
path:absolute()
)
check_results(get_results_from_contents(contents))
end
tester.not_ = function(val)
val._type = "are_not"
return val
end
return tester

View File

@@ -0,0 +1,152 @@
local builtin = require "telescope.builtin"
local runner = {}
-- State is test variable
runner.state = {
done = false,
results = {},
msgs = {},
}
local writer = function(val)
table.insert(runner.state.results, val)
end
local invalid_test_case = function(k)
error { case = k, expected = "<a valid key>", actual = k }
end
local _VALID_KEYS = {
post_typed = true,
post_close = true,
}
local replace_terms = function(input)
return vim.api.nvim_replace_termcodes(input, true, false, true)
end
runner.nvim_feed = function(text, feed_opts)
feed_opts = feed_opts or "m"
vim.api.nvim_feedkeys(text, feed_opts, true)
end
local end_test_cases = function()
runner.state.done = true
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,
}
end_test_cases()
else
writer {
location = location,
case = key,
expected = spec[1],
actual = actual,
_type = spec._type,
}
end
return ok
end
runner.log = function(msg)
table.insert(runner.state.msgs, msg)
end
runner.picker = function(picker_name, input, test_cases, opts)
opts = opts or {}
for k, _ in pairs(test_cases) do
if not _VALID_KEYS[k] then
return invalid_test_case(k)
end
end
opts.on_complete = {
runner.create_on_complete(input, test_cases),
}
opts._on_error = function(self, msg)
runner.state.done = true
writer {
location = "Error while running on complete",
expected = "To Work",
actual = msg,
}
end
runner.log "Starting picker"
builtin[picker_name](opts)
runner.log "Called picker"
end
runner.create_on_complete = function(input, test_cases)
input = replace_terms(input)
local actions = {}
for i = 1, #input do
local char = input:sub(i, i)
table.insert(actions, {
cb = function()
runner.log("Inserting char: " .. char)
runner.nvim_feed(char, "")
end,
char = char,
})
end
return function()
local action = {}
repeat
action = table.remove(actions, 1)
if action then
action.cb()
end
until not action or string.match(action.char, "%g")
if #actions > 0 then
return
end
vim.defer_fn(function()
if test_cases.post_typed then
for k, v in ipairs(test_cases.post_typed) do
if not execute_test_case("post_typed", k, v) then
return
end
end
end
runner.nvim_feed(replace_terms "<CR>", "")
vim.defer_fn(function()
if test_cases.post_close then
for k, v in ipairs(test_cases.post_close) do
if not execute_test_case("post_close", k, v) then
return
end
end
end
vim.defer_fn(end_test_cases, 20)
end, 20)
end, 20)
end
end
return runner

View File

@@ -4,7 +4,7 @@ local eq = assert.are.same
describe("process_result", function()
it("works with one entry", function()
local manager = EntryManager:new(5, nil)
local manager = EntryManager:new(5)
manager:add_entry(nil, 1, "hello", "")
@@ -12,64 +12,32 @@ describe("process_result", function()
end)
it("works with two entries", function()
local manager = EntryManager:new(5, nil)
local manager = EntryManager:new(5)
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)
eq("hello", manager:get_entry(0, 1))
eq("later", manager:get_entry(0, 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", "")
local manager = EntryManager:new(5)
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)
eq("better result", manager:get_entry(0, 1))
eq("worse result", manager:get_entry(0, 2))
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", "")
local manager = EntryManager:new(1)
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)
eq("better result", manager:get_entry(0, 1))
end)
it("should allow simple entries", function()
@@ -103,31 +71,6 @@ describe("process_result", function()
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", "")
@@ -138,20 +81,12 @@ describe("process_result", function()
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", "")
local manager = EntryManager:new(2)
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("better result", manager:get_entry(0, 1))
eq(4, manager.worst_acceptable_score)
end)
@@ -167,8 +102,8 @@ describe("process_result", function()
manager:add_entry(picker, 0.5, "same same", "asdf")
manager:add_entry(picker, 0.5, "same", "asdf")
eq("same", manager:get_entry(1))
eq("same same", manager:get_entry(2))
eq("same", manager:get_entry(0, 1))
eq("same same", manager:get_entry(0, 2))
end)
it("should call tiebreaker if score is the same, keep initial", function()
@@ -183,7 +118,21 @@ describe("process_result", function()
manager:add_entry(picker, 0.5, "same same", "asdf")
manager:add_entry(picker, 0.5, "same", "asdf")
eq("same", manager:get_entry(2))
eq("same same", manager:get_entry(1))
eq("same", manager:get_entry(0, 2))
eq("same same", manager:get_entry(0, 1))
end)
it(":window() should return table of resuls", function()
local manager = EntryManager:new(5, nil)
manager:add_entry(nil, 1, "first")
manager:add_entry(nil, 2, "second")
manager:add_entry(nil, 3, "third")
manager:add_entry(nil, 4, "fourth")
manager:add_entry(nil, 5, "sixth")
eq(5, manager.linked_states.size)
eq({ "second", "third" }, manager:window(2, 3))
end)
end)

View File

@@ -1,6 +1,4 @@
require("plenary.reload").reload_module "telescope"
local tester = require "telescope.pickers._test"
local tester = require "telescope.testharness"
local disp = function(val)
return vim.inspect(val, { newline = " ", indent = "" })
@@ -11,6 +9,7 @@ describe("builtin.find_files", function()
tester.run_file "find_files__readme"
end)
<<<<<<< HEAD
it("should be able to move selections", function()
tester.run_file "find_files__with_ctrl_n"
end)
@@ -19,6 +18,14 @@ describe("builtin.find_files", function()
{ sorting_strategy = "descending" },
{ sorting_strategy = "ascending" },
} do
=======
for _, configuration in
ipairs {
{ sorting_strategy = "descending" },
{ sorting_strategy = "ascending" },
}
do
>>>>>>> 20a1519 (feat(fps): Add refresh style display for telescope)
it("should not display devicons when disabled: " .. disp(configuration), function()
tester.run_string(string.format(
[[

View File

@@ -0,0 +1,109 @@
local tester = require "telescope.testharness"
--[[
Available functions are
- fixtures/file_a.txt
- fixtures/file_abc.txt
--]]
describe("scroll_cycle", function()
it("should be able to cycle selections: cycle", function()
tester.run_string [[
runner.picker("find_files", "fixtures/file<c-p>", {
post_close = {
{ "lua/tests/fixtures/file_abc.txt", helper.get_selection_value },
},
}, {
sorting_strategy = "ascending",
scroll_strategy = "cycle",
})
]]
end)
for _, sorting in ipairs { "ascending", "descending" } do
for _, key in ipairs { "<c-n>", "<c-p>" } do
it(string.format("Cycle: %sx2 %s", key, sorting), function()
tester.run_string(([[
runner.picker("find_files", "fixtures/file%s%s", {
post_typed = {
{ "lua/tests/fixtures/file_a.txt", helper.get_selection_value },
},
}, {
sorting_strategy = "%s",
scroll_strategy = "cycle",
}
) ]]):format(key, key, sorting))
end)
it(string.format("Cycle: %sx3 %s", key, sorting), function()
tester.run_string(([[
runner.picker("find_files", "fixtures/file%s%s%s", {
post_typed = {
{ "lua/tests/fixtures/file_abc.txt", helper.get_selection_value },
},
}, {
sorting_strategy = "%s",
scroll_strategy = "cycle",
}
) ]]):format(key, key, key, sorting))
end)
end
end
it("should be able to cycle selections: limit", function()
tester.run_string [[
runner.picker("find_files", "fixtures/file<c-p>", {
post_close = {
{ "lua/tests/fixtures/file_a.txt", helper.get_selection_value },
},
}, {
sorting_strategy = "ascending",
scroll_strategy = "limit",
})
]]
end)
it("long: cycle to top", function()
tester.run_string [[
runner.picker("find_files", "fixtures/long<c-p>", {
post_close = {
{ "lua/tests/fixtures/long_11111111111.md", helper.get_selection_value },
},
}, {
sorting_strategy = "ascending",
scroll_strategy = "cycle",
height = 10,
})
]]
end)
it("long: cycle to top", function()
tester.run_string [[
runner.picker("find_files", "fixtures/long<c-n>", {
post_close = {
{ "lua/tests/fixtures/long_11111111111.md", helper.get_selection_value },
},
}, {
sorting_strategy = "descending",
scroll_strategy = "cycle",
height = 10,
})
]]
end)
it("long: smash <c-p>", function()
tester.run_string [[
runner.picker("find_files", "fixtures/long<c-p><c-p><c-p><c-p><c-p>", {
post_typed = {
{ "lua/tests/fixtures/long_111111.md", helper.get_selection_value },
},
}, {
sorting_strategy = "descending",
scroll_strategy = "cycle",
layout_config = {
height = 8,
},
})
]]
end)
end)

View File

@@ -1,9 +1,4 @@
require("plenary.reload").reload_module "telescope"
local tester = require "telescope.pickers._test"
local log = require "telescope.log"
log.use_console = false
local tester = require "telescope.testharness"
describe("scrolling strategies", function()
it("should handle cycling for full list", function()

View File

@@ -0,0 +1,39 @@
local testharness = require "telescope.testharness"
describe("testing harness", function()
it("should find the readme, using lowercase", function()
testharness.run_string [[
runner.picker('find_files', 'readme.md', {
post_typed = {
{ "> readme.md", GetPrompt },
{ " README.md", GetBestResult },
},
post_close = {
{ 'README.md', GetFile },
}
}, {
disable_devicons = true,
})
]]
end)
it("should find the readme, using uppercase", function()
testharness.run_string [[
runner.picker('find_files', 'RE', {
post_close = {
{ 'README.md', GetFile },
}
})
]]
end)
it("Should find telescope prompt file", function()
testharness.run_string [[
runner.picker('find_files', 'TelescopePrompt', {
post_close = {
{ 'TelescopePrompt.lua', GetFile },
}
})
]]
end)
end)

0
lua/tests/fixtures/long_1.md vendored Normal file
View File

0
lua/tests/fixtures/long_11.md vendored Normal file
View File

0
lua/tests/fixtures/long_111.md vendored Normal file
View File

0
lua/tests/fixtures/long_1111.md vendored Normal file
View File

0
lua/tests/fixtures/long_11111.md vendored Normal file
View File

0
lua/tests/fixtures/long_111111.md vendored Normal file
View File

0
lua/tests/fixtures/long_1111111.md vendored Normal file
View File

0
lua/tests/fixtures/long_11111111.md vendored Normal file
View File

0
lua/tests/fixtures/long_111111111.md vendored Normal file
View File

0
lua/tests/fixtures/long_1111111111.md vendored Normal file
View File

View File

View File

@@ -1,7 +1,7 @@
local tester = require "telescope.pickers._test"
local helper = require "telescope.pickers._test_helpers"
local helper = require "telescope.testharness.helpers"
local runner = require "telescope.testharness.runner"
tester.builtin_picker("find_files", "README.md", {
runner.picker("find_files", "README.md", {
post_close = {
{ "README.md", helper.get_file },
},

View File

@@ -1,12 +1,11 @@
require("plenary.reload").reload_module "plenary"
require("plenary.reload").reload_module "telescope"
local tester = require "telescope.testharness"
local runner = require "telescope.testharness.runner"
local helper = require "telescope.testharness.helpers"
local tester = require "telescope.pickers._test"
local helper = require "telescope.pickers._test_helpers"
tester.builtin_picker("find_files", "telescope<c-n>", {
runner.picker("find_files", "plugin<c-n>", {
post_close = {
tester.not_ { "plugin/telescope.vim", helper.get_file },
tester.not_ { "telescope.vim", helper.get_file },
{ "TelescopePrompt.lua", helper.get_file },
},
}, {
sorting_strategy = "descending",

View File

@@ -1,8 +0,0 @@
local tester = require "telescope.pickers._test"
local helper = require "telescope.pickers._test_helpers"
tester.builtin_picker("find_files", "fixtures/file<c-p>", {
post_close = {
{ "lua/tests/fixtures/file_abc.txt", helper.get_selection_value },
},
})

89
scratch/files.py Normal file
View File

@@ -0,0 +1,89 @@
from pathlib import Path
import subprocess
something = subprocess.run(["rg", "--files"], capture_output=True)
files = something.stdout.decode('utf-8').split('\n')
files.sort()
user_input = "lua finders/async"
REJECTED = -1.0
GOOD = 2.0
def is_subset(item, prompt) -> float:
prompt_chars = set()
for c in prompt:
prompt_chars.add(c)
item_chars = set()
for c in item:
item_chars.add(c)
return prompt_chars.issubset(item_chars)
def proportion_of_contained_letters(prompt, item) -> float:
prompt_chars = set()
for c in prompt:
prompt_chars.add(c)
item_chars = set()
for c in item:
item_chars.add(c)
contained = 0
for prompt_char in prompt_chars:
if prompt_char in item_chars:
contained += 1
return contained / len(prompt_chars)
def jerry_match(prompt: str, item) -> float:
p = Path(item)
split = prompt.split(" ", maxsplit=2)
language = split[0]
filter = split[1]
if p.suffix != "." + language:
return REJECTED
if filter in item:
return GOOD
proprotion = proportion_of_contained_letters(filter, item)
if proprotion < 0.75:
return REJECTED
return proprotion
def score_results(prompt, files):
results = []
for f in files:
score = jerry_match(prompt, f)
if score == REJECTED:
continue
results.append({'score': score, 'item': f})
results.sort(key=lambda x: x["score"], reverse=True)
return results
while True:
i = input("Filter Phrase > ")
if not i:
break
results = score_results(i, files)
for result in results[:10]:
print(result["item"])
# x = [1, 2, 3, 4, 4, 2, 1, 3]
# print(x)
# print(set(x))
# x = {1, 2, 3}
# y = {1}
#
# print("x.issubset(y)", x.issubset(y))
# print("y.issubset(x)", y.issubset(x))