From d95bb8c8e51fabbd45f5054bda1ee04a34e8661d Mon Sep 17 00:00:00 2001
From: JuanZoran <1430359574@qq.com>
Date: Mon, 30 Jan 2023 12:01:58 +0800
Subject: [PATCH 1/2] feat: add simple baidu query api support

---
 .gitignore                 |   1 -
 README.md                  |   5 +-
 install.sh                 |   5 +
 lua/Trans/content.lua      |   5 +-
 lua/Trans/init.lua         |  21 +-
 lua/Trans/query/baidu.lua  |  49 +++++
 lua/Trans/query/youdao.lua |  48 +++++
 lua/Trans/util/base64.lua  |  39 ++++
 lua/Trans/util/md5.lua     | 431 +++++++++++++++++++++++++++++++++++++
 lua/Trans/view/float.lua   |   2 +
 lua/Trans/view/hover.lua   |  58 +++--
 lua/Trans/window.lua       |  19 +-
 12 files changed, 645 insertions(+), 38 deletions(-)
 create mode 100644 lua/Trans/query/baidu.lua
 create mode 100644 lua/Trans/query/youdao.lua
 create mode 100644 lua/Trans/util/base64.lua
 create mode 100644 lua/Trans/util/md5.lua

diff --git a/.gitignore b/.gitignore
index 70d18fb..931afad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
-lua/Trans/util/
 lua/Trans/test/
 note/
 demo.mp4
diff --git a/README.md b/README.md
index a839be1..a3ca0a9 100644
--- a/README.md
+++ b/README.md
@@ -69,7 +69,7 @@ https://user-images.githubusercontent.com/107862700/213752097-2eee026a-ddee-4531
 use {
     'JuanZoran/Trans.nvim'
     run = 'bash ./install.sh',
-    requires = 'kharji/sqlite.lua',
+    requires = 'kkharji/sqlite.lua',
     -- 如果你不需要任何配置的话, 可以直接按照下面的方式启动
     config = function ()
         require'Trans'.setup{
@@ -89,7 +89,7 @@ use {
         { 'n', 'mi' },
     },
     run = 'bash ./install.sh', -- 自动下载使用的本地词库
-    requires = 'kharji/sqlite.lua',
+    requires = 'kkharji/sqlite.lua',
     config = function()
         require("Trans").setup {} -- 启动Trans
         vim.keymap.set({"v", 'n'}, "mm", '<Cmd>Translate<CR>', { desc = ' Translate' }) -- 自动判断virtual 还是 normal 模式
@@ -146,6 +146,7 @@ use {
     - 需要确保安装了`nodejs`
     - 进入插件的`tts`目录运行`npm install`
         > 如果`install.sh`运行正常则自动安装,如果安装失败,请尝试手动安装
+- `title`的配置,只对`neovim 0.9`版本有效
 
 ## Festival配置
 > 仅针对`linux`用户说明
diff --git a/install.sh b/install.sh
index 94d0570..57f24e0 100755
--- a/install.sh
+++ b/install.sh
@@ -1,6 +1,11 @@
 #!/usr/bin/env bash
 set -e
 
+if test -e "$HOME/.vim/dict/ultimate.db"; then
+    exit
+fi
+
+
 mkdir -p "$HOME/.vim/dict"
 
 wget https://github.com/skywind3000/ECDICT-ultimate/releases/download/1.0.0/ecdict-ultimate-sqlite.zip -O /tmp/dict.zip
diff --git a/lua/Trans/content.lua b/lua/Trans/content.lua
index c0a961c..8852511 100644
--- a/lua/Trans/content.lua
+++ b/lua/Trans/content.lua
@@ -85,7 +85,10 @@ local content = {
         end
 
         local space = math.floor(((self.window.width - width) / (size - 1)))
-        assert(space > 0, 'try to expand window size')
+        if space > 0 then
+            return
+        end
+
         local interval = (' '):rep(space)
         return setmetatable({
             text = table.concat(strs, interval),
diff --git a/lua/Trans/init.lua b/lua/Trans/init.lua
index a2cb24c..37d4828 100644
--- a/lua/Trans/init.lua
+++ b/lua/Trans/init.lua
@@ -1,11 +1,11 @@
 local M = {}
 
-
 local title = vim.fn.has('nvim-0.9') == 1 and {
     { '', 'TransTitleRound' },
     { ' Trans', 'TransTitle' },
     { '', 'TransTitleRound' },
-} or ' Trans'
+} or nil
+
 
 M.conf = {
     view = {
@@ -19,7 +19,6 @@ M.conf = {
         border = 'rounded',
         title = title,
         keymap = {
-            -- TODO :
             pageup = '[[',
             pagedown = ']]',
             pin = '<leader>[',
@@ -40,6 +39,7 @@ M.conf = {
             'BufLeave',
         },
         auto_play = true,
+        timeout = 3000,
     },
     float = {
         width = 0.8,
@@ -87,6 +87,17 @@ M.conf = {
 
     db_path = '$HOME/.vim/dict/ultimate.db',
 
+    engine = {
+        baidu = {
+            appid = '',
+            appPasswd = '',
+        },
+        -- youdao = {
+        --     appkey = '',
+        --     appPasswd = '',
+        -- },
+    },
+
     -- TODO :
     -- register word
     -- history = {
@@ -103,11 +114,11 @@ M.setup = function(opts)
 
     local float = M.conf.float
 
-    if float.height < 0 and float.height <= 1 then
+    if 0 < float.height and float.height <= 1 then
         float.height = math.floor((vim.o.lines - vim.o.cmdheight - 1) * float.height)
     end
 
-    if float.width < 0 and float.width <= 1 then
+    if 0 < float.width and float.width <= 1 then
         float.width = math.floor(vim.o.columns * float.width)
     end
 
diff --git a/lua/Trans/query/baidu.lua b/lua/Trans/query/baidu.lua
new file mode 100644
index 0000000..0746ca0
--- /dev/null
+++ b/lua/Trans/query/baidu.lua
@@ -0,0 +1,49 @@
+local baidu     = require('Trans').conf.engine.baidu
+local appid     = baidu.appid
+local appPasswd = baidu.appPasswd
+local salt      = tostring(math.random(bit.rshift(1, 5)))
+local uri       = 'https://fanyi-api.baidu.com/api/trans/vip/translate'
+
+if appid == '' or appPasswd == '' then
+    error('请查看README, 实现在线翻译或者设置将在线翻译设置为false')
+end
+
+local ok, curl = pcall(require, 'plenary.curl')
+if not ok then
+    error('plenary not found')
+end
+
+local function get_field(word)
+    local to   = 'zh'
+    local tmp  = appid .. word .. salt .. appPasswd
+    local sign = require('Trans.util.md5').sumhexa(tmp)
+
+    return {
+        q     = word,
+        from  = 'auto',
+        to    = to,
+        appid = appid,
+        salt  = salt,
+        sign  = sign,
+    }
+end
+
+return function(word)
+    local query = get_field(word)
+    local output = curl.post(uri, {
+        data = query,
+        headers = {
+            content_type = "application/x-www-form-urlencoded",
+        }
+    })
+
+    if output.exit == 0 and output.status == 200 then
+        local res = vim.fn.json_decode(output.body)
+        if res and res.trans_result then
+            return {
+                word = word,
+                translation = res.trans_result[1].dst,
+            }
+        end
+    end
+end
diff --git a/lua/Trans/query/youdao.lua b/lua/Trans/query/youdao.lua
new file mode 100644
index 0000000..43e7dfd
--- /dev/null
+++ b/lua/Trans/query/youdao.lua
@@ -0,0 +1,48 @@
+local youdao    = require("Trans").conf.engine.youdao
+local appKey    = youdao.appKey
+local appPasswd = youdao.appPasswd
+local uri       = 'https://openapi.youdao.com/api'
+local salt      = tostring(math.random(bit.rshift(1, 5)))
+
+
+local ok, curl = pcall(require, 'plenary.curl')
+if not ok then
+    error('plenary not found')
+end
+
+
+local function get_field(word)
+    local len     = #word
+    local curtime = tostring(os.time())
+    local input   = len > 20 and
+        word:sub(1, 10) .. len .. word:sub(-10) or word
+
+    -- sign=sha256(应用ID+input+salt+curtime+应用密钥);
+    local hash = appKey .. input .. salt .. curtime .. appPasswd
+    local sign = vim.fn.sha256(hash)
+
+    return {
+        q        = word,
+        from     = 'auto',
+        to       = 'zh-CHS',
+        signType = 'v3',
+        appKey   = appKey,
+        salt     = salt,
+        curtime  = curtime,
+        sign     = sign,
+    }
+end
+
+return function(word)
+    -- return result
+    local field  = get_field(word)
+    local output = curl.post(uri, {
+        body = field,
+    })
+    if output.exit == 0 and output.status == 200 then
+        local result = vim.fn.json_decode(output.body)
+        if result and result.errorCode == 0 then
+            --- TODO :
+        end
+    end
+end
diff --git a/lua/Trans/util/base64.lua b/lua/Trans/util/base64.lua
new file mode 100644
index 0000000..417f8e9
--- /dev/null
+++ b/lua/Trans/util/base64.lua
@@ -0,0 +1,39 @@
+local ffi = require('ffi')
+local base64 = {}
+
+local b64 = ffi.new('unsigned const char[65]',
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
+
+function base64.encode(str)
+    local band, bor, lsh, rsh = bit.band, bit.bor, bit.lshift, bit.rshift
+    local len = #str
+    local enc_len = 4 * math.ceil(len / 3) -- (len + 2) // 3 * 4 after Lua 5.3
+
+    local src = ffi.new('unsigned const char[?]', len+1, str)
+    local enc = ffi.new('unsigned char[?]', enc_len+1)
+
+    local i, j = 0, 0
+    while i < len-2 do
+        enc[j] = b64[band(rsh(src[i], 2), 0x3F)]
+        enc[j+1] = b64[bor(lsh(band(src[i], 0x3), 4), rsh(band(src[i+1], 0xF0), 4))]
+        enc[j+2] = b64[bor(lsh(band(src[i+1], 0xF), 2), rsh(band(src[i+2], 0xC0), 6))]
+        enc[j+3] = b64[band(src[i+2], 0x3F)]
+        i, j = i+3, j+4
+    end
+
+    if i < len then
+        enc[j] = b64[band(rsh(src[i], 2), 0x3F)]
+        if i == len-1 then
+            enc[j+1] = b64[lsh(band(src[i], 0x3), 4)]
+            enc[j+2] = 0x3D
+        else
+            enc[j+1] = b64[bor(lsh(band(src[i], 0x3), 4), rsh(band(src[i+1], 0xF0), 4))]
+            enc[j+2] = b64[lsh(band(src[i+1], 0xF), 2)]
+        end
+        enc[j+3] = 0x3D
+    end
+
+    return ffi.string(enc, enc_len)
+end
+
+return base64
diff --git a/lua/Trans/util/md5.lua b/lua/Trans/util/md5.lua
new file mode 100644
index 0000000..4b3fdfb
--- /dev/null
+++ b/lua/Trans/util/md5.lua
@@ -0,0 +1,431 @@
+local md5 = {}
+-- local md5 = {
+--     _VERSION     = "md5.lua 1.1.0",
+--     _DESCRIPTION = "MD5 computation in Lua (5.1-3, LuaJIT)",
+--     _URL         = "https://github.com/kikito/md5.lua",
+--     _LICENSE     = [[
+--     MIT LICENSE
+--
+--     Copyright (c) 2013 Enrique García Cota + Adam Baldwin + hanzao + Equi 4 Software
+--
+--     Permission is hereby granted, free of charge, to any person obtaining a
+--     copy of this software and associated documentation files (the
+--     "Software"), to deal in the Software without restriction, including
+--     without limitation the rights to use, copy, modify, merge, publish,
+--     distribute, sublicense, and/or sell copies of the Software, and to
+--     permit persons to whom the Software is furnished to do so, subject to
+--     the following conditions:
+--
+--     The above copyright notice and this permission notice shall be included
+--     in all copies or substantial portions of the Software.
+--
+--     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+--     OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+--     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+--     IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+--     CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+--     TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+--     SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+--   ]]
+-- }
+
+-- bit lib implementions
+
+local char, byte, format, rep, sub =
+string.char, string.byte, string.format, string.rep, string.sub
+local bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift
+
+local ok, bit = pcall(require, 'bit')
+local ok_ffi, ffi = pcall(require, 'ffi')
+if ok then
+    bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift = bit.bor, bit.band, bit.bnot, bit.bxor, bit.rshift,
+        bit.lshift
+else
+    ok, bit = pcall(require, 'bit32')
+
+    if ok then
+
+        bit_not = bit.bnot
+
+        local tobit = function(n)
+            return n <= 0x7fffffff and n or -(bit_not(n) + 1)
+        end
+
+        local normalize = function(f)
+            return function(a, b) return tobit(f(tobit(a), tobit(b))) end
+        end
+
+        bit_or, bit_and, bit_xor = normalize(bit.bor), normalize(bit.band), normalize(bit.bxor)
+        bit_rshift, bit_lshift = normalize(bit.rshift), normalize(bit.lshift)
+
+    else
+
+        local function tbl2number(tbl)
+            local result = 0
+            local power = 1
+            for i = 1, #tbl do
+                result = result + tbl[i] * power
+                power = power * 2
+            end
+            return result
+        end
+
+        local function expand(t1, t2)
+            local big, small = t1, t2
+            if (#big < #small) then
+                big, small = small, big
+            end
+            -- expand small
+            for i = #small + 1, #big do
+                small[i] = 0
+            end
+        end
+
+        local to_bits -- needs to be declared before bit_not
+
+        bit_not = function(n)
+            local tbl = to_bits(n)
+            local size = math.max(#tbl, 32)
+            for i = 1, size do
+                if (tbl[i] == 1) then
+                    tbl[i] = 0
+                else
+                    tbl[i] = 1
+                end
+            end
+            return tbl2number(tbl)
+        end
+
+        -- defined as local above
+        to_bits = function(n)
+            if (n < 0) then
+                -- negative
+                return to_bits(bit_not(math.abs(n)) + 1)
+            end
+            -- to bits table
+            local tbl = {}
+            local cnt = 1
+            local last
+            while n > 0 do
+                last     = n % 2
+                tbl[cnt] = last
+                n        = (n - last) / 2
+                cnt      = cnt + 1
+            end
+
+            return tbl
+        end
+
+        bit_or = function(m, n)
+            local tbl_m = to_bits(m)
+            local tbl_n = to_bits(n)
+            expand(tbl_m, tbl_n)
+
+            local tbl = {}
+            for i = 1, #tbl_m do
+                if (tbl_m[i] == 0 and tbl_n[i] == 0) then
+                    tbl[i] = 0
+                else
+                    tbl[i] = 1
+                end
+            end
+
+            return tbl2number(tbl)
+        end
+
+        bit_and = function(m, n)
+            local tbl_m = to_bits(m)
+            local tbl_n = to_bits(n)
+            expand(tbl_m, tbl_n)
+
+            local tbl = {}
+            for i = 1, #tbl_m do
+                if (tbl_m[i] == 0 or tbl_n[i] == 0) then
+                    tbl[i] = 0
+                else
+                    tbl[i] = 1
+                end
+            end
+
+            return tbl2number(tbl)
+        end
+
+        bit_xor = function(m, n)
+            local tbl_m = to_bits(m)
+            local tbl_n = to_bits(n)
+            expand(tbl_m, tbl_n)
+
+            local tbl = {}
+            for i = 1, #tbl_m do
+                if (tbl_m[i] ~= tbl_n[i]) then
+                    tbl[i] = 1
+                else
+                    tbl[i] = 0
+                end
+            end
+
+            return tbl2number(tbl)
+        end
+
+        bit_rshift = function(n, bits)
+            local high_bit = 0
+            if (n < 0) then
+                -- negative
+                n = bit_not(math.abs(n)) + 1
+                high_bit = 0x80000000
+            end
+
+            local floor = math.floor
+
+            for i = 1, bits do
+                n = n / 2
+                n = bit_or(floor(n), high_bit)
+            end
+            return floor(n)
+        end
+
+        bit_lshift = function(n, bits)
+            if (n < 0) then
+                -- negative
+                n = bit_not(math.abs(n)) + 1
+            end
+
+            for i = 1, bits do
+                n = n * 2
+            end
+            return bit_and(n, 0xFFFFFFFF)
+        end
+    end
+end
+
+-- convert little-endian 32-bit int to a 4-char string
+local lei2str
+-- function is defined this way to allow full jit compilation (removing UCLO instruction in LuaJIT)
+if ok_ffi then
+    local ct_IntType = ffi.typeof("int[1]")
+    lei2str = function(i) return ffi.string(ct_IntType(i), 4) end
+else
+    lei2str = function(i)
+        local f = function(s) return char(bit_and(bit_rshift(i, s), 255)) end
+        return f(0) .. f(8) .. f(16) .. f(24)
+    end
+end
+
+
+
+-- convert raw string to big-endian int
+local function str2bei(s)
+    local v = 0
+    for i = 1, #s do
+        v = v * 256 + byte(s, i)
+    end
+    return v
+end
+
+-- convert raw string to little-endian int
+local str2lei
+
+if ok_ffi then
+    local ct_constcharptr = ffi.typeof("const char*")
+    local ct_constintptr = ffi.typeof("const int*")
+    str2lei = function(s)
+        local int = ct_constcharptr(s)
+        return ffi.cast(ct_constintptr, int)[0]
+    end
+else
+    str2lei = function(s)
+        local v = 0
+        for i = #s, 1, -1 do
+            v = v * 256 + byte(s, i)
+        end
+        return v
+    end
+end
+
+
+-- cut up a string in little-endian ints of given size
+local function cut_le_str(s)
+    return {
+        str2lei(sub(s, 1, 4)),
+        str2lei(sub(s, 5, 8)),
+        str2lei(sub(s, 9, 12)),
+        str2lei(sub(s, 13, 16)),
+        str2lei(sub(s, 17, 20)),
+        str2lei(sub(s, 21, 24)),
+        str2lei(sub(s, 25, 28)),
+        str2lei(sub(s, 29, 32)),
+        str2lei(sub(s, 33, 36)),
+        str2lei(sub(s, 37, 40)),
+        str2lei(sub(s, 41, 44)),
+        str2lei(sub(s, 45, 48)),
+        str2lei(sub(s, 49, 52)),
+        str2lei(sub(s, 53, 56)),
+        str2lei(sub(s, 57, 60)),
+        str2lei(sub(s, 61, 64)),
+    }
+end
+
+-- An MD5 mplementation in Lua, requires bitlib (hacked to use LuaBit from above, ugh)
+-- 10/02/2001 jcw@equi4.com
+
+local CONSTS = {
+    0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
+    0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
+    0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+    0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
+    0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
+    0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
+    0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
+    0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
+    0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+    0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
+    0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
+    0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+    0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
+    0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
+    0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+    0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
+    0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476
+}
+
+local f = function(x, y, z) return bit_or(bit_and(x, y), bit_and(-x - 1, z)) end
+local g = function(x, y, z) return bit_or(bit_and(x, z), bit_and(y, -z - 1)) end
+local h = function(x, y, z) return bit_xor(x, bit_xor(y, z)) end
+local i = function(x, y, z) return bit_xor(y, bit_or(x, -z - 1)) end
+local z = function(ff, a, b, c, d, x, s, ac)
+    a = bit_and(a + ff(b, c, d) + x + ac, 0xFFFFFFFF)
+    -- be *very* careful that left shift does not cause rounding!
+    return bit_or(bit_lshift(bit_and(a, bit_rshift(0xFFFFFFFF, s)), s), bit_rshift(a, 32 - s)) + b
+end
+
+local function transform(A, B, C, D, X)
+    local a, b, c, d = A, B, C, D
+    local t = CONSTS
+
+    a = z(f, a, b, c, d, X[0], 7, t[1])
+    d = z(f, d, a, b, c, X[1], 12, t[2])
+    c = z(f, c, d, a, b, X[2], 17, t[3])
+    b = z(f, b, c, d, a, X[3], 22, t[4])
+    a = z(f, a, b, c, d, X[4], 7, t[5])
+    d = z(f, d, a, b, c, X[5], 12, t[6])
+    c = z(f, c, d, a, b, X[6], 17, t[7])
+    b = z(f, b, c, d, a, X[7], 22, t[8])
+    a = z(f, a, b, c, d, X[8], 7, t[9])
+    d = z(f, d, a, b, c, X[9], 12, t[10])
+    c = z(f, c, d, a, b, X[10], 17, t[11])
+    b = z(f, b, c, d, a, X[11], 22, t[12])
+    a = z(f, a, b, c, d, X[12], 7, t[13])
+    d = z(f, d, a, b, c, X[13], 12, t[14])
+    c = z(f, c, d, a, b, X[14], 17, t[15])
+    b = z(f, b, c, d, a, X[15], 22, t[16])
+
+    a = z(g, a, b, c, d, X[1], 5, t[17])
+    d = z(g, d, a, b, c, X[6], 9, t[18])
+    c = z(g, c, d, a, b, X[11], 14, t[19])
+    b = z(g, b, c, d, a, X[0], 20, t[20])
+    a = z(g, a, b, c, d, X[5], 5, t[21])
+    d = z(g, d, a, b, c, X[10], 9, t[22])
+    c = z(g, c, d, a, b, X[15], 14, t[23])
+    b = z(g, b, c, d, a, X[4], 20, t[24])
+    a = z(g, a, b, c, d, X[9], 5, t[25])
+    d = z(g, d, a, b, c, X[14], 9, t[26])
+    c = z(g, c, d, a, b, X[3], 14, t[27])
+    b = z(g, b, c, d, a, X[8], 20, t[28])
+    a = z(g, a, b, c, d, X[13], 5, t[29])
+    d = z(g, d, a, b, c, X[2], 9, t[30])
+    c = z(g, c, d, a, b, X[7], 14, t[31])
+    b = z(g, b, c, d, a, X[12], 20, t[32])
+
+    a = z(h, a, b, c, d, X[5], 4, t[33])
+    d = z(h, d, a, b, c, X[8], 11, t[34])
+    c = z(h, c, d, a, b, X[11], 16, t[35])
+    b = z(h, b, c, d, a, X[14], 23, t[36])
+    a = z(h, a, b, c, d, X[1], 4, t[37])
+    d = z(h, d, a, b, c, X[4], 11, t[38])
+    c = z(h, c, d, a, b, X[7], 16, t[39])
+    b = z(h, b, c, d, a, X[10], 23, t[40])
+    a = z(h, a, b, c, d, X[13], 4, t[41])
+    d = z(h, d, a, b, c, X[0], 11, t[42])
+    c = z(h, c, d, a, b, X[3], 16, t[43])
+    b = z(h, b, c, d, a, X[6], 23, t[44])
+    a = z(h, a, b, c, d, X[9], 4, t[45])
+    d = z(h, d, a, b, c, X[12], 11, t[46])
+    c = z(h, c, d, a, b, X[15], 16, t[47])
+    b = z(h, b, c, d, a, X[2], 23, t[48])
+
+    a = z(i, a, b, c, d, X[0], 6, t[49])
+    d = z(i, d, a, b, c, X[7], 10, t[50])
+    c = z(i, c, d, a, b, X[14], 15, t[51])
+    b = z(i, b, c, d, a, X[5], 21, t[52])
+    a = z(i, a, b, c, d, X[12], 6, t[53])
+    d = z(i, d, a, b, c, X[3], 10, t[54])
+    c = z(i, c, d, a, b, X[10], 15, t[55])
+    b = z(i, b, c, d, a, X[1], 21, t[56])
+    a = z(i, a, b, c, d, X[8], 6, t[57])
+    d = z(i, d, a, b, c, X[15], 10, t[58])
+    c = z(i, c, d, a, b, X[6], 15, t[59])
+    b = z(i, b, c, d, a, X[13], 21, t[60])
+    a = z(i, a, b, c, d, X[4], 6, t[61])
+    d = z(i, d, a, b, c, X[11], 10, t[62])
+    c = z(i, c, d, a, b, X[2], 15, t[63])
+    b = z(i, b, c, d, a, X[9], 21, t[64])
+
+    return bit_and(A + a, 0xFFFFFFFF), bit_and(B + b, 0xFFFFFFFF),
+        bit_and(C + c, 0xFFFFFFFF), bit_and(D + d, 0xFFFFFFFF)
+end
+
+----------------------------------------------------------------
+
+local function md5_update(self, s)
+    self.pos = self.pos + #s
+    s = self.buf .. s
+    for ii = 1, #s - 63, 64 do
+        local X = cut_le_str(sub(s, ii, ii + 63))
+        assert(#X == 16)
+        X[0] = table.remove(X, 1) -- zero based!
+        self.a, self.b, self.c, self.d = transform(self.a, self.b, self.c, self.d, X)
+    end
+    self.buf = sub(s, math.floor(#s / 64) * 64 + 1, #s)
+    return self
+end
+
+local function md5_finish(self)
+    local msgLen = self.pos
+    local padLen = 56 - msgLen % 64
+
+    if msgLen % 64 > 56 then padLen = padLen + 64 end
+
+    if padLen == 0 then padLen = 64 end
+
+    local s = char(128) ..
+        rep(char(0), padLen - 1) .. lei2str(bit_and(8 * msgLen, 0xFFFFFFFF)) .. lei2str(math.floor(msgLen / 0x20000000))
+    md5_update(self, s)
+
+    assert(self.pos % 64 == 0)
+    return lei2str(self.a) .. lei2str(self.b) .. lei2str(self.c) .. lei2str(self.d)
+end
+
+----------------------------------------------------------------
+
+function md5.new()
+    return { a = CONSTS[65], b = CONSTS[66], c = CONSTS[67], d = CONSTS[68],
+        pos = 0,
+        buf = '',
+        update = md5_update,
+        finish = md5_finish }
+end
+
+function md5.tohex(s)
+    return format("%08x%08x%08x%08x", str2bei(sub(s, 1, 4)), str2bei(sub(s, 5, 8)), str2bei(sub(s, 9, 12)),
+        str2bei(sub(s, 13, 16)))
+end
+
+function md5.sum(s)
+    return md5.new():update(s):finish()
+end
+
+function md5.sumhexa(s)
+    return md5.tohex(md5.sum(s))
+end
+
+return md5
diff --git a/lua/Trans/view/float.lua b/lua/Trans/view/float.lua
index 8cf0c7e..1a03212 100644
--- a/lua/Trans/view/float.lua
+++ b/lua/Trans/view/float.lua
@@ -47,6 +47,8 @@ local function set_title()
                 it('', round)
             )
         )
+
+        title:newline('')
     end
 end
 
diff --git a/lua/Trans/view/hover.lua b/lua/Trans/view/hover.lua
index ca2a6fb..1d21e85 100644
--- a/lua/Trans/view/hover.lua
+++ b/lua/Trans/view/hover.lua
@@ -4,6 +4,7 @@ local conf = require('Trans').conf
 local m_window
 local m_result
 local m_content
+
 -- content utility
 local node = require("Trans.node")
 local t = node.text
@@ -25,9 +26,12 @@ end
 local process = {
     title = function()
         local icon = conf.icon
+        local line
+        if m_result.word:find(' ', 1, true) then
+            line = it(m_result.word, 'TransWord')
 
-        m_content:addline(
-            m_content:format(
+        else
+            line = m_content:format(
                 it(m_result.word, 'TransWord'),
                 t(
                     it('['),
@@ -37,7 +41,9 @@ local process = {
                 it(m_result.collins and icon.star:rep(m_result.collins) or icon.notfound, 'TransCollins'),
                 it(m_result.oxford == 1 and icon.yes or icon.no)
             )
-        )
+
+        end
+        m_content:addline(line)
     end,
 
     tag = function()
@@ -211,7 +217,6 @@ action = {
             m_window:bufset('bufhidden', 'wipe')
             vim.keymap.del('n', conf.hover.keymap.pin, { buffer = true })
 
-
             --- NOTE : 只允许存在一个pin窗口
             local buf = m_window.bufnr
             pin = true
@@ -250,9 +255,11 @@ action = {
     end,
 
     play = vim.fn.has('linux') == 1 and function()
-        vim.fn.jobstart('echo ' .. m_result.word .. ' | festival --tts')
+        local cmd = ([[echo "%s" | festival --tts]]):format(m_result.word)
+        vim.fn.jobstart(cmd)
     end or function()
-        local file = debug.getinfo(1, "S").source:sub(2):match('(.*)lua/') .. 'tts/say.js'
+        local seperator = vim.fn.has('unix') and '/' or '\\'
+        local file = debug.getinfo(1, "S").source:sub(2):match('(.*)lua') .. seperator .. 'tts' .. seperator .. 'say.js'
         vim.fn.jobstart('node ' .. file .. ' ' .. m_result.word)
     end,
 }
@@ -263,7 +270,7 @@ return function(word)
         word = { word, 's' },
     }
 
-    m_result = require('Trans.query.offline')(word) -- 目前只处理了本地数据库的查询
+    m_result = require('Trans.query.offline')(word)
     local hover = conf.hover
 
     m_window = require("Trans.window")(false, {
@@ -279,8 +286,19 @@ return function(word)
     m_window.animation = hover.animation
     m_content = m_window.contents[1]
 
-    if m_result then
-        if hover.auto_play then action.play() end
+
+    -- TODO :Progress Bar
+    if not m_result then
+        m_result = require('Trans.query.baidu')(word)
+    end
+
+    if m_result and m_result.translation then
+        if hover.auto_play then
+            local ok = pcall(action.play)
+            if not ok then
+                vim.notify('自动发音失败, 请检查README发音部分', vim.log.WARN)
+            end
+        end
 
         for _, field in ipairs(conf.order) do
             process[field]()
@@ -305,14 +323,16 @@ return function(word)
     end)
 
     -- Auto Close
-    cmd_id = api.nvim_create_autocmd(
-        hover.auto_close_events, {
-        buffer = 0,
-        callback = function()
-            m_window:set('wrap', false)
-            m_window:try_close()
-            try_del_keymap()
-            api.nvim_del_autocmd(cmd_id)
-        end,
-    })
+    if hover.auto_close_events then
+        cmd_id = api.nvim_create_autocmd(
+            hover.auto_close_events, {
+            buffer = 0,
+            callback = function()
+                m_window:set('wrap', false)
+                m_window:try_close()
+                try_del_keymap()
+                api.nvim_del_autocmd(cmd_id)
+            end,
+        })
+    end
 end
diff --git a/lua/Trans/window.lua b/lua/Trans/window.lua
index 3df1ab3..31fd894 100644
--- a/lua/Trans/window.lua
+++ b/lua/Trans/window.lua
@@ -1,5 +1,9 @@
 local api = vim.api
 
+--- TODO : progress bar
+--- char: ■ | □ | ▇ | ▏ ▎ ▍ ▌ ▋ ▊ ▉ █
+--- ◖■■■■■■■◗▫◻ ▆ ▆ ▇⃞ ▉⃞
+
 ---@diagnostic disable-next-line: duplicate-set-field
 function string:width()
     ---@diagnostic disable-next-line: param-type-mismatch
@@ -85,7 +89,7 @@ local window = {
 
                     else
                         busy = false
-                        if type(callback) == 'function' then
+                        if callback then
                             callback()
                         end
                     end
@@ -101,13 +105,6 @@ local window = {
         end
     end,
 
-    ---**重新绘制内容**(标题不变)
-    ---@param self table 窗口对象
-    redraw = function(self)
-        self.content:attach()
-    end,
-
-
     ---安全的关闭窗口
     try_close = function(self, callback)
         if self:is_open() then
@@ -115,7 +112,6 @@ local window = {
             self.config = api.nvim_win_get_config(self.winid)
             local animation = self.animation
             if animation.close then
-
                 local handler
                 local function wrap(name, target)
                     local count = self[target]
@@ -202,7 +198,7 @@ return function(entry, option)
         title     = nil,
         col       = nil,
         row       = nil,
-        title_pos = 'center',
+        title_pos = nil,
         focusable = false,
         zindex    = 100,
         style     = 'minimal',
@@ -211,6 +207,9 @@ return function(entry, option)
     for k, v in pairs(option) do
         opt[k] = v
     end
+    if opt.title then
+        opt.title_pos = 'center'
+    end
 
     local bufnr = api.nvim_create_buf(false, true)
     local ok, winid = pcall(api.nvim_open_win, bufnr, entry, opt)

From 2377dbc0a7c9553dcb2f9a693fbe0a6970406c9e Mon Sep 17 00:00:00 2001
From: JuanZoran <1430359574@qq.com>
Date: Mon, 30 Jan 2023 20:09:57 +0800
Subject: [PATCH 2/2] feat: add online query support

---
 README.md                 |  40 +++++-----
 lua/Trans/content.lua     |  10 +--
 lua/Trans/init.lua        |  10 +--
 lua/Trans/query/baidu.lua |  45 +++++++----
 lua/Trans/translate.lua   |   2 +-
 lua/Trans/util/curl.lua   |  61 ++++++++++++++
 lua/Trans/view/float.lua  |  19 +++--
 lua/Trans/view/hover.lua  | 164 ++++++++++++++++++++++++++------------
 lua/Trans/window.lua      |  54 ++++++-------
 9 files changed, 272 insertions(+), 133 deletions(-)
 create mode 100644 lua/Trans/util/curl.lua

diff --git a/README.md b/README.md
index a3ca0a9..d4622dc 100644
--- a/README.md
+++ b/README.md
@@ -199,13 +199,8 @@ require'Trans'.setup {
         width = 37,
         height = 27,
         border = 'rounded',
-        title = {
-            { '', 'TransTitleRound' },
-            { ' Trans', 'TransTitle' },
-            { '', 'TransTitleRound' },
-        },
+        title = title,
         keymap = {
-            -- TODO :
             pageup = '[[',
             pagedown = ']]',
             pin = '<leader>[',
@@ -226,16 +221,13 @@ require'Trans'.setup {
             'BufLeave',
         },
         auto_play = true,
+        timeout = 3000,
     },
     float = {
         width = 0.8,
         height = 0.8,
         border = 'rounded',
-        title = {
-            { '', 'TransTitleRound' },
-            { ' Trans', 'TransTitle' },
-            { '', 'TransTitleRound' },
-        },
+        title = title,
         keymap = {
             quit = 'q',
         },
@@ -271,17 +263,28 @@ require'Trans'.setup {
         -- yes = '✔️',
         -- no = '❌'
     },
-    theme = 'default', -- 目前可选的: default, tokyonight, dracula
+    theme = 'default',
+    -- theme = 'dracula',
+    -- theme = 'tokyonight',
+
     db_path = '$HOME/.vim/dict/ultimate.db',
 
-    -- TODO  add online translate engine
-    -- online_search = {
-    --     enable = false,
-    --     engine = {},
+    engine = {
+        -- 目前支持hover窗口支持百度, 默认不开启
+        -- baidu = {
+        --     appid = '',
+        --     appPasswd = '',
+        -- },
+    },
+
+    -- TODO :
+    -- register word
+    -- history = {
+    --     -- TOOD
     -- }
 
-    -- TODO register word
-}
+    -- TODO :add online translate engine
+} 
 ```
 
 ## 快捷键绑定
@@ -342,6 +345,7 @@ vim.keymap.set('n', 'mi', '<Cmd>TranslateInput<CR>')
     },
 }
 ```
+
 ## 声明
 - 本插件词典基于[ECDICT](https://github.com/skywind3000/ECDICT)
 
diff --git a/lua/Trans/content.lua b/lua/Trans/content.lua
index 8852511..912589a 100644
--- a/lua/Trans/content.lua
+++ b/lua/Trans/content.lua
@@ -15,6 +15,7 @@ local content = {
         if not self.modifiable then
             error('content can not add newline now')
         end
+
         self.size = self.size + 1
         self.lines[self.size] = value
     end,
@@ -23,6 +24,7 @@ local content = {
         if not self.modifiable then
             error('content can not add newline now')
         end
+
         self.hl_size = self.hl_size + 1
         self.highlights[self.hl_size] = opt
     end,
@@ -32,6 +34,7 @@ local content = {
         clear(self.lines)
         clear(self.highlights)
         self.size = 0
+        self.hl_size = 0
     end,
 
     ---将内容连接上对应的窗口
@@ -42,6 +45,7 @@ local content = {
             return
         end
 
+        offset = offset or 0
         self.window:bufset('modifiable', true)
         local window = self.window
         --- NOTE : 使用-1 则需要按顺序设置
@@ -71,7 +75,6 @@ local content = {
         end
     end,
 
-
     format = function(self, ...)
         local nodes = { ... }
         local size = #nodes
@@ -85,10 +88,7 @@ local content = {
         end
 
         local space = math.floor(((self.window.width - width) / (size - 1)))
-        if space > 0 then
-            return
-        end
-
+        assert(space > 0, 'try to expand the window')
         local interval = (' '):rep(space)
         return setmetatable({
             text = table.concat(strs, interval),
diff --git a/lua/Trans/init.lua b/lua/Trans/init.lua
index 37d4828..03f582f 100644
--- a/lua/Trans/init.lua
+++ b/lua/Trans/init.lua
@@ -88,11 +88,11 @@ M.conf = {
     db_path = '$HOME/.vim/dict/ultimate.db',
 
     engine = {
-        baidu = {
-            appid = '',
-            appPasswd = '',
-        },
-        -- youdao = {
+        -- baidu = {
+        --     appid = '',
+        --     appPasswd = '',
+        -- },
+        -- -- youdao = {
         --     appkey = '',
         --     appPasswd = '',
         -- },
diff --git a/lua/Trans/query/baidu.lua b/lua/Trans/query/baidu.lua
index 0746ca0..61c5c8f 100644
--- a/lua/Trans/query/baidu.lua
+++ b/lua/Trans/query/baidu.lua
@@ -1,17 +1,14 @@
 local baidu     = require('Trans').conf.engine.baidu
 local appid     = baidu.appid
 local appPasswd = baidu.appPasswd
-local salt      = tostring(math.random(bit.rshift(1, 5)))
+local salt      = tostring(math.random(bit.lshift(1, 15)))
 local uri       = 'https://fanyi-api.baidu.com/api/trans/vip/translate'
 
 if appid == '' or appPasswd == '' then
     error('请查看README, 实现在线翻译或者设置将在线翻译设置为false')
 end
 
-local ok, curl = pcall(require, 'plenary.curl')
-if not ok then
-    error('plenary not found')
-end
+local post = require('Trans.util.curl').POST
 
 local function get_field(word)
     local to   = 'zh'
@@ -28,22 +25,40 @@ local function get_field(word)
     }
 end
 
+--- this is a nice plugin
+---返回一个channel
+---@param word string
+---@return function
 return function(word)
     local query = get_field(word)
-    local output = curl.post(uri, {
+    local result
+
+    post(uri, {
         data = query,
         headers = {
             content_type = "application/x-www-form-urlencoded",
-        }
+        },
+        callback = function(str)
+            if result then
+                return
+            elseif str ~= '' then
+                local res = vim.fn.json_decode(str)
+                if res and res.trans_result then
+                    result = {
+                        word = word,
+                        translation = res.trans_result[1].dst,
+                    }
+
+                else
+                    result = false
+                end
+            else
+                result = false
+            end
+        end,
     })
 
-    if output.exit == 0 and output.status == 200 then
-        local res = vim.fn.json_decode(output.body)
-        if res and res.trans_result then
-            return {
-                word = word,
-                translation = res.trans_result[1].dst,
-            }
-        end
+    return function()
+        return result
     end
 end
diff --git a/lua/Trans/translate.lua b/lua/Trans/translate.lua
index 94e5ee5..d37ba38 100644
--- a/lua/Trans/translate.lua
+++ b/lua/Trans/translate.lua
@@ -42,7 +42,7 @@ local function translate(mode, view)
     mode = mode or vim.api.nvim_get_mode().mode
     view = view or require('Trans').conf.view[mode]
     assert(mode and view)
-    local word = get_word(mode)
+    local word = get_word(mode):gsub('^%s+', '', 1)
     require('Trans.view.' .. view)(word)
 end
 
diff --git a/lua/Trans/util/curl.lua b/lua/Trans/util/curl.lua
new file mode 100644
index 0000000..a136328
--- /dev/null
+++ b/lua/Trans/util/curl.lua
@@ -0,0 +1,61 @@
+--- TODO :wrapper for curl
+local curl = {}
+-- local example = {
+--     data = {},
+--     headers = {
+--         k = 'v',
+--     },
+--     callback = function(output)
+
+--     end,
+-- }
+
+
+
+curl.GET = function(uri, opts)
+    --- TODO :
+end
+
+
+curl.POST = function(uri, opts)
+    vim.validate {
+        uri = { uri, 's' },
+        opts = { opts, 't' }
+    }
+
+    local cmd = { 'curl', '-s', uri }
+    local size = 3
+
+    local function insert(...)
+        for _, v in ipairs { ... } do
+            size = size + 1
+            cmd[size] = v
+        end
+    end
+
+    local s = '"%s=%s"'
+
+    if opts.headers then
+        for k, v in pairs(opts.headers) do
+            insert('-H', s:format(k, v))
+        end
+    end
+
+    for k, v in pairs(opts.data) do
+        insert('-d', s:format(k, v))
+    end
+
+    local option
+    if opts.callback then
+        option = {
+            on_stdout = function (_, output)
+                opts.callback(table.concat(output))
+            end,
+        }
+    end
+
+    vim.fn.jobstart(table.concat(cmd, ' '), option)
+end
+
+
+return curl
diff --git a/lua/Trans/view/float.lua b/lua/Trans/view/float.lua
index 1a03212..d687dd0 100644
--- a/lua/Trans/view/float.lua
+++ b/lua/Trans/view/float.lua
@@ -72,18 +72,18 @@ return function(word)
     m_result = require('Trans.query.' .. engine_us)(word)
 
     local opt = {
-        relative = 'editor',
-        width    = float.width,
-        height   = float.height,
-        border   = float.border,
-        title    = float.title,
-        row      = bit.rshift((vim.o.lines - float.height), 1),
-        col      = bit.rshift((vim.o.columns - float.width), 1),
-        zindex   = 50,
+        relative  = 'editor',
+        width     = float.width,
+        height    = float.height,
+        border    = float.border,
+        title     = float.title,
+        animation = float.animation,
+        row       = bit.rshift((vim.o.lines - float.height), 1),
+        col       = bit.rshift((vim.o.columns - float.width), 1),
+        zindex    = 50,
     }
 
     m_window = require('Trans.window')(true, opt)
-    m_window.animation = float.animation
 
     set_title()
     m_content = m_window.contents[2]
@@ -95,7 +95,6 @@ return function(word)
         set_tag_hl(engine_us, 'fail')
     end
 
-    m_window:draw()
     m_window:open()
     m_window:bufset('bufhidden', 'wipe')
 
diff --git a/lua/Trans/view/hover.lua b/lua/Trans/view/hover.lua
index 1d21e85..601b377 100644
--- a/lua/Trans/view/hover.lua
+++ b/lua/Trans/view/hover.lua
@@ -1,5 +1,6 @@
 local api = vim.api
 local conf = require('Trans').conf
+local new_window = require('Trans.window')
 
 local m_window
 local m_result
@@ -41,7 +42,6 @@ local process = {
                 it(m_result.collins and icon.star:rep(m_result.collins) or icon.notfound, 'TransCollins'),
                 it(m_result.oxford == 1 and icon.yes or icon.no)
             )
-
         end
         m_content:addline(line)
     end,
@@ -166,14 +166,6 @@ local process = {
             m_content:newline('')
         end
     end,
-
-    failed = function()
-        m_content:addline(
-            it(conf.icon.notfound .. m_indent .. '没有找到相关的翻译', 'TransFailed')
-        )
-
-        m_window:set_width(m_content.lines[1]:width())
-    end,
 }
 
 
@@ -264,64 +256,134 @@ action = {
     end,
 }
 
+local function handle()
+    local hover = conf.hover
+    if hover.auto_play then
+        local ok = pcall(action.play)
+        if not ok then
+            vim.notify('自动发音失败, 请检查README发音部分', vim.log.WARN)
+        end
+    end
+
+    for _, field in ipairs(conf.order) do
+        process[field]()
+    end
+
+    for act, key in pairs(hover.keymap) do
+        vim.keymap.set('n', key, action[act], { buffer = true, silent = true })
+    end
+end
+
+local function online_query(word)
+    -- TODO :Progress Bar
+    local wait = {}
+    local size = 0
+    for k, _ in pairs(conf.engine) do
+        size = size + 1
+        wait[size] = require('Trans.query.' .. k)(word)
+    end
+    local error_msg = conf.icon.notfound .. '    没有找到相关的翻译'
+
+    m_window:set_height(1)
+    local width = m_window.width
+    m_window:set_width(error_msg:width())
+    if size == 0 then
+        m_content:addline(it(error_msg, 'TransFailed'))
+        m_window:open()
+        return
+    end
+
+    m_window:open()
+
+    local timeout = conf.hover.timeout
+    local interval = math.floor(timeout / m_window.width)
+
+    -- --- char: ■ | □ | ▇ | ▏ ▎ ▍ ▌ ▋ ▊ ▉ █
+    -- --- ◖■■■■■■■◗▫◻ ▆ ▆ ▇⃞ ▉⃞
+    local cell = '▇'
+
+    local i = 1
+    local do_progress
+    do_progress = function()
+        m_content:wipe()
+        for j = 1, size do
+            local res = wait[j]()
+            if res then
+                m_result = res
+                m_window:set_width(width)
+                handle()
+                m_content:attach()
+
+                -- TODO :Animation
+                m_window.height = m_content:actual_height(true)
+                m_window:open {
+                    animation = 'fold',
+                }
+                return
+
+            elseif res == false then
+                table.remove(wait, j)
+                size = size - 1
+            end
+        end
+
+        if i == m_window.width or size == 0 then
+            --- HACK : change framework
+            m_content:addline(
+                it(error_msg, 'TransFailed')
+            )
+
+            m_content:attach()
+
+        else
+            m_content:addline(
+                it(cell:rep(i), 'MoreMsg')
+            )
+            i = i + 1
+            m_content:attach()
+            vim.defer_fn(do_progress, interval)
+        end
+    end
+
+    do_progress()
+end
 
 return function(word)
     vim.validate {
         word = { word, 's' },
     }
 
-    m_result = require('Trans.query.offline')(word)
     local hover = conf.hover
-
-    m_window = require("Trans.window")(false, {
-        relative = 'cursor',
-        width    = hover.width,
-        height   = hover.height,
-        title    = hover.title,
-        border   = hover.border,
-        col      = 1,
-        row      = 1,
+    m_window = new_window(false, {
+        relative  = 'cursor',
+        width     = hover.width,
+        height    = hover.height,
+        title     = hover.title,
+        border    = hover.border,
+        animation = hover.animation,
+        col       = 1,
+        row       = 1,
     })
 
-    m_window.animation = hover.animation
     m_content = m_window.contents[1]
 
+    m_result = require('Trans.query.offline')(word)
+    if m_result then
+        handle()
+        m_window:open({
+            callback = function()
+                m_window:set('wrap', true)
+            end,
+        })
 
-    -- TODO :Progress Bar
-    if not m_result then
-        m_result = require('Trans.query.baidu')(word)
-    end
-
-    if m_result and m_result.translation then
-        if hover.auto_play then
-            local ok = pcall(action.play)
-            if not ok then
-                vim.notify('自动发音失败, 请检查README发音部分', vim.log.WARN)
-            end
-        end
-
-        for _, field in ipairs(conf.order) do
-            process[field]()
-        end
-
-        for act, key in pairs(hover.keymap) do
-            vim.keymap.set('n', key, action[act], { buffer = true, silent = true })
+        local height = m_content:actual_height(true)
+        if height < m_window.height then
+            m_window:set_height(height)
         end
     else
-        process.failed()
+        online_query(word)
     end
 
-    m_window:draw()
-
-    local height = m_content:actual_height(true)
-    if height < m_window.height then
-        m_window:set_height(height)
-    end
-
-    m_window:open(function()
-        m_window:set('wrap', true)
-    end)
-
     -- Auto Close
     if hover.auto_close_events then
         cmd_id = api.nvim_create_autocmd(
diff --git a/lua/Trans/window.lua b/lua/Trans/window.lua
index 31fd894..c95a735 100644
--- a/lua/Trans/window.lua
+++ b/lua/Trans/window.lua
@@ -60,10 +60,7 @@ local window = {
         end)
     end,
 
-    ---**第一次**绘制窗口的内容
-    ---@param self table 窗口的对象
     draw = function(self)
-        -- TODO :
         local offset = 0
         for _, content in ipairs(self.contents) do
             content:attach(offset)
@@ -71,9 +68,13 @@ local window = {
         end
     end,
 
-    open = function(self, callback)
-        local animation = self.animation
-        if animation.open then
+    open = function(self, opts)
+        self:draw()
+        opts = opts or {}
+        local interval = self.animation.interval
+        local animation = opts.animation or self.animation.open
+
+        if animation then
             check_busy()
 
             local handler
@@ -85,12 +86,12 @@ local window = {
                         busy = true
                         count = count + 1
                         api[action](self.winid, count)
-                        vim.defer_fn(handler[name], animation.interval)
+                        vim.defer_fn(handler[name], interval)
 
                     else
                         busy = false
-                        if callback then
-                            callback()
+                        if opts.callback then
+                            opts.callback()
                         end
                     end
                 end
@@ -101,7 +102,7 @@ local window = {
                 fold = wrap('fold', 'height'),
             }
 
-            handler[animation.open]()
+            handler[animation]()
         end
     end,
 
@@ -191,22 +192,19 @@ return function(entry, option)
     }
 
     local opt = {
-        relative  = nil,
-        width     = nil,
-        height    = nil,
-        border    = nil,
-        title     = nil,
-        col       = nil,
-        row       = nil,
+        relative  = option.relative,
+        width     = option.width,
+        height    = option.height,
+        border    = option.border,
+        title     = option.title,
+        col       = option.col,
+        row       = option.row,
         title_pos = nil,
         focusable = false,
-        zindex    = 100,
+        zindex    = option.zindex or 100,
         style     = 'minimal',
     }
 
-    for k, v in pairs(option) do
-        opt[k] = v
-    end
     if opt.title then
         opt.title_pos = 'center'
     end
@@ -219,12 +217,13 @@ return function(entry, option)
 
     local win
     win = {
-        winid    = winid,
-        bufnr    = bufnr,
-        width    = opt.width,
-        height   = opt.height,
-        hl       = api.nvim_create_namespace('TransWinHl'),
-        contents = setmetatable({}, {
+        winid     = winid,
+        bufnr     = bufnr,
+        width     = opt.width,
+        height    = opt.height,
+        animation = option.animation,
+        hl        = api.nvim_create_namespace('TransWinHl'),
+        contents  = setmetatable({}, {
             __index = function(self, key)
                 assert(type(key) == 'number')
                 self[key] = require('Trans.content')(win)
@@ -234,7 +233,6 @@ return function(entry, option)
     }
 
     setmetatable(win, { __index = window })
-
     -- FIXME  :config this
     win:bufset('filetype', 'Trans')
     win:bufset('buftype', 'nofile')