Repository: uga-rosa/translate.nvim
Branch: main
Commit: 1a841e56407b
Files: 67
Total size: 129.4 KB
Directory structure:
gitextract_mwzms6v0/
├── .github/
│ └── ISSUE_TEMPLATE/
│ └── bug_report.yml
├── .gitignore
├── LICENSE
├── README.md
├── doc/
│ └── translate-nvim.txt
├── lua/
│ └── translate/
│ ├── command.lua
│ ├── config.lua
│ ├── init.lua
│ ├── kit/
│ │ ├── Async/
│ │ │ ├── AsyncTask.lua
│ │ │ ├── AsyncTask.spec.lua
│ │ │ ├── init.lua
│ │ │ └── init.spec.lua
│ │ ├── Cache.lua
│ │ ├── Cache.spec.lua
│ │ ├── Config.lua
│ │ ├── Config.spec.lua
│ │ ├── LSP/
│ │ │ ├── Position.lua
│ │ │ ├── Position.spec.lua
│ │ │ ├── Range.lua
│ │ │ └── Range.spec.lua
│ │ ├── Lua/
│ │ │ ├── TreeSitter.lua
│ │ │ ├── TreeSitter.spec.lua
│ │ │ ├── init.lua
│ │ │ └── init.spec.lua
│ │ ├── Vim/
│ │ │ ├── Buffer.lua
│ │ │ ├── Buffer.spec.lua
│ │ │ ├── Highlight.lua
│ │ │ ├── Highlight.spec.lua
│ │ │ ├── Keymap.lua
│ │ │ ├── Keymap.spec.lua
│ │ │ ├── Syntax.lua
│ │ │ └── Syntax.spec.lua
│ │ ├── init.lua
│ │ └── init.spec.lua
│ ├── preset/
│ │ ├── command/
│ │ │ ├── deepl.lua
│ │ │ ├── deepl_free.lua
│ │ │ ├── deepl_pro.lua
│ │ │ ├── google.lua
│ │ │ └── translate_shell.lua
│ │ ├── output/
│ │ │ ├── floating.lua
│ │ │ ├── insert.lua
│ │ │ ├── register.lua
│ │ │ ├── replace.lua
│ │ │ └── split.lua
│ │ ├── parse_after/
│ │ │ ├── deepl.lua
│ │ │ ├── deepl_free.lua
│ │ │ ├── deepl_pro.lua
│ │ │ ├── google.lua
│ │ │ ├── head.lua
│ │ │ ├── no_handle.lua
│ │ │ ├── oneline.lua
│ │ │ ├── rate.lua
│ │ │ ├── translate_shell.lua
│ │ │ └── window.lua
│ │ └── parse_before/
│ │ ├── concat.lua
│ │ ├── natural.lua
│ │ ├── no_handle.lua
│ │ └── trim.lua
│ └── util/
│ ├── comment.lua
│ ├── context.lua
│ ├── replace.lua
│ ├── select.lua
│ ├── utf8.lua
│ └── util.lua
├── plugin/
│ └── translate.lua
├── stylua.toml
└── utils/
└── minimal.vim
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: Report a problem in translate.nvim
labels: [bug]
body:
- type: checkboxes
id: not-question
attributes:
label: Not a question
options:
- label: This is not a question. If you have a question, use [discussions](https://github.com/uga-rosa/translate.nvim/discussions).
required: true
- type: textarea
attributes:
label: "Description"
description: "Describe in detail what happens"
validations:
required: true
- type: textarea
attributes:
label: "Environments"
description: "information such as OS and neovim version"
validations:
required: true
- type: textarea
attributes:
label: "Minimal reproducible full config"
description: |
You must provide a working config based on [this](https://github.com/uga-rosa/translate.nvim/blob/main/utils/minimal.vim).
The config file should be complete, not partial, and can be used alone to reproduce bugs.
**Don't use packer.nvim for this.**
validations:
required: true
- type: textarea
attributes:
label: "Steps to reproduce"
description: "Full reproduction steps. Include a sample file if your issue relates to a specific filetype."
validations:
required: true
- type: textarea
attributes:
label: "Expected behavior"
description: "A description of the behavior you expected."
validations:
required: true
- type: textarea
attributes:
label: "Actual behavior"
description: "A description of the actual behavior."
validations:
required: true
- type: textarea
attributes:
label: "Additional context"
description: "Any other relevant information"
================================================
FILE: .gitignore
================================================
/doc/tags
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 uga-rosa
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.
================================================
FILE: README.md
================================================
# translate.nvim

# Features
- You can use any command you like for translation.
- Google translate API (default)
- [translate-shell](https://github.com/soimort/translate-shell)
- [DeepL API Pro/Free](https://www.deepl.com/en/docs-api/)
- The results of the translation can be output in a variety of ways.
- Floating window
- Split window
- Insert to the current buffer
- Replace the original text
- Set the register
- The translation command and output method can be specified as command arguments.
- In addition to the above presets, you can add your own functions.
# Requirements
- neovim 0.8+
The default Google Translate requires nothing but [curl](https://curl.se/).
If you use [translate-shell](https://github.com/soimort/translate-shell), you need to install `trans` command.
If you use [DeepL API Pro/Free](https://www.deepl.com/en/docs-api/), you need the authorization key for DeepL API Pro/Free.
In addition, you need curl to send the request.
# Quick start
## Install
With any plugin manager you like (e.g. [vim-plug](https://github.com/junegunn/vim-plug), [packer.nvim](https://github.com/wbthomason/packer.nvim), [dein.vim](https://github.com/Shougo/dein.vim))
## Setup
This plugin has default settings, so there is no need to call setup if you want to use it as is.
This is my setting.
```lua
vim.g.deepl_api_auth_key = "MY_AUTH_KEY"
require("translate").setup({
default = {
command = "deepl_pro",
},
preset = {
output = {
split = {
append = true,
},
},
},
})
```
See help for available options.
## Command
This plugin provides `:Translate`.
I put the quote from the help in the fold.
<details><summary>:Translate</summary><div>
:[range]Translate {target-lang} [{-options}...]
{target-lang}: Required. The language into which the text should be
translated. The format varies depending on the external command used.
|:Translate| can take |:range|. |v|, |V| and |CTRL-V| are supported. If it was
not given, |:Translate| treats current cursor line.
available options:
- '-source='
The language of the text to be translated.
- '-parse_before='
The functions to format texts of selection. You can
use a comma-separated string. If omitted,
|translate-nvim-option-default-parse-before|.
- '-command='
The extermal command to use translation. If omitted,
|translate-nvim-option-default-command| is used.
- '-parse_after='
The functions to format the result of extermal
command. You can use a comma-separated string.
If omitted, |translate-nvim-option-default-parse-after|.
- '-output='
The function to pass the translation result. If
omitted, |translate-nvim-option-default-output|.
- '-comment'
Special option, used as a flag. If this flag is set
and the cursor is over a comment, whole comment is
treated as a selection.
Use <Cmd> for mapping.
If you cannot use it, you must change the format with nmap and xmap.
nnoremap me <Cmd>Translate EN<CR>
xnoremap me <Cmd>Translate EN<CR>
Another way.
nnoremap me :<C-u>Translate EN<CR>
xnoremap me :Translate EN<CR>
</div></details>
# Translate the word under the cursor
You can use this mapping.
```vim
nnoremap <space>tw viw:Translate ZH<CR>
```
================================================
FILE: doc/translate-nvim.txt
================================================
*translate-nvim.txt* Use external translate command in nvim
==============================================================================
Contents *translate-nvim-contents*
Introduction |translate-nvim-introduction|
Command |translate-nvim-command|
Setup |translate-nvim-setup|
Option |translate-nvim-option|
- default |translate-nvim-option-default|
- preset |translate-nvim-option-preset|
- parse_before |translate-nvim-option-parse-before|
- command |translate-nvim-option-command|
- parse_after |translate-nvim-option-parse-after|
- output |translate-nvim-option-output|
- replace_symbols |translate-nvim-option-replace-symbols|
- silent |translate-nvim-option-silent|
preset |translate-nvim-preset|
- parse_before |translate-nvim-preset-parse-before|
- natural |translate-nvim-preset-parse-before-natural|
- trim |translate-nvim-preset-parse-before-trim|
- concat |translate-nvim-preset-parse-before-concat|
- no_handle |translate-nvim-preset-parse-before-no-handle|
- command |translate-nvim-preset-command|
- google |translate-nvim-preset-command-google|
- translate_shell |translate-nvim-preset-command-translate-shell|
- deepl_free |translate-nvim-preset-command-deepl-free|
- deepl_pro |translate-nvim-preset-command-deepl-pro|
- parse_after |translate-nvim-preset-parse-after|
- oneline |translate-nvim-preset-parse-after-oneline|
- head |translate-nvim-preset-parse-after-head|
- rate |translate-nvim-preset-parse-after-rate|
- window |translate-nvim-preset-parse-after-window|
- no_handle |translate-nvim-preset-parse-after-no-handle|
- translate_shell |translate-nvim-preset-parse-after-translate-shell|
- deepl |translate-nvim-preset-parse-after-deepl|
- output |translate-nvim-preset-output|
- split |translate-nvim-preset-output-split|
- floating |translate-nvim-preset-output-floating|
- insert |translate-nvim-preset-output-insert|
- replace |translate-nvim-preset-output-replace|
- register |translate-nvim-preset-output-register|
Variables |translate-nvim-variables|
==============================================================================
Introduction *translate-nvim-introduction*
*translate.nvim* *translate-nvim*
translate.nvim ~
|translate.nvim| is a plugin for nvim that allows you to translate the
selection with any external command and handle the result as you like.
It provides |:Translate| for it.
Requirement:
- neovim >= 0.7
- curl (for |translate-nvim-preset-command-google|,
|translate-nvim-preset-command-deepl-free|, and
|translate-nvim-preset-command-deepl-pro|)
- DeepL API authorization key (for
|translate-nvim-preset-command-deepl-free| and
|translate-nvim-preset-command-deepl-pro|)
- trans (for |translate-nvim-preset-command-translate-shell|)
==============================================================================
Command *translate-nvim-command*
*:Translate*
:[range]Translate {target-lang} [{-options}...]
{target-lang}: Required. The language into which the text should be
translated. The format varies depending on the external command used.
|:Translate| can take |:range|. |v|, |V| and |CTRL-V| are supported. If it was
not given, |:Translate| treats current cursor line.
available options:
- '-source='
The language of the text to be translated.
- '-parse_before='
The functions to format texts of selection. You can
use a comma-separated string. If omitted,
|translate-nvim-option-default-parse-before|.
- '-command='
The extermal command to use translation. If omitted,
|translate-nvim-option-default-command| is used.
- '-parse_after='
The functions to format the result of extermal
command. You can use a comma-separated string.
If omitted, |translate-nvim-option-default-parse-after|.
- '-output='
The function to pass the translation result. If
omitted, |translate-nvim-option-default-output|.
- '-comment'
Special option, used as a flag. If this flag is set
and the cursor is over a comment, whole comment is
treated as a selection.
If mapping |:Translate|, You can use |<Cmd>|.
>
nnoremap me <Cmd>Translate EN<CR>
xnoremap me <Cmd>Translate EN<CR>
<
If you cannot use it, you must change the format with nmap and xmap.
>
nnoremap me :<C-u>Translate EN<CR>
xnoremap me :Translate EN<CR>
<
==============================================================================
Setup *translate-nvim-setup*
The options are set through the setup function. See |translate-nvim-option| to
check available options.
>
require("translate").setup({
default = {
command = "deepl_free",
output = "floating",
},
preset = {
output = {
insert = {
base = "top",
off = -1,
},
},
},
})
<
==============================================================================
Option *translate-nvim-option*
*translate-nvim-option-default*
default ~
table
'parse_before', 'command', 'parse_after' and 'output' used by |:Translate|.
See |translate-nvim-preset|, to check the presets provided by |translate-nvim|.
*translate-nvim-option-default-parse-before*
parse_before ~
string
default: 'trim,natural'
See |translate-nvim-preset-parse-before-trim| and
|translate-nvim-preset-parse-before-concat|.
*translate-nvim-option-default-command*
command ~
string
default: 'google'
See |translate-nvim-preset-command-google|.
*translate-nvim-option-default-parse-after*
parse_after ~
string
default: 'head'
See |translate-nvim-preset-parse-after-remove-newline| and
|translate-nvim-preset-parse-after-floating|.
*translate-nvim-option-default-output*
output ~
string
default: 'floating'
See |translate-nvim-preset-output-floating|.
*translate-nvim-option-preset*
preset ~
Options passed to the presets.
*translate-nvim-option-preset-parse-before*
parse_before ~
*translate-nvim-option-preset-parse-before-natural*
natural ~
table
default: {
lang_abbr = {},
end_marks = {},
start_marks = {},
}
Table 'lang_abbr' for converting the '-source' option of the
command to the language, list 'end_marks' of the
end-of-sentence characters pattern (vim regular expression,
|/\V|), and list 'start_marks' of the start-of-sentence
characters pattern (same 'end_marks'). Use lowercase for
language names and their abbreviations.
For example, in English, which is defined by default.
>
{
lang_abbr = {
en = "english",
eng = "english",
},
end_marks = {
english = {
".",
"?",
"!",
":",
";",
},
},
}
<
Only 'english', 'japanese', and 'chinese' have rules defined
by default. Other languages can be defined by yourself or PRs
are welcome!
*translate-nvim-option-preset-parse-before-trim*
trim ~
nil
There are currently no options for 'trim'.
*translate-nvim-option-preset-parse-before-concat*
concat ~
table
default: { sep = " " }
Sets the delimiter used to join lines.
*translate-nvim-option-preset-command*
command ~
*translate-nvim-option-preset-command-google*
google ~
table
default: { args = {} }
Set the extra arguments to be passed to the 'curl' command.
*translate-nvim-option-preset-command-translate-shell*
translate_shell ~
table
default: { args = {} }
Set the extra arguments to be passed to the 'trans' command.
*translate-nvim-option-preset-command-deepl-free*
deepl_free ~
table
default: { args = {} }
Set the extra arguments to be passed to the 'curl' command.
*translate-nvim-option-preset-command-deepl-pro*
deepl_pro ~
table
default: { args = {} }
Set the extra arguments to be passed to the 'curl' command.
*translate-nvim-option-preset-parse-after*
parse_after ~
*translate-nvim-option-preset-parse-after-oneline*
*translate-nvim-option-preset-parse-after-head*
*translate-nvim-option-preset-parse-after-rate*
*translate-nvim-option-preset-parse-after-translate-shell*
*translate-nvim-option-preset-parse-after-deepl*
oneline, head, rate, translate_shell, deepl ~
nil
There are currently no options for these.
*translate-nvim-option-preset-parse-after-window*
window ~
table:
default: {
width = 0.8
}
The 'width' is a percentage of the current window width. If it
is greater than 1, that value, not the percentage, is used as
the fixed value.
*translate-nvim-option-preset-output*
output ~
*translate-nvim-option-preset-output-split*
split ~
table
default: {
position = "top",
min_size = 5
max_size = 0.5
name = "translate://output",
filetype = "translate",
append = false,
}
The 'position' is where the result will be placed: 'top' or
'bottom'. The 'min_size' and 'max_size' are buffer size
limits. The buffer size depends on the number of lines of the
translation result, but you can set an upper and lower limit.
If it is less than 1, it is a percentage of the current
window. In the event of a conflict, min_size takes precedence.
The 'name' and 'filetype' is set to the split buffer. If
'append' is true, without deleting the previous translation
result, the current one will be added to the last line.
*translate-nvim-option-preset-output-floating*
floating ~
table
default: {
relative = "cursor",
style = "minimal",
width = nil,
height = nil,
row = 1,
col = 1,
border = "single",
filetype = "translate",
zindex = 50,
}
The option passed as the 3rd argument of |nvim_open_win()|.
The 'width' and 'height' are automatically calculated from the
received array.
*translate-nvim-option-preset-output-insert*
insert ~
table
default: {
base = 'bottom',
off = 0,
}
Where to insert the translation result. If 'base' is 'top',
the first line of the selection is used as the base, else if
'bottom', the last of line the selection. Finally, add 'off'
to the base. For example, with the default, it will be
inserted just bellow the selection.
*translate-nvim-option-preset-output-replace*
replace ~
You can choose the behavior of replace: 'head' respects the
starting position and the original position; 'rate' calculates
and distributes a percentage of the length of each original
line.
*translate-nvim-option-preset-output-register*
register ~
table
default: {
name = vim.v.register
}
Sets the translation result to the register specified by
'name'. Users who set |clipboard| may want to check
|register-variable| before changing this option.
*translate-nvim-option-parse-before*
parse_before ~
table
default: {}
You can set any function you want to use for formatting selection.
Set tables with the value which has as 'cmd' key a function that returns
the command and arguments.
Check 'lua/translate/preset/parse_before' for details.
*translate-nvim-option-command*
command ~
table
default: {}
You can set any external command you want to use for translation.
Set tables with the value which has as 'cmd' key a function that returns
the command and arguments.
Check 'lua/translate/preset/command' for details.
*translate-nvim-option-parse-after*
parse_after ~
table
default: {}
You can set functions to format the result of the translation. Set tables with
the value which has as 'cmd' key a function. Check
'lua/translate/preset/parse_after' for details.
*translate-nvim-option-output*
output ~
table
default: {}
You can set functions to be passed the result of the translation. Set tables
with the value which has as 'cmd' key a function. Check
'lua/translate/preset/output' for details.
*translate-nvim-option-replace-symbols*
replace_symbols ~
table
default: {
translate_shell = {
["="] = "{@E@}",
["#"] = "{@S@}",
["/"] = "{@C@}",
},
deepl_free = {},
deepl_pro = {},
google = {},
}
This plugin escapes special strings for successful translation. This is its
corresponding dictionary. For example, translate_shell has problems with '='
being translated into the strange string 'u003d', or failing to translate
strings that begin with '/'. Therefore, we temporarily convert the symbols to
special symbols such as '{{@E@}}' before performing the translation, and then
restore the symbols in the translation result for normal translation.
*translate-nvim-option-silent*
silent ~
boolean
default: false
If true, the 'Translate success/failed' messages will be disabled.
==============================================================================
Preset *translate-nvim-preset*
The following is a list of commands, parsing functions, and output methods
provided by this plugin.
*translate-nvim-preset-parse-before*
parse_before ~
A set of functions that take an array of lines of text from a selection and
process them into a string that is eventually passed to the translation
command. The second and subsequent functions receive the return value of the
previous function.
*translate-nvim-preset-parse-before-natural*
natural ~
Separates selection with a blank line or when the start/end of a line
is the start/end of a sentence. To use it, pass the 'source' option to
the command and tell it the original language. By default, this
grammar rule is defined only for English, Japanese and Chinese.
*translate-nvim-preset-parse-before-trim*
trim ~
Execute |vim.trim()| on each line.
*translate-nvim-preset-parse-before-concat*
concat ~
Concatenates selection into a single string using a delimiter. The
delimiter is <Space> by default. If you want to change it, use
|translate-nvim-option-preset-parse-before-concat|.
*translate-nvim-preset-parse-before-no-handle*
no_handle ~
If you don't want to adjust anything, use this.
*translate-nvim-preset-command*
command ~
API/External commands used for translation.
'curl' is required except for translate_shell.
<https://curl.se/>
*translate-nvim-preset-command-google*
google ~
Use Google Translate API via GAS.
There is nothing for you to prepare.
*translate-nvim-preset-command-translate-shell*
translate_shell ~
Use translate-shell.
You need to install 'trans' command.
<https://github.com/soimort/translate-shell>
*translate-nvim-preset-command-deepl-free*
deepl_free ~
Use DeepL API Free
<https://www.deepl.com/en/docs-api/>
Set your DeepL API authorization key to |g:deepl_api_auth_key|.
*translate-nvim-preset-command-deepl-pro*
deepl_pro ~
Use DeepL API Pro
<https://www.deepl.com/en/docs-api/>
What you need is the same as |translate-nvim-preset-command-deepl-free|.
*translate-nvim-preset-parse-after*
parse_after ~
A set of functions that take a result of translation and process them into a
string that is eventually passed to the output. The second and subsequent
functions receive the return value of the previous function.
*translate-nvim-preset-parse-after-oneline*
oneline ~
Summarize the results on a single line.
It is intended to be used with 'split', 'insert', and 'replace'. It is
deprecated for use in 'floating' as it may not fit on the screen.
*translate-nvim-preset-parse-after-head*
head ~
Splits the translation result to fit the display width of the original
text in the selected area. We cannot guarantee the number of
characters in the last line because the number of characters changes
before and after the translation.
It is intended to be used with 'split', 'insert', 'replace', and
'window'.
*translate-nvim-preset-parse-after-rate*
rate ~
Divides the translation result by the percentage of each line of the
original text display width.
It is intended to be used with 'split', 'insert', 'replace', and
'window'.
*translate-nvim-preset-parse-after-window*
window ~
Splits the text to fit the specified window width. Default is 0.8
(percentage of the current window). Use
|translate-nvim-option-preset-parse-after-window| to change it.
It is intended to be used with 'split', 'insert', 'replace', and
'window'.
*translate-nvim-preset-parse-after-no-handle*
no_handle ~
If you don't want to adjust anything, use this.
*translate-nvim-preset-parse-after-translate-shell*
translate_shell ~
If 'command' is 'translate_shell', this parser is added automatically.
In other words, you do not need to specify this unless you want to use
only this. Split by line breaks in the translation result or remove
extra line break characters at the end of it.
*translate-nvim-preset-parse-after-deepl*
deepl ~
If 'command' is 'deepl_pro/free', this parser is added automatically.
In other words, you do not need to specify this unless you want to use
only this. DeepL API returns the response in json format, which is
parsed and the text of the translation result is taken. Use
vim.json.decode (neovim 0.6.0+) or |json_decode|
*translate-nvim-preset-output*
output ~
Function passed the result of translation.
*translate-nvim-preset-output-split*
split ~
Split the window and output the result to it. By default, the previous
translation result is deleted each time. If you want to keep it, use
the option. See |translate-nvim-option-preset-output-split|.
*translate-nvim-preset-output-floating*
floating ~
Display the result in a floating window. See
|translate-nvim-option-preset-output-floating|.
*translate-nvim-preset-output-insert*
insert ~
Insert the result into the current buffer. By default, it is inserted
just below the selection. See
|translate-nvim-option-preset-output-insert|.
*translate-nvim-preset-output-replace*
replace ~
Replace the original text with the result. See
|translate-nvim-option-preset-output-replace|.
*translate-nvim-preset-output-register*
register ~
Set the result to the register. See
|translate-nvim-option-preset-output-register|.
==============================================================================
Variables *translate-nvim-variables*
*g:deepl_api_auth_key*
g:deepl_api_auth_key ~
Authentication key for DeepL API.
vim:tw=78:ts=8:noet:ft=help:norl:
================================================
FILE: lua/translate/command.lua
================================================
local fn = vim.fn
local config = require("translate.config")
local M = {}
local modes = {
"parse_before",
"command",
"parse_after",
"output",
"source",
"comment",
}
---@param arglead string #The leading portion of the argument currently being completed on
---@param cmdline string #The entire command line
---@param _ number #the cursor position in it (byte index)
---@return string[]?
function M.get_complete_list(arglead, cmdline, _)
local mode
if not vim.startswith(arglead, "-") then
mode = "target"
elseif arglead:find("^%-.*=") then
mode = arglead:match("^%-(.*)=")
else
return modes
end
if vim.tbl_contains({ "parse_before", "command", "parse_after", "output" }, mode) then
return config.get_keys(mode)
elseif vim.tbl_contains({ "source", "target" }, mode) then
local command = cmdline:match("-command=(%S+)")
command = command or config.get("default").command
local module = config.config.command[command] or config._preset.command[command]
if module and module.complete_list then
return module.complete_list(mode == "target")
end
end
end
---@param cb fun(mode: string, fargs: string[])
function M.create_command(cb)
vim.api.nvim_create_user_command("Translate", function(opts)
-- If range is 0, not given, it has been called from normal mode, or visual mode with `<Cmd>` mapping.
-- Otherwise it must have been called from visual mode.
local mode = opts.range == 0 and fn.mode() or fn.visualmode()
cb(mode, opts.fargs)
end, {
range = 0,
nargs = "+",
complete = M.get_complete_list,
})
end
return M
================================================
FILE: lua/translate/config.lua
================================================
local M = {}
M._preset = {
parse_before = {
natural = require("translate.preset.parse_before.natural"),
trim = require("translate.preset.parse_before.trim"),
concat = require("translate.preset.parse_before.concat"),
no_handle = require("translate.preset.parse_before.no_handle"),
},
command = {
translate_shell = require("translate.preset.command.translate_shell"),
deepl_free = require("translate.preset.command.deepl_free"),
deepl_pro = require("translate.preset.command.deepl_pro"),
google = require("translate.preset.command.google"),
},
parse_after = {
oneline = require("translate.preset.parse_after.oneline"),
head = require("translate.preset.parse_after.head"),
rate = require("translate.preset.parse_after.rate"),
window = require("translate.preset.parse_after.window"),
no_handle = require("translate.preset.parse_after.no_handle"),
translate_shell = require("translate.preset.parse_after.translate_shell"),
deepl_free = require("translate.preset.parse_after.deepl_free"),
deepl_pro = require("translate.preset.parse_after.deepl_pro"),
google = require("translate.preset.parse_after.google"),
},
output = {
floating = require("translate.preset.output.floating"),
split = require("translate.preset.output.split"),
insert = require("translate.preset.output.insert"),
replace = require("translate.preset.output.replace"),
register = require("translate.preset.output.register"),
},
}
M.config = {
default = {
parse_before = "trim,natural",
command = "google",
parse_after = "head",
output = "floating",
},
parse_before = {},
command = {},
parse_after = {},
output = {},
preset = {
parse_before = {
natural = {
lang_abbr = {},
end_marks = {},
start_marks = {},
},
concat = {
sep = " ",
},
},
command = {
google = {
args = {},
},
translate_shell = {
args = {},
},
deepl_free = {
args = {},
},
deepl_pro = {
args = {},
},
},
parse_after = {
window = {
width = 0.8,
},
},
output = {
floating = {
relative = "cursor",
style = "minimal",
row = 1,
col = 1,
border = "single",
filetype = "translate",
zindex = 50,
},
split = {
position = "top",
min_size = 3,
max_size = 0.3,
name = "translate://output",
filetype = "translate",
append = false,
},
insert = {
base = "bottom",
off = 0,
},
register = {
name = vim.v.register,
},
},
},
silent = false,
replace_symbols = {
translate_shell = {
["="] = "{@E@}",
["#"] = "{@S@}",
["/"] = "{@C@}",
["\\n"] = "{@N@}",
},
deepl_free = {},
deepl_pro = {},
google = {},
},
}
---@param opt table
function M.setup(opt)
M.config = vim.tbl_deep_extend("force", M.config, opt)
end
---@param name string
---@return boolean|table
function M.get(name)
return M.config[name]
end
---@param mode string
---@param name string
---@return fun(lines: string[], command_args: table)
---@return string
function M.get_func(mode, name)
name = name or M.config.default[mode]
local module = M.config[mode][name] or M._preset[mode][name]
if module and module.cmd then
return module.cmd, name
else
error(("Invalid name of %s: %s"):format(module, name))
end
end
---@param mode string
---@param names string
---@return fun(text: string, command_args: table)[]
---@return string[]
function M.get_funcs(mode, names)
names = names or M.config.default[mode]
names = vim.split(names, ",")
local modules = {}
for _, name in ipairs(names) do
local module = M.config[mode][name] or M._preset[mode][name]
if module and module.cmd then
table.insert(modules, module.cmd)
else
error(("Invalid name of %s: %s"):format(mode, name))
end
end
return modules, names
end
---For completion of command ':Translate'
---@param mode string
---@return string[]
function M.get_keys(mode)
local keys = vim.tbl_keys(M.config[mode])
keys = vim.list_extend(keys, vim.tbl_keys(M._preset[mode]))
return keys
end
return M
================================================
FILE: lua/translate/init.lua
================================================
local luv = vim.loop
local config = require("translate.config")
local replace = require("translate.util.replace")
local select = require("translate.util.select")
local util = require("translate.util.util")
local create_command = require("translate.command").create_command
local M = {}
---@param mode string
---@param args string[]
function M.translate(mode, args)
args = M._parse_args(args)
local pos = select.get(args, mode)
if #pos == 0 then
vim.notify("Selection could not be recognized.")
return
end
M._translate(pos, args)
end
---@param opts string[]
---@return table
function M._parse_args(opts)
local args = {}
for _, opt in ipairs(opts) do
local name, arg = opt:match("-([a-z_]+)=(.*)") -- e.g. '-parse_after=head'
if not name then
name = opt:match("-(%l+)") -- for '-comment'
if name then
arg = true
else -- '{target-lang}'
name = "target"
arg = opt
end
end
args[name] = arg
end
return args
end
local function pipes()
local stdin = luv.new_pipe(false)
local stdout = luv.new_pipe(false)
local stderr = luv.new_pipe(false)
return { stdin, stdout, stderr }
end
local function set_to_top(tbl, elem)
if tbl[1] ~= elem then
table.insert(tbl, 1, elem)
end
end
---@param pos positions
---@param cmd_args table
function M._translate(pos, cmd_args)
local parse_before = config.get_funcs("parse_before", cmd_args.parse_before)
local command, command_name = config.get_func("command", cmd_args.command)
local parse_after = config.get_funcs("parse_after", cmd_args.parse_after)
local output = config.get_func("output", cmd_args.output)
replace.set_command_name(command_name)
set_to_top(parse_before, replace.before)
set_to_top(parse_after, replace.after)
local after_process = config._preset.parse_after[command_name]
if after_process and after_process.cmd then
set_to_top(parse_after, after_process.cmd)
end
local lines = M._selection(pos)
pos._lines_selected = lines
---@type string[]
lines = M._run(parse_before, lines, pos, cmd_args)
if not pos._group then
pos._group = util.seq(#lines)
end
local cmd, args = command(lines, cmd_args)
local stdio = pipes()
local handle
handle = luv.spawn(cmd, { args = args, stdio = stdio }, function(code)
if not config.get("silent") then
if code == 0 then
print("Translate success")
else
print("Translate failed")
end
end
handle:close()
end)
if not handle then
return
end
luv.read_start(
stdio[2],
vim.schedule_wrap(function(err, result)
assert(not err, err)
if result then
result = M._run(parse_after, result, pos)
output(result, pos)
end
end)
)
end
---@param pos positions
---@return string[]
function M._selection(pos)
local lines = {}
for i, line in ipairs(pos._lines) do
local col = pos[i].col
table.insert(lines, line:sub(col[1], col[2]))
end
return lines
end
---@generic T
---@param functions function[]
---@param arg `T`
---@param pos positions
---@param cmd_args? string[]
---@return T
function M._run(functions, arg, pos, cmd_args)
for _, func in ipairs(functions) do
arg = func(arg, pos, cmd_args)
end
return arg
end
---@param opt table
function M.setup(opt)
config.setup(opt)
create_command(M.translate)
vim.g.loaded_translate_nvim = true
end
return M
================================================
FILE: lua/translate/kit/Async/AsyncTask.lua
================================================
local Lua = require("___plugin_name___.kit.Lua")
---@class ___plugin_name___.kit.Async.AsyncTask<T>: { value: T }
---@field private value T
---@field private status ___plugin_name___.kit.Async.AsyncTask.Status
---@field private chained boolean
---@field private children (fun(): any)[]
local AsyncTask = {}
AsyncTask.__index = AsyncTask
---@alias ___plugin_name___.kit.Async.AsyncTask.Status integer
AsyncTask.Status = {}
AsyncTask.Status.Pending = 0
AsyncTask.Status.Fulfilled = 1
AsyncTask.Status.Rejected = 2
---Handle unhandled rejection.
---@param err any
function AsyncTask.on_unhandled_rejection(err)
error(err)
end
---Return the value is AsyncTask or not.
---@param value any
---@return boolean
function AsyncTask.is(value)
return getmetatable(value) == AsyncTask
end
---Resolve all tasks.
---@param tasks any[]
---@return ___plugin_name___.kit.Async.AsyncTask
function AsyncTask.all(tasks)
return AsyncTask.new(function(resolve, reject)
local values = {}
local count = 0
for i, task in ipairs(tasks) do
AsyncTask.resolve(task)
:next(function(value)
values[i] = value
count = count + 1
if #tasks == count then
resolve(values)
end
end)
:catch(reject)
end
end)
end
---Create resolved AsyncTask.
---@param v any
---@return ___plugin_name___.kit.Async.AsyncTask
function AsyncTask.resolve(v)
if AsyncTask.is(v) then
return v
end
return AsyncTask.new(function(resolve)
resolve(v)
end)
end
---Create new AsyncTask.
---@NOET: The AsyncTask has similar interface to JavaScript Promise but the AsyncTask can be worked as synchronous.
---@param v any
---@return ___plugin_name___.kit.Async.AsyncTask
function AsyncTask.reject(v)
if AsyncTask.is(v) then
return v
end
return AsyncTask.new(function(_, reject)
reject(v)
end)
end
---Create new async task object.
---@generic T
---@param runner fun(resolve: fun(value: T), reject: fun(err: any))
function AsyncTask.new(runner)
local self = setmetatable({}, AsyncTask)
self.gc = Lua.gc(function()
if self.status == AsyncTask.Status.Rejected then
if not self.chained then
AsyncTask.on_unhandled_rejection(self.value)
end
end
end)
self.value = nil
self.status = AsyncTask.Status.Pending
self.chained = false
self.children = {}
local ok, err = pcall(function()
runner(function(res)
if self.status ~= AsyncTask.Status.Pending then
return
end
self.status = AsyncTask.Status.Fulfilled
self.value = res
for _, c in ipairs(self.children) do
c()
end
end, function(err)
if self.status ~= AsyncTask.Status.Pending then
return
end
self.status = AsyncTask.Status.Rejected
self.value = err
for _, c in ipairs(self.children) do
c()
end
end)
end)
if not ok then
self.status = AsyncTask.Status.Rejected
self.value = err
for _, c in ipairs(self.children) do
c()
end
end
return self
end
---Sync async task.
---@NOTE: This method uses `vim.wait` so that this can't wait the typeahead to be empty.
---@param timeout? number
---@return any
function AsyncTask:sync(timeout)
vim.wait(timeout or 24 * 60 * 60 * 1000, function()
return self.status ~= AsyncTask.Status.Pending
end, 0)
if self.status == AsyncTask.Status.Rejected then
error(self.value)
end
if self.status ~= AsyncTask.Status.Fulfilled then
error("AsyncTask:sync is timeout.")
end
return self.value
end
---Register next step.
---@param on_fulfilled fun(value: any): any
function AsyncTask:next(on_fulfilled)
return self:_dispatch(on_fulfilled, function(err)
error(err)
end)
end
---Register catch step.
---@param on_rejected fun(value: any): any
---@return ___plugin_name___.kit.Async.AsyncTask
function AsyncTask:catch(on_rejected)
return self:_dispatch(function(value)
return value
end, on_rejected)
end
---Dispatch task state.
---@param on_fulfilled fun(value: any): any
---@param on_rejected fun(err: any): any
---@return ___plugin_name___.kit.Async.AsyncTask
function AsyncTask:_dispatch(on_fulfilled, on_rejected)
self.chained = true
local function dispatch(resolve, reject)
if self.status == AsyncTask.Status.Fulfilled then
local res = on_fulfilled(self.value)
if AsyncTask.is(res) then
res:next(resolve, reject)
else
resolve(res)
end
else
local res = on_rejected(self.value)
if AsyncTask.is(res) then
res:next(resolve, reject)
else
resolve(res)
end
end
end
if self.status == AsyncTask.Status.Pending then
return AsyncTask.new(function(resolve, reject)
table.insert(self.children, function()
dispatch(resolve, reject)
end)
end)
end
return AsyncTask.new(dispatch)
end
return AsyncTask
================================================
FILE: lua/translate/kit/Async/AsyncTask.spec.lua
================================================
local AsyncTask = require("___plugin_name___.kit.Async.AsyncTask")
describe("kit.Async", function()
local once = function(fn)
local done = false
return function(...)
if done then
error("already called")
end
done = true
return fn(...)
end
end
it("should work AsyncTask:{next/catch}", function()
-- first.
local one_task = AsyncTask.new(once(function(resolve)
vim.schedule(function()
resolve(1)
end)
end))
assert.are.equals(one_task:sync(), 1)
-- next with return value.
local two_task = one_task:next(once(function(value)
return value + 1
end))
assert.are.equals(two_task:sync(), 2)
-- next with return AsyncTask.
local three_task = two_task:next(once(function(value)
return AsyncTask.new(function(resolve)
vim.schedule(function()
resolve(value + 1)
end)
end)
end))
assert.are.equals(three_task:sync(), 3)
-- throw error.
local err_task = three_task:next(once(function()
error("error")
end))
local _, err = pcall(function()
return err_task:sync()
end)
assert.are_not.equals(string.match(err, "error$"), nil)
-- skip rejected task's next.
local steps = {}
local catch_task = err_task
:next(once(function()
table.insert(steps, 1)
end))
:next(once(function()
table.insert(steps, 2)
end))
:catch(function()
return "catch"
end)
:next(function(value)
table.insert(steps, 3)
return value
end)
assert.are.same(steps, { 3 })
assert.are.equals(catch_task:sync(), "catch")
end)
it("should throw timeout error", function()
local task = AsyncTask.new(function(resolve)
vim.defer_fn(resolve, 500)
end)
local ok = pcall(function()
return task:sync(100)
end)
assert.is_false(ok)
end)
it("should work AsyncTask.all", function()
local now = vim.loop.now()
local values = AsyncTask.all({
AsyncTask.new(function(resolve)
vim.defer_fn(function()
resolve(1)
end, 300)
end),
AsyncTask.new(function(resolve)
vim.defer_fn(function()
resolve(2)
end, 200)
end),
AsyncTask.new(function(resolve)
vim.defer_fn(function()
resolve(3)
end, 100)
end),
}):sync()
assert.are.same(values, { 1, 2, 3 })
assert.is_true((vim.loop.now() - now) - 300 < 10)
end)
it("should work AsyncTask.on_unhandled_rejection", function()
local object
local called = false
AsyncTask.on_unhandled_rejection = function()
called = true
end
-- has no catched task.
object = AsyncTask.new(function()
error("error")
end)
object = nil
called = false
collectgarbage("collect")
assert.are.equals(object, nil)
assert.are.equals(called, true)
-- has no catched task.
object = AsyncTask.new(function()
error("error")
end):catch(function()
-- ignore
end)
object = nil
called = false
collectgarbage("collect")
assert.are.equals(object, nil)
assert.are.equals(called, false)
-- has no catched task.
object = AsyncTask.new(function()
error("error")
end):next(function()
-- ignore
end)
object = nil
called = false
collectgarbage("collect")
assert.are.equals(object, nil)
assert.are.equals(called, true)
end)
end)
================================================
FILE: lua/translate/kit/Async/init.lua
================================================
local AsyncTask = require("___plugin_name___.kit.Async.AsyncTask")
_G.__kit__ = _G.__kit__ or {}
_G.__kit__.Async = _G.__kit__.Async or {}
_G.__kit__.Async.threads = _G.__kit__.Async.threads or {}
local Async = {}
---Run async function immediately.
---@param runner fun()
---@param ... any
---@return ___plugin_name___.kit.Async.AsyncTask
function Async.run(runner, ...)
return Async.async(runner)(...)
end
---Create async function.
---@param runner fun()
---@return fun(): ___plugin_name___.kit.Async.AsyncTask
function Async.async(runner)
return function(...)
local args = { ... }
return AsyncTask.new(function(resolve, reject)
local thread = coroutine.create(runner)
_G.__kit__.Async.threads[thread] = true
local function next_step(ok, v)
if coroutine.status(thread) == "dead" then
_G.__kit__.Async.threads[thread] = nil
if not ok then
return reject(v)
end
return AsyncTask.resolve(v):next(resolve):catch(reject)
end
AsyncTask.resolve(v)
:next(function(...)
next_step(coroutine.resume(thread, ...))
end)
:catch(function(...)
next_step(coroutine.resume(thread, ...))
end)
end
next_step(coroutine.resume(thread, unpack(args)))
end)
end
end
---Await async task.
---@param task ___plugin_name___.kit.Async.AsyncTask
---@return any
function Async.await(task)
if not _G.__kit__.Async.threads[coroutine.running()] then
error("`Async.await` must be called in async function.")
end
return coroutine.yield(AsyncTask.resolve(task))
end
return Async
================================================
FILE: lua/translate/kit/Async/init.spec.lua
================================================
local Async = require("___plugin_name___.kit.Async")
local AsyncTask = require("___plugin_name___.kit.Async.AsyncTask")
local async = Async.async
local await = Async.await
describe("kit.Async", function()
it("should work like JavaScript Promise", function()
local multiply = async(function(v)
return AsyncTask.new(function(resolve)
vim.schedule(function()
resolve(v * v)
end)
end)
end)
local num = async(function()
local num = 2
num = await(multiply(num))
num = await(multiply(num))
return num
end)():sync()
assert.are.equal(num, 16)
end)
end)
================================================
FILE: lua/translate/kit/Cache.lua
================================================
---Create cache key.
---@private
---@param key string[]|string
---@return string
local function _key(key)
if type(key) == "table" then
return table.concat(key, ":")
end
return key
end
---@class ___plugin_name___.kit.Cache
---@field private keys table<string, boolean>
---@field private entries table<string, any>
local Cache = {}
---Create new cache instance.
function Cache.new()
local self = setmetatable({}, { __index = Cache })
self.keys = {}
self.entries = {}
return self
end
---Get cache entry.
---@param key string[]|string
---@return any
function Cache:get(key)
return self.entries[_key(key)]
end
---Set cache entry.
---@param key string[]|string
---@param val any
function Cache:set(key, val)
key = _key(key)
self.keys[key] = true
self.entries[key] = val
end
---Delete cache entry.
---@param key string[]|string
function Cache:del(key)
key = _key(key)
self.keys[key] = nil
self.entries[key] = nil
end
---Return this cache has the key entry or not.
---@param key string[]|string
---@return boolean
function Cache:has(key)
key = _key(key)
return not not self.keys[key]
end
---Ensure cache entry.
---@generic T
---@param key string[]|string
---@param callback function(): T
---@return T
function Cache:ensure(key, callback)
if not self:has(key) then
self:set(key, callback())
end
return self:get(key)
end
return Cache
================================================
FILE: lua/translate/kit/Cache.spec.lua
================================================
local Cache = require("___plugin_name___.kit.Cache")
describe("kit.Cache", function()
it("should works {get,set,has,del}", function()
local cache = Cache.new()
assert.equal(cache:get("unknown"), nil)
assert.equal(cache:has("unknown"), false)
cache:set("known", nil)
assert.equal(cache:get("known"), nil)
assert.equal(cache:has("known"), true)
cache:del("known")
assert.equal(cache:get("known"), nil)
assert.equal(cache:has("known"), false)
end)
it("should work ensure", function()
local ensure = setmetatable({
count = 0,
}, {
__call = function(self)
self.count = self.count + 1
end,
})
local cache = Cache.new()
-- Ensure the value.
assert.equal(cache:ensure("key", ensure), nil)
assert.equal(cache:has("key"), true)
assert.equal(ensure.count, 1)
-- Doesn't call when the value was ensured.
assert.equal(cache:ensure("key", ensure), nil)
assert.equal(ensure.count, 1)
-- Call after delete.
cache:del("key")
assert.equal(cache:ensure("key", ensure), nil)
assert.equal(ensure.count, 2)
end)
end)
================================================
FILE: lua/translate/kit/Config.lua
================================================
local kit = require("___plugin_name___.kit")
local Cache = require("___plugin_name___.kit.Cache")
---@class ___plugin_name___.kit.Config.Schema # kit.macro.remove
---@alias ___plugin_name___.kit.Config.SchemaInternal ___plugin_name___.kit.Config.Schema|{ revision: integer }
---@class ___plugin_name___.kit.Config
---@field private _cache ___plugin_name___.kit.Cache
---@field private _default ___plugin_name___.kit.Config.SchemaInternal
---@field private _global ___plugin_name___.kit.Config.SchemaInternal
---@field private _filetype table<string, ___plugin_name___.kit.Config.SchemaInternal>
---@field private _buffer table<integer, ___plugin_name___.kit.Config.SchemaInternal>
local Config = {}
---Create new config instance.
---@param default? ___plugin_name___.kit.Config.Schema
function Config.new(default)
local self = setmetatable({}, { __index = Config })
self._cache = Cache.new()
self._default = default or {}
self._global = {}
self._filetype = {}
self._buffer = {}
return self
end
---Set default configuration.
---@param default ___plugin_name___.kit.Config.Schema
function Config:default(default)
self._default = default
end
---Update global config.
---@param config ___plugin_name___.kit.Config.Schema
function Config:global(config)
local revision = (self._global.revision or 1) + 1
self._global = config or {}
self._global.revision = revision
end
---Update filetype config.
---@param filetypes string|string[]
---@param config ___plugin_name___.kit.Config.Schema
function Config:filetype(filetypes, config)
for _, filetype in ipairs(kit.to_array(filetypes)) do
local revision = ((self._filetype[filetype] or {}).revision or 1) + 1
self._filetype[filetype] = config or {}
self._filetype[filetype].revision = revision
end
end
---Update filetype config.
---@param bufnr integer
---@param config ___plugin_name___.kit.Config.Schema
function Config:buffer(bufnr, config)
bufnr = bufnr == 0 and vim.api.nvim_get_current_buf() or bufnr
local revision = ((self._buffer[bufnr] or {}).revision or 1) + 1
self._buffer[bufnr] = config or {}
self._buffer[bufnr].revision = revision
end
---Create setup interface.
---@return fun(config: ___plugin_name___.kit.Config.Schema)|{ filetype: fun(filetypes: string|string[], config: ___plugin_name___.kit.Config.Schema), buffer: fun(bufnr: integer, config: ___plugin_name___.kit.Config.Schema) }
function Config:create_setup_interface()
return setmetatable({}, {
---@param config ___plugin_name___.kit.Config.Schema
__call = function(_, config)
self:global(config)
end,
---@param filetypes string|string[]
---@param config ___plugin_name___.kit.Config.Schema
filetype = function(_, filetypes, config)
self:filetype(filetypes, config)
end,
---@param bufnr integer
---@param config ___plugin_name___.kit.Config.Schema
buffer = function(_, bufnr, config)
self:buffer(bufnr, config)
end,
})
end
---Get current configuration.
---@return ___plugin_name___.kit.Config.Schema
function Config:get()
local filetype = vim.api.nvim_buf_get_option(0, "filetype")
local bufnr = vim.api.nvim_get_current_buf()
return self._cache:ensure({
self._global.revision or 0,
(self._buffer[bufnr] or {}).revision or 0,
(self._filetype[filetype] or {}).revision or 0,
}, function()
local config = self._default
config = kit.merge(self._global, config)
config = kit.merge(self._filetype[filetype] or {}, config)
config = kit.merge(self._buffer[bufnr] or {}, config)
config.revision = nil
return config
end)
end
return Config
================================================
FILE: lua/translate/kit/Config.spec.lua
================================================
local Config = require("___plugin_name___.kit.Config")
describe("kit.Config", function()
before_each(function()
vim.cmd([[enew]])
end)
it("should {setup,get} global config", function()
local config = Config.new()
config:global({ key = 1 })
assert.are.same(config:get(), { key = 1 })
end)
it("should {setup,get} filetype config", function()
local config = Config.new()
vim.cmd([[set filetype=lua]])
config:filetype("lua", { key = 1 })
assert.are.same(config:get(), { key = 1 })
vim.cmd([[set filetype=]])
assert.are.same(config:get(), {})
end)
it("should {setup,get} buffer config", function()
local config = Config.new()
config:buffer(0, { key = 1 })
assert.are.same(config:get(), { key = 1 })
vim.cmd([[new]])
assert.are.same(config:get(), {})
end)
it("should merge configuration", function()
local config = Config.new()
local bufnr = vim.api.nvim_get_current_buf()
vim.cmd([[set filetype=lua]])
config:global({ global = 1 })
config:filetype("lua", { filetype = 1 })
config:buffer(0, { buffer = 1 })
assert.are.same(config:get(), { global = 1, filetype = 1, buffer = 1 })
vim.cmd([[set filetype=]])
assert.are.same(config:get(), { global = 1, buffer = 1 })
vim.cmd([[new]])
assert.are.same(config:get(), { global = 1 })
vim.cmd(([[%sbuffer]]):format(bufnr))
assert.are.same(config:get(), { global = 1, buffer = 1 })
vim.cmd([[set filetype=lua]])
assert.are.same(config:get(), { global = 1, filetype = 1, buffer = 1 })
end)
end)
================================================
FILE: lua/translate/kit/LSP/Position.lua
================================================
local Buffer = require("___plugin_name___.kit.Vim.Buffer")
---@class ___plugin_name___.kit.LSP.Position
---@field public line integer
---@field public character integer
local Position = {}
---@alias ___plugin_name___.kit.LSP.Position.Encoding 'utf8'|'utf16'|'utf32'
Position.Encoding = {}
Position.Encoding.UTF8 = "utf8"
Position.Encoding.UTF16 = "utf16"
Position.Encoding.UTF32 = "utf32"
---Return the value is position or not.
---@param v any
---@return boolean
function Position.is(v)
return type(v) == "table" and type(v.line) == "number" and type(v.character) == "number"
end
---Create cursor position.
---@param encoding ___plugin_name___.kit.LSP.Position.Encoding
---@return ___plugin_name___.kit.LSP.Position
function Position.cursor(encoding)
encoding = encoding or Position.Encoding.UTF16
local cursor = vim.api.nvim_win_get_cursor(0)
local text = vim.api.nvim_get_current_line()
local utf8 = { line = cursor[1] - 1, character = cursor[2] }
if encoding == Position.Encoding.UTF8 then
return utf8
elseif encoding == Position.Encoding.UTF16 then
return Position.to_utf16(text, utf8, Position.Encoding.UTF8)
elseif encoding == Position.Encoding.UTF32 then
return Position.to_utf32(text, utf8, Position.Encoding.UTF8)
end
end
---Convert position to utf8 from specified encoding.
---@param expr string|integer
---@param position ___plugin_name___.kit.LSP.Position
---@param from_encoding ___plugin_name___.kit.LSP.Position.Encoding
---@return ___plugin_name___.kit.LSP.Position
function Position.to_vim(expr, position, from_encoding)
if from_encoding == Position.Encoding.UTF8 then
return position
end
local text = Buffer.at(expr, position.line)
if from_encoding == Position.Encoding.UTF16 then
return Position.to_utf8(text, position, Position.Encoding.UTF16)
elseif from_encoding == Position.Encoding.UTF32 then
return Position.to_utf8(text, position, Position.Encoding.UTF32)
end
end
---Convert position to utf8 from specified encoding.
---@param text string
---@param position ___plugin_name___.kit.LSP.Position
---@param from_encoding? ___plugin_name___.kit.LSP.Position.Encoding
---@return ___plugin_name___.kit.LSP.Position
function Position.to_utf8(text, position, from_encoding)
from_encoding = from_encoding or Position.Encoding.UTF16
if from_encoding == Position.Encoding.UTF8 then
return position
end
local ok, byteindex = pcall(function()
return vim.str_byteindex(text, position.character, from_encoding == Position.Encoding.UTF16)
end)
if not ok then
return position
end
return { line = position.line, character = byteindex }
end
---Convert position to utf16 from specified encoding.
---@param text string
---@param position ___plugin_name___.kit.LSP.Position
---@param from_encoding? ___plugin_name___.kit.LSP.Position.Encoding
---@return ___plugin_name___.kit.LSP.Position
function Position.to_utf16(text, position, from_encoding)
local utf8 = Position.to_utf8(text, position, from_encoding)
for index = utf8.character, 0, -1 do
local ok, utf16index = pcall(function()
return select(2, vim.str_utfindex(text, index))
end)
if ok then
return { line = utf8.line, character = utf16index }
end
end
return position
end
---Convert position to utf32 from specified encoding.
---@param text string
---@param position ___plugin_name___.kit.LSP.Position
---@param from_encoding? ___plugin_name___.kit.LSP.Position.Encoding
---@return ___plugin_name___.kit.LSP.Position
function Position.to_utf32(text, position, from_encoding)
local utf8 = Position.to_utf8(text, position, from_encoding)
for index = utf8.character, 0, -1 do
local ok, utf32index = pcall(function()
return select(1, vim.str_utfindex(text, index))
end)
if ok then
return { line = utf8.line, character = utf32index }
end
end
return position
end
return Position
================================================
FILE: lua/translate/kit/LSP/Position.spec.lua
================================================
local Position = require("___plugin_name___.kit.LSP.Position")
describe("kit.LSP.Position", function()
local text = "🗿🗿🗿"
local utf8 = #text
local utf16 = select(2, vim.str_utfindex(text, utf8))
local utf32 = select(1, vim.str_utfindex(text, utf8))
before_each(function()
vim.cmd(([[
enew!
set noswapfile
call setline(1, ['%s'])
]]):format(text))
end)
for _, to in ipairs({
{
method = "to_utf8",
encoding = Position.Encoding.UTF8,
character = utf8,
},
{
method = "to_utf16",
encoding = Position.Encoding.UTF16,
character = utf16,
},
{
method = "to_utf32",
encoding = Position.Encoding.UTF32,
character = utf32,
},
}) do
for _, from in ipairs({
{ character = utf8, encoding = Position.Encoding.UTF8 },
{ character = utf16, encoding = Position.Encoding.UTF16 },
{ character = utf32, encoding = Position.Encoding.UTF32 },
}) do
it(("should convert %s <- %s"):format(to.encoding, from.encoding), function()
local converted = Position[to.method](text, { line = 1, character = from.character }, from.encoding)
assert.are.same(to.character, converted.character)
end)
end
end
end)
================================================
FILE: lua/translate/kit/LSP/Range.lua
================================================
local Position = require("___plugin_name___.kit.LSP.Position")
---@class ___plugin_name___.kit.LSP.Range
---@field public start ___plugin_name___.kit.LSP.Position
---@field public ['end'] ___plugin_name___.kit.LSP.Position
local Range = {}
---Return the value is range or not.
---@param v any
---@return boolean
function Range.is(range)
return type(range) == "table" and Position.is(range.start) and Position.is(range["end"])
end
---Return the range is empty or not.
---@param range ___plugin_name___.kit.LSP.Range
---@return boolean
function Range.empty(range)
return range.start.line == range["end"].line and range.start.character == range["end"].character
end
---Convert range to utf8 from specified encoding.
---@param expr string|integer
---@param range ___plugin_name___.kit.LSP.Range
---@param from_encoding ___plugin_name___.kit.LSP.Position.Encoding
---@return ___plugin_name___.kit.LSP.Range
function Range.to_vim(expr, range, from_encoding)
return {
start = Position.to_vim(expr, range.start, from_encoding),
["end"] = Position.to_vim(expr, range["end"], from_encoding),
}
end
return Range
================================================
FILE: lua/translate/kit/LSP/Range.spec.lua
================================================
local Range = require("___plugin_name___.kit.LSP.Range")
describe("kit.LSP.Range", function()
it("should return the range is empty or not", function()
local position1 = { line = 0, character = 0 }
local position2 = { line = 0, character = 1 }
assert.are.equal(Range.empty({ start = position1, ["end"] = position1 }), true)
assert.are.equal(Range.empty({ start = position1, ["end"] = position2 }), false)
end)
end)
================================================
FILE: lua/translate/kit/Lua/TreeSitter.lua
================================================
local TreeSitter = {}
---@alias ___plugin_name___.kit.Lua.TreeSitter.VisitStatus 'stop'|'skip'
TreeSitter.VisitStatus = {}
TreeSitter.VisitStatus.Stop = "stop"
TreeSitter.VisitStatus.Skip = "skip"
---Get the leaf node at the specified position.
---@param row integer # 0-based
---@param col integer # 0-based
---@return userdata?
function TreeSitter.get_node_at(row, col)
local parser = TreeSitter.get_parser()
if not parser then
return
end
for _, tree in ipairs(parser:trees()) do
local node = tree:root():descendant_for_range(row, col, row, col)
if node then
local leaf = TreeSitter.get_first_leaf(node)
if leaf then
return leaf
end
end
end
end
---Get first leaf node within the specified node.
---@param node userdata
---@return userdata?
function TreeSitter.get_first_leaf(node)
if node:child_count() > 0 then
return TreeSitter.get_first_leaf(node:child(0))
end
return node
end
---Get last leaf node within the specified node.
---@param node userdata
---@return userdata?
function TreeSitter.get_last_leaf(node)
if node:child_count() > 0 then
return TreeSitter.get_last_leaf(node:child(node:child_count() - 1))
end
return node
end
---Get next leaf node.
---@param node userdata
---@return userdata?
function TreeSitter.get_next_leaf(node)
local function next(node_)
local next_sibling = node_:next_sibling()
if next_sibling then
return TreeSitter.get_first_leaf(next_sibling)
else
local parent = node_:parent()
while parent do
next_sibling = parent:next_sibling()
if next_sibling then
return TreeSitter.get_first_leaf(next_sibling)
end
parent = parent:parent()
end
end
end
return next(TreeSitter.get_first_leaf(node))
end
---Get prev leaf node.
---@param node userdata
---@return userdata
function TreeSitter.get_prev_leaf(node)
local function prev(node_)
local prev_sibling = node_:prev_sibling()
if prev_sibling then
return TreeSitter.get_last_leaf(prev_sibling)
else
local parent = node_:parent()
while parent do
prev_sibling = parent:prev_sibling()
if prev_sibling then
return TreeSitter.get_last_leaf(prev_sibling)
end
parent = parent:parent()
end
end
end
return prev(TreeSitter.get_last_leaf(node))
end
---Return the node contained the position or not.
---@param node userdata
---@param row integer # 0-based
---@param col integer # 0-based
---@param option { s: boolean, e: boolean }
---@return boolean
function TreeSitter.within(node, row, col, option)
option = option or {}
option.s = option.s ~= nil and option.s or true
option.e = option.e ~= nil and option.e or false
local s_row, s_col, e_row, e_col = node:range()
local s_in = s_row < row or (s_row == row and (option.s and (s_col <= col) or (s_col < col)))
local e_in = row < e_row or (row == e_row and (option.e and (col <= e_col) or (col < e_col)))
return s_in and e_in
end
---Extract nodes that matched the specified mapping.
---@param scope userdata
---@param mapping table
---@return userdata[]
function TreeSitter.extract(scope, mapping)
local nodes = {}
for node_type, next_mapping in pairs(mapping) do
if node_type == scope:type() then
if type(next_mapping) == "table" then
for c in scope:iter_children() do
for _, node in ipairs(TreeSitter.extract(c, next_mapping)) do
table.insert(nodes, node)
end
end
elseif next_mapping == true then
table.insert(nodes, scope)
end
end
end
return nodes
end
---Return the node is matched the specified mapping.
---@param node userdata
---@param mapping table
---@return userdata?
function TreeSitter.matches(node, mapping)
local parent = node
while parent do
if vim.tbl_contains(TreeSitter.extract(parent, mapping), node) then
return parent
end
parent = parent:parent()
end
end
---Search next specific node.
---@param node userdata
---@param predicate fun(node: userdata): boolean
---@return userdata?
function TreeSitter.search_next(node, predicate)
local current = node
while current do
-- down search.
local matched = nil
TreeSitter.visit(current, function(node_)
if node ~= node_ and predicate(node_) then
matched = node_
return TreeSitter.VisitStatus.Stop
end
end)
if matched then
return matched
end
-- up search.
while current do
local next_sibling = current:next_sibling()
if next_sibling then
current = next_sibling
break
end
current = current:parent()
end
end
end
---Search specific parent node.
---@param node userdata
---@param predicate fun(node: userdata): boolean
---@return userdata?
function TreeSitter.search_parent(node, predicate)
local parent = node:parent()
while parent do
if predicate(parent) then
return parent
end
parent = parent:parent()
end
end
---Get all parents.
---@param node userdata
---@return userdata[]
function TreeSitter.parents(node)
local parents = {}
while node do
table.insert(parents, 1, node)
node = node:parent()
end
return parents
end
---Visit all nodes.
---@param scope userdata
---@param predicate fun(node: userdata, ctx: { depth: integer }): boolean
---@param option? { reversed: boolean }
function TreeSitter.visit(scope, predicate, option)
option = option or { reversed = false }
local function visit(node, ctx)
if not node then
return true
end
local status = predicate(node, ctx)
if status == TreeSitter.VisitStatus.Stop then
return status -- stop visitting.
elseif status ~= TreeSitter.VisitStatus.Skip then
local init, last, step
if option.reversed then
init, last, step = node:child_count() - 1, 0, -1
else
init, last, step = 0, node:child_count() - 1, 1
end
for i = init, last, step do
if visit(node:child(i), { depth = ctx.depth + 1 }) == TreeSitter.VisitStatus.Stop then
return TreeSitter.VisitStatus.Stop
end
end
end
end
return visit(scope, { depth = 1 })
end
---Return the node is matched the specified capture.
---@param query userdata
---@param node userdata
---@return boolean
function TreeSitter.is_capture(query, node, capture)
for id, match in query:iter_captures(node:parent()) do
if match:id() == node:id() and query.captures[id] == capture then
return true
end
end
return false
end
---Get node text.
---@param node userdata
---@return string[]
function TreeSitter.get_node_text(node)
local ok, text = pcall(function()
local args = { 0, node:range() }
table.insert(args, {})
return vim.api.nvim_buf_get_text(unpack(args))
end)
if not ok then
return { "" }
end
return text
end
---Get parser.
---@return table
function TreeSitter.get_parser()
return vim.treesitter.get_parser(0, vim.api.nvim_buf_get_option(0, "filetype"))
end
---Dump node or node-table.
---@param node userdata|userdata[]
function TreeSitter.dump(node)
if not node then
return print(node)
end
if type(node) == "table" then
if #node == 0 then
return print("empty table")
end
for _, v in ipairs(node) do
TreeSitter.dump(v)
end
return
end
local message = node:type()
local current = node:parent()
while current do
message = current:type() .. " ~ " .. message
current = current:parent()
if not current then
break
end
end
print(message)
end
return TreeSitter
================================================
FILE: lua/translate/kit/Lua/TreeSitter.spec.lua
================================================
---@diagnostic disable: need-check-nil, param-type-mismatch
local helper = require("kit.helper")
local TreeSitter = require("___plugin_name___.kit.Lua.TreeSitter")
describe("kit.Lua.TreeSitter", function()
before_each(function()
vim.cmd([[
enew!
syntax off
set filetype=lua
call setline(1, [
\ 'function A()',
\ ' return 1',
\ 'end',
\ 'if "then" then',
\ ' print(a())',
\ 'elseif "else if" then',
\ ' print(a())',
\ 'elseif "else if" then',
\ ' if "then" then',
\ ' return 1',
\ ' end',
\ 'else',
\ ' print(a())',
\ 'end',
\ ])
]])
end)
describe("get_next_leaf & get_prev_leaf", function()
it("should return all leaves", function()
local current, lines = nil, vim.api.nvim_buf_get_lines(0, 0, -1, false)
current = TreeSitter.get_node_at(0, 0)
local next_leaves = {}
while current do
table.insert(next_leaves, TreeSitter.get_node_text(current))
current = TreeSitter.get_next_leaf(current)
end
current = TreeSitter.get_node_at(#lines - 1, #lines[#lines] - 1)
local prev_leaves = {}
while current do
table.insert(prev_leaves, 1, TreeSitter.get_node_text(current))
current = TreeSitter.get_prev_leaf(current)
end
assert.are.same(next_leaves, prev_leaves)
end)
end)
describe("get_captures", function()
it("should return all captured name", function()
vim.treesitter.set_query(
"lua",
"pairs",
[[
[
(function_declaration [
("function" @pair)
("end" @pair)
])
] @pair_context
]]
)
local node = TreeSitter.get_node_at(0, 0)
assert.is_true(TreeSitter.is_capture(vim.treesitter.get_query("lua", "pairs"), node, "pair"))
end)
end)
end)
================================================
FILE: lua/translate/kit/Lua/init.lua
================================================
local Lua = {}
---Create gabage collection detector.
---@param callback fun(...: any): any
---@return userdata
function Lua.gc(callback)
local gc = newproxy(true)
getmetatable(gc).__gc = callback
return gc
end
return Lua
================================================
FILE: lua/translate/kit/Lua/init.spec.lua
================================================
local Lua = require("___plugin_name___.kit.Lua")
describe("kit.Lua", function()
it("should detect gc timing.", function()
local called = false
local object = {
marker = Lua.gc(function()
called = true
end),
}
object = nil
collectgarbage("collect")
assert.are.equals(object, nil)
assert.are.equals(called, true)
end)
end)
================================================
FILE: lua/translate/kit/Vim/Buffer.lua
================================================
local kit = require("___plugin_name___.kit")
local Highlight = require("___plugin_name___.kit.Vim.Highlight")
local Buffer = {}
---Ensure buffer number.
---NOTE: This function only supports '%' as special symbols.
---NOTE: This function uses `vim.fn.bufload`. It can cause side-effect.
---@param expr string|number
---@return number
function Buffer.ensure(expr)
if type(expr) == "number" then
if not vim.api.nvim_buf_is_valid(expr) then
error(string.format([=[[kit.Vim.Buffer] expr=`%s` is not a valid]=], expr))
end
else
if expr == "%" then
expr = vim.api.nvim_get_current_buf()
end
if vim.fn.bufexists(expr) == 0 then
expr = vim.fn.bufadd(expr)
vim.api.nvim_buf_set_option(expr, "buflisted", true)
else
expr = vim.fn.bufnr(expr)
end
end
if not vim.api.nvim_buf_is_loaded(expr) then
vim.fn.bufload(expr)
end
return expr
end
---Get buffer line.
---@param expr string|number
---@param line number
---@return string
function Buffer.at(expr, line)
return vim.api.nvim_buf_get_lines(Buffer.ensure(expr), line, line + 1, false)[1] or ""
end
---Open buffer.
---@param cmd table # The `new` command argument. See :help nvim_parse_cmd()`
---@param range? ___plugin_name___.kit.Vim.Range
function Buffer.open(cmd, range)
vim.cmd.new(cmd)
local Range = require("___plugin_name___.kit.LSP.Range")
if Range.is(range) and not Range.empty(range) then
vim.api.nvim_win_set_cursor(0, { range.start.line + 1, range.start.character })
Highlight.blink(range)
end
end
return Buffer
================================================
FILE: lua/translate/kit/Vim/Buffer.spec.lua
================================================
local Buffer = require("___plugin_name___.kit.Vim.Buffer")
describe("kit.Vim.Buffer", function()
before_each(function()
vim.cmd([[
enew!
set noswapfile
]])
end)
it("should ensure bufnr via didn't loaded filename", function()
local buf = Buffer.ensure(vim.api.nvim_get_runtime_file("syntax/markdown.vim", true)[1])
assert.are.equal(vim.api.nvim_buf_get_option(buf, "buflisted"), true)
assert.are.equal(vim.api.nvim_buf_is_valid(buf), true)
assert.are.equal(vim.api.nvim_buf_is_loaded(buf), true)
assert.are.equal(#vim.api.nvim_buf_get_lines(buf, 0, -1, true), 169)
end)
it("should ensure bufnr via pseudo filename", function()
local buf = Buffer.ensure("this-file-is-not-exists")
assert.are.equal(vim.api.nvim_buf_get_option(buf, "buflisted"), true)
assert.are.equal(vim.api.nvim_buf_is_valid(buf), true)
assert.are.equal(vim.api.nvim_buf_is_loaded(buf), true)
assert.are.equal(#vim.api.nvim_buf_get_lines(buf, 0, -1, true), 1)
end)
it("should ensure bufnr via existing buffer", function()
local org = vim.api.nvim_get_current_buf()
local buf = Buffer.ensure(org)
assert.are.equal(org, buf)
assert.are.equal(vim.api.nvim_buf_get_option(buf, "buflisted"), true)
assert.are.equal(vim.api.nvim_buf_is_valid(buf), true)
assert.are.equal(vim.api.nvim_buf_is_loaded(buf), true)
assert.are.equal(#vim.api.nvim_buf_get_lines(buf, 0, -1, true), 1)
end)
end)
================================================
FILE: lua/translate/kit/Vim/Highlight.lua
================================================
local kit = require("___plugin_name___.kit")
local Async = require("___plugin_name___.kit.Async")
local AsyncTask = require("___plugin_name___.kit.Async.AsyncTask")
local Highlight = {}
Highlight.namespace = vim.api.nvim_create_namespace("___plugin_name___.kit.Vim.Highlight")
---Blink specified range.
---@param range ___plugin_name___.kit.LSP.Range
---@param option? { delay: integer, count: integer }
---@return ___plugin_name___.kit.Async.AsyncTask
function Highlight.blink(range, option)
option = kit.merge(option or {}, {
delay = 150,
count = 2,
})
local function timeout(timeout)
return AsyncTask.new(function(resolve)
vim.defer_fn(vim.schedule_wrap(resolve), timeout)
end)
end
return Async.run(function()
Async.await(timeout(option.delay * 1.2))
for i = 1, option.count do
vim.highlight.range(
0,
Highlight.namespace,
"IncSearch",
{ range.start.line, range.start.character },
{ range["end"].line, range["end"].character },
{}
)
Async.await(timeout(option.delay * 0.8))
vim.api.nvim_buf_clear_namespace(0, Highlight.namespace, 0, -1)
Async.await(timeout(option.delay))
end
end)
end
return Highlight
================================================
FILE: lua/translate/kit/Vim/Highlight.spec.lua
================================================
local Highlight = require("___plugin_name___.kit.Vim.Highlight")
describe("kit.Vim.Highlight", function()
it("should not throw error", function()
Highlight.blink({
start = { line = 0, character = 0 },
["end"] = { line = 0, character = 0 },
}):sync()
end)
end)
================================================
FILE: lua/translate/kit/Vim/Keymap.lua
================================================
local AsyncTask = require("___plugin_name___.kit.Async.AsyncTask")
local Keymap = {}
Keymap._callbacks = {}
---Replace termcodes.
---@param keys string
---@return string
function Keymap.termcodes(keys)
return vim.api.nvim_replace_termcodes(keys, true, true, true)
end
---Send keys.
---@param keys string
---@param mode string
function Keymap.send(keys, mode)
local callback = Keymap.termcodes('<Cmd>lua require("___plugin_name___.kit.Vim.Keymap")._resolve()<CR>')
return AsyncTask.new(function(resolve)
table.insert(Keymap._callbacks, resolve)
if string.match(mode, "i") then
vim.api.nvim_feedkeys(callback, "in", true)
vim.api.nvim_feedkeys(keys, mode, true)
else
vim.api.nvim_feedkeys(keys, mode, true)
vim.api.nvim_feedkeys(callback, "n", true)
end
end)
end
---Test spec helper.
---@param spec fun(): any
function Keymap.spec(spec)
local task = AsyncTask.resolve():next(spec)
vim.api.nvim_feedkeys("", "x", true)
task:sync()
collectgarbage("collect")
end
---Resolve running keys.
function Keymap._resolve()
table.remove(Keymap._callbacks, 1)()
end
return Keymap
================================================
FILE: lua/translate/kit/Vim/Keymap.spec.lua
================================================
local Async = require("___plugin_name___.kit.Async")
local Keymap = require("___plugin_name___.kit.Vim.Keymap")
local async = Async.async
local await = Async.await
describe("kit.Vim.Keymap", function()
it("should insert keysequence with async-await", function()
vim.keymap.set(
"i",
"<Plug>(kit.Vim.Keymap.send)",
async(function()
await(Keymap.send("foo", "in"))
await(Keymap.send("bar", "in"))
await(Keymap.send("baz", "in"))
end)
)
Keymap.spec(async(function()
await(Keymap.send(Keymap.termcodes("i{<Plug>(kit.Vim.Keymap.send)}"), "i"))
end))
--NOTE: The `i` flag works only first time.
assert.are.equals(vim.api.nvim_get_current_line(), "{foo}barbaz")
end)
end)
================================================
FILE: lua/translate/kit/Vim/Syntax.lua
================================================
local kit = require("___plugin_name___.kit")
local Syntax = {}
---Get all syntax groups for specified position.
---NOTE: This function accepts 0-origin cursor position.
---@param cursor number[]
---@return string[]
function Syntax.get_syntax_groups(cursor)
return kit.concat(Syntax.get_vim_syntax_groups(cursor), Syntax.get_treesitter_syntax_groups(cursor))
end
---Get vim's syntax groups for specified position.
---NOTE: This function accepts 0-origin cursor position.
---@param cursor number[]
---@return string[]
function Syntax.get_vim_syntax_groups(cursor)
local groups = {}
for _, syntax_id in ipairs(vim.fn.synstack(cursor[1] + 1, cursor[2] + 1)) do
table.insert(groups, vim.fn.synIDattr(vim.fn.synIDtrans(syntax_id), "name"))
end
return groups
end
---Get tree-sitter's syntax groups for specified position.
---NOTE: This function accepts 0-origin cursor position.
---@param cursor number[]
---@return string[]
function Syntax.get_treesitter_syntax_groups(cursor)
local groups = {}
for _, capture in ipairs(vim.treesitter.get_captures_at_pos(0, cursor[1], cursor[2])) do
table.insert(groups, ("@%s"):format(capture.capture))
end
return groups
end
return Syntax
================================================
FILE: lua/translate/kit/Vim/Syntax.spec.lua
================================================
local helper = require("kit.helper")
local Syntax = require("___plugin_name___.kit.Vim.Syntax")
describe("kit.Vim.Syntax", function()
before_each(function()
vim.cmd([[
enew!
set filetype=vim
call setline(1, ['let var = 1'])
]])
end)
it("should return vim syntax group", function()
vim.cmd([[ syntax on ]])
assert.are.same(Syntax.get_syntax_groups({ 0, 3 }), {})
assert.are.same(Syntax.get_syntax_groups({ 0, 4 }), { "Identifier" })
assert.are.same(Syntax.get_syntax_groups({ 0, 6 }), { "Identifier" })
assert.are.same(Syntax.get_syntax_groups({ 0, 7 }), {})
end)
it("should return treesitter syntax group", function()
helper.ensure_treesitter_parser("vim")
vim.cmd([[ syntax off ]])
assert.are.same(Syntax.get_syntax_groups({ 0, 3 }), {})
assert.are.same(Syntax.get_syntax_groups({ 0, 4 }), { "@variable" })
assert.are.same(Syntax.get_syntax_groups({ 0, 6 }), { "@variable" })
assert.are.same(Syntax.get_syntax_groups({ 0, 7 }), {})
end)
end)
================================================
FILE: lua/translate/kit/init.lua
================================================
--[[
MIT License
Copyright (c) 2022 hrsh7th
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.
]]
--
local kit = {}
---Create unique id.
---@return integer
kit.uuid = setmetatable({
uuid = 0,
}, {
__call = function(self)
self.uuid = self.uuid + 1
return self.uuid
end,
})
-- https://neovim.io/doc/user/deprecated.html#vim.tbl_islist()
local islist = vim.islist or vim.tbl_islist
---Merge two tables.
---@generic T
---NOTE: This doesn't merge array-like table.
---@param tbl1 T
---@param tbl2 T
---@return T
function kit.merge(tbl1, tbl2)
local is_dict1 = type(tbl1) == "table" and (not islist(tbl1) or vim.tbl_isempty(tbl1))
local is_dict2 = type(tbl2) == "table" and (not islist(tbl2) or vim.tbl_isempty(tbl2))
if is_dict1 and is_dict2 then
local new_tbl = {}
for k, v in pairs(tbl2) do
if tbl1[k] ~= vim.NIL then
new_tbl[k] = kit.merge(tbl1[k], v)
end
end
for k, v in pairs(tbl1) do
if tbl2[k] == nil then
new_tbl[k] = v ~= vim.NIL and v or nil
end
end
return new_tbl
elseif is_dict1 and not is_dict2 then
return kit.merge(tbl1, {})
elseif not is_dict1 and is_dict2 then
return kit.merge(tbl2, {})
end
if tbl1 == vim.NIL then
return nil
elseif tbl1 == nil then
return tbl2
else
return tbl1
end
end
---Concatenate two tables.
---NOTE: This doesn't concatenate dict-like table.
---@param tbl1 table
---@param tbl2 table
function kit.concat(tbl1, tbl2)
local new_tbl = {}
for _, item in ipairs(tbl1) do
table.insert(new_tbl, item)
end
for _, item in ipairs(tbl2) do
table.insert(new_tbl, item)
end
return new_tbl
end
---The value to array.
---@param value any
---@return table
function kit.to_array(value)
if type(value) == "table" then
if islist(value) or vim.tbl_isempty(value) then
return value
end
end
return { value }
end
---Check the value is array.
---@param value any
---@return boolean
function kit.is_array(value)
return type(value) == "table" and (islist(value) or vim.tbl_isempty(value))
end
---Reverse the array.
---@param array table
---@return table
function kit.reverse(array)
if not kit.is_array(array) then
error("[kit] specified value is not an array.")
end
local new_array = {}
for i = #array, 1, -1 do
table.insert(new_array, array[i])
end
return new_array
end
---Map array values.
---@generic T
---@param array T[]
---@parma func fun(item: T, index: number): V
---@reutrn T[]
function kit.map(array, func)
local new_array = {}
for i, item in ipairs(array) do
table.insert(new_array, func(item, i))
end
return new_array
end
return kit
================================================
FILE: lua/translate/kit/init.spec.lua
================================================
local kit = require("___plugin_name___.kit")
describe("kit", function()
describe(".merge", function()
it("should merge two dict", function()
assert.are.same(
kit.merge({
a = true,
b = {
c = vim.NIL,
},
d = {
e = 3,
},
}, {
a = false,
b = {
c = true,
},
d = {
f = {
g = vim.NIL,
},
},
}),
{
a = true,
b = {},
d = {
e = 3,
f = {},
},
}
)
end)
end)
describe(".concat", function()
it("should concat two list", function()
assert.are.same(kit.concat({ 1, 2, 3 }, { 4, 5, 6 }), { 1, 2, 3, 4, 5, 6 })
end)
end)
describe(".to_array", function()
it("should convert value to array", function()
assert.are.same(kit.to_array(1), { 1 })
assert.are.same(kit.to_array({ 1, 2, 3 }), { 1, 2, 3 })
assert.are.same(kit.to_array({}), {})
assert.are.same(kit.to_array({ a = 1 }), { { a = 1 } })
end)
end)
describe(".is_array", function()
it("should check array or not", function()
assert.are.equal(kit.is_array({}), true)
assert.are.equal(kit.is_array({ 1 }), true)
assert.are.equal(kit.is_array({ a = 1 }), false)
assert.are.equal(kit.is_array(1), false)
end)
end)
describe(".reverse", function()
it("should reverse the array", function()
assert.are.same(kit.reverse({ 1, 2, 3 }), { 3, 2, 1 })
end)
end)
describe(".map", function()
it("should map array values", function()
local array = kit.map({ "1", "2", "3" }, function(v)
return tonumber(v, 10)
end)
assert.are.same(array, { 1, 2, 3 })
end)
end)
end)
================================================
FILE: lua/translate/preset/command/deepl.lua
================================================
local M = {}
local json_encode = vim.json and vim.json.encode or vim.fn.json_encode
---@param url string
---@param lines string[]
---@param command_args table
---@return string
---@return string[]
function M._cmd(url, lines, command_args)
if not vim.g.deepl_api_auth_key then
error("[translate.nvim] Set your DeepL API authorization key to g:deepl_api_auth_key.")
end
local cmd = "curl"
local args = {
"-X",
"POST",
"-s",
url,
"--header",
"Content-Type: application/json",
"--header",
"Authorization: DeepL-Auth-Key " .. vim.g.deepl_api_auth_key,
"--data",
json_encode({
text = lines,
target_lang = command_args.target,
source_lang = command_args.source,
})
}
return cmd, args
end
function M.complete_list(is_target)
-- See <https://www.deepl.com/docs-api/translating-text/>
local list = {
"BG",
"CS",
"DA",
"DE",
"EL",
"EN",
"ES",
"ET",
"FI",
"FR",
"HU",
"IT",
"JA",
"LT",
"LV",
"NL",
"PL",
"PT",
"RO",
"RU",
"SK",
"SL",
"SV",
"ZH",
}
if is_target then
local append = {
"EN-GB",
"EN-US",
"PT-PT",
"PT-BR",
}
list = vim.list_extend(list, append)
end
return list
end
return M
================================================
FILE: lua/translate/preset/command/deepl_free.lua
================================================
local deepl = require("translate.preset.command.deepl")
local M = {}
---@param lines string[]
---@param command_args table
---@return string cmd
---@return string[] args
function M.cmd(lines, command_args)
local url = "https://api-free.deepl.com/v2/translate"
local cmd, args = deepl._cmd(url, lines, command_args)
local options = require("translate.config").get("preset").command.deepl_free
if #options.args > 0 then
args = vim.list_extend(args, options.args)
end
return cmd, args
end
M.complete_list = deepl.complete_list
return M
================================================
FILE: lua/translate/preset/command/deepl_pro.lua
================================================
local deepl = require("translate.preset.command.deepl")
local M = {}
---@param lines string[]
---@param command_args table
---@return string cmd
---@return string[] args
function M.cmd(lines, command_args)
local url = "https://api.deepl.com/v2/translate"
local cmd, args = deepl._cmd(url, lines, command_args)
local options = require("translate.config").get("preset").command.deepl_pro
if #options.args > 0 then
args = vim.list_extend(args, options.args)
end
return cmd, args
end
M.complete_list = deepl.complete_list
return M
================================================
FILE: lua/translate/preset/command/google.lua
================================================
local util = require("translate.util.util")
local M = {}
M.url =
"https://script.google.com/macros/s/AKfycbxLRZgWI3UyHvHuYVyH1StiXbzJDHyibO5XpVZm5kMlXFlzaFVtLReR0ZteEkUbecRpPQ/exec"
---@param lines string[]
---@param command_args table
---@return string
---@return string[]
function M.cmd(lines, command_args)
local data = vim.json.encode({
text = lines,
target = command_args.target,
source = command_args.source,
})
local cmd, args
if vim.fn.has("win32") == 1 then
cmd = "cmd.exe"
local path = util.write_temp_data(data)
args = {
"/c",
table.concat({
"curl",
"-sL",
M.url,
"-d",
"@" .. path,
}, " "),
}
else
cmd = "curl"
args = {
"-sL",
M.url,
"-d",
data,
}
end
local options = require("translate.config").get("preset").command.google
if #options.args > 0 then
args = vim.list_extend(args, options.args)
end
return cmd, args
end
function M.complete_list()
-- See <https://cloud.google.com/translate/docs/languages>
local list = {
"af",
"sq",
"am",
"ar",
"hy",
"az",
"eu",
"be",
"bn",
"bs",
"bg",
"ca",
"ceb",
"zh",
"zh-CN",
"zh-TW",
"co",
"hr",
"cs",
"da",
"nl",
"en",
"eo",
"et",
"fi",
"fr",
"fy",
"gl",
"ka",
"de",
"el",
"gu",
"ht",
"ha",
"haw",
"he",
"iw",
"hi",
"hmn",
"hu",
"is",
"ig",
"id",
"ga",
"it",
"ja",
"jv",
"kn",
"kk",
"km",
"rw",
"ko",
"ku",
"ky",
"lo",
"lv",
"lt",
"lb",
"mk",
"mg",
"ms",
"ml",
"mt",
"mi",
"mr",
"mn",
"my",
"ne",
"no",
"ny",
"or",
"ps",
"fa",
"pl",
"pt",
"pa",
"ro",
"ru",
"sm",
"gd",
"sr",
"st",
"sn",
"sd",
"si",
"sk",
"sl",
"so",
"es",
"su",
"sw",
"sv",
"tl",
"tg",
"ta",
"tt",
"te",
"th",
"tr",
"tk",
"uk",
"ur",
"ug",
"uz",
"vi",
"cy",
"xh",
"yi",
"yo",
"zu",
}
return list
end
return M
================================================
FILE: lua/translate/preset/command/translate_shell.lua
================================================
local M = {}
---@param lines string[]
---@param command_args table
---@return string
---@return string[]
function M.cmd(lines, command_args)
local text = table.concat(lines, "\n")
local source = command_args.source or ""
local target = command_args.target
local cmd = "trans"
local args = {
"-b",
"-no-ansi",
"-no-autocorrect",
}
local options = require("translate.config").get("preset").command.translate_shell
if #options.args > 0 then
args = vim.list_extend(args, options.args)
end
table.insert(args, source .. ":" .. target)
table.insert(args, text)
return cmd, args
end
function M.complete_list()
-- See <https://github.com/soimort/translate-shell/wiki/Languages>
local list = {
"af",
"afr",
"am",
"amh",
"ar",
"ara",
"az",
"aze",
"ba",
"bak",
"be",
"bel",
"bg",
"bul",
"bn",
"ben",
"bs",
"bos",
"ca",
"cat",
"ceb",
"ceb",
"co",
"cos",
"cs",
"ces",
"cy",
"cym",
"da",
"dan",
"de",
"deu",
"el",
"ell",
"en",
"eng",
"eo",
"epo",
"es",
"spa",
"et",
"est",
"eu",
"eus",
"fa",
"fas",
"fi",
"fin",
"fj",
"fij",
"fr",
"fra",
"fy",
"fry",
"ga",
"gle",
"gd",
"gla",
"gl",
"glg",
"gu",
"guj",
"ha",
"hau",
"haw",
"haw",
"he",
"heb",
"hi",
"hin",
"hmn",
"hmn",
"hr",
"hrv",
"ht",
"hat",
"hu",
"hun",
"hy",
"hye",
"id",
"ind",
"ig",
"ibo",
"is",
"isl",
"it",
"ita",
"ja",
"jpn",
"jv",
"jav",
"ka",
"kat",
"kk",
"kaz",
"km",
"khm",
"kn",
"kan",
"ko",
"kor",
"ku",
"kur",
"ky",
"kir",
"la",
"lat",
"lb",
"ltz",
"lo",
"lao",
"lt",
"lit",
"lv",
"lav",
"mg",
"mlg",
"mhr",
"mhr",
"mi",
"mri",
"mk",
"mkd",
"ml",
"mal",
"mn",
"mon",
"mr",
"mar",
"mrj",
"mrj",
"ms",
"msa",
"mt",
"mlt",
"mww",
"mww",
"my",
"mya",
"ne",
"nep",
"nl",
"nld",
"no",
"nor",
"ny",
"nya",
"or",
"ori",
"otq",
"otq",
"pa",
"pan",
"pap",
"pap",
"pl",
"pol",
"ps",
"pus",
"pt",
"por",
"ro",
"ron",
"ru",
"rus",
"rw",
"kin",
"sd",
"snd",
"si",
"sin",
"sk",
"slk",
"sl",
"slv",
"sm",
"smo",
"sn",
"sna",
"so",
"som",
"sq",
"sqi",
"sr-Cyrl",
"srp",
"sr-Latn",
"srp",
"st",
"sot",
"su",
"sun",
"sv",
"swe",
"sw",
"swa",
"ta",
"tam",
"te",
"tel",
"tg",
"tgk",
"th",
"tha",
"tk",
"tuk",
"tl",
"tgl",
"tlh",
"tlh",
"tlh-Qaak",
"tlh",
"to",
"ton",
"tr",
"tur",
"tt",
"tat",
"ty",
"tah",
"udm",
"udm",
"ug",
"uig",
"uk",
"ukr",
"ur",
"urd",
"uz",
"uzb",
"vi",
"vie",
"xh",
"xho",
"yi",
"yid",
"yo",
"yor",
"yua",
"yua",
"yue",
"yue",
"zh-CN",
"zho",
"zh-TW",
"zho",
"zu",
"zul",
}
return list
end
return M
================================================
FILE: lua/translate/preset/output/floating.lua
================================================
local api = vim.api
local util = require("translate.util.util")
local M = {
window = {},
}
function M.cmd(lines, _)
if type(lines) == "string" then
lines = { lines }
end
M.window.close()
local options = require("translate.config").get("preset").output.floating
local buf = api.nvim_create_buf(false, true)
api.nvim_buf_set_lines(buf, 0, -1, true, lines)
api.nvim_set_option_value("filetype", options.filetype, { buf = buf })
local width = util.max_width_in_string_list(lines)
local height = #lines
local win = api.nvim_open_win(buf, false, {
relative = options.relative,
style = options.style,
width = width,
height = height,
row = options.row,
col = options.col,
border = options.border,
zindex = options.zindex,
})
M.window._current = { win = win, buf = buf }
api.nvim_create_autocmd("CursorMoved", {
callback = M.window.close,
once = true,
})
end
function M.window.close()
if M.window._current then
api.nvim_win_close(M.window._current.win, false)
api.nvim_buf_delete(M.window._current.buf, {})
M.window._current = nil
end
end
return M
================================================
FILE: lua/translate/preset/output/insert.lua
================================================
local api = vim.api
local M = {}
function M.cmd(lines, pos)
if type(lines) == "string" then
lines = { lines }
end
local lines_origin = pos._lines
for i, line in ipairs(lines) do
local p = pos[i]
local indent = string.rep(" ", #lines_origin[i]:sub(1, p.col[1] - 1))
lines[i] = indent .. line
end
local options = require("translate.config").get("preset").output.insert
local row
if options.base == "top" then
row = pos[1].row
else -- "bottom"
row = pos[#pos].row
end
row = row + options.off
api.nvim_buf_set_lines(0, row, row, false, lines)
end
return M
================================================
FILE: lua/translate/preset/output/register.lua
================================================
local fn = vim.fn
local M = {}
---Set the register
---@param lines string[]
function M.cmd(lines, _)
local newline
local ff = vim.o.fileformat
if ff == "unix" then
newline = "\n"
elseif ff == "dos" then
newline = "\r\n"
else
newline = "\r"
end
local text = table.concat(lines, newline)
local options = require("translate.config").get("preset").output.register
local name = options.name
fn.setreg(name, text)
end
return M
================================================
FILE: lua/translate/preset/output/replace.lua
================================================
local api = vim.api
local M = {}
function M.cmd(lines, pos)
if type(lines) == "string" then
lines = { lines }
end
local lines_origin = pos._lines
for i, p in ipairs(pos) do
local pre = lines_origin[i]:sub(1, p.col[1] - 1)
local suf = lines_origin[i]:sub(p.col[2] + 1)
lines[i] = pre .. (lines[i] or "") .. suf
end
api.nvim_buf_set_lines(0, pos[1].row - 1, pos[#pos].row, true, lines)
end
return M
================================================
FILE: lua/translate/preset/output/split.lua
================================================
local fn = vim.fn
local api = vim.api
local M = {}
function M.cmd(lines, pos)
if type(lines) == "string" then
lines = { lines }
end
local lines_origin = pos._lines
-- Remain indentation
for i, line in ipairs(lines) do
local p = pos[i]
local indent = string.rep(" ", #lines_origin[i]:sub(1, p.col[1] - 1))
lines[i] = indent .. line
end
local option = require("translate.config").get("preset").output.split
local size = M._get_size(#lines, option)
local function split_win()
local cmd = option.position == "bottom" and "botright" or "topleft"
cmd = cmd .. " " .. size .. "new"
vim.cmd(cmd)
end
local current_win_id = fn.win_getid()
if fn.bufexists(option.name) == 1 then
local bufnr = fn.bufnr(option.name)
local winid = fn.win_findbuf(bufnr)
-- Buffer is present, but window is closed
if vim.tbl_isempty(winid) then
split_win()
vim.cmd("e " .. option.name)
else
fn.win_gotoid(winid[1])
end
else
split_win()
vim.cmd("e " .. option.name)
api.nvim_set_option_value("buftype", "nofile", { buf = 0 })
api.nvim_set_option_value("filetype", option.filetype, { buf = 0 })
end
if option.append and not M._buf_empty() then
api.nvim_buf_set_lines(0, -1, -1, false, lines)
else
api.nvim_buf_set_lines(0, 0, -1, false, lines)
end
-- Move cursor to bottom
api.nvim_win_set_cursor(0, { fn.line("$"), 0 })
fn.win_gotoid(current_win_id)
end
function M._buf_empty()
if fn.line("$") ~= 1 then
return false
end
local line = fn.getline(1)
if line ~= "" then
return false
end
return true
end
function M._get_size(size, option)
local min_size = option.min_size
if min_size < 1 then
min_size = math.floor(api.nvim_win_get_height(0) * min_size)
end
local max_size = option.max_size
if max_size < 1 then
max_size = math.floor(api.nvim_win_get_height(0) * max_size)
end
if size <= min_size then
return min_size
end
if size >= max_size then
return max_size
end
return size
end
return M
================================================
FILE: lua/translate/preset/parse_after/deepl.lua
================================================
local M = {}
local json_decode = vim.json and vim.json.decode or vim.fn.json_decode
---@param response string #json string
---@return string[]
function M.cmd(response)
local decoded = json_decode(response)
local results = {}
for _, r in ipairs(decoded.translations) do
table.insert(results, r.text)
end
return results
end
return M
================================================
FILE: lua/translate/preset/parse_after/deepl_free.lua
================================================
return require("translate.preset.parse_after.deepl")
================================================
FILE: lua/translate/preset/parse_after/deepl_pro.lua
================================================
return require("translate.preset.parse_after.deepl")
================================================
FILE: lua/translate/preset/parse_after/google.lua
================================================
local M = {}
function M.cmd(text, _)
return vim.json.decode(text)
end
return M
================================================
FILE: lua/translate/preset/parse_after/head.lua
================================================
local api = vim.api
local util = require("translate.util.util")
local M = {}
---Cut the results of translation to fit the original width of the selection.
---The width of the last line cannot be guaranteed because the number of characters changes.
---@param lines string[]
---@param pos table
---@return string[]
function M.cmd(lines, pos)
local results = {}
for i, text in ipairs(lines) do
local group = pos._group[i]
local widths_origin = {}
local sum_width_origin = 0
for _, g in ipairs(group) do
local width = api.nvim_strwidth(pos._lines_selected[g])
table.insert(widths_origin, width)
sum_width_origin = sum_width_origin + width
end
local sum_width_result = api.nvim_strwidth(text)
local widths = widths_origin
if sum_width_origin > sum_width_result then
local l = sum_width_origin
for j = #widths, 1, -1 do
local w = widths[j]
l = l - w
if l >= sum_width_result then
table.remove(widths, j)
else
widths[j] = sum_width_result - l
break
end
end
end
if #widths > 1 then
local result = util.text_cut(text, widths)
local diff = #group - #widths
if diff > 0 then
for _ = 1, diff do
table.insert(result, "")
end
end
results = vim.list_extend(results, result)
else
table.insert(results, text)
end
end
return results
end
return M
================================================
FILE: lua/translate/preset/parse_after/no_handle.lua
================================================
local M = {}
function M.cmd(lines, _)
return lines
end
return M
================================================
FILE: lua/translate/preset/parse_after/oneline.lua
================================================
local M = {}
---@param lines string[]
---@return string[]
function M.cmd(lines, _)
lines = { table.concat(lines, "") }
return lines
end
return M
================================================
FILE: lua/translate/preset/parse_after/rate.lua
================================================
local api = vim.api
local util = require("translate.util.util")
local M = {}
---Cut the results of translation to fit the rate of the original width of the selection.
---@param lines string[]
---@param pos table
---@return string[]
function M.cmd(lines, pos)
local results = {}
for i, text in ipairs(lines) do
local group = pos._group[i]
local width_origin = {}
local sum_width_origin = 0
for _, g in ipairs(group) do
local width = api.nvim_strwidth(pos._lines_selected[g])
table.insert(width_origin, width)
sum_width_origin = sum_width_origin + width
end
local sum_width_result = api.nvim_strwidth(text)
local width = vim.tbl_map(function(w)
return math.floor(w / sum_width_origin * sum_width_result)
end, width_origin)
if #width > 1 then
results = vim.list_extend(results, util.text_cut(text, width))
else
table.insert(results, text)
end
end
return results
end
return M
================================================
FILE: lua/translate/preset/parse_after/translate_shell.lua
================================================
local M = {}
---@param text string
---@return string[]
function M.cmd(text, _)
local crlf
-- Remove the extra CRLF at the end.
if vim.endswith(text, "\r\n") then
crlf = "\r\n"
text = text:sub(1, -3)
else
crlf = text:sub(-1)
text = text:sub(1, -2)
end
local lines = vim.split(text, crlf)
return lines
end
return M
================================================
FILE: lua/translate/preset/parse_after/window.lua
================================================
local api = vim.api
local util = require("translate.util.util")
local M = {}
---Cut the text to fit the window width.
---@param lines string[]
---@return string[]
function M.cmd(lines, _)
local option = require("translate.config").get("preset").parse_after.window
local width = option.width
if width <= 1 then
width = math.floor(api.nvim_win_get_width(0) * option.width)
end
local results = {}
for _, text in ipairs(lines) do
results = vim.list_extend(results, util.text_cut(text, width))
end
return results
end
return M
================================================
FILE: lua/translate/preset/parse_before/concat.lua
================================================
local M = {}
---@param lines string[]
---@return string[]
function M.cmd(lines)
local options = require("translate.config").get("preset").parse_before.concat
local sep = options.sep
lines = { table.concat(lines, sep) }
return lines
end
return M
================================================
FILE: lua/translate/preset/parse_before/natural.lua
================================================
local util = require("translate.util.util")
local M = {}
local function inc(tbl, index)
if tbl[index] then
return index + 1
end
return index
end
---@param lines string[]
---@param pos positions
---@param cmd_args table
---@return string[]
function M.cmd(lines, pos, cmd_args)
local option = require("translate.config").get("preset").parse_before.natural
local end_regex
local start_regex
if cmd_args.source then
local ends = M.get_end(cmd_args.source, option)
if ends then
end_regex = vim.regex([[\V\%(]] .. table.concat(ends, [[\|]]) .. [[\)\$]])
end
local starts = M.get_start(cmd_args.source, option)
if starts then
start_regex = vim.regex([[^\V\%(]] .. table.concat(starts, [[\|]]) .. [[\)]])
end
end
pos._group = {}
local results = {}
local original_index, result_index = 1, 1
while true do
local line = lines[original_index]
if not line then
break
end
if line == "" then
result_index = inc(results, result_index)
util.append_dict_list(results, result_index, line)
util.append_dict_list(pos._group, result_index, original_index)
if results[result_index] then
result_index = result_index + 1
end
else
if start_regex and start_regex:match_str(line) then
result_index = inc(results, result_index)
end
util.append_dict_list(results, result_index, line)
util.append_dict_list(pos._group, result_index, original_index)
if end_regex and end_regex:match_str(line) then
result_index = inc(results, result_index)
end
end
original_index = original_index + 1
end
results = vim.tbl_map(function(r)
return table.concat(r, " ")
end, results)
return results
end
M.lang_abbr = {
en = "english",
eng = "english",
ja = "japanese",
jpn = "japanese",
zh = "chinese",
zho = "chinese",
["zh-CN"] = "chinese",
["zh-TW"] = "chinese",
}
-- vim's regex pattern (vary no magic '\V')
M.end_marks = {
english = {
".",
"?",
"!",
":",
";",
},
japanese = {
"。",
".",
"?",
"?",
"!",
"!",
":",
";",
},
chinese = {
"。",
"!",
"?",
":",
},
}
-- vim's regex pattern (vary no magic '\V')
M.start_marks = {
english = {
[[\u\U]],
},
}
function M.get_end(lang, option)
lang = lang:lower()
lang = option.lang_abbr[lang] or M.lang_abbr[lang]
return option.end_marks[lang] or M.end_marks[lang]
end
function M.get_start(lang, option)
lang = lang:lower()
lang = option.lang_abbr[lang] or M.lang_abbr[lang]
return option.start_marks[lang] or M.start_marks[lang]
end
return M
================================================
FILE: lua/translate/preset/parse_before/no_handle.lua
================================================
local M = {}
---@param lines string[]
---@return string[]
function M.cmd(lines)
return lines
end
return M
================================================
FILE: lua/translate/preset/parse_before/trim.lua
================================================
local M = {}
---@param lines string[]
---@param pos positions
---@return string[]
function M.cmd(lines, pos)
for i, line in ipairs(lines) do
local pre = line:match("^%s*")
pos[i].col[1] = pos[i].col[1] + #pre
local suf = line:match("%s*$")
pos[i].col[2] = pos[i].col[2] - #suf
lines[i] = line:sub(#pre + 1, -#suf - 1)
end
return lines
end
return M
================================================
FILE: lua/translate/util/comment.lua
================================================
local fn = vim.fn
local api = vim.api
local context = require("translate.util.context")
local util = require("translate.util.util")
local M = {}
local string_symbols = {
python = {
{ begin = [[''']], last = [[''']] },
{ begin = [["""]], last = [["""]] },
},
}
function M.get_range() -- example 2. (see below)
-- Common comments can be of the following types.
-- 1. The comment string repeats at the start of each line (e.g. this line).
-- This may be strung together on multiple lines to form a single comment.
-- 2. Similar 1., but comments begin in the middle of the line (e.g. the comment four lines above).
-- 3. three-piece comment (e.g. c's '/* comment */').
--
-- First, check to see if the current line is 1. by looking at the beginning of the line
-- since the only case in which it is necessary to recursively examine is in 1.
-- If not 1, then use highlighting or treesitter to take the range of comments.
-- We have already established that it is either 2 or 3, so all that remains is to remove the comment sign.
local comments = M.get_comments()
-- (1, 1) indexed cursor position
local cursor = api.nvim_win_get_cursor(0)
cursor[2] = cursor[2] + 1
local pos = {
_mode = "comment",
}
if M.is_pattern1(comments, cursor[1], pos) then
return pos
end
-- { row_s, col_s, row_e, col_e }
local range = context.ts.get_range("comment", cursor) or context.vim.get_range("Comment", cursor)
if range then
M.remove_comment_symbol(comments, range, pos)
else
-- filetype check
local ft = vim.bo.filetype
if vim.tbl_contains(vim.tbl_keys(string_symbols), ft) then
range = context.ts.get_range("string", cursor) or context.vim.get_range("String", cursor)
if range then
M.remove_string_symbol(string_symbols[ft], range, pos)
end
else
vim.notify("Here is not in comments.")
end
end
return pos
end
function M.get_comments()
-- Ignore 'n' and 'f' because they are complicated and not used often.
local comments = {}
for comment in vim.gsplit(vim.bo.comments, ",") do
local flags, com = comment:match("^(.*):(.*)$")
if flags:find("b") then
-- Blank required after com
com = com .. [[\s]]
end
if flags:find("s") then
-- Start of three-piece comment
util.append_dict_list(comments, "s", com)
elseif flags:find("m") then
-- Middle of three-piece comment
util.append_dict_list(comments, "m", com)
elseif flags:find("e") then
-- End of three-piece comment
util.append_dict_list(comments, "e", com)
elseif not flags:find("f") then
-- When flags have none of the 'f', 's', 'm' or 'e' flags, Vim assumes the comment
-- string repeats at the start of each line. The flags field may be empty.
util.append_dict_list(comments, "empty", com)
end
end
comments = vim.tbl_map(function(c)
return [[\V\%(]] .. table.concat(c, [[\|]]) .. [[\)]]
end, comments)
return comments
end
function M.remove_comment_symbol(comments, range, pos)
local lines = api.nvim_buf_get_lines(0, range[1] - 1, range[3], true)
pos._lines = lines
if range[1] == range[3] and M.is_pattern2(comments, range, pos) then
return pos
end
-- If you have made it this far, it should be pattern 3.
-- So if it fails inside is_pattern3, it is an error.
M.assert_pattern3(comments, range, pos)
return pos
end
---Check if a line of 'row' is pattern 1, and if so, check if the lines above and below they are also pattern 1.
---Even if the pattern is 1, if the indentation and comment symbols are different, they are not considered to be
---in the same group.
---@param comments table
---@param row number
---@param pos table
---@return boolean is_pattern1
function M.is_pattern1(comments, row, pos)
if not util.has_key(comments, "empty") then
return false
end
local ok, col, prefix = M._is_pattern1(comments, row)
if not ok then
return false
end
table.insert(pos, { row = row, col = col })
local function search(dir, border)
local attention_row = row
while true do
attention_row = attention_row + dir
if attention_row == border then
break
end
ok, col = M._is_pattern1(comments, attention_row, prefix)
if not ok then
break
end
local p = { row = attention_row, col = col }
if dir == -1 then
table.insert(pos, 1, p)
else
table.insert(pos, p)
end
end
end
-- Search above
search(-1, 1)
-- Search below
search(1, fn.line("$"))
-- update
pos._lines = api.nvim_buf_get_lines(0, pos[1].row - 1, pos[#pos].row, true)
return true
end
---Checks if a line is pattern 1, and if so, returns the range removed indentation and
---comment string. If we already known a line is pattern 1, using 'prefix' to look for
---lines above and below it that begin with the same indentation and comment string.
---@param comments table
---@param row number
---@param prefix? string
---@return boolean? is_pattern1
---@return table? col
---@return string? prefix
function M._is_pattern1(comments, row, prefix)
-- 1. the comment string repeats at the start of each line (e.g. this line)
local line = fn.getline(row)
if prefix then
return vim.startswith(line, prefix), { #prefix + 1, #line }
else
local indent = [[^\V\s\*]]
local col_s, col_e = vim.regex(indent .. comments.empty):match_line(0, row - 1)
if col_s then
prefix = line:sub(col_s, col_e)
return true, { #prefix + 1, #line }, prefix
end
end
end
function M.is_pattern2(comments, range, pos)
if not util.has_key(comments, "empty") then
return false
end
local line = pos._lines[1]
local comment = line:sub(range[2], range[4])
local _, col_e = vim.regex("^" .. comments.empty):match_str(comment)
if col_e then
table.insert(pos, { row = range[1], col = { range[2] + col_e + 1, range[4] } })
return true
end
end
function M.assert_pattern3(comments, range, pos)
if not util.has_key(comments, "s", "m", "e") then
error("Invalid &comments")
end
-- like v selection
for i, line in ipairs(pos._lines) do
local indent = line:match("^%s*")
local p = { row = range[1] + i - 1, col = { #indent + 1, #line } }
table.insert(pos, p)
end
pos[1].col[1] = range[2]
pos[#pos].col[2] = math.min(pos[#pos].col[2], range[4])
-- Remove start of three-piece
local first_line = pos._lines[1]:sub(range[2])
if vim.regex("^" .. comments.s .. [[\s\*\$]]):match_str(first_line) then
-- This line is unnecessary because it is only a comment string
table.remove(pos, 1)
table.remove(pos._lines, 1)
else
local _, num_of_com = vim.regex("^" .. comments.s):match_str(first_line)
if num_of_com then
pos[1].col[1] = pos[1].col[1] + num_of_com
else
error("The start of three-piece can't found")
end
end
-- Remove middle of three-piece if exists
if #pos > 2 then
for i = 2, #pos do
local selected = pos._lines[i]:sub(pos[i].col[1], pos[i].col[2])
local _, num_of_com = vim.regex("^" .. comments.m):match_str(selected)
-- In the case of the last line, end of three-piece may be misunderstood as middle of three-piece.
if num_of_com and (i < #pos or not vim.regex("^" .. comments.e):match_str(selected)) then
pos[i].col[1] = pos[i].col[1] - num_of_com
end
end
end
-- Remove end of three-piece
local last_line = pos._lines[#pos._lines]:sub(1, range[4])
if vim.regex([[^\V\s\*]] .. comments.e .. [[\$]]):match_str(last_line) then
-- This line is unnecessary because it is only a comment string
table.remove(pos, #pos)
table.remove(pos._lines, #pos._lines)
else
local comStart, comEnd = vim.regex(comments.e .. [[\$]]):match_str(last_line)
if comStart then
local num_of_com = comEnd - comStart
pos[#pos].col[2] = pos[#pos].col[2] - num_of_com
else
error("The end of three-piece can't found")
end
end
end
function M.remove_string_symbol(symbols, range, pos)
local begin_row, last_row = range[1], range[3]
local begin_col, last_col = range[2], range[4]
local lines = api.nvim_buf_get_lines(0, begin_row - 1, last_row, true)
pos._lines = lines
for i, line in ipairs(lines) do
pos[i] = { row = begin_row + i - 1, col = { 1, #line } }
end
pos[1].col[1] = begin_col
pos[#pos].col[2] = last_col
for _, s in ipairs(symbols) do
if vim.startswith(lines[1]:sub(begin_col), s.begin) then
pos[1].col[1] = pos[1].col[1] + #s.begin - 1
pos[#pos].col[2] = pos[#pos].col[2] - #s.last
if #pos >= 2 then
local indent = lines[2]:match("^%s*")
if #indent > 0 then
for i = 2, #pos do
pos[i].col[1] = #indent + 1
end
end
end
if pos[1].col[1] == pos[1].col[2] then
table.remove(pos, 1)
table.remove(pos._lines, 1)
end
if pos[#pos].col[1] == pos[#pos].col[2] then
pos[#pos] = nil
pos._lines[#pos._lines] = nil
end
end
end
end
return M
================================================
FILE: lua/translate/util/context.lua
================================================
local fn = vim.fn
local util = require("translate.util.util")
local TreeSitter = require("translate.kit.Lua.TreeSitter")
local M = {
vim = {},
ts = {},
}
---Get vim's syntax groups for specified position.
---NOTE: This function accepts 1-origin cursor position.
---@param cursor number[] @{lnum, col}
---@return string[]?
function M.vim.get_range(group_name, cursor)
if not M.vim.is_group(group_name, cursor) then
return
end
-- Search start position
local pos_s = util.tbl_copy(cursor)
while true do
local _pos_s = M.vim.jump(pos_s, 0)
if _pos_s and M.vim.is_group(group_name, _pos_s) then
pos_s = _pos_s
else
break
end
end
-- Search end position
local pos_e = util.tbl_copy(cursor)
while true do
local _pos_e = M.vim.jump(pos_e, 1)
if _pos_e and M.vim.is_group(group_name, _pos_e) then
pos_e = _pos_e
else
break
end
end
local range = util.concat(pos_s, pos_e)
return range
end
---Moves to the end of the next word or the beginning of the previous word.
---@param pos number[] @{ row, col }
---@param dir integer @if 0, next, otherwise previous
---@return { row: number, col: number }?
function M.vim.jump(pos, dir)
local row, col = pos[1], pos[2]
local current_line = fn.getline(row)
if dir == 0 then -- Head of previous word
col = current_line:sub(1, col - 1):find("%S+%s*$")
if not col then
repeat
row = row - 1
if row < 1 then
return
end
current_line = fn.getline(row)
col = current_line:find("%S+%s*$")
until col
end
else -- Tail of next word
local max_row = fn.line("$")
_, col = current_line:find("%S+", col + 1)
if not col then
-- next not empty line
repeat
row = row + 1
if row > max_row then
return
end
current_line = fn.getline(row)
_, col = current_line:find("%S+")
until col
end
end
return { row, col }
end
function M.vim.is_group(group_name, pos)
for _, syntax_id in ipairs(fn.synstack(pos[1], pos[2])) do
if fn.synIDattr(fn.synIDtrans(syntax_id), "name") == group_name then
return true
end
end
return false
end
---Get tree-sitter's syntax groups for specified position.
---@param node_type string
---@param pos number[] (1,1)-index
---@return string[]? range
function M.ts.get_range(node_type, pos)
local row, col = unpack(pos)
-- (1, 1) -> (0, 0)
row = row - 1
col = col - 1
local node = TreeSitter.get_node_at(row, col)
if node == nil then
return
end
local parents = TreeSitter.parents(node)
for _, p_node in ipairs(parents) do
if p_node:type() == node_type then
local s_row, s_col, e_row, e_col = p_node:range()
-- From 0-index to 1-index
s_row = s_row + 1
s_col = s_col + 1
e_row = e_row + 1
e_col = e_col + 1
return { s_row, s_col, e_row, e_col }
end
end
end
return M
================================================
FILE: lua/translate/util/replace.lua
================================================
local config = require("translate.config")
local M = {
command_name = "",
}
---@param command_name string
function M.set_command_name(command_name)
M.command_name = command_name
end
---@param lines string[]
---@param is_before boolean
---@return string[]
local function replace(lines, is_before)
local replace_symbols = config.get("replace_symbols") or {}
local symbols = replace_symbols[M.command_name]
if symbols and next(symbols) ~= nil then
for i, line in ipairs(lines) do
for org, rep in pairs(symbols) do
if is_before then
line = line:gsub(org, rep)
else
line = line:gsub(rep, org)
end
end
lines[i] = line
end
end
return lines
end
---@param lines string[]
---@return string[]
function M.before(lines)
return replace(lines, true)
end
---@param lines string[] | string
---@return string[]
function M.after(lines)
if type(lines) == "string" then
lines = { lines }
end
return replace(lines, false)
end
return M
================================================
FILE: lua/translate/util/select.lua
================================================
local api = vim.api
local fn = vim.fn
local comment = require("translate.util.comment")
local utf8 = require("translate.util.utf8")
local util = require("translate.util.util")
local M = {}
local L = {}
---@class position
---@field row integer
---@field col integer[] { begin, last }
---@class positions
---@field _lines string[]
---@field _mode "comment" | "n" | "v" | "V" | ""
---@field [1] position[]
---@param args table
---@param mode string
---@return positions
function M.get(args, mode)
if args.comment then
return comment.get_range()
elseif mode == "n" then
return L.get_current_line()
else
return L.get_visual_selected(mode)
end
end
---@param mode string
---@return positions
function L.get_visual_selected(mode)
local start, last
-- When called from command line, "v" and "." return the same locations (cursor position, not selection range).
-- In this case, '< and '> must be used.
if util.same_pos(".", "v") then
start = util.getpos("'<")
last = util.getpos("'>")
else
start = util.getpos("v")
last = util.getpos(".")
end
local pos_s, pos_e = util.which_front(start, last)
local lines = api.nvim_buf_get_lines(0, pos_s[1] - 1, pos_e[1], true)
local pos = {}
pos._lines = lines
pos._mode = mode
if mode == "V" then
for i, line in ipairs(lines) do
table.insert(pos, { row = pos_s[1] + i - 1, col = { 1, #line } })
end
else
local last_line = fn.getline(pos_e[1])
local is_end = pos_e[2] == #last_line + 1 -- Selected to the end of each line.
if not is_end then
local offset = utf8.offset(last_line, 2, pos_e[2])
if offset then
pos_e[2] = offset - 1
else -- The last character of the line.
pos_e[2] = #last_line
end
end
if mode == "v" then
for i, line in ipairs(lines) do
local p = { row = pos_s[1] + i - 1, col = { 1, #line } }
table.insert(pos, p)
end
pos[1].col[1] = pos_s[2]
pos[#pos].col[2] = pos_e[2]
elseif mode == "" then
for i, _ in ipairs(lines) do
local row = pos_s[1] + i - 1
local col_end = is_end and #fn.getline(row) or pos_e[2]
table.insert(pos, { row = row, col = { pos_s[2], col_end } })
end
end
end
return pos
end
---@return positions
function L.get_current_line()
local row = fn.line(".")
local line = api.nvim_get_current_line()
local pos = { { row = row, col = { 1, #line } } }
pos._lines = { line }
pos._mode = "n"
return pos
end
return M
================================================
FILE: lua/translate/util/utf8.lua
================================================
local utf8 = {}
local bit = require("bit") -- luajit
local band = bit.band
local bor = bit.bor
local rshift = bit.rshift
local lshift = bit.lshift
---The pattern (a string, not a function) "[\0-\x7F\xC2-\xF4][\x80-\xBF]*",
---which matches exactly one UTF-8 byte sequence, assuming that the subject is a valid UTF-8 string.
utf8.charpattern = "[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*"
---@param idx integer
---@param func_name string
---@param range_name string
---@return string @error message
local function create_errmsg(idx, func_name, range_name)
return string.format("bad argument #%s to '%s' (%s out of range)", idx, func_name, range_name)
end
---Converts indexes of a string to positive numbers.
---@param str string
---@param idx integer
---@return boolean, integer
local function validate_range(str, idx)
idx = idx > 0 and idx or #str + idx + 1
if idx < 0 or idx > #str then
return false
end
return true, idx
end
---Receives zero or more integers, converts each one to its corresponding UTF-8 byte sequence
---and returns a string with the concatenation of all these sequences.
---@vararg integer
---@return string
function utf8.char(...)
local buffer = {}
for i, v in ipairs({ ... }) do
if v < 0 or v > 0x10FFFF then
error(create_errmsg(i, "char", "value"), 2)
elseif v < 0x80 then
-- single-byte
buffer[i] = string.char(v)
elseif v < 0x800 then
-- two-byte
local b1 = bor(0xC0, band(rshift(v, 6), 0x1F)) -- 110x-xxxx
local b2 = bor(0x80, band(v, 0x3F)) -- 10xx-xxxx
buffer[i] = string.char(b1, b2)
elseif v < 0x10000 then
-- three-byte
local b1 = bor(0xE0, band(rshift(v, 12), 0x0F)) -- 1110-xxxx
local b2 = bor(0x80, band(rshift(v, 6), 0x3F)) -- 10xx-xxxx
local b3 = bor(0x80, band(v, 0x3F)) -- 10xx-xxxx
buffer[i] = string.char(b1, b2, b3)
else
-- four-byte
local b1 = bor(0xF0, band(rshift(v, 18), 0x07)) -- 1111-0xxx
local b2 = bor(0x80, band(rshift(v, 12), 0x3F)) -- 10xx-xxxx
local b3 = bor(0x80, band(rshift(v, 6), 0x3F)) -- 10xx-xxxx
local b4 = bor(0x80, band(v, 0x3F)) -- 10xx-xxxx
buffer[i] = string.char(b1, b2, b3, b4)
end
end
return table.concat(buffer, "")
end
---Returns the next one character range.
---@param s string
---@param start_pos integer
---@return integer start_pos, integer end_pos
local function next_char(s, start_pos)
local b1 = s:byte(start_pos)
if not b1 then
return -- for offset's #s+1
end
local end_pos
if band(b1, 0x80) == 0x00 then -- single-byte (0xxx-xxxx)
return start_pos, start_pos
elseif 0xC2 <= b1 and b1 <= 0xDF then -- two-byte (range 0xC2 to 0xDF)
end_pos = start_pos + 1
elseif band(b1, 0xF0) == 0xE0 then -- three-byte (1110-xxxx)
end_pos = start_pos + 2
elseif 0xF0 <= b1 and b1 <= 0xF4 then -- four-byte (range 0xF0 to 0xF4)
end_pos = start_pos + 3
else -- invalid 1st byte
return
end
-- validate (end_pos)
if end_pos > #s then
return
end
-- validate (continuation)
for _, bn in ipairs({ s:byte(start_pos + 1, end_pos) }) do
if band(bn, 0xC0) ~= 0x80 then -- 10xx-xxxx?
return
end
end
return start_pos, end_pos
end
---Returns values so that the construction
---
---for p, c in utf8.codes(s) do body end
---
---will iterate over all UTF-8 characters in string s, with p being the position (in bytes) and c the code point of each character.
---It raises an error if it meets any invalid byte sequence.
---@param s string
---@return function iterator
function utf8.codes(s)
vim.validate({
s = { s, "string" },
})
local i = 1
return function()
if i > #s then
return
end
local start_pos, end_pos = next_char(s, i)
if start_pos == nil then
error("invalid UTF-8 code", 2)
end
i = end_pos + 1
return start_pos, s:sub(start_pos, end_pos)
end
end
---Returns the code points (as integers) from all characters in s that start between byte position i and j (both included).
---The default for i is 1 and for j is i.
---It raises an error if it meets any invalid byte sequence.
---@param s string
---@param i? integer start position. default=1
---@param j? integer end position. default=i
---@return integer @code point
function utf8.codepoint(s, i, j)
vim.validate({
s = { s, "string" },
i = { i, "number", true },
j = { j, "number", true },
})
local ok
ok, i = validate_range(s, i or 1)
if not ok then
error(create_errmsg(2, "codepoint", "initial potision"), 2)
end
ok, j = validate_range(s, j or i)
if not ok then
error(create_errmsg(3, "codepoint", "final potision"), 2)
end
local ret = {}
repeat
local char_start, char_end = next_char(s, i)
if char_start == nil then
error("invalid UTF-8 code", 2)
end
i = char_end + 1
local len = char_end - char_start + 1
if len == 1 then
-- single-byte
table.insert(ret, s:byte(char_start))
else
-- multi-byte
local b1 = s:byte(char_start)
b1 = band(lshift(b1, len + 1), 0xFF) -- e.g. 110x-xxxx -> xxxx-x000
b1 = lshift(b1, len * 5 - 7) -- >> len+1 and << (len-1)*6
local cp = 0
for k = char_start + 1, char_end do
local bn = s:byte(k)
cp = bor(lshift(cp, 6), band(bn, 0x3F))
end
cp = bor(b1, cp)
table.insert(ret, cp)
end
until char_end >= j
return unpack(ret)
end
---Returns the number of UTF-8 characters in string s that start between positions i and j (both inclusive).
---The default for i is 1 and for j is -1.
---If it finds any invalid byte sequence, returns fail plus the position of the first invalid byte.
---@param s string
---@param i? integer start position. default=1
---@param j? integer end position. default=-1
---@return integer
function utf8.len(s, i, j)
vim.validate({
s = { s, "string" },
i = { i, "number", true },
j = { j, "number", true },
})
local ok
ok, i = validate_range(s, i or 1)
if not ok then
error(create_errmsg(2, "len", "initial potision"), 2)
end
ok, j = validate_range(s, j or -1)
if not ok then
error(create_errmsg(3, "len", "final potision"), 2)
end
local len = 0
repeat
local char_start, char_end = next_char(s, i)
if char_start == nil then
return nil, i
end
i = char_end + 1
len = len + 1
until char_end >= j
return len
end
---Returns the position (in bytes) where the encoding of the n-th character of s (counting from position i) starts.
---A negative n gets characters before position i.
---The default for i is 1 when n is non-negative and #s+1 otherwise, so that utf8.offset(s, -n) gets the offset of the n-th character from the end of the string.
---If the specified character is neither in the subject nor right after its end, the function returns fail.
---
---As a special case, when n is 0 the function returns the start of the encoding of the character that contains the i-th byte of s.
---@param s string
---@param n integer
---@param i? integer start position. if n >= 0, default=1, otherwise default=#s+1
---@return integer
function utf8.offset(s, n, i)
vim.validate({
s = { s, "string" },
n = { n, "number" },
i = { i, "number", true },
})
i = i or n >= 0 and 1 or #s + 1
if n >= 0 or i ~= #s + 1 then
local ok
ok, i = validate_range(s, i)
if not ok then
error(create_errmsg(3, "offset", "position"), 2)
end
end
if n == 0 then
for j = i, 1, -1 do
local char_start = next_char(s, j)
if char_start then
return char_start
end
end
elseif n > 0 then
if not next_char(s, i) then
error("initial position is a continuation byte", 2)
end
for j = i, #s do
local char_start = next_char(s, j)
if char_start then
n = n - 1
if n == 0 then
return char_start
end
end
end
else
if i ~= #s + 1 and not next_char(s, i) then
error("initial position is a continuation byte", 2)
end
for j = i, 1, -1 do
local char_start = next_char(s, j)
if char_start then
n = n + 1
if n == 0 then
return char_start
end
end
end
end
end
return utf8
================================================
FILE: lua/translate/util/util.lua
================================================
local fn = vim.fn
local api = vim.api
local luv = vim.loop
local utf8 = require("translate.util.utf8")
local M = {}
---Copy the table
---NOTE: Metatable is not considered
---@param tbl table
---@return table
function M.tbl_copy(tbl)
if type(tbl) ~= "table" then
return tbl
end
local new = {}
for k, v in pairs(tbl) do
if type(v) == "table" then
new[k] = M.tbl_copy(v)
else
new[k] = v
end
end
return new
end
---Concatenate two list-like tables.
---@param t1 table
---@param t2 table
---@return table
function M.concat(t1, t2)
local new = {}
for _, v in ipairs(t1) do
table.insert(new, v)
end
for _, v in ipairs(t2) do
table.insert(new, v)
end
return new
end
---Add an element to dict[key]
---dict is a table with an array for values.
---@param dict {any: any[]}
---@param key any
---@param elem any
function M.append_dict_list(dict, key, elem)
if not dict[key] then
dict[key] = {}
end
table.insert(dict[key], elem)
end
function M.text_cut(text, widths)
local widths_is_table = type(widths) == "table"
local function get_width(row)
return widths_is_table and widths[row] or widths
end
local lines = {}
local row, col = 1, 0
local width = get_width(1)
local function skip_blank_line()
while width == 0 do
M.append_dict_list(lines, row, "")
row = row + 1
width = get_width(row)
end
end
skip_blank_line()
for p, char in utf8.codes(text) do
local l = api.nvim_strwidth(char)
if col + l > width then
if widths_is_table and widths[row + 1] == nil then
local residue = text:sub(p)
M.append_dict_list(lines, row, residue)
break
end
row = row + 1
width = get_width(row)
col = 0
skip_blank_line()
end
M.append_dict_list(lines, row, char)
col = col + l
end
for i, line in ipairs(lines) do
lines[i] = table.concat(line, "")
end
if #lines == 0 then
lines = { "" }
end
return lines
end
function M.max_width_in_string_list(list)
local max = api.nvim_strwidth(list[1])
for i = 2, #list do
local v = api.nvim_strwidth(list[i])
if v > max then
max = v
end
end
return max
end
function M.has_key(tbl, ...)
local keys = { ... }
for _, k in ipairs(keys) do
if tbl[k] == nil then
return false
end
end
return true
end
---@param last integer
---@return integer[][]
function M.seq(last)
local l = {}
for i = 1, last do
l[i] = { i }
end
return l
end
---Compare position
---@param pos1 number[] #{row, col}
---@param pos2 number[] #{row, col}
---@return number[], number[] #front, end
function M.which_front(pos1, pos2)
-- Row Comparison
if pos1[1] < pos2[1] then
return pos1, pos2
elseif pos1[1] > pos2[1] then
return pos2, pos1
else
-- Col Comparison
if pos1[2] < pos2[2] then
return pos1, pos2
else
return pos2, pos1
end
end
end
---Wrapper function for getpos() that returns only 'row' and 'col'.
---@param expr string
---@return integer[] {row, col}
function M.getpos(expr)
local p = vim.fn.getpos(expr)
local result = { p[2], p[3] }
return result
end
---Returns whether cursor positions are equal.
---@param expr1 string
---@param expr2 string
---@return boolean
function M.same_pos(expr1, expr2)
local p1 = M.getpos(expr1)
local p2 = M.getpos(expr2)
return p1[1] == p2[1] and p1[2] == p2[2]
end
---@param text string #json string
---@return string
function M.write_temp_data(text)
local dir = fn.expand(fn.stdpath("cache") .. "/translate")
vim.fn.mkdir(dir, "p")
local path = fn.expand(dir .. "/data.json")
-- tonumber("666", 8) -> 438
local fd = assert(luv.fs_open(path, "w", 438))
assert(luv.fs_write(fd, text))
assert(luv.fs_close(fd))
return path
end
return M
================================================
FILE: plugin/translate.lua
================================================
if vim.g.loaded_translate_nvim then
return
end
require("translate").setup({})
================================================
FILE: stylua.toml
================================================
column_width = 120
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferDouble"
================================================
FILE: utils/minimal.vim
================================================
let s:plug_dir = expand('/tmp/plugged/vim-plug')
if !filereadable(s:plug_dir .. '/autoload/plug.vim')
execute printf('!curl -fLo %s/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim', s:plug_dir)
end
execute 'set runtimepath+=' . s:plug_dir
call plug#begin(s:plug_dir)
Plug 'uga-rosa/translate.nvim'
call plug#end()
PlugInstall | quit
lua <<EOF
require('translate').setup({
-- Minimal configurations required to reproduce the problem.
})
EOF
gitextract_mwzms6v0/
├── .github/
│ └── ISSUE_TEMPLATE/
│ └── bug_report.yml
├── .gitignore
├── LICENSE
├── README.md
├── doc/
│ └── translate-nvim.txt
├── lua/
│ └── translate/
│ ├── command.lua
│ ├── config.lua
│ ├── init.lua
│ ├── kit/
│ │ ├── Async/
│ │ │ ├── AsyncTask.lua
│ │ │ ├── AsyncTask.spec.lua
│ │ │ ├── init.lua
│ │ │ └── init.spec.lua
│ │ ├── Cache.lua
│ │ ├── Cache.spec.lua
│ │ ├── Config.lua
│ │ ├── Config.spec.lua
│ │ ├── LSP/
│ │ │ ├── Position.lua
│ │ │ ├── Position.spec.lua
│ │ │ ├── Range.lua
│ │ │ └── Range.spec.lua
│ │ ├── Lua/
│ │ │ ├── TreeSitter.lua
│ │ │ ├── TreeSitter.spec.lua
│ │ │ ├── init.lua
│ │ │ └── init.spec.lua
│ │ ├── Vim/
│ │ │ ├── Buffer.lua
│ │ │ ├── Buffer.spec.lua
│ │ │ ├── Highlight.lua
│ │ │ ├── Highlight.spec.lua
│ │ │ ├── Keymap.lua
│ │ │ ├── Keymap.spec.lua
│ │ │ ├── Syntax.lua
│ │ │ └── Syntax.spec.lua
│ │ ├── init.lua
│ │ └── init.spec.lua
│ ├── preset/
│ │ ├── command/
│ │ │ ├── deepl.lua
│ │ │ ├── deepl_free.lua
│ │ │ ├── deepl_pro.lua
│ │ │ ├── google.lua
│ │ │ └── translate_shell.lua
│ │ ├── output/
│ │ │ ├── floating.lua
│ │ │ ├── insert.lua
│ │ │ ├── register.lua
│ │ │ ├── replace.lua
│ │ │ └── split.lua
│ │ ├── parse_after/
│ │ │ ├── deepl.lua
│ │ │ ├── deepl_free.lua
│ │ │ ├── deepl_pro.lua
│ │ │ ├── google.lua
│ │ │ ├── head.lua
│ │ │ ├── no_handle.lua
│ │ │ ├── oneline.lua
│ │ │ ├── rate.lua
│ │ │ ├── translate_shell.lua
│ │ │ └── window.lua
│ │ └── parse_before/
│ │ ├── concat.lua
│ │ ├── natural.lua
│ │ ├── no_handle.lua
│ │ └── trim.lua
│ └── util/
│ ├── comment.lua
│ ├── context.lua
│ ├── replace.lua
│ ├── select.lua
│ ├── utf8.lua
│ └── util.lua
├── plugin/
│ └── translate.lua
├── stylua.toml
└── utils/
└── minimal.vim
Condensed preview — 67 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (145K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 1767,
"preview": "name: Bug Report\ndescription: Report a problem in translate.nvim\nlabels: [bug]\nbody:\n - type: checkboxes\n id: not-qu"
},
{
"path": ".gitignore",
"chars": 10,
"preview": "/doc/tags\n"
},
{
"path": "LICENSE",
"chars": 1065,
"preview": "MIT License\n\nCopyright (c) 2021 uga-rosa\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "README.md",
"chars": 3767,
"preview": "# translate.nvim\n\n\n\nlocal M = {}\n\nlocal modes = {\n \"parse_before\",\n \"comman"
},
{
"path": "lua/translate/config.lua",
"chars": 4325,
"preview": "local M = {}\n\nM._preset = {\n parse_before = {\n natural = require(\"translate.preset.parse_before.natural\"),\n trim "
},
{
"path": "lua/translate/init.lua",
"chars": 3415,
"preview": "local luv = vim.loop\n\nlocal config = require(\"translate.config\")\nlocal replace = require(\"translate.util.replace\")\nlocal"
},
{
"path": "lua/translate/kit/Async/AsyncTask.lua",
"chars": 4888,
"preview": "local Lua = require(\"___plugin_name___.kit.Lua\")\n\n---@class ___plugin_name___.kit.Async.AsyncTask<T>: { value: T }\n---@f"
},
{
"path": "lua/translate/kit/Async/AsyncTask.spec.lua",
"chars": 3492,
"preview": "local AsyncTask = require(\"___plugin_name___.kit.Async.AsyncTask\")\n\ndescribe(\"kit.Async\", function()\n local once = func"
},
{
"path": "lua/translate/kit/Async/init.lua",
"chars": 1646,
"preview": "local AsyncTask = require(\"___plugin_name___.kit.Async.AsyncTask\")\n\n_G.__kit__ = _G.__kit__ or {}\n_G.__kit__.Async = _G."
},
{
"path": "lua/translate/kit/Async/init.spec.lua",
"chars": 632,
"preview": "local Async = require(\"___plugin_name___.kit.Async\")\nlocal AsyncTask = require(\"___plugin_name___.kit.Async.AsyncTask\")\n"
},
{
"path": "lua/translate/kit/Cache.lua",
"chars": 1378,
"preview": "---Create cache key.\n---@private\n---@param key string[]|string\n---@return string\nlocal function _key(key)\n if type(key)"
},
{
"path": "lua/translate/kit/Cache.spec.lua",
"chars": 1130,
"preview": "local Cache = require(\"___plugin_name___.kit.Cache\")\n\ndescribe(\"kit.Cache\", function()\n it(\"should works {get,set,has,d"
},
{
"path": "lua/translate/kit/Config.lua",
"chars": 3612,
"preview": "local kit = require(\"___plugin_name___.kit\")\nlocal Cache = require(\"___plugin_name___.kit.Cache\")\n\n---@class ___plugin_n"
},
{
"path": "lua/translate/kit/Config.spec.lua",
"chars": 1574,
"preview": "local Config = require(\"___plugin_name___.kit.Config\")\n\ndescribe(\"kit.Config\", function()\n before_each(function()\n v"
},
{
"path": "lua/translate/kit/LSP/Position.lua",
"chars": 3901,
"preview": "local Buffer = require(\"___plugin_name___.kit.Vim.Buffer\")\n\n---@class ___plugin_name___.kit.LSP.Position\n---@field publi"
},
{
"path": "lua/translate/kit/LSP/Position.spec.lua",
"chars": 1256,
"preview": "local Position = require(\"___plugin_name___.kit.LSP.Position\")\n\ndescribe(\"kit.LSP.Position\", function()\n local text = \""
},
{
"path": "lua/translate/kit/LSP/Range.lua",
"chars": 1123,
"preview": "local Position = require(\"___plugin_name___.kit.LSP.Position\")\n\n---@class ___plugin_name___.kit.LSP.Range\n---@field publ"
},
{
"path": "lua/translate/kit/LSP/Range.spec.lua",
"chars": 435,
"preview": "local Range = require(\"___plugin_name___.kit.LSP.Range\")\n\ndescribe(\"kit.LSP.Range\", function()\n it(\"should return the r"
},
{
"path": "lua/translate/kit/Lua/TreeSitter.lua",
"chars": 7606,
"preview": "local TreeSitter = {}\n\n---@alias ___plugin_name___.kit.Lua.TreeSitter.VisitStatus 'stop'|'skip'\nTreeSitter.VisitStatus ="
},
{
"path": "lua/translate/kit/Lua/TreeSitter.spec.lua",
"chars": 1925,
"preview": "---@diagnostic disable: need-check-nil, param-type-mismatch\nlocal helper = require(\"kit.helper\")\nlocal TreeSitter = requ"
},
{
"path": "lua/translate/kit/Lua/init.lua",
"chars": 229,
"preview": "local Lua = {}\n\n---Create gabage collection detector.\n---@param callback fun(...: any): any\n---@return userdata\nfunction"
},
{
"path": "lua/translate/kit/Lua/init.spec.lua",
"chars": 374,
"preview": "local Lua = require(\"___plugin_name___.kit.Lua\")\n\ndescribe(\"kit.Lua\", function()\n it(\"should detect gc timing.\", functi"
},
{
"path": "lua/translate/kit/Vim/Buffer.lua",
"chars": 1560,
"preview": "local kit = require(\"___plugin_name___.kit\")\nlocal Highlight = require(\"___plugin_name___.kit.Vim.Highlight\")\n\nlocal Buf"
},
{
"path": "lua/translate/kit/Vim/Buffer.spec.lua",
"chars": 1456,
"preview": "local Buffer = require(\"___plugin_name___.kit.Vim.Buffer\")\n\ndescribe(\"kit.Vim.Buffer\", function()\n before_each(function"
},
{
"path": "lua/translate/kit/Vim/Highlight.lua",
"chars": 1236,
"preview": "local kit = require(\"___plugin_name___.kit\")\nlocal Async = require(\"___plugin_name___.kit.Async\")\nlocal AsyncTask = requ"
},
{
"path": "lua/translate/kit/Vim/Highlight.spec.lua",
"chars": 285,
"preview": "local Highlight = require(\"___plugin_name___.kit.Vim.Highlight\")\n\ndescribe(\"kit.Vim.Highlight\", function()\n it(\"should "
},
{
"path": "lua/translate/kit/Vim/Keymap.lua",
"chars": 1130,
"preview": "local AsyncTask = require(\"___plugin_name___.kit.Async.AsyncTask\")\n\nlocal Keymap = {}\n\nKeymap._callbacks = {}\n\n---Replac"
},
{
"path": "lua/translate/kit/Vim/Keymap.spec.lua",
"chars": 749,
"preview": "local Async = require(\"___plugin_name___.kit.Async\")\nlocal Keymap = require(\"___plugin_name___.kit.Vim.Keymap\")\n\nlocal a"
},
{
"path": "lua/translate/kit/Vim/Syntax.lua",
"chars": 1199,
"preview": "local kit = require(\"___plugin_name___.kit\")\n\nlocal Syntax = {}\n\n---Get all syntax groups for specified position.\n---NOT"
},
{
"path": "lua/translate/kit/Vim/Syntax.spec.lua",
"chars": 1028,
"preview": "local helper = require(\"kit.helper\")\nlocal Syntax = require(\"___plugin_name___.kit.Vim.Syntax\")\n\ndescribe(\"kit.Vim.Synta"
},
{
"path": "lua/translate/kit/init.lua",
"chars": 3634,
"preview": "--[[\nMIT License\n\nCopyright (c) 2022 hrsh7th\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "lua/translate/kit/init.spec.lua",
"chars": 1838,
"preview": "local kit = require(\"___plugin_name___.kit\")\n\ndescribe(\"kit\", function()\n describe(\".merge\", function()\n it(\"should "
},
{
"path": "lua/translate/preset/command/deepl.lua",
"chars": 1305,
"preview": "local M = {}\n\nlocal json_encode = vim.json and vim.json.encode or vim.fn.json_encode\n\n---@param url string\n---@param lin"
},
{
"path": "lua/translate/preset/command/deepl_free.lua",
"chars": 555,
"preview": "local deepl = require(\"translate.preset.command.deepl\")\n\nlocal M = {}\n\n---@param lines string[]\n---@param command_args t"
},
{
"path": "lua/translate/preset/command/deepl_pro.lua",
"chars": 549,
"preview": "local deepl = require(\"translate.preset.command.deepl\")\n\nlocal M = {}\n\n---@param lines string[]\n---@param command_args t"
},
{
"path": "lua/translate/preset/command/google.lua",
"chars": 2231,
"preview": "local util = require(\"translate.util.util\")\n\nlocal M = {}\n\nM.url =\n \"https://script.google.com/macros/s/AKfycbxLRZgWI3U"
},
{
"path": "lua/translate/preset/command/translate_shell.lua",
"chars": 3406,
"preview": "local M = {}\n\n---@param lines string[]\n---@param command_args table\n---@return string\n---@return string[]\nfunction M.cmd"
},
{
"path": "lua/translate/preset/output/floating.lua",
"chars": 1141,
"preview": "local api = vim.api\n\nlocal util = require(\"translate.util.util\")\n\nlocal M = {\n window = {},\n}\n\nfunction M.cmd(lines, _)"
},
{
"path": "lua/translate/preset/output/insert.lua",
"chars": 609,
"preview": "local api = vim.api\n\nlocal M = {}\n\nfunction M.cmd(lines, pos)\n if type(lines) == \"string\" then\n lines = { lines }\n "
},
{
"path": "lua/translate/preset/output/register.lua",
"chars": 459,
"preview": "local fn = vim.fn\n\nlocal M = {}\n\n---Set the register\n---@param lines string[]\nfunction M.cmd(lines, _)\n local newline\n "
},
{
"path": "lua/translate/preset/output/replace.lua",
"chars": 432,
"preview": "local api = vim.api\n\nlocal M = {}\n\nfunction M.cmd(lines, pos)\n if type(lines) == \"string\" then\n lines = { lines }\n "
},
{
"path": "lua/translate/preset/output/split.lua",
"chars": 2067,
"preview": "local fn = vim.fn\nlocal api = vim.api\n\nlocal M = {}\n\nfunction M.cmd(lines, pos)\n if type(lines) == \"string\" then\n li"
},
{
"path": "lua/translate/preset/parse_after/deepl.lua",
"chars": 348,
"preview": "local M = {}\n\nlocal json_decode = vim.json and vim.json.decode or vim.fn.json_decode\n\n---@param response string #json st"
},
{
"path": "lua/translate/preset/parse_after/deepl_free.lua",
"chars": 53,
"preview": "return require(\"translate.preset.parse_after.deepl\")\n"
},
{
"path": "lua/translate/preset/parse_after/deepl_pro.lua",
"chars": 53,
"preview": "return require(\"translate.preset.parse_after.deepl\")\n"
},
{
"path": "lua/translate/preset/parse_after/google.lua",
"chars": 83,
"preview": "local M = {}\n\nfunction M.cmd(text, _)\n return vim.json.decode(text)\nend\n\nreturn M\n"
},
{
"path": "lua/translate/preset/parse_after/head.lua",
"chars": 1461,
"preview": "local api = vim.api\n\nlocal util = require(\"translate.util.util\")\n\nlocal M = {}\n\n---Cut the results of translation to fit"
},
{
"path": "lua/translate/preset/parse_after/no_handle.lua",
"chars": 68,
"preview": "local M = {}\n\nfunction M.cmd(lines, _)\n return lines\nend\n\nreturn M\n"
},
{
"path": "lua/translate/preset/parse_after/oneline.lua",
"chars": 151,
"preview": "local M = {}\n\n---@param lines string[]\n---@return string[]\nfunction M.cmd(lines, _)\n lines = { table.concat(lines, \"\") "
},
{
"path": "lua/translate/preset/parse_after/rate.lua",
"chars": 969,
"preview": "local api = vim.api\n\nlocal util = require(\"translate.util.util\")\n\nlocal M = {}\n\n---Cut the results of translation to fit"
},
{
"path": "lua/translate/preset/parse_after/translate_shell.lua",
"chars": 347,
"preview": "local M = {}\n\n---@param text string\n---@return string[]\nfunction M.cmd(text, _)\n local crlf\n -- Remove the extra CRLF "
},
{
"path": "lua/translate/preset/parse_after/window.lua",
"chars": 551,
"preview": "local api = vim.api\n\nlocal util = require(\"translate.util.util\")\n\nlocal M = {}\n\n---Cut the text to fit the window width."
},
{
"path": "lua/translate/preset/parse_before/concat.lua",
"chars": 256,
"preview": "local M = {}\n\n---@param lines string[]\n---@return string[]\nfunction M.cmd(lines)\n local options = require(\"translate.co"
},
{
"path": "lua/translate/preset/parse_before/natural.lua",
"chars": 2666,
"preview": "local util = require(\"translate.util.util\")\n\nlocal M = {}\n\nlocal function inc(tbl, index)\n if tbl[index] then\n retur"
},
{
"path": "lua/translate/preset/parse_before/no_handle.lua",
"chars": 110,
"preview": "local M = {}\n\n---@param lines string[]\n---@return string[]\nfunction M.cmd(lines)\n return lines\nend\n\nreturn M\n"
},
{
"path": "lua/translate/preset/parse_before/trim.lua",
"chars": 379,
"preview": "local M = {}\n\n---@param lines string[]\n---@param pos positions\n---@return string[]\nfunction M.cmd(lines, pos)\n for i, l"
},
{
"path": "lua/translate/util/comment.lua",
"chars": 9098,
"preview": "local fn = vim.fn\nlocal api = vim.api\n\nlocal context = require(\"translate.util.context\")\nlocal util = require(\"translate"
},
{
"path": "lua/translate/util/context.lua",
"chars": 2954,
"preview": "local fn = vim.fn\n\nlocal util = require(\"translate.util.util\")\nlocal TreeSitter = require(\"translate.kit.Lua.TreeSitter\""
},
{
"path": "lua/translate/util/replace.lua",
"chars": 1015,
"preview": "local config = require(\"translate.config\")\n\nlocal M = {\n command_name = \"\",\n}\n\n---@param command_name string\nfunction M"
},
{
"path": "lua/translate/util/select.lua",
"chars": 2522,
"preview": "local api = vim.api\nlocal fn = vim.fn\n\nlocal comment = require(\"translate.util.comment\")\nlocal utf8 = require(\"translate"
},
{
"path": "lua/translate/util/utf8.lua",
"chars": 8243,
"preview": "local utf8 = {}\n\nlocal bit = require(\"bit\") -- luajit\n\nlocal band = bit.band\nlocal bor = bit.bor\nlocal rshift = bit.rshi"
},
{
"path": "lua/translate/util/util.lua",
"chars": 3818,
"preview": "local fn = vim.fn\nlocal api = vim.api\nlocal luv = vim.loop\nlocal utf8 = require(\"translate.util.utf8\")\n\nlocal M = {}\n\n--"
},
{
"path": "plugin/translate.lua",
"chars": 81,
"preview": "if vim.g.loaded_translate_nvim then\n return\nend\n\nrequire(\"translate\").setup({})\n"
},
{
"path": "stylua.toml",
"chars": 114,
"preview": "column_width = 120\nline_endings = \"Unix\"\nindent_type = \"Spaces\"\nindent_width = 2\nquote_style = \"AutoPreferDouble\"\n"
},
{
"path": "utils/minimal.vim",
"chars": 500,
"preview": "let s:plug_dir = expand('/tmp/plugged/vim-plug')\nif !filereadable(s:plug_dir .. '/autoload/plug.vim')\n execute printf('"
}
]
About this extraction
This page contains the full source code of the uga-rosa/translate.nvim GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 67 files (129.4 KB), approximately 38.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.