From 2d6a8fe2855b5999e443a2d231735448c679f514 Mon Sep 17 00:00:00 2001 From: David Alcalde Date: Wed, 14 Jan 2026 17:15:16 -0500 Subject: [PATCH] feat: add new ordering metrics "filename" and "directory" --- README.md | 4 +- lua/bento/init.lua | 120 +++++++++++++++++++++++++++++++++++++++----- lua/bento/ui.lua | 9 +--- lua/bento/utils.lua | 4 +- 4 files changed, 112 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 71ec41d..909ebd5 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ require("bento").setup({ max_open_buffers = nil, -- Max buffers (nil = unlimited) buffer_deletion_metric = "frecency_access", -- Metric for buffer deletion (see below) buffer_notify_on_delete = true, -- Notify when deleting a buffer (false for silent deletion) - ordering_metric = "access", -- Buffer ordering: nil (arbitrary), "access", or "edit" + ordering_metric = "access", -- Buffer ordering: nil (arbitrary) | "access" | "edit" | "filename" | "directory" default_action = "open", -- Action when pressing label directly ui = { @@ -212,7 +212,7 @@ require("bento").setup({ | `max_open_buffers` | number/nil | `nil` | Maximum number of buffers to keep open (`nil` = unlimited) | | `buffer_deletion_metric` | string | `"frecency_access"` | Metric used to decide which buffer to delete when limit is reached (see below) | | `buffer_notify_on_delete` | boolean | `true` | Whether to create a notification via `vim.notify` when a buffer is deleted by the plugin | -| `ordering_metric` | string/nil | `"access"` | Buffer ordering: `nil` (arbitrary), `"access"` (by last access time), or `"edit"` (by last edit time). Most recent first. | +| `ordering_metric` | string/nil | `"access"` | Buffer ordering: `nil` (arbitrary), `"access"` (by last access time, most recent first), `"edit"` (by last edit time, most recent first), `"filename"` (by filename alphabetically) or `"directory"` (by relative path alphabetically) | | `default_action` | string | `"open"` | Default action mode when menu expands | | `highlights` | table | See below | Highlight groups for all UI elements | | `actions` | table | Built-in actions | Action definitions (see Actions section) | diff --git a/lua/bento/init.lua b/lua/bento/init.lua index 4e4775e..864f268 100644 --- a/lua/bento/init.lua +++ b/lua/bento/init.lua @@ -577,21 +577,83 @@ local function get_buffer_metric_value(buf_id, metric_type) return 0 end ---- Get the ordering metric value for a buffer (used for sorting) ---- Returns higher values for more recently accessed/edited buffers ---- @param buf_id number Buffer ID ---- @return number Ordering value -function M.get_ordering_value(buf_id) - local config = M.get_config() - local ordering_metric = config.ordering_metric +--- Default stable sort comparator by buffer ID +--- @param a {buf_id: number} +--- @param b {buf_id: number} +--- @return boolean +local function sort_by_buf_id(a, b) + return a.buf_id < b.buf_id +end - if not ordering_metric then - return 0 +--- Generic comparator using a value getter function +--- Falls back to buf_id sort when values are equal +--- @param a {buf_id: number} +--- @param b {buf_id: number} +--- @param value_getter fun(buf_id: number): any +--- @param asc? boolean Sort ascending (default: false) +--- @return boolean +local function sort_by_value(a, b, value_getter, asc) + local a_val = value_getter(a.buf_id) + local b_val = value_getter(b.buf_id) + if a_val == b_val then + return sort_by_buf_id(a, b) + elseif (asc or false) then + return a_val < b_val + else + return a_val > b_val end +end - local metrics = M.buffer_metrics[buf_id] +--- Sort by filename +--- @param a {buf_id: number} +--- @param b {buf_id: number} +--- @return boolean +local function sort_buffers_by_filename(a, b) + --- Get the filename value for a buffer to compare by + --- @param buf_id number Buffer ID + --- @return string Filename + local function get_value(buf_id) + local buf_info = vim.fn.getbufinfo(buf_id)[1] + local filename = vim.fn.fnamemodify(buf_info.name or "", ":t") + return string.lower(filename) + end + return sort_by_value(a, b, get_value, true) +end + +--- Sort by directory components (path segments) +--- @param a {buf_id: number} +--- @param b {buf_id: number} +--- @return boolean +local function sort_buffers_by_directory(a, b) + --- Get the relative path segments list for a buffer + --- @param buf_id number Buffer ID + --- @return string[] Path segments + local function get_values(buf_id) + local buf_info = vim.fn.getbufinfo(buf_id)[1] + local rel_path = vim.fn.fnamemodify(buf_info.name or "", ":.") + return utils.split_path(string.lower(rel_path)) + end + local a_vals = get_values(a.buf_id) + local b_vals = get_values(b.buf_id) + local min_length = math.min(#a_vals, #b_vals) + for i = 1, min_length do + if i == min_length or a_vals[i] ~= b_vals[i] then + return a_vals[i] < b_vals[i] + end + end +end - if ordering_metric == "access" then +--- Sort by most recent access time +--- @param a {buf_id: number} +--- @param b {buf_id: number} +--- @return boolean +local function sort_buffers_by_access(a, b) + --- Get the ordering metric value for a buffer (used for sorting) + --- Returns higher values for more recently accessed buffers + --- @param buf_id number Buffer ID + --- @return number Ordering value + local function get_value(buf_id) + local metrics = M.buffer_metrics[buf_id] if metrics and #metrics.access_times > 0 then return metrics.access_times[#metrics.access_times] end @@ -600,14 +662,46 @@ function M.get_ordering_value(buf_id) return buf_info.lastused or 0 end return 0 - elseif ordering_metric == "edit" then + end + return sort_by_value(a, b, get_value) +end + +--- Sort by most recent edit time +--- @param a {buf_id: number} +--- @param b {buf_id: number} +--- @return boolean +local function sort_buffers_by_edit(a, b) + --- Get the ordering metric value for a buffer (used for sorting) + --- Returns higher values for more recently edited buffers + --- @param buf_id number Buffer ID + --- @return number Ordering value + local function get_value(buf_id) + local metrics = M.buffer_metrics[buf_id] if metrics and #metrics.edit_times > 0 then return metrics.edit_times[#metrics.edit_times] end return 0 end + return sort_by_value(a, b, get_value) +end - return 0 +--- Returns the appropriate sort comparator function based on config.ordering_metric +--- @return fun(a: {buf_id: number}, b: {buf_id: number}): boolean +function M.get_buffers_sort_function() + local config = M.get_config() + local ordering_metric = config.ordering_metric + if not ordering_metric then + return sort_by_buf_id + elseif ordering_metric == "access" then + return sort_buffers_by_access + elseif ordering_metric == "edit" then + return sort_buffers_by_edit + elseif ordering_metric == "filename" then + return sort_buffers_by_filename + elseif ordering_metric == "directory" then + return sort_buffers_by_directory + end + return sort_by_buf_id end --- Initialize marks for all valid buffers diff --git a/lua/bento/ui.lua b/lua/bento/ui.lua index e2c55ad..993cb3a 100644 --- a/lua/bento/ui.lua +++ b/lua/bento/ui.lua @@ -201,14 +201,7 @@ local function update_marks() config = bento.get_config() if config.ordering_metric then - table.sort(marks, function(a, b) - local a_val = bento.get_ordering_value(a.buf_id) - local b_val = bento.get_ordering_value(b.buf_id) - if a_val == b_val then - return a.buf_id < b.buf_id - end - return a_val > b_val - end) + table.sort(marks, bento.get_buffers_sort_function()) end end diff --git a/lua/bento/utils.lua b/lua/bento/utils.lua index 9f234e8..8516bba 100644 --- a/lua/bento/utils.lua +++ b/lua/bento/utils.lua @@ -13,7 +13,7 @@ end --- Split a path into components (directories + filename) --- @param path string File path --- @return string[] Path components -local function split_path(path) +function M.split_path(path) local components = {} for part in string.gmatch(path, "[^/\\]+") do table.insert(components, part) @@ -54,7 +54,7 @@ function M.get_display_names(paths) else local path_components = {} for _, path in ipairs(group) do - path_components[path] = split_path(path) + path_components[path] = M.split_path(path) end for _, path in ipairs(group) do