Vim convert expression to file - vim

I'm trying to write a function which takes the file opened by vim and moves it to a standard directory. My script checks out the name of the current buffer, takes the head of the path of the current file, then transforms it into an expression, stores it in a variable, and then manipulates the expression so that it becomes the path I want.
Afterwards, I am trying to use saveas to save the buffer at a new location.
However, saveas receives an argument of type {file}.
How can I convert an expression to type file?

You need to use execute:
exe 'saveas ' . g:filepath
See h :exe for details.

Vimscript is evaluated exactly like the Ex commands typed in the : command-line. There were no variables in ex, so there's no way to specify them. When typing a command interactively, you'd probably use <C-R>= to insert variable contents:
:sleep <C-R>=timetowait<CR>m<CR>
... but in a script, :execute must be used. All the literal parts of the Ex command must be quoted (single or double quotes), and then concatenated with the variables:
execute 'sleep' timetowait . 'm'
Escaping
For arguments like filenames, you have to consider that some characters are special (:help cmdline-special; e.g. % is replaced with the current buffer name), and whitespace should be escaped. Therefore, you need to process your variable through the fnameescape() function:
:execute 'saveas' fnameescape(g:filepath)

Related

Save new file in vim where filename is stored in a register?

I have a file that stores filename and a word on content like
filename1 this is some content
filename2 this is some content
I have already written macro that would create new files and populate the field, The question that i have is while saving is it possible to save the file to filename* ? currently i have stored the filename to vim register but not sure on how to save to that file.
Vimscript is evaluated exactly like the Ex commands typed in the : command-line. There were no variables in ex, so there's no way to specify them. When typing a command interactively, you'd probably use <C-R> to insert variable or register contents:
:write <C-R>a<CR>
... but in a script, :execute must be used. All the literal parts of the Ex command must be quoted (single or double quotes), and then concatenated with the variables; for registers, use the special # sigil:
execute 'write' #a
To handle special characters in the filename, add this:
execute 'write' fnameescape(#a)
Alternative
If each line corresponds to a single file and its contents (and you have many such lines), creating a new buffer and saving it (in a macro) will take quite some time. You can alternatively use the low-level writefile() function and skip the buffer creation:
:global/^/ let [filename, content] = matchlist(getline('.'), '\(\S\+\)\s\(.*\)')[1:2] | call writefile([content], filename)
Does it have to be Vim?
Also, such a task might be easier to perform in a scripting language (often as a one-liner), e.g. in Python or Perl.

Returning to Normal Mode from Insert Mode after "execute normal"

You can perform normal mode commands programmatically in Ex mode, via execute normal, e.g.
:execute "normal" "iNEWTEXT\<Esc>0"
This switches to insert mode (i), writes "NEWTEXT", escapes to normal mode (\< Esc>), then moves to the start of the line (0).
However, using a non-constant string, either a register or variable, the behavior is different. For example, suppose you have the same command above saved on a line in any file (not necessarily a vimscript file):
iNEWTEXT\<Esc>0
You can then copy the text into any register (here, z) via "zy$ and execute the register via #z. This time, though, the output is different:
NEWTEXT\<Esc>0
After entering insert mode, the Escape is no longer treated as a special character, and is instead taken literally. Alternative forms like \e don't work either. Is there a way around this?
EDIT: Using Ingo's answer, I created the the following function. Basically, the use is for having a set of normal/insert commands embedded within the text of the file, and being able to execute them. More commonly, something similar is used for running Ex commands from a line of text, but I couldn't find anything that did this exact thing for normal and insert mode.
So, you'd have text like the following in your file:
jy10j10jpO\<Esc>jEll
When on that line, you could call the function or a remap, and the commands would execute (in this example, copying and pasting 10 lines, and moving 2 columns past the first word). Ingo's alternatives are better for serious usage, namely sourcing commands from another file, having the command in the .vimrc, or a file-type specific option. Macros saved by a session would work just as well, and are more practical than having commands scattered throughout a file. In my case, I was syncing across multiple devices, and didn't want to have another file or clutter my vimrc with this very specific command, but didn't mind cluttering this specific file itself. Think of this like a portable macro.
" Execute current line as Vim normal mode commands.
nnoremap <A-y> :call EvaluateLineAsNormalModeCmd()<CR>
function! EvaluateLineAsNormalModeCmd()
let g:getCurrentLine = getline(".")
"have to :execute twice: once to get the contents of the
"register inserted into a double-quoted string, and then once for
"the :normal to evaluate the string.
execute 'execute "normal" "' . g:getCurrentLine . '"'
endfunction
EDIT2/3: Here are two functions using Christian Brabandt's answer. They work about the same but can put the user in insert mode at the end (whereas, based on my minimal information, 'i' in the other context is considered an incomplete command and not executed, and :startinsert can't be used in that situation). PS: Please don't ask me what all those single and double quotes are doing, as I can't wrap my head around it O_o
function! EvaluateLineAsNormalModeCmd()
normal! 0y$
execute ':call feedkeys("'.#".'", "t")'
endfunction
function! EvaluateLineAsNormalModeCmd()
let g:getCurrentLine = getline(".")
execute ':call feedkeys("'.g:getCurrentLine.'", "t")'
endfunction
If you really need this (the use case is dubious), you have to :execute twice: once to get the contents of the register inserted into a double-quoted string, and then once for the :normal to evaluate the string.
:execute 'execute "normal" "' . #z . '"'
PS: Please give more background; what is your final goal? When a question is only about a small technical step, it's difficult to provide a good answer. If you don't tell us why you want this, it's easy to succumb to the XY problem.
I would rather use the feedkeys() function. E.g. for your sample, this should work:
exe ':call feedkeys("'.#".'", "t")'
(If you yanked your line into the unnamed register, else adjust the register name accordingly). Note, quoting could get ugly.
To understand what is going on, this is what is done:
exe ':call feedkeys(' - First part of the feedkeys() function call
" - Start of Quote for the first argument
. - String concatenation
#" - content of the unnamed register
. - String concatenation
' - Start of second part of the feedkeys function call
" - End of Quote for the first argument
, "t")' - Second argument of feedkeys() function call
You could also do it in 2 steps like this:
exe ':let a="'. #". '"' - Also needs to quote #" correctly.
call feedkeys(a, 't')
which should be easier to understand. The exe call is only to translate the normalized key notation into literal keys.

Vim - writing to filename in vimscript variable

I'm doing something like
:let foo="bar"
:echom foo
bar
:w foo
"foo" [New File] 0 lines, 0 characters written
I am expecting/hoping to write a file named "bar", not a file named "foo". Assuming that I have a string stored in a variable "foo", how can I write the current buffer to a file with the name being that string?
As an aside, can someone explain what :w foo and :echom foo are doing different with regards to foo?
Vimscript evaluation rules
Vimscript is evaluated exactly like the Ex commands typed in the : command-line. There were no variables in ex, so there's no way to specify them. When typing a command interactively, you'd probably use <C-R>= to insert variable contents:
:sleep <C-R>=timetowait<CR>m<CR>
... but in a script, :execute must be used. All the literal parts of the Ex command must be quoted (single or double quotes), and then concatenated with the variables:
execute 'sleep' timetowait . 'm'
Like :execute above, the :echo[msg command is particular in that it takes a variable argument, whereas most commands (like :write) do not, and treat the argument(s) literally.
Your particular problem
As above, your issue is best resolved via execute:
:execute 'write' foo
However, note that if foo contains any regular filename, it still needs to be escaped for the :write function, which understands some special notation (e.g. % stands for the current buffer name), and likes to have spaces escaped:
:execute 'write' fnameescape(foo)
Only
:execute 'write ' . foo<CR>
and
:write <C-r>=foo<CR><CR>
do what you want.
Variables can be used in a concatenation, case 1, or in an expression, case 2.

When to use vim.eval and when to use vim.command in python-vim?

From vim doc:
vim.command(str) *python-command*
Executes the vim (ex-mode) command str. Returns None.
vim.eval(str) *python-eval*
Evaluates the expression str using the vim internal expression
evaluator (see |expression|). Returns the expression result as:
- a string if the Vim expression evaluates to a string or number
- a list if the Vim expression evaluates to a Vim list
- a dictionary if the Vim expression evaluates to a Vim dictionary
Dictionaries and lists are recursively expanded.
I am having trouble to distinguish these two, perhaps it's because I don't understand the difference between expression and command in the first place. An explanation with some examples is very very welcome.
TL;DR: Use command() to execute a Vim command for its side effects, and eval() to get back a value computed by a Vimscript function.
Like other programming languages, Vim(script) distinguishes between a procedure (which you just invoke, but get nothing back; the interesting thing is the actions it performs), and a function (that returns a value, and can optionally also perform actions like a procedure).
Example
With :split foo.txt, you invoke this to open a file in a window split. The command returns nothing, but its effects can be easily observed (another window opens, a file being edited in there). You'd use vim.command() for that.
With :echo winnr('$'), you query the number of open windows. The :echo prints that value, but if you wanted that in Python, you'd use vim.eval("winnr('$')"). (But note that certain Vim properties are already exposed by the Python integration in Vim; you'd only use this for stuff that's not available in Python yet.)
assume you have two strings:
"5d"
and
"5+5"
if you call command() on those, it is same as in vim press :, then input command.
"5d" -> remove the 5th line
"5+5" -> move cursor to 10th line
if you call eval(), vim evaluates the string as vim expression
"5d" -> error
"5+5" -> 10

Custom vi command: How to insert current date with a variable's content?

I'm new to the vi editor and I would like to create a simple custom command in .vimrc that inserts something like 2012-03-13 22:21:17.0 +0100 / Daniel.
Actually, my command (in .vimrc) is as follows:
command! InsertTime :normal a<C-R>=strftime('%F %H:%M:%S.0 %z')<CR>
I also set a variable:
let myname="Daniel"
InsertTime inserts the date perfectly. But how can I concatenate it with the content of my variable?
To concatenate, vim scripts use . caracter. So try this one :
In vimrc:
let myname="Daniel"
command! InsertTime :normal a<C-R>=strftime('%F %H:%M:%S.0 %z') . "/" . myname<CR>
no tested there.
Since you said you're new to "vim" I am going to assume you don't know any of the things I'm about tell you. Mucho sorry if you already know them.
If you're going to do this a lot (insert the line "%F %H:%M:%S.0 %z / Daniel"), instead of defining a command, which you have to invoke with a :command_name, define a macro and/or an input macro that can be invoked with just two or three character.
To define an input macro, do the following at the ':' prompt, or add it to your $HOME/.exrc or $HOME/.vimrc file (without the preceding ':'):
:map <C-X><C-X> Go<ESC>!!date '+\%F \%H:\%M:\%S.0 \%z'<CR>A / Daniel<ESC>
Now when you're in "vi" (but not in input mode), typing control-Xcontrol-X will:
G go to last line in file; replace this with the "motion" keys sequence appropriate for your use (or nothing at all if you want to append the line right after the cursor)
o open a new line
<ESC> escape out of input mode
!!date ... invoke the date command, replace the current line with its stdout (output)
A append at the end of the line (now having the "date")
/ Dan... verbatim intput text
<ESC> escape out of input mode
control-Xcontrol-X can be some unusual sequence that you'd normally not use for anything, nor used by any "vi" operation that you might use. I use as the first character, because in "vi", decrements the next integer on the line after the cursor, if any. That is something I hardly ever do. I define my macros to be invoked with <C-X><C-B>, <C-X><C-D>, <C-X>s1, etc.
To create an input macro, well, that's another whole long subject, and I'm tired of typing today, so, another day. :)

Resources