13

When I have to type a lot of text I often tend to keep my finger pressed on shift when I write the first letter of a sentence which often give:

[...]end of sentence. NEw sentence[...]

Here the E of NEw should be lower case. I am then trying to create a function which would detect if the second letter of the first word of the sentence I'm typing is upper case and which would lower case it. The important part would be that the correction should be done automatically while I'm typing the end of my sentence.

So far I've tried to play with the autocommand event InsertCharPre before realizing that the text can't be modified by a function triggered by this event.

What would be a good solution?

Note That so far I don't need to focus on edges cases like acronyms which should be in upper case or this kind of things.

EDIT I've made this, which is a not-perfect workaround:

autocmd CursorMovedI * call RemoveUnwantedUpper()

function! RemoveUnwantedUpper()
    " Get the current sentence
    " Based on http://stackoverflow.com/a/23315227/4194289
    let l:save_clipboard = &clipboard
    set clipboard= " Avoid clobbering the selection and clipboard registers.
    let l:save_reg = getreg('"')
    let l:save_regmode = getregtype('"')

    normal! y(
    normal! ``

    let l:sentence =getreg('"') 

    call setreg('"', l:save_reg, l:save_regmode)
    let &clipboard = l:save_clipboard

    " Check that we entered a new word (space inserted)
    if l:sentence[len(l:sentence)-1] != " "
       return
    endif 

    " Check if the word is the first one of the sentence
    let l:size = len(split(l:sentence, " "))
    if l:size > 1 
        return
    endif

    " If the last char entered is a space (new word) remove the unwanted Upper case
   normal! bl
   normal! vu
   normal! ``

endfunction

It has a problem since the first character I enter in insert mode is moved to the end of the line, but I think that can be corrected.

I guess now my question becomes a code review question:

  • How can I get rid of the side effect which moves the first character inserted?
  • Is it the best method possible?
  • This method seems to slow down Vim: how could it be improved?
6

Here's a little something a cooked up really fast. It's less robust I think, but it's also much lighter. Maybe you could incorporate it into yours to make it faster? I'll probably revise this a bit when I have time.

inoremap <Space> <C-o>:call CheckCase()<CR><Space>

function! CheckCase()
   normal! may2b
   let l:words = getreg('"')
   if l:words[0] == '.'
      normal! wlvu
   endif
   normal! `a
endfunction

Not a full solution I think, but thought I would try a different approach and see if it sparked anything for you. :)

  • 4
    The idea of remapping <Space> seems pretty interesting since it reduces the number of invocations of the functions. I'll try to work in this way too! – statox May 25 '16 at 15:02
4

I don't know how reliable it is, but you could try this:

augroup FixDoubleUppercase
    autocmd!
    autocmd InsertCharPre * if v:char =~ '\u' && getline('.') =~ '\v(\.\s+|^\s*)\u%' . col('.') . 'c' | let v:char = tolower(v:char) | endif
augroup END

It installs an autocmd which executes the following command before you insert a character:

if v:char =~ '\u' && getline('.') =~ '\v(\.\s+|^\s*)\u%' . col('.') . 'c' | let v:char = tolower(v:char) | endif

The character you're about to insert is stored in the internal variable v:char, and if the test:

v:char =~ '\u' && getline('.') =~ '\v(\.\s+|^\s*)\u%' . col('.') . 'c'

... succeeds, then the autocmd assigns a new value to v:char, which is tolower(v:char).

The test checks whether you were about to insert an uppercase letter (v:char =~ '\u') and your cursor is after the first character of the first word of a sentence:

getline('.') =~ '\v(\.\s+|^\s*)\u%' . col('.') . 'c'

Edit: I don't know if there's a difference (performance-wise) between these 2 syntaxes: :let myvar = test ? new_value : old_value and :if test | let myvar = new_value | endif.

I once read that when you want to optimize your code, you have to use as few Ex commands as possible. So maybe the 2nd syntax (which could be counted as 3 Ex commands: :if, :let, :endif) is slower than the 1st one, I don't know.

But if this is the case, you could replace the autocmd with:

autocmd InsertCharPre * let v:char = (v:char =~ '\u' && getline('.') =~ '\v(\.\s+|^\s*)\u%' . col('.') . 'c') ? tolower(v:char) : v:char
  • 1
    Now that's an elegant solution! According to my first tests it works well. I'll have to use it for some time to be sure it is robust. I like how you modified v:char that the idea I was missing on my first try. – statox May 25 '16 at 15:18
1

Another way to do it (not so automatic, and not so universal; just shorter):

  • since you are writing sentences/prose, not code, have Vim activate spell checking in your text buffers
  • create a fast insert mode combo key that goes back to the last spelling mistakes, fixes it, then resumes position/state; e.g.:

    inoremap <C-s> <Esc>[s1z=gi
    

So now, say you start writing

"THis is a mistake|"

... and you just realize the mistake - what to do? - simply hit <C-s> and continue writing your sentence.

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.