local cfg = require('outline.config') local hover = require('outline.hover') local outline = require('outline') local M = {} local state = { preview_buf = nil, preview_win = nil, } local function is_current_win_outline() local curwin = vim.api.nvim_get_current_win() return curwin == outline.view.winnr end local function has_code_win() local isWinValid = vim.api.nvim_win_is_valid(outline.state.code_win) if not isWinValid then return false end local bufnr = vim.api.nvim_win_get_buf(outline.state.code_win) local isBufValid = vim.api.nvim_buf_is_valid(bufnr) return isBufValid end M.has_code_win = has_code_win ---Get the correct column to place the floating window based on -- Relative positions of the outline and the code window. ---@param preview_width integer local function get_col(preview_width) ---@type integer local outline_winnr = outline.view.winnr local outline_col = vim.api.nvim_win_get_position(outline_winnr)[2] local outline_width = vim.api.nvim_win_get_width(outline_winnr) local code_col = vim.api.nvim_win_get_position(outline.state.code_win)[2] -- TODO: What if code win is below/above outline instead? local col = outline_col if outline_col > code_col then col = col - preview_width - 3 else col = col + outline_width + 1 end return col end ---@param preview_height integer ---@param outline_height integer local function get_row(preview_height, outline_height) local offset = math.floor((outline_height - preview_height) / 2) - 1 return vim.api.nvim_win_get_position(outline.view.winnr)[1] + offset end local function get_height() return vim.api.nvim_win_get_height(outline.view.winnr) end local function get_hovered_node() local hovered_line = vim.api.nvim_win_get_cursor(outline.view.winnr)[1] local node = outline.state.flattened_outline_items[hovered_line] return node end local function update_preview(code_buf) code_buf = code_buf or vim.api.nvim_win_get_buf(outline.state.code_win) local node = get_hovered_node() if not node then return end local lines = vim.api.nvim_buf_get_lines(code_buf, 0, -1, false) if state.preview_buf ~= nil then vim.api.nvim_buf_set_lines(state.preview_buf, 0, -1, false, lines) vim.api.nvim_win_set_cursor(state.preview_win, { node.line + 1, node.character }) end end local function setup_preview_buf() local code_buf = vim.api.nvim_win_get_buf(outline.state.code_win) local ft = vim.api.nvim_buf_get_option(code_buf, 'filetype') vim.api.nvim_buf_set_option(state.preview_buf, 'syntax', ft) local ts_highlight_fn = vim.treesitter.start if not _G._outline_nvim_has[8] then local ok, ts_highlight = pcall(require, 'nvim-treesitter.highlight') if ok then ts_highlight_fn = ts_highlight.attach end end pcall(ts_highlight_fn, state.preview_buf, ft) vim.api.nvim_buf_set_option(state.preview_buf, 'bufhidden', 'delete') vim.api.nvim_win_set_option(state.preview_win, 'cursorline', true) update_preview(code_buf) end local function set_bg_hl() vim.api.nvim_win_set_option(state.preview_win, 'winhl', cfg.o.preview_window.winhl) vim.api.nvim_win_set_option(state.preview_win, 'winblend', cfg.o.preview_window.winblend) end local function show_preview() if state.preview_win == nil and state.preview_buf == nil then state.preview_buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_attach(state.preview_buf, false, { on_detach = function() state.preview_buf = nil state.preview_win = nil end, }) local height = get_height() local width = cfg.get_preview_width() local winheight = math.max(math.ceil(height / 2), cfg.o.preview_window.min_height) state.preview_win = vim.api.nvim_open_win(state.preview_buf, false, { relative = 'editor', height = winheight, width = width, bufpos = { 0, 0 }, col = get_col(width), -- Position preview window middle-aligned vertically row = get_row(winheight, height), border = cfg.o.preview_window.border, }) setup_preview_buf() else update_preview() end end function M.show() if not is_current_win_outline() or #vim.api.nvim_list_wins() < 2 then return end show_preview() set_bg_hl() if cfg.o.preview_window.open_hover_on_preview then hover.show_hover() end end function M.close() if has_code_win() then if state.preview_win ~= nil and vim.api.nvim_win_is_valid(state.preview_win) then vim.api.nvim_win_close(state.preview_win, true) end if state.hover_win ~= nil and vim.api.nvim_win_is_valid(state.hover_win) then vim.api.nvim_win_close(state.hover_win, true) end end end function M.toggle() if state.preview_win ~= nil then M.close() else M.show() end end return M