Vim: What's the difference between let and set? - vim

What's the difference between let and set in the vim editor?
I've always wondered why both of them exist?
Also, I'd be interested to hear its historical background.

:set is for setting options, :let for assigning a value to a variable.
It happens that the value for an option is linked to the name of the option prepended by a & (the &option-name construct then behaves very similar to "ordinary" variables). So, the following are equivalent:
:set tw=40
:let &tw=40
But, for example, assigning 50 to the global variable foo (:let g:foo=50) cannot be achieved with a :set command (because g:foo is a variable and not an option).
Some options are boolean like. When setting these, no value is needed (as in :set noic and the opposite :set ic).

Set is a more user-friendly interface specialized for options
E.g.
:verbose set
to display all options in effect.
:set tw=40
Will work as a shorthand for set textwidth=40
:set wrap&
Will set the default value for option wrap
:set nowrap
Will unset the option
:set wrap!
Will toggle the option
Most importantly,
:setTab # to get tab completion!
Few of the above can (easily) be achieved with let.

:set only works with options, and sehe's answer showcases some good usage examples.
:let on the other hand can do almost everything that :set can do, plus more. It can assign a value to
a variable, e.g. let vi = 'vim'
an option, e.g. let &tw = 40
a register, e.g. let #a = $HOME . '/vimfiles'
an environment variable, e.g. let $NOTHING = 'NOTHING'
Another major difference is that the right hand side of :let is an expression, meaning you can do things like string concatenation (as seen in my register example above) and arithmetic operations (e.g. let &tw = 40 + 60). This also means that you have to quote the value if it's a string. :set on the other hand reads the value verbatim.
It's easier to use :set with options even though :let can also do most of it, Here are some comparison using sehe's examples ("n/a" means no way to do it with :let)
:verbose set vs n/a (don't think there's another way to list all options)
:set tw=40 vs :let &tw = 40 (yes, you can use the same shorthand in let too)
:set wrap& vs n/a
:set nowrap vs :let &wrap = 0 (for boolean options, 0 is false and 1 is true)
:set wrap! vs :let &wrap = !&wrap
A few more examples
print the value of an option: :set formatoptions? vs :echo &formatoptions (let doesn't print values, unlike set)
assigning to multiple options at the same time:
:set et sw=4 sts=4
vs
:let [&et, &sw, &sts] = [0, 4, 4]
set global option: setglobal et vs let &g:et = 1
set local option: setlocal et vs let &l:et = 1
See :h :set and :h :let for more details
tl;dr
:set only works with options but the syntax is much simpler. :let works with not just options but also variables, registers, and environment variables. Unlike :set, the right hand side of :let is an expression.

Expanding on what people have written about :let, I've noticed that it can be used to assign a value in a variable to an option, something :set can't do. For example, this function uses let to assign the value in the global variable orig_tw to the textwidthoption:
" Toggle Autowrap
" Default of 72 but can be overridden by tw settings in other vimrc files
let g:orig_tw = 72
function Toggle_autowrap_mode()
if &textwidth == 0
" Must use let instead of set here in order for g:orig_tw to be
" evaluated properly
let &textwidth = g:orig_tw
echo "Autowrap mode on tw=" . &textwidth
else
let g:orig_tw = &textwidth
set textwidth=0
echo "Autowrap mode off tw=" . &textwidth
endif
endfunction
noremap _A :call Toggle_autowrap_mode()<CR>

It's very simple.
As people have said set is for options and works better because of the limitation. Also set is the historical command that all versions of vi use to set their options. Most (all?) other versions of vi don't have let.
But possibly most important is that set works on all versions of vim, the let command can be omitted when you compile vim. The standard tiny and small builds do this.
If it's missing let gives you the error:
E319: Sorry, the command is not available in this version
Note: if and endif are not implemented either in vim.tiny but in this case the commands do not give an error, instead everything between the two commands is skipped INCLUDING else.

Related

Autocmd InsertEnter in init.lua to highligh cursor does not work

I am migrating from a well tested and used for years init.vim to a new init.lua. The last two lines do not show errors but they do not work. The idea is to show an underline when I enter insert mode and remove it when in normal mode.
In init.lua
vim.cmd('highlight Cursorline cterm=NONE gui=NONE guibg=NONE')
vim.cmd('autocmd InsertEnter * highlight Cursorline cterm=underline gui=underline') -- Does not work
vim.cmd('autocmd InsertLeave * highlight Cursorline cterm=NONE gui=NONE') -- Does not work
Full init.lua
-- General setting
vim.api.nvim_set_option('mouse', 'a')
vim.api.nvim_set_option('laststatus', 2)
vim.api.nvim_set_option('title', true)
vim.api.nvim_set_option('cursorline', true)
vim.api.nvim_set_option('clipboard', 'unnamedplus') -- Register * as clipboard
vim.api.nvim_set_option('ignorecase', true) -- Search ignore case
vim.api.nvim_set_option('splitright', true)
vim.api.nvim_set_option('splitbelow', true)
-- Search
vim.api.nvim_set_option('hlsearch', true) -- Highlight search
vim.api.nvim_set_option('incsearch', true) -- set incremental search
-- UTF-8
vim.api.nvim_set_option('encoding', 'utf-8')
vim.api.nvim_set_option('fileencoding', 'utf-8')
--vim.api.nvim_set_option('termencoding', 'utf-8')
-- Tabs
vim.api.nvim_set_option('expandtab', false)
vim.api.nvim_set_option('tabstop', 4)
vim.api.nvim_set_option('shiftwidth', 4)
vim.api.nvim_set_option('textwidth', 100)
--vim.api.nvim_set_option('', )
-- Colors
vim.api.nvim_set_option('termguicolors', true)
vim.api.nvim_set_option('syntax', 'on')
vim.api.nvim_command('colorscheme oceanicnext')
-- SHORTCUTS --------------------------------------------------
-- General shortcuts
vim.api.nvim_set_keymap('n', 'cc', ':!make', {})
vim.api.nvim_set_keymap('n', '<S-s><S-s>', ':w<cr>', {})
vim.api.nvim_set_keymap('n', 'ls', ':buffers<cr>', {})
vim.api.nvim_set_keymap('n', 'gb', ':w<cr>:buffers<cr>:b<space>', {})
vim.api.nvim_set_keymap('n', '<space>', '/', {})
-- LaTeX shortcuts
vim.api.nvim_command('augroup filetype_tex')
-- Begin End block
vim.api.nvim_command('autocmd filetype tex inoremap <C-B> <ESC>YpkI\\begin{<ESC>A}<ESC>jI\\end{<ESC>A}<esc>kA')
-- Greek letters
vim.api.nvim_set_keymap('i','<C-g>a','\\alpha',{noremap=true})
vim.api.nvim_set_keymap('i','<C-g>b','\\beta',{noremap=true})
vim.api.nvim_set_keymap('i','<C-g>e','\\epsilon',{noremap=true})
vim.api.nvim_set_keymap('i','<C-g>g','\\gamma',{noremap=true})
vim.api.nvim_set_keymap('i','<C-g>l','\\lambda',{noremap=true})
vim.api.nvim_set_keymap('i','<C-g>m','\\mu',{noremap=true})
vim.api.nvim_set_keymap('i','<C-g>o','\\omega',{noremap=true})
vim.api.nvim_set_keymap('i','<C-g>s','\\sigma',{noremap=true})
-- Pretty letter for sets
vim.api.nvim_set_keymap('i','<C-b>c','\\mathbb{C}',{noremap=true})
vim.api.nvim_set_keymap('i','<C-b>k','\\mathbb{K}',{noremap=true})
vim.api.nvim_set_keymap('i','<C-b>n','\\mathbb{N}',{noremap=true})
vim.api.nvim_set_keymap('i','<C-b>r','\\mathbb{R}',{noremap=true})
-- Vectors
vim.api.nvim_set_keymap('i','<C-e>0','\\vec{0}',{noremap=true})
vim.api.nvim_set_keymap('i','<C-e>e','\\vec{e}',{noremap=true})
vim.api.nvim_set_keymap('i','<C-e>u','\\vec{u}',{noremap=true})
vim.api.nvim_set_keymap('i','<C-e>v','\\vec{v}',{noremap=true})
vim.api.nvim_set_keymap('i','<C-e>w','\\vec{w}',{noremap=true})
vim.api.nvim_set_keymap('i','<C-e>x','\\vec{x}',{noremap=true})
vim.api.nvim_set_keymap('i','<C-e>y','\\vec{y}',{noremap=true})
vim.api.nvim_set_keymap('i','<C-e>z','\\vec{z}',{noremap=true})
vim.api.nvim_command('augroup end')
-- Relative line numbers
vim.api.nvim_set_option('number', true)
vim.api.nvim_set_option('relativenumber', true)
vim.cmd('augroup numbertoggle')
vim.cmd('autocmd!')
vim.cmd('autocmd BufEnter,FocusGained,InsertLeave * :set relativenumber')
vim.cmd('autocmd BufLeave,FocusLost,InsertEnter * :set norelativenumber')
vim.cmd('autocmd BufLeave,FocusLost,InsertEnter * :set number')
vim.cmd('augroup end')
-- Change aspect if in insert mode or not
vim.cmd('highlight Cursorline cterm=NONE gui=NONE guibg=NONE')
vim.cmd('autocmd InsertEnter * highlight Cursorline cterm=underline gui=underline')
vim.cmd('autocmd InsertLeave * highlight Cursorline cterm=NONE gui=NONE')
There is a bug, but it isn't the autocmd. You can tell if you :set cursorline in Vim that your highlights do work.
At this point you might be confused, because you already have:
vim.api.nvim_set_option('cursorline', true)
in your init.nvim. Should work, right?
Well, turns out not. Take a look at this:
:echo &cursorline
:lua vim.api.nvim_set_option('cursorline', true)
:echo &cursorline
In nvim 0.5.0-beta (built a couple weeks ago, nvim doesn't explicitly say when exactly), the output of this is two zeroes - not 0 and 1. As in, nvim_set_option('cursorline', true) has no effect. Real sus.
Let's look at :h 'cursorline' for a minute:
'cursorline' 'cul' 'nocursorline' 'nocul'
'cursorline' 'cul' boolean (default off)
local to window
{not available when compiled without the +syntax
feature}
Highlight the text line of the cursor with CursorLine hl-CursorLine.
Useful to easily spot the cursor. Will make screen redrawing slower.
When Visual mode is active the highlighting isn't used to make it
easier to see the selected text.
Look closely at local to window - this is why your code doesn't work. I'm not gonna pretend to understand why (because I don't use nvim that much and I personally find the lua api to be a mess), but nvim_set_options doesn't work for window variables. Interestingly enough, if you try passing an int instead of a bool to cursorline, the method will throw an error, but it'll still fail to set it like you'd expect. I'll get back to this bit at the end of the post.
Let's look at another example of that with an arbitrary variable; 'foldmarker'
:echo &foldmarker
> {{{,}}}
:lua vim.api.nvim_set_option('foldmarker', 'literally,anythingelse')
> (completes, no error)
:echo &foldmarker
> {{{,}}}
:lua vim.api.nvim_win_set_option(0, 'foldmarker', 'literally,anythingelse')
> (completes, no error)
:echo &foldmarker
> literally,anythingelse
As an exercise to the reader, the first step can be done with 'paste' as well. 'paste' is a global variable, and presumably as a consequence, nvim_win_set_option(0, 'paste', true) will throw
Interestingly enough, this works fine with just a normal :set:
:set foldmarker&
:echo &foldmarker
> {{{,}}}
:set foldmarker=literally,anythingelse
:echo &foldmarker
> literally,anythingelse
This means, all you need to do to fix your code is:
vim.api.nvim_win_set_option(0, 'cursorline', true)
Or alternatively:
vim.api.nvim_command('set cursorline')
TL;DR: the reason this works is because you're now actually setting cursorline. Window options can't be set with vim.api.nvim_set_option(name, value) (and I don't know why). Your autocmds are fine, but they don't work because cursorline is off. This is also unrelated to the highlights themselves; clearing the highlight just hides the cursorline, and doesn't disable the cursorline option. This also means your autocmds and highlights are fine in this case, but that it's your use of the wrong API function for a window variable that caused this problem.
And a final bit of advice: check the documentation of the variables you're setting. If it says window, you'll have to use nvim_win_set_option, and if it's a buffer, you need nvim_buf_set_option. These are also covered in nanotee's nvim lua guide - which also covers the meaning of the extra variable.
I'm not entirely sure if this behavior constitutes a bug or if it just is unintuitive API design, but I found a bug related to 'number' when researching bug reports after finishing the rest of the answer that may indicate that this is intended behavior. I'm not gonna be reporting it as a bug, or opening an issue to ask if this is intended, though, so this is mild speculation on my side. However, the evidence at hand still makes it look like it's intended.
This also means you have to make sure you know what type of variable you're setting if you're using the *_set_option() APIs. If it really is intended for nvim_set_option() to silently fail window options, you'll have to manually check to make sure you're using the right functions on the right variables, because the API functions aren't gonna tell you when you're doing it wrong. Or alternatively just use vim.api.nvim_command() - Vim's :set does set window variables properly.
You can use: vim.o or vim.opt
Note: to reproduce :set option_name need use vim.o.option_name = true or vim.opt.option_name = true. For example: vim.opt.number = true

Display the current status of `expandtab` in statusline in macvim

I am currently using vim-airline in my macvim and I want to display the status of expandtab whether it is set or not in the statusline.
I can find out the status of expandtab by running the following command :set expandtab?. From the vim-airline documentation I found that I can use something like this
let g:airline_section_b = '%{getcwd()}'
I modified it to
let g:airline_section_b = '%{expandtab?}'
but I am getting the error undefined variable: expandtab.
Can someone kindly tell how I can retrieve the status of expandtab and then show it in the status line. Thanks.
:set does not access variables, so you cannot use the question mark to query variables.
You are trying to access the variable expandtab variable, which doesn't exists. You actually want to access an option setting and those are accessed using the & prefix.
Sou you should add:
let g:airline_section_b = '%{&expandtab}'
Note, the quesion mark is not necessary and has no special meaning for VimL.
See :h expr-option for the details.
Update
This will only display 1 (expandtab set) or 0 (expandtab not set). What should work however is something like this:
let g:airline_section_b = '%{&expandtab?"et":"noet"}'
Which will display 'et' when expandtab is set or 'noet' when expandtab is not set. This uses the <cond>?<true>:<false> expression to display a certain string depening on the value of the <cond> condition. This is explained in the help below :h expr1
Vim options can be accessed live variables if prefixed with &. Example:
let g:airline_section_b = '%{&expandtab}'
See :h :let-& for more

How do i add a shortcut to print a variable to stdout?

While coding, i want to select a variable and automatically add a print statement like below by using a custom shortcut:
foo = 5
bar = foo * 5
If i place my cursor on bar and use this shortcut, i want the output to change to:
foo = 5
bar = foo * 5
p "bar = #{bar}"
Can anyone help me in adding this shortcut to my vimrc based on the filetype (ruby, python, java etc)?
snippets are like the built-in :abbreviate on steroids, usually with parameter insertions, mirroring, and multiple stops inside them. One of the first, very famous (and still widely used) Vim plugins is snipMate (inspired by the TextMate editor); unfortunately, it's not maintained any more; though there is a fork. A modern alternative (that requires Python though) is UltiSnips. There are more, see this list on the Vim Tips Wiki.
There are three things to evaluate: First, the features of the snippet engine itself, second, the quality and breadth of snippets provided by the author or others; third, how easy it is to add new snippets.
function Print(p)
let tmp = a:p.' "'.expand("<cword>").' = #{'.expand("<cword>").'}"'
call append(line('.'), tmp)
endfunction
autocmd BufNewFile,BufRead *.py nmap <Leader>x :call Print("print")<CR>

How to set numeric variables in vim functions

I'm trying to write a function that calls setlocal to set some variables to the param(s) that I pass in. But I'm getting the error Number required after =: tabstop=...
function! MyFunction(param)
setlocal tabstop=param
setlocal tabstop=a:param
endfunction
Both lines will fail. Is there some sort of variable interpolation I'm missing?
You need to define the option as an &option variable. For example:
fun! MyFun(param)
let &l:tabstop = a:param
endfun
See :help let-&. The &l: is listed a little below that tag showing that it is for the equivalent of setlocal. Basically, when you want to set an option to an expression instead of a defined value then you need to use let &option= instead of set option=. Use let &l:option= instead of setlocal option=. There is also &g:option to set the option globally.

how to dynamically change menu title in VIM's toolbar?

I am doing a vim plugin for personal use, and it triggers from toolbar.
when I click Plugin in GVIM toolbar, it create a sub menu called 'common'.
When I click common, it start check my vim settings and create sub menu, for instance, show/hide line number. But the following code has some problem, and cannot work. What's wrong with it?
amenu &Plugin.&Common :call <SID>createSubMenu()<CR>
fun! s:createSubMenu()
let isNum = &number
if isNum == '1'
amenu &Plugin.&Common.Hide\ Line\ &Number :set nonu
else
amenu &Plugin.&Common.Show\ Line\ &Number :set nu
endif
endfunction
--------------------- Resolved -----------------------
The code above cannot be used. It's completed wrong.
Please refer to Luc Hermitte's answer.
You need to :unmenu the old entry to remove it.
BTW, I already have a fully functional toggling/cycling engine for values, than also updates menus. It's done with a function from lh-vim-lib, see the test file to have examples of use, and the documentation for more explanations.
:amenu works like map commands: you must include <CR> at the end.
:amenu &Plugin.&Common.Hide\ Line\ &Number :set nonu<CR>
Also, <SID> and s: are not the same.
You should also probably scope isNum to your script with s:
An additional remark: why don't you have a single menu entry that proposes to toggle line numbering and calls :set nu!?

Resources