refactor: imporve this plugin file framework

This commit is contained in:
JuanZoran
2023-01-19 17:06:26 +08:00
parent 00f394ac99
commit e80e6efbc8
20 changed files with 598 additions and 1166 deletions

View File

@@ -1,129 +0,0 @@
# 字段说明
<!--toc:start-->
- [字段说明](#字段说明)
- [本地](#本地)
- [有道](#有道)
- [中英](#中英)
- [百度](#百度)
- [返回结果](#返回结果)
- [彩云小译](#彩云小译)
- [必应](#必应)
- [腾讯翻译君](#腾讯翻译君)
- [阿里翻译](#阿里翻译)
- [火山翻译](#火山翻译)
- [金山词霸](#金山词霸)
<!--toc:end-->
## 本地
- `word`
查询的字符串
- `phonetic`
音标
- `collins`
柯林斯星级: integer
- `oxford`
是否为牛津词汇: integer (1为是)
- `tag`
标签
- `pos`
词性
- `exchange`
词态变化
- `translation`
中文翻译
- `definition`
英文注释
## 有道
### 中英
basic JSONObject 简明释义
phonetic text 词典音标
usPhonetic text 美式音标
ukPhonetic text 英式音标
ukSpeech text 英式发音
usSpeech text 美式发音
explains text 基本释义
text text 短语
explain String Array 词义解释列表
wordFormats Object Array 单词形式变化列表
name String 形式名称,例如:复数
web JSONArray 网络释义
phrase String 词组
meaning String 含义
synonyms JSONObject 近义词
pos String 词性
words String Array 近义词列表
trans String 释义
antonyms ObjectArray 反义词
relatedWords JSONArray 相关词
wordNet JSONObject 汉语词典网络释义
phonetic String 发音
meanings ObjectArray 释义
meaning String 释义
example array 示例
dict String 词典deeplink
webDict String 词典网页deeplink
sentenceSample text 例句
sentence text 例句
sentenceBold text 将查询内容加粗的例句
translation text 例句翻译
wfs text 单词形式变化
exam_type text 考试类型
## 百度
from string 源语言 返回用户指定的语言或者自动检测出的语种源语言设为auto时
to string 目标语言 返回用户指定的目标语言
trans_result array 翻译结果 返回翻译结果包括src和dst字段
trans_result.*.src string 原文 接入举例中的“apple”
trans_result.*dst string 译文 接入举例中的“苹果”
error_code integer 错误码 仅当出现错误时显示
以下字段仅开通了词典、tts用户可见
src_tts string 原文tts链接 mp3格式暂时无法指定发音
dst_tts string 译文tts链接 mp3格式暂时无法指定发音
dict string 中英词典资源 返回中文或英文词典资源,包含音标;简明释义等内容
### 返回结果
- 英-> 中
```json
{
"from": "en",
"to": "zh",
"trans_result": [
{
"src": "apple",
"dst": "苹果"
}
]
}
```
- 中->英
```json
{
"from": "zh",
"to": "en",
"trans_result": [
{
"src": "中国",
"dst": "China"
}
]
}
```
## 彩云小译
句子翻译
> sh xiaoyi.sh en2zh "You know some birds are not meant to be caged, their feathers are just too bright."
> 你知道有些鸟不应该被关在笼子里,它们的羽毛太亮了。
## 必应
## 腾讯翻译君
## 阿里翻译
## 火山翻译
## 金山词霸
## Dictionary

View File

@@ -1,132 +0,0 @@
# API说明
<!--toc:start-->
- [API说明](#api说明)
- [数据结构](#数据结构)
- [翻译](#翻译)
- [窗口](#窗口)
- [翻译结果](#翻译结果)
- [内容单位](#内容单位)
- [窗口绘制逻辑](#窗口绘制逻辑)
- [hover](#hover)
- [float](#float)
<!--toc:end-->
## 数据结构
### 翻译
- `word`
待翻译的字符串: string
- `sentence`
是否为句子: boolean
- `result`
翻译查询的结果: table
> 见: [翻译结果](#翻译结果)
- `engine`
### 窗口
- `style`
风格: string
- `height`
高度: integer
- `width`
宽度: integer
- `border`
边框样式: string
- `winhl`
窗口的高亮: string
### 翻译结果
**无特殊说明, 所有字段均为`string`类型**
- `word`
查询的字符串
- `phonetic`
音标
- `collins`
柯林斯星级: integer
- `oxford`
是否为牛津词汇: integer (1为是)
- `tag`
标签
- `pos`
词性
- `exchange`
词态变化
- `translation`
中文翻译
- `definition`
英文注释
### 内容单位
- `field` 字段
> 是展示的最小单位
**属性**
- `1`
存储的文本: string
- `2` (optional)
对应的高亮: string
- `_start`
起始行: integer
- `_end`
结束行: integer
> **注意:** `_start` 和`_end` 字段只有已经被展示到窗口后才会被设置
**方法**
-
- `line`
> 窗口展示的每一行, 一个行有多个`field`
**属性**
- `text`
当前保存的字符串: string
- `hls`
行内的高亮: table
- `index`
行号[0为起始下标]
- `fields`
行内保存的`field`
- `status`
行内是否需要更新: boolean
> **注意:** `num` 只有已经被展示到窗口后才会被设置
**方法**
- `update`
更新`text``hls`
- `data`
获取行的text
- `insert`
添加新`field`
- `add_highlight`
添加高亮
> 参数: bufnr
- `content` 内容
> 窗口展示的单位, 一个内容内有多个`line`
**方法**
- `data`
返回lines和highlights
- `insert`
插入新的行
- `attach`
将内容展示到buffer
> 参数: bufnr
# 窗口绘制逻辑
- 获取所有组件
## hover
- 按照order顺序加载
- 按组件间距为4计算组件个数能否在一行以内放下
- 放不下则按照组件间距为4 计算组件个数并对齐
- 放得下则重新计算组间距
获得组件行内容, 设置到行内
获取组件高亮, 设置高亮
## float
由定义好的逻辑,绘制整个窗口

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,57 +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 routes = {
offline = function(word)
local res = dict:select('stardict', {
where = {
word = word,
},
keys = {
'word',
'phonetic',
'definition',
'translation',
'pos',
'collins',
'oxford',
'tag',
'exchange',
},
})
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,96 +0,0 @@
# 命令说明
<!--toc:start-->
- [命令说明](#命令说明)
- [Translate](#translate)
- [TranslateInput](#translateinput)
- [TranslateHistory](#translatehistory)
- [自定义](#自定义)
- [可选项说明](#可选项说明)
- [示例](#示例)
<!--toc:end-->
## Translate
**窗口风格默认为:** `cursor`
- 动作(action):
- `vsplit` 水平分屏
- `split` 垂直分屏
- `float` 窗口样式又`cursor` 变为`float`
- `online_query` 使用在线引擎重新进行查询
- `history_insert` 将此次查询的单词记录到历史记录
- `next` 展示下一个引擎的查询结果(如果默认设置了多个引擎)
- `prev` 展示上一个查询结果
> 如果没有设置自动保存历史的话
- `history` 查看历史查询的记录
- `online_query`:
- `local_add` 将此次查询的结果添加到本地数据库
> **如果本地已经存在该单词,会询问是否需要覆盖掉相同的字段**
- `local_update` 和*local_add* 类似, 但是不会询问是否覆盖
- `diff` 对比本地查询结果和此次在线查询的区别
> **注意**: 动作是任何窗口通用的
## TranslateInput
**窗口风格默认为:** `float`
- 自行得到要查询的单词
- TODO:
- fuzzy match
## TranslateHistory
**窗口风格默认为:** `float`
- 查看历史查询
---
## 自定义
### 可选项说明
- 查询方式(method): `string`
- `input` 自行输入需要查询的单词
- `last` 显示上一次查询的结果
- `history`
- 查询引擎(engine): `string | table`
- `offline` 离线的数据库
- `youcao` 有道api
- `baidu` 百度api
- `google` 谷歌api
- `bing` 必应api
- `iciba` 金山词霸api
- `xunfei` 讯飞api
- 窗口风格(win): `string | table`
- 样式(style):
- `cursor` 在光标附近弹出
- `float` 悬浮窗口
- `split` 在上方或者下方分屏
- `vsplit` 在左边或者右边分屏
- 高度(height):
- `value > 1` 最大高度
- `0 <= value <= 1` 相对高度
- `0 < value` 无限制
- 宽度(width):
> 和`高度(height)`相同
### 示例
```lua
vim.keymap.set('n', 'mi', function ()
require('Trans').translate({
method = 'input', -- 不填则自动判断mode获取查询的单词
engine = { -- 异步查询所有的引擎, 按照列表
'offline',
'youdao',
'baidu'
},
-- win = 'cursor'
win = {
style = 'cursor',
height = 50,
width = 30,
}
})
end, { desc = '在光标旁弹出输入的单词释义'})
```

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,168 +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()
local l = 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[l], {
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[l] = value
self.len = l
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,222 +0,0 @@
---@diagnostic disable: unused-local
local M = {}
local icon = require('Trans').conf.icon
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'
)
line.add_item(
'[' .. (exist(result.phonetic) and result.phonetic or icon.notfound) .. ']',
'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 = {}
local size = 0
for tag in vim.gsplit(result.tag, ' ', true) do
size = size + 1
tags[size] = tag_map[tag]
end
for i = 1, size, 3 do
content:addline(
indent .. tags[i] .. ' ' .. (tags[i + 1] or '') .. ' ' .. (tags[i + 2] or ''),
'TransTag'
)
end
content:addline('')
end
end,
pos = function(result, content)
if exist(result.pos) then
expl(content, '词性')
for pos in vim.gsplit(result.pos, '/', true) do
content:addline(
indent .. pos_map[pos:sub(1, 1)] .. pos:sub(3) .. '%',
'TransPos'
)
end
content:addline('')
end
end,
exchange = function(result, content)
if exist(result.exchange) then
expl(content, '词形变化')
for exc in vim.gsplit(result.exchange, '/', true) do
content:addline(
indent .. exchange_map[exc:sub(1, 1)] .. ' ' .. exc:sub(3),
'TransExchange'
)
end
content:addline('')
end
end,
translation = function(result, content)
expl(content, '中文翻译')
for trs in vim.gsplit(result.translation, '\n', true) do
content:addline(
indent .. trs,
'TransTranslation'
)
end
content:addline('')
end,
definition = function(result, content)
if exist(result.definition) then
expl(content, '英文注释')
for def in vim.gsplit(result.definition, '\n', true) do
def = def:gsub('^%s+', '', 1) -- TODO :判断是否需要分割空格
content:addline(
indent .. def,
'TransDefinition'
)
end
content:addline('')
end
end,
failed = function(content)
content:addline(
icon.notfound .. indent .. '没有找到相关的翻译',
'TransFailed'
)
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,20 +2,35 @@ local M = {}
M.conf = {
view = {
input = 'float',
i = 'float',
n = 'hover',
v = 'hover',
},
window = {
border = 'rounded',
animation = true,
-- animation = true,
hover = {
width = 36,
height = 26,
border = 'rounded',
title = {
{ '', 'TransTitleRound' },
-- { '', 'TransTitleRound' },
{ ' Trans', 'TransTitle' },
-- { '', 'TransTitleRound' },
{ '', 'TransTitleRound' },
},
},
float = {
width = 0.8,
height = 0.8,
border = 'rounded',
title = {
{ '', 'TransTitleRound' },
-- { '', 'TransTitleRound' },
{ ' Trans', 'TransTitle' },
-- { '', 'TransTitleRound' },
{ '', 'TransTitleRound' },
},
},
},
@@ -35,22 +50,16 @@ 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 = {
@@ -58,6 +67,12 @@ M.conf = {
pagedown = ']]',
},
},
-- TODO :
-- engine = {
-- -- TODO
-- 'offline',
-- }
-- history = {
-- -- TOOD
-- }
@@ -71,7 +86,6 @@ M.conf = {
-- TODO register word
}
M.setup = function(opts)
if opts then
M.conf = vim.tbl_deep_extend('force', M.conf, opts)
@@ -83,13 +97,10 @@ M.setup = function(opts)
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)
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

@@ -7,15 +7,14 @@ vim.api.nvim_create_user_command('Translate', function()
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,
@@ -61,6 +60,6 @@ local highlights = {
},
}
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

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

@@ -0,0 +1,43 @@
-- local function generate_opts(view)
-- -- TODO :
-- vim.validate {
-- view = { view, 's' },
-- }
-- local hover = conf.hover
-- local float = conf.float
-- local title_pos = 'center'
-- local title = {
-- { '', 'TransTitleRound' },
-- -- { '', 'TransTitleRound' },
-- { conf.icon.title .. ' Trans', 'TransTitle' },
-- -- { '', 'TransTitleRound' },
-- { '', 'TransTitleRound' },
-- }
--
-- return ({
-- hover = {
-- relative = 'cursor',
-- width = hover.width,
-- height = hover.height,
-- border = hover.border,
-- title = title,
-- title_pos = title_pos,
-- focusable = false,
-- zindex = 100,
-- col = 2,
-- row = 2,
-- },
-- float = {
-- relative = 'editor',
-- width = float.width,
-- height = float.height,
-- border = float.border,
-- title = title,
-- title_pos = title_pos,
-- focusable = false,
-- zindex = 75,
-- row = math.floor((vim.o.lines - float.height) / 2),
-- col = math.floor((vim.o.columns - float.width) / 2),
-- },
-- })[view]
-- end

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

@@ -0,0 +1,211 @@
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 function handle(word)
vim.validate {
word = { word, 's' },
}
-- 目前只处理了本地数据库的查询
m_result = require('Trans.query.offline')(word)
local hover = conf.window.hover
hover.relative = 'cursor'
hover.col = 2
hover.row = 2
m_window.init(false, hover)
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 = m_window.try_close,
})
m_window.set('wrap', true)
m_window.adjust()
end
return handle

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

@@ -0,0 +1,251 @@
local M = {}
local api = vim.api
-- local conf = require('Trans').conf
--- =================== Window Attributes ================================
-- M.height --> 窗口的高度
-- M.size --> 窗口的行数
-- M.width --> 窗口的宽度
-- M.winid --> 窗口的handle
-- M.bufnr --> 窗口对应的buffer的handle
M.bufnr = api.nvim_create_buf(false, true)
M.hl = api.nvim_create_namespace('TransWinHl')
-- M.<++> --> <++>
api.nvim_buf_set_option(M.bufnr, 'filetype', 'Trans')
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.winid = api.nvim_open_win(M.bufnr, entry, opt)
M.set('winhl', 'Normal:TransWin,FloatBorder:TransBorder')
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
M.try_close = function()
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, 13)
else
-- Wait animation done
vim.defer_fn(function()
api.nvim_win_close(M.winid, true)
end, 15)
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
---设置窗口选项
---@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
--- =================== Window lines ================================
M.lines = {}
---- ============ Utility functions ============
local function insert_line(text)
vim.validate {
text = { text, 's' },
}
M.size = M.size + 1
M.lines[M.size] = text
end
local function current_line_index()
return M.size - 1
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 = current_line_index(), -- NOTE : 高亮的行号是以0为第一行
_start = 0,
_end = -1,
})
elseif type(opt) == 'table' then
-- TODO :
error('TODO')
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 = current_line_index() + 1,
_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 = current_line_index(),
_start = _start,
_end = _end,
})
end
M.lines[l] = M.lines[l] .. text
end
end
--- =================== Window Highlights ================================
M.highlights = {}
--- TODO : add helpful function for highlights
return M