How to implement Caesar cipher-like text substitution in Vim? - vim

I was doing some puzzle where each English letter is replaced by the one two letters down the alphabet. For example, the word apple is to be transformed into crrng, as a + 2 → c, b + 2 → d, etc.
In Python, I was able to implement this transformation using the maketrans()
string method. I wonder: Is it possible to do the same via search and replace in Vim?

1. If the alphabetic characters are arranged sequentially in the target
encoding (as is the case for ASCII and some alphabets in UTF-8, like
English), one can use the following substitution command:
:%s/./\=nr2char(char2nr(submatch(0))+2)/g
(Before running the command, make sure that the encoding option
is set accordingly.)
However, this replacement implements a non-circular letter shift.
A circular shift can be implemented by two substitutions separately
handling lowercase and uppercase letters:
:%s/\l/\=nr2char(char2nr('a') + (char2nr(submatch(0)) - char2nr('a') + 2) % 26)/g
:%s/\u/\=nr2char(char2nr('A') + (char2nr(submatch(0)) - char2nr('A') + 2) % 26)/g
2. Another way is to translate characters using the tr() function.
Let us assume that the variable a contains lowercase characters
of an alphabet arranged in correct order, and the variable a1 hold
the string of characters corresponding to those in a (below is
an example for English letters).
:let a = 'abcdefghijklmnopqrstuvwxyz'
:let a1 = a[2:] . a[:1]
To avoid typing the whole alphabet by hand, the value of a can be
produced as follows:
:let a = join(map(range(char2nr('a'), char2nr('z')), 'nr2char(v:val)'), '')
Then, to replace each letter on a line by the letter two positions down
the alphabet, one can use the following substitution:
:%s/.*/\=tr(submatch(0), a . toupper(a), a1 . toupper(a1))

Yes, \= will execute the function
%s/\(.\)/\=nr2char(char2nr(submatch(1)) + 2)/g

Can't think of anything in vim, but you could use the unix command line utility 'tr' (stands for translate, I believe).

The puzzle you describe is widely known as the caesar cipher, and is normally implemented via the tr command or sed -e y/. Since y is not available in vim, you'll need a pretty dirty hack like ib proposed, but calling tr is much nicer work.
Especially considering the corner case of y and z: I assume these should be mapped to a and b, respectively?

Related

How to convert visual selection from unicode to the corresponding character in vim command?

I'm trying to convert multiple instances of Unicode codes to their corresponding characters.
I have some text with this format:
U+00A9
And I want to generate the following next to it:
©
I have tried to select the code in visual mode and use the selection range '<,'> in command mode as input for i_CTRL_V but I don't know how to use special keys on a command.
I haven't found anything useful in the manual with :help command-mode . I could solve this problem using other tools but I want to improve my vim knowledge. Any hint is appreciated.
Edit:
As #m_mlvx has pointed out my goal is to visually select, then run some command that looks up the Unicode and does the substitution. Manually input a substitution like :s/U+00A9/U+00A9 ©/g is not what I'm interested in as it would require manually typing each of the special characters on every substitution.
Any hint is appreciated.
Here are a whole lot of them…
:help i_ctrl-v is about insert mode and ranges matter in command-line mode so :help command-mode is totally irrelevant.
When they work on text, Ex commands only work on lines, not arbitrary text. This makes ranges like '<,'> irrelevant in this case.
After carefully reading :help i_ctrl-v_digit, linked from :help i_ctrl-v, we can conclude that it is supposed to be used:
with a lowercase u,
without the +,
without worrying about the case of the value.
So both of these should be correct:
<C-v>u00a9
<C-v>u00A9
But your input is U+00A9 so, even if you somehow manage to "capture" that U+00A9, you won't be able to use it as-is: it must be sanitized first. I would go with a substitution but, depending on how you want to use that value in the end, there are probably dozens of methods:
substitute('U+00A9', '\(\a\)+\(.*\)', '\L\1\2', '')
Explanation:
\(\a\) captures an alphabetic character.
+ matches a literal +.
\(.*\) captures the rest.
\L lowercases everything that comes after it.
\1\2 reuses the two capture groups above.
From there, we can imagine a substitution-based method. Assuming "And I want to generate the following next to it" means that you want to obtain:
U+00A9©
you could do:
v<motion>
y
:call feedkeys("'>a\<C-v>" . substitute(#", '\(\a\)+\(.*\)', '\L\1\2', '') . "\<Esc>")<CR>
Explanation:
v<motion> visually selects the text covered by <motion>.
y yanks it to the "unnamed register" #".
:help feedkeys() is used as low-level way to send a complex series of characters to Vim's input queue. It allows us to build the macro programatically before executing it.
'> moves the cursor to the end of the visual selection.
a starts insert mode after the cursor.
<C-v> + the output of the substitution inserts the appropriate character.
That snippet begs for being turned into a mapping, though.
In case you would like to just convert unicodes to corresponding characters, you could use such nr2char function:
:%s/U+\(\x\{4\}\)/\=nr2char('0x'.submatch(1))/g
Brief explanation
U+\(\x\{4\}\) - search for a specific pattern (U+ and four hexadecimal characters which are stored in group 1)
\= - substitute with result of expression
'0x'.submatch(1) - append 0x to our group (U+00A9 -> 0x00A9)
In case you would like to have unicode character next to text you need to modify slightly right side (use submatch(0) to get full match and . to append)
In case someone wonders how to compose the substitution command:
'<,'>s/\<[uU]+\(\x\+\)\>/\=submatch(0)..' '..nr2char(str2nr(submatch(1), 16), 1)/g
The regex is:
word start
Letter "U" or "u"
Literal "plus"
One or more hex digits (put into "capture group")
word end
Then substituted by (:h sub-replace-expression) concatenation of:
the whole matched string
single space
character by UTF-8 hex code taken from "capture group"
This is to be executed in Visual/command mode and works over selected line range.

Searching for an exact match with a singular digit

I'm trying to search for only a singular digit in vim by itself. For example, if there are two sets of digits 1 and 123 and I want to search for 1, I would only want the singular 1 digit to be found.
I have tried using regular expressions like \<1> and \%(a)#
You almost had the right solution. You want:
\<1\>
This is because each angled bracket needs to be escaped. Alternatively, you could use:
\v<1>
The \v flag tells vim to treat more characters as special without needing to be escaped (for example, (){}+<> all become special rather than literal text. Read :h /\v for more on this.
A great reference for learning regex in vim is vimregex.com. The \<\> characters are explained in 4.1 "Anchors".
If you want to match text like 1.23 this is possible too. Two different approaches:
Modify the iskeyword option so that it includes .. This will also affect how w moves
Use \v<1(\d|.)#!, which basically means "a 1 at the beginning of a word, that isn't followed by some other digit or a period."

Substitute and change case for program variables

I'm changing some notation in a few source code files.
In particular, variable names using the format
m_variable1
m_anothervariable
should be renamed and reformatted to
mVariable1
mAnotherVariable
That is, substitute m_ with m and make the next character uppercase.
I know how todo simple substitutions, like
%s/m_/m/gc
using vim, but not sure how to add syntax for changing a char to uppercase in a substitute statement?
You can make the first character of variable name uppercase, but I think you can hardly separate words from a consecutive string simply by built-in command.
I hope following command will help you:
:%s/\vm_(\w+)/m\u\1/g
Explaination
\v enables the 'very magic' mode
\u makes the first character of word after it uppercase
\1 references the first captured group
Result
mVariable1
mAnothervariable

vim: replace sub-match with the same number of new strings

My plan is to do a pretty standard search replace, replacing all instances of old_string with new_string. The problem is that I only want to do this for an arbitrary number of old_strings following a specific prefix. So for example:
old_string = "a"
new_string = "b"
prefix = "xxxx"
xxxxaaaaaaaa => xxxxbbbbbbbb
xxxxaaapostfix => xxxxbbbpostfix
xxaaaa => xxaaaa
etc. I'm not sure how to do this. I imagine there's some way to say s/xxxxa*/xxxxb{number of a's}/g or something, but I have no idea what it is.
You can definitely do this! I would use the \= register to evaluate some vimscript. From :h s/\=:
Substitute with an expression *sub-replace-expression*
*sub-replace-\=* *s/\=*
When the substitute string starts with "\=" the remainder is interpreted as an
expression.
The special meaning for characters as mentioned at |sub-replace-special| does
not apply except for "<CR>". A <NL> character is used as a line break, you
can get one with a double-quote string: "\n". Prepend a backslash to get a
real <NL> character (which will be a NUL in the file).
Then you can use the repeat and submatch functions to build the right string. For example:
:%s/\(xxxx\)\(a\+\)/\=submatch(1).repeat('b', len(submatch(2)))
I chose to use \+ instead of * because then the pattern will not be found after the substitute command finished (this effects hlsearch and n)
Of course, if you use the \zs and \ze (start/end of match) atoms, you can use less capturing groups, which makes this waaay shorter and clearer.
:%s/xxxx\zsa\+/\=repeat('b', len(submatch(0)))
If you have perl support, you can use
:%perldo s/xxxx\Ka+/"b" x length($&)/ge
xxxx\Ka+ match one or more a only if preceded by xxxx
lookbehind with \K
/ge replace all occurrences in line, e allows to use Perl code in replacement section
"b" x length($&) the string b repeated length($&) number of times
See :h perl for more info

Writing whole alphabet in Vim

I sometimes need to write the whole alphabet abcd…z and I hate typing it letter by letter in Vim's insert mode. Does there exist any method to do this more efficiently?
I know about the ga command which gives me the ascii code of the character where the cursor is … but don't know anything about how to mix it with my standard solution to type numbers from 1 to (for example) 5000: a1ESCqqyyp^Aq4998#q …
Using set nrformats+=alpha:
ia<Esc>qqylp<C-a>q24#q
Step by step:
ia<Esc> " Start with 'a'
qqylp<C-a>q " #q will duplicate the last character and increment it
24#q " Append c..z
If your shell does brace expansion this is a pretty elegant solution:
:r !printf '\%s' {a..z}
:read! reads the output of an external command into the current buffer. In this case, it reads the output of the shell's printf applied to {a..z} after it's been expanded by the shell.
How about this command:
:put =join(map(range(char2nr('a'),char2nr('z')),'nr2char(v:val)'),'')
Collect the ASCII values of the characters in the range from a to z, then map them over the nr2char() function and insert the result into the current buffer with :put =.
When you leave out the enclosing join( … ,'') you get the characters on a separate line each.
See
:h nr2char(),
:h char2nr(),
:h :put,
and look up range(), map(), join() and friends in the list-functions table.
First, set nrformats+=alpha.
Then:
ia<ESC>Y25p<CTRL-V>}g<CTRL-A>k26gJ
Which means:
ia insert the initial a
Y25p yank the a and duplicate it on 25 lines
<CTRL-V> go into visual block mode
} go to the last character at the end of the current paragraph
g<CTRL-A> incrementally increase each alphabetic character (see help v_g_CTRL-A)
k go up one line
26gJ join 26 lines without inserting or removing any spaces
Which leads to:
abcdefghijklmnopqrstuvwxyz
I have found a shorter solution (you don't need to change nrformats beforehand) while solving http://www.vimgolf.com/challenges/5ebe8a63d8085e000c2f5bd5
iabcdefghijklm<Esc>yiwg??P
which means:
iabcdefghijklm<Esc> insert first half of the alphabet
yiw copy it
g?? ROT13 encode (shift by 13 letters) to get the second half
P paste the first half
You might try using Vim abbreviations or a full-fledged snippet manager plugin like UltiSnips. It might take a few moments to set up, and you'd have to type that alphabet one more time to define it as an abbreviation or snippet, but after that you'd be able to insert the alphabet or any other common chunk of text much more easily.

Resources