Compare commits
30 Commits
experiment
...
dev
Author | SHA1 | Date | |
---|---|---|---|
82779cc299 | |||
eb68b8bb95 | |||
073e8667b2 | |||
c2e56f7769 | |||
5845a40d94 | |||
f17171d28b | |||
ab212fefe1 | |||
586cd6ff08 | |||
b5e626a88c | |||
e993dc12be | |||
871cc1e48a | |||
6e02f3b1ba | |||
377c84c146 | |||
ba08913b1e | |||
854bce7b5a | |||
a48caf51e1 | |||
24a838646d | |||
156e03306a | |||
0a1b6cf742 | |||
758b9c0b33 | |||
3faab735fb | |||
335c5079cc | |||
38b8e20729 | |||
fcde85544a | |||
2ae2effecc | |||
da6e717f2c | |||
576f1eb66a | |||
9357574b5c | |||
987e1a3341 | |||
b27bc4ed26 |
21
LICENCE
Normal file
21
LICENCE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Zoran
|
||||||
|
|
||||||
|
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.
|
250
README.md
250
README.md
@ -2,51 +2,48 @@
|
|||||||
|
|
||||||
<!--toc:start-->
|
<!--toc:start-->
|
||||||
|
|
||||||
- [Trans.nvim](#transnvim) - [注意: 当前分支目前没有发布, README.md 的描述并不准确, 遇到问题请切换到 `main`分支或者联系我](#注意-当前分支目前没有发布-readmemd-的描述并不准确-遇到问题请切换到-main分支或者联系我)
|
- [Trans.nvim](#transnvim)
|
||||||
- [特点](#特点)
|
- [特点](#特点)
|
||||||
- [屏幕截图](#屏幕截图)
|
- [屏幕截图](#屏幕截图)
|
||||||
- [演示](#演示)
|
- [演示](#演示)
|
||||||
- [离线查询](#离线查询)
|
- [离线查询](#离线查询)
|
||||||
- [\*在线查询\*\* (有道)](#在线查询-有道)
|
- [在线查询演示 (有道)](#在线查询演示-有道)
|
||||||
- [主题](#主题)
|
- [主题](#主题)
|
||||||
- [安装](#安装)
|
- [安装](#安装)
|
||||||
- [配置](#配置)
|
- [配置](#配置)
|
||||||
|
- [快捷键绑定](#快捷键绑定)
|
||||||
- [高亮组](#高亮组)
|
- [高亮组](#高亮组)
|
||||||
- [声明](#声明)
|
- [声明](#声明)
|
||||||
- [感谢](#感谢)
|
- [感谢](#感谢)
|
||||||
- [贡献](#贡献)
|
- [贡献](#贡献)
|
||||||
|
- [从 v1 (main)分支迁移](#从-v1-main分支迁移)
|
||||||
- [待办 (画大饼)](#待办-画大饼)
|
- [待办 (画大饼)](#待办-画大饼)
|
||||||
- [项目情况](#项目情况)
|
- [项目情况](#项目情况)
|
||||||
<!--toc:end-->
|
<!--toc:end-->
|
||||||
|
|
||||||
### 注意: 当前分支目前没有发布, README.md 的描述并不准确, 遇到问题请切换到 `main`分支或者联系我
|
> **插件默认词库的路径为插件目录**
|
||||||
|
|
||||||
|
例如: `lazy` 用户应该在 `$HOME/.local/share/nvim/lazy/Trans.nvim`
|
||||||
|
|
||||||
## 特点
|
## 特点
|
||||||
|
|
||||||
- 使用纯 lua 编写, 速度极快
|
- `使用纯 lua 编写`
|
||||||
|
|
||||||
> `Lazy.nvim`的记录: <font color="#0099FF">`➜ Trans.nvim 0.82ms`</font>
|
|
||||||
|
|
||||||
- **可以定义快捷键读英文单词**
|
|
||||||
|
|
||||||
> 见 wiki
|
|
||||||
|
|
||||||
- 大部分功能可以自定义:
|
- 大部分功能可以自定义:
|
||||||
- 高亮
|
- 🔍 高亮
|
||||||
- 悬浮大小
|
- 👀 悬浮大小
|
||||||
- 排版顺序
|
- 📜 排版顺序
|
||||||
- 弹窗大小
|
- 💬 弹窗大小
|
||||||
- `舒服窗口动画`
|
- 🎉 舒服窗口动画
|
||||||
- etc (更多可以查看[配置](#配置))
|
- 更多可以查看[配置](#配置)
|
||||||
- **完全离线** 的单词翻译体验 (可能后面会支持在线翻译)
|
- `离线`和`在线`翻译的支持
|
||||||
- 支持显示:
|
- 支持显示:
|
||||||
- 柯林斯星级
|
- 🌟 柯林斯星级
|
||||||
- 牛津 3000 词汇
|
- 📚 牛津 3000 词汇
|
||||||
- 中文翻译
|
- 🇨🇳 中文翻译
|
||||||
- 英文翻译 (不是英译中, 而是用英文解释)
|
- 🇺🇸 英文翻译 (不是英译中, 而是用英文解释)
|
||||||
- 词根
|
- 🌱 词根
|
||||||
- etc
|
- etc
|
||||||
- 舒服的排版和`动画`
|
- 支持`平滑动画`
|
||||||
- 支持 `normal`和 `visual`模式
|
- 支持 `normal`和 `visual`模式
|
||||||
> <font color='#FF9900'>不支持 visual-block mode</font>
|
> <font color='#FF9900'>不支持 visual-block mode</font>
|
||||||
- 本地词库单词量: `430w`
|
- 本地词库单词量: `430w`
|
||||||
@ -61,13 +58,13 @@
|
|||||||
|
|
||||||
https://user-images.githubusercontent.com/107862700/226175984-1a95bea7-8d66-450e-87e1-ba9c91c37ab8.mp4
|
https://user-images.githubusercontent.com/107862700/226175984-1a95bea7-8d66-450e-87e1-ba9c91c37ab8.mp4
|
||||||
|
|
||||||
### 在线查询 (有道)
|
### 在线查询演示 (有道)
|
||||||
|
|
||||||
https://user-images.githubusercontent.com/107862700/226176106-c2962dd3-d66c-499c-b44a-1f471b79fe38.mp4
|
https://user-images.githubusercontent.com/107862700/226176106-c2962dd3-d66c-499c-b44a-1f471b79fe38.mp4
|
||||||
|
|
||||||
**使用在线查询需要配置相应的 app_id 和 app_passwd**
|
**使用在线查询需要配置相应的 app_id 和 app_passwd**
|
||||||
|
|
||||||
在线查询配置见: [wiki](https://github.com/JuanZoran/Trans.nvim/wiki/%E9%85%8D%E7%BD%AE#%E5%9C%A8%E7%BA%BF%E6%9F%A5%E8%AF%A2%E9%85%8D%E7%BD%AE)
|
配置说明见: [wiki](https://github.com/JuanZoran/Trans.nvim/wiki/%E9%85%8D%E7%BD%AE#%E5%9C%A8%E7%BA%BF%E6%9F%A5%E8%AF%A2%E9%85%8D%E7%BD%AE)
|
||||||
|
|
||||||
### 主题
|
### 主题
|
||||||
|
|
||||||
@ -139,6 +136,7 @@ use {
|
|||||||
```lua
|
```lua
|
||||||
{
|
{
|
||||||
"JuanZoran/Trans.nvim",
|
"JuanZoran/Trans.nvim",
|
||||||
|
build = function () require'Trans'.install() end,
|
||||||
keys = {
|
keys = {
|
||||||
-- 可以换成其他你想映射的键
|
-- 可以换成其他你想映射的键
|
||||||
{ 'mm', mode = { 'n', 'x' }, '<Cmd>Translate<CR>', desc = ' Translate' },
|
{ 'mm', mode = { 'n', 'x' }, '<Cmd>Translate<CR>', desc = ' Translate' },
|
||||||
@ -157,40 +155,32 @@ use {
|
|||||||
|
|
||||||
<font color="#FF9900">**注意事项**: </font>
|
<font color="#FF9900">**注意事项**: </font>
|
||||||
|
|
||||||
- `install.sh`
|
|
||||||
|
|
||||||
- 使用了 `wget`下载词库, 安装请确保你的环境变量中存在 wget
|
|
||||||
- install.sh 下载后会自动将词库解压, 并移动到 `$HOME/.vim/dict`文件夹下
|
|
||||||
- 目前仅在 `Ubuntu22.04`的环境下测试通过
|
|
||||||
> 如果上述条件不符合, 请删掉 `run = 'install.sh'`部分, 考虑手动安装词库
|
|
||||||
> 如果上述条件满足, 仍出现问题, 欢迎在 issue 里向我反馈,我会及时尝试解决
|
|
||||||
|
|
||||||
- 下载词典的过程中, 需要能够 `流畅的访问github下载`
|
- 下载词典的过程中, 需要能够 `流畅的访问github下载`
|
||||||
|
|
||||||
> 词库文件压缩包大小为: **281M**
|
如果下载出现问题, 正常是会自动下载
|
||||||
> 解压缩后的大小大概为: 1.2G
|
|
||||||
|
|
||||||
- 安装后如果不能正常运行, 请尝试检查一下问题:
|
> 词库文件压缩包大小为: **281M**
|
||||||
|
> 解压缩后的大小大概为: **1.2G**
|
||||||
|
|
||||||
- 本机是否已经安装了 `sqlite3`
|
- 安装后如果不能正常运行, 清尝试运行 `checkhealth Trans`
|
||||||
> Linux 下安装:
|
|
||||||
> `sudo pacman -S sqlite # Arch`
|
|
||||||
> `sudo apt-get install sqlite3 libsqlite3-dev # Ubuntu`
|
|
||||||
|
|
||||||
> **尝试运行 `checkhealth Trans`**
|
- **`auto_play`** 的使用:
|
||||||
|
|
||||||
- **`auto_play`** 使用步骤:
|
- `Linux` 需要安装`festival`
|
||||||
|
|
||||||
> linux 只需要安装`festival`
|
> `sudo apt-get install festival festvox-kallpc16k`
|
||||||
> sudo apt-get install festival festvox-kallpc16k
|
|
||||||
> **_如果你想要设置音色,发音可以访问:_** [Festival 官方](https://www.cstr.ed.ac.uk/projects/festival/morevoices.html)
|
|
||||||
> 可以选择英音、美音、男声、女声
|
|
||||||
|
|
||||||
> 其他操作系统
|
**如果你想要设置音色,发音可以访问:** [Festival 官方](https://www.cstr.ed.ac.uk/projects/festival/morevoices.html)
|
||||||
|
可以选择英音、美音、男声、女声
|
||||||
|
|
||||||
- 需要确保安装了`nodejs`
|
- `Termux` 需要安装`termux-api`
|
||||||
- 进入插件的`tts`目录运行`npm install`
|
|
||||||
> 如果`install`运行正常则自动安装,如果安装失败,请尝试手动安装
|
- `Mac` 使用系统的`say`命令
|
||||||
|
|
||||||
|
- `Windows` 使用 `nodejs`的 say 模块, 如果你有更好的方案欢迎提供 PR
|
||||||
|
- 需要确保安装了`nodejs`
|
||||||
|
- 进入插件的`tts`目录运行`npm install`
|
||||||
|
> 如果`install`运行正常则自动安装,如果安装失败,请尝试手动安装
|
||||||
|
|
||||||
- `title`的配置,只对`neovim 0.9+`版本有效
|
- `title`的配置,只对`neovim 0.9+`版本有效
|
||||||
|
|
||||||
@ -244,12 +234,16 @@ use {
|
|||||||
|
|
||||||
## 配置
|
## 配置
|
||||||
|
|
||||||
详细见**wiki**: [配置说明](https://github.com/JuanZoran/Trans.nvim/wiki/%E9%85%8D%E7%BD%AE)
|
详细见**wiki**: [基本配置说明](https://github.com/JuanZoran/Trans.nvim/wiki/%E9%85%8D%E7%BD%AE)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>默认配置</summary>
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
require 'Trans'.setup {
|
default_conf = {
|
||||||
---@type string the directory for database file and password file
|
---@type string the directory for database file and password file
|
||||||
dir = require 'Trans'.plugin_dir,
|
dir = require 'Trans'.plugin_dir,
|
||||||
|
debug = true,
|
||||||
---@type 'default' | 'dracula' | 'tokyonight' global Trans theme [see lua/Trans/style/theme.lua]
|
---@type 'default' | 'dracula' | 'tokyonight' global Trans theme [see lua/Trans/style/theme.lua]
|
||||||
theme = 'default', -- default | tokyonight | dracula
|
theme = 'default', -- default | tokyonight | dracula
|
||||||
strategy = {
|
strategy = {
|
||||||
@ -294,11 +288,13 @@ require 'Trans'.setup {
|
|||||||
split_width = 60,
|
split_width = 60,
|
||||||
padding = 10, -- padding for hover window width
|
padding = 10, -- padding for hover window width
|
||||||
keymaps = {
|
keymaps = {
|
||||||
pageup = '[[',
|
-- INFO : No default keymaps anymore, please set it yourself
|
||||||
pagedown = ']]',
|
-- pageup = '<C-u>',
|
||||||
pin = '<leader>[',
|
-- pagedown = '<C-d>',
|
||||||
close = '<leader>]',
|
-- pin = '<leader>[',
|
||||||
toggle_entry = '<leader>;',
|
-- close = '<leader>]',
|
||||||
|
-- toggle_entry = '<leader>;',
|
||||||
|
|
||||||
-- play = '_', -- Deprecated
|
-- play = '_', -- Deprecated
|
||||||
},
|
},
|
||||||
---@type string[] auto close events
|
---@type string[] auto close events
|
||||||
@ -349,6 +345,8 @@ require 'Trans'.setup {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## 快捷键绑定
|
## 快捷键绑定
|
||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
@ -361,66 +359,90 @@ vim.keymap.set({'n', 'x'}, 'mm', '<Cmd>Translate<CR>')
|
|||||||
vim.keymap.set({'n', 'x'}, 'mk', '<Cmd>TransPlay<CR>') -- 自动发音选中或者光标下的单词
|
vim.keymap.set({'n', 'x'}, 'mk', '<Cmd>TransPlay<CR>') -- 自动发音选中或者光标下的单词
|
||||||
```
|
```
|
||||||
|
|
||||||
## 高亮组
|
**窗口快捷键**
|
||||||
|
|
||||||
> 默认定义
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
{
|
require 'Trans'.setup {
|
||||||
TransWord = {
|
frontend = {
|
||||||
fg = '#7ee787',
|
hover = {
|
||||||
bold = true,
|
keymaps = {
|
||||||
|
-- pageup = 'whatever you want',
|
||||||
|
-- pagedown = 'whatever you want',
|
||||||
|
-- pin = 'whatever you want',
|
||||||
|
-- close = 'whatever you want',
|
||||||
|
-- toggle_entry = 'whatever you want',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
TransPhonetic = {
|
},
|
||||||
link = 'Linenr'
|
|
||||||
},
|
|
||||||
TransTitle = {
|
|
||||||
fg = '#0f0f15',
|
|
||||||
bg = '#75beff',
|
|
||||||
bold = true,
|
|
||||||
},
|
|
||||||
TransTitleRound = {
|
|
||||||
fg = '#75beff',
|
|
||||||
},
|
|
||||||
TransTag = {
|
|
||||||
-- fg = '#e5c07b',
|
|
||||||
link = '@tag'
|
|
||||||
},
|
|
||||||
TransExchange = {
|
|
||||||
link = 'TransTag',
|
|
||||||
},
|
|
||||||
TransPos = {
|
|
||||||
link = 'TransTag',
|
|
||||||
},
|
|
||||||
TransTranslation = {
|
|
||||||
link = 'TransWord',
|
|
||||||
},
|
|
||||||
TransDefinition = {
|
|
||||||
link = 'Moremsg',
|
|
||||||
},
|
|
||||||
TransWin = {
|
|
||||||
link = 'Normal',
|
|
||||||
},
|
|
||||||
TransBorder = {
|
|
||||||
fg = '#89B4FA',
|
|
||||||
},
|
|
||||||
TransCollins = {
|
|
||||||
fg = '#faf743',
|
|
||||||
bold = true,
|
|
||||||
},
|
|
||||||
TransFailed = {
|
|
||||||
fg = '#7aa89f',
|
|
||||||
},
|
|
||||||
TransWaitting = {
|
|
||||||
link = 'MoreMsg'
|
|
||||||
},
|
|
||||||
TransWeb = {
|
|
||||||
-- TODO :
|
|
||||||
link = 'MoreMsg',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> 当窗口没有打开的时候, key 会被使用`vim.api.nvim_feedkey`来执行
|
||||||
|
|
||||||
|
## 高亮组
|
||||||
|
|
||||||
|
所有主题可见 `lua/Trans/style/theme.lua`
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>默认主题</summary>
|
||||||
|
|
||||||
|
```lua
|
||||||
|
TransWord = {
|
||||||
|
fg = '#7ee787',
|
||||||
|
bold = true,
|
||||||
|
}
|
||||||
|
TransPhonetic = {
|
||||||
|
link = 'Linenr'
|
||||||
|
}
|
||||||
|
TransTitle = {
|
||||||
|
fg = '#0f0f15',
|
||||||
|
bg = '#75beff',
|
||||||
|
bold = true,
|
||||||
|
}
|
||||||
|
TransTitleRound = {
|
||||||
|
fg = '#75beff',
|
||||||
|
}
|
||||||
|
TransTag = {
|
||||||
|
-- fg = '#e5c07b',
|
||||||
|
link = '@tag'
|
||||||
|
}
|
||||||
|
TransExchange = {
|
||||||
|
link = 'TransTag',
|
||||||
|
}
|
||||||
|
TransPos = {
|
||||||
|
link = 'TransTag',
|
||||||
|
}
|
||||||
|
TransTranslation = {
|
||||||
|
link = 'TransWord',
|
||||||
|
}
|
||||||
|
TransDefinition = {
|
||||||
|
link = 'Moremsg',
|
||||||
|
}
|
||||||
|
TransWin = {
|
||||||
|
link = 'Normal',
|
||||||
|
}
|
||||||
|
TransBorder = {
|
||||||
|
fg = '#89B4FA',
|
||||||
|
}
|
||||||
|
TransCollins = {
|
||||||
|
fg = '#faf743',
|
||||||
|
bold = true,
|
||||||
|
}
|
||||||
|
TransFailed = {
|
||||||
|
fg = '#7aa89f',
|
||||||
|
}
|
||||||
|
TransWaitting = {
|
||||||
|
link = 'MoreMsg'
|
||||||
|
}
|
||||||
|
TransWeb = {
|
||||||
|
link = 'MoreMsg',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## 声明
|
## 声明
|
||||||
|
|
||||||
- 本插件词典基于[ECDICT](https://github.com/skywind3000/ECDICT)
|
- 本插件词典基于[ECDICT](https://github.com/skywind3000/ECDICT)
|
||||||
@ -436,6 +458,10 @@ vim.keymap.set({'n', 'x'}, 'mk', '<Cmd>TransPlay<CR>') -- 自动发音选中或
|
|||||||
> 更新比较频繁, 文档先鸽了
|
> 更新比较频繁, 文档先鸽了
|
||||||
> 如果你想要参加这个项目, 可以提 issue, 我会把文档补齐
|
> 如果你想要参加这个项目, 可以提 issue, 我会把文档补齐
|
||||||
|
|
||||||
|
## 从 v1 (main)分支迁移
|
||||||
|
|
||||||
|
见[wiki](https://github.com/JuanZoran/Trans.nvim/wiki/%E4%BB%8E(v1)main%E5%88%86%E6%94%AF%E8%BF%81%E7%A7%BB)
|
||||||
|
|
||||||
## 待办 (画大饼)
|
## 待办 (画大饼)
|
||||||
|
|
||||||
- [x] 快捷键定义
|
- [x] 快捷键定义
|
||||||
|
@ -15,4 +15,9 @@
|
|||||||
- [ ] Unlimit width for sentence
|
- [ ] Unlimit width for sentence
|
||||||
|
|
||||||
|
|
||||||
|
已知问题:
|
||||||
|
1. 缓存了的单词, 无法使用toggle_entry 进入页面
|
||||||
|
2. 加载配置需要输入所有表的key
|
||||||
|
|
||||||
|
|
||||||
- default_strategy can't deal with table correctly
|
- default_strategy can't deal with table correctly
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
---@class Baidu: TransOnlineBackend
|
---@class Baidu: TransBackendOnline
|
||||||
---@field uri string api uri
|
---@field conf { app_id: string, app_passwd: string }
|
||||||
---@field salt string
|
|
||||||
---@field app_id string
|
|
||||||
---@field app_passwd string
|
|
||||||
---@field disable boolean
|
|
||||||
local M = {
|
local M = {
|
||||||
uri = 'https://fanyi-api.baidu.com/api/trans/vip/translate',
|
name = 'baidu',
|
||||||
salt = tostring(math.random(bit.lshift(1, 15))),
|
display_text = '百度',
|
||||||
name = 'baidu',
|
uri = 'https://fanyi-api.baidu.com/api/trans/vip/translate',
|
||||||
name_zh = '百度',
|
salt = tostring(math.random(bit.lshift(1, 15))),
|
||||||
method = 'get',
|
method = 'get',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
local Trans = require 'Trans'
|
local Trans = require 'Trans'
|
||||||
|
|
||||||
---@class BaiduQuery
|
---@class BaiduQuery
|
||||||
@ -27,14 +22,17 @@ local Trans = require 'Trans'
|
|||||||
---@param data TransData
|
---@param data TransData
|
||||||
---@return BaiduQuery
|
---@return BaiduQuery
|
||||||
function M.get_query(data)
|
function M.get_query(data)
|
||||||
local tmp = M.app_id .. data.str .. M.salt .. M.app_passwd
|
local m_conf = M.conf
|
||||||
|
assert(m_conf, 'Load Baidu config failed')
|
||||||
|
|
||||||
|
local tmp = m_conf.app_id .. data.str .. M.salt .. m_conf.app_passwd
|
||||||
local sign = Trans.util.md5.sumhexa(tmp)
|
local sign = Trans.util.md5.sumhexa(tmp)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
q = data.str,
|
q = data.str,
|
||||||
from = data.from,
|
from = data.from,
|
||||||
to = data.to,
|
to = data.to,
|
||||||
appid = M.app_id,
|
appid = m_conf.app_id,
|
||||||
salt = M.salt,
|
salt = M.salt,
|
||||||
sign = sign,
|
sign = sign,
|
||||||
}
|
}
|
||||||
@ -57,14 +55,31 @@ function M.formatter(body, data)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class TransBackend
|
|
||||||
---@field baidu Baidu
|
|
||||||
return M
|
return M
|
||||||
|
|
||||||
-- -- NOTE :free tts:
|
|
||||||
-- -- https://zj.v.api.aa1.cn/api/baidu-01/?msg=我爱你&choose=0&su=100&yd=5
|
|
||||||
-- -- 选择转音频的人物,女生1 输入0 | 女生2输入:5|男生1 输入:1|男生2 输入:2|男生3 输入:3
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- NOTE :free tts:
|
||||||
|
-- https://zj.v.api.aa1.cn/api/baidu-01/?msg=我爱你&choose=0&su=100&yd=5
|
||||||
|
-- 选择转音频的人物,女生1 输入0 | 女生2输入:5|男生1 输入:1|男生2 输入:2|男生3 输入:3
|
||||||
-- {
|
-- {
|
||||||
-- body = '{"from":"en","to":"zh","trans_result":[{"src":"require","dst":"\\u8981\\u6c42"}]}',
|
-- body = '{"from":"en","to":"zh","trans_result":[{"src":"require","dst":"\\u8981\\u6c42"}]}',
|
||||||
-- exit = 0,
|
-- exit = 0,
|
||||||
|
@ -1,4 +1,191 @@
|
|||||||
local Trans = require 'Trans'
|
local Trans = require 'Trans'
|
||||||
|
if false then
|
||||||
|
-- local dict = db:open(Trans.conf.dir .. Trans.separator .. 'ultimate.db')
|
||||||
|
local db = require 'sqlite.db'
|
||||||
|
local conf = Trans.loader.conf
|
||||||
|
local dict = db:open(conf.dict)
|
||||||
|
vim.api.nvim_create_autocmd('VimLeavePre', {
|
||||||
|
callback = function()
|
||||||
|
if db:isopen() then db:close() end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
---@class TransOfflineBackend
|
||||||
|
local M = {
|
||||||
|
name = 'offline',
|
||||||
|
name_zh = '本地',
|
||||||
|
no_wait = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
---@param data any
|
||||||
|
function M.query(data)
|
||||||
|
if data.is_word == false or data.from == 'zh' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local res = dict:select(conf.db_name, {
|
||||||
|
where = { word = data.str },
|
||||||
|
keys = M.query_field,
|
||||||
|
limit = 1,
|
||||||
|
})[1]
|
||||||
|
|
||||||
|
data.result.offline = res and M.formatter(res) or false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- this is a awesome plugin
|
||||||
|
M.query_field = {
|
||||||
|
'word',
|
||||||
|
'phonetic',
|
||||||
|
'definition',
|
||||||
|
'translation',
|
||||||
|
'pos',
|
||||||
|
'collins',
|
||||||
|
'oxford',
|
||||||
|
'tag',
|
||||||
|
'exchange',
|
||||||
|
}
|
||||||
|
|
||||||
|
local function exist(str)
|
||||||
|
return str and str ~= ''
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type (fun(res):any)[]
|
||||||
|
local formatter = {
|
||||||
|
title = function(res)
|
||||||
|
local title = {
|
||||||
|
word = res.word,
|
||||||
|
oxford = res.oxford,
|
||||||
|
collins = res.collins,
|
||||||
|
phonetic = res.phonetic,
|
||||||
|
}
|
||||||
|
|
||||||
|
res.word = nil
|
||||||
|
res.oxford = nil
|
||||||
|
res.collins = nil
|
||||||
|
res.phonetic = nil
|
||||||
|
return title
|
||||||
|
end,
|
||||||
|
tag = function(res)
|
||||||
|
if not exist(res.tag) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local tag_map = {
|
||||||
|
zk = '中考',
|
||||||
|
gk = '高考',
|
||||||
|
ky = '考研',
|
||||||
|
gre = 'gre ',
|
||||||
|
cet4 = '四级',
|
||||||
|
cet6 = '六级',
|
||||||
|
ielts = '雅思',
|
||||||
|
toefl = '托福',
|
||||||
|
}
|
||||||
|
|
||||||
|
local tag = {}
|
||||||
|
for i, _tag in ipairs(vim.split(res.tag, ' ', { plain = true })) do
|
||||||
|
tag[i] = tag_map[_tag]
|
||||||
|
end
|
||||||
|
|
||||||
|
return tag
|
||||||
|
end,
|
||||||
|
exchange = function(res)
|
||||||
|
if not exist(res.exchange) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local exchange_map = {
|
||||||
|
['0'] = '原型 ',
|
||||||
|
['1'] = '类别 ',
|
||||||
|
['p'] = '过去式 ',
|
||||||
|
['r'] = '比较级 ',
|
||||||
|
['t'] = '最高级 ',
|
||||||
|
['b'] = '比较级 ',
|
||||||
|
['z'] = '最高级 ',
|
||||||
|
['s'] = '复数 ',
|
||||||
|
['d'] = '过去分词 ',
|
||||||
|
['i'] = '现在分词 ',
|
||||||
|
['3'] = '第三人称单数',
|
||||||
|
['f'] = '第三人称单数',
|
||||||
|
}
|
||||||
|
|
||||||
|
local exchange = {}
|
||||||
|
for _, _exchange in ipairs(vim.split(res.exchange, '/', { plain = true })) do
|
||||||
|
exchange[exchange_map[_exchange:sub(1, 1)]] = _exchange:sub(3)
|
||||||
|
end
|
||||||
|
|
||||||
|
return exchange
|
||||||
|
end,
|
||||||
|
pos = function(res)
|
||||||
|
if not exist(res.pos) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
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 pos = {}
|
||||||
|
for _, _pos in ipairs(vim.split(res.pos, '/', { plain = true })) do
|
||||||
|
pos[pos_map[_pos:sub(1, 1)]] = ('%2s%%'):format(_pos:sub(3))
|
||||||
|
end
|
||||||
|
|
||||||
|
return pos
|
||||||
|
end,
|
||||||
|
translation = function(res)
|
||||||
|
if not exist(res.translation) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local translation = {}
|
||||||
|
for i, _translation in ipairs(vim.split(res.translation, '\n', { plain = true })) do
|
||||||
|
translation[i] = _translation
|
||||||
|
end
|
||||||
|
|
||||||
|
return translation
|
||||||
|
end,
|
||||||
|
definition = function(res)
|
||||||
|
if not exist(res.definition) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local definition = {}
|
||||||
|
for i, _definition in ipairs(vim.split(res.definition, '\n', { plain = true })) do
|
||||||
|
-- -- TODO :判断是否需要分割空格
|
||||||
|
definition[i] = _definition:gsub('^%s+', '', 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
return definition
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
---Formater for TransResul
|
||||||
|
---@param res TransResult
|
||||||
|
---@return TransResult
|
||||||
|
function M.formatter(res)
|
||||||
|
for field, func in pairs(formatter) do
|
||||||
|
res[field] = func(res)
|
||||||
|
end
|
||||||
|
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class TransBackends
|
||||||
|
---@field offline TransOfflineBackend
|
||||||
|
return {
|
||||||
|
name = 'offline',
|
||||||
|
name_zh = '本地',
|
||||||
|
no_wait = true,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local db = require 'sqlite.db'
|
local db = require 'sqlite.db'
|
||||||
local path = Trans.conf.dir .. Trans.separator .. 'ultimate.db'
|
local path = Trans.conf.dir .. Trans.separator .. 'ultimate.db'
|
||||||
@ -18,8 +205,6 @@ local M = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
---@param data any
|
---@param data any
|
||||||
---@return any
|
|
||||||
---@overload fun(TransData): TransResult
|
|
||||||
function M.query(data)
|
function M.query(data)
|
||||||
if data.is_word == false or data.from == 'zh' then
|
if data.is_word == false or data.from == 'zh' then
|
||||||
return
|
return
|
||||||
@ -99,6 +284,8 @@ local formatter = {
|
|||||||
['p'] = '过去式 ',
|
['p'] = '过去式 ',
|
||||||
['r'] = '比较级 ',
|
['r'] = '比较级 ',
|
||||||
['t'] = '最高级 ',
|
['t'] = '最高级 ',
|
||||||
|
['b'] = '比较级 ',
|
||||||
|
['z'] = '最高级 ',
|
||||||
['s'] = '复数 ',
|
['s'] = '复数 ',
|
||||||
['d'] = '过去分词 ',
|
['d'] = '过去分词 ',
|
||||||
['i'] = '现在分词 ',
|
['i'] = '现在分词 ',
|
||||||
@ -176,6 +363,4 @@ function M.formatter(res)
|
|||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class TransBackends
|
|
||||||
---@field offline TransOfflineBackend
|
|
||||||
return M
|
return M
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
---@class Youdao: TransOnlineBackend
|
---@class Youdao: TransBackendOnline
|
||||||
---@field uri string api uri
|
---@field uri string api uri
|
||||||
---@field salt string
|
---@field salt string
|
||||||
---@field app_id string
|
|
||||||
---@field app_passwd string
|
|
||||||
---@field disable boolean
|
---@field disable boolean
|
||||||
|
---@field conf { app_id: string, app_passwd: string }
|
||||||
local M = {
|
local M = {
|
||||||
uri = 'https://openapi.youdao.com/api',
|
uri = 'https://openapi.youdao.com/api',
|
||||||
salt = tostring(math.random(bit.lshift(1, 15))),
|
name = 'youdao',
|
||||||
name = 'youdao',
|
display_text = '有道',
|
||||||
name_zh = '有道',
|
method = 'get',
|
||||||
method = 'get',
|
salt = tostring(math.random(bit.lshift(1, 15))),
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class YoudaoQuery
|
---@class YoudaoQuery
|
||||||
@ -26,7 +25,7 @@ local M = {
|
|||||||
---@return YoudaoQuery
|
---@return YoudaoQuery
|
||||||
function M.get_query(data)
|
function M.get_query(data)
|
||||||
local str = data.str
|
local str = data.str
|
||||||
local app_id = M.app_id
|
local m_conf = M.conf
|
||||||
local salt = M.salt
|
local salt = M.salt
|
||||||
local curtime = tostring(os.time())
|
local curtime = tostring(os.time())
|
||||||
|
|
||||||
@ -38,7 +37,7 @@ function M.get_query(data)
|
|||||||
|
|
||||||
|
|
||||||
-- sign=sha256(应用ID+input+salt+curtime+应用密钥); 一二三四五六七八九十
|
-- sign=sha256(应用ID+input+salt+curtime+应用密钥); 一二三四五六七八九十
|
||||||
local hash = app_id .. input .. salt .. curtime .. M.app_passwd
|
local hash = m_conf.app_id .. input .. salt .. curtime .. m_conf.app_passwd
|
||||||
local sign = vim.fn.sha256(hash)
|
local sign = vim.fn.sha256(hash)
|
||||||
|
|
||||||
|
|
||||||
@ -47,7 +46,7 @@ function M.get_query(data)
|
|||||||
to = data.from == 'zh' and 'en' or 'zh-CHS',
|
to = data.from == 'zh' and 'en' or 'zh-CHS',
|
||||||
from = 'auto',
|
from = 'auto',
|
||||||
signType = 'v3',
|
signType = 'v3',
|
||||||
appKey = app_id,
|
appKey = m_conf.app_id,
|
||||||
salt = M.salt,
|
salt = M.salt,
|
||||||
curtime = curtime,
|
curtime = curtime,
|
||||||
sign = sign,
|
sign = sign,
|
||||||
@ -160,6 +159,26 @@ end
|
|||||||
---@class TransBackend
|
---@class TransBackend
|
||||||
---@field youdao Youdao
|
---@field youdao Youdao
|
||||||
return M
|
return M
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- INFO :Query Result Example
|
-- INFO :Query Result Example
|
||||||
-- {
|
-- {
|
||||||
|
8
lua/Trans/core/animation.lua
Normal file
8
lua/Trans/core/animation.lua
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
local Trans = require'Trans'
|
||||||
|
|
||||||
|
---@class Trans
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
@ -1,27 +1,68 @@
|
|||||||
local Trans = require 'Trans'
|
local Trans = require 'Trans'
|
||||||
|
|
||||||
|
|
||||||
---@class TransBackend
|
---@class TransBackend
|
||||||
---@field no_wait? boolean whether need to wait for the result
|
---@field no_wait? boolean whether need to wait for the result
|
||||||
---@field all_name string[] @all backend name
|
---@field name string
|
||||||
---@field name string @backend name
|
---@field display_text string?
|
||||||
---@field name_zh string @backend name in Chinese
|
---@field conf table? @User specific config
|
||||||
|
|
||||||
---@class TransOnlineBackend: TransBackend
|
|
||||||
|
---@class TransBackendOnline: TransBackend
|
||||||
---@field uri string @request uri
|
---@field uri string @request uri
|
||||||
---@field method 'get' | 'post' @request method
|
---@field method 'get' | 'post' @request method
|
||||||
---@field formatter fun(body: table, data: TransData): TransResult|false|nil @formatter
|
---@field formatter fun(body: table, data: TransData): TransResult|false|nil transform response body to TransResult
|
||||||
---@field get_query fun(data: TransData): table<string, string> @get query
|
---@field get_query fun(data: TransData): table<string, any> @get query table
|
||||||
---@field header? table<string, string> | fun(data: TransData): table<string, string> @request header
|
---@field error_message? fun(errorCode) @get error message
|
||||||
---@field debug? fun(body: table?) @debug
|
|
||||||
|
-- -@field header table<string, string>|fun(data: TransData): table<string, string> @request header
|
||||||
|
|
||||||
|
|
||||||
local conf = Trans.conf
|
---@class TransBackendOffline: TransBackend
|
||||||
--- INFO :Parse online engine keys config file
|
---@field query fun(data: TransData)
|
||||||
local path = conf.dir .. '/Trans.json'
|
|
||||||
|
|
||||||
|
|
||||||
|
---@class TransBackendCore
|
||||||
|
local M = {
|
||||||
|
---@type table<string, TransBackend> backendname -> backend source
|
||||||
|
sources = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
local m_util = {}
|
||||||
|
|
||||||
|
-- INFO :Template method for online query
|
||||||
|
---@param data TransData @data
|
||||||
|
---@param backend TransBackendOnline @backend
|
||||||
|
function M.do_query(data, backend)
|
||||||
|
local name = backend.name
|
||||||
|
local formatter = backend.formatter
|
||||||
|
-- local header = type(backend.header) == 'function' and backend.header(data) or backend.header
|
||||||
|
|
||||||
|
local function handle(output)
|
||||||
|
local status, body = pcall(vim.json.decode, output.body)
|
||||||
|
if not status or not body then
|
||||||
|
data.result[name] = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
data.result[name] = formatter(body, data)
|
||||||
|
end
|
||||||
|
|
||||||
|
Trans.curl[backend.method](backend.uri, {
|
||||||
|
query = backend.get_query(data),
|
||||||
|
callback = handle,
|
||||||
|
--- FIXME :
|
||||||
|
header = header,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO :Implement all of utility functions
|
||||||
|
M.util = m_util
|
||||||
|
|
||||||
|
|
||||||
|
-- INFO :Parse configuration file
|
||||||
|
local path = Trans.conf.dir .. '/Trans.json'
|
||||||
local file = io.open(path, 'r')
|
local file = io.open(path, 'r')
|
||||||
|
|
||||||
|
|
||||||
local user_conf = {}
|
local user_conf = {}
|
||||||
if file then
|
if file then
|
||||||
local content = file:read '*a'
|
local content = file:read '*a'
|
||||||
@ -29,33 +70,16 @@ if file then
|
|||||||
file:close()
|
file:close()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- WARNING : [Breaking change] 'Trans.json' should use json object instead of array
|
||||||
local all_name = {}
|
|
||||||
for _, config in ipairs(user_conf) do
|
|
||||||
if not config.disable then
|
|
||||||
all_name[#all_name + 1] = config.name
|
|
||||||
user_conf[config.name] = config
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---@class TransBackends
|
|
||||||
---@field all_name string[] all backend names
|
|
||||||
|
|
||||||
---@class Trans
|
---@class Trans
|
||||||
---@field backend TransBackends
|
---@field backend TransBackendCore
|
||||||
return setmetatable({
|
return setmetatable(M, {
|
||||||
all_name = all_name,
|
|
||||||
}, {
|
|
||||||
__index = function(self, name)
|
__index = function(self, name)
|
||||||
---@type TransBackend
|
---@type TransBackend
|
||||||
local backend = require('Trans.backend.' .. name)
|
local backend = require('Trans.backend.' .. name)
|
||||||
|
backend.conf = user_conf[name]
|
||||||
|
|
||||||
for key, value in pairs(user_conf[name] or {}) do
|
self.sources[name] = backend
|
||||||
backend[key] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
self[name] = backend
|
|
||||||
return backend
|
return backend
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
@ -2,7 +2,7 @@ local api, fn = vim.api, vim.fn
|
|||||||
|
|
||||||
---@class TransBuffer
|
---@class TransBuffer
|
||||||
---@field bufnr integer buffer handle
|
---@field bufnr integer buffer handle
|
||||||
---@field [number] string|TransNode|TransNode[] buffer[line] content
|
---@field [integer] string|TransNode|TransNode[] buffer[line] content
|
||||||
local buffer = {}
|
local buffer = {}
|
||||||
|
|
||||||
-- INFO : corountine can't invoke C function
|
-- INFO : corountine can't invoke C function
|
||||||
@ -37,7 +37,7 @@ end
|
|||||||
|
|
||||||
---Destory buffer
|
---Destory buffer
|
||||||
function buffer:destroy()
|
function buffer:destroy()
|
||||||
api.nvim_buf_delete(self.bufnr, { force = true })
|
pcall(api.nvim_buf_delete, self.bufnr, { force = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
---Set buffer load keymap
|
---Set buffer load keymap
|
||||||
@ -91,11 +91,7 @@ end
|
|||||||
---@return integer
|
---@return integer
|
||||||
function buffer:line_count()
|
function buffer:line_count()
|
||||||
local line_count = api.nvim_buf_line_count(self.bufnr)
|
local line_count = api.nvim_buf_line_count(self.bufnr)
|
||||||
if line_count == 1 and self[1] == '' then
|
return line_count == 1 and self[1] == '' and 0 or line_count
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
return line_count
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Set line content
|
---Set line content
|
||||||
@ -151,11 +147,11 @@ buffer.__index = function(self, key)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
buffer.__newindex = function(self, key, nodes)
|
buffer.__newindex = function(self, key, values)
|
||||||
if type(key) == 'number' then
|
if type(key) == 'number' then
|
||||||
self:setline(nodes, key)
|
self:setline(values, key)
|
||||||
else
|
else
|
||||||
rawset(self, key, nodes)
|
rawset(self, key, values)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,137 +0,0 @@
|
|||||||
---@class Trans
|
|
||||||
---@field conf TransConf
|
|
||||||
|
|
||||||
|
|
||||||
---@class TransConf
|
|
||||||
return {
|
|
||||||
---@type string the directory for database file and password file
|
|
||||||
dir = require 'Trans'.plugin_dir,
|
|
||||||
debug = true,
|
|
||||||
---@type 'default' | 'dracula' | 'tokyonight' global Trans theme [see lua/Trans/style/theme.lua]
|
|
||||||
theme = 'default', -- default | tokyonight | dracula
|
|
||||||
strategy = {
|
|
||||||
---@type { frontend:string, backend:string | string[] } fallback strategy for mode
|
|
||||||
default = {
|
|
||||||
frontend = 'hover',
|
|
||||||
backend = '*',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
---@type table frontend options
|
|
||||||
frontend = {
|
|
||||||
---@class TransFrontendOpts
|
|
||||||
---@field keymaps table<string, string>
|
|
||||||
default = {
|
|
||||||
query = 'fallback',
|
|
||||||
border = 'rounded',
|
|
||||||
title = vim.fn.has 'nvim-0.9' == 1 and {
|
|
||||||
{ '', 'TransTitleRound' },
|
|
||||||
{ ' Trans', 'TransTitle' },
|
|
||||||
{ '', 'TransTitleRound' },
|
|
||||||
} or nil, -- need nvim-0.9+
|
|
||||||
auto_play = true,
|
|
||||||
---@type {open: string | boolean, close: string | boolean, interval: integer} Hover Window Animation
|
|
||||||
animation = {
|
|
||||||
open = 'slid', -- 'fold', 'slid'
|
|
||||||
close = 'slid',
|
|
||||||
interval = 12,
|
|
||||||
},
|
|
||||||
timeout = 2000,
|
|
||||||
},
|
|
||||||
---@class TransHoverOpts : TransFrontendOpts
|
|
||||||
hover = {
|
|
||||||
---@type integer Max Width of Hover Window
|
|
||||||
width = 37,
|
|
||||||
---@type integer Max Height of Hover Window
|
|
||||||
height = 27,
|
|
||||||
---@type string -- see: /lua/Trans/style/spinner
|
|
||||||
spinner = 'dots',
|
|
||||||
---@type string
|
|
||||||
fallback_message = '{{notfound}} 翻译超时或没有找到相关的翻译',
|
|
||||||
auto_resize = true,
|
|
||||||
split_width = 60,
|
|
||||||
padding = 10, -- padding for hover window width
|
|
||||||
keymaps = {
|
|
||||||
pageup = '[[',
|
|
||||||
pagedown = ']]',
|
|
||||||
pin = '<leader>[',
|
|
||||||
close = '<leader>]',
|
|
||||||
toggle_entry = '<leader>;',
|
|
||||||
-- play = '_', -- Deprecated
|
|
||||||
},
|
|
||||||
---@type string[] auto close events
|
|
||||||
auto_close_events = {
|
|
||||||
'InsertEnter',
|
|
||||||
'CursorMoved',
|
|
||||||
'BufLeave',
|
|
||||||
},
|
|
||||||
---@type table<string, string[]> order to display translate result
|
|
||||||
order = {
|
|
||||||
default = {
|
|
||||||
'str',
|
|
||||||
'translation',
|
|
||||||
'definition',
|
|
||||||
},
|
|
||||||
offline = {
|
|
||||||
'title',
|
|
||||||
'tag',
|
|
||||||
'pos',
|
|
||||||
'exchange',
|
|
||||||
'translation',
|
|
||||||
'definition',
|
|
||||||
},
|
|
||||||
youdao = {
|
|
||||||
'title',
|
|
||||||
'translation',
|
|
||||||
'definition',
|
|
||||||
'web',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
icon = {
|
|
||||||
-- or use emoji
|
|
||||||
list = '●', -- ● | ○ | ◉ | ◯ | ◇ | ◆ | ▪ | ▫ | ⬤ | 🟢 | 🟡 | 🟣 | 🟤 | 🟠| 🟦 | 🟨 | 🟧 | 🟥 | 🟪 | 🟫 | 🟩 | 🟦
|
|
||||||
star = '', -- ⭐ | ✴ | ✳ | ✲ | ✱ | ✰ | ★ | ☆ | 🌟 | 🌠 | 🌙 | 🌛 | 🌜 | 🌟 | 🌠 | 🌌 | 🌙 |
|
|
||||||
notfound = ' ', --❔ | ❓ | ❗ | ❕|
|
|
||||||
yes = '✔', -- ✅ | ✔️ | ☑
|
|
||||||
no = '', -- ❌ | ❎ | ✖ | ✘ | ✗ |
|
|
||||||
cell = '■', -- ■ | □ | ▇ | ▏ ▎ ▍ ▌ ▋ ▊ ▉
|
|
||||||
web = '', --🌍 | 🌎 | 🌏 | 🌐 |
|
|
||||||
tag = '',
|
|
||||||
pos = '',
|
|
||||||
exchange = '',
|
|
||||||
definition = '',
|
|
||||||
translation = '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
-- TODO :
|
|
||||||
-- float = {
|
|
||||||
-- width = 0.8,
|
|
||||||
-- height = 0.8,
|
|
||||||
-- border = 'rounded',
|
|
||||||
-- keymap = {
|
|
||||||
-- quit = 'q',
|
|
||||||
-- },
|
|
||||||
-- animation = {
|
|
||||||
-- open = 'fold',
|
|
||||||
-- close = 'fold',
|
|
||||||
-- interval = 10,
|
|
||||||
-- },
|
|
||||||
-- tag = {
|
|
||||||
-- wait = '#519aba',
|
|
||||||
-- fail = '#e46876',
|
|
||||||
-- success = '#10b981',
|
|
||||||
-- },
|
|
||||||
-- },
|
|
||||||
|
|
||||||
|
|
||||||
-- local title = {
|
|
||||||
-- "████████╗██████╗ █████╗ ███╗ ██╗███████╗",
|
|
||||||
-- "╚══██╔══╝██╔══██╗██╔══██╗████╗ ██║██╔════╝",
|
|
||||||
-- " ██║ ██████╔╝███████║██╔██╗ ██║███████╗",
|
|
||||||
-- " ██║ ██╔══██╗██╔══██║██║╚██╗██║╚════██║",
|
|
||||||
-- " ██║ ██║ ██║██║ ██║██║ ╚████║███████║",
|
|
||||||
-- " ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝",
|
|
||||||
--}
|
|
@ -1,53 +1,39 @@
|
|||||||
local Trans = require 'Trans'
|
local Trans = require 'Trans'
|
||||||
|
|
||||||
|
---@class TransData: TransDataOption
|
||||||
---@class TransData
|
---@field mode string @The mode of the str
|
||||||
---@field from string @Source language type
|
---@field from string @Source language type
|
||||||
---@field to string @Target language type
|
---@field to string @Target language type
|
||||||
---@field is_word boolean @Is the str a word
|
|
||||||
---@field str string @The original string
|
---@field str string @The original string
|
||||||
---@field mode string @The mode of the str
|
|
||||||
---@field result table<string, TransResult|nil|false> @The result of the translation
|
---@field result table<string, TransResult|nil|false> @The result of the translation
|
||||||
---@field frontend TransFrontend
|
---@field frontend TransFrontend
|
||||||
|
---@field is_word? boolean @Is the str a word
|
||||||
---@field trace table<string, string> debug message
|
---@field trace table<string, string> debug message
|
||||||
---@field backends table<string, TransBackend>
|
---@field backends TransBackend[]
|
||||||
local M = {}
|
local M = {}
|
||||||
M.__index = M
|
M.__index = M
|
||||||
|
|
||||||
---TransData constructor
|
---TransData constructor
|
||||||
---@param opts table
|
---@param opts TransDataOption
|
||||||
---@return TransData
|
---@return TransData
|
||||||
function M.new(opts)
|
function M.new(opts)
|
||||||
local mode = opts.mode
|
|
||||||
local str = opts.str
|
|
||||||
|
|
||||||
|
|
||||||
|
---@cast opts TransData
|
||||||
|
local mode = opts.mode
|
||||||
|
opts.result = {}
|
||||||
|
opts.trace = {}
|
||||||
local strategy = Trans.conf.strategy[mode]
|
local strategy = Trans.conf.strategy[mode]
|
||||||
local data = setmetatable({
|
|
||||||
str = str,
|
|
||||||
mode = mode,
|
|
||||||
result = {},
|
|
||||||
trace = {},
|
|
||||||
}, M)
|
|
||||||
|
|
||||||
|
|
||||||
data.frontend = Trans.frontend[strategy.frontend].new()
|
---@cast opts TransData
|
||||||
data.backends = {}
|
setmetatable(opts, M)
|
||||||
for i, name in ipairs(strategy.backend) do
|
|
||||||
data.backends[i] = Trans.backend[name]
|
|
||||||
end
|
|
||||||
|
|
||||||
if Trans.util.is_English(str) then
|
|
||||||
data.from = 'en'
|
|
||||||
data.to = 'zh'
|
|
||||||
else
|
|
||||||
data.from = 'zh'
|
|
||||||
data.to = 'en'
|
|
||||||
end
|
|
||||||
|
|
||||||
data.is_word = Trans.util.is_word(str)
|
-- NOTE : whether should we use the default strategy
|
||||||
|
opts.frontend = Trans.frontend[strategy.frontend].new()
|
||||||
|
opts.backends = {}
|
||||||
|
|
||||||
return data
|
return opts
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class TransResult
|
---@class TransResult
|
||||||
@ -67,12 +53,8 @@ end
|
|||||||
---@return string? backend.name
|
---@return string? backend.name
|
||||||
function M:get_available_result()
|
function M:get_available_result()
|
||||||
local result = self.result
|
local result = self.result
|
||||||
|
|
||||||
if result['offline'] then return result['offline'], 'offline' end
|
|
||||||
|
|
||||||
for _, backend in ipairs(self.backends) do
|
for _, backend in ipairs(self.backends) do
|
||||||
if result[backend.name] then
|
if result[backend.name] then
|
||||||
---@diagnostic disable-next-line: return-type-mismatch
|
|
||||||
return result[backend.name], backend.name
|
return result[backend.name], backend.name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
8
lua/Trans/core/debug.lua
Normal file
8
lua/Trans/core/debug.lua
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---@class Trans
|
||||||
|
---@field debug fun(message: string, level: number?)
|
||||||
|
|
||||||
|
return function (message, level)
|
||||||
|
level = level or vim.log.levels.INFO
|
||||||
|
-- TODO : custom messaage filter
|
||||||
|
vim.notify(message, level)
|
||||||
|
end
|
@ -1,32 +1,65 @@
|
|||||||
local Trans = require 'Trans'
|
local Trans = require 'Trans'
|
||||||
local conf = Trans.conf
|
|
||||||
local frontend_opts = conf.frontend
|
---@class TransFrontendOpts
|
||||||
|
local default_frontend = {
|
||||||
|
auto_play = true,
|
||||||
|
query = 'fallback',
|
||||||
|
border = 'rounded',
|
||||||
|
title = vim.fn.has 'nvim-0.9' == 1 and {
|
||||||
|
{ '', 'TransTitleRound' },
|
||||||
|
{ ' Trans', 'TransTitle' },
|
||||||
|
{ '', 'TransTitleRound' },
|
||||||
|
} or nil, -- need nvim-0.9+
|
||||||
|
---@type {open: string | boolean, close: string | boolean, interval: integer} Window Animation
|
||||||
|
animation = {
|
||||||
|
open = 'slid', -- 'fold', 'slid'
|
||||||
|
close = 'slid',
|
||||||
|
interval = 12,
|
||||||
|
},
|
||||||
|
timeout = 2000, -- only for online backend query
|
||||||
|
}
|
||||||
|
|
||||||
|
if Trans.conf.frontend.default then
|
||||||
|
default_frontend = vim.tbl_extend('force', default_frontend, Trans.conf.frontend.default)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function empty_method(method)
|
||||||
|
return function() error('Method [' .. method .. '] not implemented') end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
---@class TransFrontend
|
---@class TransFrontend
|
||||||
---@field opts TransFrontendOpts
|
---@field opts? TransFrontendOpts options which user can set
|
||||||
---@field get_active_instance fun():TransFrontend?
|
---@field get_active_instance fun():TransFrontend?
|
||||||
---@field process fun(self: TransFrontend, data: TransData)
|
|
||||||
---@field wait fun(self: TransFrontend): fun(backend: TransBackend): boolean Update wait status
|
---@field wait fun(self: TransFrontend): fun(backend: TransBackend): boolean Update wait status
|
||||||
---@field execute fun(action: string) @Execute action for frontend instance
|
---@field execute fun(action: string) @Execute action for frontend instance
|
||||||
---@field fallback fun() @Fallback method when no result
|
|
||||||
---@field setup? fun() @Setup method for frontend [optional] **NOTE: This method will be called when frontend is first used**
|
---@field setup? fun() @Setup method for frontend [optional] **NOTE: This method will be called when frontend is first used**
|
||||||
|
local M = {
|
||||||
|
---@type fun() @Fallback method when no result
|
||||||
|
fallback = empty_method 'fallback',
|
||||||
|
---@type fun(self: TransFrontend, data: TransData) @render backend result
|
||||||
|
process = empty_method 'process',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---@class Trans
|
---@class Trans
|
||||||
---@field frontend TransFrontend
|
---@field frontend TransFrontend
|
||||||
return setmetatable({}, {
|
return setmetatable(M, {
|
||||||
__index = function(self, name)
|
__index = function(self, name)
|
||||||
local opts = vim.tbl_extend('keep', frontend_opts[name] or {}, frontend_opts.default)
|
|
||||||
|
|
||||||
---@type TransFrontend
|
---@type TransFrontend
|
||||||
local frontend = require('Trans.frontend.' .. name)
|
local frontend = require('Trans.frontend.' .. name)
|
||||||
|
|
||||||
frontend.opts = opts
|
frontend.opts =
|
||||||
self[name] = frontend
|
vim.tbl_extend('force', frontend.opts or {}, default_frontend, Trans.conf.frontend[name])
|
||||||
|
|
||||||
if frontend.setup then
|
if frontend.setup then
|
||||||
frontend.setup()
|
frontend.setup()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
rawset(self, name, frontend)
|
||||||
return frontend
|
return frontend
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
19
lua/Trans/core/helper.lua
Normal file
19
lua/Trans/core/helper.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
local fn, api = vim.fn, vim.api
|
||||||
|
local Trans = require 'Trans'
|
||||||
|
|
||||||
|
---@class TransHelper for Trans module dev
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
|
---Get abs_path of file
|
||||||
|
---@param path string[]
|
||||||
|
---@param is_dir boolean? [default]: false
|
||||||
|
---@return string @Generated path
|
||||||
|
function M.relative_path(path, is_dir)
|
||||||
|
return Trans.plugin_dir .. table.concat(path, Trans.separator) .. (is_dir and Trans.separator or '')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
7
lua/Trans/core/init.lua
Normal file
7
lua/Trans/core/init.lua
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
-- Return Core module
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
@ -40,7 +40,6 @@ return function()
|
|||||||
vim.notify(debug_message, vim.log.ERROR)
|
vim.notify(debug_message, vim.log.ERROR)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
Trans.curl.get(uri, {
|
Trans.curl.get(uri, {
|
||||||
output = zip,
|
output = zip,
|
||||||
callback = handle,
|
callback = handle,
|
||||||
@ -51,7 +50,7 @@ return function()
|
|||||||
vim.notify(message, vim.log.levels.INFO)
|
vim.notify(message, vim.log.levels.INFO)
|
||||||
|
|
||||||
-- INFO : Install tts dependencies
|
-- INFO : Install tts dependencies
|
||||||
if fn.has 'linux' == 0 and fn.has 'mac' == 0 then
|
if Trans.system == 'win' then
|
||||||
os.execute 'cd ./tts && npm install'
|
os.execute 'cd ./tts && npm install'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,44 +1,31 @@
|
|||||||
|
---@class Trans
|
||||||
local Trans = require 'Trans'
|
local Trans = require 'Trans'
|
||||||
|
|
||||||
local function set_strategy_opts(conf)
|
---@alias TransMode 'visual' 'input'
|
||||||
local all_backends = Trans.backend.all_name
|
local default_strategy = {
|
||||||
local g_strategy = conf.strategy
|
frontend = 'hover',
|
||||||
|
backend = {
|
||||||
|
'offline',
|
||||||
|
-- 'youdao',
|
||||||
|
-- 'baidu',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
local function parse_backend(backend)
|
Trans.conf = {
|
||||||
if type(backend) == 'string' then
|
---@type string the directory for database file and password file
|
||||||
return backend == '*' and all_backends or { backend }
|
dir = require 'Trans'.plugin_dir,
|
||||||
end
|
---@type 'default' | 'dracula' | 'tokyonight' global Trans theme [@see lua/Trans/style/theme.lua]
|
||||||
|
theme = 'default',
|
||||||
|
---@type table<TransMode, { frontend:string, backend:string | string[] }> fallback strategy for mode
|
||||||
|
-- input = {
|
||||||
|
-- visual = {
|
||||||
|
-- ...
|
||||||
|
strategy = vim.defaulttable(function()
|
||||||
|
return setmetatable({}, default_strategy)
|
||||||
|
end),
|
||||||
|
frontend = {},
|
||||||
|
}
|
||||||
|
|
||||||
return backend
|
|
||||||
end
|
|
||||||
|
|
||||||
local default_strategy = g_strategy.default
|
|
||||||
default_strategy.backend = parse_backend(default_strategy.backend)
|
|
||||||
default_strategy.__index = default_strategy
|
|
||||||
|
|
||||||
g_strategy.default = nil
|
|
||||||
|
|
||||||
setmetatable(g_strategy, {
|
|
||||||
__index = function()
|
|
||||||
return default_strategy
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, strategy in pairs(g_strategy) do
|
|
||||||
strategy.backend = parse_backend(strategy.backend)
|
|
||||||
setmetatable(strategy, default_strategy)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local function define_highlights(conf)
|
|
||||||
local set_hl = vim.api.nvim_set_hl
|
|
||||||
local highlights = Trans.style.theme[conf.theme]
|
|
||||||
for hl, opt in pairs(highlights) do
|
|
||||||
set_hl(0, hl, opt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---@class Trans
|
---@class Trans
|
||||||
@ -48,8 +35,12 @@ return function(opts)
|
|||||||
Trans.conf = vim.tbl_deep_extend('force', Trans.conf, opts)
|
Trans.conf = vim.tbl_deep_extend('force', Trans.conf, opts)
|
||||||
end
|
end
|
||||||
local conf = Trans.conf
|
local conf = Trans.conf
|
||||||
conf.dir = vim.fn.expand(conf.dir)
|
conf.dir = vim.fn.expand(conf.dir)
|
||||||
|
|
||||||
set_strategy_opts(conf)
|
-- INFO : set highlight
|
||||||
define_highlights(conf)
|
local set_hl = vim.api.nvim_set_hl
|
||||||
|
local highlights = Trans.style.theme[conf.theme]
|
||||||
|
for hl, opt in pairs(highlights) do
|
||||||
|
set_hl(0, hl, opt)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,94 +1,45 @@
|
|||||||
local Trans = require 'Trans'
|
local Trans = require 'Trans'
|
||||||
local util = Trans.util
|
|
||||||
|
|
||||||
local function init_opts(opts)
|
local function process(opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
opts.mode = opts.mode or vim.fn.mode()
|
opts.mode = opts.mode or vim.fn.mode()
|
||||||
opts.str = util.get_str(opts.mode)
|
local str = Trans.util.get_str(opts.mode)
|
||||||
return opts
|
opts.str = str
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---To Do Online Query
|
if not str or str == '' then
|
||||||
---@param data TransData @data
|
Trans.debug 'No string to translate'
|
||||||
---@param backend TransOnlineBackend @backend
|
return
|
||||||
local function do_query(data, backend)
|
|
||||||
-- TODO : template method for online query
|
|
||||||
local name = backend.name
|
|
||||||
local uri = backend.uri
|
|
||||||
local method = backend.method
|
|
||||||
local formatter = backend.formatter
|
|
||||||
local query = backend.get_query(data)
|
|
||||||
local header = type(backend.header) == 'function' and backend.header(data) or backend.header
|
|
||||||
|
|
||||||
local function handle(output)
|
|
||||||
local status, body = pcall(vim.json.decode, output.body)
|
|
||||||
if not status or not body then
|
|
||||||
if not Trans.conf.debug then
|
|
||||||
backend.debug(body)
|
|
||||||
data.trace[name] = output
|
|
||||||
end
|
|
||||||
|
|
||||||
data.result[name] = false
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- vim.print(data.result[name])
|
|
||||||
data.result[name] = formatter(body, data)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Trans.curl[method](uri, {
|
|
||||||
query = query,
|
|
||||||
callback = handle,
|
|
||||||
header = header,
|
|
||||||
})
|
|
||||||
-- Hook ?
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type table<string, fun(data: TransData):boolean>
|
if opts.from == nil and opts.to == nil then
|
||||||
local strategy = {
|
-- INFO : Default support [zh -> en] or [en -> zh]
|
||||||
fallback = function(data)
|
if Trans.util.is_english(str) then
|
||||||
local result = data.result
|
opts.from = 'en'
|
||||||
Trans.backend.offline.query(data)
|
opts.to = 'zh'
|
||||||
|
else
|
||||||
if result.offline then return true end
|
opts.from = 'zh'
|
||||||
|
opts.to = 'en'
|
||||||
local update = data.frontend:wait()
|
|
||||||
for _, backend in ipairs(data.backends) do
|
|
||||||
do_query(data, backend)
|
|
||||||
|
|
||||||
local name = backend.name
|
|
||||||
---@cast backend TransBackend
|
|
||||||
while result[name] == nil do
|
|
||||||
if not update(backend) then break end
|
|
||||||
end
|
|
||||||
|
|
||||||
if result[name] then return true end
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
assert(opts.from and opts.to, 'opts.from and opts.to must be set at the same time')
|
||||||
|
|
||||||
return false
|
opts.is_word = opts.is_word or Trans.util.is_word(str)
|
||||||
end,
|
|
||||||
--- TODO :More Strategys
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- HACK : Core process logic
|
|
||||||
local function process(opts)
|
|
||||||
opts = init_opts(opts)
|
|
||||||
local str = opts.str
|
|
||||||
if not str or str == '' then return end
|
|
||||||
|
|
||||||
|
|
||||||
-- Find in cache
|
-- Find in cache
|
||||||
if Trans.cache[str] then
|
if Trans.cache[str] then
|
||||||
local data = Trans.cache[str]
|
local data = Trans.cache[str]
|
||||||
data.frontend:process(data)
|
return data.frontend:process(data)
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- Create new data
|
||||||
local data = Trans.data.new(opts)
|
local data = Trans.data.new(opts)
|
||||||
if strategy[data.frontend.opts.query](data) then
|
if Trans.strategy[data.frontend.opts.query](data) then
|
||||||
Trans.cache[data.str] = data
|
Trans.cache[data.str] = data
|
||||||
data.frontend:process(data)
|
data.frontend:process(data)
|
||||||
else
|
else
|
||||||
@ -97,8 +48,17 @@ local function process(opts)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---@class TransDataOption
|
||||||
|
---@field mode string?
|
||||||
|
---@field frontend string?
|
||||||
|
---@field from string? @Source language type
|
||||||
|
---@field to string? @Target language type
|
||||||
|
---@field is_word? boolean @Is the str a word
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- NOTE : Use coroutine to stop and resume the process (for animation)
|
||||||
|
|
||||||
---@class Trans
|
---@class Trans
|
||||||
---@field translate fun(opts: { frontend: string?, mode: string?}?) Translate string core function
|
---@field translate fun(opts: TransDataOption?) Translate string core function
|
||||||
return function(opts)
|
return function(...) coroutine.wrap(process)(...) end
|
||||||
coroutine.wrap(process)(opts)
|
|
||||||
end
|
|
||||||
|
@ -3,7 +3,6 @@ local fn, api = vim.fn, vim.api
|
|||||||
---@class TransUtil
|
---@class TransUtil
|
||||||
local M = require 'Trans'.metatable 'util'
|
local M = require 'Trans'.metatable 'util'
|
||||||
|
|
||||||
|
|
||||||
---Get selected text
|
---Get selected text
|
||||||
---@return string
|
---@return string
|
||||||
function M.get_select()
|
function M.get_select()
|
||||||
@ -32,7 +31,27 @@ function M.get_select()
|
|||||||
local e = #lines
|
local e = #lines
|
||||||
lines[1] = lines[1]:sub(s_col)
|
lines[1] = lines[1]:sub(s_col)
|
||||||
lines[e] = line:sub(1, e_col)
|
lines[e] = line:sub(1, e_col)
|
||||||
return table.concat(lines)
|
return table.concat(lines, ' ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get selected text
|
||||||
|
---@return string
|
||||||
|
function M.get_lines()
|
||||||
|
local _start = vim.fn.getpos 'v'
|
||||||
|
local _end = vim.fn.getpos '.'
|
||||||
|
|
||||||
|
if _start[2] > _end[2] then
|
||||||
|
_start, _end = _end, _start
|
||||||
|
end
|
||||||
|
|
||||||
|
local s_row, e_row = _start[2], _end[2]
|
||||||
|
|
||||||
|
if s_row == e_row then
|
||||||
|
return vim.fn.getline(s_row)
|
||||||
|
else
|
||||||
|
local lines = vim.fn.getline(s_row, e_row)
|
||||||
|
return table.concat(lines, ' ')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -52,7 +71,8 @@ function M.get_str(mode)
|
|||||||
return fn.input '需要翻译的字符串: '
|
return fn.input '需要翻译的字符串: '
|
||||||
end,
|
end,
|
||||||
V = function()
|
V = function()
|
||||||
print 'TODO'
|
api.nvim_input '<Esc>'
|
||||||
|
return M.get_lines()
|
||||||
end,
|
end,
|
||||||
})[mode]():match '^%s*(.-)%s*$'
|
})[mode]():match '^%s*(.-)%s*$'
|
||||||
end
|
end
|
||||||
@ -60,6 +80,7 @@ end
|
|||||||
---Puase coroutine for {ms} milliseconds
|
---Puase coroutine for {ms} milliseconds
|
||||||
---@param ms integer
|
---@param ms integer
|
||||||
function M.pause(ms)
|
function M.pause(ms)
|
||||||
|
assert(ms)
|
||||||
local co = coroutine.running()
|
local co = coroutine.running()
|
||||||
vim.defer_fn(function()
|
vim.defer_fn(function()
|
||||||
coroutine.resume(co)
|
coroutine.resume(co)
|
||||||
@ -70,7 +91,7 @@ end
|
|||||||
---Detect whether the string is English
|
---Detect whether the string is English
|
||||||
---@param str string
|
---@param str string
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function M.is_English(str)
|
function M.is_english(str)
|
||||||
local char = { str:byte(1, -1) }
|
local char = { str:byte(1, -1) }
|
||||||
for i = 1, #str do
|
for i = 1, #str do
|
||||||
if char[i] > 128 then
|
if char[i] > 128 then
|
||||||
@ -114,7 +135,6 @@ function M.center(node, win_width)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local str = node[1]
|
local str = node[1]
|
||||||
win_width = str:width()
|
|
||||||
local space = math.max(0, math.floor((win_width - str:width()) / 2))
|
local space = math.max(0, math.floor((win_width - str:width()) / 2))
|
||||||
node[1] = string.rep(' ', space) .. str
|
node[1] = string.rep(' ', space) .. str
|
||||||
return node
|
return node
|
||||||
@ -174,7 +194,7 @@ end
|
|||||||
---@param str string
|
---@param str string
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function M.is_word(str)
|
function M.is_word(str)
|
||||||
return str:match '%w+' == str
|
return str:find '%W' == nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param list any[]
|
---@param list any[]
|
||||||
@ -204,6 +224,13 @@ function M.list_fields(list, field)
|
|||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- function M.checker(method, ...)
|
||||||
|
-- -- TODO :Use function programming to simplify the code
|
||||||
|
-- local params = { ... }
|
||||||
|
|
||||||
|
-- end
|
||||||
|
|
||||||
|
|
||||||
---@class Trans
|
---@class Trans
|
||||||
---@field util TransUtil
|
---@field util TransUtil
|
||||||
return M
|
return M
|
||||||
|
@ -124,7 +124,8 @@ function window:try_close()
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
api.nvim_win_close(self.winid, true)
|
|
||||||
|
pcall(api.nvim_win_close, self.winid, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Set window local highlight group
|
---Set window local highlight group
|
||||||
@ -161,21 +162,7 @@ window.__index = window
|
|||||||
|
|
||||||
|
|
||||||
---@alias WindowOpts
|
---@alias WindowOpts
|
||||||
---|{style: string, border: string, focusable: boolean, noautocmd?: boolean, relative: string, width: integer, height: integer, col: integer, row: integer, zindex?: integer, title?: table | string}
|
---|{style: string, border: string, focusable: boolean, noautocmd?: boolean, relative: 'mouse'|'cursor'|'editor'|'win', width: integer, height: integer, col: integer, row: integer, zindex?: integer, title?: table | string}
|
||||||
|
|
||||||
|
|
||||||
---@class TransWindowOpts
|
|
||||||
local default_opts = {
|
|
||||||
enter = false,
|
|
||||||
winid = -1,
|
|
||||||
---@type WindowOpts
|
|
||||||
win_opts = {
|
|
||||||
style = 'minimal',
|
|
||||||
border = 'rounded',
|
|
||||||
focusable = false,
|
|
||||||
noautocmd = true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
---@class TransWindow
|
---@class TransWindow
|
||||||
@ -197,11 +184,34 @@ local default_opts = {
|
|||||||
-- relative = relative,
|
-- relative = relative,
|
||||||
-- }
|
-- }
|
||||||
|
|
||||||
|
---@class TransWindowOpts
|
||||||
|
local default_opts = {
|
||||||
|
enter = false,
|
||||||
|
winid = -1,
|
||||||
|
---@type WindowOpts
|
||||||
|
win_opts = {
|
||||||
|
-- INFO : ensured options
|
||||||
|
-- col
|
||||||
|
-- row
|
||||||
|
-- width
|
||||||
|
-- height
|
||||||
|
-- relative
|
||||||
|
-- zindex
|
||||||
|
style = 'minimal',
|
||||||
|
border = 'rounded',
|
||||||
|
focusable = true,
|
||||||
|
noautocmd = true,
|
||||||
|
},
|
||||||
|
animation = nil, ---@type table? Hover Window Animation
|
||||||
|
buffer = nil, ---@type TransBuffer attached buffer object
|
||||||
|
}
|
||||||
|
|
||||||
---Create new window
|
---Create new window
|
||||||
---@param opts TransWindowOpts window config
|
---@param opts TransWindowOpts window config
|
||||||
---@return TransWindow
|
---@return TransWindow
|
||||||
function window.new(opts)
|
function window.new(opts)
|
||||||
opts = vim.tbl_deep_extend('keep', opts, default_opts)
|
opts = vim.tbl_deep_extend('keep', opts, default_opts)
|
||||||
|
opts.animation = opts.animation or { interval = 12 }
|
||||||
|
|
||||||
local win = setmetatable(opts, window)
|
local win = setmetatable(opts, window)
|
||||||
---@cast win TransWindow
|
---@cast win TransWindow
|
||||||
|
@ -121,3 +121,34 @@ return M
|
|||||||
-- iciba = 'iciba',
|
-- iciba = 'iciba',
|
||||||
-- offline = '本地',
|
-- offline = '本地',
|
||||||
-- }
|
-- }
|
||||||
|
|
||||||
|
|
||||||
|
-- TODO :
|
||||||
|
-- float = {
|
||||||
|
-- width = 0.8,
|
||||||
|
-- height = 0.8,
|
||||||
|
-- border = 'rounded',
|
||||||
|
-- keymap = {
|
||||||
|
-- quit = 'q',
|
||||||
|
-- },
|
||||||
|
-- animation = {
|
||||||
|
-- open = 'fold',
|
||||||
|
-- close = 'fold',
|
||||||
|
-- interval = 10,
|
||||||
|
-- },
|
||||||
|
-- tag = {
|
||||||
|
-- wait = '#519aba',
|
||||||
|
-- fail = '#e46876',
|
||||||
|
-- success = '#10b981',
|
||||||
|
-- },
|
||||||
|
-- },
|
||||||
|
|
||||||
|
|
||||||
|
-- local title = {
|
||||||
|
-- "████████╗██████╗ █████╗ ███╗ ██╗███████╗",
|
||||||
|
-- "╚══██╔══╝██╔══██╗██╔══██╗████╗ ██║██╔════╝",
|
||||||
|
-- " ██║ ██████╔╝███████║██╔██╗ ██║███████╗",
|
||||||
|
-- " ██║ ██╔══██╗██╔══██║██║╚██╗██║╚════██║",
|
||||||
|
-- " ██║ ██║ ██║██║ ██║██║ ╚████║███████║",
|
||||||
|
-- " ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝",
|
||||||
|
--}
|
||||||
|
@ -5,9 +5,11 @@ local strategy = {
|
|||||||
pageup = function(hover)
|
pageup = function(hover)
|
||||||
hover.buffer:normal 'gg'
|
hover.buffer:normal 'gg'
|
||||||
end,
|
end,
|
||||||
|
|
||||||
pagedown = function(hover)
|
pagedown = function(hover)
|
||||||
hover.buffer:normal 'G'
|
hover.buffer:normal 'G'
|
||||||
end,
|
end,
|
||||||
|
|
||||||
pin = function(hover)
|
pin = function(hover)
|
||||||
if hover.pin then
|
if hover.pin then
|
||||||
return
|
return
|
||||||
@ -27,9 +29,11 @@ local strategy = {
|
|||||||
|
|
||||||
window:set('wrap', true)
|
window:set('wrap', true)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
close = function(hover)
|
close = function(hover)
|
||||||
hover:destroy()
|
hover:destroy()
|
||||||
end,
|
end,
|
||||||
|
|
||||||
toggle_entry = function(hover)
|
toggle_entry = function(hover)
|
||||||
if api.nvim_get_current_win() ~= hover.window.winid then
|
if api.nvim_get_current_win() ~= hover.window.winid then
|
||||||
api.nvim_set_current_win(hover.window.winid)
|
api.nvim_set_current_win(hover.window.winid)
|
||||||
@ -48,6 +52,12 @@ local strategy = {
|
|||||||
---@class TransHover
|
---@class TransHover
|
||||||
---@field execute fun(hover: TransHover, action: string)
|
---@field execute fun(hover: TransHover, action: string)
|
||||||
return function(hover, action)
|
return function(hover, action)
|
||||||
-- TODO :
|
return strategy[action](hover)
|
||||||
strategy[action](hover)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- This function will be called within coroutine, so we can't use __call
|
||||||
|
-- return setmetatable(strategy, {
|
||||||
|
-- __call = function(_, hover, action)
|
||||||
|
-- return strategy[action](hover)
|
||||||
|
-- end,
|
||||||
|
-- })
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
---@type Trans
|
---@type Trans
|
||||||
local Trans = require 'Trans'
|
local Trans = require 'Trans'
|
||||||
|
local util = Trans.util
|
||||||
|
|
||||||
-- FIXME :Adjust Window Size
|
-- FIXME :Adjust Window Size
|
||||||
|
|
||||||
@ -9,25 +10,93 @@ local Trans = require 'Trans'
|
|||||||
---@field window TransWindow @hover window
|
---@field window TransWindow @hover window
|
||||||
---@field queue TransHover[] @hover queue for all hover instances
|
---@field queue TransHover[] @hover queue for all hover instances
|
||||||
---@field destroy_funcs fun(hover:TransHover)[] @functions to be executed when hover window is closed
|
---@field destroy_funcs fun(hover:TransHover)[] @functions to be executed when hover window is closed
|
||||||
---@field opts TransHoverOpts @options for hover window
|
---@field opts TransHoverOpts @hover window options
|
||||||
---@field pin boolean @whether hover window is pinned
|
---@field pin boolean @whether hover window is pinned
|
||||||
local M = Trans.metatable('frontend.hover', {
|
local M = Trans.metatable('frontend.hover', {
|
||||||
ns = vim.api.nvim_create_namespace 'TransHoverWin',
|
ns = vim.api.nvim_create_namespace 'TransHoverWin',
|
||||||
queue = {},
|
queue = {},
|
||||||
|
---@class TransHoverOpts: TransFrontendOpts
|
||||||
|
opts = {
|
||||||
|
---@type integer Max Width of Hover Window
|
||||||
|
width = 37,
|
||||||
|
---@type integer Max Height of Hover Window
|
||||||
|
height = 27,
|
||||||
|
---@type string -- see: /lua/Trans/style/spinner
|
||||||
|
spinner = 'dots',
|
||||||
|
---@type string
|
||||||
|
fallback_message = '{{notfound}} {{error_message}}',
|
||||||
|
auto_resize = true,
|
||||||
|
split_width = 60,
|
||||||
|
padding = 10, -- padding for hover window width
|
||||||
|
keymaps = {
|
||||||
|
-- pageup = '<C-u>',
|
||||||
|
-- pagedown = '<C-d>',
|
||||||
|
-- pin = '<leader>[',
|
||||||
|
-- close = '<leader>]',
|
||||||
|
-- toggle_entry = '<leader>;',
|
||||||
|
},
|
||||||
|
---@type string[] auto close events
|
||||||
|
auto_close_events = {
|
||||||
|
'InsertEnter',
|
||||||
|
'CursorMoved',
|
||||||
|
'BufLeave',
|
||||||
|
},
|
||||||
|
---@type table<string, string[]> order to display translate result
|
||||||
|
order = {
|
||||||
|
default = {
|
||||||
|
'str',
|
||||||
|
'translation',
|
||||||
|
'definition',
|
||||||
|
},
|
||||||
|
offline = {
|
||||||
|
'title',
|
||||||
|
'tag',
|
||||||
|
'pos',
|
||||||
|
'exchange',
|
||||||
|
'translation',
|
||||||
|
'definition',
|
||||||
|
},
|
||||||
|
youdao = {
|
||||||
|
'title',
|
||||||
|
'translation',
|
||||||
|
'definition',
|
||||||
|
'web',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
-- or use emoji
|
||||||
|
list = '●', -- ● | ○ | ◉ | ◯ | ◇ | ◆ | ▪ | ▫ | ⬤ | 🟢 | 🟡 | 🟣 | 🟤 | 🟠| 🟦 | 🟨 | 🟧 | 🟥 | 🟪 | 🟫 | 🟩 | 🟦
|
||||||
|
star = '', -- ⭐ | ✴ | ✳ | ✲ | ✱ | ✰ | ★ | ☆ | 🌟 | 🌠 | 🌙 | 🌛 | 🌜 | 🌟 | 🌠 | 🌌 | 🌙 |
|
||||||
|
notfound = ' ', --❔ | ❓ | ❗ | ❕|
|
||||||
|
yes = '✔', -- ✅ | ✔️ | ☑
|
||||||
|
no = '', -- ❌ | ❎ | ✖ | ✘ | ✗ |
|
||||||
|
cell = '■', -- ■ | □ | ▇ | ▏ ▎ ▍ ▌ ▋ ▊ ▉
|
||||||
|
web = '', --🌍 | 🌎 | 🌏 | 🌐 |
|
||||||
|
tag = '',
|
||||||
|
pos = '',
|
||||||
|
exchange = '',
|
||||||
|
definition = '',
|
||||||
|
translation = '',
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
M.__index = M
|
M.__index = M
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Set up function which will be invoked when this module is loaded
|
||||||
|
Because the options are not loaded yet when this module is loaded
|
||||||
|
--]]
|
||||||
function M.setup()
|
function M.setup()
|
||||||
local set = vim.keymap.set
|
local set = vim.keymap.set
|
||||||
for action, key in pairs(M.opts.keymaps) do
|
for action, key in pairs(M.opts.keymaps) do
|
||||||
set('n', key, function()
|
set('n', key, function()
|
||||||
local instance = M.get_active_instance()
|
local instance = M.get_active_instance()
|
||||||
|
|
||||||
if instance then
|
if instance then
|
||||||
coroutine.wrap(instance.execute)(instance, action)
|
coroutine.wrap(instance.execute)(instance, action)
|
||||||
else
|
else
|
||||||
return key
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key, true, true, true), 'n', false)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@ -88,17 +157,20 @@ end
|
|||||||
function M:init_window(opts)
|
function M:init_window(opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local m_opts = self.opts
|
local m_opts = self.opts
|
||||||
|
|
||||||
|
|
||||||
|
---@format disable-next
|
||||||
local option = {
|
local option = {
|
||||||
buffer = self.buffer,
|
buffer = self.buffer,
|
||||||
animation = m_opts.animation,
|
animation = m_opts.animation,
|
||||||
win_opts = {
|
win_opts = {
|
||||||
col = opts.col or 1,
|
|
||||||
row = opts.row or 1,
|
|
||||||
width = opts.width or m_opts.width,
|
|
||||||
height = opts.height or m_opts.height,
|
|
||||||
relative = opts.relative or 'cursor',
|
relative = opts.relative or 'cursor',
|
||||||
|
col = opts.col or 1,
|
||||||
|
row = opts.row or 1,
|
||||||
|
width = opts.width or m_opts.width,
|
||||||
|
height = opts.height or m_opts.height,
|
||||||
title = m_opts.title,
|
title = m_opts.title,
|
||||||
title_pos = m_opts.title and 'center' or nil,
|
title_pos = m_opts.title and 'center',
|
||||||
zindex = 100,
|
zindex = 100,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -118,7 +190,6 @@ end
|
|||||||
---Get Check function for waiting
|
---Get Check function for waiting
|
||||||
---@return fun(backend: TransBackend): boolean
|
---@return fun(backend: TransBackend): boolean
|
||||||
function M:wait()
|
function M:wait()
|
||||||
local util = Trans.util
|
|
||||||
local opts = self.opts
|
local opts = self.opts
|
||||||
local buffer = self.buffer
|
local buffer = self.buffer
|
||||||
local pause = util.pause
|
local pause = util.pause
|
||||||
@ -126,7 +197,7 @@ function M:wait()
|
|||||||
local spinner = Trans.style.spinner[opts.spinner]
|
local spinner = Trans.style.spinner[opts.spinner]
|
||||||
local times = opts.width - spinner[1]:width()
|
local times = opts.width - spinner[1]:width()
|
||||||
local size = #spinner
|
local size = #spinner
|
||||||
local interval = math.floor(opts.timeout / opts.width)
|
local interval = math.floor(opts.timeout / times)
|
||||||
|
|
||||||
self:init_window {
|
self:init_window {
|
||||||
height = 2,
|
height = 2,
|
||||||
@ -139,7 +210,7 @@ function M:wait()
|
|||||||
local it = util.node.item
|
local it = util.node.item
|
||||||
return function(backend)
|
return function(backend)
|
||||||
cur = cur + 1
|
cur = cur + 1
|
||||||
buffer[1] = pr(backend.name_zh)
|
buffer[1] = pr(backend.display_text)
|
||||||
buffer[2] = it { spinner[cur % size + 1] .. (cell):rep(cur), 'TransWaitting' }
|
buffer[2] = it { spinner[cur % size + 1] .. (cell):rep(cur), 'TransWaitting' }
|
||||||
pause(interval)
|
pause(interval)
|
||||||
return cur < times
|
return cur < times
|
||||||
@ -154,7 +225,7 @@ function M:fallback()
|
|||||||
|
|
||||||
local buffer = self.buffer
|
local buffer = self.buffer
|
||||||
buffer:wipe()
|
buffer:wipe()
|
||||||
buffer[1] = Trans.util.center(fallback_msg, opts.width)
|
buffer[1] = util.center(fallback_msg, opts.width)
|
||||||
buffer:add_highlight(1, 'TransFailed')
|
buffer:add_highlight(1, 'TransFailed')
|
||||||
if not self.window then
|
if not self.window then
|
||||||
self:init_window {
|
self:init_window {
|
||||||
@ -168,7 +239,7 @@ end
|
|||||||
|
|
||||||
---Defer function when process done
|
---Defer function when process done
|
||||||
function M:defer()
|
function M:defer()
|
||||||
Trans.util.main_loop(function()
|
util.main_loop(function()
|
||||||
self.window:set('wrap', true)
|
self.window:set('wrap', true)
|
||||||
self.buffer:set('modifiable', false)
|
self.buffer:set('modifiable', false)
|
||||||
|
|
||||||
@ -204,7 +275,6 @@ function M:process(data)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local util = Trans.util
|
|
||||||
local opts = self.opts
|
local opts = self.opts
|
||||||
local buffer = self.buffer
|
local buffer = self.buffer
|
||||||
|
|
||||||
@ -227,11 +297,11 @@ function M:process(data)
|
|||||||
local width =
|
local width =
|
||||||
valid and
|
valid and
|
||||||
(opts.auto_resize and
|
(opts.auto_resize and
|
||||||
math.max(
|
math.max(
|
||||||
math.min(opts.width, util.display_width(lines) + opts.padding),
|
math.min(opts.width, util.display_width(lines) + opts.padding),
|
||||||
math.min(data.str:width(), opts.split_width)
|
math.min(data.str:width(), opts.split_width)
|
||||||
)
|
)
|
||||||
or opts.width)
|
or opts.width)
|
||||||
or math.min(opts.width, util.display_width(lines) + opts.padding)
|
or math.min(opts.width, util.display_width(lines) + opts.padding)
|
||||||
|
|
||||||
local height = math.min(opts.height, util.display_height(lines, width))
|
local height = math.min(opts.height, util.display_height(lines, width))
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
local Trans = require 'Trans'
|
local Trans = require 'Trans'
|
||||||
local health, fn = vim.health, vim.fn
|
local health, fn = vim.health, vim.fn
|
||||||
|
|
||||||
local ok = health.report_ok
|
local ok = health.ok or health.report_ok
|
||||||
local warn = health.report_warn
|
local warn = health.warn or health.report_warn
|
||||||
local error = health.report_error
|
local error = health.error or health.report_error
|
||||||
local has = fn.has
|
local has = fn.has
|
||||||
local executable = fn.executable
|
local executable = fn.executable
|
||||||
|
|
||||||
local function check_neovim_version()
|
local function check_neovim_version()
|
||||||
if has 'nvim-0.9' == 1 then
|
if has 'nvim-0.9' == 1 then
|
||||||
ok [[You have [neovim-nightly] ]]
|
ok [[You have [neovim-0.9] ]]
|
||||||
else
|
else
|
||||||
warn [[Trans Title requires Neovim 0.9 or newer
|
warn [[Trans Title requires Neovim 0.9 or newer
|
||||||
See neovim-nightly: [https://github.com/neovim/neovim/releases/tag/nightly]
|
See neovim-nightly: [https://github.com/neovim/neovim/releases/tag/nightly]
|
||||||
@ -38,13 +38,13 @@ local function check_binary_dependencies()
|
|||||||
'sqlite3',
|
'sqlite3',
|
||||||
}
|
}
|
||||||
|
|
||||||
if has 'linux' == 1 then
|
binary_dependencies[3] = ({
|
||||||
binary_dependencies[3] = 'festival'
|
win = 'node',
|
||||||
elseif has 'mac' == 1 then
|
mac = 'say',
|
||||||
binary_dependencies[3] = 'say'
|
linux = 'festival',
|
||||||
else
|
termux = 'termux-tts-speak',
|
||||||
binary_dependencies[3] = 'node'
|
})[Trans.system]
|
||||||
end
|
|
||||||
|
|
||||||
for _, dep in ipairs(binary_dependencies) do
|
for _, dep in ipairs(binary_dependencies) do
|
||||||
if executable(dep) == 1 then
|
if executable(dep) == 1 then
|
||||||
|
@ -5,41 +5,48 @@
|
|||||||
local function metatable(folder_name, origin)
|
local function metatable(folder_name, origin)
|
||||||
return setmetatable(origin or {}, {
|
return setmetatable(origin or {}, {
|
||||||
__index = function(tbl, key)
|
__index = function(tbl, key)
|
||||||
local status, result = pcall(require, ('Trans.%s.%s'):format(folder_name, key))
|
local found, result = pcall(require, ('Trans.%s.%s'):format(folder_name, key))
|
||||||
if status then
|
if found then
|
||||||
tbl[key] = result
|
rawset(tbl, key, result)
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@class string
|
---@class string
|
||||||
---@field width function @Get string display width
|
---@field width function @Get string display width
|
||||||
---@field play function @Use tts to play string
|
---@field play function @Use tts to play string
|
||||||
|
|
||||||
|
|
||||||
local sep = vim.loop.os_uname().sysname == 'Windows' and '\\' or '/'
|
local uname = vim.loop.os_uname().sysname
|
||||||
|
local system =
|
||||||
|
uname == 'Darwin' and 'mac' or
|
||||||
|
uname == 'Windows_NT' and 'win' or
|
||||||
|
uname == 'Linux' and (vim.fn.executable 'termux-api-start' == 1 and 'termux' or 'linux') or
|
||||||
|
error 'Unknown System, Please Report Issue'
|
||||||
|
|
||||||
|
|
||||||
|
local separator = system == 'win' and '\\\\' or '/'
|
||||||
|
|
||||||
---@class Trans
|
---@class Trans
|
||||||
---@field style table @Style module
|
---@field style table @Style module
|
||||||
---@field cache table<string, TransData> @Cache for translated data object
|
---@field cache table<string, TransData> @Cache for translated data object
|
||||||
---@field plugin_dir string @Plugin directory
|
---@field plugin_dir string @Plugin directory
|
||||||
---@field separator string @Path separator
|
---@field separator string @Path separator
|
||||||
|
---@field system 'mac'|'win'|'termux'|'linux' @Path separator
|
||||||
|
---@field strategy table<string, fun(data: TransData):boolean> Translate string core function
|
||||||
local M = metatable('core', {
|
local M = metatable('core', {
|
||||||
cache = {},
|
cache = {},
|
||||||
style = metatable 'style',
|
style = metatable 'style',
|
||||||
plugin_dir = debug.getinfo(1, 'S').source:sub(2):match('(.-)lua' .. sep .. 'Trans'),
|
strategy = metatable 'strategy',
|
||||||
separator = sep,
|
separator = separator,
|
||||||
|
system = system,
|
||||||
|
plugin_dir = debug.getinfo(1, 'S').source:sub(2):match('(.-)lua' .. separator .. 'Trans'),
|
||||||
})
|
})
|
||||||
|
|
||||||
M.metatable = metatable
|
M.metatable = metatable
|
||||||
|
|
||||||
---Get abs_path of file
|
|
||||||
---@param path string[]
|
|
||||||
---@param is_dir boolean?
|
|
||||||
---@return string
|
|
||||||
function M.relative_path(path, is_dir)
|
|
||||||
return M.plugin_dir .. table.concat(path, sep) .. (is_dir and sep or '')
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
27
lua/Trans/strategy/fallback.lua
Normal file
27
lua/Trans/strategy/fallback.lua
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---Fallback query strategy
|
||||||
|
---@param data TransData
|
||||||
|
---@return boolean @true if query success
|
||||||
|
return function(data)
|
||||||
|
local result = data.result
|
||||||
|
local update
|
||||||
|
for _, backend in ipairs(data.backends) do
|
||||||
|
local name = backend.name
|
||||||
|
|
||||||
|
if backend.no_wait then
|
||||||
|
---@cast backend TransOfflineBackend
|
||||||
|
backend.query(data)
|
||||||
|
else
|
||||||
|
---@cast backend TransOnlineBackend
|
||||||
|
require 'Trans'.backend.do_query(data, backend)
|
||||||
|
update = update or data.frontend:wait()
|
||||||
|
|
||||||
|
while result[name] == nil and update(backend) do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@cast backend TransBackend
|
||||||
|
if result[name] then return true end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
@ -29,248 +29,461 @@
|
|||||||
-- SOFTWARE.
|
-- SOFTWARE.
|
||||||
return {
|
return {
|
||||||
dots = {
|
dots = {
|
||||||
"⠋",
|
'⠋',
|
||||||
"⠙",
|
'⠙',
|
||||||
"⠹",
|
'⠹',
|
||||||
"⠸",
|
'⠸',
|
||||||
"⠼",
|
'⠼',
|
||||||
"⠴",
|
'⠴',
|
||||||
"⠦",
|
'⠦',
|
||||||
"⠧",
|
'⠧',
|
||||||
"⠇",
|
'⠇',
|
||||||
"⠏",
|
'⠏',
|
||||||
},
|
},
|
||||||
dots_negative = { -- dots2
|
dots_negative = { -- dots2
|
||||||
"⣾",
|
'⣾',
|
||||||
"⣽",
|
'⣽',
|
||||||
"⣻",
|
'⣻',
|
||||||
"⢿",
|
'⢿',
|
||||||
"⡿",
|
'⡿',
|
||||||
"⣟",
|
'⣟',
|
||||||
"⣯",
|
'⣯',
|
||||||
"⣷",
|
'⣷',
|
||||||
},
|
},
|
||||||
dots_snake = { -- dots3
|
dots_snake = { -- dots3
|
||||||
"⠋",
|
'⠋',
|
||||||
"⠙",
|
'⠙',
|
||||||
"⠚",
|
'⠚',
|
||||||
"⠒",
|
'⠒',
|
||||||
"⠂",
|
'⠂',
|
||||||
"⠂",
|
'⠂',
|
||||||
"⠒",
|
'⠒',
|
||||||
"⠲",
|
'⠲',
|
||||||
"⠴",
|
'⠴',
|
||||||
"⠦",
|
'⠦',
|
||||||
"⠖",
|
'⠖',
|
||||||
"⠒",
|
'⠒',
|
||||||
"⠐",
|
'⠐',
|
||||||
"⠐",
|
'⠐',
|
||||||
"⠒",
|
'⠒',
|
||||||
"⠓",
|
'⠓',
|
||||||
"⠋",
|
'⠋',
|
||||||
},
|
},
|
||||||
dots_footsteps = { -- dots10
|
dots_footsteps = { -- dots10
|
||||||
"⢄",
|
'⢄',
|
||||||
"⢂",
|
'⢂',
|
||||||
"⢁",
|
'⢁',
|
||||||
"⡁",
|
'⡁',
|
||||||
"⡈",
|
'⡈',
|
||||||
"⡐",
|
'⡐',
|
||||||
"⡠",
|
'⡠',
|
||||||
},
|
},
|
||||||
dots_hop = { -- dots11
|
dots_hop = { -- dots11
|
||||||
"⠁",
|
'⠁',
|
||||||
"⠂",
|
'⠂',
|
||||||
"⠄",
|
'⠄',
|
||||||
"⡀",
|
'⡀',
|
||||||
"⢀",
|
'⢀',
|
||||||
"⠠",
|
'⠠',
|
||||||
"⠐",
|
'⠐',
|
||||||
"⠈",
|
'⠈',
|
||||||
},
|
},
|
||||||
line = {
|
line = {
|
||||||
"-",
|
'-',
|
||||||
"\\",
|
'\\',
|
||||||
"|",
|
'|',
|
||||||
"/",
|
'/',
|
||||||
},
|
},
|
||||||
pipe = {
|
pipe = {
|
||||||
"┤",
|
'┤',
|
||||||
"┘",
|
'┘',
|
||||||
"┴",
|
'┴',
|
||||||
"└",
|
'└',
|
||||||
"├",
|
'├',
|
||||||
"┌",
|
'┌',
|
||||||
"┬",
|
'┬',
|
||||||
"┐",
|
'┐',
|
||||||
},
|
},
|
||||||
dots_ellipsis = { -- simpleDots
|
dots_ellipsis = { -- simpleDots
|
||||||
". ",
|
'. ',
|
||||||
".. ",
|
'.. ',
|
||||||
"...",
|
'...',
|
||||||
" ",
|
' ',
|
||||||
},
|
},
|
||||||
dots_scrolling = { -- simpleDotsScrolling
|
dots_scrolling = { -- simpleDotsScrolling
|
||||||
". ",
|
'. ',
|
||||||
".. ",
|
'.. ',
|
||||||
"...",
|
'...',
|
||||||
" ..",
|
' ..',
|
||||||
" .",
|
' .',
|
||||||
" ",
|
' ',
|
||||||
},
|
},
|
||||||
star = {
|
star = {
|
||||||
"✶",
|
'✶',
|
||||||
"✸",
|
'✸',
|
||||||
"✹",
|
'✹',
|
||||||
"✺",
|
'✺',
|
||||||
"✹",
|
'✹',
|
||||||
"✷",
|
'✷',
|
||||||
},
|
},
|
||||||
flip = {
|
flip = {
|
||||||
"_",
|
'_',
|
||||||
"_",
|
'_',
|
||||||
"_",
|
'_',
|
||||||
"-",
|
'-',
|
||||||
"`",
|
'`',
|
||||||
"`",
|
'`',
|
||||||
"'",
|
"'",
|
||||||
"´",
|
'´',
|
||||||
"-",
|
'-',
|
||||||
"_",
|
'_',
|
||||||
"_",
|
'_',
|
||||||
"_",
|
'_',
|
||||||
},
|
},
|
||||||
hamburger = {
|
hamburger = {
|
||||||
"☱",
|
'☱',
|
||||||
"☲",
|
'☲',
|
||||||
"☴",
|
'☴',
|
||||||
},
|
},
|
||||||
grow_vertical = { -- growVertical
|
grow_vertical = { -- growVertical
|
||||||
"▁",
|
'▁',
|
||||||
"▃",
|
'▃',
|
||||||
"▄",
|
'▄',
|
||||||
"▅",
|
'▅',
|
||||||
"▆",
|
'▆',
|
||||||
"▇",
|
'▇',
|
||||||
"▆",
|
'▆',
|
||||||
"▅",
|
'▅',
|
||||||
"▄",
|
'▄',
|
||||||
"▃",
|
'▃',
|
||||||
},
|
},
|
||||||
grow_horizontal = { -- growHorizontal
|
grow_horizontal = { -- growHorizontal
|
||||||
"▏",
|
'▏',
|
||||||
"▎",
|
'▎',
|
||||||
"▍",
|
'▍',
|
||||||
"▌",
|
'▌',
|
||||||
"▋",
|
'▋',
|
||||||
"▊",
|
'▊',
|
||||||
"▉",
|
'▉',
|
||||||
"▊",
|
'▊',
|
||||||
"▋",
|
'▋',
|
||||||
"▌",
|
'▌',
|
||||||
"▍",
|
'▍',
|
||||||
"▎",
|
'▎',
|
||||||
},
|
},
|
||||||
noise = {
|
noise = {
|
||||||
"▓",
|
'▓',
|
||||||
"▒",
|
'▒',
|
||||||
"░",
|
'░',
|
||||||
},
|
},
|
||||||
dots_bounce = { -- bounce
|
dots_bounce = { -- bounce
|
||||||
"⠁",
|
'⠁',
|
||||||
"⠂",
|
'⠂',
|
||||||
"⠄",
|
'⠄',
|
||||||
"⠂",
|
'⠂',
|
||||||
},
|
},
|
||||||
triangle = {
|
triangle = {
|
||||||
"◢",
|
'◢',
|
||||||
"◣",
|
'◣',
|
||||||
"◤",
|
'◤',
|
||||||
"◥",
|
'◥',
|
||||||
},
|
},
|
||||||
arc = {
|
arc = {
|
||||||
"◜",
|
'◜',
|
||||||
"◠",
|
'◠',
|
||||||
"◝",
|
'◝',
|
||||||
"◞",
|
'◞',
|
||||||
"◡",
|
'◡',
|
||||||
"◟",
|
'◟',
|
||||||
},
|
},
|
||||||
circle = {
|
circle = {
|
||||||
"◡",
|
'◡',
|
||||||
"⊙",
|
'⊙',
|
||||||
"◠",
|
'◠',
|
||||||
},
|
},
|
||||||
square_corners = { -- squareCorners
|
square_corners = { -- squareCorners
|
||||||
"◰",
|
'◰',
|
||||||
"◳",
|
'◳',
|
||||||
"◲",
|
'◲',
|
||||||
"◱",
|
'◱',
|
||||||
},
|
},
|
||||||
circle_quarters = { -- circleQuarters
|
circle_quarters = { -- circleQuarters
|
||||||
"◴",
|
'◴',
|
||||||
"◷",
|
'◷',
|
||||||
"◶",
|
'◶',
|
||||||
"◵",
|
'◵',
|
||||||
},
|
},
|
||||||
circle_halves = { -- circleHalves
|
circle_halves = { -- circleHalves
|
||||||
"◐",
|
'◐',
|
||||||
"◓",
|
'◓',
|
||||||
"◑",
|
'◑',
|
||||||
"◒",
|
'◒',
|
||||||
},
|
},
|
||||||
dots_toggle = { -- toggle
|
dots_toggle = { -- toggle
|
||||||
"⊶",
|
'⊶',
|
||||||
"⊷",
|
'⊷',
|
||||||
},
|
},
|
||||||
box_toggle = { -- toggle2
|
box_toggle = { -- toggle2
|
||||||
"▫",
|
'▫',
|
||||||
"▪",
|
'▪',
|
||||||
},
|
},
|
||||||
arrow = {
|
arrow = {
|
||||||
"←",
|
'←',
|
||||||
"↖",
|
'↖',
|
||||||
"↑",
|
'↑',
|
||||||
"↗",
|
'↗',
|
||||||
"→",
|
'→',
|
||||||
"↘",
|
'↘',
|
||||||
"↓",
|
'↓',
|
||||||
"↙",
|
'↙',
|
||||||
},
|
},
|
||||||
clock = {
|
clock = {
|
||||||
"🕛 ",
|
'🕛 ',
|
||||||
"🕐 ",
|
'🕐 ',
|
||||||
"🕑 ",
|
'🕑 ',
|
||||||
"🕒 ",
|
'🕒 ',
|
||||||
"🕓 ",
|
'🕓 ',
|
||||||
"🕔 ",
|
'🕔 ',
|
||||||
"🕕 ",
|
'🕕 ',
|
||||||
"🕖 ",
|
'🕖 ',
|
||||||
"🕗 ",
|
'🕗 ',
|
||||||
"🕘 ",
|
'🕘 ',
|
||||||
"🕙 ",
|
'🕙 ',
|
||||||
"🕚 ",
|
'🕚 ',
|
||||||
},
|
},
|
||||||
earth = {
|
earth = {
|
||||||
"🌍 ",
|
'🌍 ',
|
||||||
"🌎 ",
|
'🌎 ',
|
||||||
"🌏 ",
|
'🌏 ',
|
||||||
},
|
},
|
||||||
moon = {
|
moon = {
|
||||||
"🌑 ",
|
'🌑 ',
|
||||||
"🌒 ",
|
'🌒 ',
|
||||||
"🌓 ",
|
'🌓 ',
|
||||||
"🌔 ",
|
'🌔 ',
|
||||||
"🌕 ",
|
'🌕 ',
|
||||||
"🌖 ",
|
'🌖 ',
|
||||||
"🌗 ",
|
'🌗 ',
|
||||||
"🌘 ",
|
'🌘 ',
|
||||||
},
|
},
|
||||||
dots_pulse = { -- point
|
dots_pulse = { -- point
|
||||||
"∙∙∙",
|
'∙∙∙',
|
||||||
"●∙∙",
|
'●∙∙',
|
||||||
"∙●∙",
|
'∙●∙',
|
||||||
"∙∙●",
|
'∙∙●',
|
||||||
"∙∙∙",
|
'∙∙∙',
|
||||||
|
},
|
||||||
|
fistBump = {
|
||||||
|
'🤜 🤛 ',
|
||||||
|
'🤜 🤛 ',
|
||||||
|
'🤜 🤛 ',
|
||||||
|
' 🤜 🤛 ',
|
||||||
|
' 🤜🤛 ',
|
||||||
|
' 🤜✨🤛 ',
|
||||||
|
'🤜 ✨ 🤛 ',
|
||||||
|
},
|
||||||
|
monkey = {
|
||||||
|
'🙈 ',
|
||||||
|
'🙈 ',
|
||||||
|
'🙉 ',
|
||||||
|
'🙊 '
|
||||||
|
},
|
||||||
|
soccerHeader = {
|
||||||
|
' 🧑⚽️ 🧑 ',
|
||||||
|
'🧑 ⚽️ 🧑 ',
|
||||||
|
'🧑 ⚽️ 🧑 ',
|
||||||
|
'🧑 ⚽️ 🧑 ',
|
||||||
|
'🧑 ⚽️ 🧑 ',
|
||||||
|
'🧑 ⚽️ 🧑 ',
|
||||||
|
'🧑 ⚽️🧑 ',
|
||||||
|
'🧑 ⚽️ 🧑 ',
|
||||||
|
'🧑 ⚽️ 🧑 ',
|
||||||
|
'🧑 ⚽️ 🧑 ',
|
||||||
|
'🧑 ⚽️ 🧑 ',
|
||||||
|
'🧑 ⚽️ 🧑 ',
|
||||||
|
},
|
||||||
|
weather = {
|
||||||
|
'☀️ ',
|
||||||
|
'☀️ ',
|
||||||
|
'☀️ ',
|
||||||
|
'🌤 ',
|
||||||
|
'⛅️ ',
|
||||||
|
'🌥 ',
|
||||||
|
'☁️ ',
|
||||||
|
'🌧 ',
|
||||||
|
'🌨 ',
|
||||||
|
'🌧 ',
|
||||||
|
'🌨 ',
|
||||||
|
'🌧 ',
|
||||||
|
'🌨 ',
|
||||||
|
'⛈ ',
|
||||||
|
'🌨 ',
|
||||||
|
'🌧 ',
|
||||||
|
'🌨 ',
|
||||||
|
'☁️ ',
|
||||||
|
'🌥 ',
|
||||||
|
'⛅️ ',
|
||||||
|
'🌤 ',
|
||||||
|
'☀️ ',
|
||||||
|
'☀️ ',
|
||||||
|
},
|
||||||
|
speaker = {
|
||||||
|
'🔈 ',
|
||||||
|
'🔉 ',
|
||||||
|
'🔊 ',
|
||||||
|
'🔉 ',
|
||||||
|
},
|
||||||
|
smiley = {
|
||||||
|
'😄 ',
|
||||||
|
'😝 ',
|
||||||
|
},
|
||||||
|
toggle = {
|
||||||
|
'⊶',
|
||||||
|
'⊷'
|
||||||
|
},
|
||||||
|
toggle10 = {
|
||||||
|
'㊂',
|
||||||
|
'㊀',
|
||||||
|
'㊁'
|
||||||
|
},
|
||||||
|
toggle11 = {
|
||||||
|
'⧇',
|
||||||
|
'⧆'
|
||||||
|
},
|
||||||
|
toggle12 = {
|
||||||
|
'☗',
|
||||||
|
'☖'
|
||||||
|
},
|
||||||
|
toggle13 = {
|
||||||
|
'=',
|
||||||
|
'*',
|
||||||
|
'-'
|
||||||
|
},
|
||||||
|
toggle2 = {
|
||||||
|
'▫',
|
||||||
|
'▪'
|
||||||
|
},
|
||||||
|
toggle3 = {
|
||||||
|
'□',
|
||||||
|
'■'
|
||||||
|
},
|
||||||
|
toggle4 = {
|
||||||
|
'■',
|
||||||
|
'□',
|
||||||
|
'▪',
|
||||||
|
'▫'
|
||||||
|
},
|
||||||
|
toggle5 = {
|
||||||
|
'▮',
|
||||||
|
'▯'
|
||||||
|
},
|
||||||
|
toggle6 = {
|
||||||
|
'ဝ',
|
||||||
|
'၀'
|
||||||
|
},
|
||||||
|
toggle7 = {
|
||||||
|
'⦾',
|
||||||
|
'⦿'
|
||||||
|
},
|
||||||
|
toggle8 = {
|
||||||
|
'◍',
|
||||||
|
'◌'
|
||||||
|
},
|
||||||
|
toggle9 = {
|
||||||
|
'◉',
|
||||||
|
'◎'
|
||||||
|
},
|
||||||
|
star2 = {
|
||||||
|
'+',
|
||||||
|
'x',
|
||||||
|
'*'
|
||||||
|
},
|
||||||
|
orangeBluePulse = {
|
||||||
|
'🔸 ',
|
||||||
|
'🔶 ',
|
||||||
|
'🟠 ',
|
||||||
|
'🟠 ',
|
||||||
|
'🔶 ',
|
||||||
|
'🔹 ',
|
||||||
|
'🔷 ',
|
||||||
|
'🔵 ',
|
||||||
|
'🔵 ',
|
||||||
|
'🔷 ',
|
||||||
|
},
|
||||||
|
orangePulse = {
|
||||||
|
'🔸 ',
|
||||||
|
'🔶 ',
|
||||||
|
'🟠 ',
|
||||||
|
'🟠 ',
|
||||||
|
'🔶 '
|
||||||
|
},
|
||||||
|
mindblown = {
|
||||||
|
'😐 ',
|
||||||
|
'😐 ',
|
||||||
|
'😮 ',
|
||||||
|
'😮 ',
|
||||||
|
'😦 ',
|
||||||
|
'😦 ',
|
||||||
|
'😧 ',
|
||||||
|
'😧 ',
|
||||||
|
'🤯 ',
|
||||||
|
'💥 ',
|
||||||
|
'✨ ',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
},
|
||||||
|
hearts = {
|
||||||
|
'💛 ',
|
||||||
|
'💙 ',
|
||||||
|
'💜 ',
|
||||||
|
'💚 ',
|
||||||
|
'❤️ '
|
||||||
|
},
|
||||||
|
fingerDance = {
|
||||||
|
'🤘 ',
|
||||||
|
'🤟 ',
|
||||||
|
'🖖 ',
|
||||||
|
'✋ ',
|
||||||
|
'🤚 ',
|
||||||
|
'👆 '
|
||||||
|
},
|
||||||
|
christmas = {
|
||||||
|
'🌲',
|
||||||
|
'🎄'
|
||||||
|
},
|
||||||
|
circleHalves = {
|
||||||
|
'◐',
|
||||||
|
'◓',
|
||||||
|
'◑',
|
||||||
|
'◒'
|
||||||
|
},
|
||||||
|
bouncingBall = {
|
||||||
|
'( ● )',
|
||||||
|
'( ● )',
|
||||||
|
'( ● )',
|
||||||
|
'( ● )',
|
||||||
|
'( ●)',
|
||||||
|
'( ● )',
|
||||||
|
'( ● )',
|
||||||
|
'( ● )',
|
||||||
|
'( ● )',
|
||||||
|
'(● )',
|
||||||
|
},
|
||||||
|
bluePulse = {
|
||||||
|
'🔹 ',
|
||||||
|
'🔷 ',
|
||||||
|
'🔵 ',
|
||||||
|
'🔵 ',
|
||||||
|
'🔷 '
|
||||||
|
},
|
||||||
|
betaWave = {
|
||||||
|
'ρββββββ',
|
||||||
|
'βρβββββ',
|
||||||
|
'ββρββββ',
|
||||||
|
'βββρβββ',
|
||||||
|
'ββββρββ',
|
||||||
|
'βββββρβ',
|
||||||
|
'ββββββρ',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -4,62 +4,63 @@ local util = require 'Trans'.util
|
|||||||
---@field [1] string text to be rendered
|
---@field [1] string text to be rendered
|
||||||
---@field render fun(self: TransNode, buffer: TransBuffer, line: number, col: number) render the node
|
---@field render fun(self: TransNode, buffer: TransBuffer, line: number, col: number) render the node
|
||||||
|
|
||||||
|
local item = (function()
|
||||||
|
---@class TransItem : TransNode
|
||||||
|
local mt = {
|
||||||
|
---@param self TransItem
|
||||||
|
---@param buffer TransBuffer
|
||||||
|
---@param line integer
|
||||||
|
---@param col integer
|
||||||
|
render = function(self, buffer, line, col)
|
||||||
|
if self[2] then
|
||||||
|
buffer:add_highlight(line, self[2], col, col + #self[1])
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
mt.__index = mt
|
||||||
|
|
||||||
---@class TransItem : TransNode
|
---Basic item node
|
||||||
local item_meta = {
|
---@param tuple {[1]: string, [2]: string?}
|
||||||
---@param self TransItem
|
---@return TransItem
|
||||||
---@param buffer TransBuffer
|
return function(tuple)
|
||||||
---@param line integer
|
return setmetatable(tuple, mt)
|
||||||
---@param col integer
|
end
|
||||||
render = function(self, buffer, line, col)
|
end)()
|
||||||
if self[2] then
|
|
||||||
buffer:add_highlight(line, self[2], col, col + #self[1])
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
---@class TransText : TransNode
|
local text = (function()
|
||||||
---@field step string
|
---@class TransText : TransNode
|
||||||
---@field nodes TransNode[]
|
---@field step string
|
||||||
local text_meta = {
|
---@field nodes TransNode[]
|
||||||
---@param self TransText
|
|
||||||
---@param buffer TransBuffer
|
|
||||||
---@param line integer
|
|
||||||
---@param col integer
|
|
||||||
render = function(self, buffer, line, col)
|
|
||||||
local nodes = self.nodes
|
|
||||||
local step = self.step
|
|
||||||
local len = step and #step or 0
|
|
||||||
|
|
||||||
for _, node in ipairs(nodes) do
|
local mt = {
|
||||||
node:render(buffer, line, col)
|
---@param self TransText
|
||||||
col = col + #node[1] + len
|
---@param buffer TransBuffer
|
||||||
end
|
---@param line integer
|
||||||
end,
|
---@param col integer
|
||||||
}
|
render = function(self, buffer, line, col)
|
||||||
|
local nodes = self.nodes
|
||||||
|
local step = self.step
|
||||||
|
local len = step and #step or 0
|
||||||
|
|
||||||
item_meta.__index = item_meta
|
for _, node in ipairs(nodes) do
|
||||||
text_meta.__index = text_meta
|
node:render(buffer, line, col)
|
||||||
|
col = col + #node[1] + len
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
mt.__index = mt
|
||||||
|
|
||||||
---Basic item node
|
---@param nodes {[number]: TransNode, step: string?}
|
||||||
---@param tuple {[1]: string, [2]: string?}
|
---@return TransText
|
||||||
---@return TransItem
|
return function(nodes)
|
||||||
local function item(tuple)
|
return setmetatable({
|
||||||
return setmetatable(tuple, item_meta)
|
[1] = table.concat(util.list_fields(nodes, 1), nodes.step),
|
||||||
end
|
step = nodes.step,
|
||||||
|
nodes = nodes,
|
||||||
|
}, mt)
|
||||||
|
end
|
||||||
---@param nodes {[number]: TransNode, step: string?}
|
end)()
|
||||||
---@return TransText
|
|
||||||
local function text(nodes)
|
|
||||||
return setmetatable({
|
|
||||||
[1] = table.concat(util.list_fields(nodes, 1), nodes.step),
|
|
||||||
step = nodes.step,
|
|
||||||
nodes = nodes,
|
|
||||||
}, text_meta)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---@param args {[number]: TransNode, width: integer, spin: string?}
|
---@param args {[number]: TransNode, width: integer, spin: string?}
|
||||||
@ -85,7 +86,6 @@ end
|
|||||||
---@class TransUtil
|
---@class TransUtil
|
||||||
---@field node TransNodes
|
---@field node TransNodes
|
||||||
|
|
||||||
|
|
||||||
---@class TransNodes
|
---@class TransNodes
|
||||||
return {
|
return {
|
||||||
item = item,
|
item = item,
|
||||||
|
@ -6,6 +6,7 @@ _G.api = vim.api
|
|||||||
_G.fn = vim.fn
|
_G.fn = vim.fn
|
||||||
_G.mock = require 'luassert.mock'
|
_G.mock = require 'luassert.mock'
|
||||||
_G.stub = require 'luassert.stub'
|
_G.stub = require 'luassert.stub'
|
||||||
|
string.width = api.nvim_strwidth
|
||||||
|
|
||||||
---@param func fun(buffer: TransBuffer)
|
---@param func fun(buffer: TransBuffer)
|
||||||
---@return fun()
|
---@return fun()
|
||||||
|
@ -1,9 +1,92 @@
|
|||||||
|
---@diagnostic disable: param-type-mismatch
|
||||||
require 'test.setup'
|
require 'test.setup'
|
||||||
|
|
||||||
local util = Trans.util
|
local util = Trans.util
|
||||||
|
|
||||||
describe('util.display_height', with_buffer(function(buffer)
|
describe('util.display_height', function()
|
||||||
--- TODO :
|
|
||||||
it('can calculate the height of lines when window with wrap option', function()
|
it('can calculate the height of lines when window with wrap option', function()
|
||||||
|
local lines = {
|
||||||
|
'1234567890',
|
||||||
|
'1234567890',
|
||||||
|
'1234567890',
|
||||||
|
'1234567890',
|
||||||
|
'1234567890',
|
||||||
|
'1234567890',
|
||||||
|
'1234567890',
|
||||||
|
'1234567890',
|
||||||
|
'1234567890',
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.are.equal(#lines, util.display_height(lines, 10))
|
||||||
|
assert.are.equal(#lines, util.display_height(lines, 11))
|
||||||
|
assert.are.equal(2 * #lines, util.display_height(lines, 9))
|
||||||
|
|
||||||
|
-- Unicode width test
|
||||||
|
local u_lines = {
|
||||||
|
'12345678👍', -- 10
|
||||||
|
'あうえお', -- 8
|
||||||
|
'𠮷野い𠮷家野家家', -- 16
|
||||||
|
'👍👍👍お家', -- 10
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.are.equal(4, util.display_height(u_lines, 20))
|
||||||
|
assert.are.equal(4, util.display_height(u_lines, 16))
|
||||||
|
assert.are.equal(5, util.display_height(u_lines, 10))
|
||||||
|
assert.are.equal(7, util.display_height(u_lines, 8))
|
||||||
|
assert.are.equal(9, util.display_height(u_lines, 7))
|
||||||
end)
|
end)
|
||||||
end))
|
end)
|
||||||
|
|
||||||
|
describe('util.display_width', function()
|
||||||
|
it('can calculate the max width of lines', function()
|
||||||
|
local lines = {
|
||||||
|
'1234567890',
|
||||||
|
'123456789',
|
||||||
|
'12345678',
|
||||||
|
'1234567',
|
||||||
|
'123456',
|
||||||
|
'12345',
|
||||||
|
'1234',
|
||||||
|
'123',
|
||||||
|
'12',
|
||||||
|
'1',
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.are.equal(10, util.display_width(lines))
|
||||||
|
-- Unicode width test
|
||||||
|
local u_lines = {
|
||||||
|
'12345678👍', -- 10
|
||||||
|
'あうえお', -- 8
|
||||||
|
'𠮷野い𠮷家野家家', -- 16
|
||||||
|
'👍👍👍お家', -- 10
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.are.equal(16, util.display_width(u_lines))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('util.center', function()
|
||||||
|
it('will return the node if its width more than width', function()
|
||||||
|
local node = i { '1234567890' }
|
||||||
|
assert.are.same(node, util.center(node, 9))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('will auto padding space', function()
|
||||||
|
local node = i { '1234567890' }
|
||||||
|
assert.are.same(i { (' '):rep(2) .. '1234567890' }, util.center(node, 15))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('util.is_word', function()
|
||||||
|
it('can detect word', function()
|
||||||
|
for test, value in pairs {
|
||||||
|
['あうえお'] = false,
|
||||||
|
['hello'] = true,
|
||||||
|
[' hello'] = false,
|
||||||
|
['hello world'] = false,
|
||||||
|
['test_cool'] = false,
|
||||||
|
} do
|
||||||
|
assert.are.same(util.is_word(test), value)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
100
lua/test/window_spec.lua
Normal file
100
lua/test/window_spec.lua
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
require 'test.setup'
|
||||||
|
|
||||||
|
|
||||||
|
describe('window', with_buffer(function(buffer)
|
||||||
|
local window
|
||||||
|
before_each(function()
|
||||||
|
buffer:wipe()
|
||||||
|
window = Trans.window.new {
|
||||||
|
buffer = buffer,
|
||||||
|
win_opts = {
|
||||||
|
col = 1,
|
||||||
|
row = 1,
|
||||||
|
width = 1,
|
||||||
|
height = 1,
|
||||||
|
relative = 'editor',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
window:set('wrap', false)
|
||||||
|
end)
|
||||||
|
|
||||||
|
after_each(function()
|
||||||
|
window:try_close()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can work well when no pass animation table', function()
|
||||||
|
window:open()
|
||||||
|
assert.is_true(api.nvim_win_is_valid(window.winid))
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('smooth_expand', function()
|
||||||
|
it('can work well when no pass animation table', function()
|
||||||
|
for field, values in pairs {
|
||||||
|
width = {
|
||||||
|
10,
|
||||||
|
6,
|
||||||
|
8,
|
||||||
|
5,
|
||||||
|
},
|
||||||
|
height = {
|
||||||
|
10,
|
||||||
|
6,
|
||||||
|
3,
|
||||||
|
},
|
||||||
|
} do
|
||||||
|
for _, value in ipairs(values) do
|
||||||
|
window:smooth_expand { field = field, to = value }
|
||||||
|
assert.are.same(value, window[field](window))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("don't change window wrap option", function()
|
||||||
|
window:smooth_expand { field = 'width', to = 10 }
|
||||||
|
assert.is_false(window:option 'wrap')
|
||||||
|
|
||||||
|
|
||||||
|
window:set('wrap', true)
|
||||||
|
window:smooth_expand { field = 'width', to = 10 }
|
||||||
|
assert.is_true(window:option 'wrap')
|
||||||
|
|
||||||
|
window:smooth_expand { field = 'height', to = 10 }
|
||||||
|
assert.is_true(window:option 'wrap')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("resize() don't change window wrap option", function()
|
||||||
|
window:resize { width = 10, height = 10 }
|
||||||
|
assert.is_false(window:option 'wrap')
|
||||||
|
|
||||||
|
|
||||||
|
window:set('wrap', true)
|
||||||
|
window:resize { width = 5, height = 5 }
|
||||||
|
assert.is_true(window:option 'wrap')
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('adjust_height() can auto adjust window height to buffer display height', function()
|
||||||
|
for idx, content in ipairs {
|
||||||
|
'cool',
|
||||||
|
'co10',
|
||||||
|
'家👍',
|
||||||
|
'👍ol',
|
||||||
|
'cあl',
|
||||||
|
'家野',
|
||||||
|
} do
|
||||||
|
buffer[idx] = content
|
||||||
|
end
|
||||||
|
|
||||||
|
local max_height = vim.o.lines - 2
|
||||||
|
for width, expect in ipairs {
|
||||||
|
[2] = 12,
|
||||||
|
[3] = 12,
|
||||||
|
[4] = 6,
|
||||||
|
[5] = 6,
|
||||||
|
} do
|
||||||
|
window:smooth_expand { field = 'width', to = width }
|
||||||
|
window:adjust_height()
|
||||||
|
assert.are.same(math.min(expect, max_height), window:height())
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end))
|
2
makefile
2
makefile
@ -1,4 +1,4 @@
|
|||||||
.PHONE: test
|
.PHONE: test
|
||||||
|
|
||||||
test:
|
test:
|
||||||
nvim --headless --noplugin -u script/minimal_init.vim -c "PlenaryBustedDirectory lua/test/ { minimal_init = './scripts/minimal_init.vim' }" -c 'qa!'
|
nvim --headless --noplugin -u scripts/minimal_init.vim -c "PlenaryBustedDirectory lua/test/ { minimal_init = './scripts/minimal_init.vim' }" -c 'qa!'
|
||||||
|
@ -1,34 +1,39 @@
|
|||||||
local api, fn = vim.api, vim.fn
|
local api, fn = vim.api, vim.fn
|
||||||
|
|
||||||
|
|
||||||
--- INFO :Define plugin command
|
--- INFO :Define plugin command
|
||||||
local Trans = require("Trans")
|
local Trans = require 'Trans'
|
||||||
local command = api.nvim_create_user_command
|
local command = api.nvim_create_user_command
|
||||||
|
|
||||||
command("Translate", function()
|
command('Translate', function() Trans.translate() end,
|
||||||
Trans.translate()
|
{ desc = ' Translate cursor word' })
|
||||||
end, { desc = " Translate cursor word" })
|
|
||||||
|
|
||||||
|
|
||||||
command("TranslateInput", function()
|
command('TranslateInput', function() Trans.translate { mode = 'i' } end,
|
||||||
Trans.translate({ mode = 'i' })
|
{ desc = ' Translate input word' })
|
||||||
end, { desc = " Translate input word" })
|
|
||||||
|
|
||||||
command("TransPlay", function()
|
command('TransPlay', function()
|
||||||
local util = Trans.util
|
local util = Trans.util
|
||||||
local str = util.get_str(vim.fn.mode())
|
local str = util.get_str(vim.fn.mode())
|
||||||
if str and str ~= "" and util.is_English(str) then
|
if str and str ~= '' and util.is_english(str) then
|
||||||
str:play()
|
str:play()
|
||||||
end
|
end
|
||||||
end, { desc = " Auto play" })
|
end, { desc = ' Auto play' })
|
||||||
|
|
||||||
|
|
||||||
string.width = api.nvim_strwidth
|
string.width = api.nvim_strwidth
|
||||||
|
|
||||||
|
local system = Trans.system
|
||||||
local f =
|
local f =
|
||||||
fn.has('linux') == 1 and ([[echo %q | festival --tts]])
|
(vim.fn.has 'wsl' == 1 or system == 'win') and
|
||||||
or fn.has('mac') == 1 and ([[say %q]])
|
'powershell.exe -Command "Add-Type -AssemblyName System.speech;(New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak(\\\"%s\\\")"' or
|
||||||
or 'node' .. Trans.relative_path { 'tts', 'say.js' } .. ' %q'
|
system == 'mac' and 'say %q' or
|
||||||
|
system == 'termux' and 'termux-tts-speak %q' or
|
||||||
|
system == 'linux' and 'echo %q | festival --tts' or
|
||||||
|
error 'Unsupported system'
|
||||||
|
|
||||||
|
|
||||||
string.play = function(self)
|
string.play = function(self)
|
||||||
fn.jobstart(f:format(self))
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
local s = string.gsub(self, '\"', ' ')
|
||||||
|
fn.jobstart(f:format(s))
|
||||||
end
|
end
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"say": "^0.16.0"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
const say = require('say')
|
|
||||||
|
|
||||||
// console.log(word)
|
|
||||||
say.speak(process.argv.slice(2))
|
|
Reference in New Issue
Block a user