Wansmer / langmapper.nvim

A plugin that makes Neovim more friendly to non-English input methods 🤝
MIT License
128 stars 7 forks source link

How to use with multiple layouts / languages #13

Closed DeadlySquad13 closed 5 months ago

DeadlySquad13 commented 1 year ago

Hello! Thanks for the plugin, it helps a lot! Unfortunately, I still can't get my head around workflow with three and more layouts. In my case it's ru, en-qwerty and en-hdn (custom layout Hands Down Neu). I want to use nvim commands when either ru or en-hdn is selected.

Currently, I have only defaults from readme:

    local en =                   [[`qwertyuiop[]asdfghjkl;'\\zxcvbnm,./]]
    local hands_down_neu =       [[`wfmpv/.q"'z(rsntg,aeihj)\xcldb-uoyk]]

    local en_shift =             [[~QWERT^[_]P{}ASDFGHJKL:"||ZXCVBNM<>?]]
    local hands_down_neu_shift = [[~WFMPV*:Q[]Z{RSNTG;AEIHJ}|XCLDB+UOYK]]

    langmap = vim.fn.join({
        -- | `to` should be first     | `from` should be second
        -- escape(ru_shift) .. ';' .. escape(en_shift),
        escape(hands_down_neu_shift) .. ';' .. escape(en_shift),
        escape(hands_down_neu) .. ';' .. escape(en),
    }, ','),
return {
  'Wansmer/langmapper.nvim',
  lazy = false,
  priority = 1, -- High priority is needed if you will use `autoremap()`.
  config = function()
    require('langmapper').setup({--[[ your config ]]})
  end,
}
return {
  'folke/which-key.nvim',

  opts = require('ds_omega.config.Ui.which_key.settings'),
  config = function(_, opts)
    vim.o.timeout = true
    vim.o.timeoutlen = 300

    local lmu = require('langmapper.utils')
    local view = require('which-key.view')
    local execute = view.execute

    -- wrap `execute()` and translate sequence back
    view.execute = function(prefix_i, mode, buf)
      -- Translate back to English characters
      prefix_i = lmu.translate_keycode(prefix_i, 'default', 'ru')
      execute(prefix_i, mode, buf)
    end

    require('which-key').setup(opts)

    -- Just some small wrapper around `which_key.register`.
    local apply_keymappings = require('ds_omega.config.Ui.which_key.utils').apply_keymappings

    local mappings = require('ds_omega.config.keymappings')

    for mode, mode_mappings in pairs(mappings) do
        apply_keymappings(mode, mode_mappings)
    end
  end
}

Notice that I have which-key setup with ru while langmap with en-hdn. This way all my custom mappings work in ru and only default mappings in en-hdn. Of course it's not solution as I have to define a lot of mappings with which-key and I can't use anything more then hjkl in en-hdn)

I assume it's possible to make a langmap with two layouts combined. But not sure how should which-key be handled.

I hope someone can help with advise on how to setup this plugin and langmap! I'm sure there're a lot of multilingual people or just enthusiasts with custom layouts who will struggle with this issue!

Wansmer commented 1 year ago

Hi! I personally don't use more than two input methods, but technically it should work:

  1. Try to add both layouts (ru and hdn) to langmap:

    langmap = vim.fn.join({
        escape(hands_down_neu_shift) .. ';' .. escape(en_shift),
        escape(hands_down_neu) .. ';' .. escape(en),
        escape(ru_shift) .. ';' .. escape(en_shift),
        escape(ru) .. ';' .. escape(en),
    }, ','),
  2. Duplicate execute in which-key:

    view.execute = function(prefix_i, mode, buf)
      prefix_i = lmu.translate_keycode(prefix_i, 'default', 'ru')
      execute(prefix_i, mode, buf)
      prefix_i2 = lmu.translate_keycode(prefix_i, 'default', 'hdn')
      execute(prefix_i2, mode, buf)
    end
  3. Don't forget to add hdn to the settings:

    require('langmapper').setup({
    --[[ other settings ]]
    layouts = {
    ru = { --[[ ru settings ]]
    },
    hdn = {
      id = 'you_layout_id_from_os',
      -- It is an example, I have not tested this
      layout = [[wfmpv/.q"'z(rsntg,aeihj)xcldb-uoykWFMPV*:Q[]Z{RSNTG;AEIHJ}XCLDB+UOYK]],
      default_layout = [[qwertyuiop[]asdfghjkl;'\zxcvbnm,./QWERT^[_]P{}ASDFGHJKL:"|ZXCVBNM<>?]],
    },
    },
    -- [[ other settings ]]
    })

    I'm not sure if this would work. Please let me know the result.

DeadlySquad13 commented 1 year ago

Hi, thanks for the quick response! I tested your solution and it only partially works. At first I thought that there's something off with layout strings so I modified your code a little bit to be sure that everything matches:

return {
    'Wansmer/langmapper.nvim',
    lazy = false,
    priority = 1, -- High priority is needed if you will use `autoremap()`.
    opts = function()
      local im_select_get_current_layout_id = function()
        local cmd = 'im-select'
        if vim.fn.executable(cmd) then
          local output = vim.split(vim.trim(vim.fn.system(cmd)), '\n')
          return output[#output]
        end
      end

      -- The same tables as in langmap:
      local ru = [[ёйцукенгшщзхъфывапролджэ\\ячсмитьбю.]]
      local en = [[`qwertyuiop[]asdfghjkl;'\\zxcvbnm,./]]
      local hands_down_neu = [[`wfmpv/.q"'z(rsntg,aeihj)\xcldb-uoyk]]

      local ru_shift = [[ЁЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭ//ЯЧСМИТЬБЮ,]]
      local en_shift = [[~QWERTYUIOP{}ASDFGHJKL:"||ZXCVBNM<>?]]
      local hands_down_neu_shift = [[~WFMPV*:Q[]Z{RSNTG;AEIHJ}|XCLDB+UOYK]]

      return {
          ---@type string Standart English layout (on Mac, It may be different in your case.)
          default_layout = en .. en_shift,
          layouts = {
              ru = {
                  id = '1049',
                  layout = ru .. ru_shift,
                  default_layout = nil,
              },
              hdn = {
                  id = '1033',
                  layout = hands_down_neu .. hands_down_neu_shift,
                  -- layout = [[wfmpv/.q"'z(rsntg,aeihj)xcldb-uoykWFMPV*:Q[]Z{RSNTG;AEIHJ}XCLDB+UOYK]],
                  -- default_layout = [[qwertyuiop[]asdfghjkl;'\zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"|ZXCVBNM<>?]],
                  default_layout = nil,
              },
          },
          os = {
              -- Darwin - Mac OS, the result of `vim.loop.os_uname().sysname`
              Darwin = {
                  ---Function for getting current keyboard layout on your OS
                  ---Should return string with id of layout
                  ---@return string
                  get_current_layout_id = im_select_get_current_layout_id,
              },
              Windows_NT = {
                  ---Function for getting current keyboard layout on your OS
                  ---Should return string with id of layout
                  ---@return string
                  get_current_layout_id = im_select_get_current_layout_id,
              },
          },
      }
    end,
    config = function(_, opts)
      require('langmapper').setup(opts)
    end,
}

But it worked only with russian layout, hdn was completely off.

I started investigating this problem from langmap as I thought that at least basic hjkl should work. Nevertheless, after doublechecking it was clear that langmap is completely fine.

I tried removing ru translating from whichkey and it didn't help either. What was the most interesting is the fact that hdn layout wasn't working - not ru which I thought should be broken.

I looked at the langmapper layout table once again and decided to remove ru. After next restart I've got an error indicating different length of layouts for ru (btw, seems like a bug to me. Why is ru always assumed to be present in layouts table and got merged with defaults if it isn't?). Nevertheless, hdn started working. Of course without ru custom mappings, only default ones so langmap is 100% right.

Wansmer commented 1 year ago

(btw, seems like a bug to me. Why is ru always assumed to be present in layouts table and got merged with defaults if it isn't?)

Hmm, how did you delete ru? The correct way is to use the use_layouts = { 'hdn' }. But maybe you're right - I'll consider adding a disable field to the layout settings.

Is it works without using which-key?

Also, how did you check langmap? Did you see at output of := vim.opt.langmap:get()?

DeadlySquad13 commented 1 year ago

(btw, seems like a bug to me. Why is ru always assumed to be present in layouts table and got merged with defaults if it isn't?)

Hmm, how did you delete ru? The correct way is to use the use_layouts = { 'hdn' }.

Just removed it from layouts table in setup call.

But maybe you're right - I'll consider adding a disable field to the layout settings.

I think it's more intuitive from a user point of view to just delete unneeded layout rather then manage it via enabled / disabled tables.

Also, how did you check langmap? Did you see at output of := vim.opt.langmap:get()?

Langmap was set, I checked it a few times by :set langmap?. However, by checking I meant: do base keymaps like hjkl work or not.

Is it works without using which-key?

I disabled which-key completely and tried with this config (added ru back and added keymap to setup). It worked for both layouts:

return {
    'Wansmer/langmapper.nvim',
    lazy = false,
    priority = 1, -- High priority is needed if you will use `autoremap()`.
    opts = function()
      local im_select_get_current_layout_id = function()
        local cmd = 'im-select'
        if vim.fn.executable(cmd) then
          local output = vim.split(vim.trim(vim.fn.system(cmd)), '\n')
          return output[#output]
        end
      end

      local ru = [[ёйцукенгшщзхъфывапролджэ\\ячсмитьбю.]]
      local en = [[`qwertyuiop[]asdfghjkl;'\\zxcvbnm,./]]
      local hands_down_neu = [[`wfmpv/.q"'z(rsntg,aeihj)\xcldb-uoyk]]

      local ru_shift = [[ЁЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭ//ЯЧСМИТЬБЮ,]]
      local en_shift = [[~QWERTYUIOP{}ASDFGHJKL:"||ZXCVBNM<>?]]
      local hands_down_neu_shift = [[~WFMPV*:Q[]Z{RSNTG;AEIHJ}|XCLDB+UOYK]]

      return {
          ---@type string Standart English layout (on Mac, It may be different in your case.)
          default_layout = en .. en_shift,
          layouts = {
              ru = {
                  id = '1049',
                  layout = ru .. ru_shift,
                      default_layout = nil,
              },
              hdn = {
                  id = '1033',
                  layout = hands_down_neu .. hands_down_neu_shift,
                  -- layout = [[wfmpv/.q"'z(rsntg,aeihj)xcldb-uoykWFMPV*:Q[]Z{RSNTG;AEIHJ}XCLDB+UOYK]],
                  -- default_layout = [[qwertyuiop[]asdfghjkl;'\zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"|ZXCVBNM<>?]],
                  default_layout = nil,
              },
          },
          os = {
              -- Darwin - Mac OS, the result of `vim.loop.os_uname().sysname`
              Darwin = {
                  ---Function for getting current keyboard layout on your OS
                  ---Should return string with id of layout
                  ---@return string
                  get_current_layout_id = im_select_get_current_layout_id,
              },
              Windows_NT = {
                  ---Function for getting current keyboard layout on your OS
                  ---Should return string with id of layout
                  ---@return string
                  get_current_layout_id = im_select_get_current_layout_id,
              },
          },
      }
    end,
    config = function(_, opts)
      require('langmapper').setup(opts)

      local map = require('langmapper').map

      map('n', '<Leader>nb', '<Cmd>Telescope buffers<Cr>')
    end,
}
Wansmer commented 1 year ago

I disabled which-key completely and tried with this config (added ru back and added keymap to setup). It worked for both layouts:

Well, it's mean what problem with which-key when using more than two layouts. I have no idea how to fix this now.

One of the possible solutions is to check the layout in the execute function to send the right key, but this will cause the big delay after each keypress. (I just checked again the exetute implementation in the which-key source and it feeds keys in real time. No way to prepare mapping translation.)

When I figure out how to fix it, I'll let you know.

DeadlySquad13 commented 1 year ago

I disabled which-key completely and tried with this config (added ru back and added keymap to setup). It worked for both layouts:

Well, it's mean what problem with which-key when using more than two layouts. I have no idea how to fix this now.

One of the possible solutions is to check the layout in the execute function to send the right key, but this will cause the big delay after each keypress. (I just checked again the exetute implementation in the which-key source and it feeds keys in real time. No way to prepare mapping translation.)

When I figure out how to fix it, I'll let you know.

Thanks a lot for your effort, hope you will find the solution.

On my side I will try which-key alternatives like legendary, maybe they will work better.

DeadlySquad13 commented 1 year ago

@Wansmer Can you please explain how hack_keymap option works? In particular: is there any difference between it and map function.

I'm asking because it seemed strange to me that all mappings (default and which-key) didn't work even when I removed ru translation from view.execute. In particular hdn mappings weren't working, not ru.

return {
  'folke/which-key.nvim',
  enabled = true,

  opts = require('ds_omega.config.Ui.which_key.settings'),
  config = function(_, opts)
    vim.o.timeout = true
    vim.o.timeoutlen = 300

    local lmu = require('langmapper.utils')
    local view = require('which-key.view')
    local execute = view.execute

    -- wrap `execute()` and translate sequence back
    view.execute = function(prefix_i, mode, buf)
      -- Translate back to English characters
      -- local ru_prefix_i = lmu.translate_keycode(prefix_i, 'default', 'ru')
      -- execute(ru_prefix_i, mode, buf)

      local hdn_prefix_i = lmu.translate_keycode(prefix_i, 'default', 'hdn')
      execute(hdn_prefix_i, mode, buf)
    end

    require('which-key').setup(opts)

    local apply_keymappings = require('ds_omega.config.Ui.which_key.utils').apply_keymappings

    local mappings = require('ds_omega.config.keymappings')

    for mode, mode_mappings in pairs(mappings) do
        apply_keymappings(mode, mode_mappings)
    end
  end
}

I decided to test my theory by setting hack_keymap to false. And it fixed default mappings for hdn. Which-key mappings still work only for one layout even if I return ru traslation back to view.execute. Looks like last one overrides previous.

After this small experiment It seems to me that something's off with hack_keymap mechanism. Otherwise it shouldn't have ruined default mappings. On the other hand map util works fine on both layouts. As for me, it's strange... Are there any major differences between these two?

Wansmer commented 1 year ago

The best explanation is to look at the code of hack_keymap. Hacking keymap is just a wrapper of vim.api.nvim_keymap_set with translate_keycode inside (same as map). It works correctly.

You can use map without hack_keymap and autoremap if it works better for you.