wlh320 / rime-ls

A language server for Rime input method engine 通过 LSP 代码补全使用 Rime 输入法
BSD 3-Clause "New" or "Revised" License
208 stars 13 forks source link

用数字选词以后还需要一次空格才能上屏幕 #20

Open dirichy opened 9 months ago

dirichy commented 9 months ago

Uploading RPReplay_Final1708562563.mp4…

dirichy commented 9 months ago

这个是bug吗还是本来就是这么设计的,有没有可能按数字直接就可以继续输入呀

wlh320 commented 9 months ago

目前暂时只能做到这样,LSP 只提供候选项,应该不清楚用户最终具体是怎么选择的?如果是针对特定的编辑器,或许可以通过写插件之类的做到按数字直接输入。

也有可能是我调研不清楚,我有空的时候再研究一下 LSP 协议

dirichy commented 9 months ago

有没有可能可以用keymap解决呢,数字k映射成让第k个选项上屏。这个做法有一个问题就是如果打的字比较长但是后面的选项比较短的话选了短的选项会吃掉所有打的字。所以能不能判断是否在按完数字键以后已经给所有的拼音选好了字呢?这个应该是LSP可以做的吧,我看了打印出来的entries,有一点点复杂,想问问里面有没有能判断是否已经给所有拼音选好字的选项,谢谢!

dirichy commented 9 months ago

https://github.com/wlh320/rime-ls/assets/144102919/0769aa34-31ab-4dbf-a5df-9b1040a5bb2a

我目前只能实现到这一步 不知道有没有办法检测是否已经完成一次输入。

wlh320 commented 9 months ago

感觉现在是可以判断的,已经可以上屏的字前面就没有数字序号了。

只考虑 nvim 场景的话,应该是可以通过给 cmp 写一些 lua 逻辑来做到,比如按数字键之后判断候选项只有一个并且不是数字开头,就自动补全之类的。我初步判断有希望实现,但我对 lua 和 cmp 的接口都不太熟悉,你可以试试。LSP server 这边应该是不用做什么东西

dirichy commented 9 months ago

好的,如果实现了我就把代码贴出来

dirichy commented 9 months ago

https://github.com/wlh320/rime-ls/assets/144102919/f50bf67a-167f-4a75-af17-248b8d8dabab

初步解决,但是这个方法性能比较差,快速输入的时候有时候不跟手。现在的原理是在数字输入后检查不全项目,相当慢。 想问问您这个lsp里面有没有接口能判断每个项目匹配到的输入的部分是什么,比如我输入nihao的时候第二个项目是“你”,我有没有可能通过api知道这个项目匹配到的是“ni”。 如果可以的话我就可以不在数字输入后在做判断而是在数字输入时直接判断数字对应的项目是否应该直接上屏了。

wlh320 commented 9 months ago

LSP 本身是存在一些限制的,我用 LSP 的初衷是一套代码尽可能支持更多编辑器。如果确定用 vim 或者 nvim,也可以试试一些直接应用 rime + 补全插件 的方案,比如之前的 rime-ls 用户实现的 https://github.com/yao-weijie/cmp-rime 。等我有时间我也会看看有没有更好的解决方案。

wlh320 commented 8 months ago

我试了一下,直接用 cmp 的 confirm 确实非常慢,但是手动调用 vim.lsp.util.apply_text_edits 就很快

这是我的实现,就是在配置 cmp 那里通过 cmp 提供的 callback 接口,判断 entry 的情况手动补全

但是实现还很初步,标点符号也会直接上屏 可以修改判断用的正则表达式,只自动上屏汉字,避免影响正常编程 Edit: 增加了简单的标点判断

    cmp.event:on('menu_opened', function()
      entry = cmp.core.view:get_first_entry()
      if entry and entry.source.name == "nvim_lsp"
          and entry.source.source.client.name == "rime_ls" then
        local item = entry:get_completion_item()
        local label = item.label
        local pos1, _ = string.find(label, "^%d%.")
        local pos2, _ = string.find(label, "^[,。《》?;:“”、!()【】「」〖〗]")
        if pos1 == nil and pos2 == nil then
          vim.lsp.util.apply_text_edits({ item.textEdit }, 0, 'utf-16')
        end
      end
    end)
liubianshi commented 8 months ago

我采用的思路是重新定义数字 1-9 这几个快捷键,当 rime_ls 通过数字键返回唯一的的候选项时,模拟按数字 0 的操作,数字 0 在这时候会上屏汉字。实际使用下来,效果和普通输入法类似,应该算非常跟手了。

M.keymaps["0"] = cmp.mapping(function(fallback)
  if not cmp.visible() or not utils.buf_rime_enabled() then
    return fallback()
  end

  local first_entry = cmp.core.view:get_first_entry()
  if not M.input_method_take_effect(first_entry) then
    return fallback()
  end

  M.rimels_auto_upload(cmp.core.view:get_entries())
end, { "i" })

for numkey = 1, 9 do
  local numkey_str = tostring(numkey)
  M.keymaps[numkey_str] = cmp.mapping(function(fallback)
    if not cmp.visible() or not utils.buf_rime_enabled() then
      return fallback()
    else
      local first_entry = cmp.core.view:get_first_entry()
      if
        not M.input_method_take_effect(
          first_entry,
          { "probe_punctuation_after_half_symbol" }
        )
      then
        return fallback()
      end
    end
    cmp.mapping.close()
    feedkey(numkey_str, "n")
    cmp.complete()
    feedkey("0", "m")
  end, { "i" })
end

这中间调用了一些辅助函数,如果有兴趣,可以在 liubianshi/cmp-lsp-rimels 中查看。

TwIStOy commented 7 months ago

你如果补全也在用 nvim-cmp 的话,可以尝试下我这个方案。

local just_inserted = false

local rime_ls_auto_confirm = vim.schedule_wrap(function()
  local cmp = require("cmp")
  if not cmp.visible() then
    return
  end
  local entries = cmp.core.view:get_entries()
  if entries == nil or #entries == 0 then
    return
  end
  local rime_ls_entries_cnt = 0
  for _, entry in ipairs(entries) do
    if is_rime_entry(entry) then
      rime_ls_entries_cnt = rime_ls_entries_cnt + 1
    end
  end
  local first_entry = cmp.get_selected_entry()
  if first_entry == nil then
    first_entry = cmp.core.view:get_first_entry()
  end
  if
    first_entry ~= nil
    and rime_ls_entries_cnt == 1
    and is_rime_entry(first_entry)
    and text_edit_range_length(first_entry) == 4
  then
    cmp.confirm {
      behavior = cmp.ConfirmBehavior.Insert,
      select = true,
    }
  end
end)

vim.api.nvim_create_autocmd("InsertCharPre", {
  buffer = bufnr,
  callback = function()
    just_inserted = true
  end,
})
vim.api.nvim_create_autocmd({ "TextChangedI", "TextChangedP" }, {
  buffer = bufnr,
  callback = function()
    if just_inserted then
      -- check completion
      rime_ls_auto_confirm()
      just_inserted = false
    end
  end,
})

这部分:https://github.com/TwIStOy/dotvim/blob/df9e00b6376c62a285b8c8fe37e61c4ffed251e8/lua/dotvim/pkgs/extra/misc/rime.lua#L46-L87

cxwx commented 6 months ago

感觉数字选词这一块交给 cmp 之类的步骤比较好,毕竟大多数人开了不止一个 lsp,插件的优势就是混合输入。

cxwx commented 6 months ago

nvim-cmp 的 index 功能还没merge https://github.com/hrsh7th/nvim-cmp/pull/1491/files

Kaiser-Yang commented 3 months ago

你如果补全也在用 nvim-cmp 的话,可以尝试下我这个方案。 这部分:https://github.com/TwIStOy/dotvim/blob/df9e00b6376c62a285b8c8fe37e61c4ffed251e8/lua/dotvim/pkgs/extra/misc/rime.lua#L46-L87

感谢 @TwIStOy 提供的方案,给了我很大的启发,但是提供的方案在快速输入的时候会有明显的卡顿,因此我对其做了如下的改动(没有绑定空格上屏,如果需要,只需要增加一个绑定即可):

local rime_ls_filetypes = { 'markdown', 'vimwiki' }
local function is_rime_entry(entry)
  return vim.tbl_get(entry, "source", "name") == "nvim_lsp"
    and vim.tbl_get(entry, "source", "source", "client", "name")
      == "rime_ls"
end
local cmp = require("cmp")
local function auto_upload_rime()
    if not cmp.visible() then
        return
    end
    local entries = cmp.core.view:get_entries()
    if entries == nil or #entries == 0 then
        return
    end
    local first_entry = cmp.get_selected_entry()
    if first_entry == nil then
        first_entry = cmp.core.view:get_first_entry()
    end
    if first_entry ~= nil and is_rime_entry(first_entry) then
        local rime_ls_entries_cnt = 0
        for _, entry in ipairs(entries) do
            if is_rime_entry(entry) then
                rime_ls_entries_cnt = rime_ls_entries_cnt + 1
                if rime_ls_entries_cnt == 2 then
                    break
                end
            end
        end
        if rime_ls_entries_cnt == 1 then
            cmp.confirm {
                behavior = cmp.ConfirmBehavior.Insert,
                select = true,
            }
        end
    end
end
vim.api.nvim_create_autocmd('FileType', {
    pattern = rime_ls_filetypes,
    callback = function ()
        for numkey = 1, 9 do
            local numkey_str = tostring(numkey)
            vim.api.nvim_buf_set_keymap(0, 'i', numkey_str, '', {
                noremap = true,
                silent = false,
                callback = function()
                    vim.fn.feedkeys(numkey_str, 'n')
                    vim.schedule(auto_upload_rime)
                end
            })
            vim.api.nvim_buf_set_keymap(0, 's', numkey_str, '', {
                noremap = true,
                silent = false,
                callback = function()
                    vim.fn.feedkeys(numkey_str, 'n')
                    vim.schedule(auto_upload_rime)
                end
            })
        end
    end
})

思路就是只对数字进行绑定,而不是在每次插入后都触发检查,这样只有在按下数字按键的时候才会触发自动上屏的检查,同时在检查的时候在可选数量为2时就直接结束循环,这样可以在补全列表的元素数量很多的时候提升性能,从而解决卡顿的问题。