From b6b9a38847afb5c9fa1a061df12816980b2de01f Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Fri, 2 Feb 2024 22:08:50 +0100 Subject: [PATCH 01/10] fix(marks): fix missing entry-index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes the missing `entry.index` - in the marks-extension for telescope - which caused the index to always show `nil` - or rather `n…` - due to a fixed width of 2, in the dialogue. This commit also added a dynamic width and left-padding for the index-column, so the index is being right-aligned. --- lua/telescope/_extensions/marks.lua | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lua/telescope/_extensions/marks.lua b/lua/telescope/_extensions/marks.lua index 5101ff3e..2c0402b3 100644 --- a/lua/telescope/_extensions/marks.lua +++ b/lua/telescope/_extensions/marks.lua @@ -10,7 +10,15 @@ local function filter_empty_string(list) local next = {} for idx = 1, #list do if list[idx].value ~= "" then - table.insert(next, list[idx]) + local item = list[idx] + table.insert(next, { + index = idx, + value = item.value, + context = { + row = item.context.row, + col = item.context.col, + }, + }) end end @@ -18,9 +26,14 @@ local function filter_empty_string(list) end local generate_new_finder = function() + local results = filter_empty_string(harpoon:list().items) + local results_idx_str_len = string.len(tostring(#results)) return finders.new_table({ - results = filter_empty_string(harpoon:list().items), + results = results, entry_maker = function(entry) + local entry_idx_str = tostring(entry.index) + local entry_idx_str_len = string.len(entry_idx_str) + local entry_idx_lpad = string.rep(" ", results_idx_str_len - entry_idx_str_len) local line = entry.value .. ":" .. entry.context.row @@ -29,14 +42,14 @@ local generate_new_finder = function() local displayer = entry_display.create({ separator = " - ", items = { - { width = 2 }, + { width = results_idx_str_len }, { width = 50 }, { remaining = true }, }, }) local make_display = function() return displayer({ - tostring(entry.index), + entry_idx_lpad .. entry_idx_str, line, }) end From 8a4cd155fa864345238e13a1d431900853c20c2b Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Sun, 4 Feb 2024 00:12:38 +0100 Subject: [PATCH 02/10] feat(picker): behave more like a telescope builtin file-picker These changes allow the telescope harpoon-picker to behave more like a builtin telescope file-picker. Both global and individual telescope- customizations will have an effect. --- lua/telescope/_extensions/marks.lua | 187 ++++++++++++++++------------ 1 file changed, 110 insertions(+), 77 deletions(-) diff --git a/lua/telescope/_extensions/marks.lua b/lua/telescope/_extensions/marks.lua index 2c0402b3..802d8c3e 100644 --- a/lua/telescope/_extensions/marks.lua +++ b/lua/telescope/_extensions/marks.lua @@ -3,6 +3,8 @@ local action_utils = require("telescope.actions.utils") local entry_display = require("telescope.pickers.entry_display") local finders = require("telescope.finders") local pickers = require("telescope.pickers") +local utils = require("telescope.utils") +local make_entry = require("telescope.make_entry") local conf = require("telescope.config").values local harpoon = require("harpoon") @@ -12,7 +14,6 @@ local function filter_empty_string(list) if list[idx].value ~= "" then local item = list[idx] table.insert(next, { - index = idx, value = item.value, context = { row = item.context.row, @@ -25,124 +26,156 @@ local function filter_empty_string(list) return next end -local generate_new_finder = function() +local generate_new_finder = function(opts) local results = filter_empty_string(harpoon:list().items) local results_idx_str_len = string.len(tostring(#results)) + local make_file_entry = make_entry.gen_from_file(opts) return finders.new_table({ results = results, - entry_maker = function(entry) - local entry_idx_str = tostring(entry.index) - local entry_idx_str_len = string.len(entry_idx_str) - local entry_idx_lpad = string.rep(" ", results_idx_str_len - entry_idx_str_len) - local line = entry.value - .. ":" - .. entry.context.row - .. ":" - .. entry.context.col + entry_maker = function(harpoon_item) + local entry = make_file_entry(harpoon_item.value) -- value => path local displayer = entry_display.create({ separator = " - ", items = { { width = results_idx_str_len }, - { width = 50 }, { remaining = true }, }, }) - local make_display = function() + entry.display = function(et) + local et_idx_str = tostring(et.index) + local et_idx_str_len = string.len(et_idx_str) + local et_idx_lpad = string.rep(" ", results_idx_str_len - et_idx_str_len) + local path_to_display = utils.transform_path(opts, et.value) + -- TODO create another column for the row and col, with a dynamic width, like the results-index! + local line = path_to_display + .. ":" + .. harpoon_item.context.row + .. ":" + .. harpoon_item.context.col return displayer({ - entry_idx_lpad .. entry_idx_str, - line, + { et_idx_lpad .. et_idx_str }, + { line }, }) end - return { - value = entry, - ordinal = line, - display = make_display, - lnum = entry.row, - col = entry.col, - filename = entry.value, - } + return entry end, }) end -local delete_harpoon_mark = function(prompt_bufnr) - local confirmation = - vim.fn.input(string.format("Delete current mark(s)? [y/n]: ")) - if - string.len(confirmation) == 0 - or string.sub(string.lower(confirmation), 0, 1) ~= "y" - then - print(string.format("Didn't delete mark")) - return - end - - local selection = action_state.get_selected_entry() - harpoon:list():remove(selection.value) - - local function get_selections() - local results = {} - action_utils.map_selections(prompt_bufnr, function(entry) - table.insert(results, entry) - end) - return results - end - - local selections = get_selections() - for _, current_selection in ipairs(selections) do - harpoon:list():remove(current_selection.value) +local delete_harpoon_mark = function(prompt_bufnr, opts) + local selections = {} + action_utils.map_selections(prompt_bufnr, function(entry) + table.insert(selections, entry) + end) + table.sort(selections, function(a, b) + return a.index < b.index + end) + + local count = 0 + + if #selections > 0 then + for i = #selections, 1, -1 do + local selection = selections[i] + harpoon:list():removeAt(selection.index) + count = count + 1 + end + else + local selection = action_state.get_selected_entry() + if selection ~= nil then + harpoon:list():removeAt(selection.index) + count = count + 1 + else + return 0 + end end local current_picker = action_state.get_current_picker(prompt_bufnr) - current_picker:refresh(generate_new_finder(), { reset_prompt = true }) + current_picker:refresh(generate_new_finder(opts), { reset_prompt = true }) + + return count end -local move_mark_up = function(prompt_bufnr) - local selection = action_state.get_selected_entry() - local length = harpoon:list():length() +local delete_harpoon_mark_prompt = function(opts) + return function(prompt_bufnr) + vim.ui.input({ + prompt = "Delete selected marks? [Yes/no]: ", + default = "y", + }, function(input) + if input == nil then + return + end - if selection.index == length then - return + local input_str = string.lower(input) + if input_str == "y" or input_str == "yes" then + local deletion_count = delete_harpoon_mark(prompt_bufnr, opts) + if deletion_count == 0 then + print("No marks deleted") + elseif deletion_count == 1 then + print("Deleted 1 mark") + else + print("Deleted " .. deletion_count .. " marks") + end + else + print("No action taken") + end + end) end +end - local mark_list = harpoon:list().items +local move_mark_up = function(opts) + return function(prompt_bufnr) + local selection = action_state.get_selected_entry() + local length = harpoon:list():length() + local current_index = selection.index + if current_index == length then + return + end - table.remove(mark_list, selection.index) - table.insert(mark_list, selection.index + 1, selection.value) + local mark_list = harpoon:list().items + local current_item = mark_list[current_index] + table.remove(mark_list, selection.index) + table.insert(mark_list, selection.index + 1, current_item) - local current_picker = action_state.get_current_picker(prompt_bufnr) - current_picker:refresh(generate_new_finder(), { reset_prompt = true }) + local current_picker = action_state.get_current_picker(prompt_bufnr) + current_picker:refresh(generate_new_finder(opts), { reset_prompt = true }) + end end -local move_mark_down = function(prompt_bufnr) - local selection = action_state.get_selected_entry() - if selection.index == 1 then - return +local move_mark_down = function (opts) + return function(prompt_bufnr) + local selection = action_state.get_selected_entry() + local current_index = selection.index + if current_index == 1 then + return + end + + local mark_list = harpoon:list().items + local current_item = mark_list[current_index] + table.remove(mark_list, current_index) + table.insert(mark_list, current_index - 1, current_item) + + local current_picker = action_state.get_current_picker(prompt_bufnr) + current_picker:refresh(generate_new_finder(opts), { reset_prompt = true }) end - local mark_list = harpoon:list().items - table.remove(mark_list, selection.index) - table.insert(mark_list, selection.index - 1, selection.value) - local current_picker = action_state.get_current_picker(prompt_bufnr) - current_picker:refresh(generate_new_finder(), { reset_prompt = true }) end return function(opts) opts = opts or {} - pickers .new(opts, { prompt_title = "harpoon marks", - finder = generate_new_finder(), + finder = generate_new_finder(opts), sorter = conf.generic_sorter(opts), - previewer = conf.grep_previewer(opts), + previewer = conf.file_previewer(opts), attach_mappings = function(_, map) - map("i", "", delete_harpoon_mark) - map("n", "", delete_harpoon_mark) + map("i", "", delete_harpoon_mark_prompt(opts)) + map("n", "", delete_harpoon_mark_prompt(opts)) - map("i", "", move_mark_up) - map("n", "", move_mark_up) + map("i", "", move_mark_up(opts)) + map("n", "", move_mark_up(opts)) - map("i", "", move_mark_down) - map("n", "", move_mark_down) + map("i", "", move_mark_down(opts)) + map("n", "", move_mark_down(opts)) return true end, }) From b1272274e980895c924cc5d73013fd7884a724ba Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Sun, 4 Feb 2024 01:27:22 +0100 Subject: [PATCH 03/10] feat(picker): add icons to the harpoon file-picker --- lua/telescope/_extensions/marks.lua | 55 +++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/lua/telescope/_extensions/marks.lua b/lua/telescope/_extensions/marks.lua index 802d8c3e..418ed75e 100644 --- a/lua/telescope/_extensions/marks.lua +++ b/lua/telescope/_extensions/marks.lua @@ -5,6 +5,7 @@ local finders = require("telescope.finders") local pickers = require("telescope.pickers") local utils = require("telescope.utils") local make_entry = require("telescope.make_entry") +local strings = require("plenary.strings") local conf = require("telescope.config").values local harpoon = require("harpoon") @@ -30,17 +31,39 @@ local generate_new_finder = function(opts) local results = filter_empty_string(harpoon:list().items) local results_idx_str_len = string.len(tostring(#results)) local make_file_entry = make_entry.gen_from_file(opts) + local disable_devicons = opts.disable_devicons + + local icon_width = 0 + if not disable_devicons then + local icon, _ = utils.get_devicons("fname", disable_devicons) + icon_width = strings.strdisplaywidth(icon) + end + return finders.new_table({ results = results, entry_maker = function(harpoon_item) local entry = make_file_entry(harpoon_item.value) -- value => path - local displayer = entry_display.create({ - separator = " - ", - items = { - { width = results_idx_str_len }, - { remaining = true }, - }, - }) + local icon, hl_group = utils.get_devicons(entry.filename, disable_devicons) + local display_config = nil + if not disable_devicons then + display_config = { + separator = " ", + items = { + { width = results_idx_str_len }, + { width = icon_width }, + { remaining = true }, + }, + } + else + display_config = { + separator = " - ", + items = { + { width = results_idx_str_len }, + { remaining = true }, + }, + } + end + local displayer = entry_display.create(display_config) entry.display = function(et) local et_idx_str = tostring(et.index) local et_idx_str_len = string.len(et_idx_str) @@ -52,10 +75,20 @@ local generate_new_finder = function(opts) .. harpoon_item.context.row .. ":" .. harpoon_item.context.col - return displayer({ - { et_idx_lpad .. et_idx_str }, - { line }, - }) + local entry_values = nil + if not disable_devicons then + entry_values = { + { et_idx_lpad .. et_idx_str }, + { icon, hl_group }, + { line }, + } + else + entry_values = { + { et_idx_lpad .. et_idx_str }, + { line }, + } + end + return displayer(entry_values) end return entry end, From 37fcece876f6ec084d3ee0d3b2a7e75ce6066a1c Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Sun, 4 Feb 2024 02:04:11 +0100 Subject: [PATCH 04/10] fix(picker): give the row and column their own place, in the display-config --- lua/telescope/_extensions/marks.lua | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lua/telescope/_extensions/marks.lua b/lua/telescope/_extensions/marks.lua index 418ed75e..2b78c615 100644 --- a/lua/telescope/_extensions/marks.lua +++ b/lua/telescope/_extensions/marks.lua @@ -52,14 +52,16 @@ local generate_new_finder = function(opts) { width = results_idx_str_len }, { width = icon_width }, { remaining = true }, + { width = 6 }, }, } else display_config = { - separator = " - ", + separator = " ", items = { { width = results_idx_str_len }, { remaining = true }, + { width = 6 }, }, } end @@ -69,23 +71,19 @@ local generate_new_finder = function(opts) local et_idx_str_len = string.len(et_idx_str) local et_idx_lpad = string.rep(" ", results_idx_str_len - et_idx_str_len) local path_to_display = utils.transform_path(opts, et.value) - -- TODO create another column for the row and col, with a dynamic width, like the results-index! - local line = path_to_display - .. ":" - .. harpoon_item.context.row - .. ":" - .. harpoon_item.context.col local entry_values = nil if not disable_devicons then entry_values = { { et_idx_lpad .. et_idx_str }, { icon, hl_group }, - { line }, + { path_to_display }, + { harpoon_item.context.row .. ":" .. harpoon_item.context.col }, } else entry_values = { { et_idx_lpad .. et_idx_str }, - { line }, + { path_to_display }, + { harpoon_item.context.row .. ":" .. harpoon_item.context.col }, } end return displayer(entry_values) @@ -196,7 +194,7 @@ return function(opts) opts = opts or {} pickers .new(opts, { - prompt_title = "harpoon marks", + prompt_title = "Harpoon Marks", finder = generate_new_finder(opts), sorter = conf.generic_sorter(opts), previewer = conf.file_previewer(opts), From d5300aa011633ad67069f7049547593e5c96b9c0 Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Sun, 4 Feb 2024 19:11:49 +0100 Subject: [PATCH 05/10] feat(picker): enhance the usability of the marks-picker Improve the `move`- and `delete`-operations, by making them feel smoother. Also make the movements behave more like "builtin" telescope- movements. --- lua/telescope/_extensions/marks.lua | 197 ++++++++++++++++------------ 1 file changed, 116 insertions(+), 81 deletions(-) diff --git a/lua/telescope/_extensions/marks.lua b/lua/telescope/_extensions/marks.lua index 2b78c615..6e51c9c3 100644 --- a/lua/telescope/_extensions/marks.lua +++ b/lua/telescope/_extensions/marks.lua @@ -1,34 +1,31 @@ +local harpoon = require("harpoon") +local actions = require("telescope.actions") local action_state = require("telescope.actions.state") local action_utils = require("telescope.actions.utils") -local entry_display = require("telescope.pickers.entry_display") +local conf = require("telescope.config").values local finders = require("telescope.finders") +local make_entry = require("telescope.make_entry") local pickers = require("telescope.pickers") +local entry_display = require("telescope.pickers.entry_display") local utils = require("telescope.utils") -local make_entry = require("telescope.make_entry") local strings = require("plenary.strings") -local conf = require("telescope.config").values -local harpoon = require("harpoon") -local function filter_empty_string(list) - local next = {} - for idx = 1, #list do - if list[idx].value ~= "" then - local item = list[idx] - table.insert(next, { - value = item.value, - context = { - row = item.context.row, - col = item.context.col, - }, - }) - end +local function make_results(list) + local results = {} + for _, item in pairs(list) do + table.insert(results, { + value = item.value, + context = { + row = item.context.row, + col = item.context.col, + }, + }) end - - return next + return results end local generate_new_finder = function(opts) - local results = filter_empty_string(harpoon:list().items) + local results = make_results(harpoon:list().items) local results_idx_str_len = string.len(tostring(#results)) local make_file_entry = make_entry.gen_from_file(opts) local disable_devicons = opts.disable_devicons @@ -93,7 +90,7 @@ local generate_new_finder = function(opts) }) end -local delete_harpoon_mark = function(prompt_bufnr, opts) +local delete_mark_selections = function(prompt_bufnr) local selections = {} action_utils.map_selections(prompt_bufnr, function(entry) table.insert(selections, entry) @@ -105,12 +102,14 @@ local delete_harpoon_mark = function(prompt_bufnr, opts) local count = 0 if #selections > 0 then + -- delete marks from multi-selection for i = #selections, 1, -1 do local selection = selections[i] harpoon:list():removeAt(selection.index) count = count + 1 end else + -- delete marks from single-selection local selection = action_state.get_selected_entry() if selection ~= nil then harpoon:list():removeAt(selection.index) @@ -120,74 +119,112 @@ local delete_harpoon_mark = function(prompt_bufnr, opts) end end + -- delete picker-selections local current_picker = action_state.get_current_picker(prompt_bufnr) - current_picker:refresh(generate_new_finder(opts), { reset_prompt = true }) + current_picker:delete_selection(function() end) return count end -local delete_harpoon_mark_prompt = function(opts) - return function(prompt_bufnr) - vim.ui.input({ - prompt = "Delete selected marks? [Yes/no]: ", - default = "y", - }, function(input) - if input == nil then - return - end +local delete_mark_selections_prompt = function(prompt_bufnr) + vim.ui.input({ + prompt = "Delete selected marks? [Yes/no]: ", + default = "y", + }, function(input) + if input == nil then + return + end - local input_str = string.lower(input) - if input_str == "y" or input_str == "yes" then - local deletion_count = delete_harpoon_mark(prompt_bufnr, opts) - if deletion_count == 0 then - print("No marks deleted") - elseif deletion_count == 1 then - print("Deleted 1 mark") - else - print("Deleted " .. deletion_count .. " marks") - end + local input_str = string.lower(input) + if input_str == "y" or input_str == "yes" then + local deletion_count = delete_mark_selections(prompt_bufnr) + if deletion_count == 0 then + print("No marks deleted") + elseif deletion_count == 1 then + print("Deleted 1 mark") else - print("No action taken") + print("Deleted " .. deletion_count .. " marks") end - end) - end + else + print("No action taken") + end + end) end -local move_mark_up = function(opts) - return function(prompt_bufnr) - local selection = action_state.get_selected_entry() - local length = harpoon:list():length() - local current_index = selection.index - if current_index == length then - return - end +local move_mark_next = function(prompt_bufnr) + -- get current index + local current_selection = action_state.get_selected_entry() + local current_index = current_selection.index + + -- get next index + actions.move_selection_next(prompt_bufnr) + local next_selection = action_state.get_selected_entry() + local next_index = next_selection.index + + -- swap harpoon-items + local mark_list = harpoon:list().items + local current_item = mark_list[current_index] + local next_item = mark_list[next_index] + mark_list[current_index] = next_item + mark_list[next_index] = current_item + + -- swap telescope-entries + local current_value = current_selection.value + local next_value = next_selection.value + local current_display = current_selection.display + local next_display = next_selection.display + current_selection.value = next_value + next_selection.value = current_value + current_selection.display = next_display + next_selection.display = current_display + + -- refresh picker + local current_picker = action_state.get_current_picker(prompt_bufnr) + local selection_row = current_picker:get_selection_row() + current_picker:refresh() - local mark_list = harpoon:list().items - local current_item = mark_list[current_index] - table.remove(mark_list, selection.index) - table.insert(mark_list, selection.index + 1, current_item) + vim.wait(1) -- wait for refresh - local current_picker = action_state.get_current_picker(prompt_bufnr) - current_picker:refresh(generate_new_finder(opts), { reset_prompt = true }) - end + -- select row + current_picker:set_selection(selection_row) end -local move_mark_down = function (opts) - return function(prompt_bufnr) - local selection = action_state.get_selected_entry() - local current_index = selection.index - if current_index == 1 then - return - end +local move_mark_previous = function(prompt_bufnr) + -- get current index + local current_selection = action_state.get_selected_entry() + local current_index = current_selection.index + + -- get previous index + actions.move_selection_previous(prompt_bufnr) + local previous_selection = action_state.get_selected_entry() + local previous_index = previous_selection.index + + -- swap harpoon items + local mark_list = harpoon:list().items + local current_item = mark_list[current_index] + local previous_item = mark_list[previous_index] + mark_list[current_index] = previous_item + mark_list[previous_index] = current_item + + -- swap telescope entries + local current_value = current_selection.value + local previous_value = previous_selection.value + local current_display = current_selection.display + local previous_display = previous_selection.display + current_selection.value = previous_value + previous_selection.value = current_value + current_selection.display = previous_display + previous_selection.display = current_display + + -- refresh picker + local current_picker = action_state.get_current_picker(prompt_bufnr) + local selection_row = current_picker:get_selection_row() + current_picker:refresh() - local mark_list = harpoon:list().items - local current_item = mark_list[current_index] - table.remove(mark_list, current_index) - table.insert(mark_list, current_index - 1, current_item) + vim.wait(1) -- wait for refresh - local current_picker = action_state.get_current_picker(prompt_bufnr) - current_picker:refresh(generate_new_finder(opts), { reset_prompt = true }) - end + -- select row + current_picker:set_selection(selection_row) end return function(opts) @@ -199,14 +236,12 @@ return function(opts) sorter = conf.generic_sorter(opts), previewer = conf.file_previewer(opts), attach_mappings = function(_, map) - map("i", "", delete_harpoon_mark_prompt(opts)) - map("n", "", delete_harpoon_mark_prompt(opts)) - - map("i", "", move_mark_up(opts)) - map("n", "", move_mark_up(opts)) - - map("i", "", move_mark_down(opts)) - map("n", "", move_mark_down(opts)) + map("i", "", delete_mark_selections_prompt) + map("n", "", delete_mark_selections_prompt) + map("i", "", move_mark_previous) + map("n", "", move_mark_previous) + map("i", "", move_mark_next) + map("n", "", move_mark_next) return true end, }) From 61192a308d93dbc4837de0bc119354ad7240fa75 Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Mon, 5 Feb 2024 12:25:58 +0100 Subject: [PATCH 06/10] fix(marks): icrement the `harpoon_item.context.col` by 1, for display --- lua/telescope/_extensions/marks.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/telescope/_extensions/marks.lua b/lua/telescope/_extensions/marks.lua index 6e51c9c3..ce4a4695 100644 --- a/lua/telescope/_extensions/marks.lua +++ b/lua/telescope/_extensions/marks.lua @@ -69,18 +69,20 @@ local generate_new_finder = function(opts) local et_idx_lpad = string.rep(" ", results_idx_str_len - et_idx_str_len) local path_to_display = utils.transform_path(opts, et.value) local entry_values = nil + local row = harpoon_item.context.row + local column = harpoon_item.context.col + 1 if not disable_devicons then entry_values = { { et_idx_lpad .. et_idx_str }, { icon, hl_group }, { path_to_display }, - { harpoon_item.context.row .. ":" .. harpoon_item.context.col }, + { row .. ":" .. column }, } else entry_values = { { et_idx_lpad .. et_idx_str }, { path_to_display }, - { harpoon_item.context.row .. ":" .. harpoon_item.context.col }, + { row .. ":" .. column }, } end return displayer(entry_values) From e0bdf91cafce78136547e982db59a262492fa8eb Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Sun, 11 Feb 2024 12:42:10 +0100 Subject: [PATCH 07/10] feat(picker): sort by index This change allows the telescope-picker to sort by index. If one of the search-terms equals an index, it'll get a better score. Partial matches of an index, will get a slightly better score. Matches with the main entry - i.e. the filename - will always have the best score; showing them before an index-match. --- lua/telescope/_extensions/marks.lua | 41 ++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/lua/telescope/_extensions/marks.lua b/lua/telescope/_extensions/marks.lua index ce4a4695..6952e599 100644 --- a/lua/telescope/_extensions/marks.lua +++ b/lua/telescope/_extensions/marks.lua @@ -8,6 +8,7 @@ local make_entry = require("telescope.make_entry") local pickers = require("telescope.pickers") local entry_display = require("telescope.pickers.entry_display") local utils = require("telescope.utils") +local sorters = require("telescope.sorters") local strings = require("plenary.strings") local function make_results(list) @@ -24,7 +25,7 @@ local function make_results(list) return results end -local generate_new_finder = function(opts) +local make_finder = function(opts) local results = make_results(harpoon:list().items) local results_idx_str_len = string.len(tostring(#results)) local make_file_entry = make_entry.gen_from_file(opts) @@ -92,6 +93,40 @@ local generate_new_finder = function(opts) }) end +local make_sorter = function(opts) + local sorter = conf.generic_sorter(opts) + local generic_scoring = sorter.scoring_function + sorter.scoring_function = function(self, prompt, line, entry) + local score = generic_scoring(self, prompt, line, entry) + local multiplier = 1 + + -- set the multiplier, when matching an index + local index = entry.index + local index_str = tostring(index) + for value in string.gmatch(prompt, "%S+") do + local num = tonumber(value) + if num ~= nil then + if num == index then + multiplier = 0.25 + break -- found an exact match + elseif index_str:match(value) then + multiplier = 0.5 + -- continue looking for a better match + end + end + end + + if score ~= -1 then -- generic_sorter found a match + score = score * multiplier -- make the score better + elseif multiplier ~= 1 then -- has matched an index + score = multiplier -- make the score slightly better + end + + return score + end + return sorter +end + local delete_mark_selections = function(prompt_bufnr) local selections = {} action_utils.map_selections(prompt_bufnr, function(entry) @@ -234,8 +269,8 @@ return function(opts) pickers .new(opts, { prompt_title = "Harpoon Marks", - finder = generate_new_finder(opts), - sorter = conf.generic_sorter(opts), + finder = make_finder(opts), + sorter = make_sorter(opts), previewer = conf.file_previewer(opts), attach_mappings = function(_, map) map("i", "", delete_mark_selections_prompt) From ffa1e78a8626325066c81efaab0317817f23db28 Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Thu, 4 Apr 2024 17:02:01 +0200 Subject: [PATCH 08/10] refactor: remove unused require --- lua/telescope/_extensions/marks.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/telescope/_extensions/marks.lua b/lua/telescope/_extensions/marks.lua index 6952e599..ad615c37 100644 --- a/lua/telescope/_extensions/marks.lua +++ b/lua/telescope/_extensions/marks.lua @@ -8,7 +8,6 @@ local make_entry = require("telescope.make_entry") local pickers = require("telescope.pickers") local entry_display = require("telescope.pickers.entry_display") local utils = require("telescope.utils") -local sorters = require("telescope.sorters") local strings = require("plenary.strings") local function make_results(list) From 0abc1d24bd933069b6a568eaeef19ca0c95aad05 Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Thu, 4 Apr 2024 22:12:28 +0200 Subject: [PATCH 09/10] refactor(picker): modularize the marks-picker This can be used to extend and override the key-mappings inside the `attach_mappings`-function, for example: ```lua local themes = require("telescope.themes") local hm_actions = require("telescope._extensions.harpoon_marks.actions") require("telescope").extensions.harpoon.marks(themes.get_dropdown({ previewer = false, layout_config = { width = 0.6 }, path_display = { truncate = 10 }, attach_mappings = function(_, map) map("i", "", hm_actions.delete_mark_selections) map("n", "", hm_actions.delete_mark_selections) return true end, })) ``` --- lua/telescope/_extensions/harpoon.lua | 4 +- .../_extensions/harpoon_marks/actions.lua | 145 +++++++++ .../_extensions/harpoon_marks/finders.lua | 94 ++++++ .../_extensions/harpoon_marks/marks.lua | 26 ++ .../_extensions/harpoon_marks/sorters.lua | 39 +++ lua/telescope/_extensions/marks.lua | 285 ------------------ 6 files changed, 306 insertions(+), 287 deletions(-) create mode 100644 lua/telescope/_extensions/harpoon_marks/actions.lua create mode 100644 lua/telescope/_extensions/harpoon_marks/finders.lua create mode 100644 lua/telescope/_extensions/harpoon_marks/marks.lua create mode 100644 lua/telescope/_extensions/harpoon_marks/sorters.lua delete mode 100644 lua/telescope/_extensions/marks.lua diff --git a/lua/telescope/_extensions/harpoon.lua b/lua/telescope/_extensions/harpoon.lua index 6de4b7a5..b65b60c8 100644 --- a/lua/telescope/_extensions/harpoon.lua +++ b/lua/telescope/_extensions/harpoon.lua @@ -1,11 +1,11 @@ local has_telescope, telescope = pcall(require, "telescope") if not has_telescope then - error("harpoon.nvim requires nvim-telescope/telescope.nvim") + error("This extension requires telescope.nvim (https://github.com/nvim-telescope/telescope.nvim)") end return telescope.register_extension({ exports = { - marks = require("telescope._extensions.marks"), + marks = require("telescope._extensions.harpoon_marks.marks"), }, }) diff --git a/lua/telescope/_extensions/harpoon_marks/actions.lua b/lua/telescope/_extensions/harpoon_marks/actions.lua new file mode 100644 index 00000000..33ad6ee3 --- /dev/null +++ b/lua/telescope/_extensions/harpoon_marks/actions.lua @@ -0,0 +1,145 @@ +local harpoon = require("harpoon") +local actions = require("telescope.actions") +local action_state = require("telescope.actions.state") +local action_utils = require("telescope.actions.utils") + +local M = {} + +M.delete_mark_selections = function(prompt_bufnr) + local selections = {} + action_utils.map_selections(prompt_bufnr, function(entry) + table.insert(selections, entry) + end) + table.sort(selections, function(a, b) + return a.index < b.index + end) + + local count = 0 + + if #selections > 0 then + -- delete marks from multi-selection + for i = #selections, 1, -1 do + local selection = selections[i] + harpoon:list():removeAt(selection.index) + count = count + 1 + end + else + -- delete marks from single-selection + local selection = action_state.get_selected_entry() + if selection ~= nil then + harpoon:list():removeAt(selection.index) + count = count + 1 + else + return 0 + end + end + + -- delete picker-selections + local current_picker = action_state.get_current_picker(prompt_bufnr) + current_picker:delete_selection(function() end) + + return count +end + +M.delete_mark_selections_prompt = function(prompt_bufnr) + vim.ui.input({ + prompt = "Delete selected marks? [Yes/no]: ", + default = "y", + }, function(input) + if input == nil then + return + end + + local input_str = string.lower(input) + if input_str == "y" or input_str == "yes" then + local deletion_count = M.delete_mark_selections(prompt_bufnr) + if deletion_count == 0 then + print("No marks deleted") + elseif deletion_count == 1 then + print("Deleted 1 mark") + else + print("Deleted " .. deletion_count .. " marks") + end + else + print("No action taken") + end + end) +end + +M.move_mark_next = function(prompt_bufnr) + -- get current index + local current_selection = action_state.get_selected_entry() + local current_index = current_selection.index + + -- get next index + actions.move_selection_next(prompt_bufnr) + local next_selection = action_state.get_selected_entry() + local next_index = next_selection.index + + -- swap harpoon-items + local mark_list = harpoon:list().items + local current_item = mark_list[current_index] + local next_item = mark_list[next_index] + mark_list[current_index] = next_item + mark_list[next_index] = current_item + + -- swap telescope-entries + local current_value = current_selection.value + local next_value = next_selection.value + local current_display = current_selection.display + local next_display = next_selection.display + current_selection.value = next_value + next_selection.value = current_value + current_selection.display = next_display + next_selection.display = current_display + + -- refresh picker + local current_picker = action_state.get_current_picker(prompt_bufnr) + local selection_row = current_picker:get_selection_row() + current_picker:refresh() + + vim.wait(1) -- wait for refresh + + -- select row + current_picker:set_selection(selection_row) +end + +M.move_mark_previous = function(prompt_bufnr) + -- get current index + local current_selection = action_state.get_selected_entry() + local current_index = current_selection.index + + -- get previous index + actions.move_selection_previous(prompt_bufnr) + local previous_selection = action_state.get_selected_entry() + local previous_index = previous_selection.index + + -- swap harpoon items + local mark_list = harpoon:list().items + local current_item = mark_list[current_index] + local previous_item = mark_list[previous_index] + mark_list[current_index] = previous_item + mark_list[previous_index] = current_item + + -- swap telescope entries + local current_value = current_selection.value + local previous_value = previous_selection.value + local current_display = current_selection.display + local previous_display = previous_selection.display + current_selection.value = previous_value + previous_selection.value = current_value + current_selection.display = previous_display + previous_selection.display = current_display + + -- refresh picker + local current_picker = action_state.get_current_picker(prompt_bufnr) + local selection_row = current_picker:get_selection_row() + current_picker:refresh() + + vim.wait(1) -- wait for refresh + + -- select row + current_picker:set_selection(selection_row) +end + +return M diff --git a/lua/telescope/_extensions/harpoon_marks/finders.lua b/lua/telescope/_extensions/harpoon_marks/finders.lua new file mode 100644 index 00000000..eb7c2709 --- /dev/null +++ b/lua/telescope/_extensions/harpoon_marks/finders.lua @@ -0,0 +1,94 @@ +local harpoon = require("harpoon") +local finders = require("telescope.finders") +local make_entry = require("telescope.make_entry") +local entry_display = require("telescope.pickers.entry_display") +local utils = require("telescope.utils") +local strings = require("plenary.strings") + +local M = {} + +local function make_results(list) + local results = {} + for _, item in pairs(list) do + table.insert(results, { + value = item.value, + context = { + row = item.context.row, + col = item.context.col, + }, + }) + end + return results +end + +M.file_mark_finder = function(opts) + local results = make_results(harpoon:list().items) + local results_idx_str_len = string.len(tostring(#results)) + local make_file_entry = make_entry.gen_from_file(opts) + local disable_devicons = opts.disable_devicons + + local icon_width = 0 + if not disable_devicons then + local icon, _ = utils.get_devicons("fname", disable_devicons) + icon_width = strings.strdisplaywidth(icon) + end + + return finders.new_table({ + results = results, + entry_maker = function(harpoon_item) + local entry = make_file_entry(harpoon_item.value) -- value => path + local icon, hl_group = + utils.get_devicons(entry.filename, disable_devicons) + local display_config = nil + if not disable_devicons then + display_config = { + separator = " ", + items = { + { width = results_idx_str_len }, + { width = icon_width }, + { remaining = true }, + { width = 6 }, + }, + } + else + display_config = { + separator = " ", + items = { + { width = results_idx_str_len }, + { remaining = true }, + { width = 6 }, + }, + } + end + local displayer = entry_display.create(display_config) + entry.display = function(et) + local et_idx_str = tostring(et.index) + local et_idx_str_len = string.len(et_idx_str) + local et_idx_lpad = + string.rep(" ", results_idx_str_len - et_idx_str_len) + local path_to_display = utils.transform_path(opts, et.value) + local entry_values = nil + local row = harpoon_item.context.row + local column = harpoon_item.context.col + 1 + if not disable_devicons then + entry_values = { + { et_idx_lpad .. et_idx_str }, + { icon, hl_group }, + { path_to_display }, + { row .. ":" .. column }, + } + else + entry_values = { + { et_idx_lpad .. et_idx_str }, + { path_to_display }, + { row .. ":" .. column }, + } + end + return displayer(entry_values) + end + return entry + end, + }) +end + +return M diff --git a/lua/telescope/_extensions/harpoon_marks/marks.lua b/lua/telescope/_extensions/harpoon_marks/marks.lua new file mode 100644 index 00000000..4046c0de --- /dev/null +++ b/lua/telescope/_extensions/harpoon_marks/marks.lua @@ -0,0 +1,26 @@ +local conf = require("telescope.config").values +local pickers = require("telescope.pickers") + +return function(opts) + local hm_finders = require("telescope._extensions.harpoon_marks.finders") + local hm_sorters = require("telescope._extensions.harpoon_marks.sorters") + local hm_actions = require("telescope._extensions.harpoon_marks.actions") + opts = opts or {} + pickers + .new(opts, { + prompt_title = "Harpoon Marks", + finder = hm_finders.file_mark_finder(opts), + sorter = hm_sorters.file_mark_sorter(opts), + previewer = conf.file_previewer(opts), + attach_mappings = function(_, map) + map("i", "", hm_actions.delete_mark_selections_prompt) + map("n", "", hm_actions.delete_mark_selections_prompt) + map("i", "", hm_actions.move_mark_previous) + map("n", "", hm_actions.move_mark_previous) + map("i", "", hm_actions.move_mark_next) + map("n", "", hm_actions.move_mark_next) + return true + end, + }) + :find() +end diff --git a/lua/telescope/_extensions/harpoon_marks/sorters.lua b/lua/telescope/_extensions/harpoon_marks/sorters.lua new file mode 100644 index 00000000..a84a1e42 --- /dev/null +++ b/lua/telescope/_extensions/harpoon_marks/sorters.lua @@ -0,0 +1,39 @@ +local conf = require("telescope.config").values + +local M = {} + +M.file_mark_sorter = function(opts) + local sorter = conf.generic_sorter(opts) + local generic_scoring = sorter.scoring_function + sorter.scoring_function = function(self, prompt, line, entry) + local score = generic_scoring(self, prompt, line, entry) + local multiplier = 1 + + -- set the multiplier, when matching an index + local index = entry.index + local index_str = tostring(index) + for value in string.gmatch(prompt, "%S+") do + local num = tonumber(value) + if num ~= nil then + if num == index then + multiplier = 0.25 + break -- found an exact match + elseif index_str:match(value) then + multiplier = 0.5 + -- continue looking for a better match + end + end + end + + if score ~= -1 then -- generic_sorter found a match + score = score * multiplier -- make the score better + elseif multiplier ~= 1 then -- has matched an index + score = multiplier -- make the score slightly better + end + + return score + end + return sorter +end + +return M diff --git a/lua/telescope/_extensions/marks.lua b/lua/telescope/_extensions/marks.lua deleted file mode 100644 index ad615c37..00000000 --- a/lua/telescope/_extensions/marks.lua +++ /dev/null @@ -1,285 +0,0 @@ -local harpoon = require("harpoon") -local actions = require("telescope.actions") -local action_state = require("telescope.actions.state") -local action_utils = require("telescope.actions.utils") -local conf = require("telescope.config").values -local finders = require("telescope.finders") -local make_entry = require("telescope.make_entry") -local pickers = require("telescope.pickers") -local entry_display = require("telescope.pickers.entry_display") -local utils = require("telescope.utils") -local strings = require("plenary.strings") - -local function make_results(list) - local results = {} - for _, item in pairs(list) do - table.insert(results, { - value = item.value, - context = { - row = item.context.row, - col = item.context.col, - }, - }) - end - return results -end - -local make_finder = function(opts) - local results = make_results(harpoon:list().items) - local results_idx_str_len = string.len(tostring(#results)) - local make_file_entry = make_entry.gen_from_file(opts) - local disable_devicons = opts.disable_devicons - - local icon_width = 0 - if not disable_devicons then - local icon, _ = utils.get_devicons("fname", disable_devicons) - icon_width = strings.strdisplaywidth(icon) - end - - return finders.new_table({ - results = results, - entry_maker = function(harpoon_item) - local entry = make_file_entry(harpoon_item.value) -- value => path - local icon, hl_group = utils.get_devicons(entry.filename, disable_devicons) - local display_config = nil - if not disable_devicons then - display_config = { - separator = " ", - items = { - { width = results_idx_str_len }, - { width = icon_width }, - { remaining = true }, - { width = 6 }, - }, - } - else - display_config = { - separator = " ", - items = { - { width = results_idx_str_len }, - { remaining = true }, - { width = 6 }, - }, - } - end - local displayer = entry_display.create(display_config) - entry.display = function(et) - local et_idx_str = tostring(et.index) - local et_idx_str_len = string.len(et_idx_str) - local et_idx_lpad = string.rep(" ", results_idx_str_len - et_idx_str_len) - local path_to_display = utils.transform_path(opts, et.value) - local entry_values = nil - local row = harpoon_item.context.row - local column = harpoon_item.context.col + 1 - if not disable_devicons then - entry_values = { - { et_idx_lpad .. et_idx_str }, - { icon, hl_group }, - { path_to_display }, - { row .. ":" .. column }, - } - else - entry_values = { - { et_idx_lpad .. et_idx_str }, - { path_to_display }, - { row .. ":" .. column }, - } - end - return displayer(entry_values) - end - return entry - end, - }) -end - -local make_sorter = function(opts) - local sorter = conf.generic_sorter(opts) - local generic_scoring = sorter.scoring_function - sorter.scoring_function = function(self, prompt, line, entry) - local score = generic_scoring(self, prompt, line, entry) - local multiplier = 1 - - -- set the multiplier, when matching an index - local index = entry.index - local index_str = tostring(index) - for value in string.gmatch(prompt, "%S+") do - local num = tonumber(value) - if num ~= nil then - if num == index then - multiplier = 0.25 - break -- found an exact match - elseif index_str:match(value) then - multiplier = 0.5 - -- continue looking for a better match - end - end - end - - if score ~= -1 then -- generic_sorter found a match - score = score * multiplier -- make the score better - elseif multiplier ~= 1 then -- has matched an index - score = multiplier -- make the score slightly better - end - - return score - end - return sorter -end - -local delete_mark_selections = function(prompt_bufnr) - local selections = {} - action_utils.map_selections(prompt_bufnr, function(entry) - table.insert(selections, entry) - end) - table.sort(selections, function(a, b) - return a.index < b.index - end) - - local count = 0 - - if #selections > 0 then - -- delete marks from multi-selection - for i = #selections, 1, -1 do - local selection = selections[i] - harpoon:list():removeAt(selection.index) - count = count + 1 - end - else - -- delete marks from single-selection - local selection = action_state.get_selected_entry() - if selection ~= nil then - harpoon:list():removeAt(selection.index) - count = count + 1 - else - return 0 - end - end - - -- delete picker-selections - local current_picker = action_state.get_current_picker(prompt_bufnr) - current_picker:delete_selection(function() end) - - return count -end - -local delete_mark_selections_prompt = function(prompt_bufnr) - vim.ui.input({ - prompt = "Delete selected marks? [Yes/no]: ", - default = "y", - }, function(input) - if input == nil then - return - end - - local input_str = string.lower(input) - if input_str == "y" or input_str == "yes" then - local deletion_count = delete_mark_selections(prompt_bufnr) - if deletion_count == 0 then - print("No marks deleted") - elseif deletion_count == 1 then - print("Deleted 1 mark") - else - print("Deleted " .. deletion_count .. " marks") - end - else - print("No action taken") - end - end) -end - -local move_mark_next = function(prompt_bufnr) - -- get current index - local current_selection = action_state.get_selected_entry() - local current_index = current_selection.index - - -- get next index - actions.move_selection_next(prompt_bufnr) - local next_selection = action_state.get_selected_entry() - local next_index = next_selection.index - - -- swap harpoon-items - local mark_list = harpoon:list().items - local current_item = mark_list[current_index] - local next_item = mark_list[next_index] - mark_list[current_index] = next_item - mark_list[next_index] = current_item - - -- swap telescope-entries - local current_value = current_selection.value - local next_value = next_selection.value - local current_display = current_selection.display - local next_display = next_selection.display - current_selection.value = next_value - next_selection.value = current_value - current_selection.display = next_display - next_selection.display = current_display - - -- refresh picker - local current_picker = action_state.get_current_picker(prompt_bufnr) - local selection_row = current_picker:get_selection_row() - current_picker:refresh() - - vim.wait(1) -- wait for refresh - - -- select row - current_picker:set_selection(selection_row) -end - -local move_mark_previous = function(prompt_bufnr) - -- get current index - local current_selection = action_state.get_selected_entry() - local current_index = current_selection.index - - -- get previous index - actions.move_selection_previous(prompt_bufnr) - local previous_selection = action_state.get_selected_entry() - local previous_index = previous_selection.index - - -- swap harpoon items - local mark_list = harpoon:list().items - local current_item = mark_list[current_index] - local previous_item = mark_list[previous_index] - mark_list[current_index] = previous_item - mark_list[previous_index] = current_item - - -- swap telescope entries - local current_value = current_selection.value - local previous_value = previous_selection.value - local current_display = current_selection.display - local previous_display = previous_selection.display - current_selection.value = previous_value - previous_selection.value = current_value - current_selection.display = previous_display - previous_selection.display = current_display - - -- refresh picker - local current_picker = action_state.get_current_picker(prompt_bufnr) - local selection_row = current_picker:get_selection_row() - current_picker:refresh() - - vim.wait(1) -- wait for refresh - - -- select row - current_picker:set_selection(selection_row) -end - -return function(opts) - opts = opts or {} - pickers - .new(opts, { - prompt_title = "Harpoon Marks", - finder = make_finder(opts), - sorter = make_sorter(opts), - previewer = conf.file_previewer(opts), - attach_mappings = function(_, map) - map("i", "", delete_mark_selections_prompt) - map("n", "", delete_mark_selections_prompt) - map("i", "", move_mark_previous) - map("n", "", move_mark_previous) - map("i", "", move_mark_next) - map("n", "", move_mark_next) - return true - end, - }) - :find() -end From a4f9c0b0d3889306e9563e64384f5a8679adb584 Mon Sep 17 00:00:00 2001 From: "Kim A. Brandt" Date: Thu, 4 Apr 2024 22:48:46 +0200 Subject: [PATCH 10/10] fix: rename `harpoon:list():removeAt` into `harpoon:list():remove_at` --- lua/harpoon/list.lua | 2 +- lua/telescope/_extensions/harpoon_marks/actions.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/harpoon/list.lua b/lua/harpoon/list.lua index bf8008ac..b6a5b6f8 100644 --- a/lua/harpoon/list.lua +++ b/lua/harpoon/list.lua @@ -192,7 +192,7 @@ end function HarpoonList:remove_at(index) if self.items[index] then Logger:log( - "HarpoonList:removeAt", + "HarpoonList:remove_at", { item = self.items[index], index = index } ) Extensions.extensions:emit( diff --git a/lua/telescope/_extensions/harpoon_marks/actions.lua b/lua/telescope/_extensions/harpoon_marks/actions.lua index 33ad6ee3..4e96877b 100644 --- a/lua/telescope/_extensions/harpoon_marks/actions.lua +++ b/lua/telescope/_extensions/harpoon_marks/actions.lua @@ -20,14 +20,14 @@ M.delete_mark_selections = function(prompt_bufnr) -- delete marks from multi-selection for i = #selections, 1, -1 do local selection = selections[i] - harpoon:list():removeAt(selection.index) + harpoon:list():remove_at(selection.index) count = count + 1 end else -- delete marks from single-selection local selection = action_state.get_selected_entry() if selection ~= nil then - harpoon:list():removeAt(selection.index) + harpoon:list():remove_at(selection.index) count = count + 1 else return 0