diff --git a/lua/outline/config.lua b/lua/outline/config.lua index ab827ff..b103124 100644 --- a/lua/outline/config.lua +++ b/lua/outline/config.lua @@ -154,20 +154,18 @@ end function M.get_window_width() if M.o.outline_window.relative_width then return math.ceil(vim.o.columns * (M.o.outline_window.width / 100)) - else - return M.o.outline_window.width end + return M.o.outline_window.width end function M.get_preview_width() if M.o.preview_window.relative_width then - local relative_width = math.ceil(vim.o.columns * (M.o.preview_window.width / 100)) + local relative_width = math.max( + math.ceil(vim.o.columns * (M.o.preview_window.width / 100)), + M.o.preview_window.min_width + ) - if relative_width < M.o.preview_window.min_width then - return M.o.preview_window.min_width - else - return relative_width - end + return relative_width else return M.o.preview_window.width end @@ -332,6 +330,10 @@ function M.resolve_config() M.o.keymaps[action] = { keys } end end + ----- PREVIEW ----- + M.o.preview_window.width = M.get_preview_width() + ----- WINDOW ----- + M.o.outline_window.width = M.get_window_width() end ---Ensure l is either table, false, or nil. If not, print warning using given diff --git a/lua/outline/init.lua b/lua/outline/init.lua index 4998537..2bc20a8 100644 --- a/lua/outline/init.lua +++ b/lua/outline/init.lua @@ -279,7 +279,7 @@ function M.show_status() if not sidebar then ctx.code_win_active = true else - ctx.code_win_active = require('outline.preview').has_code_win(win) + ctx.code_win_active = sidebar:has_code_win() end end diff --git a/lua/outline/preview.lua b/lua/outline/preview.lua index 14bb911..2054e2e 100644 --- a/lua/outline/preview.lua +++ b/lua/outline/preview.lua @@ -1,88 +1,67 @@ local cfg = require('outline.config') -local hover = require('outline.hover') -local outline = require('outline') +local providers = require('outline.providers') -local M = {} +local conf -local state = { - preview_buf = nil, - preview_win = nil, -} +---@class outline.Preview +local Preview = {} -local function is_current_win_outline() - local curwin = vim.api.nvim_get_current_win() - return curwin == outline.current.view.win +---@class outline.Preview +---@field buf integer +---@field win integer +---@field width integer +---@field height integer +---@field outline_height integer +---@field s outline.Sidebar + +---@param s outline.Sidebar +function Preview:new(s) + -- Config must have been setup when calling Preview:new + conf = cfg.o.preview_window + return setmetatable({ + buf = nil, + win = nil, + s = s, + width = nil, + height = nil, + }, { __index = Preview }) end -local function has_code_win(winnr) - if not outline.current then - return false - end - winnr = winnr or outline.current.code.win - return vim.api.nvim_win_is_valid(winnr) and vim.api.nvim_buf_is_valid(outline.current.code.buf) +---Creates new preview window and sets the content. Calls setup and set_lines. +function Preview:create() + self.buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_attach(self.buf, false, { + on_detach = function() + self.buf = nil + self.win = nil + end, + }) + -- FIXME: Re-calculate dimensions on update-preview, in case outline window + -- was resized between last preview and next preview? + self.outline_height = vim.api.nvim_win_get_height(self.s.view.win) + self.width = conf.width + self.height = math.max(math.ceil(self.outline_height / 2), conf.min_height) + self.win = vim.api.nvim_open_win(self.buf, false, { + relative = 'editor', + height = self.height, + width = self.width, + bufpos = { 0, 0 }, + col = self:calc_col(), + row = self:calc_row(), + border = conf.border, + }) + self:setup() + self:set_lines() end -M.has_code_win = has_code_win +---Set up highlights, window, and buffer options +function Preview:setup() + vim.api.nvim_win_set_option(self.win, 'winhl', conf.winhl) + vim.api.nvim_win_set_option(self.win, 'winblend', conf.winblend) ----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.current.view.win - 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.current.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.current.view.win)[1] + offset -end - -local function get_height() - return vim.api.nvim_win_get_height(outline.current.view.win) -end - -local function get_hovered_node() - local hovered_line = vim.api.nvim_win_get_cursor(outline.current.view.win)[1] - local node = outline.current.flats[hovered_line] - return node -end - -local function update_preview(code_buf) - code_buf = code_buf or outline.current.code.buf - - 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 = outline.current.code.buf + local code_buf = self.s.code.buf local ft = vim.api.nvim_buf_get_option(code_buf, 'filetype') - - vim.api.nvim_buf_set_option(state.preview_buf, 'syntax', ft) + vim.api.nvim_buf_set_option(self.buf, 'syntax', ft) local ts_highlight_fn = vim.treesitter.start if not _G._outline_nvim_has[8] then @@ -91,75 +70,95 @@ local function setup_preview_buf() ts_highlight_fn = ts_highlight.attach end end - pcall(ts_highlight_fn, state.preview_buf, ft) + pcall(ts_highlight_fn, self.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) + vim.api.nvim_buf_set_option(self.buf, 'bufhidden', 'delete') + vim.api.nvim_win_set_option(self.win, 'cursorline', true) + vim.api.nvim_buf_set_option(self.buf, 'modifiable', false) 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 +---Get the correct column to place the floating window based on relative +---positions of the outline and the code window. +function Preview:calc_col() + ---@type integer + local outline_winnr = self.s.view.win + 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(self.s.code.win)[2] -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() + -- TODO: What if code win is below/above outline instead? + + local col = outline_col + if outline_col > code_col then + col = col - self.width - 3 else - update_preview() + col = col + outline_width + 1 + end + + return col +end + +---Get the vertically center-aligned row for preview window +function Preview:calc_row() + local offset = math.floor((self.outline_height - self.height) / 2) - 1 + return vim.api.nvim_win_get_position(self.s.view.win)[1] + offset +end + +---Set and update preview buffer content +function Preview:set_lines() + -- TODO: Editable, savable buffer in the preview like VS Code for quick + -- edits? It can be like LSP. Trigger preview to open, trigger again to focus + -- (so buffer can be edited). + -- This can be achieved by simply opening the buffer from inside the preview + -- window. + -- This also removes the need of manually setting highlights, treesitter etc. + -- The preview window will look exactly the same as in the code window. + local node = self.s:_current_node() + if not node then + return + end + local lines = vim.api.nvim_buf_get_lines(self.s.code.buf, 0, -1, false) + + if self.buf ~= nil then + vim.api.nvim_buf_set_option(self.buf, 'modifiable', true) + vim.api.nvim_buf_set_lines(self.buf, 0, -1, false, lines) + vim.api.nvim_buf_set_option(self.buf, 'modifiable', false) + vim.api.nvim_win_set_cursor(self.win, { node.line + 1, node.character }) end end -function M.show() - if not is_current_win_outline() or #vim.api.nvim_list_wins() < 2 then +---Create or update preview +function Preview:show() + if not self.s:has_focus() 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() + if self.buf and self.win then + self:set_lines() else - M.show() + self:create() + end + + if conf.open_hover_on_preview then + providers.action(self.s, 'show_hover', { self.s }) end end -return M +function Preview:close() + -- TODO: Why was this in symbols-outline.nvim? + -- if self.s:has_code_win() then + if self.win ~= nil and vim.api.nvim_win_is_valid(self.win) then + vim.api.nvim_win_close(self.win, true) + end + -- end +end + +function Preview:toggle() + if self.win ~= nil then + self:close() + else + self:show() + end +end + +return Preview diff --git a/lua/outline/sidebar.lua b/lua/outline/sidebar.lua index e9ae074..192bf43 100644 --- a/lua/outline/sidebar.lua +++ b/lua/outline/sidebar.lua @@ -1,4 +1,5 @@ local View = require('outline.view') +local Preview = require('outline.preview') local cfg = require('outline.config') local folding = require('outline.folding') local parser = require('outline.parser') @@ -24,10 +25,12 @@ local Sidebar = {} ---@field code outline.SidebarCodeState ---@field autocmds { [integer]: integer } winnr to autocmd id ---@field provider outline.Provider? +---@field preview outline.Preview function Sidebar:new() return setmetatable({ view = View:new(), + preview = Preview:new(), code = { buf = 0, win = 0 }, items = {}, flats = {}, @@ -136,7 +139,7 @@ function Sidebar:setup_keymaps() goto_and_close = { '_goto_and_close', {} }, down_and_jump = { '_move_and_jump', { 'down' } }, up_and_jump = { '_move_and_jump', { 'up' } }, - toggle_preview = { require('outline.preview').toggle, {} }, + toggle_preview = { function() self.preview:toggle() end, {} }, fold_toggle = { '_toggle_fold', {} }, fold = { '_set_folded', { true } }, unfold = { '_set_folded', { false } }, @@ -163,12 +166,12 @@ function Sidebar:setup_buffer_autocmd() if cfg.o.preview_window.auto_preview then vim.api.nvim_create_autocmd('CursorMoved', { buffer = 0, - callback = require('outline.preview').show, + callback = function() self.preview:show() end, }) else vim.api.nvim_create_autocmd('CursorMoved', { buffer = 0, - callback = require('outline.preview').close, + callback = function() self.preview:close() end, }) end if cfg.o.outline_window.auto_jump then @@ -465,6 +468,10 @@ function Sidebar:_set_all_folded(folded, nodes) self:_update_lines(true, current) end +function Sidebar:has_code_win() + return self.code.win and self.code.buf and self.code.win ~= 0 and self.code.buf ~= 0 and vim.api.nvim_win_is_valid(self.code.win) and vim.api.nvim_buf_is_valid(self.code.buf) +end + ---@see outline.follow_cursor ---@param opts outline.OutlineOpts? ---@return boolean ok @@ -473,7 +480,7 @@ function Sidebar:follow_cursor(opts) return false end - if require('outline.preview').has_code_win(self.code.win) then + if self:has_code_win() then self:_highlight_current_item(self.code.win, true) else return false @@ -516,6 +523,7 @@ function Sidebar:open(opts) end if not self.view:is_open() then + self.preview.s = self self.provider = providers.find_provider() if not self.provider then utils.echo('No providers found for current buffer') @@ -531,6 +539,7 @@ end function Sidebar:close() local code_win = self.code.win self.view:close() + self.preview:close() vim.fn.win_gotoid(code_win) end @@ -547,7 +556,7 @@ end ---@see outline.focus_code ---@return boolean ok function Sidebar:focus_code() - if require('outline.preview').has_code_win(self.code.win) then + if self:has_code_win() then vim.fn.win_gotoid(self.code.win) return true end @@ -557,7 +566,7 @@ end ---@see outline.focus_toggle ---@return boolean ok function Sidebar:focus_toggle() - if self.view:is_open() and require('outline.preview').has_code_win(self.code.win) then + if self.view:is_open() and self:has_code_win() then local winid = vim.fn.win_getid() if winid == self.code.win then vim.fn.win_gotoid(self.view.win) diff --git a/lua/outline/view.lua b/lua/outline/view.lua index 7d73f4a..6067e77 100644 --- a/lua/outline/view.lua +++ b/lua/outline/view.lua @@ -24,7 +24,7 @@ function View:setup_view(split_command) vim.cmd(split_command) -- resize to a % of the current window size - vim.cmd('vertical resize ' .. cfg.get_window_width()) + vim.cmd('vertical resize ' .. cfg.o.outline_window.width) -- get current (outline) window and attach our buffer to it self.win = vim.api.nvim_get_current_win()