1

I want to convert nonASCII characters to similar ASCII alternatives. I can do it at shell using echo şİçğ | iconv -f utf-8 -t ascii//translit but how can I do it for selected vim text?

1

You can implement a mapping to translit a visual selection by using the system() function and passing the contents of the selection as the input to the system command.

This mapping should work:

xnoremap <leader>T ygv"=system('iconv -f utf-8 -t ascii//translit', @@)<cr>p

Breaking it down:

  • y: Yank the visual selection into the default register.
  • gv: Go back to visual mode, on the previous selection. (This will allow us to replace it with a p next.)
  • "=: Use the expression register for the next p operation.
  • system(...)<cr>: Enter the system expression. The final <cr> finishes entering the expression and goes back to normal mode, where you can use it.
  • 'iconv ...': The external command to run on a shell.
  • @@: A reference to the default register, where we previously yanked the visual selection.
  • p: Put. In this case, put the contents of the expression register, replacing the visual selection.

You access this mapping by entering visual mode, selecting some text and using \T (or similar, if you have overridden mapleader, in which case use that rather than a backslash as prefix.)

This works in both character-wise and linewise Visual mode. It doesn't work that well in block-wise mode, for some reason Vim loses track of the block once you modify the register.

With a bit more work, you could also make this work in normal mode with an operator (like \Tw or \Tiw to translit the current word under the cursor.) For that case, you set 'operatorfunc' to a function and then call g@ with a motion.

This code should work:

function! Translit(type)
  if a:type ==# 'visual'
    execute "normal! gvy"
  elseif a:type ==# 'char'
    execute "normal! `[v`]y"
  elseif a:type ==# 'line'
    execute "normal! '[V']y"
  else
    return
  endif
  call setreg('"', 
      \ system('iconv -f utf-8 -t ascii//translit', @@),
      \ getregtype('"'))
  normal! gvp
endfunction
xnoremap <leader>T :<c-u>call Translit('visual')<cr>
nnoremap <leader>T :set operatorfunc=Translit<cr>g@

For more details on this solution, you might want to read this "Grep Operator" section in "Learn Vimscript the Hard Way". (Read the whole thing and make the exercises if you really want to learn Vimscript. It's an invaluable resource for that!)

UPDATE: This code also fixes the issue with block-wise Visual mode, by using setreg() with a third argument after getregtype() to preserve the block format. (Thanks @JürgenKrämer for figuring that out!)

  • 1
    This! The command is ok for a line-wise operation, but dealing directly with the selected text allows broader cases. – perelo Aug 22 at 13:27
  • 1
    Maybe xnoremap? Great answer – D. Ben Knoble Aug 22 at 18:07
  • 1
    @D.BenKnoble Good point. I don't really use Select mode at all, so I'm not sure I understand it correctly. I checked and confirm this doesn't work in Select mode, but I'm not sure that I can tell why it doesn't. I'll take a look later, try to figure this one out. Cheers! – filbranden Aug 22 at 20:57
  • @D.BenKnoble Huh... I just checked and it works great in Select mode, all of Select char/line/block-wise work... Why did you suggest to use xnoremap instead? Ah, maybe because the usual behavior of a printable character in Select mode is to replace the selection, right? Yeah that would make sense... – filbranden Aug 23 at 1:55
  • 1
    @filbranden that’s exactly right; snippets often use something like select mode to make it a sort of « form fillable » type thing (describing it is hard), so this map might surprise a select-mode user – D. Ben Knoble Aug 23 at 2:00
3

For a line-wise operations, you can make a command and mappings :

command! -range Translit <line1>,<line2>!iconv -f utf-8 -t ascii//translit
nnoremap <leader>t :Translit<CR>
xnoremap <leader>t :Translit<CR>
  • The normal mapping <leader>t will translate the current line.
  • You can visually select lines with V and enter <leader>t to translit the selected lines.
  • You can also call it manually with a range, e.g :2,20Translit or :%Translit etc...
  • <line1> and <line2> are replaced by the actual line numbers from the range (e.g 2 and 20) before executing the command.

:{range}![!]{filter} [!][arg]             *:range!*
          Filter {range} lines through the external program
          {filter}. ...

The command I propose makes it easier to maintain. You can manage your mappings with the core command written in one place.

  • Thanks. So what does <line1>,<line2> means? Is it 2 and 20 at :2,20Translit command? – Ahmed selim üzüm Aug 22 at 10:29
  • Also, when I visually select a word, it changes the line. Is there a way for it too in vim? – Ahmed selim üzüm Aug 22 at 10:55
1

It's possible to do this internally in Vimscript, without shelling out to an external executable.

Vim's regular expressions include support for "equivalence classes" i.e. characters that "have almost the same meaning, e.g., when ignoring accents". (See :help /[[=.)

For example, you can replace the first ş in the current line with s by running the :substitute command:

:s/[[=s=]]/s

You can use this to write a command that will perform such a substitution for all characters in the currently selected range of lines:

command -range=% ToASCII call ToASCII(<line1>, <line2>)

function! ToASCII(start_line, end_line) abort
  " Set up the range of lines to edit
  let range_string = a:start_line.','.a:end_line

  " Iterate over the alphabet
  for c in range(65, 90)
    " Iterate over upper/lower case
    for capitilization in [0, 32]
      " Get the string version of the number
      let ascii_char = nr2char(c + capitilization)

      " Build a substitute command
      let subst_string = 's/[[='.ascii_char.'=]]/'.ascii_char.'/ge'

      " Run the command over the range of lines
      execute range_string.subst_string
    endfor
  endfor
endfunction

N.B. Because this code uses :substitute, it won't work well for characterwise visual selections. It's possible to write a solution that does, but the code would be a bit more complicated.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.