Skip to content

phanen/cword.nvim

Repository files navigation

cword.nvim

CJK-aware word motion for Neovim. w lands on cjdict-merged CJK runs (你好 is one word, hello is one word) so daw, viw, etc. work the same way they do on Latin identifiers. w/b/e/ge also handle non-keyword non-whitespace runs (->, **, ]]) as single words, matching stock Vim's behaviour.

Dependencies

  • Neovim >= 0.11
  • libicuuc (the icu_ffi backend is mandatory; every supported platform ships it and the LuaJIT FFI binding loads it eagerly)

Install

{
  'phanen/cword.nvim',
  lazy = true,
  keys = { 'w', 'b', 'e', 'ge', '<m-f>', '<m-b>', '<c-w>' },
  config = function()
    local cword = require('cword')
    local opts = { noremap = true, silent = true }

    -- Normal + visual
    vim.keymap.set({ 'n', 'x' }, 'w',  cword.move_forward, opts)
    vim.keymap.set({ 'n', 'x' }, 'b',  cword.move_backward, opts)
    vim.keymap.set({ 'n', 'x' }, 'e',  cword.move_end_forward, opts)
    vim.keymap.set({ 'n', 'x' }, 'ge', cword.move_end_backward, opts)

    -- Operator-pending (d/c/y + motion). `expr = true` is required:
    -- the handler returns a `<Cmd>lua ...<CR>` string that aborts
    -- the pending operator and runs the actual delete/change/yank
    -- in normal mode with `virtualedit=onemore` (so the cursor can
    -- sit one cell past the last byte — this is what makes CJK
    -- end-of-line motion exact).
    vim.keymap.set('o', 'w',  cword.op_forward,       vim.tbl_extend('force', opts, { expr = true }))
    vim.keymap.set('o', 'b',  cword.op_backward,      vim.tbl_extend('force', opts, { expr = true }))
    vim.keymap.set('o', 'e',  cword.op_end_forward,  vim.tbl_extend('force', opts, { expr = true }))
    vim.keymap.set('o', 'ge', cword.op_end_backward, vim.tbl_extend('force', opts, { expr = true }))

    -- Insert mode
    vim.keymap.set('i', '<m-f>', cword.insert_forward, opts)
    vim.keymap.set('i', '<m-b>', cword.insert_backward, opts)
    vim.keymap.set('i', '<c-w>', cword.insert_delete_word, opts)

    -- Command-line mode
    vim.keymap.set('c', '<m-f>', cword.cmdline_forward, opts)
    vim.keymap.set('c', '<m-b>', cword.cmdline_backward, opts)
    vim.keymap.set('c', '<c-w>', cword.cmdline_delete_word, opts)
  end,
}

API

Function Description
require('cword').setup(opts?) Bind the segmenter (optional, auto-called on first move_*). No opts.
cword.move_forward w handler. Supports count, wraps, visual mode.
cword.move_backward b handler.
cword.move_end_forward e handler.
cword.move_end_backward ge handler.
cword.op_forward Operator-pending w (use in 'o' mode with expr = true). Pairs with d/c/y.
cword.op_backward Operator-pending b.
cword.op_end_forward Operator-pending e.
cword.op_end_backward Operator-pending ge.
cword.insert_forward Insert-mode <m-f> / <alt-f>. Move cursor forward one word.
cword.insert_backward Insert-mode <m-b> / <alt-b>. Move cursor backward one word.
cword.insert_delete_word Insert-mode <c-w>. Delete word backward.
cword.cmdline_forward Command-line <m-f>. Move cursor forward one word.
cword.cmdline_backward Command-line <m-b>. Move cursor backward one word.
cword.cmdline_delete_word Command-line <c-w>. Delete word backward.
cword.Segmenter The icu_ffi segmenter module (.cut(str) → token list).
cword.motion Pure motion functions (forward(cut, line, cursor) etc.).
cword.get_cword() CJK-aware version of expand('<cword>'). Returns the merged run (你好) instead of one char. Empty string when the cursor is on whitespace or a non-word token.
cword.get_token() Full token under the cursor ({text, byte_start, byte_end, is_word_like}); nil on whitespace / non-word tokens.

Segmentation backend

Backend 你好世界 Notes
icu_ffi 你好, 世界 Real ICU via LuaJIT FFI. Merges CJK runs via cjdict; treats Latin and punctuation with ICU's word rules. Mandatory, no other backend.

Similar

License

MIT

About

ICU's word segmenter

Topics

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors