Comando para percorrer sugestões de ortografia

12

Mapeei zzpara 1z=, o que é ótimo na maioria das vezes, mas de vez em quando a primeira sugestão não é a correta.

Então, eu gostaria de continuar repetindo zz(ou .) para percorrer as outras sugestões.

Um segundo zzna mesma palavra, então, funcionaria como u2z=, um terceiro zzfuncionaria como u3z=e assim por diante.

Alguma ideia de como fazer isto?


Editar:

Com base na incrível resposta de @ nobe4, consegui fazer o que quero, mas deixarei aqui por um tempo, caso alguém tenha alguma melhoria ou sugestão:

let s:spell_position = []
let s:spell_count = 0
let s:spell_word = ""

function! LoopSpell()

    if s:spell_position != getpos('.') ||
            \ (s:spell_count > 0 && s:spell_word !~ expand("<cword>"))
        let s:spell_count = 0
        let s:spell_position = getpos('.')
    endif

    if s:spell_count > 0
        silent execute "normal! u"
    endif

    let s:current_word = expand("<cword>")
    if len(s:current_word) <= 0
        return
    endif

    let s:spell_suggestions = spellsuggest(expand(s:current_word))
    if len(s:spell_suggestions) <= 0
        return
    endif

    if s:spell_count >= len(s:spell_suggestions)
        let s:spell_word = s:current_word
        let s:spell_count = 0
    else
        let s:spell_word = s:spell_suggestions[s:spell_count]
        let s:spell_count += 1
    endif
    silent execute "normal! ciw" . s:spell_word
    let s:spell_position = getpos('.')

endfunction

nnoremap <c-m> :call LoopSpell()<CR>

(Alterei o mapeamento para <c-m>por causa do comentário do @ Vitor. Isso também me permite manter essas teclas pressionadas e meio que rolar as sugestões muito rapidamente. Estou pensando nisso <c-mistake>.)

dbmrq
fonte
2
Eu sugiro que você verifique este plugin que foi feito por um usuário deste site. É realmente melhora o fluxo de trabalho de verificação ortográfica: para começar a corrigir você usar o :Correctcomando: você será capaz de navegar através das palavras para corrigir com ne N, uma janela de divisão abre com todas as sugestões de correção você pode simplesmente navegar por eles com je ke <CR>vontade aplique a correção.
Statox
@statox Obrigado pela sugestão. Vou dar uma olhada, mas ainda quero que meu zzcomando conserte coisas específicas rapidamente.
dbmrq
3
Espero que você esteja ciente de que originalmente zzcentraliza a janela em torno da linha atual. Provavelmente é um dos atalhos que eu uso com mais frequência. Você também deve fazer o checkout zbe zt.
Vitor
@ Victor Interessante, eu não sabia disso! Eu geralmente mantenho meu nível scrolloffalto, mas isso ainda parece útil, vou considerar outro mapeamento. Obrigado!
Dbmrq
Este script vim faz a correção da conclusão da palavra / feitiço / sinônimos (usando aspell, enciclopédia, dicionário) stackoverflow.com/a/46645434/476175
mosh

Respostas:

6

Aqui está o que eu vim com:

Rodar feitiço

feitiço girar

Recursos

  • As marcas '[e ']são usadas para acompanhar o texto que está sendo trabalhado. Fazer uma alteração em outro lugar efetivamente "aceitará" a alteração sugerida.
  • Aceita uma contagem.
  • Retrocede usando zp
  • Repetível usando vim-repeat .
  • Desfazer uma vez para restaurar a palavra original, independentemente de quantas sugestões tenham sido repetidas.
  • Funciona no modo visual para obter sugestões para palavras divididas (por exemplo, "linha principal" -> "título")
    • Usa '<e '>marca para acompanhar o texto.
    • Nota : Não parece ser repetível com o vim-repeat .
  • A palavra original que está sendo alterada é mantida no registro sem nome.
  • As sugestões originais, anteriores, atuais e próximas são exibidas na linha de comando.
  • Comando ingênuo :SpellRotateSubAllpara substituir todo o texto correspondente ao original pela sugestão atual.

Plugin: spellrotate.vim

function! s:spell_rotate(dir, visual) abort
  if a:visual
    " Restore selection.  This line is seen throughout the function if the
    " selection is cleared right before a potential return.
    normal! gv
    if getline("'<") != getline("'>")
      echo 'Spell Rotate: can''t give suggestions for multiple lines'
      return
    endif
  endif

  if !&spell
    echo 'Spell Rotate: spell not enabled.'
    return
  endif

  " Keep the view to restore after a possible jump using the change marks.
  let view = winsaveview()
  let on_spell_word = 0

  if exists('b:_spell') && getline("'[") == getline("']")
    let bounds = b:_spell.bounds
    " Confirm that the cursor is between the bounds being tracked.
    let on_spell_word = bounds[0][0] == bounds[1][0]
          \ && view.lnum == bounds[0][0]
          \ && view.col >= bounds[0][1]
          \ && view.col <= bounds[1][1]
  endif

  " Make sure the correct register is used
  let register = &clipboard == 'unnamed'
        \ ? '*' : &clipboard == 'unnamedplus'
        \ ? '+' : '"'

  " Store the text in the unnamed register.  Note that yanking will clear
  " the visual selection.
  if on_spell_word
    if a:visual
      keepjumps normal! y
    else
      keepjumps normal! `[v`]y
    endif
    call winrestview(view)
  elseif a:visual
    keepjumps normal! y
  else
    keepjumps normal! viwy
  endif

  let cword = getreg(register)

  if !on_spell_word || b:_spell.alts[b:_spell.index] != cword
    " Start a new list of suggestions.  The word being replaced will
    " always be at index 0.
    let spell_list = [cword] + spellsuggest(cword)
    let b:_spell = {
          \ 'index': 0,
          \ 'bounds': [[0, 0], [0, 0]],
          \ 'cword': cword,
          \ 'alts': spell_list,
          \ 'n_alts': len(spell_list),
          \ }

    if len(b:_spell.alts) > 1
      " Do something to change the buffer and force a new undo point to be
      " created.  This is because `undojoin` is used below and it won't
      " work if we're not at the last point of the undo history.
      if a:visual
        normal! xP
      else
        normal! ix
        normal! x
      endif
    endif
  endif

  if a:visual
    normal! gv
  endif

  if len(b:_spell.alts) < 2
    echo 'Spell Rotate: No suggestions'
    return
  endif

  " Force the next changes to be part of the last undo point
  undojoin

  " Setup vim-repeat if it exists.
  silent! call repeat#set(printf("\<Plug>(SpellRotate%s%s)",
        \ a:dir < 0 ? 'Backward' : 'Forward', a:visual ? 'V' : ''))

  " Get the suggested, previous, and next text
  let i = (b:_spell.index + (a:dir * v:count1)) % b:_spell.n_alts
  if i < 0
    let i += b:_spell.n_alts
  endif

  let next = (i + 1) % b:_spell.n_alts
  let prev = (i - 1) % b:_spell.n_alts
  if prev < 0
    let prev += b:_spell.n_alts
  endif

  let next_word = b:_spell.alts[next]
  let prev_word = b:_spell.alts[prev]

  let b:_spell.index = i
  call setreg(register, b:_spell.alts[i])

  if a:visual
    normal! p`[v`]
  else
    keepjumps normal! gvp
  endif

  " Keep the original word in the unnamed register
  call setreg(register, b:_spell.cword)

  let b:_spell.bounds = [
        \ getpos(a:visual ? "'<" : "'[")[1:2],
        \ getpos(a:visual ? "'>" : "']")[1:2],
        \ ]

  echon printf('Suggestion %*s of %s for "', strlen(b:_spell.n_alts - 1), b:_spell.index, b:_spell.n_alts - 1)
  echohl Title
  echon b:_spell.cword
  echohl None
  echon '":  '

  if a:dir < 0
    echohl String
  else
    echohl Comment
  endif
  echon prev_word
  echohl None

  echon ' < '

  echohl Keyword
  echon b:_spell.alts[i]
  echohl None

  echon ' > '

  if a:dir > 0
    echohl String
  else
    echohl Comment
  endif
  echon next_word
  echohl None

  redraw
endfunction


function! s:spell_rotate_suball() abort
  if !exists('b:_spell') || len(b:_spell.alts) < 2
    return
  endif
  execute '%s/'.b:_spell.cword.'/'.b:_spell.alts[b:_spell.index].'/g'
endfunction


command! SpellRotateSubAll call s:spell_rotate_suball()

nnoremap <silent> <Plug>(SpellRotateForward) :<c-u>call <sid>spell_rotate(v:count1, 0)<cr>
nnoremap <silent> <Plug>(SpellRotateBackward) :<c-u>call <sid>spell_rotate(-v:count1, 0)<cr>
vnoremap <silent> <Plug>(SpellRotateForwardV) :<c-u>call <sid>spell_rotate(v:count1, 1)<cr>
vnoremap <silent> <Plug>(SpellRotateBackwardV) :<c-u>call <sid>spell_rotate(-v:count1, 1)<cr>

nmap <silent> zz <Plug>(SpellRotateForward)
nmap <silent> zp <Plug>(SpellRotateBackward)
vmap <silent> zz <Plug>(SpellRotateForwardV)
vmap <silent> zp <Plug>(SpellRotateBackwardV)
Tommy A
fonte
1
Uau, agora estamos conversando! Você deve transformá-lo em um plug-in independente, para que possamos manter futuras alterações e melhorias no mesmo local. Ou posso tentar fazer isso se você não estiver interessado.
Dbmrq 13/07/16
@danielbmarques Fácil, aqui vai: github.com/tweekmonster/spellrotate.vim
Tommy A
Fantástico, obrigado! Aceitarei sua resposta como a correta, já que é exatamente o que eu queria e mais, e darei a recompensa ao @ nobe4 por todo o seu esforço e ajuda.
Dbmrq 13/07/2016
@danielbmarques Sem problemas. Eu estou nele para as perguntas e soluções interessantes 😄
Tommy A
5

Como o @statox sugeriu, você pode usar o plugin que escrevi: vimcorrect .

Vou explicar basicamente como funciona, então, se você quiser reutilizar parte dela, pode.

Para focar na próxima palavra com erro ortográfico, eu uso diretamente ]se [sconforme eles pulam para a próxima correspondência / anterior. Eu defini uma função de correspondência personalizada para destacar a palavra atual:

insira a descrição da imagem aqui

matchadd('error', '\%'.line('.').'l'.'\%'.col('.').'c'.s:current_word)

Que adicionam ao grupo de correspondências errora palavra atual na linha / coluna atual (para impedir a correspondência múltipla na mesma linha).


A spellbadword()função retorna uma lista de possíveis correções para a palavra sob o cursor.

Simplesmente mostro essa lista em um buffer e mapeio <CR>para substituir a palavra com erro de ortografia pela linha atual (ou seja, uma possível palavra corrigida).


Também mapeio ne Npara ]se [s, como estou acostumado a pressioná-los para pesquisar.

q está mapeado para sair do plug-in, feche a divisão e remova o destaque.

Nota : ainda é altamente instável, mas pretendo fazer algumas alterações em breve. Se você sente que pode / deseja melhorar este plugin, fique à vontade para abrir / abrir uma solicitação de recebimento.

nobe4
fonte
Obrigada pelo esclarecimento. Seu plugin está ótimo, eu definitivamente vou usá-lo. No entanto, ainda quero meu zzcomando, para que eu possa consertar as coisas rapidamente sem entrar em um modo especial. Talvez possamos acrescentar isso vimcorrectse eu descobrir. :)
dbmrq
Bem, eu definitivamente preciso adicionar mais personalização. Assim, a definição de mapeamento personalizado pode ser uma melhoria que você pode adicionar se você quiser :) (se você começar a desenvolver em vimscript pode ser uma boa maneira de aprender)
nobe4
2

Aqui está uma função que deve funcionar:

let s:last_spell_changedtick = {}

function! LoopSpell()
  " Save current line and column
  let l:line = line('.')
  let l:col = col('.')

  " check if the current line/column is already in the last_spell_changedtick
  if has_key(s:last_spell_changedtick, l:line) == 0
    let s:last_spell_changedtick[l:line] = {}
  endif

  if has_key(s:last_spell_changedtick[l:line], l:col) == 0
    let s:last_spell_changedtick[l:line][l:col] = 0
  endif

  " If the value already exists, undo the change
  if s:last_spell_changedtick[l:line][l:col] != 0
    normal u
  endif

  " Get the current word
  let l:current_word = spellbadword()
  if len(l:current_word) == 0
    call <SID>Quit()
  endif

  " Get suggestions for the current word
  let s:current_word = l:current_word[0]
  let l:suggestions = spellsuggest(expand(s:current_word))

  " If the current word present no spelling suggestions, pass
  if len(suggestions) <= 0
    return
  endif

  " Replace the word with suggestion
  silent execute "normal! ce" . l:suggestions[s:last_spell_changedtick[l:line][l:col]]
  normal! b

  " Increment the count
  let s:last_spell_changedtick[l:line][l:col] = s:last_spell_changedtick[l:line][l:col] + 1

endfunction

function! LoopConfirm()
  let s:last_spell_changedtick = {}
endfunction

nnoremap zz :call LoopSpell()<CR>
nnoremap z= :call LoopConfirm()<CR>

A idéia básica é mapear cada palavra alterada para um par de linhas / colunas (para que não funcione apenas para um elemento) e verificar se o elemento já foi modificado.

Para fazer a substituição, é basicamente o que meu plug-in faz:

  • buscar a palavra incorreta atual
  • verifique se existem correções
  • substituir palavra por sugestão corrigida

Ao usar isso, se você quiser voltar para a palavra incorreta, basta pressionar u.

A LoopConfirmfunção redefine o dicionário; portanto, se você alterar seu texto, poderá chamá-lo para evitar colisões.

Informe-me se você encontrar algum problema / se tiver alguma dúvida.

nobe4
fonte
Uh, isso parece bom. Ainda tem muitos problemas, no entanto. Pegue uma frase como "o qick borwn foz jums oferece o lazi dor" e tente corrigir cada palavra dessa maneira. Eu nunca consigo "teh" para "the", embora seja o número 4 da lista. "qick" funciona, mas "borwn" muda para outra coisa, mesmo que "brown" seja o primeiro da lista e depois pula diretamente para "foz". Eu nunca superei isso. Também não gosto da z=parte extra , mas provavelmente poderia encontrar uma maneira de contornar isso se o resto funcionasse. Isso está chegando muito perto do que eu quero, no entanto. Vou continuar tentando consertar. Obrigado!
Dbmrq
Veja minha atualização, eu adiciono um incremento muito cedo :) Sim, eu não estou feliz com o z= . Mas com esse método, você precisa manter uma referência de onde você está. Mas se você não precisa manter todas as referências ao mesmo tempo eu posso simplificar isso :)
nobe4
Não sei o que você quer dizer com "manter todas as referências ao mesmo tempo" ... mas não poderíamos simplesmente redefinir o dicionário sempre que o cursor se mover? A função verifica se o cursor está no mesmo local em que foi chamado na última vez em que foi chamado e, se não for, é redefinido.
Dbmrq
Da mesma forma, parece que não funciona corretamente quando o cursor não está no início da palavra. Tente corrigir todos os erros nessa frase, colocando o cursor no meio de cada palavra. Eu pulo para a próxima imediatamente.
Dbmrq
1
Ok, acho que entendi! Confira minha última edição. Isso parece funcionar perfeitamente. Deixarei a pergunta em aberto um pouco mais para ver se mais alguém tem algo a acrescentar, mas sua resposta foi ótima, obrigado. :)
dbmrq
2

Além de outras respostas, não é realmente uma maneira construído na direita para Vim: <C-x>s. Isso usará o menu de conclusão do modo de inserção do Vim.

Pressionar <C-x>sno modo de inserção deve corrigir a palavra sob o cursor até a primeira sugestão e mostrar o menu de conclusão com mais sugestões (se houver). Você pode usar a 'completeopt'configuração para personalizar algumas configurações para o menu de conclusão.

É um pouco chato que isso funcione apenas no modo de inserção e o uso de <C-x><C-s>pode ser problemático (veja a nota abaixo), para que você possa definir seu próprio mapeamento para isso:

inoremap <expr> <C-@>  pumvisible() ?  "\<C-n>" : "\<C-x>s"
nnoremap <expr> <C-@> pumvisible() ?  "i\<C-n>" : "i\<C-x>s"

<C-@> é Control + Space.

Veja também :help ins-completion :help i_CTRL-X_s


Pessoalmente, uso uma versão mais avançada que "adivinhará" se quisermos fazer a verificação ortográfica do trabalho ou usar o preenchimento automático regular para código:

fun! GuessType()
    " Use omnicomplete for Go
    if &filetype == 'go'
        let l:def = "\<C-x>\<C-o>"
    " Keyword complete for anything else
    else
        let l:def = "\<C-x>\<C-n>"
    endif

    " If we have spell suggestions for the current word, use that. Otherwise use
    " whatever we figured out above.
    try
        if spellbadword()[1] != ''
            return "\<C-x>s"
        else
            return l:def
        endif
    catch
        return l:def
    endtry
endfun

inoremap <expr> <C-@>  pumvisible() ?  "\<C-n>" : GuessType()
inoremap <expr> <Down> pumvisible() ? "\<C-n>" : "\<Down>"
inoremap <expr> <Up> pumvisible() ? "\<C-p>" : "\<Up>"
nnoremap <expr> <C-@> pumvisible() ?  "i\<C-n>" : 'i' . GuessType()

Acredito que também existem alguns plugins que fazem coisas semelhantes (como o SuperTab, que é bastante popular), mas eu nunca consegui fazê-los se comportar como eu quero.


Advertência : Se você estiver usando o Vim em um terminal, <C-s>significa "parar a saída". É por isso que ambos <C-x><C-s> e <C-x>s são mapeados por padrão. Use <C-q>para continuar a saída se pressionar <C-s>por acidente. Você também pode desativar <C-s>se não o usar (consulte esta pergunta ). Se você estiver usando o GVim, poderá ignorar isso.

Martin Tournoij
fonte