From 977bed4c49c0f974861e3e8e6f7ecb70c3cb1d2f Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 10 Jul 2024 21:31:32 +0200 Subject: [PATCH 1/5] feat: docs view --- docs/config.norg | 4 +- lua/neocomplete/config.lua | 3 +- lua/neocomplete/init.lua | 6 +++ lua/neocomplete/menu/docs_view.lua | 76 +++++++++++++++++++++++++++ lua/neocomplete/menu/init.lua | 27 ++++++++++ lua/neocomplete/menu/window.lua | 26 +++++++++ lua/neocomplete/types/config.lua | 2 + lua/neocomplete/types/docs_view.lua | 13 +++++ lua/neocomplete/types/menu_window.lua | 8 +++ 9 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 lua/neocomplete/menu/docs_view.lua create mode 100644 lua/neocomplete/types/docs_view.lua diff --git a/docs/config.norg b/docs/config.norg index 09c21f3..e3a9bce 100644 --- a/docs/config.norg +++ b/docs/config.norg @@ -16,7 +16,7 @@ tangle: { delimiter: none } created: 2023-11-17T16:29:17+0100 -updated: 2024-06-22T17:04:13+0100 +updated: 2024-07-13T10:21:09+0100 version: 1.1.1 @end @@ -201,6 +201,8 @@ version: 1.1.1 @code lua --- Maximum height of the documentation view ---@field max_height integer + --- Maximum width of the documentation view + ---@field max_width integer --- The border of the documentation view ---@field border string|string[]|string[][] --- Character used for the scrollbar diff --git a/lua/neocomplete/config.lua b/lua/neocomplete/config.lua index dbc9ca2..30cd7b3 100644 --- a/lua/neocomplete/config.lua +++ b/lua/neocomplete/config.lua @@ -31,7 +31,8 @@ config.defaults = { alignment = {}, }, docs_view = { - max_height = 7, + max_height = 8, + max_width = 80, border = "rounded", scrollbar = "█", }, diff --git a/lua/neocomplete/init.lua b/lua/neocomplete/init.lua index ef434a4..b3eb62f 100644 --- a/lua/neocomplete/init.lua +++ b/lua/neocomplete/init.lua @@ -35,6 +35,12 @@ neocomplete.api = { jump_to_entry = function(index) neocomplete.core.menu.index = index end, + doc_is_open = function() + return neocomplete.core and neocomplete.core.menu and neocomplete.core.menu:docs_visible() + end, + scroll_docs = function(delta) + neocomplete.core.menu:scroll_docs(delta) + end, } --- Sets up neocomplete diff --git a/lua/neocomplete/menu/docs_view.lua b/lua/neocomplete/menu/docs_view.lua new file mode 100644 index 0000000..1bfdda6 --- /dev/null +++ b/lua/neocomplete/menu/docs_view.lua @@ -0,0 +1,76 @@ +---@type neocomplete.docs_view +---@diagnostic disable-next-line: missing-fields +local Docs = {} + +function Docs.new(entry, x_offset, position, config) + ---@type neocomplete.docs_view + local self = setmetatable({}, { __index = Docs }) + self.current_scroll = 1 + if not entry.completion_item.documentation then + return nil + end + local documentation = entry.completion_item.documentation + local format = "markdown" + local contents + if type(documentation) == "table" and documentation.kind == "plaintext" then + format = "plaintext" + contents = vim.split(documentation.value or "", "\n", { trimempty = true }) + else + contents = vim.lsp.util.convert_input_to_markdown_lines(documentation --[[@as string]]) + end + + local TODO = 100000 + local width = math.min(vim.o.columns - x_offset, config.max_width) + local height = math.min(TODO, config.max_height) + self.bufnr = vim.api.nvim_create_buf(false, true) + + local do_stylize = format == "markdown" and vim.g.syntax_on ~= nil + + if do_stylize then + contents = vim.lsp.util._normalize_markdown(contents, { width = width }) + vim.bo[self.bufnr].filetype = "markdown" + vim.treesitter.start(self.bufnr) + vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, contents) + else + -- Clean up input: trim empty lines + contents = vim.split(table.concat(contents, "\n"), "\n", { trimempty = true }) + + if format then + vim.bo[self.bufnr].syntax = format + end + vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, true, contents) + end + + self.winnr = vim.api.nvim_open_win(self.bufnr, false, { + relative = "cursor", + anchor = position == "below" and "NW" or "SW", + border = config.border, + style = "minimal", + width = width, + height = math.min(height, #contents), + row = position == "below" and 1 or 0, + col = x_offset, + }) + + vim.api.nvim_set_option_value("scrolloff", 0, { win = self.winnr }) + + return self +end + +function Docs:scroll(delta) + self.current_scroll = self.current_scroll + delta + local top_visible = vim.fn.line("w0", self.winnr) + local bottom_visible = vim.fn.line("w$", self.winnr) + local visible_amount = bottom_visible - top_visible + 1 + self.current_scroll = + math.min(self.current_scroll, #vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) - visible_amount - 1) + self:set_scroll(self.current_scroll) +end + +function Docs:set_scroll(line) + vim.api.nvim_win_call(self.winnr, function() + vim.cmd("normal! " .. line .. "zt") + end) +end + +return Docs diff --git a/lua/neocomplete/menu/init.lua b/lua/neocomplete/menu/init.lua index 76bc703..7028644 100644 --- a/lua/neocomplete/menu/init.lua +++ b/lua/neocomplete/menu/init.lua @@ -28,6 +28,31 @@ function Menu.close(self) require("neocomplete.ghost_text").hide() end +local function draw_docs(menu, entry, config) + if not entry then + return + end + if entry.source.source.resolve_item then + entry.source.source:resolve_item(entry.completion_item, function(resolved_item) + entry.completion_item = resolved_item + menu.window:open_docs_view(entry, config) + end) + else + menu.window:open_docs_view(entry, config) + end +end + +function Menu:docs_visible() + return self.window and self.window.docs_view ~= nil +end + +function Menu:scroll_docs(delta) + if not self:docs_visible() then + return + end + self.window.docs_view:scroll(delta) +end + function Menu:select_next(count) count = count or 1 self.index = self.index + count @@ -35,6 +60,7 @@ function Menu:select_next(count) self.index = self.index - #self.entries - 1 end self.window:set_scroll(self.index, 1) + draw_docs(self, self:get_active_entry(), self.config.ui.docs_view) self:draw() end @@ -45,6 +71,7 @@ function Menu:select_prev(count) self.index = #self.entries + self.index + 1 end self.window:set_scroll(self.index, -1) + draw_docs(self, self:get_active_entry(), self.config.ui.docs_view) self:draw() end diff --git a/lua/neocomplete/menu/window.lua b/lua/neocomplete/menu/window.lua index 5ee5dc7..6e8d266 100644 --- a/lua/neocomplete/menu/window.lua +++ b/lua/neocomplete/menu/window.lua @@ -10,7 +10,9 @@ function Menu_window.new(buf, scrollbar_buf) self.config = require("neocomplete.config").options self.buf = buf self.position = nil + self.opened_at = {} self.scrollbar = {} + self.docs_view = nil self.scrollbar.win = nil self.max_height = nil self.scrollbar.buf = scrollbar_buf @@ -50,6 +52,10 @@ function Menu_window:open_win(entries, offset) end self.max_height = height self.position = position + self.opened_at = { + row = cursor[1] - 1, + col = cursor[2] - offset, + } self.winnr = vim.api.nvim_open_win(self.buf, false, { relative = "cursor", height = height, @@ -65,6 +71,26 @@ function Menu_window:open_win(entries, offset) self:open_scrollbar_win(width, height, offset) end +function Menu_window:open_docs_view(entry, config) + pcall(function() + vim.api.nvim_win_close(self.docs_view.winnr, true) + end) + self.docs_view = require("neocomplete.menu.docs_view").new( + entry, + self.opened_at.col + vim.api.nvim_win_get_width(self.winnr) - (vim.api.nvim_win_get_cursor(0)[2] - 1) + 1, + self.position, + config + ) + vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI", "InsertCharPre", "InsertLeave" }, { + callback = function() + pcall(function() + vim.api.nvim_win_close(self.docs_view.winnr, true) + end) + self.docs_view = nil + end, + }) +end + function Menu_window:readjust(entries, offset) if not entries or #entries < 1 then self:close() diff --git a/lua/neocomplete/types/config.lua b/lua/neocomplete/types/config.lua index b904065..3e37ad8 100644 --- a/lua/neocomplete/types/config.lua +++ b/lua/neocomplete/types/config.lua @@ -38,6 +38,8 @@ ---@class neocomplete.config.ui.docs --- Maximum height of the documentation view ---@field max_height integer +--- Maximum width of the documentation view +---@field max_width integer --- The border of the documentation view ---@field border string|string[]|string[][] --- Character used for the scrollbar diff --git a/lua/neocomplete/types/docs_view.lua b/lua/neocomplete/types/docs_view.lua new file mode 100644 index 0000000..1937cd6 --- /dev/null +++ b/lua/neocomplete/types/docs_view.lua @@ -0,0 +1,13 @@ +---@class neocomplete.docs_view +--- Winnr of the docs window +---@field winnr? integer +--- The current top line of the window +---@field current_scroll integer +--- Creates a new view +---@field new fun(entry: neocomplete.entry, x_offset: integer, position: "above"|"below", config: neocomplete.config.ui.docs): neocomplete.docs_view? +--- Buffer used for view +---@field bufnr integer +--- Scroll the window up or down +---@field scroll fun(self: neocomplete.docs_view, delta: integer) +--- Sets the scroll to a certain line +---@field set_scroll fun(self: neocomplete.docs_view, line: integer) diff --git a/lua/neocomplete/types/menu_window.lua b/lua/neocomplete/types/menu_window.lua index f05ad27..bf26f08 100644 --- a/lua/neocomplete/types/menu_window.lua +++ b/lua/neocomplete/types/menu_window.lua @@ -25,3 +25,11 @@ ---@field set_scroll fun(self: neocomplete.menu_window, index: integer, direction: integer): nil --- Opens a new main window ---@field open_win fun(self: neocomplete.menu_window, entries: neocomplete.entry[], offset: integer): nil +--- Change scroll of window +---@field scroll table +--- Where the window was last opened +---@field opened_at {row: integer, col: integer} +--- Instance of documentation view +---@field docs_view neocomplete.docs_view +--- Opens a new docs view +---@field open_docs_view fun(self: neocomplete.menu_window, entry: neocomplete.entry, config: neocomplete.config.ui.docs): nil From 959d5acbeeee56c53f2e6ae4427e402891948566 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 13 Jul 2024 18:37:36 +0200 Subject: [PATCH 2/5] feat: extract window class to utils --- docs/menu.norg | 14 +- lua/neocomplete/menu/draw.lua | 36 +---- lua/neocomplete/menu/init.lua | 100 ++++++++++--- lua/neocomplete/types/menu.lua | 4 +- lua/neocomplete/types/menu_window.lua | 35 ----- lua/neocomplete/types/window.lua | 41 ++++++ lua/neocomplete/{menu => utils}/window.lua | 159 +++++++++++---------- 7 files changed, 232 insertions(+), 157 deletions(-) delete mode 100644 lua/neocomplete/types/menu_window.lua create mode 100644 lua/neocomplete/types/window.lua rename lua/neocomplete/{menu => utils}/window.lua (62%) diff --git a/docs/menu.norg b/docs/menu.norg index 6ed462e..d86e52b 100644 --- a/docs/menu.norg +++ b/docs/menu.norg @@ -9,7 +9,7 @@ categories: [ types ] created: 2024-05-29T11:30:25+0100 -updated: 2024-07-10T19:00:55+0100 +updated: 2024-07-13T21:57:35+0100 tangle: { languages: { lua: ../lua/neocomplete/types/menu.lua @@ -135,12 +135,20 @@ version: 1.1.1 @end * Fields -** Window +** Menu Window #tangle @code lua --- Wrapper for utilities for the window of the menu - ---@field window neocomplete.menu_window + ---@field menu_window neocomplete.window + @end + +** Docs Window + #tangle + @code lua + --- Wrapper for utilities for the window of the docs + ---@field docs_window neocomplete.window @end + ** Entries This field is used to store all the entries of the completion menu. #tangle diff --git a/lua/neocomplete/menu/draw.lua b/lua/neocomplete/menu/draw.lua index f5a62a4..f1f3954 100644 --- a/lua/neocomplete/menu/draw.lua +++ b/lua/neocomplete/menu/draw.lua @@ -39,17 +39,16 @@ return function(self) local width, entry_texts = format_utils.get_width(self.entries) local aligned_table = format_utils.get_align_tables(self.entries) local column = 0 - vim.api.nvim_buf_clear_namespace(self.buf, self.ns, 0, -1) - vim.api.nvim_buf_clear_namespace(self.scrollbar_buf, self.ns, 0, -1) + vim.api.nvim_buf_clear_namespace(self.menu_window.buf, self.ns, 0, -1) local spaces = {} for _ = 1, #self.entries do table.insert(spaces, (" "):rep(width)) end - vim.api.nvim_buf_set_lines(self.buf, 0, -1, false, spaces) + vim.api.nvim_buf_set_lines(self.menu_window.buf, 0, -1, false, spaces) if self.index and self.index > 0 then for i = 0, #self.entries do if i == self.index then - vim.api.nvim_buf_set_extmark(self.buf, self.ns, i - 1, 0, { + vim.api.nvim_buf_set_extmark(self.menu_window.buf, self.ns, i - 1, 0, { virt_text = { { string.rep(" ", width), "@neocomplete.selected" } }, virt_text_pos = "overlay", }) @@ -66,7 +65,7 @@ return function(self) end local cur_line_text = table.concat(line_text, "") table.insert(texts, cur_line_text) - vim.api.nvim_buf_set_extmark(self.buf, self.ns, line - 1, column, { + vim.api.nvim_buf_set_extmark(self.menu_window.buf, self.ns, line - 1, column, { virt_text = aligned_chunks, virt_text_pos = "overlay", hl_mode = "combine", @@ -78,44 +77,23 @@ return function(self) local length = utils.longest(texts) add_extmarks(aligned_sec, function(chunk) return { string.rep(" ", length - #chunk[1]) .. chunk[1], chunk[2] } - end, self.buf, self.ns, column) + end, self.menu_window.buf, self.ns, column) column = column + length elseif alignment[i] == "center" then local texts = get_texts(aligned_sec) local length = utils.longest(texts) add_extmarks(aligned_sec, function(chunk) return { string.rep(" ", math.floor((length - #chunk[1]) / 2)) .. chunk[1], chunk[2] } - end, self.buf, self.ns, column) + end, self.menu_window.buf, self.ns, column) column = column + length end end for line, entry in ipairs(self.entries) do for _, idx in ipairs(entry.matches or {}) do - vim.api.nvim_buf_add_highlight(self.buf, self.ns, "@neocomplete.match", line - 1, idx - 1, idx) + vim.api.nvim_buf_add_highlight(self.menu_window.buf, self.ns, "@neocomplete.match", line - 1, idx - 1, idx) end end - if not self.window.scrollbar_is_open then - return - end - - local top_visible = vim.fn.line("w0", self.window.winnr) - local bottom_visible = vim.fn.line("w$", self.window.winnr) - local visible_entries = bottom_visible - top_visible + 1 - if visible_entries >= #self.entries then - return - end - local scrollbar_height = - math.max(math.min(math.floor(visible_entries * (visible_entries / #self.entries) + 0.5), visible_entries), 1) - vim.api.nvim_buf_set_lines(self.scrollbar_buf, 0, -1, false, vim.split(string.rep(" ", visible_entries + 1), "")) - local scrollbar_offset = math.max(math.floor(visible_entries * (top_visible / #self.entries)), 1) - for i = scrollbar_offset, scrollbar_offset + scrollbar_height do - vim.api.nvim_buf_set_extmark(self.scrollbar_buf, self.ns, i - 1, 0, { - virt_text = { { self.config.ui.menu.scrollbar, "PmenuSbar" } }, - virt_text_pos = "overlay", - }) - end - local line = vim.api.nvim_get_current_line() local cursor = vim.api.nvim_win_get_cursor(0) local cursor_col = cursor[2] diff --git a/lua/neocomplete/menu/init.lua b/lua/neocomplete/menu/init.lua index 7028644..621f311 100644 --- a/lua/neocomplete/menu/init.lua +++ b/lua/neocomplete/menu/init.lua @@ -2,55 +2,118 @@ ---@diagnostic disable-next-line: missing-fields local Menu = {} +local format_utils = require("neocomplete.utils.format") + function Menu.new() ---@type neocomplete.menu local self = setmetatable({}, { __index = Menu }) self.entries = nil self.ns = vim.api.nvim_create_namespace("neocomplete") self.config = require("neocomplete.config").options - self.buf = vim.api.nvim_create_buf(false, true) self.index = 0 - ---@diagnostic disable-next-line: missing-fields - self.scrollbar_buf = vim.api.nvim_create_buf(false, true) - self.window = require("neocomplete.menu.window").new(self.buf, self.scrollbar_buf) + self.menu_window = require("neocomplete.utils.window").new() + self.docs_window = require("neocomplete.utils.window").new() return self end Menu.draw = require("neocomplete.menu.draw") function Menu:readjust_win(offset) - self.window:readjust(self.entries, offset) + self.index = 0 + local width, _ = format_utils.get_width(self.entries) + if not self.entries or #self.entries < 1 then + self.menu_window:close() + return + end + self.menu_window:readjust(#self.entries, width, offset) self:draw() + self.menu_window:draw_scrollbar() end function Menu.close(self) - self.window:close() + self.menu_window:close() + self.docs_window:close() require("neocomplete.ghost_text").hide() end +---@param menu neocomplete.menu local function draw_docs(menu, entry, config) if not entry then return end + + local function open_docs_window(doc_entry, x_offset) + if not doc_entry.completion_item.documentation then + return + end + local documentation = doc_entry.completion_item.documentation + local format = "markdown" + local contents + if type(documentation) == "table" and documentation.kind == "plaintext" then + format = "plaintext" + contents = vim.split(documentation.value or "", "\n", { trimempty = true }) + else + contents = vim.lsp.util.convert_input_to_markdown_lines(documentation --[[@as string]]) + end + + local TODO = 100000 + local width = math.min(vim.o.columns - x_offset, config.max_width) + local height = math.min(TODO, config.max_height) + + local do_stylize = format == "markdown" and vim.g.syntax_on ~= nil + + if do_stylize then + contents = vim.lsp.util._normalize_markdown(contents, { width = width }) + vim.bo[menu.docs_window.buf].filetype = "markdown" + vim.treesitter.start(menu.docs_window.buf) + vim.api.nvim_buf_set_lines(menu.docs_window.buf, 0, -1, false, contents) + else + -- Clean up input: trim empty lines + contents = vim.split(table.concat(contents, "\n"), "\n", { trimempty = true }) + + if format then + vim.bo[menu.docs_window.buf].syntax = format + end + vim.api.nvim_buf_set_lines(menu.docs_window.buf, 0, -1, true, contents) + end + + menu.docs_window:open_cursor_relative(width, height, -x_offset) + menu.docs_window:draw_scrollbar() + + vim.api.nvim_set_option_value("scrolloff", 0, { win = menu.docs_window.winnr }) + end + if entry.source.source.resolve_item then entry.source.source:resolve_item(entry.completion_item, function(resolved_item) entry.completion_item = resolved_item - menu.window:open_docs_view(entry, config) + open_docs_window( + entry, + menu.menu_window.opened_at.col + + vim.api.nvim_win_get_width(menu.menu_window.winnr) + - (vim.api.nvim_win_get_cursor(0)[2] - 1) + + 1 + ) end) else - menu.window:open_docs_view(entry, config) + open_docs_window( + entry, + menu.menu_window.opened_at.col + + vim.api.nvim_win_get_width(menu.menu_window.winnr) + - (vim.api.nvim_win_get_cursor(0)[2] - 1) + + 1 + ) end end function Menu:docs_visible() - return self.window and self.window.docs_view ~= nil + return self.docs_window:is_open() end function Menu:scroll_docs(delta) if not self:docs_visible() then return end - self.window.docs_view:scroll(delta) + self.docs_window:scroll(delta) end function Menu:select_next(count) @@ -59,9 +122,10 @@ function Menu:select_next(count) if self.index > #self.entries then self.index = self.index - #self.entries - 1 end - self.window:set_scroll(self.index, 1) + self.menu_window:set_scroll(self.index, 1) draw_docs(self, self:get_active_entry(), self.config.ui.docs_view) self:draw() + self.menu_window:draw_scrollbar() end function Menu:select_prev(count) @@ -70,9 +134,10 @@ function Menu:select_prev(count) if self.index < 0 then self.index = #self.entries + self.index + 1 end - self.window:set_scroll(self.index, -1) + self.menu_window:set_scroll(self.index, -1) draw_docs(self, self:get_active_entry(), self.config.ui.docs_view) self:draw() + self.menu_window:draw_scrollbar() end function Menu:open(entries, offset) @@ -84,9 +149,11 @@ function Menu:open(entries, offset) return end self.index = 0 - self.window:open_win(entries, offset) + local width, _ = format_utils.get_width(self.entries) + self.menu_window:open_cursor_relative(width, #self.entries, offset) self:draw() - self.window:set_scroll(self.index, -1) + self.menu_window:draw_scrollbar() + self.menu_window:set_scroll(self.index, -1) end function Menu:get_active_entry() @@ -110,11 +177,12 @@ function Menu:confirm() return end self:complete(entry) - self.window:close() + self.menu_window:close() + self.docs_window:close() end function Menu:is_open() - return self.window:is_open() + return self.menu_window:is_open() end return Menu diff --git a/lua/neocomplete/types/menu.lua b/lua/neocomplete/types/menu.lua index d7b371b..ad43eb8 100644 --- a/lua/neocomplete/types/menu.lua +++ b/lua/neocomplete/types/menu.lua @@ -23,7 +23,9 @@ --- Readjust size of completino window ---@field readjust_win fun(self: neocomplete.menu, offset: integer): nil --- Wrapper for utilities for the window of the menu ----@field window neocomplete.menu_window +---@field menu_window neocomplete.window +--- Wrapper for utilities for the window of the docs +---@field docs_window neocomplete.window --- Entries of the menu ---@field entries neocomplete.entry[] --- Namespace used for the menu diff --git a/lua/neocomplete/types/menu_window.lua b/lua/neocomplete/types/menu_window.lua deleted file mode 100644 index bf26f08..0000000 --- a/lua/neocomplete/types/menu_window.lua +++ /dev/null @@ -1,35 +0,0 @@ ----@class neocomplete.menu_window ---- Creates a new instance of the menu window ----@field new fun(buf: integer, scrollbar_buf: integer): neocomplete.menu_window ----@field winnr? integer ---- Instance of the neocomplete config ----@field config neocomplete.config ----@field buf integer ---- Whether the window is currently opened above or below the cursor ----@field position? "above"|"below" ---- Data for the scrollbar of the window ----@field scrollbar {win: integer, buf: integer} ---- The maximum available height where the window is currently open ----@field max_height integer ---- Method to check whether the window is open or not ----@field is_open fun(self: neocomplete.menu_window): boolean ---- Method to check whether the scrollbar window is open or not ----@field scrollbar_is_open fun(self: neocomplete.menu_window): boolean ---- Adjust the window size to new entries. Modifies height and width while keeping position ----@field readjust fun(self: neocomplete.menu_window, entries: neocomplete.entry[], offset: integer): nil ---- Opens the window for the scrollbar ----@field open_scrollbar_win fun(self: neocomplete.menu_window, width: integer, height: integer, offset: integer): nil ---- Closes the window and the scrollbar window and resets fields ----@field close fun(self: neocomplete.menu_window): nil ---- Sets the scroll of the window ----@field set_scroll fun(self: neocomplete.menu_window, index: integer, direction: integer): nil ---- Opens a new main window ----@field open_win fun(self: neocomplete.menu_window, entries: neocomplete.entry[], offset: integer): nil ---- Change scroll of window ----@field scroll table ---- Where the window was last opened ----@field opened_at {row: integer, col: integer} ---- Instance of documentation view ----@field docs_view neocomplete.docs_view ---- Opens a new docs view ----@field open_docs_view fun(self: neocomplete.menu_window, entry: neocomplete.entry, config: neocomplete.config.ui.docs): nil diff --git a/lua/neocomplete/types/window.lua b/lua/neocomplete/types/window.lua new file mode 100644 index 0000000..0967a58 --- /dev/null +++ b/lua/neocomplete/types/window.lua @@ -0,0 +1,41 @@ +---@class neocomplete.window +--- Creates a new instance of the menu window +---@field new fun(): neocomplete.window +---@field winnr? integer +--- Instance of the neocomplete config +---@field config neocomplete.config +---@field buf integer +--- Whether the window is currently opened above or below the cursor +---@field position? "above"|"below" +--- Data for the scrollbar of the window +---@field scrollbar {win: integer, buf: integer} +--- The maximum available height where the window is currently open +---@field max_height integer +--- Method to check whether the window is open or not +---@field is_open fun(self: neocomplete.window): boolean +--- Method to check whether the scrollbar window is open or not +---@field scrollbar_is_open fun(self: neocomplete.window): boolean +--- Adjust the window size to new entries. Modifies height and width while keeping position +---@field readjust fun(self: neocomplete.window, content_len: integer, width: integer, offset: integer): nil +--- Opens the window for the scrollbar +---@field open_scrollbar_win fun(self: neocomplete.window, width: integer, height: integer, offset: integer): nil +--- Closes the window and the scrollbar window and resets fields +---@field close fun(self: neocomplete.window): nil +--- Sets the scroll of the window +---@field set_scroll fun(self: neocomplete.window, index: integer, direction: integer): nil +--- Opens a new main window +---@field open_cursor_relative fun(self: neocomplete.window, width: integer, wanted_height: integer, offset: integer): nil +--- Draw the scrollbar for the window if needed +---@field draw_scrollbar fun(self: neocomplete.window): nil +--- Change scroll of window +---@field scroll fun(self: neocomplete.window, delta: integer) +--- Where the window was last opened +---@field opened_at {row: integer, col: integer} +--- Instance of documentation view +---@field docs_view neocomplete.docs_view +--- Opens a new docs view +---@field open_docs_view fun(self: neocomplete.window, entry: neocomplete.entry, config: neocomplete.config.ui.docs): nil +--- Namespace used for setting extmarks +---@field ns integer +--- Current scroll of the window +---@field current_scroll integer diff --git a/lua/neocomplete/menu/window.lua b/lua/neocomplete/utils/window.lua similarity index 62% rename from lua/neocomplete/menu/window.lua rename to lua/neocomplete/utils/window.lua index 6e8d266..77d39c0 100644 --- a/lua/neocomplete/menu/window.lua +++ b/lua/neocomplete/utils/window.lua @@ -1,25 +1,25 @@ ----@type neocomplete.menu_window +---@type neocomplete.window ---@diagnostic disable-next-line: missing-fields -local Menu_window = {} -local format_utils = require("neocomplete.utils.format") +local Window = {} -function Menu_window.new(buf, scrollbar_buf) - ---@type neocomplete.menu_window - local self = setmetatable({}, { __index = Menu_window }) +function Window.new() + ---@type neocomplete.window + local self = setmetatable({}, { __index = Window }) self.winnr = nil self.config = require("neocomplete.config").options - self.buf = buf + self.ns = vim.api.nvim_create_namespace("neocomplete_window") + self.buf = vim.api.nvim_create_buf(false, true) self.position = nil self.opened_at = {} self.scrollbar = {} - self.docs_view = nil self.scrollbar.win = nil self.max_height = nil - self.scrollbar.buf = scrollbar_buf + self.current_scroll = 1 + self.scrollbar.buf = vim.api.nvim_create_buf(false, true) return self end -function Menu_window:open_win(entries, offset) +function Window:open_cursor_relative(width, wanted_height, offset) if self:is_open() then self:close() end @@ -27,11 +27,10 @@ function Menu_window:open_win(entries, offset) local screenpos = vim.fn.screenpos(0, cursor[1], cursor[2] + 1) local space_below = vim.o.lines - screenpos.row - 3 - vim.o.cmdheight - local width, _ = format_utils.get_width(entries) local space_above = vim.fn.line(".") - vim.fn.line("w0") - 1 -- local space_below = vim.fn.line("w$") - vim.fn.line(".") local available_space = math.max(space_above, space_below) - local wanted_space = math.min(#entries, self.config.ui.menu.max_height) + local wanted_space = math.min(wanted_height, self.config.ui.menu.max_height) local position = "below" local config_position = self.config.ui.menu.position local height @@ -71,88 +70,49 @@ function Menu_window:open_win(entries, offset) self:open_scrollbar_win(width, height, offset) end -function Menu_window:open_docs_view(entry, config) - pcall(function() - vim.api.nvim_win_close(self.docs_view.winnr, true) - end) - self.docs_view = require("neocomplete.menu.docs_view").new( - entry, - self.opened_at.col + vim.api.nvim_win_get_width(self.winnr) - (vim.api.nvim_win_get_cursor(0)[2] - 1) + 1, - self.position, - config - ) - vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI", "InsertCharPre", "InsertLeave" }, { - callback = function() - pcall(function() - vim.api.nvim_win_close(self.docs_view.winnr, true) - end) - self.docs_view = nil - end, - }) -end - -function Menu_window:readjust(entries, offset) - if not entries or #entries < 1 then +function Window:readjust(content_len, width, offset) + if not content_len then self:close() return end if not self.winnr or not vim.api.nvim_win_is_valid(self.winnr) then self:close() - self:open_win(entries, offset) - self:set_scroll(0, -1) return end - local width, _ = format_utils.get_width(entries) local current_height = vim.api.nvim_win_get_height(self.winnr) local current_width = vim.api.nvim_win_get_width(self.winnr) if width ~= current_width then vim.api.nvim_win_set_width(self.winnr, width) end - if #entries ~= current_height then - vim.api.nvim_win_set_height(self.winnr, math.min(self.max_height, #entries)) + if content_len ~= current_height then + vim.api.nvim_win_set_height(self.winnr, math.min(self.max_height, content_len)) end - -- if self.position == "below" then - -- elseif self.position == "above" then - -- end self:set_scroll(0, -1) - self:open_scrollbar_win(width, math.min(current_height, #entries), offset) + self:open_scrollbar_win(width, math.min(current_height, content_len), offset) end -function Menu_window:open_scrollbar_win(width, height, offset) - if self.scrollbar.win then - pcall(vim.api.nvim_win_close, self.scrollbar.win, true) - self.scrollbar.win = nil - end - if self.config.ui.menu.scrollbar then - self.scrollbar.win = vim.api.nvim_open_win(self.scrollbar.buf, false, { - height = height, - relative = "cursor", - col = -offset + width, - row = self.position == "below" and 2 or -(height + 2) + 1, - width = 1, - style = "minimal", - border = "none", - zindex = 2000, - }) - end -end - -function Menu_window:close() - pcall(vim.api.nvim_win_close, self.winnr, true) - self.winnr = nil - pcall(vim.api.nvim_win_close, self.scrollbar.win, true) - self.scrollbar.win = nil - self.max_height = nil - self.position = nil +function Window:scroll(delta) + self.current_scroll = self.current_scroll + delta + local top_visible = vim.fn.line("w0", self.winnr) + local bottom_visible = vim.fn.line("w$", self.winnr) + local visible_amount = bottom_visible - top_visible + 1 + self.current_scroll = math.max(self.current_scroll, 1) + self.current_scroll = + math.min(self.current_scroll, #vim.api.nvim_buf_get_lines(self.buf, 0, -1, false) - visible_amount + 1) + vim.api.nvim_win_call(self.winnr, function() + vim.cmd("normal! " .. self.current_scroll .. "zt") + end) + self:draw_scrollbar() end -function Menu_window:set_scroll(index, direction) +function Window:set_scroll(index, direction) --- Scrolls to a certain line in the window --- This line will be at the top of the window ---@param line integer local function scroll_to_line(line) vim.api.nvim_win_call(self.winnr, function() vim.cmd("normal! " .. line .. "zt") + self.current_scroll = line end) end local top_visible = vim.fn.line("w0", self.winnr) @@ -174,12 +134,65 @@ function Menu_window:set_scroll(index, direction) end end -function Menu_window:is_open() +function Window:close() + pcall(vim.api.nvim_win_close, self.winnr, true) + self.winnr = nil + pcall(vim.api.nvim_win_close, self.scrollbar.win, true) + self.scrollbar.win = nil + self.max_height = nil + self.position = nil +end + +function Window:open_scrollbar_win(width, height, offset) + if self.scrollbar.win then + pcall(vim.api.nvim_win_close, self.scrollbar.win, true) + self.scrollbar.win = nil + end + if self.config.ui.menu.scrollbar then + self.scrollbar.win = vim.api.nvim_open_win(self.scrollbar.buf, false, { + height = height, + relative = "cursor", + col = -offset + width, + row = self.position == "below" and 2 or -(height + 2) + 1, + width = 1, + style = "minimal", + border = "none", + zindex = 2000, + }) + end +end + +function Window:draw_scrollbar() + if not self:scrollbar_is_open() then + return + end + vim.api.nvim_buf_clear_namespace(self.scrollbar.buf, self.ns, 0, -1) + + local total_lines = #vim.api.nvim_buf_get_lines(self.buf, 0, -1, false) + local top_visible = vim.fn.line("w0", self.winnr) + local bottom_visible = vim.fn.line("w$", self.winnr) + local visible_lines = bottom_visible - top_visible + 1 + if visible_lines >= total_lines then + return + end + local scrollbar_height = + math.max(math.min(math.floor(visible_lines * (visible_lines / total_lines) + 0.5), visible_lines), 1) + vim.api.nvim_buf_set_lines(self.scrollbar.buf, 0, -1, false, vim.split(string.rep(" ", visible_lines + 1), "")) + local scrollbar_offset = math.max(math.floor(visible_lines * (top_visible / total_lines)), 1) + for i = scrollbar_offset, scrollbar_offset + scrollbar_height do + vim.api.nvim_buf_set_extmark(self.scrollbar.buf, self.ns, i - 1, 0, { + virt_text = { { self.config.ui.menu.scrollbar, "PmenuSbar" } }, + virt_text_pos = "overlay", + }) + end +end + +function Window:is_open() return self.winnr ~= nil end -function Menu_window:scrollbar_is_open() +function Window:scrollbar_is_open() return self.scrollbar.win ~= nil end -return Menu_window +return Window From 6dd19c512b37e93b542297268a3e391216c179aa Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 13 Jul 2024 22:51:20 +0200 Subject: [PATCH 3/5] feat: add doc scrolling to minimal init --- minimal_init.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/minimal_init.lua b/minimal_init.lua index db3b73d..133d595 100644 --- a/minimal_init.lua +++ b/minimal_init.lua @@ -43,6 +43,22 @@ local plugins = { vim.keymap.set("i", "", "(NeocompleteSelectNext)") vim.keymap.set("i", "", "(NeocompleteSelectPrev)") + vim.keymap.set("i", "", function() + if require("neocomplete").api.doc_is_open() then + require("neocomplete").api.scroll_docs(4) + else + vim.api.nvim_feedkeys(vim.keycode(""), "n", false) + end + end) + + vim.keymap.set("i", "", function() + if require("neocomplete").api.doc_is_open() then + require("neocomplete").api.scroll_docs(-4) + else + vim.api.nvim_feedkeys(vim.keycode(""), "n", false) + end + end) + vim.api.nvim_create_autocmd("InsertLeave", { callback = function() require("neocomplete").core.menu:close() From 2f00f5be344af8c0da0bc52ab00584e2bba90f7e Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 14 Jul 2024 08:40:10 +0200 Subject: [PATCH 4/5] add types and cleanup old things --- docs/menu.norg | 18 ++++++- lua/neocomplete/menu/docs_view.lua | 76 ----------------------------- lua/neocomplete/types/docs_view.lua | 13 ----- lua/neocomplete/types/menu.lua | 4 ++ lua/neocomplete/types/window.lua | 4 -- 5 files changed, 21 insertions(+), 94 deletions(-) delete mode 100644 lua/neocomplete/menu/docs_view.lua delete mode 100644 lua/neocomplete/types/docs_view.lua diff --git a/docs/menu.norg b/docs/menu.norg index d86e52b..db3a883 100644 --- a/docs/menu.norg +++ b/docs/menu.norg @@ -9,7 +9,7 @@ categories: [ types ] created: 2024-05-29T11:30:25+0100 -updated: 2024-07-13T21:57:35+0100 +updated: 2024-07-14T08:43:31+0100 tangle: { languages: { lua: ../lua/neocomplete/types/menu.lua @@ -134,6 +134,22 @@ version: 1.1.1 ---@field readjust_win fun(self: neocomplete.menu, offset: integer): nil @end +** Docs Visible + Checks whether docs are visible or not + #tangle + @code lua + --- Checks if docs are open + ---@field docs_visible fun(self: neocomplete.menu): boolean + @end + +** Scroll docs + Scroll up or down in the docs window by `delta` lines. + #tangle + @code lua + --- Scroll in the docs window + ---@field scroll_docs fun(self: neocomplete.menu, delta: integer): nil + @end + * Fields ** Menu Window #tangle diff --git a/lua/neocomplete/menu/docs_view.lua b/lua/neocomplete/menu/docs_view.lua deleted file mode 100644 index 1bfdda6..0000000 --- a/lua/neocomplete/menu/docs_view.lua +++ /dev/null @@ -1,76 +0,0 @@ ----@type neocomplete.docs_view ----@diagnostic disable-next-line: missing-fields -local Docs = {} - -function Docs.new(entry, x_offset, position, config) - ---@type neocomplete.docs_view - local self = setmetatable({}, { __index = Docs }) - self.current_scroll = 1 - if not entry.completion_item.documentation then - return nil - end - local documentation = entry.completion_item.documentation - local format = "markdown" - local contents - if type(documentation) == "table" and documentation.kind == "plaintext" then - format = "plaintext" - contents = vim.split(documentation.value or "", "\n", { trimempty = true }) - else - contents = vim.lsp.util.convert_input_to_markdown_lines(documentation --[[@as string]]) - end - - local TODO = 100000 - local width = math.min(vim.o.columns - x_offset, config.max_width) - local height = math.min(TODO, config.max_height) - self.bufnr = vim.api.nvim_create_buf(false, true) - - local do_stylize = format == "markdown" and vim.g.syntax_on ~= nil - - if do_stylize then - contents = vim.lsp.util._normalize_markdown(contents, { width = width }) - vim.bo[self.bufnr].filetype = "markdown" - vim.treesitter.start(self.bufnr) - vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, contents) - else - -- Clean up input: trim empty lines - contents = vim.split(table.concat(contents, "\n"), "\n", { trimempty = true }) - - if format then - vim.bo[self.bufnr].syntax = format - end - vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, true, contents) - end - - self.winnr = vim.api.nvim_open_win(self.bufnr, false, { - relative = "cursor", - anchor = position == "below" and "NW" or "SW", - border = config.border, - style = "minimal", - width = width, - height = math.min(height, #contents), - row = position == "below" and 1 or 0, - col = x_offset, - }) - - vim.api.nvim_set_option_value("scrolloff", 0, { win = self.winnr }) - - return self -end - -function Docs:scroll(delta) - self.current_scroll = self.current_scroll + delta - local top_visible = vim.fn.line("w0", self.winnr) - local bottom_visible = vim.fn.line("w$", self.winnr) - local visible_amount = bottom_visible - top_visible + 1 - self.current_scroll = - math.min(self.current_scroll, #vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) - visible_amount - 1) - self:set_scroll(self.current_scroll) -end - -function Docs:set_scroll(line) - vim.api.nvim_win_call(self.winnr, function() - vim.cmd("normal! " .. line .. "zt") - end) -end - -return Docs diff --git a/lua/neocomplete/types/docs_view.lua b/lua/neocomplete/types/docs_view.lua deleted file mode 100644 index 1937cd6..0000000 --- a/lua/neocomplete/types/docs_view.lua +++ /dev/null @@ -1,13 +0,0 @@ ----@class neocomplete.docs_view ---- Winnr of the docs window ----@field winnr? integer ---- The current top line of the window ----@field current_scroll integer ---- Creates a new view ----@field new fun(entry: neocomplete.entry, x_offset: integer, position: "above"|"below", config: neocomplete.config.ui.docs): neocomplete.docs_view? ---- Buffer used for view ----@field bufnr integer ---- Scroll the window up or down ----@field scroll fun(self: neocomplete.docs_view, delta: integer) ---- Sets the scroll to a certain line ----@field set_scroll fun(self: neocomplete.docs_view, line: integer) diff --git a/lua/neocomplete/types/menu.lua b/lua/neocomplete/types/menu.lua index ad43eb8..2915b0c 100644 --- a/lua/neocomplete/types/menu.lua +++ b/lua/neocomplete/types/menu.lua @@ -22,6 +22,10 @@ ---@field complete fun(self: neocomplete.menu, entry: neocomplete.entry): nil --- Readjust size of completino window ---@field readjust_win fun(self: neocomplete.menu, offset: integer): nil +--- Checks if docs are open +---@field docs_visible fun(self: neocomplete.menu): boolean +--- Scroll in the docs window +---@field scroll_docs fun(self: neocomplete.menu, delta: integer): nil --- Wrapper for utilities for the window of the menu ---@field menu_window neocomplete.window --- Wrapper for utilities for the window of the docs diff --git a/lua/neocomplete/types/window.lua b/lua/neocomplete/types/window.lua index 0967a58..09abee3 100644 --- a/lua/neocomplete/types/window.lua +++ b/lua/neocomplete/types/window.lua @@ -31,10 +31,6 @@ ---@field scroll fun(self: neocomplete.window, delta: integer) --- Where the window was last opened ---@field opened_at {row: integer, col: integer} ---- Instance of documentation view ----@field docs_view neocomplete.docs_view ---- Opens a new docs view ----@field open_docs_view fun(self: neocomplete.window, entry: neocomplete.entry, config: neocomplete.config.ui.docs): nil --- Namespace used for setting extmarks ---@field ns integer --- Current scroll of the window From 5cf3f93029566d1aa68ad746f07b94f6bf997ddd Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 14 Jul 2024 08:44:55 +0200 Subject: [PATCH 5/5] fix(window): reset more things when closing --- lua/neocomplete/utils/window.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/neocomplete/utils/window.lua b/lua/neocomplete/utils/window.lua index 77d39c0..07b0085 100644 --- a/lua/neocomplete/utils/window.lua +++ b/lua/neocomplete/utils/window.lua @@ -139,6 +139,8 @@ function Window:close() self.winnr = nil pcall(vim.api.nvim_win_close, self.scrollbar.win, true) self.scrollbar.win = nil + self.current_scroll = 1 + self.opened_at = {} self.max_height = nil self.position = nil end