Merge pull request #4 from JuanZoran/experimental

window和content合并,同时和view解耦
This commit is contained in:
Zoran 2023-01-19 23:47:23 +08:00 committed by GitHub
commit 71eaffd7bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 664 additions and 1167 deletions

3
.gitignore vendored
View File

@ -1 +1,2 @@
lua/Trans/util/test/
lua/Trans/util/
lua/Trans/core/

View File

@ -66,6 +66,7 @@ use {
keys = {
{ 'v', 'mm' }, -- 换成其他你想用的key即可
{ 'n', 'mm' },
{ 'n', 'mi' },
},
run = 'bash ./install.sh', -- 自动下载使用的本地词库
requires = 'kharji/sqlite.lua',
@ -135,9 +136,9 @@ require'Trans'.setup {
icon = {
title = ' ', -- 
star = '',
-- notfound = '',
-- yes = '',
-- no = ''
-- notfound = ' ',
-- yes = ' ',
-- no = ' '
-- star = '⭐',
notfound = '❔',
yes = '✔️',

View File

@ -1,8 +0,0 @@
local M = {}
local query_warpper = require 'Trans.api.query'
M.query = query_warpper.query
return M

View File

@ -1,60 +0,0 @@
local M = {}
local _, db = pcall(require, 'sqlite.db')
if not _ then
error('Please check out sqlite.lua')
end
-- INFO : init database
local path = require('Trans').conf.db_path
local dict = db:open(path)
local query_fields = {
'word',
'phonetic',
'definition',
'translation',
'pos',
'collins',
'oxford',
'tag',
'exchange',
}
local routes = {
offline = function(word)
local res = dict:select('stardict', {
where = {
word = word,
},
keys = query_fields,
})
return res[1]
end,
}
-- INFO :Auto Close
vim.api.nvim_create_autocmd('VimLeavePre', {
group = require("Trans").augroup,
callback = function()
if db:isopen() then
db:close()
end
end
})
-- NOTE : local query
M.query = function(engine, word)
-- TODO : more opts
vim.validate {
word = {word, 's'},
engine = {word, 's'},
}
return routes[engine](word)
end
return M

View File

@ -1,25 +0,0 @@
-- local util = require('Trans.util')
local bufnr = require('Trans.core.window').bufnr
-- local winid = require('Trans.core.window').id
local api = vim.api
local function buf_feedkey(key)
if bufnr and api.nvim_buf_is_valid(bufnr) then
api.nvim_buf_call(bufnr, function()
vim.cmd([[normal! ]] .. key)
end)
return true
else
return false
end
end
local M = {
pageup = function()
return buf_feedkey('gg')
end,
pagedown = function()
return buf_feedkey('G')
end
}
return M

View File

@ -1,167 +0,0 @@
local M = {}
M.__index = M
M.get_width = vim.fn.strwidth
---@alias block table add_hl(key, hl_name)
---返回分配的块状区域, e_col 设置为-1则为末尾
---@param s_row integer 起始行
---@param s_col integer 起始列
---@param height integer 行数
---@param width integer 块的宽度
---@return block
function M:alloc_block(s_row, s_col, height, width)
-- -1为到行尾
width = width == -1 and self.width or width
local e_col = s_col + width
local block = {
add_hl = function(key, hl_name)
table.insert(self.highlight[s_row + key], {
name = hl_name,
_start = s_col,
_end = e_col,
})
end
}
return setmetatable(block, {
-- 访问该表的操作, 映射成lines
__index = function(_, key)
assert(0 < key and key <= height)
--- FIXME : Unicode stirng sub
return self.lines[s_row + key]:sub(s_col, e_col)
end,
__newindex = function(_, key, value)
assert(0 < key and key <= height)
local wid = self.get_width(value)
if wid > width then
error('check out the str width: Max ->' .. self.width .. ' str ->' .. wid)
else
value = value .. (' '):rep(width - wid)
end
local line = s_row + key - 1
self.lines[line] = self.lines[line]:sub(1, s_col - 1) .. value .. self.lines[line]:sub(e_col + 1)
end,
})
end
function M:alloc_items()
local items = {}
local width = 0 -- 所有item的总width
local size = 0 -- item数目
return {
add_item = function(item, highlight)
size = size + 1
local wid = self.get_width(item)
items[size] = { item, highlight }
width = width + wid
end,
load = function()
self.len = self.len + 1
local space = math.floor((self.width - width) / (size - 1))
assert(space > 0)
local interval = (' '):rep(space)
local value = ''
local function load_item(index)
if items[index][2] then
table.insert(self.highlights[self.len], {
name = items[index][2],
_start = #value,
_end = #value + #items[index][1],
})
end
value = value .. items[index][1]
end
load_item(1)
for i = 2, size do
value = value .. interval
load_item(i)
end
self.lines[self.len] = value
end
}
end
---返回新行的包装函数
---@return function
function M:text_wrapper()
local l = self.len + 1 -- 取出当前行
self.lines[l] = ''
self.len = l
return function(text, highlight)
if highlight then
local _start = #self.lines[l]
local _end = _start + #text
table.insert(self.highlights[l], {
name = highlight,
_start = _start,
_end = _end,
})
end
self.lines[l] = self.lines[l] .. text
end
end
function M:addline(text, highlight)
assert(text, 'empty text')
self.len = self.len + 1
if highlight then
table.insert(self.highlights[self.len], {
name = highlight,
_start = 0,
_end = -1
})
end
self.lines[self.len] = text
end
function M:new(width)
vim.validate {
width = { width, 'n' }
}
local default = (' '):rep(width) -- default value is empty line
local new_content = {
width = width,
len = 0,
highlights = setmetatable({}, { -- always has default value
__index = function(tbl, key)
tbl[key] = {}
return tbl[key]
end
}),
}
new_content.lines = setmetatable({}, {
__index = function(tbl, key)
tbl[key] = default
return tbl[key]
end,
__newindex = function(tbl, key, value)
assert(value, 'add no value as new line')
for i = new_content.len + 1, key - 1 do
rawset(tbl, i, '')
end
rawset(tbl, key, value)
new_content.len = key
end
})
return setmetatable(new_content, M)
end
function M:clear()
require('table.clear')(self)
end
return M

View File

@ -1,200 +0,0 @@
---@diagnostic disable: unused-local
local M = {}
local icon = require('Trans').conf.icon
-- local components = {
-- 'title',
-- 'tag',
-- 'pos',
-- 'exchange',
-- 'translation',
-- 'definition'
-- }
local tag_map = {
zk = '中考',
gk = '高考',
ky = '考研',
cet4 = '四级',
cet6 = '六级',
ielts = '雅思',
toefl = '托福',
gre = 'gre ',
}
local pos_map = {
a = '代词pron ',
c = '连接词conj ',
i = '介词prep ',
j = '形容词adj ',
m = '数词num ',
n = '名词n ',
p = '代词pron ',
r = '副词adv ',
u = '感叹词int ',
v = '动词v ',
x = '否定标记not ',
t = '不定式标记infm ',
d = '限定词determiner',
}
local exchange_map = {
['p'] = '过去式 ',
['d'] = '过去分词 ',
['i'] = '现在分词 ',
['r'] = '比较级 ',
['t'] = '最高级 ',
['s'] = '复数 ',
['0'] = '原型 ',
['1'] = '类别 ',
['3'] = '第三人称单数',
['f'] = '第三人称单数',
}
local function exist(res)
return res and res ~= ''
end
local function expl(c, text)
local wrapper = c:text_wrapper()
-- wrapper('', 'TransTitleRound')
wrapper('', 'TransTitleRound')
wrapper(text, 'TransTitle')
-- wrapper('', 'TransTitleRound')
wrapper('', 'TransTitleRound')
end
local indent = ' '
M.hover = {
title = function(result, content)
local line = content:alloc_items()
line.add_item(result.word, 'TransWord')
local pho = ('[' .. (exist(result.phonetic) and result.phonetic or icon.notfound) .. ']')
-- line.add_item(pho, 'TransPhonetic', #pho)
line.add_item(pho, 'TransPhonetic')
line.add_item((exist(result.collins) and icon.star:rep(result.collins) or icon.notfound), 'TransCollins')
line.add_item((result.oxford == 1 and icon.yes or icon.no))
line.load()
end,
tag = function(result, content)
if exist(result.tag) then
expl(content, '标签')
local tags = vim.tbl_map(function(tag)
return tag_map[tag]
end, vim.split(result.tag, ' ', { plain = true, trimempry = true }))
local size = #tags
local i = 1
while i <= size do
content:addline(indent .. tags[i] .. ' ' .. (tags[i + 1] or '') .. ' ' .. (tags[i + 2] or ''),
'TransTag')
i = i + 3
end
content:addline('')
end
end,
pos = function(result, content)
if exist(result.pos) then
expl(content, '词性')
vim.tbl_map(function(pos)
content:addline(indent .. pos_map[pos:sub(1, 1)] .. pos:sub(3) .. '%', 'TransPos')
end, vim.split(result.pos, '/', { plain = true, trimempry = true }))
content:addline('')
end
end,
exchange = function(result, content)
if exist(result.exchange) then
expl(content, '词形变化')
vim.tbl_map(function(exc)
content:addline(indent .. exchange_map[exc:sub(1, 1)] .. ' ' .. exc:sub(3), 'TransExchange')
end, vim.split(result.exchange, '/', { plain = true, trimempry = true }))
content:addline('')
end
end,
translation = function(result, content)
expl(content, '中文翻译')
vim.tbl_map(function(trs)
content:addline(indent .. trs, 'TransTranslation')
end, vim.split(result.translation, '\n', { plain = true, trimempry = true }))
content:addline('')
end,
definition = function(result, content)
if exist(result.definition) then
expl(content, '英文注释')
vim.tbl_map(function(def)
def = def:gsub('%s+', '', 1) -- TODO :判断是否需要分割空格
content:addline(indent .. def, 'TransDefinition')
end, vim.split(indent .. result.definition, '\n', { plain = true, trimempry = true }))
content:addline('')
end
end,
failed = function(content)
content:addline(icon.notfound .. indent .. '没有找到相关的翻译', 'TransNotFound')
end,
}
M.process = function(view, result)
local conf = require('Trans').conf
local content = require('Trans.core.content'):new(conf.window[view].width)
if result then
if view == 'hover' then
vim.tbl_map(function(handle)
M.hover[handle](result, content)
end, conf.order)
elseif view == 'float' then
-- TODO :
else
error('unknown view ' .. view)
end
else
M[view].failed(content)
end
return content
end
--- TODO :Content Handler for float view
M.float = {
title = function(result, content)
end,
tag = function(result, content)
end,
pos = function(result, content)
end,
exchange = function(result, content)
end,
translation = function(result, content)
end,
definition = function(result, content)
end,
faild = function(result, content)
end,
}
return M

View File

@ -1,145 +0,0 @@
local M = {}
local api = vim.api
local conf = require('Trans').conf
local util = require('Trans.util')
M.id = 0
M.ns = api.nvim_create_namespace('Trans')
M.bufnr = api.nvim_create_buf(false, true)
function M.init(view)
vim.validate {
view = { view, 's' }
}
M.view = view
local is_float = view == 'float'
M.height = conf.window[view].height
M.width = conf.window[view].width
local opts = {
relative = is_float and 'editor' or 'cursor',
width = M.width,
height = M.height,
style = 'minimal',
border = conf.window.border,
title = {
{ '', 'TransTitleRound' },
-- { '', 'TransTitleRound' },
{ conf.icon.title .. ' Trans', 'TransTitle' },
-- { '', 'TransTitleRound' },
{ '', 'TransTitleRound' },
},
title_pos = 'center',
focusable = true,
zindex = 100,
}
if is_float then
opts.row = math.floor((vim.o.lines - M.height) / 2)
opts.col = math.floor((vim.o.columns - M.width) / 2)
else
opts.row = 2
opts.col = 2
end
M.id = api.nvim_open_win(M.bufnr, is_float, opts)
end
M.draw = function(content)
api.nvim_buf_set_option(M.bufnr, 'modifiable', true)
api.nvim_buf_set_lines(M.bufnr, 0, -1, false, content.lines)
if content.highlights then
for l, _hl in pairs(content.highlights) do
for _, hl in ipairs(_hl) do
api.nvim_buf_add_highlight(M.bufnr, M.ns, hl.name, l - 1, hl._start, hl._end) -- zero index
end
end
end
M.load_opts()
end
M.load_opts = function()
api.nvim_buf_set_option(M.bufnr, 'modifiable', false)
api.nvim_buf_set_option(M.bufnr, 'filetype', 'Trans')
api.nvim_win_set_option(M.id, 'winhl', 'Normal:TransWin,FloatBorder:TransBorder')
M['load_' .. M.view .. '_opts']()
end
M.load_hover_opts = function()
local keymap = conf.keymap[M.view]
local action = require('Trans.core.action')
for act, key in pairs(keymap) do
vim.keymap.set('n', key, action[act])
end
api.nvim_create_autocmd(
{ 'InsertEnter', 'CursorMoved', 'BufLeave', }, {
buffer = 0,
once = true,
callback = M.close,
})
api.nvim_win_set_option(M.id, 'wrap', M.view ~= 'float')
local height = util.get_height(M.bufnr, M.id)
if M.height > height then
api.nvim_win_set_height(M.id, height)
M.height = height
end
end
M.load_float_opts = function()
vim.keymap.set('n', 'q', function()
if api.nvim_win_is_valid(M.id) then
api.nvim_win_close(M.id, true)
end
end, { buffer = M.bufnr, silent = true })
vim.keymap.set('n', '<Esc>', function()
if api.nvim_win_is_valid(M.id) then
api.nvim_win_close(M.id, true)
end
end, { buffer = M.bufnr, silent = true })
end
M.close = function()
if api.nvim_win_is_valid(M.id) then
if conf.window.animation then
local function narrow()
if M.height > 1 then
M.height = M.height - 1
api.nvim_win_set_height(M.id, M.height)
vim.defer_fn(narrow, 13)
else
-- Wait animation done
vim.defer_fn(function ()
api.nvim_win_close(M.id, true)
end, 15)
end
end
narrow()
else
api.nvim_win_close(M.id, true)
end
end
end
M.show = function()
M.init(M.view or 'float')
M.load_opts()
end
return M

View File

@ -2,23 +2,41 @@ local M = {}
M.conf = {
view = {
input = 'float',
i = 'float',
n = 'hover',
v = 'hover',
},
window = {
-- animation = true,
hover = {
width = 36,
height = 26,
border = 'rounded',
animation = true,
hover = {
width = 36,
height = 26,
title = {
{ '', 'TransTitleRound' },
{ ' Trans', 'TransTitle' },
{ '', 'TransTitleRound' },
},
float = {
width = 0.8,
height = 0.8,
keymap = {
-- TODO :
pageup = '[[',
pagedown = ']]',
},
animation = 13,
},
float = {
width = 0.8,
height = 0.8,
border = 'rounded',
title = {
{ '', 'TransTitleRound' },
{ ' Trans', 'TransTitle' },
{ '', 'TransTitleRound' },
},
keymap = {
quit = 'q',
},
animation = 9,
},
order = {
-- offline = {
'title',
@ -35,29 +53,22 @@ M.conf = {
-- },
},
icon = {
title = '', -- 
star = '',
-- notfound = '',
-- yes = '',
-- no = ''
-- star = '⭐',
notfound = '',
yes = '✔️',
no = ''
-- star = '⭐',
-- notfound = '',
-- yes = '',
-- no = ''
},
db_path = '$HOME/.vim/dict/ultimate.db',
-- TODO :
-- engine = {
-- -- TODO
-- 'offline',
-- }
keymap = {
-- TODO
hover = {
pageup = '[[',
pagedown = ']]',
},
},
-- history = {
-- -- TOOD
-- }
@ -71,25 +82,24 @@ M.conf = {
-- TODO register word
}
M.setup = function(opts)
if opts then
M.conf = vim.tbl_deep_extend('force', M.conf, opts)
end
local window = M.conf.window
assert(window.hover.width > 1 and window.hover.height > 1)
assert(0 < window.float.width and window.float.width <= 1)
assert(0 < window.float.height and window.float.height <= 1)
local hover = M.conf.hover
local float = M.conf.float
window.float.height = math.floor((vim.o.lines - vim.o.cmdheight - 1) * window.float.height)
window.float.width = math.floor(vim.o.columns * window.float.width)
assert(hover.width > 1 and hover.height > 1)
assert(0 < float.width and float.width <= 1)
assert(0 < float.height and float.height <= 1)
float.height = math.floor((vim.o.lines - vim.o.cmdheight - 1) * float.height)
float.width = math.floor(vim.o.columns * float.width)
M.translate = require('Trans.core').translate
M.translate = require('Trans.translate')
require("Trans.setup")
end
M.augroup = vim.api.nvim_create_augroup('Trans', { clear = true })
return M

View File

@ -0,0 +1,38 @@
local _, db = pcall(require, 'sqlite.db')
if not _ then
error('Please check out sqlite.lua')
end
-- INFO : init database
local path = require('Trans').conf.db_path
local dict = db:open(path)
vim.api.nvim_create_autocmd('VimLeavePre', {
group = require("Trans").augroup,
callback = function()
if db:isopen() then
db:close()
end
end
})
return function(word)
local res = dict:select('stardict', {
where = {
word = word,
},
keys = {
'word',
'phonetic',
'definition',
'translation',
'pos',
'collins',
'oxford',
'tag',
'exchange',
},
limit = 1,
})
return res[1]
end

View File

@ -4,20 +4,17 @@ end
vim.api.nvim_create_user_command('Translate', function()
require("Trans").translate()
end, {
desc = ' 单词翻译',
})
end, { desc = ' 单词翻译', })
vim.api.nvim_create_user_command('TranslateInput', function()
require("Trans").translate('input')
require("Trans").translate('i')
end, { desc = ' 搜索翻译' })
vim.api.nvim_create_user_command('TranslateLast', function()
require("Trans").translate('last')
end, { desc = ' 显示上一次查询的内容' })
-- vim.api.nvim_create_user_command('TranslateLast', function()
-- require("Trans").translate('last')
-- end, { desc = ' 显示上一次查询的内容' })
local highlights = {
local hls = {
TransWord = {
fg = '#7ee787',
bold = true,
@ -58,11 +55,11 @@ local highlights = {
fg = '#faf743',
bold = true,
},
TransNotFound = {
TransFailed = {
fg = '#7aa89f',
},
}
for highlight, opt in pairs(highlights) do
vim.api.nvim_set_hl(0, highlight, opt)
for hl, opt in pairs(hls) do
vim.api.nvim_set_hl(0, hl, opt)
end

View File

@ -1,10 +1,3 @@
local M = {}
local conf = require('Trans').conf
local api = require('Trans.api')
local win = require('Trans.core.window')
local handler = require('Trans.core.handler')
local function get_select()
local s_start = vim.fn.getpos("v")
local s_end = vim.fn.getpos(".")
@ -23,35 +16,33 @@ local function get_select()
return table.concat(lines, '')
end
local function get_word(method)
if method == 'n' then
local function get_word(mode)
if mode == 'n' then
return vim.fn.expand('<cword>')
elseif method == 'v' then
elseif mode == 'v' then
vim.api.nvim_input('<ESC>')
return get_select()
elseif method == 'input' then
elseif mode == 'i' then
-- TODO Use Telescope with fuzzy finder
---@diagnostic disable-next-line: param-type-mismatch
return vim.fn.input('请输入您要查询的单词: ')
elseif method == 'last' then
return win.show()
else
error('unknown method' .. method)
error('invalid mode: ' .. mode)
end
end
local function translate(mode, view)
vim.validate {
mode = { mode, 's', true },
view = { view, 's', true }
}
M.translate = function(method, view)
method = method or vim.api.nvim_get_mode().mode
view = view or conf.view[method]
local word = get_word(method)
if word then
win.init(view)
local result = api.query('offline', word)
local content = handler.process(view, result)
win.draw(content)
end
---@diagnostic disable-next-line: undefined-field
mode = mode or vim.api.nvim_get_mode().mode
view = view or require('Trans').conf.view[mode]
assert(mode and view)
local word = get_word(mode)
require('Trans.view.' .. view)(word)
end
return M
return translate

View File

@ -1,39 +0,0 @@
local ffi = require('ffi')
local base64 = {}
local b64 = ffi.new('unsigned const char[65]',
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
function base64.encode(str)
local band, bor, lsh, rsh = bit.band, bit.bor, bit.lshift, bit.rshift
local len = #str
local enc_len = 4 * math.ceil(len / 3) -- (len + 2) // 3 * 4 after Lua 5.3
local src = ffi.new('unsigned const char[?]', len+1, str)
local enc = ffi.new('unsigned char[?]', enc_len+1)
local i, j = 0, 0
while i < len-2 do
enc[j] = b64[band(rsh(src[i], 2), 0x3F)]
enc[j+1] = b64[bor(lsh(band(src[i], 0x3), 4), rsh(band(src[i+1], 0xF0), 4))]
enc[j+2] = b64[bor(lsh(band(src[i+1], 0xF), 2), rsh(band(src[i+2], 0xC0), 6))]
enc[j+3] = b64[band(src[i+2], 0x3F)]
i, j = i+3, j+4
end
if i < len then
enc[j] = b64[band(rsh(src[i], 2), 0x3F)]
if i == len-1 then
enc[j+1] = b64[lsh(band(src[i], 0x3), 4)]
enc[j+2] = 0x3D
else
enc[j+1] = b64[bor(lsh(band(src[i], 0x3), 4), rsh(band(src[i+1], 0xF0), 4))]
enc[j+2] = b64[lsh(band(src[i+1], 0xF), 2)]
end
enc[j+3] = 0x3D
end
return ffi.string(enc, enc_len)
end
return base64

View File

@ -1,18 +0,0 @@
local function get_height(bufnr, winid)
if not vim.wo[winid].wrap then
return vim.api.nvim_buf_line_count(bufnr)
end
local width = vim.api.nvim_win_get_width(winid)
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local height = 0
for i = 1, #lines do
height = height + math.max(1, (math.ceil(vim.fn.strwidth(lines[i]) / width)))
end
return height
end
return {
get_height = get_height,
}

View File

@ -1,431 +0,0 @@
local md5 = {}
-- local md5 = {
-- _VERSION = "md5.lua 1.1.0",
-- _DESCRIPTION = "MD5 computation in Lua (5.1-3, LuaJIT)",
-- _URL = "https://github.com/kikito/md5.lua",
-- _LICENSE = [[
-- MIT LICENSE
--
-- Copyright (c) 2013 Enrique García Cota + Adam Baldwin + hanzao + Equi 4 Software
--
-- Permission is hereby granted, free of charge, to any person obtaining a
-- copy of this software and associated documentation files (the
-- "Software"), to deal in the Software without restriction, including
-- without limitation the rights to use, copy, modify, merge, publish,
-- distribute, sublicense, and/or sell copies of the Software, and to
-- permit persons to whom the Software is furnished to do so, subject to
-- the following conditions:
--
-- The above copyright notice and this permission notice shall be included
-- in all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-- ]]
-- }
-- bit lib implementions
local char, byte, format, rep, sub =
string.char, string.byte, string.format, string.rep, string.sub
local bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift
local ok, bit = pcall(require, 'bit')
local ok_ffi, ffi = pcall(require, 'ffi')
if ok then
bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift = bit.bor, bit.band, bit.bnot, bit.bxor, bit.rshift,
bit.lshift
else
ok, bit = pcall(require, 'bit32')
if ok then
bit_not = bit.bnot
local tobit = function(n)
return n <= 0x7fffffff and n or -(bit_not(n) + 1)
end
local normalize = function(f)
return function(a, b) return tobit(f(tobit(a), tobit(b))) end
end
bit_or, bit_and, bit_xor = normalize(bit.bor), normalize(bit.band), normalize(bit.bxor)
bit_rshift, bit_lshift = normalize(bit.rshift), normalize(bit.lshift)
else
local function tbl2number(tbl)
local result = 0
local power = 1
for i = 1, #tbl do
result = result + tbl[i] * power
power = power * 2
end
return result
end
local function expand(t1, t2)
local big, small = t1, t2
if (#big < #small) then
big, small = small, big
end
-- expand small
for i = #small + 1, #big do
small[i] = 0
end
end
local to_bits -- needs to be declared before bit_not
bit_not = function(n)
local tbl = to_bits(n)
local size = math.max(#tbl, 32)
for i = 1, size do
if (tbl[i] == 1) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl2number(tbl)
end
-- defined as local above
to_bits = function(n)
if (n < 0) then
-- negative
return to_bits(bit_not(math.abs(n)) + 1)
end
-- to bits table
local tbl = {}
local cnt = 1
local last
while n > 0 do
last = n % 2
tbl[cnt] = last
n = (n - last) / 2
cnt = cnt + 1
end
return tbl
end
bit_or = function(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
for i = 1, #tbl_m do
if (tbl_m[i] == 0 and tbl_n[i] == 0) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl2number(tbl)
end
bit_and = function(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
for i = 1, #tbl_m do
if (tbl_m[i] == 0 or tbl_n[i] == 0) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl2number(tbl)
end
bit_xor = function(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
for i = 1, #tbl_m do
if (tbl_m[i] ~= tbl_n[i]) then
tbl[i] = 1
else
tbl[i] = 0
end
end
return tbl2number(tbl)
end
bit_rshift = function(n, bits)
local high_bit = 0
if (n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
high_bit = 0x80000000
end
local floor = math.floor
for i = 1, bits do
n = n / 2
n = bit_or(floor(n), high_bit)
end
return floor(n)
end
bit_lshift = function(n, bits)
if (n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
end
for i = 1, bits do
n = n * 2
end
return bit_and(n, 0xFFFFFFFF)
end
end
end
-- convert little-endian 32-bit int to a 4-char string
local lei2str
-- function is defined this way to allow full jit compilation (removing UCLO instruction in LuaJIT)
if ok_ffi then
local ct_IntType = ffi.typeof("int[1]")
lei2str = function(i) return ffi.string(ct_IntType(i), 4) end
else
lei2str = function(i)
local f = function(s) return char(bit_and(bit_rshift(i, s), 255)) end
return f(0) .. f(8) .. f(16) .. f(24)
end
end
-- convert raw string to big-endian int
local function str2bei(s)
local v = 0
for i = 1, #s do
v = v * 256 + byte(s, i)
end
return v
end
-- convert raw string to little-endian int
local str2lei
if ok_ffi then
local ct_constcharptr = ffi.typeof("const char*")
local ct_constintptr = ffi.typeof("const int*")
str2lei = function(s)
local int = ct_constcharptr(s)
return ffi.cast(ct_constintptr, int)[0]
end
else
str2lei = function(s)
local v = 0
for i = #s, 1, -1 do
v = v * 256 + byte(s, i)
end
return v
end
end
-- cut up a string in little-endian ints of given size
local function cut_le_str(s)
return {
str2lei(sub(s, 1, 4)),
str2lei(sub(s, 5, 8)),
str2lei(sub(s, 9, 12)),
str2lei(sub(s, 13, 16)),
str2lei(sub(s, 17, 20)),
str2lei(sub(s, 21, 24)),
str2lei(sub(s, 25, 28)),
str2lei(sub(s, 29, 32)),
str2lei(sub(s, 33, 36)),
str2lei(sub(s, 37, 40)),
str2lei(sub(s, 41, 44)),
str2lei(sub(s, 45, 48)),
str2lei(sub(s, 49, 52)),
str2lei(sub(s, 53, 56)),
str2lei(sub(s, 57, 60)),
str2lei(sub(s, 61, 64)),
}
end
-- An MD5 mplementation in Lua, requires bitlib (hacked to use LuaBit from above, ugh)
-- 10/02/2001 jcw@equi4.com
local CONSTS = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476
}
local f = function(x, y, z) return bit_or(bit_and(x, y), bit_and(-x - 1, z)) end
local g = function(x, y, z) return bit_or(bit_and(x, z), bit_and(y, -z - 1)) end
local h = function(x, y, z) return bit_xor(x, bit_xor(y, z)) end
local i = function(x, y, z) return bit_xor(y, bit_or(x, -z - 1)) end
local z = function(ff, a, b, c, d, x, s, ac)
a = bit_and(a + ff(b, c, d) + x + ac, 0xFFFFFFFF)
-- be *very* careful that left shift does not cause rounding!
return bit_or(bit_lshift(bit_and(a, bit_rshift(0xFFFFFFFF, s)), s), bit_rshift(a, 32 - s)) + b
end
local function transform(A, B, C, D, X)
local a, b, c, d = A, B, C, D
local t = CONSTS
a = z(f, a, b, c, d, X[0], 7, t[1])
d = z(f, d, a, b, c, X[1], 12, t[2])
c = z(f, c, d, a, b, X[2], 17, t[3])
b = z(f, b, c, d, a, X[3], 22, t[4])
a = z(f, a, b, c, d, X[4], 7, t[5])
d = z(f, d, a, b, c, X[5], 12, t[6])
c = z(f, c, d, a, b, X[6], 17, t[7])
b = z(f, b, c, d, a, X[7], 22, t[8])
a = z(f, a, b, c, d, X[8], 7, t[9])
d = z(f, d, a, b, c, X[9], 12, t[10])
c = z(f, c, d, a, b, X[10], 17, t[11])
b = z(f, b, c, d, a, X[11], 22, t[12])
a = z(f, a, b, c, d, X[12], 7, t[13])
d = z(f, d, a, b, c, X[13], 12, t[14])
c = z(f, c, d, a, b, X[14], 17, t[15])
b = z(f, b, c, d, a, X[15], 22, t[16])
a = z(g, a, b, c, d, X[1], 5, t[17])
d = z(g, d, a, b, c, X[6], 9, t[18])
c = z(g, c, d, a, b, X[11], 14, t[19])
b = z(g, b, c, d, a, X[0], 20, t[20])
a = z(g, a, b, c, d, X[5], 5, t[21])
d = z(g, d, a, b, c, X[10], 9, t[22])
c = z(g, c, d, a, b, X[15], 14, t[23])
b = z(g, b, c, d, a, X[4], 20, t[24])
a = z(g, a, b, c, d, X[9], 5, t[25])
d = z(g, d, a, b, c, X[14], 9, t[26])
c = z(g, c, d, a, b, X[3], 14, t[27])
b = z(g, b, c, d, a, X[8], 20, t[28])
a = z(g, a, b, c, d, X[13], 5, t[29])
d = z(g, d, a, b, c, X[2], 9, t[30])
c = z(g, c, d, a, b, X[7], 14, t[31])
b = z(g, b, c, d, a, X[12], 20, t[32])
a = z(h, a, b, c, d, X[5], 4, t[33])
d = z(h, d, a, b, c, X[8], 11, t[34])
c = z(h, c, d, a, b, X[11], 16, t[35])
b = z(h, b, c, d, a, X[14], 23, t[36])
a = z(h, a, b, c, d, X[1], 4, t[37])
d = z(h, d, a, b, c, X[4], 11, t[38])
c = z(h, c, d, a, b, X[7], 16, t[39])
b = z(h, b, c, d, a, X[10], 23, t[40])
a = z(h, a, b, c, d, X[13], 4, t[41])
d = z(h, d, a, b, c, X[0], 11, t[42])
c = z(h, c, d, a, b, X[3], 16, t[43])
b = z(h, b, c, d, a, X[6], 23, t[44])
a = z(h, a, b, c, d, X[9], 4, t[45])
d = z(h, d, a, b, c, X[12], 11, t[46])
c = z(h, c, d, a, b, X[15], 16, t[47])
b = z(h, b, c, d, a, X[2], 23, t[48])
a = z(i, a, b, c, d, X[0], 6, t[49])
d = z(i, d, a, b, c, X[7], 10, t[50])
c = z(i, c, d, a, b, X[14], 15, t[51])
b = z(i, b, c, d, a, X[5], 21, t[52])
a = z(i, a, b, c, d, X[12], 6, t[53])
d = z(i, d, a, b, c, X[3], 10, t[54])
c = z(i, c, d, a, b, X[10], 15, t[55])
b = z(i, b, c, d, a, X[1], 21, t[56])
a = z(i, a, b, c, d, X[8], 6, t[57])
d = z(i, d, a, b, c, X[15], 10, t[58])
c = z(i, c, d, a, b, X[6], 15, t[59])
b = z(i, b, c, d, a, X[13], 21, t[60])
a = z(i, a, b, c, d, X[4], 6, t[61])
d = z(i, d, a, b, c, X[11], 10, t[62])
c = z(i, c, d, a, b, X[2], 15, t[63])
b = z(i, b, c, d, a, X[9], 21, t[64])
return bit_and(A + a, 0xFFFFFFFF), bit_and(B + b, 0xFFFFFFFF),
bit_and(C + c, 0xFFFFFFFF), bit_and(D + d, 0xFFFFFFFF)
end
----------------------------------------------------------------
local function md5_update(self, s)
self.pos = self.pos + #s
s = self.buf .. s
for ii = 1, #s - 63, 64 do
local X = cut_le_str(sub(s, ii, ii + 63))
assert(#X == 16)
X[0] = table.remove(X, 1) -- zero based!
self.a, self.b, self.c, self.d = transform(self.a, self.b, self.c, self.d, X)
end
self.buf = sub(s, math.floor(#s / 64) * 64 + 1, #s)
return self
end
local function md5_finish(self)
local msgLen = self.pos
local padLen = 56 - msgLen % 64
if msgLen % 64 > 56 then padLen = padLen + 64 end
if padLen == 0 then padLen = 64 end
local s = char(128) ..
rep(char(0), padLen - 1) .. lei2str(bit_and(8 * msgLen, 0xFFFFFFFF)) .. lei2str(math.floor(msgLen / 0x20000000))
md5_update(self, s)
assert(self.pos % 64 == 0)
return lei2str(self.a) .. lei2str(self.b) .. lei2str(self.c) .. lei2str(self.d)
end
----------------------------------------------------------------
function md5.new()
return { a = CONSTS[65], b = CONSTS[66], c = CONSTS[67], d = CONSTS[68],
pos = 0,
buf = '',
update = md5_update,
finish = md5_finish }
end
function md5.tohex(s)
return format("%08x%08x%08x%08x", str2bei(sub(s, 1, 4)), str2bei(sub(s, 5, 8)), str2bei(sub(s, 9, 12)),
str2bei(sub(s, 13, 16)))
end
function md5.sum(s)
return md5.new():update(s):finish()
end
function md5.sumhexa(s)
return md5.tohex(md5.sum(s))
end
return md5

28
lua/Trans/view/float.lua Normal file
View File

@ -0,0 +1,28 @@
local m_window
local m_result
return function(word)
-- TODO :online query
m_result = require('Trans.query.offline')(word)
m_window = require('Trans.window')
local float = require('Trans').conf.float
local opt = {
relative = 'editor',
width = float.width,
height = float.height,
border = float.border,
title = float.title,
row = math.floor((vim.o.lines - float.height) / 2),
col = math.floor((vim.o.columns - float.width) / 2),
}
-- 创建窗口
m_window.init(true, opt)
m_window.center('https:github.com/JuanZoran/Trans.nvim', '@text.uri') -- only show color with treesiter
m_window.draw()
m_window.map('q', function()
m_window.try_close(float.animation)
end)
end

235
lua/Trans/view/hover.lua Normal file
View File

@ -0,0 +1,235 @@
local conf = require('Trans').conf
local icon = conf.icon
local m_window = require('Trans.window')
local m_result
local m_indent = ' '
local title = function(str)
local wrapper = m_window.text_wrap()
-- wrapper('', 'TransTitleRound')
wrapper('', 'TransTitleRound')
wrapper(str, 'TransTitle')
wrapper('', 'TransTitleRound')
-- wrapper('', 'TransTitleRound')
end
local tag_map = {
zk = '中考',
gk = '高考',
ky = '考研',
cet4 = '四级',
cet6 = '六级',
ielts = '雅思',
toefl = '托福',
gre = 'gre ',
}
local pos_map = {
a = '代词pron ',
c = '连接词conj ',
i = '介词prep ',
j = '形容词adj ',
m = '数词num ',
n = '名词n ',
p = '代词pron ',
r = '副词adv ',
u = '感叹词int ',
v = '动词v ',
x = '否定标记not ',
t = '不定式标记infm ',
d = '限定词determiner',
}
local exchange_map = {
['p'] = '过去式 ',
['d'] = '过去分词 ',
['i'] = '现在分词 ',
['r'] = '比较级 ',
['t'] = '最高级 ',
['s'] = '复数 ',
['0'] = '原型 ',
['1'] = '类别 ',
['3'] = '第三人称单数',
['f'] = '第三人称单数',
}
local exist = function(str)
return str and str ~= ''
end
local process = {
title = function()
local line = m_window.line_wrap()
line.add_item(
m_result.word,
'TransWord'
)
line.add_item(
'[' .. (exist(m_result.phonetic) and m_result.phonetic or icon.notfound) .. ']',
'TransPhonetic'
)
line.add_item(
(exist(m_result.collins) and icon.star:rep(m_result.collins) or icon.notfound),
'TransCollins'
)
line.add_item(
(m_result.oxford == 1 and icon.yes or icon.no)
)
line.load()
end,
tag = function()
if exist(m_result.tag) then
title('标签')
local tags = {}
local size = 0
local interval = ' '
for tag in vim.gsplit(m_result.tag, ' ', true) do
size = size + 1
tags[size] = tag_map[tag]
end
for i = 1, size, 3 do
m_window.addline(
m_indent .. tags[i] .. interval .. (tags[i + 1] or '') .. interval .. (tags[i + 2] or ''),
'TransTag'
)
end
m_window.addline('')
end
end,
pos = function()
if exist(m_result.pos) then
title('词性')
for pos in vim.gsplit(m_result.pos, '/', true) do
m_window.addline(
m_indent .. pos_map[pos:sub(1, 1)] .. pos:sub(3) .. '%',
'TransPos'
)
end
m_window.addline('')
end
end,
exchange = function()
if exist(m_result.exchange) then
title('词形变化')
local interval = ' '
for exc in vim.gsplit(m_result.exchange, '/', true) do
m_window.addline(
m_indent .. exchange_map[exc:sub(1, 1)] .. interval .. exc:sub(3),
'TransExchange'
)
end
m_window.addline('')
end
end,
translation = function()
title('中文翻译')
for trs in vim.gsplit(m_result.translation, '\n', true) do
m_window.addline(
m_indent .. trs,
'TransTranslation'
)
end
m_window.addline('')
end,
definition = function()
if exist(m_result.definition) then
title('英文注释')
for def in vim.gsplit(m_result.definition, '\n', true) do
def = def:gsub('^%s+', '', 1) -- TODO :判断是否需要分割空格
m_window.addline(
m_indent .. def,
'TransDefinition'
)
end
m_window.addline('')
end
end,
failed = function()
m_window.addline(
icon.notfound .. m_indent .. '没有找到相关的翻译',
'TransFailed'
)
end,
}
local action = {
pageup = function()
m_window.normal('gg')
end,
pagedown = function()
m_window.normal('G')
end,
}
return function(word)
vim.validate {
word = { word, 's' },
}
-- 目前只处理了本地数据库的查询
m_result = require('Trans.query.offline')(word)
local hover = conf.hover
local opt = {
relative = 'cursor',
width = hover.width,
height = hover.height,
title = hover.title,
border = hover.border,
col = 2,
row = 2,
}
m_window.init(false, opt)
if m_result then
for _, field in ipairs(conf.order) do
process[field]()
end
else
process.failed()
end
m_window.draw()
-- 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
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
action[act]()
end
end)
end
end

289
lua/Trans/window.lua Normal file
View File

@ -0,0 +1,289 @@
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.<++> --> <++>
function string:width()
---@diagnostic disable-next-line: param-type-mismatch
return vim.fn.strwidth(self)
end
--- =================== Load Window Options ================================
M.init = function(entry, opts)
vim.validate {
entry = { entry, 'b' },
opts = { opts, 't' }
}
local opt = {
relative = nil,
width = nil,
height = nil,
border = nil,
title = nil,
col = nil,
row = nil,
title_pos = 'center',
focusable = false,
zindex = 100,
style = 'minimal',
}
for k, v in pairs(opts) 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
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],
})
end
value = value .. item[1]
end
load_item(1)
for i = 2, size do
value = value .. interval
load_item(i)
end
insert_line(value)
end
}
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