Skip to content
Open
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
173 changes: 171 additions & 2 deletions lua/lean/infoview.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ local log = require 'lean.log'
local progress = require 'lean.progress'
local rpc = require 'lean.rpc'

local wrap_sectioned_content
local contents_for_interactive_sectioned

---Convert a buffer position to a human-readable (1, 1)-indexed string.
---Takes the workspace into account in order to return a relative path.
---@param buffer Buffer
Expand Down Expand Up @@ -172,6 +175,7 @@ vim.api.nvim_create_autocmd('LspAttach', {
---@field show_let_values boolean show let-value bodies
---@field show_term_goals boolean show expected types?
---@field reverse boolean order hypotheses bottom-to-top
---@field sectioned_display boolean display sections with headers (goals, widgets, diagnostics)

---An individual pin.
---@class Pin
Expand Down Expand Up @@ -310,6 +314,22 @@ function Infoview:__setup_pin_keymaps(buffer)
'[s',
'Move to the previous suggestion.',
},
{
'HistoryBack',
function()
self.pin:history_back()
end,
'[H',
'Go back in history',
},
{
'HistoryForward',
function()
self.pin:history_forward()
end,
']H',
'Go forward in history',
},
{
'NextLink',
function()
Expand Down Expand Up @@ -833,7 +853,12 @@ function Infoview:render_contents(params, sw)
if vim.b[bufnr].lean_lsp_died then
return components.LSP_HAS_DIED
end
return self.__contents_for(params, self.view_options, sw)
if self.view_options.sectioned_display then
local sections = contents_for_interactive_sectioned(params, self.view_options, sw)
return wrap_sectioned_content(sections, params)
else
return self.__contents_for(params, self.view_options, sw)
end
end

---Wait until the infoview has finished processing.
Expand Down Expand Up @@ -1273,6 +1298,8 @@ function Pin:new(obj)
__renderer = pin_renderer,
__tick = 0,
__instrumentation = Instrumentation:new(),
history = {},
history_index = 0,
buffer = pin_buffer,
}),
self
Expand Down Expand Up @@ -1621,6 +1648,26 @@ function Pin:move(buffer, pos)
self:request_update()
end

function Pin:history_back()
if self.history_index > 1 then
self.history_index = self.history_index - 1
self.__data_element = self.history[self.history_index]
self.__element:set_children { self.__data_element }
self:render()
vim.cmd 'redraw'
end
end

function Pin:history_forward()
if self.history_index < #self.history then
self.history_index = self.history_index + 1
self.__data_element = self.history[self.history_index]
self.__element:set_children { self.__data_element }
self:render()
vim.cmd 'redraw'
end
end

---Wrap content blocks with a clear_all event handler.
---
---@param blocks Element[]
Expand All @@ -1638,6 +1685,10 @@ local function wrap_content_blocks(blocks, params)
end
end

---Wrap a table of named sections with headers and foldable behaviour.
---@param sections table<string, Element>
---@param params lsp.TextDocumentPositionParams
---@return Element
local new_data_element
new_data_element = Element:concat(blocks, '\n\n', {
---@type EventCallbacks
Expand Down Expand Up @@ -1690,6 +1741,91 @@ local function contents_for_processing(params)
end
end

wrap_sectioned_content = function(sections, params)
if vim.tbl_isempty(sections) then
local bufnr = vim.uri_to_bufnr(params.textDocument.uri)
if vim.tbl_isempty(vim.lsp.get_clients { bufnr = bufnr, name = 'leanls' }) then
return components.LSP_HAS_DIED
elseif options.show_no_info_message then
return components.NO_INFO
else
return components.EMPTY
end
end

local section_titles = {
goals = 'Goals',
term_goals = 'Expected Type Goals',
user_widgets = 'User Widgets',
diagnostics = 'Diagnostics',
processing = 'Processing…',
}

local children = {}
for name, content in pairs(sections) do
if content ~= Element.EMPTY then
local title = section_titles[name] or name
local header = Element:new {
name = 'section-header',
text = '── ' .. title .. ' ──',
hlgroups = { 'Title' },
}
-- No folding: just concatenate header and content with a newline
local section_element = Element:concat({ header, content }, '\n')
table.insert(children, section_element)
end
end
return Element:concat(children, '\n\n') or Element.EMPTY
end
contents_for_interactive_sectioned = function(params, view_options, sw)
local processing_element = contents_for_processing(params)
if processing_element then
return { processing = processing_element }
end

local sess = sw:time('rpc_open', rpc.open, params)

local phases = {}
table.insert(phases, {
'goals',
function()
return components.goal_at(sess, view_options)
end,
})
if view_options.show_term_goals then
table.insert(phases, {
'term_goals',
function()
return components.term_goal_at(sess, view_options)
end,
})
end
table.insert(phases, {
'user_widgets',
function()
return components.user_widgets_at(sess)
end,
})
table.insert(phases, {
'diagnostics',
function()
return components.diagnostics_at(sess)
end,
})

local results = sw:concurrent('rpcs', phases)

local sections = {}
for i, result in ipairs(results) do
local blocks = result[1] or {}
if #blocks > 0 then
local name = phases[i][1]
sections[name] = Element:concat(blocks, '\n\n') or Element.EMPTY
end
end
return sections
end

---Render the combined contents of the infoview using interactive widgets.
---
---The component RPC calls (goals, term goals, user widgets, diagnostics)
Expand Down Expand Up @@ -1753,7 +1889,7 @@ end
---@param view_options InfoviewViewOptions
---@param sw std.Stopwatch
---@return Element
function contents_for_plain(params, view_options, sw)
local function contents_for_plain(params, view_options, sw)
local element = contents_for_processing(params)
if element then
return element
Expand Down Expand Up @@ -1817,6 +1953,17 @@ function Pin:update()

sw:open 'commit'
self.loading = false

if self.__data_element ~= Element.EMPTY then
if #self.history == 0 or self.history[#self.history] ~= self.__data_element then
if #self.history >= 30 then
table.remove(self.history, 1)
end
table.insert(self.history, self.__data_element)
self.history_index = #self.history
end
end

-- Carry over user-toggled foldable state so a refresh doesn't snap
-- expanded traces (etc.) back to their server-default collapsed state.
-- The previous element is read here (not before `render_contents` yields)
Expand Down Expand Up @@ -2261,4 +2408,26 @@ function infoview.contents_at(position, opts)
return result
end

---Go back in the history of the current pin.
function infoview.history_back()
with_current(function(iv)
iv.pin:history_back()
end)
end

---Go forward in the history of the current pin.
function infoview.history_forward()
with_current(function(iv)
iv.pin:history_forward()
end)
end

---Toggle sectioned display for the current infoview.
function infoview.toggle_sectioned_display()
with_current(function(iv)
iv.view_options.sectioned_display = not iv.view_options.sectioned_display
iv.pin:update()
end)
end

return infoview
Loading