DetectSpellLang will re-run when resuming a session that stores the buffer-local 'spelllang' option
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.)
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.
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.)
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.
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
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.
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:
- In the BufWinEnter autocmd you would have to skip language detection when a session is currently being restored, i.e.
SessionLoadis set to 1. - You then have to introduce a
SessionLoadPostautocmd 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&spellis 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
&spelllangand&g:spelllang. If they differ, assume that the language was already detected. This would actually work on my system since&g:spelllangwill beenwhereas&spelllangwhen detected by your plugin will be one ofen_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. OnSessionLoadPostthis will be be the session file if it restored the buffer-local option and you can compare the URI given by:verbosewith the value ofv: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.