vim-DetectSpellLang icon indicating copy to clipboard operation
vim-DetectSpellLang copied to clipboard

DetectSpellLang will re-run when resuming a session that stores the buffer-local 'spelllang' option

Open acc987 opened this issue 3 years ago • 6 comments

When localoptions has been added to 'sessionoptions', the buffer-local value of 'spelllang' (as detected by DetectSpellLang) will be saved to the session file and is available when resuming a session. Unfortunately, DetectSpellLang will currently re-detect 'spelllang' even when that information is being provided by a session file. When such a session contains a lot of buffers, resuming the session can take a (very) long time as DetectSpellLang will re-detect 'spelllang' for every buffer.

I am not familiar with the internal workings of vim sessions but would it be possible to skip detection of 'spelllang' when a session is being resumed where the buffers already have that option set?

(I am using the most recent tag of DetectSpellLang, i.e. version 2.2, as I am not sure whether the main branch is stable or not.)

acc987 avatar Sep 09 '22 13:09 acc987

Hello, thanks for letting me know. The block

https://github.com/Konfekt/vim-DetectSpellLang/blob/d5b55e3307e72e45f8d736818c76884016583538/plugin/detectspelllang.vim#L96-L121

checks for all kinds ways to explicitly set &spelllang (say by a modeline - my approach); then rerunning the spell detection is inhibited.

How to detect that the spell lang was set by a session file? If that's detectable, then we can add the check to that list.

Konfekt avatar Sep 09 '22 14:09 Konfekt

Thanks for pointing me to the logic in your source code. While I currently can't propose a solution, I can at least contribute some more observations:

I did take a look inside one of my session files (and at :h Session) and while it is possible to detect whether a session is being restored (the global variable SessionLoad would be set to 1, cf. :h SessionLoad-variable), I don't think that's enough as you wouldn't know whether the session has restored or is going to restore the buffers' 'spelllang'.

For what it's worth, I have also found out that there is a SessionLoadPost event that triggers after a session was loaded, but you would still have to figure out if the session actually restored 'spelllang' via setlocal.

If it is possible to reliably distinguish between the default (global) 'spelllang' value and whether an override has been applied via setlocal, you could use the SessionLoad variable to skip detection when entering buffers and hook into the event to then manually run the detection for all buffers which did not have had 'spelllang' applied via setlocal. A quick search as to whether such a distinction can be made has turned up nothing, unfortunately, so I am not sure whether this is actually an option.

(By the way, you can also cite a block of code by turning #L97 into #L96-L121, for example. You can also do this via the user interface: simply click on the first line you want to include and then hold Shift while clicking on the last line you want to include before generating a link and copying it to your clipboard, respectively.)

acc987 avatar Sep 09 '22 16:09 acc987

Yes, let me know if you find something. It was not on my radar as according to https://github.com/tpope/vim-sensible/blob/8985da7669bbd73afce85ef0e4a3e1ce2e488595/plugin/sensible.vim#L82 setting options by sessions is somehow deemed insensible as it could probably cause surprises.

Konfekt avatar Sep 12 '22 05:09 Konfekt

Just as a quick remark/clarification with respect to the second part of your message: Note that I am not talking about storing the global setting but the (buffer-)local setting. So my 'sessionoptions' contains localoptions but does not contain options and I do agree with tpope's sentiment that storing global options (and variables) can lead to undesired behavior. He explains his rationale here, for example: https://github.com/tpope/vim-sensible/issues/117#issuecomment-203591374

acc987 avatar Sep 12 '22 19:09 acc987

Assuming that 'sessionoptions' contains localoptions, would the SessionLoadPost event be a safe bet? Does it save all options? In any event, even if then sessionoptions always contains spelllang, I'd appreciate an echo message stating that &spelllang was set by the session file.

In the meanwhile, may I propose


function! ModelineSet(variable, value, isPersistent) abort
  let cmd = 'set ' . a:variable . (empty(a:value) ? '' : '=' . a:value)
  execute cmd

  if !a:isPersistent | return | endif

  " append modeline
  let commentstring = empty(&l:commentstring) ? 
    \ (empty(&g:commentstring) ? '# %s' : &g:commentstring) : &l:commentstring
  let modeline = substitute(commentstring,'%s',' ex: ' . cmd . ': ', '')
  call append(line('$'), '')
  call append(line('$'), modeline)
endfunction

command! -nargs=1 -bang -bar                                   SetSpellLang  call ModelineSet('spelllang', <q-args>, <bang>0)

autocmd vimrc BufWinEnter *
      \ if &l:spell |
      \   silent! exe 'nnoremap <buffer><unique> cl :<c-u>nunmap <lt>buffer> <lt>cr><cr>:call <SID>chooselang()<cr>' |
      \ endif

function! <SID>chooselang()
  echo 'Enter initial of spell-check language:'

  let lang        = nr2char(getchar())
  let isPermanent = lang =~# '[A-Z]' ? 1 : 0

  if lang is? 'e'
    call ModelineSet('spelllang', 'en', isPermanent)
  elseif lang is? 'd'
    call ModelineSet('spelllang', 'de', isPermanent)
  elseif lang is? 'p'
    call ModelineSet('spelllang', 'pt', isPermanent)
  elseif lang is? 'f'
    call ModelineSet('spelllang', 'fr', isPermanent)
  endif

endfunction

to set the spelllang and optionally add a modeline (by appending ! to the SetSpellLang command, or, in the case of the cl (choose lang) mapping, using the uppercase instead of lowercase letter).

Then whether the language was set by a modeline is detected by the somewhat recent OptionSet autocmd.

Konfekt avatar Sep 18 '22 08:09 Konfekt

Thank you for providing me with the snippet! As I currently have set nomodeline, I would still like to pursue the possibility of skipping automatic detection when a session with buffer-local 'splelllang' values is being restored.

I managed to make some more progress and it seems that the following should be possible, though you might want to make that behavior opt-in:

  1. In the BufWinEnter autocmd you would have to skip language detection when a session is currently being restored, i.e. SessionLoad is set to 1.
  2. You then have to introduce a SessionLoadPost autocmd that iterates over all buffers and attempts to figure out whether the session restored a buffer-local value of 'spelllang'. If the session has not restored a buffer-local value, automatic detection for that particular buffer must be triggered instead (if &spell is set) as per the default behavior of your plugin.

As far as I can tell there are two options to implement the second step though both involve some compromise:

  • Compare the value of &spelllang and &g:spelllang. If they differ, assume that the language was already detected. This would actually work on my system since &g:spelllang will be en whereas &spelllang when detected by your plugin will be one of en_US, en_GB, and various non-English languages.
  • Use :redir => {var} to capture the output of :verbose setlocal spelllang? in a variable. Then you can parse the output for the file that last set this option. On SessionLoadPost this will be be the session file if it restored the buffer-local option and you can compare the URI given by :verbose with the value of v:this_session. Note that this will probably involve some normalization of the URIs to actually make them comparable, but when they coincide you know that the session file restored the buffer-local value of 'spelllang', if any.

The compromise for the first option would be that this will not work in general, i.e. not when the default value of 'spelllang' is actually one of the values that is configured in g:detectspelllang_langs. The second option does not have such limitations but parsing makes some assumptions on the output of :verbose setlocal, which may change in the future. Though I am contemplating of requesting a reliable way of obtaining that information as a Vim script enhancement.

acc987 avatar Sep 25 '22 10:09 acc987