Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions lua/fff/location_utils.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
local M = {}

local set_cursor_line_mark

--- Jump to a location in the current buffer
--- @param location table|nil Location data from search results
function M.jump_to_location(location)
Expand Down Expand Up @@ -51,11 +53,12 @@ function M.highlight_location(bufnr, location, namespace)
local line_content = vim.api.nvim_buf_get_lines(bufnr, target_line - 1, target_line, false)[1] or ''
local end_col = math.min(target_col + 1, #line_content)

local line_ok, line_mark_id = set_cursor_line_mark(bufnr, namespace, target_line - 1)
if line_ok then table.insert(extmarks, { id = line_mark_id, line = target_line - 1 }) end

local ok, mark_id = pcall(vim.api.nvim_buf_set_extmark, bufnr, namespace, target_line - 1, target_col, {
end_col = end_col,
hl_group = 'IncSearch', -- inc search are better visible for a single chars
line_hl_group = 'CursorLine',
number_hl_group = 'CursorLineNr',
priority = 1000,
})

Expand All @@ -82,11 +85,12 @@ function M.highlight_location(bufnr, location, namespace)
local line_content = vim.api.nvim_buf_get_lines(bufnr, start_line - 1, start_line, false)[1] or ''
end_col = math.min(end_col, #line_content)

local line_ok, line_mark_id = set_cursor_line_mark(bufnr, namespace, start_line - 1)
if line_ok then table.insert(extmarks, { id = line_mark_id, line = start_line - 1 }) end

local ok, mark_id = pcall(vim.api.nvim_buf_set_extmark, bufnr, namespace, start_line - 1, start_col, {
end_col = end_col,
hl_group = 'IncSearch',
line_hl_group = 'CursorLine',
number_hl_group = 'CursorLineNr',
priority = 1000,
})

Expand Down Expand Up @@ -162,11 +166,7 @@ function M.highlight_grep_matches(bufnr, location, namespace)
-- line stays styled until it scrolls out of view.
if location.line then
local target_line = math.max(1, math.min(location.line, line_count))
local ok, mark_id = pcall(vim.api.nvim_buf_set_extmark, bufnr, namespace, target_line - 1, 0, {
line_hl_group = 'CursorLine',
number_hl_group = 'CursorLineNr',
priority = 999,
})
local ok, mark_id = set_cursor_line_mark(bufnr, namespace, target_line - 1)
if ok then table.insert(extmarks, { id = mark_id, line = target_line - 1 }) end
end

Expand Down Expand Up @@ -305,4 +305,21 @@ function M.format_location(location)
return ''
end

--- Pin a row with a full-width CursorLine range, above syntax but below matches
--- (`line_hl_group` bg would override every `hl_group` bg regardless of priority)
--- @param bufnr number Buffer number
--- @param namespace number Namespace for extmarks
--- @param row number 0-based row to highlight
--- @return boolean ok, number|string mark_id_or_err pcall result of nvim_buf_set_extmark
set_cursor_line_mark = function(bufnr, namespace, row)
Comment thread
cmdrrobin marked this conversation as resolved.
return pcall(vim.api.nvim_buf_set_extmark, bufnr, namespace, row, 0, {
end_row = row + 1,
end_col = 0,
hl_eol = true,
hl_group = 'CursorLine',
number_hl_group = 'CursorLineNr',
priority = 999,
})
end

return M
76 changes: 76 additions & 0 deletions tests/location_utils_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---@diagnostic disable: undefined-field, need-check-nil
local location_utils = require('fff.location_utils')

--- Collect extmarks grouped by hl_group, asserting none use line_hl_group
--- @param buf number Buffer number
--- @param ns number Namespace to read extmarks from
--- @return table<string, table[]> marks Marks keyed by hl_group with row, col and details
local function marks_by_group(buf, ns)
local by_group = {}
for _, mark in ipairs(vim.api.nvim_buf_get_extmarks(buf, ns, 0, -1, { details = true })) do
local details = mark[4]
assert.is_nil(details.line_hl_group)
if details.hl_group then
by_group[details.hl_group] = by_group[details.hl_group] or {}
table.insert(by_group[details.hl_group], { row = mark[2], col = mark[3], details = details })
end
end
return by_group
end

--- Assert a single full-width CursorLine mark on row, with lower priority than every match
--- @param marks table<string, table[]> Result of marks_by_group
--- @param match_group string Highlight group of the match marks
--- @param row number 0-based row expected to carry the CursorLine mark
local function assert_cursor_line_below(marks, match_group, row)
local cursor_line = marks.CursorLine[1]
assert.are.equal(1, #marks.CursorLine)
assert.are.equal(row, cursor_line.row)
assert.are.equal(0, cursor_line.col)
assert.are.equal(row + 1, cursor_line.details.end_row)
assert.is_true(cursor_line.details.hl_eol)
assert.are.equal('CursorLineNr', cursor_line.details.number_hl_group)

for _, match in ipairs(marks[match_group]) do
assert.is_true(match.details.priority > cursor_line.details.priority)
end
end

describe('location_utils highlight', function()
local buf, ns

before_each(function()
buf = vim.api.nvim_create_buf(false, true)
ns = vim.api.nvim_create_namespace('fff_location_utils_spec')
vim.api.nvim_buf_set_lines(buf, 0, -1, false, { 'hello world', 'abc hello x' })
end)

after_each(function() vim.api.nvim_buf_delete(buf, { force = true }) end)

it('keeps fuzzy grep matches above the pinned cursor line', function()
location_utils.highlight_location(buf, { grep_query = 'hlo', line = 2, fuzzy_match_ranges = { { 4, 9 } } }, ns)
local marks = marks_by_group(buf, ns)
local grep_hl = require('fff.conf').get().hl.grep_match or 'IncSearch'

assert.are.equal(1, #marks[grep_hl])
assert.are.equal(4, marks[grep_hl][1].col)
assert_cursor_line_below(marks, grep_hl, 1)
end)

it('keeps line:col highlight above the cursor line', function()
location_utils.highlight_location(buf, { line = 2, col = 5 }, ns)
local marks = marks_by_group(buf, ns)

assert.are.equal(4, marks.IncSearch[1].col)
assert_cursor_line_below(marks, 'IncSearch', 1)
end)

it('keeps single line range highlight above the cursor line', function()
location_utils.highlight_location(buf, { start = { line = 1, col = 1 }, ['end'] = { line = 1, col = 6 } }, ns)
local marks = marks_by_group(buf, ns)

assert.are.equal(0, marks.IncSearch[1].col)
assert.are.equal(5, marks.IncSearch[1].details.end_col)
assert_cursor_line_below(marks, 'IncSearch', 0)
end)
end)
Loading