[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug Report\ndescription: Report a problem in translate.nvim\nlabels: [bug]\nbody:\n  - type: checkboxes\n    id: not-question\n    attributes:\n      label: Not a question\n      options:\n        - label: This is not a question. If you have a question, use [discussions](https://github.com/uga-rosa/translate.nvim/discussions).\n          required: true\n\n  - type: textarea\n    attributes:\n      label: \"Description\"\n      description: \"Describe in detail what happens\"\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: \"Environments\"\n      description: \"information such as OS and neovim version\"\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: \"Minimal reproducible full config\"\n      description: |\n        You must provide a working config based on [this](https://github.com/uga-rosa/translate.nvim/blob/main/utils/minimal.vim).\n        The config file should be complete, not partial, and can be used alone to reproduce bugs. \n        **Don't use packer.nvim for this.**\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: \"Steps to reproduce\"\n      description: \"Full reproduction steps. Include a sample file if your issue relates to a specific filetype.\"\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: \"Expected behavior\"\n      description: \"A description of the behavior you expected.\"\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: \"Actual behavior\"\n      description: \"A description of the actual behavior.\"\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: \"Additional context\"\n      description: \"Any other relevant information\"\n"
  },
  {
    "path": ".gitignore",
    "content": "/doc/tags\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 uga-rosa\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# translate.nvim\n\n![demo](https://user-images.githubusercontent.com/82267684/158013979-52c8ca49-84e1-4ca0-bf30-b8165cca9135.gif)\n\n# Features\n\n- You can use any command you like for translation.\n    - Google translate API (default)\n    - [translate-shell](https://github.com/soimort/translate-shell)\n    - [DeepL API Pro/Free](https://www.deepl.com/en/docs-api/)\n- The results of the translation can be output in a variety of ways.\n    - Floating window\n    - Split window\n    - Insert to the current buffer\n    - Replace the original text\n    - Set the register\n- The translation command and output method can be specified as command arguments.\n- In addition to the above presets, you can add your own functions.\n\n\n# Requirements\n\n- neovim 0.8+\n\nThe default Google Translate requires nothing but [curl](https://curl.se/).\n\nIf you use [translate-shell](https://github.com/soimort/translate-shell), you need to install `trans` command.\n\nIf you use [DeepL API Pro/Free](https://www.deepl.com/en/docs-api/), you need the authorization key for DeepL API Pro/Free.\nIn addition, you need curl to send the request.\n\n\n# Quick start\n\n## Install\n\nWith 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))\n\n## Setup\n\nThis plugin has default settings, so there is no need to call setup if you want to use it as is.\n\nThis is my setting.\n\n```lua\nvim.g.deepl_api_auth_key = \"MY_AUTH_KEY\"\n\nrequire(\"translate\").setup({\n    default = {\n        command = \"deepl_pro\",\n    },\n    preset = {\n        output = {\n            split = {\n                append = true,\n            },\n        },\n    },\n})\n```\n\nSee help for available options.\n\n## Command\n\nThis plugin provides `:Translate`.\n\nI put the quote from the help in the fold.\n\n<details><summary>:Translate</summary><div>\n\n\n    :[range]Translate {target-lang} [{-options}...]\n\n        {target-lang}: Required. The language into which the text should be\n        translated. The format varies depending on the external command used.\n\n        |:Translate| can take |:range|. |v|, |V| and |CTRL-V| are supported. If it was\n        not given, |:Translate| treats current cursor line.\n\n        available options:\n            - '-source='\n                The language of the text to be translated.\n            - '-parse_before='\n                The functions to format texts of selection. You can\n                use a comma-separated string. If omitted,\n                |translate-nvim-option-default-parse-before|.\n            - '-command='\n                The extermal command to use translation. If omitted,\n                |translate-nvim-option-default-command| is used.\n            - '-parse_after='\n                The functions to format the result of extermal\n                command. You can use a comma-separated string.\n                If omitted, |translate-nvim-option-default-parse-after|.\n            - '-output='\n                The function to pass the translation result. If\n                omitted, |translate-nvim-option-default-output|.\n            - '-comment'\n                Special option, used as a flag. If this flag is set\n                and the cursor is over a comment, whole comment is\n                treated as a selection.\n\n\n        Use <Cmd> for mapping.\n        If you cannot use it, you must change the format with nmap and xmap.\n\n\n        nnoremap me <Cmd>Translate EN<CR>\n        xnoremap me <Cmd>Translate EN<CR>\n        \n        Another way.\n\n        nnoremap me :<C-u>Translate EN<CR>\n        xnoremap me :Translate EN<CR>\n\n\n</div></details>\n\n\n# Translate the word under the cursor\n\nYou can use this mapping.\n\n```vim\nnnoremap <space>tw viw:Translate ZH<CR>\n```\n"
  },
  {
    "path": "doc/translate-nvim.txt",
    "content": "*translate-nvim.txt*\t\t\tUse external translate command in nvim\n\n==============================================================================\nContents\t\t\t\t\t\t*translate-nvim-contents*\n\nIntroduction\t\t\t\t|translate-nvim-introduction|\nCommand\t\t\t\t\t|translate-nvim-command|\nSetup\t\t\t\t\t|translate-nvim-setup|\nOption\t\t\t\t\t|translate-nvim-option|\n- default\t\t\t\t|translate-nvim-option-default|\n- preset\t\t\t\t|translate-nvim-option-preset|\n- parse_before\t\t\t\t|translate-nvim-option-parse-before|\n- command\t\t\t\t|translate-nvim-option-command|\n- parse_after\t\t\t\t|translate-nvim-option-parse-after|\n- output\t\t\t\t|translate-nvim-option-output|\n- replace_symbols\t\t\t|translate-nvim-option-replace-symbols|\n- silent\t\t\t\t|translate-nvim-option-silent|\npreset\t\t\t\t\t|translate-nvim-preset|\n- parse_before\t\t\t\t|translate-nvim-preset-parse-before|\n  - natural\t\t\t\t|translate-nvim-preset-parse-before-natural|\n  - trim\t\t\t\t|translate-nvim-preset-parse-before-trim|\n  - concat\t\t\t\t|translate-nvim-preset-parse-before-concat|\n  - no_handle\t\t\t\t|translate-nvim-preset-parse-before-no-handle|\n- command\t\t\t\t|translate-nvim-preset-command|\n  - google\t\t\t\t|translate-nvim-preset-command-google|\n  - translate_shell\t\t\t|translate-nvim-preset-command-translate-shell|\n  - deepl_free\t\t\t\t|translate-nvim-preset-command-deepl-free|\n  - deepl_pro\t\t\t\t|translate-nvim-preset-command-deepl-pro|\n- parse_after\t\t\t\t|translate-nvim-preset-parse-after|\n  - oneline\t\t\t\t|translate-nvim-preset-parse-after-oneline|\n  - head\t\t\t\t|translate-nvim-preset-parse-after-head|\n  - rate\t\t\t\t|translate-nvim-preset-parse-after-rate|\n  - window\t\t\t\t|translate-nvim-preset-parse-after-window|\n  - no_handle\t\t\t\t|translate-nvim-preset-parse-after-no-handle|\n  - translate_shell\t\t\t|translate-nvim-preset-parse-after-translate-shell|\n  - deepl\t\t\t\t|translate-nvim-preset-parse-after-deepl|\n- output\t\t\t\t|translate-nvim-preset-output|\n  - split\t\t\t\t|translate-nvim-preset-output-split|\n  - floating\t\t\t\t|translate-nvim-preset-output-floating|\n  - insert\t\t\t\t|translate-nvim-preset-output-insert|\n  - replace\t\t\t\t|translate-nvim-preset-output-replace|\n  - register\t\t\t\t|translate-nvim-preset-output-register|\nVariables\t\t\t\t|translate-nvim-variables|\n\n\n==============================================================================\nIntroduction\t\t\t\t\t*translate-nvim-introduction*\n\n\t\t\t\t\t*translate.nvim*\t*translate-nvim*\ntranslate.nvim ~\n\n|translate.nvim| is a plugin for nvim that allows you to translate the\nselection with any external command and handle the result as you like.\n\nIt provides |:Translate| for it.\n\n\nRequirement:\n  - neovim >= 0.7\n  - curl (for |translate-nvim-preset-command-google|,\n    |translate-nvim-preset-command-deepl-free|, and\n    |translate-nvim-preset-command-deepl-pro|)\n  - DeepL API authorization key (for\n    |translate-nvim-preset-command-deepl-free| and\n    |translate-nvim-preset-command-deepl-pro|)\n  - trans (for |translate-nvim-preset-command-translate-shell|)\n\n\n==============================================================================\nCommand\t\t\t\t\t\t\t*translate-nvim-command*\n\n\t\t\t\t\t\t\t\t*:Translate*\n:[range]Translate {target-lang} [{-options}...]\n\n\t{target-lang}: Required. The language into which the text should be\n\ttranslated. The format varies depending on the external command used.\n\n\t|:Translate| can take |:range|. |v|, |V| and |CTRL-V| are supported. If it was\n\tnot given, |:Translate| treats current cursor line.\n\n\tavailable options:\n\t\t- '-source='\n\t\t\tThe language of the text to be translated.\n\t\t- '-parse_before='\n\t\t\tThe functions to format texts of selection. You can\n\t\t\tuse a comma-separated string. If omitted,\n\t\t\t|translate-nvim-option-default-parse-before|.\n\t\t- '-command='\n\t\t\tThe extermal command to use translation. If omitted,\n\t\t\t|translate-nvim-option-default-command| is used.\n\t\t- '-parse_after='\n\t\t\tThe functions to format the result of extermal\n\t\t\tcommand. You can use a comma-separated string.\n\t\t\tIf omitted, |translate-nvim-option-default-parse-after|.\n\t\t- '-output='\n\t\t\tThe function to pass the translation result. If\n\t\t\tomitted, |translate-nvim-option-default-output|.\n\t\t- '-comment'\n\t\t\tSpecial option, used as a flag. If this flag is set\n\t\t\tand the cursor is over a comment, whole comment is\n\t\t\ttreated as a selection.\n\n\n\tIf mapping |:Translate|, You can use |<Cmd>|.\n\n>\n\tnnoremap me <Cmd>Translate EN<CR>\n\txnoremap me <Cmd>Translate EN<CR>\n<\n\n        If you cannot use it, you must change the format with nmap and xmap.\n\n>\n\tnnoremap me :<C-u>Translate EN<CR>\n\txnoremap me :Translate EN<CR>\n<\n\n\n==============================================================================\nSetup\t\t\t\t\t\t\t*translate-nvim-setup*\n\nThe options are set through the setup function. See |translate-nvim-option| to\ncheck available options.\n\n>\n\trequire(\"translate\").setup({\n\t    default = {\n\t        command = \"deepl_free\",\n\t        output = \"floating\",\n\t    },\n\t    preset = {\n\t        output = {\n\t            insert = {\n\t                base = \"top\",\n\t                off = -1,\n\t            },\n\t        },\n\t    },\n\t})\n<\n\n\n==============================================================================\nOption\t\t\t\t\t\t\t*translate-nvim-option*\n\n\t\t\t\t\t\t*translate-nvim-option-default*\ndefault ~\n\ntable\n\n'parse_before', 'command', 'parse_after' and 'output' used by |:Translate|.\nSee |translate-nvim-preset|, to check the presets provided by |translate-nvim|.\n\n\n\t\t\t\t*translate-nvim-option-default-parse-before*\n\tparse_before ~\n\n\tstring\n\tdefault: 'trim,natural'\n\n\tSee |translate-nvim-preset-parse-before-trim| and\n\t|translate-nvim-preset-parse-before-concat|.\n\n\n\t\t\t\t*translate-nvim-option-default-command*\n\tcommand ~\n\n\tstring\n\tdefault: 'google'\n\n\tSee |translate-nvim-preset-command-google|.\n\n\n\t\t\t\t*translate-nvim-option-default-parse-after*\n\tparse_after ~\n\n\tstring\n\tdefault: 'head'\n\n\tSee |translate-nvim-preset-parse-after-remove-newline| and\n\t|translate-nvim-preset-parse-after-floating|.\n\n\n\t\t\t\t\t*translate-nvim-option-default-output*\n\toutput ~\n\n\tstring\n\tdefault: 'floating'\n\n\tSee |translate-nvim-preset-output-floating|.\n\n\n\t\t\t\t\t\t*translate-nvim-option-preset*\npreset ~\n\nOptions passed to the presets.\n\n\n\t\t\t\t*translate-nvim-option-preset-parse-before*\n\tparse_before ~\n\n\n\t\t\t\t*translate-nvim-option-preset-parse-before-natural*\n\t\tnatural ~\n\n\t\ttable\n\t\tdefault: {\n\t\t    lang_abbr = {},\n\t\t    end_marks = {},\n\t\t    start_marks = {},\n\t\t}\n\n\t\tTable 'lang_abbr' for converting the '-source' option of the\n\t\tcommand to the language, list 'end_marks' of the\n\t\tend-of-sentence characters pattern (vim regular expression,\n\t\t|/\\V|), and list 'start_marks' of the start-of-sentence\n\t\tcharacters pattern (same 'end_marks'). Use lowercase for\n\t\tlanguage names and their abbreviations.\n\n\t\tFor example, in English, which is defined by default.\n\n>\n\t\t{\n\t\t    lang_abbr = {\n\t\t        en = \"english\",\n\t\t        eng = \"english\",\n\t\t    },\n\t\t    end_marks = {\n\t\t        english = {\n\t\t            \".\",\n\t\t            \"?\",\n\t\t            \"!\",\n\t\t            \":\",\n\t\t            \";\",\n\t\t        },\n\t\t    },\n\t\t}\n<\n\n\t\tOnly 'english', 'japanese', and 'chinese' have rules defined\n\t\tby default. Other languages can be defined by yourself or PRs\n\t\tare welcome!\n\n\n\t\t\t\t*translate-nvim-option-preset-parse-before-trim*\n\t\ttrim ~\n\n\t\tnil\n\n\t\tThere are currently no options for 'trim'.\n\n\n\t\t\t\t*translate-nvim-option-preset-parse-before-concat*\n\t\tconcat ~\n\n\t\ttable\n\t\tdefault: { sep = \" \" }\n\n\t\tSets the delimiter used to join lines.\n\n\n\t\t\t\t\t*translate-nvim-option-preset-command*\n\tcommand ~\n\n\n\t\t\t\t*translate-nvim-option-preset-command-google*\n\t\tgoogle ~\n\n\t\ttable\n\t\tdefault: { args = {} }\n\n\t\tSet the extra arguments to be passed to the 'curl' command.\n\n\n\t\t\t*translate-nvim-option-preset-command-translate-shell*\n\t\ttranslate_shell ~\n\n\t\ttable\n\t\tdefault: { args = {} }\n\n\t\tSet the extra arguments to be passed to the 'trans' command.\n\n\n\t\t\t\t*translate-nvim-option-preset-command-deepl-free*\n\t\tdeepl_free ~\n\n\t\ttable\n\t\tdefault: { args = {} }\n\n\t\tSet the extra arguments to be passed to the 'curl' command.\n\n\n\t\t\t\t*translate-nvim-option-preset-command-deepl-pro*\n\t\tdeepl_pro ~\n\n\t\ttable\n\t\tdefault: { args = {} }\n\n\t\tSet the extra arguments to be passed to the 'curl' command.\n\n\n\t\t\t\t*translate-nvim-option-preset-parse-after*\n\tparse_after ~\n\n\n\t\t\t*translate-nvim-option-preset-parse-after-oneline*\n\t\t\t*translate-nvim-option-preset-parse-after-head*\n\t\t\t*translate-nvim-option-preset-parse-after-rate*\n\t\t\t*translate-nvim-option-preset-parse-after-translate-shell*\n\t\t\t*translate-nvim-option-preset-parse-after-deepl*\n\t\toneline, head, rate, translate_shell, deepl ~\n\n\t\tnil\n\n\t\tThere are currently no options for these.\n\n\n\t\t\t\t*translate-nvim-option-preset-parse-after-window*\n\t\twindow ~\n\n\t\ttable:\n\t\tdefault: {\n\t\t\twidth = 0.8\n\t\t}\n\n\t\tThe 'width' is a percentage of the current window width. If it\n\t\tis greater than 1, that value, not the percentage, is used as\n\t\tthe fixed value.\n\n\n\n\t\t\t\t\t*translate-nvim-option-preset-output*\n\toutput ~\n\n\n\t\t\t\t*translate-nvim-option-preset-output-split*\n\t\tsplit ~\n\n\t\ttable\n\t\tdefault: {\n\t\t\tposition = \"top\",\n\t\t\tmin_size = 5\n\t\t\tmax_size = 0.5\n\t\t\tname = \"translate://output\",\n\t\t\tfiletype = \"translate\",\n\t\t\tappend = false,\n\t\t}\n\n\t\tThe 'position' is where the result will be placed: 'top' or\n\t\t'bottom'. The 'min_size' and 'max_size' are buffer size\n\t\tlimits. The buffer size depends on the number of lines of the\n\t\ttranslation result, but you can set an upper and lower limit.\n\t\tIf it is less than 1, it is a percentage of the current\n\t\twindow. In the event of a conflict, min_size takes precedence.\n\t\tThe 'name' and 'filetype' is set to the split buffer. If\n\t\t'append' is true, without deleting the previous translation\n\t\tresult, the current one will be added to the last line.\n\n\n\t\t\t\t*translate-nvim-option-preset-output-floating*\n\t\tfloating ~\n\n\t\ttable\n\t\tdefault: {\n\t\t\trelative = \"cursor\",\n\t\t\tstyle = \"minimal\",\n\t\t\twidth = nil,\n\t\t\theight = nil,\n\t\t\trow = 1,\n\t\t\tcol = 1,\n\t\t\tborder = \"single\",\n\t\t\tfiletype = \"translate\",\n\t\t\tzindex = 50,\n\t\t}\n\n\t\tThe option passed as the 3rd argument of |nvim_open_win()|.\n\t\tThe 'width' and 'height' are automatically calculated from the\n\t\treceived array.\n\n\n\t\t\t\t*translate-nvim-option-preset-output-insert*\n\t\tinsert ~\n\n\t\ttable\n\t\tdefault: {\n\t\t\tbase = 'bottom',\n\t\t\toff = 0,\n\t\t}\n\n\t\tWhere to insert the translation result. If 'base' is 'top',\n\t\tthe first line of the selection is used as the base, else if\n\t\t'bottom', the last of line the selection. Finally, add 'off'\n\t\tto the base. For example, with the default, it will be\n\t\tinserted just bellow the selection.\n\n\n\t\t\t\t*translate-nvim-option-preset-output-replace*\n\t\treplace ~\n\n\n\t\tYou can choose the behavior of replace: 'head' respects the\n\t\tstarting position and the original position; 'rate' calculates\n\t\tand distributes a percentage of the length of each original\n\t\tline.\n\n\n\t\t\t\t*translate-nvim-option-preset-output-register*\n\t\tregister ~\n\n\t\ttable\n\t\tdefault: {\n\t\t\tname = vim.v.register\n\t\t}\n\n\t\tSets the translation result to the register specified by\n\t\t'name'. Users who set |clipboard| may want to check\n\t\t|register-variable| before changing this option.\n\n\n\t\t\t\t\t*translate-nvim-option-parse-before*\nparse_before ~\n\ntable\ndefault: {}\n\nYou can set any function you want to use for formatting selection.\nSet tables with the value which has as 'cmd' key a function that returns\nthe command and arguments.\nCheck 'lua/translate/preset/parse_before' for details.\n\n\n\t\t\t\t\t*translate-nvim-option-command*\ncommand ~\n\ntable\ndefault: {}\n\nYou can set any external command you want to use for translation.\nSet tables with the value which has as 'cmd' key a function that returns\nthe command and arguments.\nCheck 'lua/translate/preset/command' for details.\n\n\n\t\t\t\t\t*translate-nvim-option-parse-after*\nparse_after ~\n\ntable\ndefault: {}\n\nYou can set functions to format the result of the translation. Set tables with\nthe value which has as 'cmd' key a function. Check\n'lua/translate/preset/parse_after' for details.\n\n\n\t\t\t\t\t\t*translate-nvim-option-output*\noutput ~\n\ntable\ndefault: {}\n\nYou can set functions to be passed the result of the translation. Set tables\nwith the value which has as 'cmd' key a function. Check\n'lua/translate/preset/output' for details.\n\n\n\t\t\t\t\t*translate-nvim-option-replace-symbols*\nreplace_symbols ~\n\ntable\ndefault: {\n        translate_shell = {\n            [\"=\"] = \"{@E@}\",\n            [\"#\"] = \"{@S@}\",\n            [\"/\"] = \"{@C@}\",\n        },\n        deepl_free = {},\n        deepl_pro = {},\n        google = {},\n}\n\nThis plugin escapes special strings for successful translation. This is its\ncorresponding dictionary. For example, translate_shell has problems with '='\nbeing translated into the strange string 'u003d', or failing to translate\nstrings that begin with '/'. Therefore, we temporarily convert the symbols to\nspecial symbols such as '{{@E@}}' before performing the translation, and then\nrestore the symbols in the translation result for normal translation.\n\n\n\t\t\t\t\t\t*translate-nvim-option-silent*\nsilent ~\n\nboolean\ndefault: false\n\nIf true, the 'Translate success/failed' messages will be disabled.\n\n\n==============================================================================\nPreset\t\t\t\t\t\t\t*translate-nvim-preset*\n\nThe following is a list of commands, parsing functions, and output methods\nprovided by this plugin.\n\n\n\t\t\t\t\t*translate-nvim-preset-parse-before*\nparse_before ~\n\nA set of functions that take an array of lines of text from a selection and\nprocess them into a string that is eventually passed to the translation\ncommand. The second and subsequent functions receive the return value of the\nprevious function.\n\n\n\t\t\t\t*translate-nvim-preset-parse-before-natural*\n\tnatural ~\n\n\tSeparates selection with a blank line or when the start/end of a line\n\tis the start/end of a sentence. To use it, pass the 'source' option to\n\tthe command and tell it the original language. By default, this\n\tgrammar rule is defined only for English, Japanese and Chinese.\n\n\n\t\t\t\t*translate-nvim-preset-parse-before-trim*\n\ttrim ~\n\n\tExecute |vim.trim()| on each line.\n\n\n\t\t\t\t*translate-nvim-preset-parse-before-concat*\n\tconcat ~\n\n\tConcatenates selection into a single string using a delimiter. The\n\tdelimiter is <Space> by default. If you want to change it, use\n\t|translate-nvim-option-preset-parse-before-concat|.\n\n\n\t\t\t\t*translate-nvim-preset-parse-before-no-handle*\n\tno_handle ~\n\n\tIf you don't want to adjust anything, use this.\n\n\n\t\t\t\t\t\t*translate-nvim-preset-command*\ncommand ~\n\nAPI/External commands used for translation.\n'curl' is required except for translate_shell.\n<https://curl.se/>\n\n\n\t\t\t\t\t*translate-nvim-preset-command-google*\n\tgoogle ~\n\n\tUse Google Translate API via GAS.\n\tThere is nothing for you to prepare.\n\n\n\t\t\t\t*translate-nvim-preset-command-translate-shell*\n\ttranslate_shell ~\n\n\tUse translate-shell.\n\tYou need to install 'trans' command.\n\t<https://github.com/soimort/translate-shell>\n\n\n\t\t\t\t*translate-nvim-preset-command-deepl-free*\n\tdeepl_free ~\n\n\tUse DeepL API Free\n\t<https://www.deepl.com/en/docs-api/>\n\n\tSet your DeepL API authorization key to |g:deepl_api_auth_key|.\n\n\n\t\t\t\t*translate-nvim-preset-command-deepl-pro*\n\tdeepl_pro ~\n\n\tUse DeepL API Pro\n\t<https://www.deepl.com/en/docs-api/>\n\n\tWhat you need is the same as |translate-nvim-preset-command-deepl-free|.\n\n\n\t\t\t\t\t*translate-nvim-preset-parse-after*\nparse_after ~\n\nA set of functions that take a result of translation and process them into a\nstring that is eventually passed to the output. The second and subsequent\nfunctions receive the return value of the previous function.\n\n\n\t\t\t\t\t*translate-nvim-preset-parse-after-oneline*\n\toneline ~\n\n\tSummarize the results on a single line.\n\n\tIt is intended to be used with 'split', 'insert', and 'replace'. It is\n\tdeprecated for use in 'floating' as it may not fit on the screen.\n\n\n\t\t\t\t\t*translate-nvim-preset-parse-after-head*\n\thead ~\n\n\tSplits the translation result to fit the display width of the original\n\ttext in the selected area. We cannot guarantee the number of\n\tcharacters in the last line because the number of characters changes\n\tbefore and after the translation.\n\n\tIt is intended to be used with 'split', 'insert', 'replace', and\n\t'window'.\n\n\n\t\t\t\t\t*translate-nvim-preset-parse-after-rate*\n\trate ~\n\n\tDivides the translation result by the percentage of each line of the\n\toriginal text display width.\n\n\tIt is intended to be used with 'split', 'insert', 'replace', and\n\t'window'.\n\n\n\t\t\t\t\t*translate-nvim-preset-parse-after-window*\n\twindow ~\n\n\tSplits the text to fit the specified window width. Default is 0.8\n\t(percentage of the current window). Use\n\t|translate-nvim-option-preset-parse-after-window| to change it.\n\n\tIt is intended to be used with 'split', 'insert', 'replace', and\n\t'window'.\n\n\n\t\t\t\t\t*translate-nvim-preset-parse-after-no-handle*\n\tno_handle ~\n\n\tIf you don't want to adjust anything, use this.\n\n\n\t\t\t\t*translate-nvim-preset-parse-after-translate-shell*\n\ttranslate_shell ~\n\n\tIf 'command' is 'translate_shell', this parser is added automatically.\n\tIn other words, you do not need to specify this unless you want to use\n\tonly this. Split by line breaks in the translation result or remove\n\textra line break characters at the end of it.\n\n\n\t\t\t\t\t*translate-nvim-preset-parse-after-deepl*\n\tdeepl ~\n\n\tIf 'command' is 'deepl_pro/free', this parser is added automatically.\n\tIn other words, you do not need to specify this unless you want to use\n\tonly this. DeepL API returns the response in json format, which is\n\tparsed and the text of the translation result is taken. Use\n\tvim.json.decode (neovim 0.6.0+) or |json_decode|\n\n\n\t\t\t\t\t\t*translate-nvim-preset-output*\noutput ~\n\nFunction passed the result of translation.\n\n\n\t\t\t\t\t*translate-nvim-preset-output-split*\n\tsplit ~\n\n\tSplit the window and output the result to it. By default, the previous\n\ttranslation result is deleted each time. If you want to keep it, use\n\tthe option. See |translate-nvim-option-preset-output-split|.\n\n\n\t\t\t\t\t*translate-nvim-preset-output-floating*\n\tfloating ~\n\n\tDisplay the result in a floating window. See\n\t|translate-nvim-option-preset-output-floating|.\n\n\n\t\t\t\t\t*translate-nvim-preset-output-insert*\n\tinsert ~\n\n\tInsert the result into the current buffer. By default, it is inserted\n\tjust below the selection. See\n\t|translate-nvim-option-preset-output-insert|.\n\n\n\t\t\t\t\t*translate-nvim-preset-output-replace*\n\treplace ~\n\n\tReplace the original text with the result. See\n\t|translate-nvim-option-preset-output-replace|.\n\n\n\t\t\t\t\t*translate-nvim-preset-output-register*\n\tregister ~\n\n\tSet the result to the register. See\n\t|translate-nvim-option-preset-output-register|.\n\n\n\n==============================================================================\nVariables\t\t\t\t\t*translate-nvim-variables*\n\n\t\t\t\t\t\t\t*g:deepl_api_auth_key*\ng:deepl_api_auth_key ~\n\nAuthentication key for DeepL API.\n\n\n\nvim:tw=78:ts=8:noet:ft=help:norl:\n"
  },
  {
    "path": "lua/translate/command.lua",
    "content": "local fn = vim.fn\n\nlocal config = require(\"translate.config\")\n\nlocal M = {}\n\nlocal modes = {\n  \"parse_before\",\n  \"command\",\n  \"parse_after\",\n  \"output\",\n  \"source\",\n  \"comment\",\n}\n\n---@param arglead string #The leading portion of the argument currently being completed on\n---@param cmdline string #The entire command line\n---@param _ number #the cursor position in it (byte index)\n---@return string[]?\nfunction M.get_complete_list(arglead, cmdline, _)\n  local mode\n  if not vim.startswith(arglead, \"-\") then\n    mode = \"target\"\n  elseif arglead:find(\"^%-.*=\") then\n    mode = arglead:match(\"^%-(.*)=\")\n  else\n    return modes\n  end\n\n  if vim.tbl_contains({ \"parse_before\", \"command\", \"parse_after\", \"output\" }, mode) then\n    return config.get_keys(mode)\n  elseif vim.tbl_contains({ \"source\", \"target\" }, mode) then\n    local command = cmdline:match(\"-command=(%S+)\")\n    command = command or config.get(\"default\").command\n    local module = config.config.command[command] or config._preset.command[command]\n    if module and module.complete_list then\n      return module.complete_list(mode == \"target\")\n    end\n  end\nend\n\n---@param cb fun(mode: string, fargs: string[])\nfunction M.create_command(cb)\n  vim.api.nvim_create_user_command(\"Translate\", function(opts)\n    -- If range is 0, not given, it has been called from normal mode, or visual mode with `<Cmd>` mapping.\n    -- Otherwise it must have been called from visual mode.\n    local mode = opts.range == 0 and fn.mode() or fn.visualmode()\n    cb(mode, opts.fargs)\n  end, {\n    range = 0,\n    nargs = \"+\",\n    complete = M.get_complete_list,\n  })\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/config.lua",
    "content": "local M = {}\n\nM._preset = {\n  parse_before = {\n    natural = require(\"translate.preset.parse_before.natural\"),\n    trim = require(\"translate.preset.parse_before.trim\"),\n    concat = require(\"translate.preset.parse_before.concat\"),\n    no_handle = require(\"translate.preset.parse_before.no_handle\"),\n  },\n  command = {\n    translate_shell = require(\"translate.preset.command.translate_shell\"),\n    deepl_free = require(\"translate.preset.command.deepl_free\"),\n    deepl_pro = require(\"translate.preset.command.deepl_pro\"),\n    google = require(\"translate.preset.command.google\"),\n  },\n  parse_after = {\n    oneline = require(\"translate.preset.parse_after.oneline\"),\n    head = require(\"translate.preset.parse_after.head\"),\n    rate = require(\"translate.preset.parse_after.rate\"),\n    window = require(\"translate.preset.parse_after.window\"),\n    no_handle = require(\"translate.preset.parse_after.no_handle\"),\n    translate_shell = require(\"translate.preset.parse_after.translate_shell\"),\n    deepl_free = require(\"translate.preset.parse_after.deepl_free\"),\n    deepl_pro = require(\"translate.preset.parse_after.deepl_pro\"),\n    google = require(\"translate.preset.parse_after.google\"),\n  },\n  output = {\n    floating = require(\"translate.preset.output.floating\"),\n    split = require(\"translate.preset.output.split\"),\n    insert = require(\"translate.preset.output.insert\"),\n    replace = require(\"translate.preset.output.replace\"),\n    register = require(\"translate.preset.output.register\"),\n  },\n}\n\nM.config = {\n  default = {\n    parse_before = \"trim,natural\",\n    command = \"google\",\n    parse_after = \"head\",\n    output = \"floating\",\n  },\n  parse_before = {},\n  command = {},\n  parse_after = {},\n  output = {},\n  preset = {\n    parse_before = {\n      natural = {\n        lang_abbr = {},\n        end_marks = {},\n        start_marks = {},\n      },\n      concat = {\n        sep = \" \",\n      },\n    },\n    command = {\n      google = {\n        args = {},\n      },\n      translate_shell = {\n        args = {},\n      },\n      deepl_free = {\n        args = {},\n      },\n      deepl_pro = {\n        args = {},\n      },\n    },\n    parse_after = {\n      window = {\n        width = 0.8,\n      },\n    },\n    output = {\n      floating = {\n        relative = \"cursor\",\n        style = \"minimal\",\n        row = 1,\n        col = 1,\n        border = \"single\",\n        filetype = \"translate\",\n        zindex = 50,\n      },\n      split = {\n        position = \"top\",\n        min_size = 3,\n        max_size = 0.3,\n        name = \"translate://output\",\n        filetype = \"translate\",\n        append = false,\n      },\n      insert = {\n        base = \"bottom\",\n        off = 0,\n      },\n      register = {\n        name = vim.v.register,\n      },\n    },\n  },\n  silent = false,\n  replace_symbols = {\n    translate_shell = {\n      [\"=\"] = \"{@E@}\",\n      [\"#\"] = \"{@S@}\",\n      [\"/\"] = \"{@C@}\",\n      [\"\\\\n\"] = \"{@N@}\",\n    },\n    deepl_free = {},\n    deepl_pro = {},\n    google = {},\n  },\n}\n\n---@param opt table\nfunction M.setup(opt)\n  M.config = vim.tbl_deep_extend(\"force\", M.config, opt)\nend\n\n---@param name string\n---@return boolean|table\nfunction M.get(name)\n  return M.config[name]\nend\n\n---@param mode string\n---@param name string\n---@return fun(lines: string[], command_args: table)\n---@return string\nfunction M.get_func(mode, name)\n  name = name or M.config.default[mode]\n  local module = M.config[mode][name] or M._preset[mode][name]\n  if module and module.cmd then\n    return module.cmd, name\n  else\n    error((\"Invalid name of %s: %s\"):format(module, name))\n  end\nend\n\n---@param mode string\n---@param names string\n---@return fun(text: string, command_args: table)[]\n---@return string[]\nfunction M.get_funcs(mode, names)\n  names = names or M.config.default[mode]\n  names = vim.split(names, \",\")\n  local modules = {}\n  for _, name in ipairs(names) do\n    local module = M.config[mode][name] or M._preset[mode][name]\n    if module and module.cmd then\n      table.insert(modules, module.cmd)\n    else\n      error((\"Invalid name of %s: %s\"):format(mode, name))\n    end\n  end\n  return modules, names\nend\n\n---For completion of command ':Translate'\n---@param mode string\n---@return string[]\nfunction M.get_keys(mode)\n  local keys = vim.tbl_keys(M.config[mode])\n  keys = vim.list_extend(keys, vim.tbl_keys(M._preset[mode]))\n  return keys\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/init.lua",
    "content": "local luv = vim.loop\n\nlocal config = require(\"translate.config\")\nlocal replace = require(\"translate.util.replace\")\nlocal select = require(\"translate.util.select\")\nlocal util = require(\"translate.util.util\")\nlocal create_command = require(\"translate.command\").create_command\n\nlocal M = {}\n\n---@param mode string\n---@param args string[]\nfunction M.translate(mode, args)\n  args = M._parse_args(args)\n  local pos = select.get(args, mode)\n\n  if #pos == 0 then\n    vim.notify(\"Selection could not be recognized.\")\n    return\n  end\n\n  M._translate(pos, args)\nend\n\n---@param opts string[]\n---@return table\nfunction M._parse_args(opts)\n  local args = {}\n  for _, opt in ipairs(opts) do\n    local name, arg = opt:match(\"-([a-z_]+)=(.*)\") -- e.g. '-parse_after=head'\n    if not name then\n      name = opt:match(\"-(%l+)\") -- for '-comment'\n      if name then\n        arg = true\n      else -- '{target-lang}'\n        name = \"target\"\n        arg = opt\n      end\n    end\n    args[name] = arg\n  end\n  return args\nend\n\nlocal function pipes()\n  local stdin = luv.new_pipe(false)\n  local stdout = luv.new_pipe(false)\n  local stderr = luv.new_pipe(false)\n  return { stdin, stdout, stderr }\nend\n\nlocal function set_to_top(tbl, elem)\n  if tbl[1] ~= elem then\n    table.insert(tbl, 1, elem)\n  end\nend\n\n---@param pos positions\n---@param cmd_args table\nfunction M._translate(pos, cmd_args)\n  local parse_before = config.get_funcs(\"parse_before\", cmd_args.parse_before)\n  local command, command_name = config.get_func(\"command\", cmd_args.command)\n  local parse_after = config.get_funcs(\"parse_after\", cmd_args.parse_after)\n  local output = config.get_func(\"output\", cmd_args.output)\n\n  replace.set_command_name(command_name)\n  set_to_top(parse_before, replace.before)\n  set_to_top(parse_after, replace.after)\n\n  local after_process = config._preset.parse_after[command_name]\n  if after_process and after_process.cmd then\n    set_to_top(parse_after, after_process.cmd)\n  end\n\n  local lines = M._selection(pos)\n  pos._lines_selected = lines\n\n  ---@type string[]\n  lines = M._run(parse_before, lines, pos, cmd_args)\n  if not pos._group then\n    pos._group = util.seq(#lines)\n  end\n\n  local cmd, args = command(lines, cmd_args)\n  local stdio = pipes()\n\n  local handle\n  handle = luv.spawn(cmd, { args = args, stdio = stdio }, function(code)\n    if not config.get(\"silent\") then\n      if code == 0 then\n        print(\"Translate success\")\n      else\n        print(\"Translate failed\")\n      end\n    end\n    handle:close()\n  end)\n\n  if not handle then\n    return\n  end\n\n  luv.read_start(\n    stdio[2],\n    vim.schedule_wrap(function(err, result)\n      assert(not err, err)\n\n      if result then\n        result = M._run(parse_after, result, pos)\n        output(result, pos)\n      end\n    end)\n  )\nend\n\n---@param pos positions\n---@return string[]\nfunction M._selection(pos)\n  local lines = {}\n  for i, line in ipairs(pos._lines) do\n    local col = pos[i].col\n    table.insert(lines, line:sub(col[1], col[2]))\n  end\n  return lines\nend\n\n---@generic T\n---@param functions function[]\n---@param arg `T`\n---@param pos positions\n---@param cmd_args? string[]\n---@return T\nfunction M._run(functions, arg, pos, cmd_args)\n  for _, func in ipairs(functions) do\n    arg = func(arg, pos, cmd_args)\n  end\n  return arg\nend\n\n---@param opt table\nfunction M.setup(opt)\n  config.setup(opt)\n  create_command(M.translate)\n  vim.g.loaded_translate_nvim = true\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/kit/Async/AsyncTask.lua",
    "content": "local Lua = require(\"___plugin_name___.kit.Lua\")\n\n---@class ___plugin_name___.kit.Async.AsyncTask<T>: { value: T }\n---@field private value T\n---@field private status ___plugin_name___.kit.Async.AsyncTask.Status\n---@field private chained boolean\n---@field private children (fun(): any)[]\nlocal AsyncTask = {}\nAsyncTask.__index = AsyncTask\n\n---@alias ___plugin_name___.kit.Async.AsyncTask.Status integer\nAsyncTask.Status = {}\nAsyncTask.Status.Pending = 0\nAsyncTask.Status.Fulfilled = 1\nAsyncTask.Status.Rejected = 2\n\n---Handle unhandled rejection.\n---@param err any\nfunction AsyncTask.on_unhandled_rejection(err)\n  error(err)\nend\n\n---Return the value is AsyncTask or not.\n---@param value any\n---@return boolean\nfunction AsyncTask.is(value)\n  return getmetatable(value) == AsyncTask\nend\n\n---Resolve all tasks.\n---@param tasks any[]\n---@return ___plugin_name___.kit.Async.AsyncTask\nfunction AsyncTask.all(tasks)\n  return AsyncTask.new(function(resolve, reject)\n    local values = {}\n    local count = 0\n    for i, task in ipairs(tasks) do\n      AsyncTask.resolve(task)\n        :next(function(value)\n          values[i] = value\n          count = count + 1\n          if #tasks == count then\n            resolve(values)\n          end\n        end)\n        :catch(reject)\n    end\n  end)\nend\n\n---Create resolved AsyncTask.\n---@param v any\n---@return ___plugin_name___.kit.Async.AsyncTask\nfunction AsyncTask.resolve(v)\n  if AsyncTask.is(v) then\n    return v\n  end\n  return AsyncTask.new(function(resolve)\n    resolve(v)\n  end)\nend\n\n---Create new AsyncTask.\n---@NOET: The AsyncTask has similar interface to JavaScript Promise but the AsyncTask can be worked as synchronous.\n---@param v any\n---@return ___plugin_name___.kit.Async.AsyncTask\nfunction AsyncTask.reject(v)\n  if AsyncTask.is(v) then\n    return v\n  end\n  return AsyncTask.new(function(_, reject)\n    reject(v)\n  end)\nend\n\n---Create new async task object.\n---@generic T\n---@param runner fun(resolve: fun(value: T), reject: fun(err: any))\nfunction AsyncTask.new(runner)\n  local self = setmetatable({}, AsyncTask)\n\n  self.gc = Lua.gc(function()\n    if self.status == AsyncTask.Status.Rejected then\n      if not self.chained then\n        AsyncTask.on_unhandled_rejection(self.value)\n      end\n    end\n  end)\n\n  self.value = nil\n  self.status = AsyncTask.Status.Pending\n  self.chained = false\n  self.children = {}\n  local ok, err = pcall(function()\n    runner(function(res)\n      if self.status ~= AsyncTask.Status.Pending then\n        return\n      end\n      self.status = AsyncTask.Status.Fulfilled\n      self.value = res\n      for _, c in ipairs(self.children) do\n        c()\n      end\n    end, function(err)\n      if self.status ~= AsyncTask.Status.Pending then\n        return\n      end\n      self.status = AsyncTask.Status.Rejected\n      self.value = err\n      for _, c in ipairs(self.children) do\n        c()\n      end\n    end)\n  end)\n  if not ok then\n    self.status = AsyncTask.Status.Rejected\n    self.value = err\n    for _, c in ipairs(self.children) do\n      c()\n    end\n  end\n  return self\nend\n\n---Sync async task.\n---@NOTE: This method uses `vim.wait` so that this can't wait the typeahead to be empty.\n---@param timeout? number\n---@return any\nfunction AsyncTask:sync(timeout)\n  vim.wait(timeout or 24 * 60 * 60 * 1000, function()\n    return self.status ~= AsyncTask.Status.Pending\n  end, 0)\n  if self.status == AsyncTask.Status.Rejected then\n    error(self.value)\n  end\n  if self.status ~= AsyncTask.Status.Fulfilled then\n    error(\"AsyncTask:sync is timeout.\")\n  end\n  return self.value\nend\n\n---Register next step.\n---@param on_fulfilled fun(value: any): any\nfunction AsyncTask:next(on_fulfilled)\n  return self:_dispatch(on_fulfilled, function(err)\n    error(err)\n  end)\nend\n\n---Register catch step.\n---@param on_rejected fun(value: any): any\n---@return ___plugin_name___.kit.Async.AsyncTask\nfunction AsyncTask:catch(on_rejected)\n  return self:_dispatch(function(value)\n    return value\n  end, on_rejected)\nend\n\n---Dispatch task state.\n---@param on_fulfilled fun(value: any): any\n---@param on_rejected fun(err: any): any\n---@return ___plugin_name___.kit.Async.AsyncTask\nfunction AsyncTask:_dispatch(on_fulfilled, on_rejected)\n  self.chained = true\n  local function dispatch(resolve, reject)\n    if self.status == AsyncTask.Status.Fulfilled then\n      local res = on_fulfilled(self.value)\n      if AsyncTask.is(res) then\n        res:next(resolve, reject)\n      else\n        resolve(res)\n      end\n    else\n      local res = on_rejected(self.value)\n      if AsyncTask.is(res) then\n        res:next(resolve, reject)\n      else\n        resolve(res)\n      end\n    end\n  end\n\n  if self.status == AsyncTask.Status.Pending then\n    return AsyncTask.new(function(resolve, reject)\n      table.insert(self.children, function()\n        dispatch(resolve, reject)\n      end)\n    end)\n  end\n  return AsyncTask.new(dispatch)\nend\n\nreturn AsyncTask\n"
  },
  {
    "path": "lua/translate/kit/Async/AsyncTask.spec.lua",
    "content": "local AsyncTask = require(\"___plugin_name___.kit.Async.AsyncTask\")\n\ndescribe(\"kit.Async\", function()\n  local once = function(fn)\n    local done = false\n    return function(...)\n      if done then\n        error(\"already called\")\n      end\n      done = true\n      return fn(...)\n    end\n  end\n\n  it(\"should work AsyncTask:{next/catch}\", function()\n    -- first.\n    local one_task = AsyncTask.new(once(function(resolve)\n      vim.schedule(function()\n        resolve(1)\n      end)\n    end))\n    assert.are.equals(one_task:sync(), 1)\n\n    -- next with return value.\n    local two_task = one_task:next(once(function(value)\n      return value + 1\n    end))\n    assert.are.equals(two_task:sync(), 2)\n\n    -- next with return AsyncTask.\n    local three_task = two_task:next(once(function(value)\n      return AsyncTask.new(function(resolve)\n        vim.schedule(function()\n          resolve(value + 1)\n        end)\n      end)\n    end))\n    assert.are.equals(three_task:sync(), 3)\n\n    -- throw error.\n    local err_task = three_task:next(once(function()\n      error(\"error\")\n    end))\n    local _, err = pcall(function()\n      return err_task:sync()\n    end)\n    assert.are_not.equals(string.match(err, \"error$\"), nil)\n\n    -- skip rejected task's next.\n    local steps = {}\n    local catch_task = err_task\n      :next(once(function()\n        table.insert(steps, 1)\n      end))\n      :next(once(function()\n        table.insert(steps, 2)\n      end))\n      :catch(function()\n        return \"catch\"\n      end)\n      :next(function(value)\n        table.insert(steps, 3)\n        return value\n      end)\n    assert.are.same(steps, { 3 })\n    assert.are.equals(catch_task:sync(), \"catch\")\n  end)\n\n  it(\"should throw timeout error\", function()\n    local task = AsyncTask.new(function(resolve)\n      vim.defer_fn(resolve, 500)\n    end)\n    local ok = pcall(function()\n      return task:sync(100)\n    end)\n    assert.is_false(ok)\n  end)\n\n  it(\"should work AsyncTask.all\", function()\n    local now = vim.loop.now()\n    local values = AsyncTask.all({\n      AsyncTask.new(function(resolve)\n        vim.defer_fn(function()\n          resolve(1)\n        end, 300)\n      end),\n      AsyncTask.new(function(resolve)\n        vim.defer_fn(function()\n          resolve(2)\n        end, 200)\n      end),\n      AsyncTask.new(function(resolve)\n        vim.defer_fn(function()\n          resolve(3)\n        end, 100)\n      end),\n    }):sync()\n    assert.are.same(values, { 1, 2, 3 })\n    assert.is_true((vim.loop.now() - now) - 300 < 10)\n  end)\n\n  it(\"should work AsyncTask.on_unhandled_rejection\", function()\n    local object\n    local called = false\n    AsyncTask.on_unhandled_rejection = function()\n      called = true\n    end\n\n    -- has no catched task.\n    object = AsyncTask.new(function()\n      error(\"error\")\n    end)\n    object = nil\n    called = false\n    collectgarbage(\"collect\")\n    assert.are.equals(object, nil)\n    assert.are.equals(called, true)\n\n    -- has no catched task.\n    object = AsyncTask.new(function()\n      error(\"error\")\n    end):catch(function()\n      -- ignore\n    end)\n    object = nil\n    called = false\n    collectgarbage(\"collect\")\n    assert.are.equals(object, nil)\n    assert.are.equals(called, false)\n\n    -- has no catched task.\n    object = AsyncTask.new(function()\n      error(\"error\")\n    end):next(function()\n      -- ignore\n    end)\n    object = nil\n    called = false\n    collectgarbage(\"collect\")\n    assert.are.equals(object, nil)\n    assert.are.equals(called, true)\n  end)\nend)\n"
  },
  {
    "path": "lua/translate/kit/Async/init.lua",
    "content": "local AsyncTask = require(\"___plugin_name___.kit.Async.AsyncTask\")\n\n_G.__kit__ = _G.__kit__ or {}\n_G.__kit__.Async = _G.__kit__.Async or {}\n_G.__kit__.Async.threads = _G.__kit__.Async.threads or {}\n\nlocal Async = {}\n\n---Run async function immediately.\n---@param runner fun()\n---@param ... any\n---@return ___plugin_name___.kit.Async.AsyncTask\nfunction Async.run(runner, ...)\n  return Async.async(runner)(...)\nend\n\n---Create async function.\n---@param runner fun()\n---@return fun(): ___plugin_name___.kit.Async.AsyncTask\nfunction Async.async(runner)\n  return function(...)\n    local args = { ... }\n    return AsyncTask.new(function(resolve, reject)\n      local thread = coroutine.create(runner)\n      _G.__kit__.Async.threads[thread] = true\n      local function next_step(ok, v)\n        if coroutine.status(thread) == \"dead\" then\n          _G.__kit__.Async.threads[thread] = nil\n          if not ok then\n            return reject(v)\n          end\n          return AsyncTask.resolve(v):next(resolve):catch(reject)\n        end\n\n        AsyncTask.resolve(v)\n          :next(function(...)\n            next_step(coroutine.resume(thread, ...))\n          end)\n          :catch(function(...)\n            next_step(coroutine.resume(thread, ...))\n          end)\n      end\n      next_step(coroutine.resume(thread, unpack(args)))\n    end)\n  end\nend\n\n---Await async task.\n---@param task ___plugin_name___.kit.Async.AsyncTask\n---@return any\nfunction Async.await(task)\n  if not _G.__kit__.Async.threads[coroutine.running()] then\n    error(\"`Async.await` must be called in async function.\")\n  end\n  return coroutine.yield(AsyncTask.resolve(task))\nend\n\nreturn Async\n"
  },
  {
    "path": "lua/translate/kit/Async/init.spec.lua",
    "content": "local Async = require(\"___plugin_name___.kit.Async\")\nlocal AsyncTask = require(\"___plugin_name___.kit.Async.AsyncTask\")\n\nlocal async = Async.async\nlocal await = Async.await\n\ndescribe(\"kit.Async\", function()\n  it(\"should work like JavaScript Promise\", function()\n    local multiply = async(function(v)\n      return AsyncTask.new(function(resolve)\n        vim.schedule(function()\n          resolve(v * v)\n        end)\n      end)\n    end)\n    local num = async(function()\n      local num = 2\n      num = await(multiply(num))\n      num = await(multiply(num))\n      return num\n    end)():sync()\n    assert.are.equal(num, 16)\n  end)\nend)\n"
  },
  {
    "path": "lua/translate/kit/Cache.lua",
    "content": "---Create cache key.\n---@private\n---@param key string[]|string\n---@return string\nlocal function _key(key)\n  if type(key) == \"table\" then\n    return table.concat(key, \":\")\n  end\n  return key\nend\n\n---@class ___plugin_name___.kit.Cache\n---@field private keys table<string, boolean>\n---@field private entries table<string, any>\nlocal Cache = {}\n\n---Create new cache instance.\nfunction Cache.new()\n  local self = setmetatable({}, { __index = Cache })\n  self.keys = {}\n  self.entries = {}\n  return self\nend\n\n---Get cache entry.\n---@param key string[]|string\n---@return any\nfunction Cache:get(key)\n  return self.entries[_key(key)]\nend\n\n---Set cache entry.\n---@param key string[]|string\n---@param val any\nfunction Cache:set(key, val)\n  key = _key(key)\n  self.keys[key] = true\n  self.entries[key] = val\nend\n\n---Delete cache entry.\n---@param key string[]|string\nfunction Cache:del(key)\n  key = _key(key)\n  self.keys[key] = nil\n  self.entries[key] = nil\nend\n\n---Return this cache has the key entry or not.\n---@param key string[]|string\n---@return boolean\nfunction Cache:has(key)\n  key = _key(key)\n  return not not self.keys[key]\nend\n\n---Ensure cache entry.\n---@generic T\n---@param key string[]|string\n---@param callback function(): T\n---@return T\nfunction Cache:ensure(key, callback)\n  if not self:has(key) then\n    self:set(key, callback())\n  end\n  return self:get(key)\nend\n\nreturn Cache\n"
  },
  {
    "path": "lua/translate/kit/Cache.spec.lua",
    "content": "local Cache = require(\"___plugin_name___.kit.Cache\")\n\ndescribe(\"kit.Cache\", function()\n  it(\"should works {get,set,has,del}\", function()\n    local cache = Cache.new()\n    assert.equal(cache:get(\"unknown\"), nil)\n    assert.equal(cache:has(\"unknown\"), false)\n    cache:set(\"known\", nil)\n    assert.equal(cache:get(\"known\"), nil)\n    assert.equal(cache:has(\"known\"), true)\n    cache:del(\"known\")\n    assert.equal(cache:get(\"known\"), nil)\n    assert.equal(cache:has(\"known\"), false)\n  end)\n\n  it(\"should work ensure\", function()\n    local ensure = setmetatable({\n      count = 0,\n    }, {\n      __call = function(self)\n        self.count = self.count + 1\n      end,\n    })\n    local cache = Cache.new()\n\n    -- Ensure the value.\n    assert.equal(cache:ensure(\"key\", ensure), nil)\n    assert.equal(cache:has(\"key\"), true)\n    assert.equal(ensure.count, 1)\n\n    -- Doesn't call when the value was ensured.\n    assert.equal(cache:ensure(\"key\", ensure), nil)\n    assert.equal(ensure.count, 1)\n\n    -- Call after delete.\n    cache:del(\"key\")\n    assert.equal(cache:ensure(\"key\", ensure), nil)\n    assert.equal(ensure.count, 2)\n  end)\nend)\n"
  },
  {
    "path": "lua/translate/kit/Config.lua",
    "content": "local kit = require(\"___plugin_name___.kit\")\nlocal Cache = require(\"___plugin_name___.kit.Cache\")\n\n---@class ___plugin_name___.kit.Config.Schema # kit.macro.remove\n\n---@alias ___plugin_name___.kit.Config.SchemaInternal ___plugin_name___.kit.Config.Schema|{ revision: integer }\n\n---@class ___plugin_name___.kit.Config\n---@field private _cache ___plugin_name___.kit.Cache\n---@field private _default ___plugin_name___.kit.Config.SchemaInternal\n---@field private _global ___plugin_name___.kit.Config.SchemaInternal\n---@field private _filetype table<string, ___plugin_name___.kit.Config.SchemaInternal>\n---@field private _buffer table<integer, ___plugin_name___.kit.Config.SchemaInternal>\nlocal Config = {}\n\n---Create new config instance.\n---@param default? ___plugin_name___.kit.Config.Schema\nfunction Config.new(default)\n  local self = setmetatable({}, { __index = Config })\n  self._cache = Cache.new()\n  self._default = default or {}\n  self._global = {}\n  self._filetype = {}\n  self._buffer = {}\n  return self\nend\n\n---Set default configuration.\n---@param default ___plugin_name___.kit.Config.Schema\nfunction Config:default(default)\n  self._default = default\nend\n\n---Update global config.\n---@param config ___plugin_name___.kit.Config.Schema\nfunction Config:global(config)\n  local revision = (self._global.revision or 1) + 1\n  self._global = config or {}\n  self._global.revision = revision\nend\n\n---Update filetype config.\n---@param filetypes string|string[]\n---@param config ___plugin_name___.kit.Config.Schema\nfunction Config:filetype(filetypes, config)\n  for _, filetype in ipairs(kit.to_array(filetypes)) do\n    local revision = ((self._filetype[filetype] or {}).revision or 1) + 1\n    self._filetype[filetype] = config or {}\n    self._filetype[filetype].revision = revision\n  end\nend\n\n---Update filetype config.\n---@param bufnr integer\n---@param config ___plugin_name___.kit.Config.Schema\nfunction Config:buffer(bufnr, config)\n  bufnr = bufnr == 0 and vim.api.nvim_get_current_buf() or bufnr\n  local revision = ((self._buffer[bufnr] or {}).revision or 1) + 1\n  self._buffer[bufnr] = config or {}\n  self._buffer[bufnr].revision = revision\nend\n\n---Create setup interface.\n---@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) }\nfunction Config:create_setup_interface()\n  return setmetatable({}, {\n    ---@param config ___plugin_name___.kit.Config.Schema\n    __call = function(_, config)\n      self:global(config)\n    end,\n    ---@param filetypes string|string[]\n    ---@param config ___plugin_name___.kit.Config.Schema\n    filetype = function(_, filetypes, config)\n      self:filetype(filetypes, config)\n    end,\n    ---@param bufnr integer\n    ---@param config ___plugin_name___.kit.Config.Schema\n    buffer = function(_, bufnr, config)\n      self:buffer(bufnr, config)\n    end,\n  })\nend\n\n---Get current configuration.\n---@return ___plugin_name___.kit.Config.Schema\nfunction Config:get()\n  local filetype = vim.api.nvim_buf_get_option(0, \"filetype\")\n  local bufnr = vim.api.nvim_get_current_buf()\n  return self._cache:ensure({\n    self._global.revision or 0,\n    (self._buffer[bufnr] or {}).revision or 0,\n    (self._filetype[filetype] or {}).revision or 0,\n  }, function()\n    local config = self._default\n    config = kit.merge(self._global, config)\n    config = kit.merge(self._filetype[filetype] or {}, config)\n    config = kit.merge(self._buffer[bufnr] or {}, config)\n    config.revision = nil\n    return config\n  end)\nend\n\nreturn Config\n"
  },
  {
    "path": "lua/translate/kit/Config.spec.lua",
    "content": "local Config = require(\"___plugin_name___.kit.Config\")\n\ndescribe(\"kit.Config\", function()\n  before_each(function()\n    vim.cmd([[enew]])\n  end)\n\n  it(\"should {setup,get} global config\", function()\n    local config = Config.new()\n    config:global({ key = 1 })\n    assert.are.same(config:get(), { key = 1 })\n  end)\n\n  it(\"should {setup,get} filetype config\", function()\n    local config = Config.new()\n    vim.cmd([[set filetype=lua]])\n    config:filetype(\"lua\", { key = 1 })\n    assert.are.same(config:get(), { key = 1 })\n    vim.cmd([[set filetype=]])\n    assert.are.same(config:get(), {})\n  end)\n\n  it(\"should {setup,get} buffer config\", function()\n    local config = Config.new()\n    config:buffer(0, { key = 1 })\n    assert.are.same(config:get(), { key = 1 })\n    vim.cmd([[new]])\n    assert.are.same(config:get(), {})\n  end)\n\n  it(\"should merge configuration\", function()\n    local config = Config.new()\n    local bufnr = vim.api.nvim_get_current_buf()\n    vim.cmd([[set filetype=lua]])\n    config:global({ global = 1 })\n    config:filetype(\"lua\", { filetype = 1 })\n    config:buffer(0, { buffer = 1 })\n    assert.are.same(config:get(), { global = 1, filetype = 1, buffer = 1 })\n    vim.cmd([[set filetype=]])\n    assert.are.same(config:get(), { global = 1, buffer = 1 })\n    vim.cmd([[new]])\n    assert.are.same(config:get(), { global = 1 })\n    vim.cmd(([[%sbuffer]]):format(bufnr))\n    assert.are.same(config:get(), { global = 1, buffer = 1 })\n    vim.cmd([[set filetype=lua]])\n    assert.are.same(config:get(), { global = 1, filetype = 1, buffer = 1 })\n  end)\nend)\n"
  },
  {
    "path": "lua/translate/kit/LSP/Position.lua",
    "content": "local Buffer = require(\"___plugin_name___.kit.Vim.Buffer\")\n\n---@class ___plugin_name___.kit.LSP.Position\n---@field public line integer\n---@field public character integer\n\nlocal Position = {}\n\n---@alias ___plugin_name___.kit.LSP.Position.Encoding 'utf8'|'utf16'|'utf32'\nPosition.Encoding = {}\nPosition.Encoding.UTF8 = \"utf8\"\nPosition.Encoding.UTF16 = \"utf16\"\nPosition.Encoding.UTF32 = \"utf32\"\n\n---Return the value is position or not.\n---@param v any\n---@return boolean\nfunction Position.is(v)\n  return type(v) == \"table\" and type(v.line) == \"number\" and type(v.character) == \"number\"\nend\n\n---Create cursor position.\n---@param encoding ___plugin_name___.kit.LSP.Position.Encoding\n---@return ___plugin_name___.kit.LSP.Position\nfunction Position.cursor(encoding)\n  encoding = encoding or Position.Encoding.UTF16\n  local cursor = vim.api.nvim_win_get_cursor(0)\n  local text = vim.api.nvim_get_current_line()\n\n  local utf8 = { line = cursor[1] - 1, character = cursor[2] }\n  if encoding == Position.Encoding.UTF8 then\n    return utf8\n  elseif encoding == Position.Encoding.UTF16 then\n    return Position.to_utf16(text, utf8, Position.Encoding.UTF8)\n  elseif encoding == Position.Encoding.UTF32 then\n    return Position.to_utf32(text, utf8, Position.Encoding.UTF8)\n  end\nend\n\n---Convert position to utf8 from specified encoding.\n---@param expr string|integer\n---@param position ___plugin_name___.kit.LSP.Position\n---@param from_encoding ___plugin_name___.kit.LSP.Position.Encoding\n---@return ___plugin_name___.kit.LSP.Position\nfunction Position.to_vim(expr, position, from_encoding)\n  if from_encoding == Position.Encoding.UTF8 then\n    return position\n  end\n  local text = Buffer.at(expr, position.line)\n  if from_encoding == Position.Encoding.UTF16 then\n    return Position.to_utf8(text, position, Position.Encoding.UTF16)\n  elseif from_encoding == Position.Encoding.UTF32 then\n    return Position.to_utf8(text, position, Position.Encoding.UTF32)\n  end\nend\n\n---Convert position to utf8 from specified encoding.\n---@param text string\n---@param position ___plugin_name___.kit.LSP.Position\n---@param from_encoding? ___plugin_name___.kit.LSP.Position.Encoding\n---@return ___plugin_name___.kit.LSP.Position\nfunction Position.to_utf8(text, position, from_encoding)\n  from_encoding = from_encoding or Position.Encoding.UTF16\n\n  if from_encoding == Position.Encoding.UTF8 then\n    return position\n  end\n\n  local ok, byteindex = pcall(function()\n    return vim.str_byteindex(text, position.character, from_encoding == Position.Encoding.UTF16)\n  end)\n  if not ok then\n    return position\n  end\n  return { line = position.line, character = byteindex }\nend\n\n---Convert position to utf16 from specified encoding.\n---@param text string\n---@param position ___plugin_name___.kit.LSP.Position\n---@param from_encoding? ___plugin_name___.kit.LSP.Position.Encoding\n---@return ___plugin_name___.kit.LSP.Position\nfunction Position.to_utf16(text, position, from_encoding)\n  local utf8 = Position.to_utf8(text, position, from_encoding)\n  for index = utf8.character, 0, -1 do\n    local ok, utf16index = pcall(function()\n      return select(2, vim.str_utfindex(text, index))\n    end)\n    if ok then\n      return { line = utf8.line, character = utf16index }\n    end\n  end\n  return position\nend\n\n---Convert position to utf32 from specified encoding.\n---@param text string\n---@param position ___plugin_name___.kit.LSP.Position\n---@param from_encoding? ___plugin_name___.kit.LSP.Position.Encoding\n---@return ___plugin_name___.kit.LSP.Position\nfunction Position.to_utf32(text, position, from_encoding)\n  local utf8 = Position.to_utf8(text, position, from_encoding)\n  for index = utf8.character, 0, -1 do\n    local ok, utf32index = pcall(function()\n      return select(1, vim.str_utfindex(text, index))\n    end)\n    if ok then\n      return { line = utf8.line, character = utf32index }\n    end\n  end\n  return position\nend\n\nreturn Position\n"
  },
  {
    "path": "lua/translate/kit/LSP/Position.spec.lua",
    "content": "local Position = require(\"___plugin_name___.kit.LSP.Position\")\n\ndescribe(\"kit.LSP.Position\", function()\n  local text = \"🗿🗿🗿\"\n  local utf8 = #text\n  local utf16 = select(2, vim.str_utfindex(text, utf8))\n  local utf32 = select(1, vim.str_utfindex(text, utf8))\n\n  before_each(function()\n    vim.cmd(([[\n      enew!\n      set noswapfile\n      call setline(1, ['%s'])\n    ]]):format(text))\n  end)\n\n  for _, to in ipairs({\n    {\n      method = \"to_utf8\",\n      encoding = Position.Encoding.UTF8,\n      character = utf8,\n    },\n    {\n      method = \"to_utf16\",\n      encoding = Position.Encoding.UTF16,\n      character = utf16,\n    },\n    {\n      method = \"to_utf32\",\n      encoding = Position.Encoding.UTF32,\n      character = utf32,\n    },\n  }) do\n    for _, from in ipairs({\n      { character = utf8, encoding = Position.Encoding.UTF8 },\n      { character = utf16, encoding = Position.Encoding.UTF16 },\n      { character = utf32, encoding = Position.Encoding.UTF32 },\n    }) do\n      it((\"should convert %s <- %s\"):format(to.encoding, from.encoding), function()\n        local converted = Position[to.method](text, { line = 1, character = from.character }, from.encoding)\n        assert.are.same(to.character, converted.character)\n      end)\n    end\n  end\nend)\n"
  },
  {
    "path": "lua/translate/kit/LSP/Range.lua",
    "content": "local Position = require(\"___plugin_name___.kit.LSP.Position\")\n\n---@class ___plugin_name___.kit.LSP.Range\n---@field public start ___plugin_name___.kit.LSP.Position\n---@field public ['end'] ___plugin_name___.kit.LSP.Position\n\nlocal Range = {}\n\n---Return the value is range or not.\n---@param v any\n---@return boolean\nfunction Range.is(range)\n  return type(range) == \"table\" and Position.is(range.start) and Position.is(range[\"end\"])\nend\n\n---Return the range is empty or not.\n---@param range ___plugin_name___.kit.LSP.Range\n---@return boolean\nfunction Range.empty(range)\n  return range.start.line == range[\"end\"].line and range.start.character == range[\"end\"].character\nend\n\n---Convert range to utf8 from specified encoding.\n---@param expr string|integer\n---@param range ___plugin_name___.kit.LSP.Range\n---@param from_encoding ___plugin_name___.kit.LSP.Position.Encoding\n---@return ___plugin_name___.kit.LSP.Range\nfunction Range.to_vim(expr, range, from_encoding)\n  return {\n    start = Position.to_vim(expr, range.start, from_encoding),\n    [\"end\"] = Position.to_vim(expr, range[\"end\"], from_encoding),\n  }\nend\n\nreturn Range\n"
  },
  {
    "path": "lua/translate/kit/LSP/Range.spec.lua",
    "content": "local Range = require(\"___plugin_name___.kit.LSP.Range\")\n\ndescribe(\"kit.LSP.Range\", function()\n  it(\"should return the range is empty or not\", function()\n    local position1 = { line = 0, character = 0 }\n    local position2 = { line = 0, character = 1 }\n    assert.are.equal(Range.empty({ start = position1, [\"end\"] = position1 }), true)\n    assert.are.equal(Range.empty({ start = position1, [\"end\"] = position2 }), false)\n  end)\nend)\n"
  },
  {
    "path": "lua/translate/kit/Lua/TreeSitter.lua",
    "content": "local TreeSitter = {}\n\n---@alias ___plugin_name___.kit.Lua.TreeSitter.VisitStatus 'stop'|'skip'\nTreeSitter.VisitStatus = {}\nTreeSitter.VisitStatus.Stop = \"stop\"\nTreeSitter.VisitStatus.Skip = \"skip\"\n\n---Get the leaf node at the specified position.\n---@param row integer # 0-based\n---@param col integer # 0-based\n---@return userdata?\nfunction TreeSitter.get_node_at(row, col)\n  local parser = TreeSitter.get_parser()\n  if not parser then\n    return\n  end\n\n  for _, tree in ipairs(parser:trees()) do\n    local node = tree:root():descendant_for_range(row, col, row, col)\n    if node then\n      local leaf = TreeSitter.get_first_leaf(node)\n      if leaf then\n        return leaf\n      end\n    end\n  end\nend\n\n---Get first leaf node within the specified node.\n---@param node userdata\n---@return userdata?\nfunction TreeSitter.get_first_leaf(node)\n  if node:child_count() > 0 then\n    return TreeSitter.get_first_leaf(node:child(0))\n  end\n  return node\nend\n\n---Get last leaf node within the specified node.\n---@param node userdata\n---@return userdata?\nfunction TreeSitter.get_last_leaf(node)\n  if node:child_count() > 0 then\n    return TreeSitter.get_last_leaf(node:child(node:child_count() - 1))\n  end\n  return node\nend\n\n---Get next leaf node.\n---@param node userdata\n---@return userdata?\nfunction TreeSitter.get_next_leaf(node)\n  local function next(node_)\n    local next_sibling = node_:next_sibling()\n    if next_sibling then\n      return TreeSitter.get_first_leaf(next_sibling)\n    else\n      local parent = node_:parent()\n      while parent do\n        next_sibling = parent:next_sibling()\n        if next_sibling then\n          return TreeSitter.get_first_leaf(next_sibling)\n        end\n        parent = parent:parent()\n      end\n    end\n  end\n\n  return next(TreeSitter.get_first_leaf(node))\nend\n\n---Get prev leaf node.\n---@param node userdata\n---@return userdata\nfunction TreeSitter.get_prev_leaf(node)\n  local function prev(node_)\n    local prev_sibling = node_:prev_sibling()\n    if prev_sibling then\n      return TreeSitter.get_last_leaf(prev_sibling)\n    else\n      local parent = node_:parent()\n      while parent do\n        prev_sibling = parent:prev_sibling()\n        if prev_sibling then\n          return TreeSitter.get_last_leaf(prev_sibling)\n        end\n        parent = parent:parent()\n      end\n    end\n  end\n\n  return prev(TreeSitter.get_last_leaf(node))\nend\n\n---Return the node contained the position or not.\n---@param node userdata\n---@param row integer # 0-based\n---@param col integer # 0-based\n---@param option { s: boolean, e: boolean }\n---@return boolean\nfunction TreeSitter.within(node, row, col, option)\n  option = option or {}\n  option.s = option.s ~= nil and option.s or true\n  option.e = option.e ~= nil and option.e or false\n\n  local s_row, s_col, e_row, e_col = node:range()\n  local s_in = s_row < row or (s_row == row and (option.s and (s_col <= col) or (s_col < col)))\n  local e_in = row < e_row or (row == e_row and (option.e and (col <= e_col) or (col < e_col)))\n  return s_in and e_in\nend\n\n---Extract nodes that matched the specified mapping.\n---@param scope userdata\n---@param mapping table\n---@return userdata[]\nfunction TreeSitter.extract(scope, mapping)\n  local nodes = {}\n  for node_type, next_mapping in pairs(mapping) do\n    if node_type == scope:type() then\n      if type(next_mapping) == \"table\" then\n        for c in scope:iter_children() do\n          for _, node in ipairs(TreeSitter.extract(c, next_mapping)) do\n            table.insert(nodes, node)\n          end\n        end\n      elseif next_mapping == true then\n        table.insert(nodes, scope)\n      end\n    end\n  end\n  return nodes\nend\n\n---Return the node is matched the specified mapping.\n---@param node userdata\n---@param mapping table\n---@return userdata?\nfunction TreeSitter.matches(node, mapping)\n  local parent = node\n  while parent do\n    if vim.tbl_contains(TreeSitter.extract(parent, mapping), node) then\n      return parent\n    end\n    parent = parent:parent()\n  end\nend\n\n---Search next specific node.\n---@param node userdata\n---@param predicate fun(node: userdata): boolean\n---@return userdata?\nfunction TreeSitter.search_next(node, predicate)\n  local current = node\n  while current do\n    -- down search.\n    local matched = nil\n    TreeSitter.visit(current, function(node_)\n      if node ~= node_ and predicate(node_) then\n        matched = node_\n        return TreeSitter.VisitStatus.Stop\n      end\n    end)\n    if matched then\n      return matched\n    end\n\n    -- up search.\n    while current do\n      local next_sibling = current:next_sibling()\n      if next_sibling then\n        current = next_sibling\n        break\n      end\n      current = current:parent()\n    end\n  end\nend\n\n---Search specific parent node.\n---@param node userdata\n---@param predicate fun(node: userdata): boolean\n---@return userdata?\nfunction TreeSitter.search_parent(node, predicate)\n  local parent = node:parent()\n  while parent do\n    if predicate(parent) then\n      return parent\n    end\n    parent = parent:parent()\n  end\nend\n\n---Get all parents.\n---@param node userdata\n---@return userdata[]\nfunction TreeSitter.parents(node)\n  local parents = {}\n  while node do\n    table.insert(parents, 1, node)\n    node = node:parent()\n  end\n  return parents\nend\n\n---Visit all nodes.\n---@param scope userdata\n---@param predicate fun(node: userdata, ctx: { depth: integer }): boolean\n---@param option? { reversed: boolean }\nfunction TreeSitter.visit(scope, predicate, option)\n  option = option or { reversed = false }\n\n  local function visit(node, ctx)\n    if not node then\n      return true\n    end\n\n    local status = predicate(node, ctx)\n    if status == TreeSitter.VisitStatus.Stop then\n      return status -- stop visitting.\n    elseif status ~= TreeSitter.VisitStatus.Skip then\n      local init, last, step\n      if option.reversed then\n        init, last, step = node:child_count() - 1, 0, -1\n      else\n        init, last, step = 0, node:child_count() - 1, 1\n      end\n      for i = init, last, step do\n        if visit(node:child(i), { depth = ctx.depth + 1 }) == TreeSitter.VisitStatus.Stop then\n          return TreeSitter.VisitStatus.Stop\n        end\n      end\n    end\n  end\n\n  return visit(scope, { depth = 1 })\nend\n\n---Return the node is matched the specified capture.\n---@param query userdata\n---@param node userdata\n---@return boolean\nfunction TreeSitter.is_capture(query, node, capture)\n  for id, match in query:iter_captures(node:parent()) do\n    if match:id() == node:id() and query.captures[id] == capture then\n      return true\n    end\n  end\n  return false\nend\n\n---Get node text.\n---@param node userdata\n---@return string[]\nfunction TreeSitter.get_node_text(node)\n  local ok, text = pcall(function()\n    local args = { 0, node:range() }\n    table.insert(args, {})\n    return vim.api.nvim_buf_get_text(unpack(args))\n  end)\n  if not ok then\n    return { \"\" }\n  end\n  return text\nend\n\n---Get parser.\n---@return table\nfunction TreeSitter.get_parser()\n  return vim.treesitter.get_parser(0, vim.api.nvim_buf_get_option(0, \"filetype\"))\nend\n\n---Dump node or node-table.\n---@param node userdata|userdata[]\nfunction TreeSitter.dump(node)\n  if not node then\n    return print(node)\n  end\n\n  if type(node) == \"table\" then\n    if #node == 0 then\n      return print(\"empty table\")\n    end\n    for _, v in ipairs(node) do\n      TreeSitter.dump(v)\n    end\n    return\n  end\n\n  local message = node:type()\n  local current = node:parent()\n  while current do\n    message = current:type() .. \" ~ \" .. message\n    current = current:parent()\n    if not current then\n      break\n    end\n  end\n  print(message)\nend\n\nreturn TreeSitter\n"
  },
  {
    "path": "lua/translate/kit/Lua/TreeSitter.spec.lua",
    "content": "---@diagnostic disable: need-check-nil, param-type-mismatch\nlocal helper = require(\"kit.helper\")\nlocal TreeSitter = require(\"___plugin_name___.kit.Lua.TreeSitter\")\n\ndescribe(\"kit.Lua.TreeSitter\", function()\n  before_each(function()\n    vim.cmd([[\n      enew!\n      syntax off\n      set filetype=lua\n      call setline(1, [\n      \\   'function A()',\n      \\   '  return 1',\n      \\   'end',\n      \\   'if \"then\" then',\n      \\   '  print(a())',\n      \\   'elseif \"else if\" then',\n      \\   '  print(a())',\n      \\   'elseif \"else if\" then',\n      \\   '  if \"then\" then',\n      \\   '    return 1',\n      \\   '  end',\n      \\   'else',\n      \\   '  print(a())',\n      \\   'end',\n      \\ ])\n    ]])\n  end)\n\n  describe(\"get_next_leaf & get_prev_leaf\", function()\n    it(\"should return all leaves\", function()\n      local current, lines = nil, vim.api.nvim_buf_get_lines(0, 0, -1, false)\n\n      current = TreeSitter.get_node_at(0, 0)\n      local next_leaves = {}\n      while current do\n        table.insert(next_leaves, TreeSitter.get_node_text(current))\n        current = TreeSitter.get_next_leaf(current)\n      end\n\n      current = TreeSitter.get_node_at(#lines - 1, #lines[#lines] - 1)\n      local prev_leaves = {}\n      while current do\n        table.insert(prev_leaves, 1, TreeSitter.get_node_text(current))\n        current = TreeSitter.get_prev_leaf(current)\n      end\n\n      assert.are.same(next_leaves, prev_leaves)\n    end)\n  end)\n\n  describe(\"get_captures\", function()\n    it(\"should return all captured name\", function()\n      vim.treesitter.set_query(\n        \"lua\",\n        \"pairs\",\n        [[\n        [\n          (function_declaration [\n            (\"function\" @pair)\n            (\"end\" @pair)\n          ])\n        ] @pair_context\n      ]]\n      )\n      local node = TreeSitter.get_node_at(0, 0)\n      assert.is_true(TreeSitter.is_capture(vim.treesitter.get_query(\"lua\", \"pairs\"), node, \"pair\"))\n    end)\n  end)\nend)\n"
  },
  {
    "path": "lua/translate/kit/Lua/init.lua",
    "content": "local Lua = {}\n\n---Create gabage collection detector.\n---@param callback fun(...: any): any\n---@return userdata\nfunction Lua.gc(callback)\n  local gc = newproxy(true)\n  getmetatable(gc).__gc = callback\n  return gc\nend\n\nreturn Lua\n"
  },
  {
    "path": "lua/translate/kit/Lua/init.spec.lua",
    "content": "local Lua = require(\"___plugin_name___.kit.Lua\")\n\ndescribe(\"kit.Lua\", function()\n  it(\"should detect gc timing.\", function()\n    local called = false\n    local object = {\n      marker = Lua.gc(function()\n        called = true\n      end),\n    }\n    object = nil\n    collectgarbage(\"collect\")\n    assert.are.equals(object, nil)\n    assert.are.equals(called, true)\n  end)\nend)\n"
  },
  {
    "path": "lua/translate/kit/Vim/Buffer.lua",
    "content": "local kit = require(\"___plugin_name___.kit\")\nlocal Highlight = require(\"___plugin_name___.kit.Vim.Highlight\")\n\nlocal Buffer = {}\n\n---Ensure buffer number.\n---NOTE: This function only supports '%' as special symbols.\n---NOTE: This function uses `vim.fn.bufload`. It can cause side-effect.\n---@param expr string|number\n---@return number\nfunction Buffer.ensure(expr)\n  if type(expr) == \"number\" then\n    if not vim.api.nvim_buf_is_valid(expr) then\n      error(string.format([=[[kit.Vim.Buffer] expr=`%s` is not a valid]=], expr))\n    end\n  else\n    if expr == \"%\" then\n      expr = vim.api.nvim_get_current_buf()\n    end\n    if vim.fn.bufexists(expr) == 0 then\n      expr = vim.fn.bufadd(expr)\n      vim.api.nvim_buf_set_option(expr, \"buflisted\", true)\n    else\n      expr = vim.fn.bufnr(expr)\n    end\n  end\n  if not vim.api.nvim_buf_is_loaded(expr) then\n    vim.fn.bufload(expr)\n  end\n  return expr\nend\n\n---Get buffer line.\n---@param expr string|number\n---@param line number\n---@return string\nfunction Buffer.at(expr, line)\n  return vim.api.nvim_buf_get_lines(Buffer.ensure(expr), line, line + 1, false)[1] or \"\"\nend\n\n---Open buffer.\n---@param cmd table # The `new` command argument. See :help nvim_parse_cmd()`\n---@param range? ___plugin_name___.kit.Vim.Range\nfunction Buffer.open(cmd, range)\n  vim.cmd.new(cmd)\n\n  local Range = require(\"___plugin_name___.kit.LSP.Range\")\n  if Range.is(range) and not Range.empty(range) then\n    vim.api.nvim_win_set_cursor(0, { range.start.line + 1, range.start.character })\n    Highlight.blink(range)\n  end\nend\n\nreturn Buffer\n"
  },
  {
    "path": "lua/translate/kit/Vim/Buffer.spec.lua",
    "content": "local Buffer = require(\"___plugin_name___.kit.Vim.Buffer\")\n\ndescribe(\"kit.Vim.Buffer\", function()\n  before_each(function()\n    vim.cmd([[\n      enew!\n      set noswapfile\n    ]])\n  end)\n\n  it(\"should ensure bufnr via didn't loaded filename\", function()\n    local buf = Buffer.ensure(vim.api.nvim_get_runtime_file(\"syntax/markdown.vim\", true)[1])\n    assert.are.equal(vim.api.nvim_buf_get_option(buf, \"buflisted\"), true)\n    assert.are.equal(vim.api.nvim_buf_is_valid(buf), true)\n    assert.are.equal(vim.api.nvim_buf_is_loaded(buf), true)\n    assert.are.equal(#vim.api.nvim_buf_get_lines(buf, 0, -1, true), 169)\n  end)\n\n  it(\"should ensure bufnr via pseudo filename\", function()\n    local buf = Buffer.ensure(\"this-file-is-not-exists\")\n    assert.are.equal(vim.api.nvim_buf_get_option(buf, \"buflisted\"), true)\n    assert.are.equal(vim.api.nvim_buf_is_valid(buf), true)\n    assert.are.equal(vim.api.nvim_buf_is_loaded(buf), true)\n    assert.are.equal(#vim.api.nvim_buf_get_lines(buf, 0, -1, true), 1)\n  end)\n\n  it(\"should ensure bufnr via existing buffer\", function()\n    local org = vim.api.nvim_get_current_buf()\n    local buf = Buffer.ensure(org)\n    assert.are.equal(org, buf)\n    assert.are.equal(vim.api.nvim_buf_get_option(buf, \"buflisted\"), true)\n    assert.are.equal(vim.api.nvim_buf_is_valid(buf), true)\n    assert.are.equal(vim.api.nvim_buf_is_loaded(buf), true)\n    assert.are.equal(#vim.api.nvim_buf_get_lines(buf, 0, -1, true), 1)\n  end)\nend)\n"
  },
  {
    "path": "lua/translate/kit/Vim/Highlight.lua",
    "content": "local kit = require(\"___plugin_name___.kit\")\nlocal Async = require(\"___plugin_name___.kit.Async\")\nlocal AsyncTask = require(\"___plugin_name___.kit.Async.AsyncTask\")\n\nlocal Highlight = {}\n\nHighlight.namespace = vim.api.nvim_create_namespace(\"___plugin_name___.kit.Vim.Highlight\")\n\n---Blink specified range.\n---@param range ___plugin_name___.kit.LSP.Range\n---@param option? { delay: integer, count: integer }\n---@return ___plugin_name___.kit.Async.AsyncTask\nfunction Highlight.blink(range, option)\n  option = kit.merge(option or {}, {\n    delay = 150,\n    count = 2,\n  })\n\n  local function timeout(timeout)\n    return AsyncTask.new(function(resolve)\n      vim.defer_fn(vim.schedule_wrap(resolve), timeout)\n    end)\n  end\n\n  return Async.run(function()\n    Async.await(timeout(option.delay * 1.2))\n    for i = 1, option.count do\n      vim.highlight.range(\n        0,\n        Highlight.namespace,\n        \"IncSearch\",\n        { range.start.line, range.start.character },\n        { range[\"end\"].line, range[\"end\"].character },\n        {}\n      )\n      Async.await(timeout(option.delay * 0.8))\n      vim.api.nvim_buf_clear_namespace(0, Highlight.namespace, 0, -1)\n      Async.await(timeout(option.delay))\n    end\n  end)\nend\n\nreturn Highlight\n"
  },
  {
    "path": "lua/translate/kit/Vim/Highlight.spec.lua",
    "content": "local Highlight = require(\"___plugin_name___.kit.Vim.Highlight\")\n\ndescribe(\"kit.Vim.Highlight\", function()\n  it(\"should not throw error\", function()\n    Highlight.blink({\n      start = { line = 0, character = 0 },\n      [\"end\"] = { line = 0, character = 0 },\n    }):sync()\n  end)\nend)\n"
  },
  {
    "path": "lua/translate/kit/Vim/Keymap.lua",
    "content": "local AsyncTask = require(\"___plugin_name___.kit.Async.AsyncTask\")\n\nlocal Keymap = {}\n\nKeymap._callbacks = {}\n\n---Replace termcodes.\n---@param keys string\n---@return string\nfunction Keymap.termcodes(keys)\n  return vim.api.nvim_replace_termcodes(keys, true, true, true)\nend\n\n---Send keys.\n---@param keys string\n---@param mode string\nfunction Keymap.send(keys, mode)\n  local callback = Keymap.termcodes('<Cmd>lua require(\"___plugin_name___.kit.Vim.Keymap\")._resolve()<CR>')\n  return AsyncTask.new(function(resolve)\n    table.insert(Keymap._callbacks, resolve)\n    if string.match(mode, \"i\") then\n      vim.api.nvim_feedkeys(callback, \"in\", true)\n      vim.api.nvim_feedkeys(keys, mode, true)\n    else\n      vim.api.nvim_feedkeys(keys, mode, true)\n      vim.api.nvim_feedkeys(callback, \"n\", true)\n    end\n  end)\nend\n\n---Test spec helper.\n---@param spec fun(): any\nfunction Keymap.spec(spec)\n  local task = AsyncTask.resolve():next(spec)\n  vim.api.nvim_feedkeys(\"\", \"x\", true)\n  task:sync()\n  collectgarbage(\"collect\")\nend\n\n---Resolve running keys.\nfunction Keymap._resolve()\n  table.remove(Keymap._callbacks, 1)()\nend\n\nreturn Keymap\n"
  },
  {
    "path": "lua/translate/kit/Vim/Keymap.spec.lua",
    "content": "local Async = require(\"___plugin_name___.kit.Async\")\nlocal Keymap = require(\"___plugin_name___.kit.Vim.Keymap\")\n\nlocal async = Async.async\nlocal await = Async.await\n\ndescribe(\"kit.Vim.Keymap\", function()\n  it(\"should insert keysequence with async-await\", function()\n    vim.keymap.set(\n      \"i\",\n      \"<Plug>(kit.Vim.Keymap.send)\",\n      async(function()\n        await(Keymap.send(\"foo\", \"in\"))\n        await(Keymap.send(\"bar\", \"in\"))\n        await(Keymap.send(\"baz\", \"in\"))\n      end)\n    )\n    Keymap.spec(async(function()\n      await(Keymap.send(Keymap.termcodes(\"i{<Plug>(kit.Vim.Keymap.send)}\"), \"i\"))\n    end))\n    --NOTE: The `i` flag works only first time.\n    assert.are.equals(vim.api.nvim_get_current_line(), \"{foo}barbaz\")\n  end)\nend)\n"
  },
  {
    "path": "lua/translate/kit/Vim/Syntax.lua",
    "content": "local kit = require(\"___plugin_name___.kit\")\n\nlocal Syntax = {}\n\n---Get all syntax groups for specified position.\n---NOTE: This function accepts 0-origin cursor position.\n---@param cursor number[]\n---@return string[]\nfunction Syntax.get_syntax_groups(cursor)\n  return kit.concat(Syntax.get_vim_syntax_groups(cursor), Syntax.get_treesitter_syntax_groups(cursor))\nend\n\n---Get vim's syntax groups for specified position.\n---NOTE: This function accepts 0-origin cursor position.\n---@param cursor number[]\n---@return string[]\nfunction Syntax.get_vim_syntax_groups(cursor)\n  local groups = {}\n  for _, syntax_id in ipairs(vim.fn.synstack(cursor[1] + 1, cursor[2] + 1)) do\n    table.insert(groups, vim.fn.synIDattr(vim.fn.synIDtrans(syntax_id), \"name\"))\n  end\n  return groups\nend\n\n---Get tree-sitter's syntax groups for specified position.\n---NOTE: This function accepts 0-origin cursor position.\n---@param cursor number[]\n---@return string[]\nfunction Syntax.get_treesitter_syntax_groups(cursor)\n  local groups = {}\n  for _, capture in ipairs(vim.treesitter.get_captures_at_pos(0, cursor[1], cursor[2])) do\n    table.insert(groups, (\"@%s\"):format(capture.capture))\n  end\n  return groups\nend\n\nreturn Syntax\n"
  },
  {
    "path": "lua/translate/kit/Vim/Syntax.spec.lua",
    "content": "local helper = require(\"kit.helper\")\nlocal Syntax = require(\"___plugin_name___.kit.Vim.Syntax\")\n\ndescribe(\"kit.Vim.Syntax\", function()\n  before_each(function()\n    vim.cmd([[\n      enew!\n      set filetype=vim\n      call setline(1, ['let var = 1'])\n    ]])\n  end)\n\n  it(\"should return vim syntax group\", function()\n    vim.cmd([[ syntax on ]])\n    assert.are.same(Syntax.get_syntax_groups({ 0, 3 }), {})\n    assert.are.same(Syntax.get_syntax_groups({ 0, 4 }), { \"Identifier\" })\n    assert.are.same(Syntax.get_syntax_groups({ 0, 6 }), { \"Identifier\" })\n    assert.are.same(Syntax.get_syntax_groups({ 0, 7 }), {})\n  end)\n\n  it(\"should return treesitter syntax group\", function()\n    helper.ensure_treesitter_parser(\"vim\")\n    vim.cmd([[ syntax off ]])\n    assert.are.same(Syntax.get_syntax_groups({ 0, 3 }), {})\n    assert.are.same(Syntax.get_syntax_groups({ 0, 4 }), { \"@variable\" })\n    assert.are.same(Syntax.get_syntax_groups({ 0, 6 }), { \"@variable\" })\n    assert.are.same(Syntax.get_syntax_groups({ 0, 7 }), {})\n  end)\nend)\n"
  },
  {
    "path": "lua/translate/kit/init.lua",
    "content": "--[[\nMIT License\n\nCopyright (c) 2022 hrsh7th\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n]]\n--\n\nlocal kit = {}\n\n---Create unique id.\n---@return integer\nkit.uuid = setmetatable({\n  uuid = 0,\n}, {\n  __call = function(self)\n    self.uuid = self.uuid + 1\n    return self.uuid\n  end,\n})\n\n\n-- https://neovim.io/doc/user/deprecated.html#vim.tbl_islist()\nlocal islist = vim.islist or vim.tbl_islist\n\n---Merge two tables.\n---@generic T\n---NOTE: This doesn't merge array-like table.\n---@param tbl1 T\n---@param tbl2 T\n---@return T\nfunction kit.merge(tbl1, tbl2)\n  local is_dict1 = type(tbl1) == \"table\" and (not islist(tbl1) or vim.tbl_isempty(tbl1))\n  local is_dict2 = type(tbl2) == \"table\" and (not islist(tbl2) or vim.tbl_isempty(tbl2))\n  if is_dict1 and is_dict2 then\n    local new_tbl = {}\n    for k, v in pairs(tbl2) do\n      if tbl1[k] ~= vim.NIL then\n        new_tbl[k] = kit.merge(tbl1[k], v)\n      end\n    end\n    for k, v in pairs(tbl1) do\n      if tbl2[k] == nil then\n        new_tbl[k] = v ~= vim.NIL and v or nil\n      end\n    end\n    return new_tbl\n  elseif is_dict1 and not is_dict2 then\n    return kit.merge(tbl1, {})\n  elseif not is_dict1 and is_dict2 then\n    return kit.merge(tbl2, {})\n  end\n\n  if tbl1 == vim.NIL then\n    return nil\n  elseif tbl1 == nil then\n    return tbl2\n  else\n    return tbl1\n  end\nend\n\n---Concatenate two tables.\n---NOTE: This doesn't concatenate dict-like table.\n---@param tbl1 table\n---@param tbl2 table\nfunction kit.concat(tbl1, tbl2)\n  local new_tbl = {}\n  for _, item in ipairs(tbl1) do\n    table.insert(new_tbl, item)\n  end\n  for _, item in ipairs(tbl2) do\n    table.insert(new_tbl, item)\n  end\n  return new_tbl\nend\n\n---The value to array.\n---@param value any\n---@return table\nfunction kit.to_array(value)\n  if type(value) == \"table\" then\n    if islist(value) or vim.tbl_isempty(value) then\n      return value\n    end\n  end\n  return { value }\nend\n\n---Check the value is array.\n---@param value any\n---@return boolean\nfunction kit.is_array(value)\n  return type(value) == \"table\" and (islist(value) or vim.tbl_isempty(value))\nend\n\n---Reverse the array.\n---@param array table\n---@return table\nfunction kit.reverse(array)\n  if not kit.is_array(array) then\n    error(\"[kit] specified value is not an array.\")\n  end\n\n  local new_array = {}\n  for i = #array, 1, -1 do\n    table.insert(new_array, array[i])\n  end\n  return new_array\nend\n\n---Map array values.\n---@generic T\n---@param array T[]\n---@parma func fun(item: T, index: number): V\n---@reutrn T[]\nfunction kit.map(array, func)\n  local new_array = {}\n  for i, item in ipairs(array) do\n    table.insert(new_array, func(item, i))\n  end\n  return new_array\nend\n\nreturn kit\n"
  },
  {
    "path": "lua/translate/kit/init.spec.lua",
    "content": "local kit = require(\"___plugin_name___.kit\")\n\ndescribe(\"kit\", function()\n  describe(\".merge\", function()\n    it(\"should merge two dict\", function()\n      assert.are.same(\n        kit.merge({\n          a = true,\n          b = {\n            c = vim.NIL,\n          },\n          d = {\n            e = 3,\n          },\n        }, {\n          a = false,\n          b = {\n            c = true,\n          },\n          d = {\n            f = {\n              g = vim.NIL,\n            },\n          },\n        }),\n        {\n          a = true,\n          b = {},\n          d = {\n            e = 3,\n            f = {},\n          },\n        }\n      )\n    end)\n  end)\n\n  describe(\".concat\", function()\n    it(\"should concat two list\", function()\n      assert.are.same(kit.concat({ 1, 2, 3 }, { 4, 5, 6 }), { 1, 2, 3, 4, 5, 6 })\n    end)\n  end)\n\n  describe(\".to_array\", function()\n    it(\"should convert value to array\", function()\n      assert.are.same(kit.to_array(1), { 1 })\n      assert.are.same(kit.to_array({ 1, 2, 3 }), { 1, 2, 3 })\n      assert.are.same(kit.to_array({}), {})\n      assert.are.same(kit.to_array({ a = 1 }), { { a = 1 } })\n    end)\n  end)\n\n  describe(\".is_array\", function()\n    it(\"should check array or not\", function()\n      assert.are.equal(kit.is_array({}), true)\n      assert.are.equal(kit.is_array({ 1 }), true)\n      assert.are.equal(kit.is_array({ a = 1 }), false)\n      assert.are.equal(kit.is_array(1), false)\n    end)\n  end)\n\n  describe(\".reverse\", function()\n    it(\"should reverse the array\", function()\n      assert.are.same(kit.reverse({ 1, 2, 3 }), { 3, 2, 1 })\n    end)\n  end)\n\n  describe(\".map\", function()\n    it(\"should map array values\", function()\n      local array = kit.map({ \"1\", \"2\", \"3\" }, function(v)\n        return tonumber(v, 10)\n      end)\n      assert.are.same(array, { 1, 2, 3 })\n    end)\n  end)\nend)\n"
  },
  {
    "path": "lua/translate/preset/command/deepl.lua",
    "content": "local M = {}\n\nlocal json_encode = vim.json and vim.json.encode or vim.fn.json_encode\n\n---@param url string\n---@param lines string[]\n---@param command_args table\n---@return string\n---@return string[]\nfunction M._cmd(url, lines, command_args)\n  if not vim.g.deepl_api_auth_key then\n    error(\"[translate.nvim] Set your DeepL API authorization key to g:deepl_api_auth_key.\")\n  end\n\n  local cmd = \"curl\"\n  local args = {\n    \"-X\",\n    \"POST\",\n    \"-s\",\n    url,\n    \"--header\",\n    \"Content-Type: application/json\",\n    \"--header\",\n    \"Authorization: DeepL-Auth-Key \" .. vim.g.deepl_api_auth_key,\n    \"--data\",\n    json_encode({\n      text = lines,\n      target_lang = command_args.target,\n      source_lang = command_args.source,\n    })\n  }\n\n  return cmd, args\nend\n\nfunction M.complete_list(is_target)\n  -- See <https://www.deepl.com/docs-api/translating-text/>\n  local list = {\n    \"BG\",\n    \"CS\",\n    \"DA\",\n    \"DE\",\n    \"EL\",\n    \"EN\",\n    \"ES\",\n    \"ET\",\n    \"FI\",\n    \"FR\",\n    \"HU\",\n    \"IT\",\n    \"JA\",\n    \"LT\",\n    \"LV\",\n    \"NL\",\n    \"PL\",\n    \"PT\",\n    \"RO\",\n    \"RU\",\n    \"SK\",\n    \"SL\",\n    \"SV\",\n    \"ZH\",\n  }\n\n  if is_target then\n    local append = {\n      \"EN-GB\",\n      \"EN-US\",\n      \"PT-PT\",\n      \"PT-BR\",\n    }\n    list = vim.list_extend(list, append)\n  end\n\n  return list\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/command/deepl_free.lua",
    "content": "local deepl = require(\"translate.preset.command.deepl\")\n\nlocal M = {}\n\n---@param lines string[]\n---@param command_args table\n---@return string cmd\n---@return string[] args\nfunction M.cmd(lines, command_args)\n  local url = \"https://api-free.deepl.com/v2/translate\"\n  local cmd, args = deepl._cmd(url, lines, command_args)\n\n  local options = require(\"translate.config\").get(\"preset\").command.deepl_free\n  if #options.args > 0 then\n    args = vim.list_extend(args, options.args)\n  end\n\n  return cmd, args\nend\n\nM.complete_list = deepl.complete_list\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/command/deepl_pro.lua",
    "content": "local deepl = require(\"translate.preset.command.deepl\")\n\nlocal M = {}\n\n---@param lines string[]\n---@param command_args table\n---@return string cmd\n---@return string[] args\nfunction M.cmd(lines, command_args)\n  local url = \"https://api.deepl.com/v2/translate\"\n  local cmd, args = deepl._cmd(url, lines, command_args)\n\n  local options = require(\"translate.config\").get(\"preset\").command.deepl_pro\n  if #options.args > 0 then\n    args = vim.list_extend(args, options.args)\n  end\n\n  return cmd, args\nend\n\nM.complete_list = deepl.complete_list\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/command/google.lua",
    "content": "local util = require(\"translate.util.util\")\n\nlocal M = {}\n\nM.url =\n  \"https://script.google.com/macros/s/AKfycbxLRZgWI3UyHvHuYVyH1StiXbzJDHyibO5XpVZm5kMlXFlzaFVtLReR0ZteEkUbecRpPQ/exec\"\n\n---@param lines string[]\n---@param command_args table\n---@return string\n---@return string[]\nfunction M.cmd(lines, command_args)\n  local data = vim.json.encode({\n    text = lines,\n    target = command_args.target,\n    source = command_args.source,\n  })\n  local cmd, args\n  if vim.fn.has(\"win32\") == 1 then\n    cmd = \"cmd.exe\"\n    local path = util.write_temp_data(data)\n    args = {\n      \"/c\",\n      table.concat({\n        \"curl\",\n        \"-sL\",\n        M.url,\n        \"-d\",\n        \"@\" .. path,\n      }, \" \"),\n    }\n  else\n    cmd = \"curl\"\n    args = {\n      \"-sL\",\n      M.url,\n      \"-d\",\n      data,\n    }\n  end\n\n  local options = require(\"translate.config\").get(\"preset\").command.google\n  if #options.args > 0 then\n    args = vim.list_extend(args, options.args)\n  end\n\n  return cmd, args\nend\n\nfunction M.complete_list()\n  -- See <https://cloud.google.com/translate/docs/languages>\n  local list = {\n    \"af\",\n    \"sq\",\n    \"am\",\n    \"ar\",\n    \"hy\",\n    \"az\",\n    \"eu\",\n    \"be\",\n    \"bn\",\n    \"bs\",\n    \"bg\",\n    \"ca\",\n    \"ceb\",\n    \"zh\",\n    \"zh-CN\",\n    \"zh-TW\",\n    \"co\",\n    \"hr\",\n    \"cs\",\n    \"da\",\n    \"nl\",\n    \"en\",\n    \"eo\",\n    \"et\",\n    \"fi\",\n    \"fr\",\n    \"fy\",\n    \"gl\",\n    \"ka\",\n    \"de\",\n    \"el\",\n    \"gu\",\n    \"ht\",\n    \"ha\",\n    \"haw\",\n    \"he\",\n    \"iw\",\n    \"hi\",\n    \"hmn\",\n    \"hu\",\n    \"is\",\n    \"ig\",\n    \"id\",\n    \"ga\",\n    \"it\",\n    \"ja\",\n    \"jv\",\n    \"kn\",\n    \"kk\",\n    \"km\",\n    \"rw\",\n    \"ko\",\n    \"ku\",\n    \"ky\",\n    \"lo\",\n    \"lv\",\n    \"lt\",\n    \"lb\",\n    \"mk\",\n    \"mg\",\n    \"ms\",\n    \"ml\",\n    \"mt\",\n    \"mi\",\n    \"mr\",\n    \"mn\",\n    \"my\",\n    \"ne\",\n    \"no\",\n    \"ny\",\n    \"or\",\n    \"ps\",\n    \"fa\",\n    \"pl\",\n    \"pt\",\n    \"pa\",\n    \"ro\",\n    \"ru\",\n    \"sm\",\n    \"gd\",\n    \"sr\",\n    \"st\",\n    \"sn\",\n    \"sd\",\n    \"si\",\n    \"sk\",\n    \"sl\",\n    \"so\",\n    \"es\",\n    \"su\",\n    \"sw\",\n    \"sv\",\n    \"tl\",\n    \"tg\",\n    \"ta\",\n    \"tt\",\n    \"te\",\n    \"th\",\n    \"tr\",\n    \"tk\",\n    \"uk\",\n    \"ur\",\n    \"ug\",\n    \"uz\",\n    \"vi\",\n    \"cy\",\n    \"xh\",\n    \"yi\",\n    \"yo\",\n    \"zu\",\n  }\n  return list\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/command/translate_shell.lua",
    "content": "local M = {}\n\n---@param lines string[]\n---@param command_args table\n---@return string\n---@return string[]\nfunction M.cmd(lines, command_args)\n  local text = table.concat(lines, \"\\n\")\n\n  local source = command_args.source or \"\"\n  local target = command_args.target\n\n  local cmd = \"trans\"\n  local args = {\n    \"-b\",\n    \"-no-ansi\",\n    \"-no-autocorrect\",\n  }\n\n  local options = require(\"translate.config\").get(\"preset\").command.translate_shell\n  if #options.args > 0 then\n    args = vim.list_extend(args, options.args)\n  end\n\n  table.insert(args, source .. \":\" .. target)\n  table.insert(args, text)\n\n  return cmd, args\nend\n\nfunction M.complete_list()\n  -- See <https://github.com/soimort/translate-shell/wiki/Languages>\n  local list = {\n    \"af\",\n    \"afr\",\n    \"am\",\n    \"amh\",\n    \"ar\",\n    \"ara\",\n    \"az\",\n    \"aze\",\n    \"ba\",\n    \"bak\",\n    \"be\",\n    \"bel\",\n    \"bg\",\n    \"bul\",\n    \"bn\",\n    \"ben\",\n    \"bs\",\n    \"bos\",\n    \"ca\",\n    \"cat\",\n    \"ceb\",\n    \"ceb\",\n    \"co\",\n    \"cos\",\n    \"cs\",\n    \"ces\",\n    \"cy\",\n    \"cym\",\n    \"da\",\n    \"dan\",\n    \"de\",\n    \"deu\",\n    \"el\",\n    \"ell\",\n    \"en\",\n    \"eng\",\n    \"eo\",\n    \"epo\",\n    \"es\",\n    \"spa\",\n    \"et\",\n    \"est\",\n    \"eu\",\n    \"eus\",\n    \"fa\",\n    \"fas\",\n    \"fi\",\n    \"fin\",\n    \"fj\",\n    \"fij\",\n    \"fr\",\n    \"fra\",\n    \"fy\",\n    \"fry\",\n    \"ga\",\n    \"gle\",\n    \"gd\",\n    \"gla\",\n    \"gl\",\n    \"glg\",\n    \"gu\",\n    \"guj\",\n    \"ha\",\n    \"hau\",\n    \"haw\",\n    \"haw\",\n    \"he\",\n    \"heb\",\n    \"hi\",\n    \"hin\",\n    \"hmn\",\n    \"hmn\",\n    \"hr\",\n    \"hrv\",\n    \"ht\",\n    \"hat\",\n    \"hu\",\n    \"hun\",\n    \"hy\",\n    \"hye\",\n    \"id\",\n    \"ind\",\n    \"ig\",\n    \"ibo\",\n    \"is\",\n    \"isl\",\n    \"it\",\n    \"ita\",\n    \"ja\",\n    \"jpn\",\n    \"jv\",\n    \"jav\",\n    \"ka\",\n    \"kat\",\n    \"kk\",\n    \"kaz\",\n    \"km\",\n    \"khm\",\n    \"kn\",\n    \"kan\",\n    \"ko\",\n    \"kor\",\n    \"ku\",\n    \"kur\",\n    \"ky\",\n    \"kir\",\n    \"la\",\n    \"lat\",\n    \"lb\",\n    \"ltz\",\n    \"lo\",\n    \"lao\",\n    \"lt\",\n    \"lit\",\n    \"lv\",\n    \"lav\",\n    \"mg\",\n    \"mlg\",\n    \"mhr\",\n    \"mhr\",\n    \"mi\",\n    \"mri\",\n    \"mk\",\n    \"mkd\",\n    \"ml\",\n    \"mal\",\n    \"mn\",\n    \"mon\",\n    \"mr\",\n    \"mar\",\n    \"mrj\",\n    \"mrj\",\n    \"ms\",\n    \"msa\",\n    \"mt\",\n    \"mlt\",\n    \"mww\",\n    \"mww\",\n    \"my\",\n    \"mya\",\n    \"ne\",\n    \"nep\",\n    \"nl\",\n    \"nld\",\n    \"no\",\n    \"nor\",\n    \"ny\",\n    \"nya\",\n    \"or\",\n    \"ori\",\n    \"otq\",\n    \"otq\",\n    \"pa\",\n    \"pan\",\n    \"pap\",\n    \"pap\",\n    \"pl\",\n    \"pol\",\n    \"ps\",\n    \"pus\",\n    \"pt\",\n    \"por\",\n    \"ro\",\n    \"ron\",\n    \"ru\",\n    \"rus\",\n    \"rw\",\n    \"kin\",\n    \"sd\",\n    \"snd\",\n    \"si\",\n    \"sin\",\n    \"sk\",\n    \"slk\",\n    \"sl\",\n    \"slv\",\n    \"sm\",\n    \"smo\",\n    \"sn\",\n    \"sna\",\n    \"so\",\n    \"som\",\n    \"sq\",\n    \"sqi\",\n    \"sr-Cyrl\",\n    \"srp\",\n    \"sr-Latn\",\n    \"srp\",\n    \"st\",\n    \"sot\",\n    \"su\",\n    \"sun\",\n    \"sv\",\n    \"swe\",\n    \"sw\",\n    \"swa\",\n    \"ta\",\n    \"tam\",\n    \"te\",\n    \"tel\",\n    \"tg\",\n    \"tgk\",\n    \"th\",\n    \"tha\",\n    \"tk\",\n    \"tuk\",\n    \"tl\",\n    \"tgl\",\n    \"tlh\",\n    \"tlh\",\n    \"tlh-Qaak\",\n    \"tlh\",\n    \"to\",\n    \"ton\",\n    \"tr\",\n    \"tur\",\n    \"tt\",\n    \"tat\",\n    \"ty\",\n    \"tah\",\n    \"udm\",\n    \"udm\",\n    \"ug\",\n    \"uig\",\n    \"uk\",\n    \"ukr\",\n    \"ur\",\n    \"urd\",\n    \"uz\",\n    \"uzb\",\n    \"vi\",\n    \"vie\",\n    \"xh\",\n    \"xho\",\n    \"yi\",\n    \"yid\",\n    \"yo\",\n    \"yor\",\n    \"yua\",\n    \"yua\",\n    \"yue\",\n    \"yue\",\n    \"zh-CN\",\n    \"zho\",\n    \"zh-TW\",\n    \"zho\",\n    \"zu\",\n    \"zul\",\n  }\n\n  return list\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/output/floating.lua",
    "content": "local api = vim.api\n\nlocal util = require(\"translate.util.util\")\n\nlocal M = {\n  window = {},\n}\n\nfunction M.cmd(lines, _)\n  if type(lines) == \"string\" then\n    lines = { lines }\n  end\n\n  M.window.close()\n\n  local options = require(\"translate.config\").get(\"preset\").output.floating\n\n  local buf = api.nvim_create_buf(false, true)\n  api.nvim_buf_set_lines(buf, 0, -1, true, lines)\n  api.nvim_set_option_value(\"filetype\", options.filetype, { buf = buf })\n\n  local width = util.max_width_in_string_list(lines)\n  local height = #lines\n\n  local win = api.nvim_open_win(buf, false, {\n    relative = options.relative,\n    style = options.style,\n    width = width,\n    height = height,\n    row = options.row,\n    col = options.col,\n    border = options.border,\n    zindex = options.zindex,\n  })\n\n  M.window._current = { win = win, buf = buf }\n\n  api.nvim_create_autocmd(\"CursorMoved\", {\n    callback = M.window.close,\n    once = true,\n  })\nend\n\nfunction M.window.close()\n  if M.window._current then\n    api.nvim_win_close(M.window._current.win, false)\n    api.nvim_buf_delete(M.window._current.buf, {})\n    M.window._current = nil\n  end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/output/insert.lua",
    "content": "local api = vim.api\n\nlocal M = {}\n\nfunction M.cmd(lines, pos)\n  if type(lines) == \"string\" then\n    lines = { lines }\n  end\n\n  local lines_origin = pos._lines\n\n  for i, line in ipairs(lines) do\n    local p = pos[i]\n    local indent = string.rep(\" \", #lines_origin[i]:sub(1, p.col[1] - 1))\n    lines[i] = indent .. line\n  end\n\n  local options = require(\"translate.config\").get(\"preset\").output.insert\n\n  local row\n  if options.base == \"top\" then\n    row = pos[1].row\n  else -- \"bottom\"\n    row = pos[#pos].row\n  end\n\n  row = row + options.off\n\n  api.nvim_buf_set_lines(0, row, row, false, lines)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/output/register.lua",
    "content": "local fn = vim.fn\n\nlocal M = {}\n\n---Set the register\n---@param lines string[]\nfunction M.cmd(lines, _)\n  local newline\n  local ff = vim.o.fileformat\n  if ff == \"unix\" then\n    newline = \"\\n\"\n  elseif ff == \"dos\" then\n    newline = \"\\r\\n\"\n  else\n    newline = \"\\r\"\n  end\n\n  local text = table.concat(lines, newline)\n\n  local options = require(\"translate.config\").get(\"preset\").output.register\n  local name = options.name\n\n  fn.setreg(name, text)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/output/replace.lua",
    "content": "local api = vim.api\n\nlocal M = {}\n\nfunction M.cmd(lines, pos)\n  if type(lines) == \"string\" then\n    lines = { lines }\n  end\n\n  local lines_origin = pos._lines\n\n  for i, p in ipairs(pos) do\n    local pre = lines_origin[i]:sub(1, p.col[1] - 1)\n    local suf = lines_origin[i]:sub(p.col[2] + 1)\n\n    lines[i] = pre .. (lines[i] or \"\") .. suf\n  end\n\n  api.nvim_buf_set_lines(0, pos[1].row - 1, pos[#pos].row, true, lines)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/output/split.lua",
    "content": "local fn = vim.fn\nlocal api = vim.api\n\nlocal M = {}\n\nfunction M.cmd(lines, pos)\n  if type(lines) == \"string\" then\n    lines = { lines }\n  end\n\n  local lines_origin = pos._lines\n\n  -- Remain indentation\n  for i, line in ipairs(lines) do\n    local p = pos[i]\n    local indent = string.rep(\" \", #lines_origin[i]:sub(1, p.col[1] - 1))\n    lines[i] = indent .. line\n  end\n\n  local option = require(\"translate.config\").get(\"preset\").output.split\n\n  local size = M._get_size(#lines, option)\n\n  local function split_win()\n    local cmd = option.position == \"bottom\" and \"botright\" or \"topleft\"\n    cmd = cmd .. \" \" .. size .. \"new\"\n    vim.cmd(cmd)\n  end\n\n  local current_win_id = fn.win_getid()\n\n  if fn.bufexists(option.name) == 1 then\n    local bufnr = fn.bufnr(option.name)\n    local winid = fn.win_findbuf(bufnr)\n    -- Buffer is present, but window is closed\n    if vim.tbl_isempty(winid) then\n      split_win()\n      vim.cmd(\"e \" .. option.name)\n    else\n      fn.win_gotoid(winid[1])\n    end\n  else\n    split_win()\n    vim.cmd(\"e \" .. option.name)\n    api.nvim_set_option_value(\"buftype\", \"nofile\", { buf = 0 })\n    api.nvim_set_option_value(\"filetype\", option.filetype, { buf = 0 })\n  end\n\n  if option.append and not M._buf_empty() then\n    api.nvim_buf_set_lines(0, -1, -1, false, lines)\n  else\n    api.nvim_buf_set_lines(0, 0, -1, false, lines)\n  end\n\n  -- Move cursor to bottom\n  api.nvim_win_set_cursor(0, { fn.line(\"$\"), 0 })\n\n  fn.win_gotoid(current_win_id)\nend\n\nfunction M._buf_empty()\n  if fn.line(\"$\") ~= 1 then\n    return false\n  end\n\n  local line = fn.getline(1)\n  if line ~= \"\" then\n    return false\n  end\n\n  return true\nend\n\nfunction M._get_size(size, option)\n  local min_size = option.min_size\n  if min_size < 1 then\n    min_size = math.floor(api.nvim_win_get_height(0) * min_size)\n  end\n  local max_size = option.max_size\n  if max_size < 1 then\n    max_size = math.floor(api.nvim_win_get_height(0) * max_size)\n  end\n\n  if size <= min_size then\n    return min_size\n  end\n  if size >= max_size then\n    return max_size\n  end\n  return size\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/parse_after/deepl.lua",
    "content": "local M = {}\n\nlocal json_decode = vim.json and vim.json.decode or vim.fn.json_decode\n\n---@param response string #json string\n---@return string[]\nfunction M.cmd(response)\n  local decoded = json_decode(response)\n  local results = {}\n  for _, r in ipairs(decoded.translations) do\n    table.insert(results, r.text)\n  end\n  return results\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/parse_after/deepl_free.lua",
    "content": "return require(\"translate.preset.parse_after.deepl\")\n"
  },
  {
    "path": "lua/translate/preset/parse_after/deepl_pro.lua",
    "content": "return require(\"translate.preset.parse_after.deepl\")\n"
  },
  {
    "path": "lua/translate/preset/parse_after/google.lua",
    "content": "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",
    "content": "local api = vim.api\n\nlocal util = require(\"translate.util.util\")\n\nlocal M = {}\n\n---Cut the results of translation to fit the original width of the selection.\n---The width of the last line cannot be guaranteed because the number of characters changes.\n---@param lines string[]\n---@param pos table\n---@return string[]\nfunction M.cmd(lines, pos)\n  local results = {}\n\n  for i, text in ipairs(lines) do\n    local group = pos._group[i]\n\n    local widths_origin = {}\n    local sum_width_origin = 0\n    for _, g in ipairs(group) do\n      local width = api.nvim_strwidth(pos._lines_selected[g])\n      table.insert(widths_origin, width)\n      sum_width_origin = sum_width_origin + width\n    end\n    local sum_width_result = api.nvim_strwidth(text)\n\n    local widths = widths_origin\n    if sum_width_origin > sum_width_result then\n      local l = sum_width_origin\n      for j = #widths, 1, -1 do\n        local w = widths[j]\n        l = l - w\n        if l >= sum_width_result then\n          table.remove(widths, j)\n        else\n          widths[j] = sum_width_result - l\n          break\n        end\n      end\n    end\n\n    if #widths > 1 then\n      local result = util.text_cut(text, widths)\n      local diff = #group - #widths\n      if diff > 0 then\n        for _ = 1, diff do\n          table.insert(result, \"\")\n        end\n      end\n      results = vim.list_extend(results, result)\n    else\n      table.insert(results, text)\n    end\n  end\n\n  return results\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/parse_after/no_handle.lua",
    "content": "local M = {}\n\nfunction M.cmd(lines, _)\n  return lines\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/parse_after/oneline.lua",
    "content": "local M = {}\n\n---@param lines string[]\n---@return string[]\nfunction M.cmd(lines, _)\n  lines = { table.concat(lines, \"\") }\n  return lines\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/parse_after/rate.lua",
    "content": "local api = vim.api\n\nlocal util = require(\"translate.util.util\")\n\nlocal M = {}\n\n---Cut the results of translation to fit the rate of the original width of the selection.\n---@param lines string[]\n---@param pos table\n---@return string[]\nfunction M.cmd(lines, pos)\n  local results = {}\n\n  for i, text in ipairs(lines) do\n    local group = pos._group[i]\n\n    local width_origin = {}\n    local sum_width_origin = 0\n    for _, g in ipairs(group) do\n      local width = api.nvim_strwidth(pos._lines_selected[g])\n      table.insert(width_origin, width)\n      sum_width_origin = sum_width_origin + width\n    end\n    local sum_width_result = api.nvim_strwidth(text)\n\n    local width = vim.tbl_map(function(w)\n      return math.floor(w / sum_width_origin * sum_width_result)\n    end, width_origin)\n\n    if #width > 1 then\n      results = vim.list_extend(results, util.text_cut(text, width))\n    else\n      table.insert(results, text)\n    end\n  end\n\n  return results\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/parse_after/translate_shell.lua",
    "content": "local M = {}\n\n---@param text string\n---@return string[]\nfunction M.cmd(text, _)\n  local crlf\n  -- Remove the extra CRLF at the end.\n  if vim.endswith(text, \"\\r\\n\") then\n    crlf = \"\\r\\n\"\n    text = text:sub(1, -3)\n  else\n    crlf = text:sub(-1)\n    text = text:sub(1, -2)\n  end\n\n  local lines = vim.split(text, crlf)\n\n  return lines\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/parse_after/window.lua",
    "content": "local api = vim.api\n\nlocal util = require(\"translate.util.util\")\n\nlocal M = {}\n\n---Cut the text to fit the window width.\n---@param lines string[]\n---@return string[]\nfunction M.cmd(lines, _)\n  local option = require(\"translate.config\").get(\"preset\").parse_after.window\n  local width = option.width\n  if width <= 1 then\n    width = math.floor(api.nvim_win_get_width(0) * option.width)\n  end\n\n  local results = {}\n  for _, text in ipairs(lines) do\n    results = vim.list_extend(results, util.text_cut(text, width))\n  end\n\n  return results\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/parse_before/concat.lua",
    "content": "local M = {}\n\n---@param lines string[]\n---@return string[]\nfunction M.cmd(lines)\n  local options = require(\"translate.config\").get(\"preset\").parse_before.concat\n  local sep = options.sep\n  lines = { table.concat(lines, sep) }\n\n  return lines\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/parse_before/natural.lua",
    "content": "local util = require(\"translate.util.util\")\n\nlocal M = {}\n\nlocal function inc(tbl, index)\n  if tbl[index] then\n    return index + 1\n  end\n  return index\nend\n\n---@param lines string[]\n---@param pos positions\n---@param cmd_args table\n---@return string[]\nfunction M.cmd(lines, pos, cmd_args)\n  local option = require(\"translate.config\").get(\"preset\").parse_before.natural\n\n  local end_regex\n  local start_regex\n  if cmd_args.source then\n    local ends = M.get_end(cmd_args.source, option)\n    if ends then\n      end_regex = vim.regex([[\\V\\%(]] .. table.concat(ends, [[\\|]]) .. [[\\)\\$]])\n    end\n    local starts = M.get_start(cmd_args.source, option)\n    if starts then\n      start_regex = vim.regex([[^\\V\\%(]] .. table.concat(starts, [[\\|]]) .. [[\\)]])\n    end\n  end\n\n  pos._group = {}\n\n  local results = {}\n  local original_index, result_index = 1, 1\n\n  while true do\n    local line = lines[original_index]\n    if not line then\n      break\n    end\n\n    if line == \"\" then\n      result_index = inc(results, result_index)\n      util.append_dict_list(results, result_index, line)\n      util.append_dict_list(pos._group, result_index, original_index)\n      if results[result_index] then\n        result_index = result_index + 1\n      end\n    else\n      if start_regex and start_regex:match_str(line) then\n        result_index = inc(results, result_index)\n      end\n\n      util.append_dict_list(results, result_index, line)\n      util.append_dict_list(pos._group, result_index, original_index)\n\n      if end_regex and end_regex:match_str(line) then\n        result_index = inc(results, result_index)\n      end\n    end\n\n    original_index = original_index + 1\n  end\n\n  results = vim.tbl_map(function(r)\n    return table.concat(r, \" \")\n  end, results)\n\n  return results\nend\n\nM.lang_abbr = {\n  en = \"english\",\n  eng = \"english\",\n  ja = \"japanese\",\n  jpn = \"japanese\",\n  zh = \"chinese\",\n  zho = \"chinese\",\n  [\"zh-CN\"] = \"chinese\",\n  [\"zh-TW\"] = \"chinese\",\n}\n\n-- vim's regex pattern (vary no magic '\\V')\nM.end_marks = {\n  english = {\n    \".\",\n    \"?\",\n    \"!\",\n    \":\",\n    \";\",\n  },\n  japanese = {\n    \"。\",\n    \".\",\n    \"？\",\n    \"?\",\n    \"！\",\n    \"!\",\n    \"：\",\n    \"；\",\n  },\n  chinese = {\n    \"。\",\n    \"！\",\n    \"？\",\n    \"：\",\n  },\n}\n\n-- vim's regex pattern (vary no magic '\\V')\nM.start_marks = {\n  english = {\n    [[\\u\\U]],\n  },\n}\n\nfunction M.get_end(lang, option)\n  lang = lang:lower()\n  lang = option.lang_abbr[lang] or M.lang_abbr[lang]\n  return option.end_marks[lang] or M.end_marks[lang]\nend\n\nfunction M.get_start(lang, option)\n  lang = lang:lower()\n  lang = option.lang_abbr[lang] or M.lang_abbr[lang]\n  return option.start_marks[lang] or M.start_marks[lang]\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/preset/parse_before/no_handle.lua",
    "content": "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",
    "content": "local M = {}\n\n---@param lines string[]\n---@param pos positions\n---@return string[]\nfunction M.cmd(lines, pos)\n  for i, line in ipairs(lines) do\n    local pre = line:match(\"^%s*\")\n    pos[i].col[1] = pos[i].col[1] + #pre\n\n    local suf = line:match(\"%s*$\")\n    pos[i].col[2] = pos[i].col[2] - #suf\n\n    lines[i] = line:sub(#pre + 1, -#suf - 1)\n  end\n\n  return lines\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/util/comment.lua",
    "content": "local fn = vim.fn\nlocal api = vim.api\n\nlocal context = require(\"translate.util.context\")\nlocal util = require(\"translate.util.util\")\n\nlocal M = {}\n\nlocal string_symbols = {\n  python = {\n    { begin = [[''']], last = [[''']] },\n    { begin = [[\"\"\"]], last = [[\"\"\"]] },\n  },\n}\n\nfunction M.get_range() -- example 2. (see below)\n  -- Common comments can be of the following types.\n  -- 1. The comment string repeats at the start of each line (e.g. this line).\n  --    This may be strung together on multiple lines to form a single comment.\n  -- 2. Similar 1., but comments begin in the middle of the line (e.g. the comment four lines above).\n  -- 3. three-piece comment (e.g. c's '/* comment */').\n  --\n  -- First, check to see if the current line is 1. by looking at the beginning of the line\n  -- since the only case in which it is necessary to recursively examine is in 1.\n  -- If not 1, then use highlighting or treesitter to take the range of comments.\n  -- We have already established that it is either 2 or 3, so all that remains is to remove the comment sign.\n\n  local comments = M.get_comments()\n\n  -- (1, 1) indexed cursor position\n  local cursor = api.nvim_win_get_cursor(0)\n  cursor[2] = cursor[2] + 1\n\n  local pos = {\n    _mode = \"comment\",\n  }\n\n  if M.is_pattern1(comments, cursor[1], pos) then\n    return pos\n  end\n\n  -- { row_s, col_s, row_e, col_e }\n  local range = context.ts.get_range(\"comment\", cursor) or context.vim.get_range(\"Comment\", cursor)\n\n  if range then\n    M.remove_comment_symbol(comments, range, pos)\n  else\n    -- filetype check\n    local ft = vim.bo.filetype\n    if vim.tbl_contains(vim.tbl_keys(string_symbols), ft) then\n      range = context.ts.get_range(\"string\", cursor) or context.vim.get_range(\"String\", cursor)\n      if range then\n        M.remove_string_symbol(string_symbols[ft], range, pos)\n      end\n    else\n      vim.notify(\"Here is not in comments.\")\n    end\n  end\n\n  return pos\nend\n\nfunction M.get_comments()\n  -- Ignore 'n' and 'f' because they are complicated and not used often.\n  local comments = {}\n\n  for comment in vim.gsplit(vim.bo.comments, \",\") do\n    local flags, com = comment:match(\"^(.*):(.*)$\")\n\n    if flags:find(\"b\") then\n      -- Blank required after com\n      com = com .. [[\\s]]\n    end\n\n    if flags:find(\"s\") then\n      -- Start of three-piece comment\n      util.append_dict_list(comments, \"s\", com)\n    elseif flags:find(\"m\") then\n      -- Middle of three-piece comment\n      util.append_dict_list(comments, \"m\", com)\n    elseif flags:find(\"e\") then\n      -- End of three-piece comment\n      util.append_dict_list(comments, \"e\", com)\n    elseif not flags:find(\"f\") then\n      -- When flags have none of the 'f', 's', 'm' or 'e' flags, Vim assumes the comment\n      -- string repeats at the start of each line.  The flags field may be empty.\n      util.append_dict_list(comments, \"empty\", com)\n    end\n  end\n\n  comments = vim.tbl_map(function(c)\n    return [[\\V\\%(]] .. table.concat(c, [[\\|]]) .. [[\\)]]\n  end, comments)\n\n  return comments\nend\n\nfunction M.remove_comment_symbol(comments, range, pos)\n  local lines = api.nvim_buf_get_lines(0, range[1] - 1, range[3], true)\n  pos._lines = lines\n\n  if range[1] == range[3] and M.is_pattern2(comments, range, pos) then\n    return pos\n  end\n\n  -- If you have made it this far, it should be pattern 3.\n  -- So if it fails inside is_pattern3, it is an error.\n  M.assert_pattern3(comments, range, pos)\n  return pos\nend\n\n---Check if a line of 'row' is pattern 1, and if so, check if the lines above and below they are also pattern 1.\n---Even if the pattern is 1, if the indentation and comment symbols are different, they are not considered to be\n---in the same group.\n---@param comments table\n---@param row number\n---@param pos table\n---@return boolean is_pattern1\nfunction M.is_pattern1(comments, row, pos)\n  if not util.has_key(comments, \"empty\") then\n    return false\n  end\n\n  local ok, col, prefix = M._is_pattern1(comments, row)\n  if not ok then\n    return false\n  end\n\n  table.insert(pos, { row = row, col = col })\n\n  local function search(dir, border)\n    local attention_row = row\n    while true do\n      attention_row = attention_row + dir\n      if attention_row == border then\n        break\n      end\n      ok, col = M._is_pattern1(comments, attention_row, prefix)\n      if not ok then\n        break\n      end\n      local p = { row = attention_row, col = col }\n      if dir == -1 then\n        table.insert(pos, 1, p)\n      else\n        table.insert(pos, p)\n      end\n    end\n  end\n\n  -- Search above\n  search(-1, 1)\n\n  -- Search below\n  search(1, fn.line(\"$\"))\n\n  -- update\n  pos._lines = api.nvim_buf_get_lines(0, pos[1].row - 1, pos[#pos].row, true)\n\n  return true\nend\n\n---Checks if a line is pattern 1, and if so, returns the range removed indentation and\n---comment string. If we already known a line is pattern 1, using 'prefix' to look for\n---lines above and below it that begin with the same indentation and comment string.\n---@param comments table\n---@param row number\n---@param prefix? string\n---@return boolean? is_pattern1\n---@return table? col\n---@return string? prefix\nfunction M._is_pattern1(comments, row, prefix)\n  -- 1. the comment string repeats at the start of each line (e.g. this line)\n  local line = fn.getline(row)\n  if prefix then\n    return vim.startswith(line, prefix), { #prefix + 1, #line }\n  else\n    local indent = [[^\\V\\s\\*]]\n    local col_s, col_e = vim.regex(indent .. comments.empty):match_line(0, row - 1)\n    if col_s then\n      prefix = line:sub(col_s, col_e)\n      return true, { #prefix + 1, #line }, prefix\n    end\n  end\nend\n\nfunction M.is_pattern2(comments, range, pos)\n  if not util.has_key(comments, \"empty\") then\n    return false\n  end\n\n  local line = pos._lines[1]\n  local comment = line:sub(range[2], range[4])\n  local _, col_e = vim.regex(\"^\" .. comments.empty):match_str(comment)\n  if col_e then\n    table.insert(pos, { row = range[1], col = { range[2] + col_e + 1, range[4] } })\n    return true\n  end\nend\n\nfunction M.assert_pattern3(comments, range, pos)\n  if not util.has_key(comments, \"s\", \"m\", \"e\") then\n    error(\"Invalid &comments\")\n  end\n\n  -- like v selection\n  for i, line in ipairs(pos._lines) do\n    local indent = line:match(\"^%s*\")\n    local p = { row = range[1] + i - 1, col = { #indent + 1, #line } }\n    table.insert(pos, p)\n  end\n  pos[1].col[1] = range[2]\n  pos[#pos].col[2] = math.min(pos[#pos].col[2], range[4])\n\n  -- Remove start of three-piece\n  local first_line = pos._lines[1]:sub(range[2])\n  if vim.regex(\"^\" .. comments.s .. [[\\s\\*\\$]]):match_str(first_line) then\n    -- This line is unnecessary because it is only a comment string\n    table.remove(pos, 1)\n    table.remove(pos._lines, 1)\n  else\n    local _, num_of_com = vim.regex(\"^\" .. comments.s):match_str(first_line)\n    if num_of_com then\n      pos[1].col[1] = pos[1].col[1] + num_of_com\n    else\n      error(\"The start of three-piece can't found\")\n    end\n  end\n\n  -- Remove middle of three-piece if exists\n  if #pos > 2 then\n    for i = 2, #pos do\n      local selected = pos._lines[i]:sub(pos[i].col[1], pos[i].col[2])\n      local _, num_of_com = vim.regex(\"^\" .. comments.m):match_str(selected)\n      -- In the case of the last line, end of three-piece may be misunderstood as middle of three-piece.\n      if num_of_com and (i < #pos or not vim.regex(\"^\" .. comments.e):match_str(selected)) then\n        pos[i].col[1] = pos[i].col[1] - num_of_com\n      end\n    end\n  end\n\n  -- Remove end of three-piece\n  local last_line = pos._lines[#pos._lines]:sub(1, range[4])\n  if vim.regex([[^\\V\\s\\*]] .. comments.e .. [[\\$]]):match_str(last_line) then\n    -- This line is unnecessary because it is only a comment string\n    table.remove(pos, #pos)\n    table.remove(pos._lines, #pos._lines)\n  else\n    local comStart, comEnd = vim.regex(comments.e .. [[\\$]]):match_str(last_line)\n    if comStart then\n      local num_of_com = comEnd - comStart\n      pos[#pos].col[2] = pos[#pos].col[2] - num_of_com\n    else\n      error(\"The end of three-piece can't found\")\n    end\n  end\nend\n\nfunction M.remove_string_symbol(symbols, range, pos)\n  local begin_row, last_row = range[1], range[3]\n  local begin_col, last_col = range[2], range[4]\n\n  local lines = api.nvim_buf_get_lines(0, begin_row - 1, last_row, true)\n  pos._lines = lines\n\n  for i, line in ipairs(lines) do\n    pos[i] = { row = begin_row + i - 1, col = { 1, #line } }\n  end\n  pos[1].col[1] = begin_col\n  pos[#pos].col[2] = last_col\n\n  for _, s in ipairs(symbols) do\n    if vim.startswith(lines[1]:sub(begin_col), s.begin) then\n      pos[1].col[1] = pos[1].col[1] + #s.begin - 1\n      pos[#pos].col[2] = pos[#pos].col[2] - #s.last\n      if #pos >= 2 then\n        local indent = lines[2]:match(\"^%s*\")\n        if #indent > 0 then\n          for i = 2, #pos do\n            pos[i].col[1] = #indent + 1\n          end\n        end\n      end\n      if pos[1].col[1] == pos[1].col[2] then\n        table.remove(pos, 1)\n        table.remove(pos._lines, 1)\n      end\n      if pos[#pos].col[1] == pos[#pos].col[2] then\n        pos[#pos] = nil\n        pos._lines[#pos._lines] = nil\n      end\n    end\n  end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/util/context.lua",
    "content": "local fn = vim.fn\n\nlocal util = require(\"translate.util.util\")\nlocal TreeSitter = require(\"translate.kit.Lua.TreeSitter\")\n\nlocal M = {\n  vim = {},\n  ts = {},\n}\n\n---Get vim's syntax groups for specified position.\n---NOTE: This function accepts 1-origin cursor position.\n---@param cursor number[] @{lnum, col}\n---@return string[]?\nfunction M.vim.get_range(group_name, cursor)\n  if not M.vim.is_group(group_name, cursor) then\n    return\n  end\n\n  -- Search start position\n  local pos_s = util.tbl_copy(cursor)\n  while true do\n    local _pos_s = M.vim.jump(pos_s, 0)\n    if _pos_s and M.vim.is_group(group_name, _pos_s) then\n      pos_s = _pos_s\n    else\n      break\n    end\n  end\n\n  -- Search end position\n  local pos_e = util.tbl_copy(cursor)\n  while true do\n    local _pos_e = M.vim.jump(pos_e, 1)\n    if _pos_e and M.vim.is_group(group_name, _pos_e) then\n      pos_e = _pos_e\n    else\n      break\n    end\n  end\n\n  local range = util.concat(pos_s, pos_e)\n  return range\nend\n\n---Moves to the end of the next word or the beginning of the previous word.\n---@param pos number[] @{ row, col }\n---@param dir integer @if 0, next, otherwise previous\n---@return { row: number, col: number }?\nfunction M.vim.jump(pos, dir)\n  local row, col = pos[1], pos[2]\n  local current_line = fn.getline(row)\n\n  if dir == 0 then -- Head of previous word\n    col = current_line:sub(1, col - 1):find(\"%S+%s*$\")\n    if not col then\n      repeat\n        row = row - 1\n        if row < 1 then\n          return\n        end\n        current_line = fn.getline(row)\n        col = current_line:find(\"%S+%s*$\")\n      until col\n    end\n  else -- Tail of next word\n    local max_row = fn.line(\"$\")\n\n    _, col = current_line:find(\"%S+\", col + 1)\n    if not col then\n      -- next not empty line\n      repeat\n        row = row + 1\n        if row > max_row then\n          return\n        end\n        current_line = fn.getline(row)\n        _, col = current_line:find(\"%S+\")\n      until col\n    end\n  end\n\n  return { row, col }\nend\n\nfunction M.vim.is_group(group_name, pos)\n  for _, syntax_id in ipairs(fn.synstack(pos[1], pos[2])) do\n    if fn.synIDattr(fn.synIDtrans(syntax_id), \"name\") == group_name then\n      return true\n    end\n  end\n  return false\nend\n\n---Get tree-sitter's syntax groups for specified position.\n---@param node_type string\n---@param pos number[] (1,1)-index\n---@return string[]? range\nfunction M.ts.get_range(node_type, pos)\n  local row, col = unpack(pos)\n  -- (1, 1) -> (0, 0)\n  row = row - 1\n  col = col - 1\n\n  local node = TreeSitter.get_node_at(row, col)\n  if node == nil then\n    return\n  end\n  local parents = TreeSitter.parents(node)\n  for _, p_node in ipairs(parents) do\n    if p_node:type() == node_type then\n      local s_row, s_col, e_row, e_col = p_node:range()\n      -- From 0-index to 1-index\n      s_row = s_row + 1\n      s_col = s_col + 1\n      e_row = e_row + 1\n      e_col = e_col + 1\n      return { s_row, s_col, e_row, e_col }\n    end\n  end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/util/replace.lua",
    "content": "local config = require(\"translate.config\")\n\nlocal M = {\n  command_name = \"\",\n}\n\n---@param command_name string\nfunction M.set_command_name(command_name)\n  M.command_name = command_name\nend\n\n---@param lines string[]\n---@param is_before boolean\n---@return string[]\nlocal function replace(lines, is_before)\n  local replace_symbols = config.get(\"replace_symbols\") or {}\n  local symbols = replace_symbols[M.command_name]\n  if symbols and next(symbols) ~= nil then\n    for i, line in ipairs(lines) do\n      for org, rep in pairs(symbols) do\n        if is_before then\n          line = line:gsub(org, rep)\n        else\n          line = line:gsub(rep, org)\n        end\n      end\n      lines[i] = line\n    end\n  end\n  return lines\nend\n\n---@param lines string[]\n---@return string[]\nfunction M.before(lines)\n  return replace(lines, true)\nend\n\n---@param lines string[] | string\n---@return string[]\nfunction M.after(lines)\n  if type(lines) == \"string\" then\n    lines = { lines }\n  end\n  return replace(lines, false)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/util/select.lua",
    "content": "local api = vim.api\nlocal fn = vim.fn\n\nlocal comment = require(\"translate.util.comment\")\nlocal utf8 = require(\"translate.util.utf8\")\nlocal util = require(\"translate.util.util\")\n\nlocal M = {}\nlocal L = {}\n\n---@class position\n---@field row integer\n---@field col integer[] { begin, last }\n\n---@class positions\n---@field _lines string[]\n---@field _mode \"comment\" | \"n\" | \"v\" | \"V\" | \"\u0016\"\n---@field [1] position[]\n\n---@param args table\n---@param mode string\n---@return positions\nfunction M.get(args, mode)\n  if args.comment then\n    return comment.get_range()\n  elseif mode == \"n\" then\n    return L.get_current_line()\n  else\n    return L.get_visual_selected(mode)\n  end\nend\n\n---@param mode string\n---@return positions\nfunction L.get_visual_selected(mode)\n  local start, last\n  -- When called from command line, \"v\" and \".\" return the same locations (cursor position, not selection range).\n  -- In this case, '< and '> must be used.\n  if util.same_pos(\".\", \"v\") then\n    start = util.getpos(\"'<\")\n    last = util.getpos(\"'>\")\n  else\n    start = util.getpos(\"v\")\n    last = util.getpos(\".\")\n  end\n\n  local pos_s, pos_e = util.which_front(start, last)\n\n  local lines = api.nvim_buf_get_lines(0, pos_s[1] - 1, pos_e[1], true)\n\n  local pos = {}\n  pos._lines = lines\n  pos._mode = mode\n\n  if mode == \"V\" then\n    for i, line in ipairs(lines) do\n      table.insert(pos, { row = pos_s[1] + i - 1, col = { 1, #line } })\n    end\n  else\n    local last_line = fn.getline(pos_e[1])\n    local is_end = pos_e[2] == #last_line + 1 -- Selected to the end of each line.\n    if not is_end then\n      local offset = utf8.offset(last_line, 2, pos_e[2])\n      if offset then\n        pos_e[2] = offset - 1\n      else -- The last character of the line.\n        pos_e[2] = #last_line\n      end\n    end\n\n    if mode == \"v\" then\n      for i, line in ipairs(lines) do\n        local p = { row = pos_s[1] + i - 1, col = { 1, #line } }\n        table.insert(pos, p)\n      end\n      pos[1].col[1] = pos_s[2]\n      pos[#pos].col[2] = pos_e[2]\n    elseif mode == \"\u0016\" then\n      for i, _ in ipairs(lines) do\n        local row = pos_s[1] + i - 1\n        local col_end = is_end and #fn.getline(row) or pos_e[2]\n        table.insert(pos, { row = row, col = { pos_s[2], col_end } })\n      end\n    end\n  end\n\n  return pos\nend\n\n---@return positions\nfunction L.get_current_line()\n  local row = fn.line(\".\")\n  local line = api.nvim_get_current_line()\n  local pos = { { row = row, col = { 1, #line } } }\n  pos._lines = { line }\n  pos._mode = \"n\"\n  return pos\nend\n\nreturn M\n"
  },
  {
    "path": "lua/translate/util/utf8.lua",
    "content": "local utf8 = {}\n\nlocal bit = require(\"bit\") -- luajit\n\nlocal band = bit.band\nlocal bor = bit.bor\nlocal rshift = bit.rshift\nlocal lshift = bit.lshift\n\n---The pattern (a string, not a function) \"[\\0-\\x7F\\xC2-\\xF4][\\x80-\\xBF]*\",\n---which matches exactly one UTF-8 byte sequence, assuming that the subject is a valid UTF-8 string.\nutf8.charpattern = \"[%z\\x01-\\x7F\\xC2-\\xF4][\\x80-\\xBF]*\"\n\n---@param idx integer\n---@param func_name string\n---@param range_name string\n---@return string @error message\nlocal function create_errmsg(idx, func_name, range_name)\n  return string.format(\"bad argument #%s to '%s' (%s out of range)\", idx, func_name, range_name)\nend\n\n---Converts indexes of a string to positive numbers.\n---@param str string\n---@param idx integer\n---@return boolean, integer\nlocal function validate_range(str, idx)\n  idx = idx > 0 and idx or #str + idx + 1\n  if idx < 0 or idx > #str then\n    return false\n  end\n  return true, idx\nend\n\n---Receives zero or more integers, converts each one to its corresponding UTF-8 byte sequence\n---and returns a string with the concatenation of all these sequences.\n---@vararg integer\n---@return string\nfunction utf8.char(...)\n  local buffer = {}\n  for i, v in ipairs({ ... }) do\n    if v < 0 or v > 0x10FFFF then\n      error(create_errmsg(i, \"char\", \"value\"), 2)\n    elseif v < 0x80 then\n      -- single-byte\n      buffer[i] = string.char(v)\n    elseif v < 0x800 then\n      -- two-byte\n      local b1 = bor(0xC0, band(rshift(v, 6), 0x1F)) -- 110x-xxxx\n      local b2 = bor(0x80, band(v, 0x3F)) -- 10xx-xxxx\n      buffer[i] = string.char(b1, b2)\n    elseif v < 0x10000 then\n      -- three-byte\n      local b1 = bor(0xE0, band(rshift(v, 12), 0x0F)) -- 1110-xxxx\n      local b2 = bor(0x80, band(rshift(v, 6), 0x3F)) -- 10xx-xxxx\n      local b3 = bor(0x80, band(v, 0x3F)) -- 10xx-xxxx\n      buffer[i] = string.char(b1, b2, b3)\n    else\n      -- four-byte\n      local b1 = bor(0xF0, band(rshift(v, 18), 0x07)) -- 1111-0xxx\n      local b2 = bor(0x80, band(rshift(v, 12), 0x3F)) -- 10xx-xxxx\n      local b3 = bor(0x80, band(rshift(v, 6), 0x3F)) -- 10xx-xxxx\n      local b4 = bor(0x80, band(v, 0x3F)) -- 10xx-xxxx\n      buffer[i] = string.char(b1, b2, b3, b4)\n    end\n  end\n  return table.concat(buffer, \"\")\nend\n\n---Returns the next one character range.\n---@param s string\n---@param start_pos integer\n---@return integer start_pos, integer end_pos\nlocal function next_char(s, start_pos)\n  local b1 = s:byte(start_pos)\n  if not b1 then\n    return -- for offset's #s+1\n  end\n\n  local end_pos\n\n  if band(b1, 0x80) == 0x00 then -- single-byte (0xxx-xxxx)\n    return start_pos, start_pos\n  elseif 0xC2 <= b1 and b1 <= 0xDF then -- two-byte (range 0xC2 to 0xDF)\n    end_pos = start_pos + 1\n  elseif band(b1, 0xF0) == 0xE0 then -- three-byte (1110-xxxx)\n    end_pos = start_pos + 2\n  elseif 0xF0 <= b1 and b1 <= 0xF4 then -- four-byte (range 0xF0 to 0xF4)\n    end_pos = start_pos + 3\n  else -- invalid 1st byte\n    return\n  end\n\n  -- validate (end_pos)\n  if end_pos > #s then\n    return\n  end\n  -- validate (continuation)\n  for _, bn in ipairs({ s:byte(start_pos + 1, end_pos) }) do\n    if band(bn, 0xC0) ~= 0x80 then -- 10xx-xxxx?\n      return\n    end\n  end\n\n  return start_pos, end_pos\nend\n\n---Returns values so that the construction\n---\n---for p, c in utf8.codes(s) do body end\n---\n---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.\n---It raises an error if it meets any invalid byte sequence.\n---@param s string\n---@return function iterator\nfunction utf8.codes(s)\n  vim.validate({\n    s = { s, \"string\" },\n  })\n\n  local i = 1\n  return function()\n    if i > #s then\n      return\n    end\n\n    local start_pos, end_pos = next_char(s, i)\n    if start_pos == nil then\n      error(\"invalid UTF-8 code\", 2)\n    end\n\n    i = end_pos + 1\n    return start_pos, s:sub(start_pos, end_pos)\n  end\nend\n\n---Returns the code points (as integers) from all characters in s that start between byte position i and j (both included).\n---The default for i is 1 and for j is i.\n---It raises an error if it meets any invalid byte sequence.\n---@param s string\n---@param i? integer start position. default=1\n---@param j? integer end position. default=i\n---@return integer @code point\nfunction utf8.codepoint(s, i, j)\n  vim.validate({\n    s = { s, \"string\" },\n    i = { i, \"number\", true },\n    j = { j, \"number\", true },\n  })\n\n  local ok\n  ok, i = validate_range(s, i or 1)\n  if not ok then\n    error(create_errmsg(2, \"codepoint\", \"initial potision\"), 2)\n  end\n  ok, j = validate_range(s, j or i)\n  if not ok then\n    error(create_errmsg(3, \"codepoint\", \"final potision\"), 2)\n  end\n\n  local ret = {}\n  repeat\n    local char_start, char_end = next_char(s, i)\n    if char_start == nil then\n      error(\"invalid UTF-8 code\", 2)\n    end\n\n    i = char_end + 1\n\n    local len = char_end - char_start + 1\n    if len == 1 then\n      -- single-byte\n      table.insert(ret, s:byte(char_start))\n    else\n      -- multi-byte\n      local b1 = s:byte(char_start)\n      b1 = band(lshift(b1, len + 1), 0xFF) -- e.g. 110x-xxxx -> xxxx-x000\n      b1 = lshift(b1, len * 5 - 7) -- >> len+1 and << (len-1)*6\n\n      local cp = 0\n      for k = char_start + 1, char_end do\n        local bn = s:byte(k)\n        cp = bor(lshift(cp, 6), band(bn, 0x3F))\n      end\n\n      cp = bor(b1, cp)\n      table.insert(ret, cp)\n    end\n  until char_end >= j\n\n  return unpack(ret)\nend\n\n---Returns the number of UTF-8 characters in string s that start between positions i and j (both inclusive).\n---The default for i is 1 and for j is -1.\n---If it finds any invalid byte sequence, returns fail plus the position of the first invalid byte.\n---@param s string\n---@param i? integer start position. default=1\n---@param j? integer end position. default=-1\n---@return integer\nfunction utf8.len(s, i, j)\n  vim.validate({\n    s = { s, \"string\" },\n    i = { i, \"number\", true },\n    j = { j, \"number\", true },\n  })\n\n  local ok\n  ok, i = validate_range(s, i or 1)\n  if not ok then\n    error(create_errmsg(2, \"len\", \"initial potision\"), 2)\n  end\n  ok, j = validate_range(s, j or -1)\n  if not ok then\n    error(create_errmsg(3, \"len\", \"final potision\"), 2)\n  end\n\n  local len = 0\n\n  repeat\n    local char_start, char_end = next_char(s, i)\n    if char_start == nil then\n      return nil, i\n    end\n\n    i = char_end + 1\n    len = len + 1\n  until char_end >= j\n\n  return len\nend\n\n---Returns the position (in bytes) where the encoding of the n-th character of s (counting from position i) starts.\n---A negative n gets characters before position i.\n---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.\n---If the specified character is neither in the subject nor right after its end, the function returns fail.\n---\n---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.\n---@param s string\n---@param n integer\n---@param i? integer start position. if n >= 0, default=1, otherwise default=#s+1\n---@return integer\nfunction utf8.offset(s, n, i)\n  vim.validate({\n    s = { s, \"string\" },\n    n = { n, \"number\" },\n    i = { i, \"number\", true },\n  })\n\n  i = i or n >= 0 and 1 or #s + 1\n\n  if n >= 0 or i ~= #s + 1 then\n    local ok\n    ok, i = validate_range(s, i)\n    if not ok then\n      error(create_errmsg(3, \"offset\", \"position\"), 2)\n    end\n  end\n\n  if n == 0 then\n    for j = i, 1, -1 do\n      local char_start = next_char(s, j)\n      if char_start then\n        return char_start\n      end\n    end\n  elseif n > 0 then\n    if not next_char(s, i) then\n      error(\"initial position is a continuation byte\", 2)\n    end\n\n    for j = i, #s do\n      local char_start = next_char(s, j)\n      if char_start then\n        n = n - 1\n        if n == 0 then\n          return char_start\n        end\n      end\n    end\n  else\n    if i ~= #s + 1 and not next_char(s, i) then\n      error(\"initial position is a continuation byte\", 2)\n    end\n\n    for j = i, 1, -1 do\n      local char_start = next_char(s, j)\n      if char_start then\n        n = n + 1\n        if n == 0 then\n          return char_start\n        end\n      end\n    end\n  end\nend\n\nreturn utf8\n"
  },
  {
    "path": "lua/translate/util/util.lua",
    "content": "local fn = vim.fn\nlocal api = vim.api\nlocal luv = vim.loop\nlocal utf8 = require(\"translate.util.utf8\")\n\nlocal M = {}\n\n---Copy the table\n---NOTE: Metatable is not considered\n---@param tbl table\n---@return table\nfunction M.tbl_copy(tbl)\n  if type(tbl) ~= \"table\" then\n    return tbl\n  end\n  local new = {}\n  for k, v in pairs(tbl) do\n    if type(v) == \"table\" then\n      new[k] = M.tbl_copy(v)\n    else\n      new[k] = v\n    end\n  end\n  return new\nend\n\n---Concatenate two list-like tables.\n---@param t1 table\n---@param t2 table\n---@return table\nfunction M.concat(t1, t2)\n  local new = {}\n  for _, v in ipairs(t1) do\n    table.insert(new, v)\n  end\n  for _, v in ipairs(t2) do\n    table.insert(new, v)\n  end\n  return new\nend\n\n---Add an element to dict[key]\n---dict is a table with an array for values.\n---@param dict {any: any[]}\n---@param key any\n---@param elem any\nfunction M.append_dict_list(dict, key, elem)\n  if not dict[key] then\n    dict[key] = {}\n  end\n  table.insert(dict[key], elem)\nend\n\nfunction M.text_cut(text, widths)\n  local widths_is_table = type(widths) == \"table\"\n  local function get_width(row)\n    return widths_is_table and widths[row] or widths\n  end\n\n  local lines = {}\n  local row, col = 1, 0\n  local width = get_width(1)\n\n  local function skip_blank_line()\n    while width == 0 do\n      M.append_dict_list(lines, row, \"\")\n      row = row + 1\n      width = get_width(row)\n    end\n  end\n\n  skip_blank_line()\n\n  for p, char in utf8.codes(text) do\n    local l = api.nvim_strwidth(char)\n\n    if col + l > width then\n      if widths_is_table and widths[row + 1] == nil then\n        local residue = text:sub(p)\n        M.append_dict_list(lines, row, residue)\n        break\n      end\n\n      row = row + 1\n      width = get_width(row)\n      col = 0\n\n      skip_blank_line()\n    end\n\n    M.append_dict_list(lines, row, char)\n    col = col + l\n  end\n\n  for i, line in ipairs(lines) do\n    lines[i] = table.concat(line, \"\")\n  end\n\n  if #lines == 0 then\n    lines = { \"\" }\n  end\n\n  return lines\nend\n\nfunction M.max_width_in_string_list(list)\n  local max = api.nvim_strwidth(list[1])\n  for i = 2, #list do\n    local v = api.nvim_strwidth(list[i])\n    if v > max then\n      max = v\n    end\n  end\n  return max\nend\n\nfunction M.has_key(tbl, ...)\n  local keys = { ... }\n  for _, k in ipairs(keys) do\n    if tbl[k] == nil then\n      return false\n    end\n  end\n  return true\nend\n\n---@param last integer\n---@return integer[][]\nfunction M.seq(last)\n  local l = {}\n  for i = 1, last do\n    l[i] = { i }\n  end\n  return l\nend\n\n---Compare position\n---@param pos1 number[] #{row, col}\n---@param pos2 number[] #{row, col}\n---@return number[], number[] #front, end\nfunction M.which_front(pos1, pos2)\n  -- Row Comparison\n  if pos1[1] < pos2[1] then\n    return pos1, pos2\n  elseif pos1[1] > pos2[1] then\n    return pos2, pos1\n  else\n    -- Col Comparison\n    if pos1[2] < pos2[2] then\n      return pos1, pos2\n    else\n      return pos2, pos1\n    end\n  end\nend\n\n---Wrapper function for getpos() that returns only 'row' and 'col'.\n---@param expr string\n---@return integer[] {row, col}\nfunction M.getpos(expr)\n  local p = vim.fn.getpos(expr)\n  local result = { p[2], p[3] }\n  return result\nend\n\n---Returns whether cursor positions are equal.\n---@param expr1 string\n---@param expr2 string\n---@return boolean\nfunction M.same_pos(expr1, expr2)\n  local p1 = M.getpos(expr1)\n  local p2 = M.getpos(expr2)\n  return p1[1] == p2[1] and p1[2] == p2[2]\nend\n\n---@param text string #json string\n---@return string\nfunction M.write_temp_data(text)\n  local dir = fn.expand(fn.stdpath(\"cache\") .. \"/translate\")\n  vim.fn.mkdir(dir, \"p\")\n  local path = fn.expand(dir .. \"/data.json\")\n  -- tonumber(\"666\", 8) -> 438\n  local fd = assert(luv.fs_open(path, \"w\", 438))\n  assert(luv.fs_write(fd, text))\n  assert(luv.fs_close(fd))\n  return path\nend\n\nreturn M\n"
  },
  {
    "path": "plugin/translate.lua",
    "content": "if vim.g.loaded_translate_nvim then\n  return\nend\n\nrequire(\"translate\").setup({})\n"
  },
  {
    "path": "stylua.toml",
    "content": "column_width = 120\nline_endings = \"Unix\"\nindent_type = \"Spaces\"\nindent_width = 2\nquote_style = \"AutoPreferDouble\"\n"
  },
  {
    "path": "utils/minimal.vim",
    "content": "let s:plug_dir = expand('/tmp/plugged/vim-plug')\nif !filereadable(s:plug_dir .. '/autoload/plug.vim')\n  execute printf('!curl -fLo %s/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim', s:plug_dir)\nend\n\nexecute 'set runtimepath+=' . s:plug_dir\ncall plug#begin(s:plug_dir)\nPlug 'uga-rosa/translate.nvim'\ncall plug#end()\nPlugInstall | quit\n\nlua <<EOF\nrequire('translate').setup({\n    -- Minimal configurations required to reproduce the problem.\n})\nEOF\n"
  }
]