From 5f3aa3d661c08945d16f9225200844ac44716428 Mon Sep 17 00:00:00 2001 From: JuanZoran <1430359574@qq.com> Date: Fri, 20 Jan 2023 17:04:50 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20window=E5=92=8Ccontent=E7=9A=84?= =?UTF-8?q?=E5=85=B3=E7=B3=BB=E7=94=B1=E7=BB=A7=E6=89=BF=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E7=BB=84=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lua/Trans/content.lua | 167 +++++++++++++++ lua/Trans/view/hover.lua | 51 ++--- lua/Trans/window.lua | 424 +++++++++++++++------------------------ 3 files changed, 358 insertions(+), 284 deletions(-) create mode 100644 lua/Trans/content.lua diff --git a/lua/Trans/content.lua b/lua/Trans/content.lua new file mode 100644 index 0000000..ba2f31f --- /dev/null +++ b/lua/Trans/content.lua @@ -0,0 +1,167 @@ +local api = vim.api +local content = { + newline = function(self, value) + if not self.modifiable then + error('content can not add newline now') + end + self.size = self.size + 1 + self.lines[self.size] = value + end, + + newhl = function(self, opt) + if not self.modifiable then + error('content can not add newline now') + end + self.hl_size = self.hl_size + 1 + self.highlights[self.hl_size] = opt + end, + + center_line = function(self, text, highlight) + vim.validate { + text = { text, 's' } + } + + local space = math.floor((self.window.width - text:width()) / 2) + local interval = (' '):rep(space) + self:newline(interval .. text) + if highlight then + self:newhl { + name = highlight, + line = self.size - 1, + _start = space, + _end = space + #text, + } + end + end, + + wipe = function(self) + local clear = require('table.clear') + clear(self.lines) + clear(self.highlights) + self.size = 0 + end, + + attach = function(self) + if self.size == 0 then + return + end + + self.window:bufset('modifiable', true) + local window = self.window + local offset = self.offset + + api.nvim_buf_set_lines(window.bufnr, offset, offset + 1, true, self.lines) + + for _, hl in ipairs(self.highlights) do + api.nvim_buf_add_highlight(window.bufnr, window.hl, hl.name, offset + hl.line, hl._start, hl._end) + end + self.window:bufset('modifiable', false) + end, + + actual_height = function(self) + if self.window:option('wrap') then + local height = 0 + local width = self.window.width + local lines = self.lines + for i = 1, self.size do + height = height + math.max(1, (math.ceil(lines[i]:width() / width))) + end + return height + else + return self.size + end + end, + + addline = function(self, newline, highlight) + self:newline(newline) + if highlight then + self:newhl { + name = highlight, + line = self.size - 1, + _start = 0, + _end = -1, + } + end + end, + + items_wrap = function(self) + local items = {} + local size = 0 + local width = 0 + + return { + add_item = function(item, highlight) + size = size + 1 + items[size] = { item, highlight } + width = width + item:width() + end, + + load = function() + assert(size > 1, 'no item need be loaded') + local space = math.floor((self.window.width - width) / (size - 1)) + assert(space > 0, 'try to expand window width') + local interval = (' '):rep(space) + local line = '' + + local function load_item(idx) + local item = items[idx] + if item[2] then + self:newhl { + name = item[2], + line = self.size, -- NOTE : 此时还没插入新行, size ==> 行号(zero index) + _start = #line, + _end = #line + #item[1], + } + end + line = line .. item[1] + end + + load_item(1) + for i = 2, size do + line = line .. interval + load_item(i) + end + + self:newline(line) + end + } + end, + + line_wrap = function(self) + self:newline('') + local index = self.size + return function(text, highlight) + if highlight then + local _start = #self.lines[index] + local _end = _start + #text + self:newhl { + name = highlight, + line = index - 1, + _start = _start, + _end = _end, + } + end + + self.lines[index] = self.lines[index] .. text + end + end +} + + +---content的构造函数 +---@param window table 链接的窗口 +---@return table 构造好的content +return function(window, offset) + vim.validate { + window = { window, 't' }, + } + return setmetatable({ + modifiable = true, + offset = offset or 0, + window = window, + size = 0, + hl_size = 0, + lines = {}, + highlights = {}, + }, { __index = content }) +end diff --git a/lua/Trans/view/hover.lua b/lua/Trans/view/hover.lua index 04a7db1..6db4fdb 100644 --- a/lua/Trans/view/hover.lua +++ b/lua/Trans/view/hover.lua @@ -1,12 +1,13 @@ local conf = require('Trans').conf local icon = conf.icon -local m_window = require('Trans.window') -local m_result +local m_window +local m_result +local m_content local m_indent = ' ' local title = function(str) - local wrapper = m_window.text_wrap() + local wrapper = m_content:line_wrap() -- wrapper('', 'TransTitleRound') wrapper('', 'TransTitleRound') wrapper(str, 'TransTitle') @@ -61,7 +62,7 @@ end local process = { title = function() - local line = m_window.line_wrap() + local line = m_content:items_wrap() line.add_item( m_result.word, 'TransWord' @@ -94,12 +95,13 @@ local process = { end for i = 1, size, 3 do - m_window.addline( + m_content:addline( m_indent .. tags[i] .. interval .. (tags[i + 1] or '') .. interval .. (tags[i + 2] or ''), 'TransTag' ) end - m_window.addline('') + + m_content:addline('') end end, @@ -108,13 +110,13 @@ local process = { title('词性') for pos in vim.gsplit(m_result.pos, '/', true) do - m_window.addline( + m_content:addline( m_indent .. pos_map[pos:sub(1, 1)] .. pos:sub(3) .. '%', 'TransPos' ) end - m_window.addline('') + m_content:addline('') end end, @@ -124,13 +126,13 @@ local process = { local interval = ' ' for exc in vim.gsplit(m_result.exchange, '/', true) do - m_window.addline( + m_content:addline( m_indent .. exchange_map[exc:sub(1, 1)] .. interval .. exc:sub(3), 'TransExchange' ) end - m_window.addline('') + m_content:addline('') end end, @@ -138,13 +140,13 @@ local process = { title('中文翻译') for trs in vim.gsplit(m_result.translation, '\n', true) do - m_window.addline( + m_content:addline( m_indent .. trs, 'TransTranslation' ) end - m_window.addline('') + m_content:addline('') end, definition = function() @@ -153,18 +155,18 @@ local process = { for def in vim.gsplit(m_result.definition, '\n', true) do def = def:gsub('^%s+', '', 1) -- TODO :判断是否需要分割空格 - m_window.addline( + m_content:addline( m_indent .. def, 'TransDefinition' ) end - m_window.addline('') + m_content:addline('') end end, failed = function() - m_window.addline( + m_content:addline( icon.notfound .. m_indent .. '没有找到相关的翻译', 'TransFailed' ) @@ -174,10 +176,11 @@ local process = { local action = { pageup = function() - m_window.normal('gg') + m_window:normal('gg') end, + pagedown = function() - m_window.normal('G') + m_window:normal('G') end, } @@ -200,8 +203,8 @@ return function(word) row = 2, } - - m_window.init(false, opt) + m_window = require("Trans.window")(false, opt) + m_content = m_window.content if m_result then for _, field in ipairs(conf.order) do @@ -210,24 +213,22 @@ return function(word) else process.failed() end + m_window:set('wrap', true) - m_window.draw() + m_window:draw(true) -- Auto Close vim.api.nvim_create_autocmd( { --[[ 'InsertEnter', ]] 'CursorMoved', 'BufLeave', }, { buffer = 0, once = true, callback = function() - m_window.try_close(hover.animation) -- NOTE :maybe can be passed by uesr + m_window:try_close(hover.animation) end, }) - m_window.set('wrap', true) - m_window.adjust() - for act, key in pairs(hover.keymap) do vim.keymap.set('n', key, function() - if m_window.is_open() then + if m_window:is_open() then action[act]() end end) diff --git a/lua/Trans/window.lua b/lua/Trans/window.lua index 78ab4f7..9e21064 100644 --- a/lua/Trans/window.lua +++ b/lua/Trans/window.lua @@ -1,31 +1,147 @@ local api = vim.api ---- =================== Window Attributes ================================ -local M = { - height = 0, -- 窗口的当前的高度 - size = 0, -- 窗口的行数 - width = 0, -- 窗口的当前的宽度 - lines = {}, - highlights = {}, - winid = -1, -- 窗口的handle - bufnr = -1, -- 窗口对应的buffer的handle - hl = api.nvim_create_namespace('TransWinHl'), -} - --- M.<++> --> <++> +---@diagnostic disable-next-line: duplicate-set-field function string:width() ---@diagnostic disable-next-line: param-type-mismatch return vim.fn.strwidth(self) end ---- =================== Load Window Options ================================ -M.init = function(entry, opts) +local window = { + ---设置窗口的选项 + ---@param self table 需要设置的window + ---@param option string 待设置的选项名 + ---@param value any 选项的值 + set = function(self, option, value) + api.nvim_win_set_option(self.winid, option, value) + end, + + ---设置窗口的高度 + ---@param self table 窗口类 + ---@param height integer 设置的高度 + set_height = function(self, height) + api.nvim_win_set_height(self.winid, height) + self.height = height + end, + + ---设置窗口的宽度 + ---@param self table 窗口对象 + ---@param width integer 设置的宽度 + set_width = function(self, width) + api.nvim_win_set_width(self.winid, width) + self.width = width + end, + + ---设置窗口对应的buffer + ---@param self table 同set类似 + ---@param option string + ---@param value any + bufset = function(self, option, value) + api.nvim_buf_set_option(self.bufnr, option, value) + end, + + ---查看**窗口**的选项 + ---@param self table 窗口对象 + ---@param name string 选项名 + ---@return any 对应的值 + ---@nodiscard + option = function(self, name) + return api.nvim_win_get_option(self.winid, name) + end, + + ---设置当前窗口内的键映射, **需要光标在该窗口内** + ---@param self table 窗口对象 + ---@param key string 映射的键位 + ---@param operation any 执行的操作 + map = function(self, key, operation) + vim.keymap.set('n', key, operation, { + buffer = self.bufnr, + silent = true, + }) + end, + + ---查看窗口是否是打开状态 + ---@param self table window对象 + ---@return boolean + ---@nodiscard + is_open = function(self) + return self.winid > 0 and api.nvim_win_is_valid(self.winid) + end, + + normal = function(self, key) + api.nvim_buf_call(self.bufnr, function() + vim.cmd([[normal! ]] .. key) + end) + end, + + ---**第一次**绘制窗口的内容 + ---@param self table 窗口的对象 + ---@param adjust boolean 是否需要调整窗口的高度和宽度 (只有窗口只有一行时才会调整宽度) + draw = function(self, adjust) + -- TODO : + if self.title then + self.title:attach() + end + self.content:attach() + + if adjust then + local height = self.content:actual_height() + self.title:actual_height() + if self.height > height then + self:set_height(height) + end + + if self.content.size == 1 and self.title.size == 0 then + self:set_width(self.content.lines[1]:width()) + end + end + end, + + ---**重新绘制内容**(标题不变) + ---@param self table 窗口对象 + redraw = function(self) + self.content:attach() + end, + + ---安全的关闭窗口 + ---@param self table 窗口对象 + ---@param interval integer 动画的间隔 + try_close = function(self, interval) + vim.validate { + interval = { interval, 'n' } + } + + if self:is_open() then + local function narrow() + if self.height > 1 then + self.height = self.height - 1 + api.nvim_win_set_height(self.winid, self.height) + vim.defer_fn(narrow, interval) + else + -- Wait animation done + vim.defer_fn(function() + api.nvim_win_close(self.winid, true) + self.winid = -1 + end, interval + 2) + end + end + + narrow() + end + end, +} + +---窗口对象的构造器 +---@param entry boolean 光标初始化时是否应该进入窗口 +---@param option table 需要设置的选项 +---@return table +---@nodiscard +return function(entry, option) vim.validate { entry = { entry, 'b' }, - opts = { opts, 't' } + option = { option, 't' }, } + local opt = { - relative = nil, + relative = nil, width = nil, height = nil, border = nil, @@ -37,253 +153,43 @@ M.init = function(entry, opts) zindex = 100, style = 'minimal', } - - for k, v in pairs(opts) do + for k, v in pairs(option) do opt[k] = v end - M.height = opt.height - M.width = opt.width - M.bufnr = api.nvim_create_buf(false, true) - M.winid = api.nvim_open_win(M.bufnr, entry, opt) - M.set('winhl', 'Normal:TransWin,FloatBorder:TransBorder') - M.bufset('bufhidden', 'wipe') - M.bufset('filetype', 'Trans') - M.wipe() -end + local bufnr = api.nvim_create_buf(false, true) + local winid = api.nvim_open_win(bufnr, entry, opt) - -M.draw = function() - -- TODO : - M.bufset('modifiable', true) - api.nvim_buf_set_lines(M.bufnr, 0, -1, false, M.lines) - for _, hl in ipairs(M.highlights) do - api.nvim_buf_add_highlight(M.bufnr, M.hl, hl.name, hl.line, hl._start, hl._end) - end - - M.bufset('modifiable', false) - -- vim.pretty_print(M.highlights) -end - ----清空window的数据 -M.wipe = function() - M.size = 0 - local clear = require('table.clear') - clear(M.lines) - clear(M.highlights) -end - -M.is_open = function() - return M.winid > 0 and api.nvim_win_is_valid(M.winid) -end - - ----安全的关闭窗口 ----@param interval integer 窗口关闭动画的间隔 -M.try_close = function(interval) - if M.is_open() then - local function narrow() - if M.height > 1 then - M.height = M.height - 1 - api.nvim_win_set_height(M.winid, M.height) - vim.defer_fn(narrow, interval) - else - -- Wait animation done - vim.defer_fn(function() - api.nvim_win_close(M.winid, true) - M.winid = -1 - end, interval + 2) - end - end - - narrow() - end -end - - -M.cur_height = function() - if api.nvim_win_get_option(M.winid, 'wrap') then - local height = 0 - local width = M.width - local lines = M.lines - - for i = 1, M.size do - height = height + math.max(1, (math.ceil(lines[i]:width() / width))) - end - return height - - else - return M.size - end -end - - -M.adjust = function() - local cur_height = M.cur_height() - if M.height > cur_height then - api.nvim_win_set_height(M.winid, cur_height) - M.height = cur_height - end - - if M.size == 1 then - api.nvim_win_set_width(M.winid, M.lines[1]:width()) - end -end - - ----- ============ Utility functions ============ ----设置窗口选项 ----@param option string 需要设置的窗口 ----@param value any 需要设置的值 -M.set = function(option, value) - api.nvim_win_set_option(M.winid, option, value) -end - ----设置窗口对应buffer的选项 ----@param option string 需要设置的窗口 ----@param value any 需要设置的值 -M.bufset = function(option, value) - api.nvim_buf_set_option(M.bufnr, option, value) -end - - -M.normal = function(key) - api.nvim_buf_call(M.bufnr, function() - vim.cmd([[normal! ]] .. key) - end) -end - ----设置该窗口的本地的键映射(都为normal模式) ----@param key string 映射的键 ----@param operation any 执行的操作 -M.map = function(key, operation) - -- api.nvim_buf_set_keymap(M.bufnr, 'n', key, operation, { silent = true, noremap = true, }) - vim.keymap.set('n', key, operation, { - silent = true, - buffer = M.bufnr, - }) -end - - ---- =================== Window lines ================================ -local function insert_line(text) - vim.validate { - text = { text, 's' }, - } - - M.size = M.size + 1 - M.lines[M.size] = text -end - - ----向窗口中添加新行 ----@param newline string 待添加的新行 ----@param opt? table|string 可选的行属性: highlight, TODO : -M.addline = function(newline, opt) - insert_line(newline) - - if type(opt) == 'string' then - table.insert(M.highlights, { - name = opt, - line = M.size - 1, -- NOTE : 高亮的行号是以0为第一行 - _start = 0, - _end = -1, - }) - -- elseif type(opt) == 'table' then - -- -- TODO : - -- error('TODO') - end -end - ----添加一行新的内容并居中 ----@param text string 需要居中的文本 ----@param highlight? string 可选的高亮组 -M.center = function(text, highlight) - vim.validate { - text = { text, 's' } - } - local space = math.floor((M.width - text:width()) / 2) - local interval = (' '):rep(space) - insert_line(interval .. text) - if highlight then - table.insert(M.highlights, { - name = highlight, - line = M.size - 1, - _start = space, - _end = space + #text, - }) - end -end - - ----返回一个行的包装器: 具有 [add_item] [load] 方法 ----能够添加item, 调用load格式化并载入window.lines -M.line_wrap = function() - local items = {} - local width = 0 -- 所有item的总width - local size = 0 -- item数目 - return { - add_item = function(item, highlight) - size = size + 1 - items[size] = { item, highlight } - width = width + item:width() - end, - - load = function() - assert(size > 1, 'no item need be loaded') - local space = math.floor((M.width - width) / (size - 1)) - assert(space > 0, 'try to expand window width') - local interval = (' '):rep(space) - local value = '' - local function load_item(idx) - local item = items[idx] - if item[2] then - table.insert(M.highlights, { - name = item[2], - line = M.size, -- NOTE : 此时还没插入新行, size ==> 行号(zero index) - _start = #value, - _end = #value + #item[1], - }) + local win = setmetatable({ + title = nil, + content = nil, + winid = winid, + bufnr = bufnr, + width = opt.width, + height = opt.height, + hl = api.nvim_create_namespace('TransWinHl'), + }, + { __index = function(tbl, key) + if key == 'content' then + if tbl.title then + tbl.content = require('Trans.content')(tbl, tbl.title.size) + tbl.title.modifiable = false + else + tbl.content = require('Trans.content')(tbl) end - value = value .. item[1] + return tbl.content + + elseif key == 'title' then + tbl.title = require('Trans.content')(tbl, 0) + return tbl.title + + else + return window[key] end + end }) - load_item(1) - for i = 2, size do - value = value .. interval - load_item(i) - end - - insert_line(value) - end - } + win:set('winhl', 'Normal:TransWin,FloatBorder:TransBorder') + win:bufset('bufhidden', 'wipe') + win:bufset('filetype', 'Trans') + return win end - - - -M.text_wrap = function() - insert_line('') - local l = M.size - - return function(text, highlight) - if highlight then - local _start = #M.lines[l] - local _end = _start + #text - table.insert(M.highlights, { - name = highlight, - line = M.size - 1, - _start = _start, - _end = _end, - }) - end - - M.lines[l] = M.lines[l] .. text - end -end - - - ---- =================== Window Highlights ================================ ---- TODO : add helpful function for highlights - -return M