diff --git a/lua/Trans/core/backend.lua b/lua/Trans/core/backend.lua index 6a7e2cb..e323f72 100644 --- a/lua/Trans/core/backend.lua +++ b/lua/Trans/core/backend.lua @@ -1,11 +1,16 @@ local Trans = require('Trans') -local conf = Trans.conf + +---@class TransBackend +---@field query fun(TransData): TransResult +---@field opts TransBackendOpts + + +local conf = Trans.conf --- INFO :Parse online engine keys config file local path = conf.dir .. '/Trans.json' local file = io.open(path, "r") - local result = {} if file then local content = file:read("*a") @@ -13,16 +18,32 @@ if file then file:close() end + +local default_opts = conf.backend.default +default_opts.__index = default_opts + + +---@class Trans +---@field backend table return setmetatable({}, { __index = function(self, name) - local opts = vim.tbl_extend('keep', conf.backend[name] or {}, conf.backend.default, result[name] or {}) + ---@type TransBackend local backend = require('Trans.backend.' .. name) - - for k, v in pairs(opts) do - backend[k] = v + if backend then + self[name] = backend + else + backend = self[name] + end + + backend.opts = setmetatable(conf.backend[name] or {}, default_opts) + + local private_opts = result[name] + if private_opts then + for k, v in pairs(private_opts) do + backend[k] = v + end end - self[name] = backend return backend end }) diff --git a/lua/Trans/core/buffer.lua b/lua/Trans/core/buffer.lua index f85fd9c..16248b9 100644 --- a/lua/Trans/core/buffer.lua +++ b/lua/Trans/core/buffer.lua @@ -1,17 +1,16 @@ ----@class buf ----@field bufnr integer buffer handle - ----@type buf -local buffer = {} - local api, fn = vim.api, vim.fn +---@class TransBuffer +---@field bufnr integer buffer handle +---@field [number] string buffer[line] content +local buffer = {} + ---Clear all content in buffer function buffer:wipe() api.nvim_buf_set_lines(self.bufnr, 0, -1, false, {}) end ----delete buffer [_start, _end] line content [one index] +---Delete buffer [_start, _end] line content [one index] ---@param _start integer start line index ---@param _end integer end line index function buffer:del(_start, _end) @@ -30,7 +29,7 @@ function buffer:set(name, value) api.nvim_buf_set_option(self.bufnr, name, value) end ----get buffer option +---Get buffer option ---@param name string option name ---@return any function buffer:option(name) @@ -42,7 +41,6 @@ function buffer:destroy() api.nvim_buf_delete(self.bufnr, { force = true }) end - ---Set buffer load keymap ---@param key string ---@param operation function | string @@ -53,7 +51,7 @@ function buffer:map(key, operation) }) end ----Execute normal keycode in this buffer[no recursive] +---Execute keycode in normal this buffer[no recursive] ---@param key string key code function buffer:normal(key) api.nvim_buf_call(self.bufnr, function() @@ -61,8 +59,8 @@ function buffer:normal(key) end) end ----@return boolean ---@nodiscard +---@return boolean function buffer:is_valid() return api.nvim_buf_is_valid(self.bufnr) end @@ -161,7 +159,6 @@ function buffer:setline(nodes, one_index) self:set('modifiable', false) end ----@private buffer.__index = function(self, key) local res = buffer[key] if res then @@ -169,14 +166,12 @@ buffer.__index = function(self, key) elseif type(key) == 'number' then -- return fn.getbufoneline(self.bufnr, key) -- Vimscript Function Or Lua API ?? return api.nvim_buf_get_lines(self.bufnr, key - 1, key, true)[1] - else error('invalid key: ' .. key) end end ----@private buffer.__newindex = function(self, key, nodes) if type(key) == 'number' then self:setline(nodes, key) @@ -185,12 +180,13 @@ buffer.__newindex = function(self, key, nodes) end end ----buffer constructor ----@return buf + +---@nodiscard +---TransBuffer constructor +---@return TransBuffer function buffer.new() local new_buf = setmetatable({ bufnr = api.nvim_create_buf(false, false), - extmarks = {}, }, buffer) new_buf:set('modifiable', false) @@ -199,4 +195,6 @@ function buffer.new() return new_buf end +---@class Trans +---@field buffer TransBuffer return buffer diff --git a/lua/Trans/core/conf.lua b/lua/Trans/core/conf.lua index a7ebf8f..d6dc4dc 100644 --- a/lua/Trans/core/conf.lua +++ b/lua/Trans/core/conf.lua @@ -7,34 +7,58 @@ if vim.fn.has('nvim-0.9') == 1 then } end + +---@class Trans +---@field conf TransConf + + +---@class TransConf return { + ---@type string the directory for database file and password file dir = os.getenv('HOME') .. '/.vim/dict', + ---@type table modeStrategy default strategy for mode strategy = { + ---@type { frontend:string, backend:string } fallback strategy for mode default = { frontend = 'hover', backend = '*', }, }, + ---@type table fallback backend for mode backend = { + ---@class TransBackendOpts default = { + ---@type integer timeout for backend send request timeout = 2000, }, }, + ---@type table frontend options frontend = { + ---@class TransFrontendOpts + ---@field keymaps table default = { + ---@type boolean Whether to auto play the audio auto_play = true, border = 'rounded', + title = title, -- need nvim-0.9 + ---@type {open: string | boolean, close: string | boolean, interval: integer} Hover Window Animation animation = { open = 'slid', -- 'fold', 'slid' close = 'slid', interval = 12, }, - title = title, -- need nvim-0.9 }, + ---@class HoverOptions : TransFrontendOpts hover = { + ---@type integer Max Width of Hover Window width = 37, + ---@type integer Max Height of Hover Window height = 27, - keymap = { + ---@type string -- see: /lua/Trans/style/spinner + spinner = 'dots', + ---@type string -- TODO :support replace with {{special word}} + fallback_message = '翻译超时或没有找到相关的翻译', + keymaps = { play = '_', pageup = '[[', pagedown = ']]', @@ -42,11 +66,13 @@ return { close = ']', toggle_entry = ';', }, + ---@type string[] auto close events auto_close_events = { 'InsertEnter', 'CursorMoved', 'BufLeave', }, + ---@type string[] order to display translate result order = { 'title', 'tag', @@ -55,8 +81,7 @@ return { 'translation', 'definition', }, - spinner = 'dots', -- see: /lua/Trans/style/spinner - fallback_message = '翻译超时或没有找到相关的翻译', -- TODO :support replace with {{special word}} + ---@type table icon = { -- or use emoji star = '', -- ⭐ @@ -68,7 +93,7 @@ return { }, }, style = { - -- see lua/Trans/style/theme.lua + ---@type string global Trans theme [see lua/Trans/style/theme.lua] theme = 'default', -- default | tokyonight | dracula }, } diff --git a/lua/Trans/core/curl.lua b/lua/Trans/core/curl.lua index be91429..a475ad1 100644 --- a/lua/Trans/core/curl.lua +++ b/lua/Trans/core/curl.lua @@ -1,6 +1,25 @@ +---@class TransCurl local curl = {} -curl.get = function(uri, opts) +---@class RequestResult +---@field body string +---@field exit integer exit code +---@field error string error message from stderr + + +---@class TransCurlOptions +---@field query table query arguments +---@field output string output file path +---@field headers table headers +---@field callback fun(result: RequestResult) + + +---@async +---Send a GET request use curl +---@param uri string uri for request +---@param opts +---| { query?: table, output?: string, headers?: table, callback: fun(result: RequestResult) } +function curl.get(uri, opts) local query = opts.query local output = opts.output local headers = opts.headers @@ -49,13 +68,11 @@ curl.get = function(uri, opts) end local on_exit = function(_, exit) - if callback then - callback { - exit = exit, - body = table.concat(outputs), - error = error - } - end + callback { + exit = exit, + body = table.concat(outputs), + error = table.concat(error, '\n') + } end -- vim.pretty_print(table.concat(cmd, ' ')) @@ -67,10 +84,12 @@ curl.get = function(uri, opts) }) end - ----- TODO : +--- TODO : -- curl.post = function () -- -- end + +---@class Trans +---@field curl TransCurl return curl diff --git a/lua/Trans/core/data.lua b/lua/Trans/core/data.lua index e6e3cc1..5a5bd83 100644 --- a/lua/Trans/core/data.lua +++ b/lua/Trans/core/data.lua @@ -1,42 +1,42 @@ local Trans = require('Trans') + +---@class TransData +---@field from string @Source language type +---@field to string @Target language type +---@field is_word boolean @Is the str a word +---@field str string @The original string +---@field mode string @The mode of the str +---@field result table @The result of the translation +---@field frontend TransFrontend +---@field backends table local M = {} M.__index = M ----@class data ----@field str string ----@field mode string ----@field result table ----@field frontend table ----@field backend table ----@field from string ----@field to string ----@field is_word boolean - - ----Data constructor +---TransData constructor ---@param opts table ----@return data +---@return TransData function M.new(opts) local mode = opts.mode local str = opts.str local strategy = Trans.conf.strategy[mode] - local data = { + + + local data = setmetatable({ str = str, mode = mode, result = {}, - } + }, M) - data.frontend = Trans.frontend[strategy.frontend].new() - data.backend = {} + data.frontend = Trans.frontend[strategy.frontend].new() + data.backends = {} for i, name in ipairs(strategy.backend) do - data.backend[i] = Trans.backend[name] + data.backends[i] = Trans.backend[name] end - if Trans.util.is_English(str) then data.from = 'en' data.to = 'zh' @@ -48,14 +48,23 @@ function M.new(opts) -- FIXME : Check if the str is a word data.is_word = true - return setmetatable(data, M) + return data end +---@class TransResult +---@field title table | string @table: {word, phonetic, oxford, collins} +---@field tag string[]? @array of tags +---@field pos table? @table: {name, value} +---@field exchange table? @table: {name, value} +---@field definition? string[]? @array of definitions +---@field translation? string[]? @array of translations + + ---Get the first available result [return nil if no result] ----@return table? +---@return TransResult? function M:get_available_result() local result = self.result - local backend = self.backend + local backend = self.backends for _, name in ipairs(backend) do if result[name] then @@ -64,4 +73,6 @@ function M:get_available_result() end end +---@class Trans +---@field data TransData return M diff --git a/lua/Trans/core/define.lua b/lua/Trans/core/define.lua index 0110336..540ed0b 100644 --- a/lua/Trans/core/define.lua +++ b/lua/Trans/core/define.lua @@ -1,4 +1,9 @@ +---@class Trans +---@field define TransDefine + +---@class TransDefine return { + ---@type TransMode[] modes = { 'normal', 'visual', diff --git a/lua/Trans/core/frontend.lua b/lua/Trans/core/frontend.lua index e53c35a..cb8a83b 100644 --- a/lua/Trans/core/frontend.lua +++ b/lua/Trans/core/frontend.lua @@ -3,16 +3,18 @@ local conf = Trans.conf local frontend_opts = conf.frontend +---Setup frontend Keymaps +---@param frontend TransFrontend local function set_frontend_keymap(frontend) local set = vim.keymap.set - local keymap_opts = { silent = true, expr = true } + local keymap_opts = { silent = true, expr = false, } - for action, key in pairs(frontend.opts.keymap) do + for action, key in pairs(frontend.opts.keymaps) do set('n', key, function() local instance = frontend.get_active_instance() if instance then - instance:execute(action) + coroutine.wrap(instance.execute)(instance, action) else return key end @@ -21,11 +23,21 @@ local function set_frontend_keymap(frontend) end -local M = setmetatable({}, { +---@class TransFrontend +---@field opts TransFrontendOpts +---@field get_active_instance fun():TransFrontend? +---@field process fun(data: TransData, result: TransResult) +---@field wait fun(self: TransFrontend, result: TransResult, name: string, timeout: integer) +---@field execute fun(action: string) @Execute action for frontend instance + +---@class Trans +---@field frontend TransFrontend +return setmetatable({}, { __index = function(self, name) local opts = vim.tbl_extend('keep', frontend_opts[name] or {}, frontend_opts.default) - local frontend = require('Trans.frontend.' .. name) + ---@type TransFrontend + local frontend = require('Trans.frontend.' .. name) frontend.opts = opts self[name] = frontend @@ -35,7 +47,3 @@ local M = setmetatable({}, { return frontend end }) - - - -return M diff --git a/lua/Trans/core/install.lua b/lua/Trans/core/install.lua index ea01db3..ccce316 100644 --- a/lua/Trans/core/install.lua +++ b/lua/Trans/core/install.lua @@ -1,10 +1,13 @@ +---@class Trans +---@field install fun() Download database and tts dependencies return function() local Trans = require('Trans') -- INFO :Check ultimate.db exists local dir = Trans.conf.dir local path = dir .. '/ultimate.db' + local fn = vim.fn - if vim.fn.filereadable(path) == 1 then + if fn.filereadable(path) == 1 then vim.notify('Database already exists', vim.log.WARN) return else @@ -16,8 +19,8 @@ return function() local uri = 'https://github.com/skywind3000/ECDICT-ultimate/releases/download/1.0.0/ecdict-ultimate-sqlite.zip' local loc = dir .. '/ultimate.zip' local handle = function(output) - if output.exit == 0 and vim.fn.filereadable(loc) then - if vim.fn.executable('unzip') == 0 then + if output.exit == 0 and fn.filereadable(loc) then + if fn.executable('unzip') == 0 then vim.notify('unzip not found, Please unzip ' .. loc .. 'manually', vim.log.ERROR) return end @@ -42,7 +45,7 @@ return function() }) -- INFO : Install tts dependencies - if vim.fn.has('linux') == 0 and vim.fn.has('mac') == 0 then + if fn.has('linux') == 0 and fn.has('mac') == 0 then os.execute('cd ./tts/ && npm install') end end diff --git a/lua/Trans/core/setup.lua b/lua/Trans/core/setup.lua index 4333ab1..6b2a7a8 100644 --- a/lua/Trans/core/setup.lua +++ b/lua/Trans/core/setup.lua @@ -58,7 +58,6 @@ local function set_frontend_opts(conf) end - local function define_highlights(conf) local set_hl = vim.api.nvim_set_hl local highlights = Trans.style.theme[conf.style.theme] @@ -68,7 +67,12 @@ local function define_highlights(conf) end - +---@alias TransMode +---|'normal' # Normal mode +---|'visual' # Visual mode +---|'input' # Input mode +---@class Trans +---@field setup fun(opts: { mode: string, mode: TransMode }) return function(opts) if opts then Trans.conf = vim.tbl_deep_extend('force', Trans.conf, opts) diff --git a/lua/Trans/core/translate.lua b/lua/Trans/core/translate.lua index 1e55121..b55e8f1 100644 --- a/lua/Trans/core/translate.lua +++ b/lua/Trans/core/translate.lua @@ -1,9 +1,9 @@ local Trans = require('Trans') local util = Trans.util - local function init_opts(opts) opts = opts or {} + ---@type TransMode opts.mode = opts.mode or ({ n = 'normal', v = 'visual', @@ -14,11 +14,14 @@ local function init_opts(opts) end +---To Query All Backends +---@param data TransData +---@return TransResult? @return nil if no result local function do_query(data) -- HACK :Rewrite this function to support multi requests local frontend = data.frontend local result = data.result - for _, backend in ipairs(data.backend) do + for _, backend in ipairs(data.backends) do local name = backend.name if backend.no_wait then backend.query(data) @@ -39,7 +42,6 @@ end -- HACK : Core process logic local function process(opts) - Trans.translate = coroutine.wrap(process) opts = init_opts(opts) local str = opts.str if not str or str == '' then return end @@ -64,4 +66,9 @@ local function process(opts) data.frontend:process(data, result) end -return coroutine.wrap(process) + +---@class Trans +---@field translate fun(opts: { word: string, mode: string?}) Translate string core function +return function(opts) + coroutine.wrap(process)(opts) +end diff --git a/lua/Trans/core/util.lua b/lua/Trans/core/util.lua index 84702ab..f26df32 100644 --- a/lua/Trans/core/util.lua +++ b/lua/Trans/core/util.lua @@ -1,7 +1,15 @@ -M = require('Trans').metatable('util') +---@class Trans +---@field util TransUtil +local Trans = require('Trans') local fn, api = vim.fn, vim.api +---@class TransUtil +local M = require('Trans').metatable('util') + + +---Get selected text +---@return string function M.get_select() local _start = fn.getpos("v") local _end = fn.getpos('.') @@ -33,7 +41,7 @@ function M.get_select() end ---Get Text which need to be translated ----@param mode string 'n' | 'v' | 'i' +---@param mode TransMode ---@return string function M.get_str(mode) if mode == 'n' or mode == 'normal' then @@ -48,9 +56,8 @@ function M.get_str(mode) end end - ---Puase coroutine for {ms} milliseconds ----@param ms integer milliseconds +---@param ms integer function M.pause(ms) local co = coroutine.running() vim.defer_fn(function() @@ -59,9 +66,8 @@ function M.pause(ms) coroutine.yield() end - ---Detect whether the string is English ----@param str string string to detect +---@param str string ---@return boolean function M.is_English(str) local char = { str:byte(1, -1) } @@ -73,4 +79,4 @@ function M.is_English(str) return true end -return M +Trans.util = M diff --git a/lua/Trans/core/window.lua b/lua/Trans/core/window.lua index 9acdea9..2227ba0 100644 --- a/lua/Trans/core/window.lua +++ b/lua/Trans/core/window.lua @@ -1,18 +1,11 @@ local api = vim.api local Trans = require("Trans") - ----@class win ----@field win_opts table window config [**When open**] ----@field winid integer window handle ----@field ns integer namespace for highlight ----@field animation table window animation ----@field enter boolean cursor should [enter] window when open ----@field buffer buffer attached buffer object +---@class TransWindow local window = {} ---Change window attached buffer ----@param buf buf +---@param buf TransBuffer function window:set_buf(buf) api.nvim_win_set_buf(self.winid, buf.bufnr) self.buf = buf @@ -25,11 +18,11 @@ function window:is_valid() end ---Set window option ----@param option string option name +---@param name string option name ---@param value any -function window:set(option, value) +function window:set(name, value) if self:is_valid() then - api.nvim_win_set_option(self.winid, option, value) + api.nvim_win_set_option(self.winid, name, value) end end @@ -50,19 +43,25 @@ function window:set_width(width) end ---Get window width +---@return integer function window:width() return api.nvim_win_get_width(self.winid) end ---Get window height +---@return integer function window:height() return api.nvim_win_get_height(self.winid) end +-- TODO : +-- function window:adjust() + +-- end + ---Expand window [width | height] value ----@param opts table ----|'field'string [width | height] ----|'target'integer +---@param opts +---|{ field: string, to: integer} function window:smooth_expand(opts) local field = opts.field -- width | height local from = api['nvim_win_get_' .. field](self.winid) @@ -76,18 +75,19 @@ function window:smooth_expand(opts) local wrap = self:option('wrap') - local interval = self.animation.interval for i = from + 1, to, (from < to and 1 or -1) do self:set('wrap', false) method(self.winid, i) pause(interval) end - self:set('wrap', wrap) end -function M:resize(opts) +---Resize window +---@param opts +---|{ width: integer, height: integer } +function window:resize(opts) local width = opts[1] local height = opts[2] @@ -126,9 +126,9 @@ function window:try_close() api.nvim_win_close(self.winid, true) end ----set window local highlight group +---Set window local highlight group ---@param name string ----@param opts table +---@param opts table highlight config function window:set_hl(name, opts) api.nvim_set_hl(self.ns, name, opts) end @@ -155,20 +155,22 @@ function window:open() end end - -function window:center(node) - -- TODO : - print('TODO Center') - -- local text = node[1] - -- local width = text:width() - -- local win_width = self.width - -- local space = math.max(math.floor((win_width - width) / 2), 0) - -- node[1] = (' '):rep(space) .. text - -- return node -end +-- function window:center(node) +-- -- TODO : +-- print('TODO Center') +-- -- local text = node[1] +-- -- local width = text:width() +-- -- local win_width = self.width +-- -- local space = math.max(math.floor((win_width - width) / 2), 0) +-- -- node[1] = (' '):rep(space) .. text +-- -- return node +-- end window.__index = window + + +---@class TransWindowOpts local default_opts = { enter = false, winid = -1, @@ -180,23 +182,33 @@ local default_opts = { }, } +---@class TransWindow +---@field buffer TransBuffer attached buffer object +---@field win_opts table window config [**When open**] +---@field private winid integer window handle +---@field ns integer namespace for highlight +---@field enter boolean cursor should [enter] window when open +---@field animation +---|{open: string | boolean, close: string | boolean, interval: integer} Hover Window Animation + ---Create new window ----@param opts table window config ----@return win +---@param opts TransWindowOpts window config +---@return TransWindow function window.new(opts) opts = vim.tbl_deep_extend('keep', opts, default_opts) local win = setmetatable(opts, window) + ---@cast win TransWindow + win:open() return win end +---@class Trans +---@field window TransWindow return window - -- local win_opt = { --- focusable = false, --- style = 'minimal', -- zindex = zindex, -- width = width, -- height = height, @@ -206,25 +218,3 @@ return window -- title = title, -- relative = relative, -- } - --- if field then --- win_opt[field] = 1 --- end - --- if win_opt.title then --- win_opt.title_pos = 'center' --- end - --- local win = setmetatable({ --- buf = buf, --- ns = ns, --- height = win_opt.height, --- width = win_opt.width, --- animation = animation, --- winid = api.nvim_open_win(buf.bufnr, enter, win_opt), --- }, window) - --- return win, win:expand { --- field = field, --- target = opts[field], --- } diff --git a/lua/Trans/frontend/hover/execute.lua b/lua/Trans/frontend/hover/execute.lua index b6bd2d7..c0c1f54 100644 --- a/lua/Trans/frontend/hover/execute.lua +++ b/lua/Trans/frontend/hover/execute.lua @@ -20,8 +20,7 @@ local strategy = { } - return function(hover, action) -- TODO : - coroutine.wrap(strategy[action])(hover) + strategy[action](hover) end diff --git a/lua/Trans/frontend/hover/init.lua b/lua/Trans/frontend/hover/init.lua index 12c9d30..0168700 100644 --- a/lua/Trans/frontend/hover/init.lua +++ b/lua/Trans/frontend/hover/init.lua @@ -1,10 +1,10 @@ local Trans = require('Trans') ---@class hover ----@field queue table @hover queue for all hover instances ----@field buffer buf @buffer for hover window +---@field buffer TransBuffer @buffer for hover window +---@field window TransWindow @hover window +---@field queue hover[] @hover queue for all hover instances ---@field destroy_funcs table @functions to be executed when hover window is closed ----@field window window @hover window ---@field opts table @options for hover window ---@field opts.title string @title for hover window ---@field opts.width number @width for hover window @@ -18,8 +18,6 @@ local Trans = require('Trans') ---@field opts.icon.no string @icon for no ---@field opts.icon.star string @icon for star ---@field opts.icon.cell string @icon for cell used in waitting animation - - local M = Trans.metatable('frontend.hover', { ns = vim.api.nvim_create_namespace('TransHoverWin'), queue = {}, @@ -67,7 +65,6 @@ function M:destroy() if self.buffer:is_valid() then self.buffer:destroy() end end - ---Init hover window ---@param opts table? @window options: width, height ---@return unknown diff --git a/lua/Trans/health.lua b/lua/Trans/health.lua index 2ad3350..ad06449 100644 --- a/lua/Trans/health.lua +++ b/lua/Trans/health.lua @@ -31,7 +31,6 @@ local function check_plugin_dependencies() end end - local function check_binary_dependencies() local binary_dependencies = { 'curl', diff --git a/lua/Trans/init.lua b/lua/Trans/init.lua index db79277..d3a9980 100644 --- a/lua/Trans/init.lua +++ b/lua/Trans/init.lua @@ -1,13 +1,11 @@ ---Set or Get metatable which will find module in folder ---@param folder_name string ----@param origin table? +---@param origin table? table to be set metatable ---@return table local function metatable(folder_name, origin) return setmetatable(origin or {}, { __index = function(tbl, key) - local status, result = pcall(require, ('Trans.%s.%s'):format(folder_name, key)) - if not status then return end - + local result = require(('Trans.%s.%s'):format(folder_name, key)) tbl[key] = result return result end @@ -15,11 +13,19 @@ local function metatable(folder_name, origin) end -local M = metatable('core') +---@class string +---@field width function @Get string display width +---@field play function @Use tts to play string + + +---@class Trans +---@field style table @Style module +---@field cache table @Cache for translated data object +local M = metatable('core', { + style = metatable("style"), + cache = {}, +}) M.metatable = metatable -M.style = metatable("style") - -M.cache = {} return M diff --git a/plugin/Trans.lua b/plugin/Trans.lua index f858228..6cf5eaf 100644 --- a/plugin/Trans.lua +++ b/plugin/Trans.lua @@ -1,5 +1,6 @@ local api, fn = vim.api, vim.fn + string.width = api.nvim_strwidth --- INFO :Define string play method if fn.has('linux') == 1 then