From 94c465826288161828d6a62d0e3d0582c2905493 Mon Sep 17 00:00:00 2001
From: JuanZoran <1430359574@qq.com>
Date: Thu, 19 Jan 2023 23:50:18 +0800
Subject: [PATCH 1/7] chore: modify .gitignore file

---
 .gitignore         |   2 +
 go/go.mod          |   3 --
 go/query_online.go |  50 -----------------
 install.sh         |   2 +-
 note/api.md        | 132 ---------------------------------------------
 note/engine.md     | 129 --------------------------------------------
 note/logic.md      |  96 ---------------------------------
 7 files changed, 3 insertions(+), 411 deletions(-)
 delete mode 100644 go/go.mod
 delete mode 100644 go/query_online.go
 delete mode 100644 note/api.md
 delete mode 100644 note/engine.md
 delete mode 100644 note/logic.md

diff --git a/.gitignore b/.gitignore
index a0863a7..507551e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 lua/Trans/util/
 lua/Trans/core/
+note/
+go/
diff --git a/go/go.mod b/go/go.mod
deleted file mode 100644
index 606edce..0000000
--- a/go/go.mod
+++ /dev/null
@@ -1,3 +0,0 @@
-module query_online
-
-go 1.19
diff --git a/go/query_online.go b/go/query_online.go
deleted file mode 100644
index fc5345e..0000000
--- a/go/query_online.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package query_youcao
-
-import (
-	"net/url"
-	"time"
-)
-
-const (
-	youdao    = "https://openapi.youdao.com/api"
-	appKey    = "1858465a8708c121"
-	appPasswd = "fG0sitfk16nJOlIlycnLPYZn1optxUxL"
-)
-
-type data struct {
-	q        string
-	from     string
-	to       string
-	// appKey   string
-	salt     string
-	sign     string
-	signType string
-	curtime  string
-}
-
-
-func input(word string) string {
-	var input string
-	len := len(word)
-	if len > 20 {
-		input = word[:10] + string(rune(len)) + word[len-10:]
-	} else {
-		input = word
-	}
-	return input
-}
-
-func salt(_ string) string {
-	// TODO : hash salt
-	var salt string
-
-	return salt
-}
-
-func to_value(d data) url.Values {
-	// return value
-}
-
-func Query(word string) {
-
-}
diff --git a/install.sh b/install.sh
index a939dce..a9adb84 100755
--- a/install.sh
+++ b/install.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 set -e
 
 if [[ -d "$HOME/.vim" ]]; then
diff --git a/note/api.md b/note/api.md
deleted file mode 100644
index 3b4d0ef..0000000
--- a/note/api.md
+++ /dev/null
@@ -1,132 +0,0 @@
-# API说明
-
-<!--toc:start-->
-- [API说明](#api说明)
-  - [数据结构](#数据结构)
-    - [翻译](#翻译)
-    - [窗口](#窗口)
-    - [翻译结果](#翻译结果)
-    - [内容单位](#内容单位)
-- [窗口绘制逻辑](#窗口绘制逻辑)
-  - [hover](#hover)
-  - [float](#float)
-<!--toc:end-->
-
-## 数据结构
-
-### 翻译
-- `word`  
-待翻译的字符串: string
-- `sentence`  
-是否为句子: boolean
-- `result`  
-翻译查询的结果: table
-    > 见: [翻译结果](#翻译结果) 
-- `engine`  
-
-### 窗口
-- `style`  
-风格: string
-- `height`  
-高度: integer
-- `width`  
-宽度: integer
-- `border`  
-边框样式: string
-- `winhl`  
-窗口的高亮: string
-
-### 翻译结果
-**无特殊说明, 所有字段均为`string`类型** 
-
-- `word`  
-查询的字符串
-- `phonetic`  
-音标
-- `collins`  
-柯林斯星级: integer
-- `oxford` 
-是否为牛津词汇: integer (1为是)
-- `tag`  
-标签
-- `pos`  
-词性
-- `exchange`  
-词态变化
-- `translation`  
-中文翻译
-- `definition`  
-英文注释
-
-
-### 内容单位
-- `field` 字段
-    > 是展示的最小单位  
-
-    **属性**
-    - `1`
-    存储的文本: string
-    - `2`  (optional)
-    对应的高亮: string
-    - `_start`  
-    起始行: integer
-    - `_end`  
-    结束行: integer
-    > **注意:** `_start` 和`_end` 字段只有已经被展示到窗口后才会被设置  
-
-    **方法** 
-    - 无 
-
-- `line` 行
-    > 窗口展示的每一行, 一个行有多个`field`
-
-    **属性**
-    - `text`  
-    当前保存的字符串: string
-    - `hls`  
-    行内的高亮: table
-    - `index`  
-    行号[0为起始下标]
-    - `fields`  
-    行内保存的`field`
-    - `status`  
-    行内是否需要更新: boolean
-    > **注意:** `num` 只有已经被展示到窗口后才会被设置
-
-    **方法** 
-    - `update`  
-    更新`text`和`hls`
-    - `data`  
-    获取行的text
-    - `insert`  
-    添加新`field`
-    - `add_highlight`  
-    添加高亮
-        > 参数: bufnr
-
-- `content` 内容
-    > 窗口展示的单位, 一个内容内有多个`line`
-    
-    **方法** 
-    - `data`  
-    返回lines和highlights
-    - `insert` 
-    插入新的行
-    - `attach`  
-    将内容展示到buffer
-        > 参数: bufnr
-
-# 窗口绘制逻辑
-
-- 获取所有组件
-## hover
-- 按照order顺序加载
-- 按组件间距为4计算组件个数能否在一行以内放下
-    - 放不下则按照组件间距为4 计算组件个数并对齐
-    - 放得下则重新计算组间距
-
-获得组件行内容, 设置到行内
-获取组件高亮, 设置高亮
-
-## float
-由定义好的逻辑,绘制整个窗口
diff --git a/note/engine.md b/note/engine.md
deleted file mode 100644
index 4999dc9..0000000
--- a/note/engine.md
+++ /dev/null
@@ -1,129 +0,0 @@
-# 字段说明
-
-<!--toc:start-->
-- [字段说明](#字段说明)
-  - [本地](#本地)
-  - [有道](#有道)
-    - [中英](#中英)
-  - [百度](#百度)
-    - [返回结果](#返回结果)
-  - [彩云小译](#彩云小译)
-  - [必应](#必应)
-  - [腾讯翻译君](#腾讯翻译君)
-  - [阿里翻译](#阿里翻译)
-  - [火山翻译](#火山翻译)
-  - [金山词霸](#金山词霸)
-<!--toc:end-->
-
-## 本地
-- `word`  
-查询的字符串
-- `phonetic`  
-音标
-- `collins`  
-柯林斯星级: integer
-- `oxford` 
-是否为牛津词汇: integer (1为是)
-- `tag`  
-标签
-- `pos`  
-词性
-- `exchange`  
-词态变化
-- `translation`  
-中文翻译
-- `definition`  
-英文注释
-
-## 有道
-### 中英
-
-basic	JSONObject	简明释义
-phonetic	text	词典音标
-usPhonetic	text	美式音标
-ukPhonetic	text	英式音标
-ukSpeech	text	英式发音
-usSpeech	text	美式发音
-explains	text	基本释义
-text	text	短语
-explain	String Array	词义解释列表
-wordFormats	Object Array	单词形式变化列表
-name	String	形式名称,例如:复数
-web	JSONArray	网络释义
-phrase	String	词组
-meaning	String	含义
-synonyms	JSONObject	近义词
-pos	String	词性
-words	String Array	近义词列表
-trans	String	释义
-antonyms	ObjectArray	反义词
-relatedWords	JSONArray	相关词
-wordNet	JSONObject	汉语词典网络释义
-phonetic	String	发音
-meanings	ObjectArray	释义
-meaning	String	释义
-example	array	示例
-dict	String	词典deeplink
-webDict	String	词典网页deeplink
-sentenceSample	text	例句
-sentence	text	例句
-sentenceBold	text	将查询内容加粗的例句
-translation	text	例句翻译
-wfs	text	单词形式变化
-exam_type	text	考试类型
-
-## 百度
-from	string	源语言	返回用户指定的语言,或者自动检测出的语种(源语言设为auto时)
-to	string	目标语言	返回用户指定的目标语言
-trans_result	array	翻译结果	返回翻译结果,包括src和dst字段
-trans_result.*.src	string	原文	接入举例中的“apple”
-trans_result.*dst	string	译文	接入举例中的“苹果”
-error_code	integer	错误码	仅当出现错误时显示
-以下字段仅开通了词典、tts用户可见
-src_tts	string	原文tts链接	mp3格式,暂时无法指定发音
-dst_tts	string	译文tts链接	mp3格式,暂时无法指定发音
-dict	string	中英词典资源	返回中文或英文词典资源,包含音标;简明释义等内容
-
-### 返回结果
-- 英-> 中
-```json
-{
-    "from": "en", 
-    "to": "zh", 
-    "trans_result": [
-        {
-            "src": "apple", 
-            "dst": "苹果"
-        }
-    ]
-}
-```
-- 中->英
-```json
-{
-    "from": "zh", 
-    "to": "en", 
-    "trans_result": [
-        {
-            "src": "中国", 
-            "dst": "China"
-        }
-    ]
-}
-```
-## 彩云小译
-句子翻译
-> sh xiaoyi.sh en2zh "You know some birds are not meant to be caged, their feathers are just too bright."
-> 你知道有些鸟不应该被关在笼子里,它们的羽毛太亮了。
-
-## 必应
-
-## 腾讯翻译君
-
-## 阿里翻译
-
-## 火山翻译
-
-## 金山词霸
-
-## Dictionary
diff --git a/note/logic.md b/note/logic.md
deleted file mode 100644
index fda9162..0000000
--- a/note/logic.md
+++ /dev/null
@@ -1,96 +0,0 @@
-# 命令说明
-
-<!--toc:start-->
-- [命令说明](#命令说明)
-  - [Translate](#translate)
-  - [TranslateInput](#translateinput)
-  - [TranslateHistory](#translatehistory)
-  - [自定义](#自定义)
-    - [可选项说明](#可选项说明)
-    - [示例](#示例)
-<!--toc:end-->
-
-## Translate
-**窗口风格默认为:** `cursor`
-- 动作(action):
-    - `vsplit`         水平分屏
-    - `split`          垂直分屏
-    - `float`          窗口样式又`cursor` 变为`float`
-    - `online_query`   使用在线引擎重新进行查询
-    - `history_insert` 将此次查询的单词记录到历史记录  
-    - `next`           展示下一个引擎的查询结果(如果默认设置了多个引擎)
-    - `prev`           展示上一个查询结果
-    > 如果没有设置自动保存历史的话
-
-    - `history`        查看历史查询的记录
-
-- `online_query`:
-    - `local_add`    将此次查询的结果添加到本地数据库  
-    > **如果本地已经存在该单词,会询问是否需要覆盖掉相同的字段**
-
-    - `local_update` 和*local_add* 类似, 但是不会询问是否覆盖
-    - `diff`         对比本地查询结果和此次在线查询的区别
-
->  **注意**: 动作是任何窗口通用的  
-## TranslateInput
-**窗口风格默认为:** `float`
-- 自行得到要查询的单词
-
-- TODO: 
-    - fuzzy match
-
-## TranslateHistory
-**窗口风格默认为:** `float`
-- 查看历史查询
-
----
-## 自定义
-
-### 可选项说明
-- 查询方式(method): `string`
-    - `input` 自行输入需要查询的单词
-    - `last`  显示上一次查询的结果
-    - `history`
-
-- 查询引擎(engine): `string | table`
-    - `offline` 离线的数据库
-    - `youcao` 有道api
-    - `baidu` 百度api
-    - `google` 谷歌api
-    - `bing` 必应api
-    - `iciba` 金山词霸api
-    - `xunfei` 讯飞api
-
-- 窗口风格(win): `string | table`
-    - 样式(style): 
-        - `cursor` 在光标附近弹出
-        - `float` 悬浮窗口
-        - `split` 在上方或者下方分屏
-        - `vsplit` 在左边或者右边分屏
-
-    - 高度(height):
-        - `value > 1` 最大高度
-        - `0 <= value <= 1` 相对高度
-        - `0 < value` 无限制
-
-    - 宽度(width):
-        > 和`高度(height)`相同
-### 示例
-```lua
-vim.keymap.set('n', 'mi', function ()
-    require('Trans').translate({
-        method = 'input', -- 不填则自动判断mode获取查询的单词
-        engine = { -- 异步查询所有的引擎, 按照列表
-            'offline',
-            'youdao',
-            'baidu'
-        },
-        -- win = 'cursor'
-        win = {
-            style = 'cursor',
-            height = 50,
-            width = 30,
-        }
-    })
-end, { desc = '在光标旁弹出输入的单词释义'})
-```

From 579c9268fa93c4b45b00c78f49c5cb0069a69db0 Mon Sep 17 00:00:00 2001
From: JuanZoran <1430359574@qq.com>
Date: Fri, 20 Jan 2023 14:54:11 +0800
Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E4=BA=86wind?=
 =?UTF-8?q?ow=20=E5=92=8Ccontent=E7=B1=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .gitignore           | 2 +-
 lua/Trans/window.lua | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.gitignore b/.gitignore
index 507551e..70eae03 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
 lua/Trans/util/
-lua/Trans/core/
+lua/Trans/test/
 note/
 go/
diff --git a/lua/Trans/window.lua b/lua/Trans/window.lua
index 5900289..78ab4f7 100644
--- a/lua/Trans/window.lua
+++ b/lua/Trans/window.lua
@@ -25,7 +25,7 @@ M.init = function(entry, opts)
         opts = { opts, 't' }
     }
     local opt = {
-        relative  = nil,
+       relative  = nil,
         width     = nil,
         height    = nil,
         border    = nil,

From 5f3aa3d661c08945d16f9225200844ac44716428 Mon Sep 17 00:00:00 2001
From: JuanZoran <1430359574@qq.com>
Date: Fri, 20 Jan 2023 17:04:50 +0800
Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20window=E5=92=8Ccontent=E7=9A=84?=
 =?UTF-8?q?=E5=85=B3=E7=B3=BB=E7=94=B1=E7=BB=A7=E6=89=BF=E6=94=B9=E4=B8=BA?=
 =?UTF-8?q?=E7=BB=84=E5=90=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lua/Trans/content.lua    | 167 +++++++++++++++
 lua/Trans/view/hover.lua |  51 ++---
 lua/Trans/window.lua     | 424 +++++++++++++++------------------------
 3 files changed, 358 insertions(+), 284 deletions(-)
 create mode 100644 lua/Trans/content.lua

diff --git a/lua/Trans/content.lua b/lua/Trans/content.lua
new file mode 100644
index 0000000..ba2f31f
--- /dev/null
+++ b/lua/Trans/content.lua
@@ -0,0 +1,167 @@
+local api = vim.api
+local content = {
+    newline = function(self, value)
+        if not self.modifiable then
+            error('content can not add newline now')
+        end
+        self.size = self.size + 1
+        self.lines[self.size] = value
+    end,
+
+    newhl = function(self, opt)
+        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,
+
+    center_line = function(self, text, highlight)
+        vim.validate {
+            text = { text, 's' }
+        }
+
+        local space = math.floor((self.window.width - text:width()) / 2)
+        local interval = (' '):rep(space)
+        self:newline(interval .. text)
+        if highlight then
+            self:newhl {
+                name   = highlight,
+                line   = self.size - 1,
+                _start = space,
+                _end   = space + #text,
+            }
+        end
+    end,
+
+    wipe = function(self)
+        local clear = require('table.clear')
+        clear(self.lines)
+        clear(self.highlights)
+        self.size = 0
+    end,
+
+    attach = function(self)
+        if self.size == 0 then
+            return
+        end
+
+        self.window:bufset('modifiable', true)
+        local window = self.window
+        local offset = self.offset
+
+        api.nvim_buf_set_lines(window.bufnr, offset, offset + 1, true, self.lines)
+
+        for _, hl in ipairs(self.highlights) do
+            api.nvim_buf_add_highlight(window.bufnr, window.hl, hl.name, offset + hl.line, hl._start, hl._end)
+        end
+        self.window:bufset('modifiable', false)
+    end,
+
+    actual_height = function(self)
+        if self.window:option('wrap') then
+            local height = 0
+            local width = self.window.width
+            local lines = self.lines
+            for i = 1, self.size do
+                height = height + math.max(1, (math.ceil(lines[i]:width() / width)))
+            end
+            return height
+        else
+            return self.size
+        end
+    end,
+
+    addline = function(self, newline, highlight)
+        self:newline(newline)
+        if highlight then
+            self:newhl {
+                name = highlight,
+                line = self.size - 1,
+                _start = 0,
+                _end = -1,
+            }
+        end
+    end,
+
+    items_wrap = function(self)
+        local items = {}
+        local size = 0
+        local width = 0
+
+        return {
+            add_item = function(item, highlight)
+                size = size + 1
+                items[size] = { item, highlight }
+                width = width + item:width()
+            end,
+
+            load = function()
+                assert(size > 1, 'no item need be loaded')
+                local space = math.floor((self.window.width - width) / (size - 1))
+                assert(space > 0, 'try to expand window width')
+                local interval = (' '):rep(space)
+                local line = ''
+
+                local function load_item(idx)
+                    local item = items[idx]
+                    if item[2] then
+                        self:newhl {
+                            name = item[2],
+                            line = self.size, -- NOTE : 此时还没插入新行, size ==> 行号(zero index)
+                            _start = #line,
+                            _end = #line + #item[1],
+                        }
+                    end
+                    line = line .. item[1]
+                end
+
+                load_item(1)
+                for i = 2, size do
+                    line = line .. interval
+                    load_item(i)
+                end
+
+                self:newline(line)
+            end
+        }
+    end,
+
+    line_wrap = function(self)
+        self:newline('')
+        local index = self.size
+        return function(text, highlight)
+            if highlight then
+                local _start = #self.lines[index]
+                local _end = _start + #text
+                self:newhl {
+                    name = highlight,
+                    line = index - 1,
+                    _start = _start,
+                    _end = _end,
+                }
+            end
+
+            self.lines[index] = self.lines[index] .. text
+        end
+    end
+}
+
+
+---content的构造函数
+---@param window table 链接的窗口
+---@return table 构造好的content
+return function(window, offset)
+    vim.validate {
+        window = { window, 't' },
+    }
+    return setmetatable({
+        modifiable = true,
+        offset = offset or 0,
+        window = window,
+        size = 0,
+        hl_size = 0,
+        lines = {},
+        highlights = {},
+    }, { __index = content })
+end
diff --git a/lua/Trans/view/hover.lua b/lua/Trans/view/hover.lua
index 04a7db1..6db4fdb 100644
--- a/lua/Trans/view/hover.lua
+++ b/lua/Trans/view/hover.lua
@@ -1,12 +1,13 @@
 local conf = require('Trans').conf
 local icon = conf.icon
 
-local m_window = require('Trans.window')
-local m_result
+local m_window
+local m_result 
+local m_content
 local m_indent = '    '
 
 local title = function(str)
-    local wrapper = m_window.text_wrap()
+    local wrapper = m_content:line_wrap()
     -- wrapper('', 'TransTitleRound')
     wrapper('', 'TransTitleRound')
     wrapper(str, 'TransTitle')
@@ -61,7 +62,7 @@ end
 
 local process = {
     title = function()
-        local line = m_window.line_wrap()
+        local line = m_content:items_wrap()
         line.add_item(
             m_result.word,
             'TransWord'
@@ -94,12 +95,13 @@ local process = {
             end
 
             for i = 1, size, 3 do
-                m_window.addline(
+                m_content:addline(
                     m_indent .. tags[i] .. interval .. (tags[i + 1] or '') .. interval .. (tags[i + 2] or ''),
                     'TransTag'
                 )
             end
-            m_window.addline('')
+
+            m_content:addline('')
         end
     end,
 
@@ -108,13 +110,13 @@ local process = {
             title('词性')
 
             for pos in vim.gsplit(m_result.pos, '/', true) do
-                m_window.addline(
+                m_content:addline(
                     m_indent .. pos_map[pos:sub(1, 1)] .. pos:sub(3) .. '%',
                     'TransPos'
                 )
             end
 
-            m_window.addline('')
+            m_content:addline('')
         end
     end,
 
@@ -124,13 +126,13 @@ local process = {
             local interval = '    '
 
             for exc in vim.gsplit(m_result.exchange, '/', true) do
-                m_window.addline(
+                m_content:addline(
                     m_indent .. exchange_map[exc:sub(1, 1)] .. interval .. exc:sub(3),
                     'TransExchange'
                 )
             end
 
-            m_window.addline('')
+            m_content:addline('')
         end
     end,
 
@@ -138,13 +140,13 @@ local process = {
         title('中文翻译')
 
         for trs in vim.gsplit(m_result.translation, '\n', true) do
-            m_window.addline(
+            m_content:addline(
                 m_indent .. trs,
                 'TransTranslation'
             )
         end
 
-        m_window.addline('')
+        m_content:addline('')
     end,
 
     definition = function()
@@ -153,18 +155,18 @@ local process = {
 
             for def in vim.gsplit(m_result.definition, '\n', true) do
                 def = def:gsub('^%s+', '', 1) -- TODO :判断是否需要分割空格
-                m_window.addline(
+                m_content:addline(
                     m_indent .. def,
                     'TransDefinition'
                 )
             end
 
-            m_window.addline('')
+            m_content:addline('')
         end
     end,
 
     failed = function()
-        m_window.addline(
+        m_content:addline(
             icon.notfound .. m_indent .. '没有找到相关的翻译',
             'TransFailed'
         )
@@ -174,10 +176,11 @@ local process = {
 
 local action = {
     pageup = function()
-        m_window.normal('gg')
+        m_window:normal('gg')
     end,
+
     pagedown = function()
-        m_window.normal('G')
+        m_window:normal('G')
     end,
 }
 
@@ -200,8 +203,8 @@ return function(word)
         row      = 2,
     }
 
-
-    m_window.init(false, opt)
+    m_window = require("Trans.window")(false, opt)
+    m_content = m_window.content
 
     if m_result then
         for _, field in ipairs(conf.order) do
@@ -210,24 +213,22 @@ return function(word)
     else
         process.failed()
     end
+    m_window:set('wrap', true)
 
-    m_window.draw()
+    m_window:draw(true)
     -- Auto Close
     vim.api.nvim_create_autocmd(
         { --[[ 'InsertEnter', ]] 'CursorMoved', 'BufLeave', }, {
         buffer = 0,
         once = true,
         callback = function()
-            m_window.try_close(hover.animation) -- NOTE :maybe can be passed by uesr
+            m_window:try_close(hover.animation)
         end,
     })
 
-    m_window.set('wrap', true)
-    m_window.adjust()
-
     for act, key in pairs(hover.keymap) do
         vim.keymap.set('n', key, function()
-            if m_window.is_open() then
+            if m_window:is_open() then
                 action[act]()
             end
         end)
diff --git a/lua/Trans/window.lua b/lua/Trans/window.lua
index 78ab4f7..9e21064 100644
--- a/lua/Trans/window.lua
+++ b/lua/Trans/window.lua
@@ -1,31 +1,147 @@
 local api = vim.api
---- =================== Window Attributes ================================
-local M = {
-    height     = 0, -- 窗口的当前的高度
-    size       = 0, -- 窗口的行数
-    width      = 0, -- 窗口的当前的宽度
-    lines      = {},
-    highlights = {},
-    winid      = -1, -- 窗口的handle
-    bufnr      = -1, -- 窗口对应的buffer的handle
-    hl         = api.nvim_create_namespace('TransWinHl'),
-}
-
--- M.<++>   --> <++>
 
+---@diagnostic disable-next-line: duplicate-set-field
 function string:width()
     ---@diagnostic disable-next-line: param-type-mismatch
     return vim.fn.strwidth(self)
 end
 
---- =================== Load Window Options ================================
-M.init = function(entry, opts)
+local window = {
+    ---设置窗口的选项
+    ---@param self table 需要设置的window
+    ---@param option string 待设置的选项名
+    ---@param value any 选项的值
+    set = function(self, option, value)
+        api.nvim_win_set_option(self.winid, option, value)
+    end,
+
+    ---设置窗口的高度
+    ---@param self table 窗口类
+    ---@param height integer 设置的高度
+    set_height = function(self, height)
+        api.nvim_win_set_height(self.winid, height)
+        self.height = height
+    end,
+
+    ---设置窗口的宽度
+    ---@param self table 窗口对象
+    ---@param width integer 设置的宽度
+    set_width = function(self, width)
+        api.nvim_win_set_width(self.winid, width)
+        self.width = width
+    end,
+
+    ---设置窗口对应的buffer
+    ---@param self table 同set类似
+    ---@param option string
+    ---@param value any
+    bufset = function(self, option, value)
+        api.nvim_buf_set_option(self.bufnr, option, value)
+    end,
+
+    ---查看**窗口**的选项
+    ---@param self table 窗口对象
+    ---@param name string 选项名
+    ---@return any 对应的值
+    ---@nodiscard
+    option = function(self, name)
+        return api.nvim_win_get_option(self.winid, name)
+    end,
+
+    ---设置当前窗口内的键映射, **需要光标在该窗口内**
+    ---@param self table 窗口对象
+    ---@param key string 映射的键位
+    ---@param operation any 执行的操作
+    map = function(self, key, operation)
+        vim.keymap.set('n', key, operation, {
+            buffer = self.bufnr,
+            silent = true,
+        })
+    end,
+
+    ---查看窗口是否是打开状态
+    ---@param self table window对象
+    ---@return boolean
+    ---@nodiscard
+    is_open = function(self)
+        return self.winid > 0 and api.nvim_win_is_valid(self.winid)
+    end,
+
+    normal = function(self, key)
+        api.nvim_buf_call(self.bufnr, function()
+            vim.cmd([[normal! ]] .. key)
+        end)
+    end,
+
+    ---**第一次**绘制窗口的内容
+    ---@param self table 窗口的对象
+    ---@param adjust boolean 是否需要调整窗口的高度和宽度 (只有窗口只有一行时才会调整宽度)
+    draw = function(self, adjust)
+        -- TODO :
+        if self.title then
+            self.title:attach()
+        end
+        self.content:attach()
+
+        if adjust then
+            local height = self.content:actual_height() + self.title:actual_height()
+            if self.height > height then
+                self:set_height(height)
+            end
+
+            if self.content.size == 1 and self.title.size == 0 then
+                self:set_width(self.content.lines[1]:width())
+            end
+        end
+    end,
+
+    ---**重新绘制内容**(标题不变)
+    ---@param self table 窗口对象
+    redraw = function(self)
+        self.content:attach()
+    end,
+
+    ---安全的关闭窗口
+    ---@param self table 窗口对象
+    ---@param interval integer 动画的间隔
+    try_close = function(self, interval)
+        vim.validate {
+            interval = { interval, 'n' }
+        }
+
+        if self:is_open() then
+            local function narrow()
+                if self.height > 1 then
+                    self.height = self.height - 1
+                    api.nvim_win_set_height(self.winid, self.height)
+                    vim.defer_fn(narrow, interval)
+                else
+                    -- Wait animation done
+                    vim.defer_fn(function()
+                        api.nvim_win_close(self.winid, true)
+                        self.winid = -1
+                    end, interval + 2)
+                end
+            end
+
+            narrow()
+        end
+    end,
+}
+
+---窗口对象的构造器
+---@param entry boolean 光标初始化时是否应该进入窗口
+---@param option table 需要设置的选项
+---@return table
+---@nodiscard
+return function(entry, option)
     vim.validate {
         entry = { entry, 'b' },
-        opts = { opts, 't' }
+        option = { option, 't' },
     }
+
     local opt = {
-       relative  = nil,
+        relative  = nil,
         width     = nil,
         height    = nil,
         border    = nil,
@@ -37,253 +153,43 @@ M.init = function(entry, opts)
         zindex    = 100,
         style     = 'minimal',
     }
-
-    for k, v in pairs(opts) do
+    for k, v in pairs(option) do
         opt[k] = v
     end
 
-    M.height = opt.height
-    M.width  = opt.width
-    M.bufnr  = api.nvim_create_buf(false, true)
-    M.winid  = api.nvim_open_win(M.bufnr, entry, opt)
-    M.set('winhl', 'Normal:TransWin,FloatBorder:TransBorder')
-    M.bufset('bufhidden', 'wipe')
-    M.bufset('filetype', 'Trans')
-    M.wipe()
-end
+    local bufnr = api.nvim_create_buf(false, true)
+    local winid = api.nvim_open_win(bufnr, entry, opt)
 
-
-M.draw = function()
-    -- TODO :
-    M.bufset('modifiable', true)
-    api.nvim_buf_set_lines(M.bufnr, 0, -1, false, M.lines)
-    for _, hl in ipairs(M.highlights) do
-        api.nvim_buf_add_highlight(M.bufnr, M.hl, hl.name, hl.line, hl._start, hl._end)
-    end
-
-    M.bufset('modifiable', false)
-    -- vim.pretty_print(M.highlights)
-end
-
----清空window的数据
-M.wipe = function()
-    M.size = 0
-    local clear = require('table.clear')
-    clear(M.lines)
-    clear(M.highlights)
-end
-
-M.is_open = function()
-    return M.winid > 0 and api.nvim_win_is_valid(M.winid)
-end
-
-
----安全的关闭窗口
----@param interval integer 窗口关闭动画的间隔
-M.try_close = function(interval)
-    if M.is_open() then
-        local function narrow()
-            if M.height > 1 then
-                M.height = M.height - 1
-                api.nvim_win_set_height(M.winid, M.height)
-                vim.defer_fn(narrow, interval)
-            else
-                -- Wait animation done
-                vim.defer_fn(function()
-                    api.nvim_win_close(M.winid, true)
-                    M.winid = -1
-                end, interval + 2)
-            end
-        end
-
-        narrow()
-    end
-end
-
-
-M.cur_height = function()
-    if api.nvim_win_get_option(M.winid, 'wrap') then
-        local height = 0
-        local width = M.width
-        local lines = M.lines
-
-        for i = 1, M.size do
-            height = height + math.max(1, (math.ceil(lines[i]:width() / width)))
-        end
-        return height
-
-    else
-        return M.size
-    end
-end
-
-
-M.adjust = function()
-    local cur_height = M.cur_height()
-    if M.height > cur_height then
-        api.nvim_win_set_height(M.winid, cur_height)
-        M.height = cur_height
-    end
-
-    if M.size == 1 then
-        api.nvim_win_set_width(M.winid, M.lines[1]:width())
-    end
-end
-
-
----- ============ Utility functions ============
----设置窗口选项
----@param option string 需要设置的窗口
----@param value any 需要设置的值
-M.set = function(option, value)
-    api.nvim_win_set_option(M.winid, option, value)
-end
-
----设置窗口对应buffer的选项
----@param option string 需要设置的窗口
----@param value any 需要设置的值
-M.bufset = function(option, value)
-    api.nvim_buf_set_option(M.bufnr, option, value)
-end
-
-
-M.normal = function(key)
-    api.nvim_buf_call(M.bufnr, function()
-        vim.cmd([[normal! ]] .. key)
-    end)
-end
-
----设置该窗口的本地的键映射(都为normal模式)
----@param key string 映射的键
----@param operation any 执行的操作
-M.map = function(key, operation)
-    -- api.nvim_buf_set_keymap(M.bufnr, 'n', key, operation, { silent = true, noremap = true, })
-    vim.keymap.set('n', key, operation, {
-        silent = true,
-        buffer = M.bufnr,
-    })
-end
-
-
---- =================== Window lines ================================
-local function insert_line(text)
-    vim.validate {
-        text = { text, 's' },
-    }
-
-    M.size = M.size + 1
-    M.lines[M.size] = text
-end
-
-
----向窗口中添加新行
----@param newline string 待添加的新行
----@param opt? table|string 可选的行属性: highlight, TODO :
-M.addline = function(newline, opt)
-    insert_line(newline)
-
-    if type(opt) == 'string' then
-        table.insert(M.highlights, {
-            name = opt,
-            line = M.size - 1, -- NOTE : 高亮的行号是以0为第一行
-            _start = 0,
-            _end = -1,
-        })
-        -- elseif type(opt) == 'table' then
-        --     -- TODO :
-        --     error('TODO')
-    end
-end
-
----添加一行新的内容并居中
----@param text string 需要居中的文本
----@param highlight? string 可选的高亮组
-M.center = function(text, highlight)
-    vim.validate {
-        text = { text, 's' }
-    }
-    local space = math.floor((M.width - text:width()) / 2)
-    local interval = (' '):rep(space)
-    insert_line(interval .. text)
-    if highlight then
-        table.insert(M.highlights, {
-            name = highlight,
-            line = M.size - 1,
-            _start = space,
-            _end = space + #text,
-        })
-    end
-end
-
-
----返回一个行的包装器: 具有 [add_item] [load] 方法
----能够添加item, 调用load格式化并载入window.lines
-M.line_wrap = function()
-    local items = {}
-    local width = 0 -- 所有item的总width
-    local size  = 0 -- item数目
-    return {
-        add_item = function(item, highlight)
-            size = size + 1
-            items[size] = { item, highlight }
-            width = width + item:width()
-        end,
-
-        load = function()
-            assert(size > 1, 'no item need be loaded')
-            local space = math.floor((M.width - width) / (size - 1))
-            assert(space > 0, 'try to expand window width')
-            local interval = (' '):rep(space)
-            local value = ''
-            local function load_item(idx)
-                local item = items[idx]
-                if item[2] then
-                    table.insert(M.highlights, {
-                        name = item[2],
-                        line = M.size, -- NOTE : 此时还没插入新行, size ==> 行号(zero index)
-                        _start = #value,
-                        _end = #value + #item[1],
-                    })
+    local win = setmetatable({
+        title   = nil,
+        content = nil,
+        winid   = winid,
+        bufnr   = bufnr,
+        width   = opt.width,
+        height  = opt.height,
+        hl      = api.nvim_create_namespace('TransWinHl'),
+    },
+        { __index = function(tbl, key)
+            if key == 'content' then
+                if tbl.title then
+                    tbl.content = require('Trans.content')(tbl, tbl.title.size)
+                    tbl.title.modifiable = false
+                else
+                    tbl.content = require('Trans.content')(tbl)
                 end
-                value = value .. item[1]
+                return tbl.content
+
+            elseif key == 'title' then
+                tbl.title = require('Trans.content')(tbl, 0)
+                return tbl.title
+
+            else
+                return window[key]
             end
+        end })
 
-            load_item(1)
-            for i = 2, size do
-                value = value .. interval
-                load_item(i)
-            end
-
-            insert_line(value)
-        end
-    }
+    win:set('winhl', 'Normal:TransWin,FloatBorder:TransBorder')
+    win:bufset('bufhidden', 'wipe')
+    win:bufset('filetype', 'Trans')
+    return win
 end
-
-
-
-M.text_wrap = function()
-    insert_line('')
-    local l = M.size
-
-    return function(text, highlight)
-        if highlight then
-            local _start = #M.lines[l]
-            local _end = _start + #text
-            table.insert(M.highlights, {
-                name = highlight,
-                line = M.size - 1,
-                _start = _start,
-                _end = _end,
-            })
-        end
-
-        M.lines[l] = M.lines[l] .. text
-    end
-end
-
-
-
---- =================== Window Highlights ================================
---- TODO : add helpful function for highlights
-
-return M

From daad638c031b24f1c0d3445daa5ef3c501048ceb Mon Sep 17 00:00:00 2001
From: JuanZoran <1430359574@qq.com>
Date: Fri, 20 Jan 2023 17:06:49 +0800
Subject: [PATCH 4/7] docs: modify README in TODO

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 3599452..1b5f88c 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
   - [高亮组](#高亮组)
   - [声明](#声明)
   - [感谢](#感谢)
-  - [TODO](#todo)
+  - [待办 (画大饼)](#待办-画大饼)
 <!--toc:end-->
 
 
@@ -194,7 +194,7 @@ vim.keymap.set('n', 'mi', '<Cmd>TranslateInput<CR>')
 - [sqlite.lua](https://github.com/kharji/sqlite.lua)    数据库访问
 - [T.vim](https://github.com/sicong-li/T.vim)           灵感来源
 
-## TODO
+## 待办 (画大饼)
 - ~~移动光标自动关闭窗口~~
 - 多风格样式
 - 历史查询结果保存

From 54f8869a7088059fa4f99f6728f85752241a8b74 Mon Sep 17 00:00:00 2001
From: JuanZoran <1430359574@qq.com>
Date: Fri, 20 Jan 2023 20:50:16 +0800
Subject: [PATCH 5/7] feat: add slid and fold window animation style!

---
 lua/Trans/init.lua       |  12 ++++-
 lua/Trans/view/hover.lua |   7 +--
 lua/Trans/window.lua     | 103 ++++++++++++++++++++++++++++++---------
 3 files changed, 93 insertions(+), 29 deletions(-)

diff --git a/lua/Trans/init.lua b/lua/Trans/init.lua
index a7232a1..f63ed41 100644
--- a/lua/Trans/init.lua
+++ b/lua/Trans/init.lua
@@ -21,7 +21,11 @@ M.conf = {
             pageup = '[[',
             pagedown = ']]',
         },
-        animation = 13,
+        animation = {
+            open = 'slid',
+            close = 'slid',
+            interval = 12,
+        }
     },
     float = {
         width = 0.8,
@@ -35,7 +39,11 @@ M.conf = {
         keymap = {
             quit = 'q',
         },
-        animation = 9,
+        animation = {
+            open = 'slid',
+            close = 'slid',
+            interval = 8,
+        }
     },
     order = {
         -- offline = {
diff --git a/lua/Trans/view/hover.lua b/lua/Trans/view/hover.lua
index 6db4fdb..691ac13 100644
--- a/lua/Trans/view/hover.lua
+++ b/lua/Trans/view/hover.lua
@@ -2,7 +2,7 @@ local conf = require('Trans').conf
 local icon = conf.icon
 
 local m_window
-local m_result 
+local m_result
 local m_content
 local m_indent = '    '
 
@@ -193,7 +193,7 @@ return function(word)
     -- 目前只处理了本地数据库的查询
     m_result    = require('Trans.query.offline')(word)
     local hover = conf.hover
-    local opt = {
+    local opt   = {
         relative = 'cursor',
         width    = hover.width,
         height   = hover.height,
@@ -204,6 +204,7 @@ return function(word)
     }
 
     m_window = require("Trans.window")(false, opt)
+    m_window.animation = hover.animation
     m_content = m_window.content
 
     if m_result then
@@ -231,6 +232,6 @@ return function(word)
             if m_window:is_open() then
                 action[act]()
             end
-        end)
+        end, { buffer = true, silent = true })
     end
 end
diff --git a/lua/Trans/window.lua b/lua/Trans/window.lua
index 9e21064..8fcf002 100644
--- a/lua/Trans/window.lua
+++ b/lua/Trans/window.lua
@@ -1,11 +1,13 @@
 local api = vim.api
 
+
 ---@diagnostic disable-next-line: duplicate-set-field
 function string:width()
     ---@diagnostic disable-next-line: param-type-mismatch
     return vim.fn.strwidth(self)
 end
 
+---@type window
 local window = {
     ---设置窗口的选项
     ---@param self table 需要设置的window
@@ -93,6 +95,37 @@ local window = {
                 self:set_width(self.content.lines[1]:width())
             end
         end
+
+        self:open(10)
+    end,
+
+    open = function(self)
+        local animation = self.animation
+        if animation.open then
+            local status = self:option('wrap')
+            self:set('wrap', false)
+
+            local handler
+            local function wrap(name, target)
+                local count = 0
+                return function()
+                    if count < self[target] then
+                        count = count + 1
+                        api['nvim_win_set_' .. target](self.winid, count)
+                        vim.defer_fn(handler[name], animation.interval)
+                    else
+                        self:set('wrap', status)
+                    end
+                end
+            end
+
+            handler = {
+                slid = wrap('slid', 'width'),
+                fold = wrap('fold', 'height'),
+            }
+
+            handler[animation.open]()
+        end
     end,
 
     ---**重新绘制内容**(标题不变)
@@ -102,37 +135,57 @@ local window = {
     end,
 
     ---安全的关闭窗口
-    ---@param self table 窗口对象
-    ---@param interval integer 动画的间隔
-    try_close = function(self, interval)
-        vim.validate {
-            interval = { interval, 'n' }
-        }
-
+    try_close = function(self)
         if self:is_open() then
-            local function narrow()
-                if self.height > 1 then
-                    self.height = self.height - 1
-                    api.nvim_win_set_height(self.winid, self.height)
-                    vim.defer_fn(narrow, interval)
-                else
-                    -- Wait animation done
-                    vim.defer_fn(function()
-                        api.nvim_win_close(self.winid, true)
-                        self.winid = -1
-                    end, interval + 2)
-                end
-            end
+            if self.animation.close then
+                local animation = self.animation
+                self:set('wrap', false)
 
-            narrow()
+                local handler
+                local function wrap(name, target)
+                    return function()
+                        if self[target] > 1 then
+                            self[target] = self[target] - 1
+                            api['nvim_win_set_' .. target](self.winid, self[target])
+                            vim.defer_fn(handler[name], animation.interval)
+                        else
+                            vim.defer_fn(function()
+                                api.nvim_win_close(self.winid, true)
+                                self.winid = -1
+                            end, animation.interval + 2)
+                        end
+                    end
+                end
+
+                handler = {
+                    slid = wrap('slid', 'width'),
+                    fold = wrap('fold', 'height'),
+                }
+
+                handler[animation.close]()
+
+            else
+                api.nvim_win_close(self.winid, true)
+                self.winid = -1
+            end
         end
     end,
 }
 
+---@class window
+---@field title table 窗口不变的title对象,载入后不可修改
+---@field winid integer 窗口的handle
+---@field bufnr integer 窗口对应buffer的handle
+---@field content table 窗口内容的对象, 和title一样是content类
+---@field width integer 窗口当前的宽度
+---@field height integer 窗口当前的高度
+---@field hl integer 窗口highlight的namespace
+
+
 ---窗口对象的构造器
 ---@param entry boolean 光标初始化时是否应该进入窗口
 ---@param option table 需要设置的选项
----@return table
+---@return window
 ---@nodiscard
 return function(entry, option)
     vim.validate {
@@ -161,10 +214,10 @@ return function(entry, option)
     local winid = api.nvim_open_win(bufnr, entry, opt)
 
     local win = setmetatable({
-        title   = nil,
-        content = nil,
         winid   = winid,
         bufnr   = bufnr,
+        title   = nil,
+        content = nil,
         width   = opt.width,
         height  = opt.height,
         hl      = api.nvim_create_namespace('TransWinHl'),
@@ -191,5 +244,7 @@ return function(entry, option)
     win:set('winhl', 'Normal:TransWin,FloatBorder:TransBorder')
     win:bufset('bufhidden', 'wipe')
     win:bufset('filetype', 'Trans')
+
+    ---@diagnostic disable-next-line: return-type-mismatch
     return win
 end

From c154ee94b585b81c5a62f84758482f979abe2ec2 Mon Sep 17 00:00:00 2001
From: JuanZoran <1430359574@qq.com>
Date: Fri, 20 Jan 2023 22:28:47 +0800
Subject: [PATCH 6/7] feat: add pretty cool animation and keymap support

---
 lua/Trans/init.lua       |  2 ++
 lua/Trans/view/hover.lua | 69 ++++++++++++++++++++++++++++++++++------
 lua/Trans/window.lua     | 61 +++++++++++++++++++++++++++--------
 3 files changed, 109 insertions(+), 23 deletions(-)

diff --git a/lua/Trans/init.lua b/lua/Trans/init.lua
index f63ed41..389b50f 100644
--- a/lua/Trans/init.lua
+++ b/lua/Trans/init.lua
@@ -20,6 +20,8 @@ M.conf = {
             -- TODO :
             pageup = '[[',
             pagedown = ']]',
+            pin = '_',
+            close = '+',
         },
         animation = {
             open = 'slid',
diff --git a/lua/Trans/view/hover.lua b/lua/Trans/view/hover.lua
index 691ac13..cbe7432 100644
--- a/lua/Trans/view/hover.lua
+++ b/lua/Trans/view/hover.lua
@@ -1,3 +1,4 @@
+local api = vim.api
 local conf = require('Trans').conf
 local icon = conf.icon
 
@@ -174,6 +175,16 @@ local process = {
 }
 
 
+local cmd_id
+
+local try_del_keymap = function()
+    for _, key in pairs(conf.hover.keymap) do
+        pcall(vim.keymap.del, 'n', key, { buffer = true })
+    end
+end
+
+
+local pin = false
 local action = {
     pageup = function()
         m_window:normal('gg')
@@ -182,6 +193,45 @@ local action = {
     pagedown = function()
         m_window:normal('G')
     end,
+
+    pin = function()
+        if pin then
+            error('too many window')
+        end
+        api.nvim_del_autocmd(cmd_id)
+        m_window:set('wrap', false)
+
+        m_window:try_close(function()
+            m_window:reopen(false, {
+                relative = 'editor',
+                row = 1,
+                col = vim.o.columns - m_window.width - 3,
+            }, function ()
+                m_window:set('wrap', true)
+            end)
+
+            m_window:bufset('bufhidden', 'wipe')
+            vim.keymap.del('n', conf.hover.keymap.pin, { buffer = true })
+
+            local buf = m_window.bufnr
+            pin = true
+
+            api.nvim_create_autocmd('BufWipeOut', {
+                callback = function(opt)
+                    if opt.buf == buf then
+                        pin = false
+                        api.nvim_del_autocmd(opt.id)
+                    end
+                end
+            })
+        end)
+    end,
+
+    close = function()
+        m_window:set('wrap', false)
+        m_window:try_close()
+        try_del_keymap()
+    end,
 }
 
 
@@ -214,24 +264,25 @@ return function(word)
     else
         process.failed()
     end
-    m_window:set('wrap', true)
 
     m_window:draw(true)
+    m_window:open(function ()
+        m_window:set('wrap', true)
+    end)
+
     -- Auto Close
-    vim.api.nvim_create_autocmd(
-        { --[[ 'InsertEnter', ]] 'CursorMoved', 'BufLeave', }, {
+    cmd_id = api.nvim_create_autocmd(
+        { 'InsertEnter', 'CursorMoved', 'BufLeave', }, {
         buffer = 0,
         once = true,
         callback = function()
-            m_window:try_close(hover.animation)
+            m_window:set('wrap', false)
+            m_window:try_close()
+            try_del_keymap()
         end,
     })
 
     for act, key in pairs(hover.keymap) do
-        vim.keymap.set('n', key, function()
-            if m_window:is_open() then
-                action[act]()
-            end
-        end, { buffer = true, silent = true })
+        vim.keymap.set('n', key, action[act], { buffer = true, silent = true })
     end
 end
diff --git a/lua/Trans/window.lua b/lua/Trans/window.lua
index 8fcf002..6abc068 100644
--- a/lua/Trans/window.lua
+++ b/lua/Trans/window.lua
@@ -7,6 +7,13 @@ function string:width()
     return vim.fn.strwidth(self)
 end
 
+local busy = false
+local function check_busy()
+    while busy do
+        vim.wait(50)
+    end
+end
+
 ---@type window
 local window = {
     ---设置窗口的选项
@@ -95,26 +102,28 @@ local window = {
                 self:set_width(self.content.lines[1]:width())
             end
         end
-
-        self:open(10)
     end,
 
-    open = function(self)
+    open = function(self, callback)
         local animation = self.animation
         if animation.open then
-            local status = self:option('wrap')
-            self:set('wrap', false)
+            check_busy()
 
             local handler
             local function wrap(name, target)
                 local count = 0
                 return function()
                     if count < self[target] then
+                        busy = true
                         count = count + 1
                         api['nvim_win_set_' .. target](self.winid, count)
                         vim.defer_fn(handler[name], animation.interval)
+
                     else
-                        self:set('wrap', status)
+                        busy = false
+                        if type(callback) == 'function' then
+                            callback()
+                        end
                     end
                 end
             end
@@ -134,24 +143,35 @@ local window = {
         self.content:attach()
     end,
 
+
     ---安全的关闭窗口
-    try_close = function(self)
+    try_close = function(self, callback)
         if self:is_open() then
-            if self.animation.close then
-                local animation = self.animation
-                self:set('wrap', false)
+            check_busy()
+            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]
                     return function()
-                        if self[target] > 1 then
-                            self[target] = self[target] - 1
-                            api['nvim_win_set_' .. target](self.winid, self[target])
+                        if count > 1 then
+                            busy = true
+                            count = count - 1
+                            api['nvim_win_set_' .. target](self.winid, count)
                             vim.defer_fn(handler[name], animation.interval)
+
                         else
                             vim.defer_fn(function()
                                 api.nvim_win_close(self.winid, true)
                                 self.winid = -1
+                                busy = false
+
+                                if type(callback) == 'function' then
+                                    callback()
+                                end
+
                             end, animation.interval + 2)
                         end
                     end
@@ -170,6 +190,19 @@ local window = {
             end
         end
     end,
+
+    reopen = function (self, entry, opt, callback)
+        check_busy()
+        self.config.win = nil
+        if opt then
+            for k, v in pairs(opt) do
+                self.config[k] = v
+            end
+        end
+
+        self.winid = api.nvim_open_win(self.bufnr, entry, self.config)
+        self:open(callback)
+    end
 }
 
 ---@class window
@@ -182,6 +215,7 @@ local window = {
 ---@field hl integer 窗口highlight的namespace
 
 
+
 ---窗口对象的构造器
 ---@param entry boolean 光标初始化时是否应该进入窗口
 ---@param option table 需要设置的选项
@@ -242,7 +276,6 @@ return function(entry, option)
         end })
 
     win:set('winhl', 'Normal:TransWin,FloatBorder:TransBorder')
-    win:bufset('bufhidden', 'wipe')
     win:bufset('filetype', 'Trans')
 
     ---@diagnostic disable-next-line: return-type-mismatch

From 09806f01a6d8e87a6ff45a7a97a5d6a7bb0a7573 Mon Sep 17 00:00:00 2001
From: JuanZoran <1430359574@qq.com>
Date: Fri, 20 Jan 2023 22:32:53 +0800
Subject: [PATCH 7/7] fix: fix close hover window twice in some case

---
 lua/Trans/view/hover.lua | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/lua/Trans/view/hover.lua b/lua/Trans/view/hover.lua
index cbe7432..d94b3a7 100644
--- a/lua/Trans/view/hover.lua
+++ b/lua/Trans/view/hover.lua
@@ -198,7 +198,11 @@ local action = {
         if pin then
             error('too many window')
         end
-        api.nvim_del_autocmd(cmd_id)
+        if cmd_id > 0 then
+            api.nvim_del_autocmd(cmd_id)
+            cmd_id = -1
+        end
+
         m_window:set('wrap', false)
 
         m_window:try_close(function()
@@ -206,7 +210,7 @@ local action = {
                 relative = 'editor',
                 row = 1,
                 col = vim.o.columns - m_window.width - 3,
-            }, function ()
+            }, function()
                 m_window:set('wrap', true)
             end)
 
@@ -228,6 +232,11 @@ local action = {
     end,
 
     close = function()
+        if cmd_id > 0 then
+            api.nvim_del_autocmd(cmd_id)
+            cmd_id = -1
+        end
+
         m_window:set('wrap', false)
         m_window:try_close()
         try_del_keymap()
@@ -266,7 +275,7 @@ return function(word)
     end
 
     m_window:draw(true)
-    m_window:open(function ()
+    m_window:open(function()
         m_window:set('wrap', true)
     end)