vim indentation: disable inference of enumerated list - vim

In vim, when I format the following paragraph in a plain text file using gqip, the formatter indents it like an enumerated list.
Original paragraph:
Here is some text including a number
3 in the paragraph, which may be
regarded as the start of a numbered
list when I format it.
Formatted (after gqip):
Here is some text including a number
3 in the paragraph, which may be
regarded as the start of a numbered
list when I format it.
The problem is that vim aligns the word "regarded" as if the line "3 in the paragraph..." somehow means "(3) in the paragraph". In my opinion, this is a bug in the formatting rules, because there are obvious counter-examples that occur frequently in ordinary text. So how can I refine this indentation rule to apply only when there is list-like punctuation on the number? For example, I think this is ok:
Here is some text including a number
3) in the paragraph, which may be
regarded as the start of a numbered
list when I format it.
There are counter-examples to this rule as well, but at least the error occurs less frequently. The rule could be further refined by checking for balanced parentheses--i.e.:
Here is some text (including a number
3) in the paragraph, which is not
regarded as the start of a numbered
list when I format it (because the
parenthesis is accounted for by the
opening parenthesis on line 1).

See :h fo-table 's n letter meaning, then see :h formatlistpat, which is used to recogize a list header:
" 'formatlistpat' 'flp' string (default: "^\s*\d\+[\]:.)}\t ]\s*")
" ignore '3 ' by removing space in pattern
let &formatlistpat='^\s*\d\+[\]:.)}\t]\s*'
" ignore (\n3) or [\n3] or {\n3} by adding a preceding NOT match
let &formatlistpat='\([\[({]\s*\n\)\#<!\_^\s*\d\+[\]:.)}\t]\s*'

Related

How to fix problem with tabstop configuration in ~/. vimrc

I set tab configuration in ~/.vimrc as below:
set ts=4 sts=4 sw=4
I notice that if the word is 4 characters long or above, the cursor shift into right 4 spaces as in configuration for tabstop.
But if the word is less than 4 characters long, it didn't shift into 4 spaces.
Example:
'name' + <Tab>: tab produced correct number of spaces (i.e 4 spaces)
'age' + <Tab>: tab produced wrong number of spaces (i.e 1 space only)
Why is it ?
Does the word length effect tab?
What can I do if I want to shift the cursor to 4 spaces as configured regardless of the word length?
Thanks a lot
You’re probably inserting regular tabs, which display variable-width according to what’s before and after. I find having set list on is really handy for this (though you probably won’t like the default listchars settings).
If you really want spaces (which I find better anyway, set expandtab.
Also, most long-time users recommend leaving tabstop at 8, since you can’t control how wide every one’s tabs are.
The way that the tabstop, shiftwidth, and softtabstop options work is that they control indentation to certain points that are commonly referred to as "tab stops". In other words, they're designed to always indent to a column that's a multiple of the setting.
So if your tab stops are at multiples of 4, then hitting the Tab key will cause the cursor to indent to a column that is the next multiple of 4. This is the behavior of inserting a literal tab (U+0009, CHARACTER TABULATION) into a document and then rendering it on a normal terminal (except that the width is usually 8 there). This results in text that is aligned at fixed columns, which is the desired style for most programming languages and text markup formats.
As you've noted, this does result in different amounts of indent if the words are different lengths. Typically in code, we would just cause the second column to be at the next tab stop and not care that the indents are of different lengths. That is, in your example, we'd hit Tab once on the first line and twice on the second, and start the next column at column 8.
I'm not aware of any way to force Vim to insert a specific number of spaces other than the standard editing commands. Normally users who are in this situation just hit Space four times if they really want four spaces and not an indentation to the next tab stop. You can of course create a mapping if you need to do that a lot.

Why does changing case on words change behaviour when inside a macro?

I was changing some words to uppercase, I hit viw~ to change the case of the word from lowercase to uppercase. I moved foward a word and hit . to repeat the action on the next word and I noticed that it affected the case of some of the letters of the word ahead and on other times it would not change case of the entire word.
Here is an example done with vim -u NONE on a file with a single sentence
this is a test sentence
with my cursor at the start of the sentence, I hit
viw~
my sentence is now:
THIS is an example test sentence
I move forward by 1 word with w and hit . to repeat the action.
My sentence is now:
THIS IS An example test sentence
w.
THIS IS aN Example test sentence
w.
THIS IS aN eXAMple test sentence
w.
THIS IS aN eXAMple TEST sentence
The same behaviour happens when I instead capture the actions as a macro instead of using .
I suspect vim is just changing the case of the same number of letters as was in the first word, but why? Why does viw not work in macros?
The action was repeated on an area equivalent to the area covered by the previous command. This has nothing to do with macros.
From :help .:
Note that when repeating a command that used a Visual selection, the same SIZE
of area is used, see visual-repeat.
and from :help visual-repeat:
When repeating a Visual mode operator, the operator will be applied to the
same amount of text as the last time:
- Linewise Visual mode: The same number of lines.
- Blockwise Visual mode: The same number of lines and columns.
- Normal Visual mode within one line: The same number of characters.
- Normal Visual mode with several lines: The same number of lines, in the
last line the same number of characters as in the last line the last time.
The start of the text is the Cursor position. If the "$" command was used as
one of the last commands to extend the highlighted text, the repeating will
be applied up to the rightmost column of the longest line.
One of Vim's strengths is that you don't need to select text before doing many actions. In this case, you should use the :help g~ operator, which will be repeated with . in a more intuitive way:
g~iw
instead of:
viw~
I suspect vim is just changing the case of the same number of letters [...]
You're right. In order to do what you expect, Vim would have to remember how you created the visual selection. In your example, that was easy (iw), but you may apply multiple text objects and movements, use o to move to the other side of the selection, modify that, and so on. That would be very hard to recreate elsewhere, and to avoid inconsistent behavior, Vim consistently acts stupid and just uses the previous width of the selection when redoing with the . command.
If you want to apply an operation to a certain text object or motion, skip visual mode and instead use the corresponding motion-mapping; e.g. g~iw instead of viw~.

How do I change autoformat in vim?

I use autoformat (textwidth = 72) to write my emails in vim.
The problem is that every time I write a list or short phrases, vim joins it to the line above.
p.e.
These are my options:
- option nr. 1
When I write "-", the "-" is immediately joined with the phrase above:
These are my options: -
Same when I use other kind of lists p.e. numbered lists or other symbols before the list.
Same thing when I write phrases shorter then 72 characters p.e.
This is my text.
This is my text on the 2nd line.
Autoformat changes it to:
This is my text. This is my text on the 2nd line.
How can I change this behavior?
I only want to format long lines when there are no Carriage Return <CR> in the first 72 characters.
If there is a <CR> it has to break there.
:help autoformat gives some useful hints:
You need to properly define paragraphs. The simplest is paragraphs that are
separated by a blank line. When there is no separating blank line, consider
using the 'w' flag and adding a space at the end of each line in the
paragraphs except the last one.
So, either :setlocal fo+=w, or turn off autoformat (maybe only temporarily, with a quick toggle mapping).

How to insert variable number of spaces necessary to align textual columns in Vim?

I’m a fan of Visual mode in Vim, as it allows to insert text before any given column.
For example, insertion of spaces after the quotation leaders below:
> one
> two
> three
can be done via <Ctrl-V>jjI <Esc>:
> one
> two
> three
as follows:
Start Visual mode with Ctrl-V.
Extend visual selection with jj.
Insert some spaces with I__.
Propagate the change to all the lines of the block selection with Esc.
Now I have a text file that needs some formatting. This is what it looks like:
start() -- xxx
initialize() -- xxx
go() -- xxx
Now I want to align part of this text to arrange it into columns like this:
start() -- xxx
initialize() -- xxx
go() -- xxx
The problem I have is that I cannot insert a different amount of indentation into each line and merely indenting a fixed amount of spaces/tabs is insufficient.
How can you do an indentation where all indented text will have to be aligned at the same column?
Update
I only figured out a rather verbose and unwieldy method:
Find the string position to indent from: \--.
Insert n (let's say 20) spaces before that: 20i <Esc>.
Delete a part of those spaces back to a certain column (let's say 15): d|15.
Save those steps as a macro and repeat the macro as often as necessary.
But this approach is very ugly, though!
I'm much better off without any vim plugins.
Here is my solution:
<Shift-V>jj:!column -ts --
Then insert -- into multiple lines just as you wrote in the question.
You can also append a number of comments at insertion time.
:set virtualedit=all
<Ctrl-V>jjA-- xxx<Esc>
You have to use a specific plugin, you can use either Tabular or Align plugin in this case.
They both allow you to align text on specific characters, like -- in your example. Their syntax is a bit different though. Pick the one that suit you the most.
Without plugin and if you have already entered your comments without emix's solution:
:,+2 s/--/ &
This will ensure all comments are to be shifted leftwise in order to align them properly.
Then blockwise select the column to which you want to align the text, and : 100<
An easy way to align text in columns is to use the Tabular or
Align plugin. If neither of these is ready at hand, one can use
the following somewhat tricky (and a little cumbersome looking) yet
perfectly working (for the case in question) commands.1,2
:let m=0|g/\ze -- /let m=max([m,searchpos(#/,'c')[1]])
:%s//\=repeat(' ',m-col('.'))
The purpose of the first command is to determine the width of the
column to the left of the separator (which I assume to be --
here). The width is calculated as a maximum of the lengths of the text
in the first column among all the lines. The :global command is used
to enumerate the lines containing the separator (the other lines do
not require aligning). The \ze atom located just after the beginning
of the pattern, sets the end of the match at the same position where
it starts (see :help \ze). Changing the borders of the match does
not affect the way :global command works, the pattern is written in
such a manner just to match the needs of the next substitution
command: Since these two commands could share the same pattern, it can
be omitted in the second one.
The command that is run on the matched lines,
:let m=max([m,searchpos(#/,'c')[1]])
calls the searchpos() function to search for the pattern used in the
parent :global command, and to get the column position of the match.
The pattern is referred to as #/ using the last search pattern
register (see :help "/). This takes advantage of the fact that the
:global command updates the / register as soon as it starts
executing. The c flag passed as the second argument in the
searchpos() call allows the match at the first character of a line
(:global positions the cursor at the very beginning of the line to
execute a command on), because it could be that there is no text to
the left of the separator. The searchpos() function returns a list,
the first element of which is the line number of the matched position,
and the second one is the column position. If the command is run on
a line, the line matches the pattern of the containing :global
command. As searchpos() is to look for the same pattern, there is
definitely a match on that line. Therefore, only the column starting
the match is in interest, so it gets extracted from the returning list
by the [1] subscript. This very position equals to the width of the
text in the first column of the line, plus one. Hence, the m variable
is set to the maximum of its current value and that column position.
The second command,
:%s//\=repeat(' ',m-col('.'))
pads the first occurrence of the separator on all of the lines that
contain it, with the number of spaces that is missing to make the text
before the separator to take m characters, minus one. This command
is a global substitution replacing an empty interval just before the
separator (see the comment about the :global command above) with the
result of evaluation of the expression (see :help sub-replace-\=)
repeat(' ',m-col('.'))
The repeat() function repeats its first argument (as string) the
number of times given in the second argument. Since on every
substitution the cursor is moved to the start of the pattern match,
m-col('.') equals exactly to the number of spaces needed to shift
the separator to the right to align columns (col('.') returns the
current column position of the cursor).
1 Below is a one-line version of this pair of commands.
:let m=0|exe'g/\ze -- /let m=max([m,searchpos(#/,"c")[1]])'|%s//\=repeat(' ',m-col('.'))
2 In previous revisions of the answer the commands used
to be as follows.
:let p=[0]|%s/^\ze\(.*\) -- /\=map(p,'max([v:val,len(submatch(1))+1])')[1:0]/
:exe'%s/\ze\%<'.p[0].'c -- /\=repeat(" ",'.p[0].'-col("."))'
Those who are interested in these particular commands can find their
detailed description in this answer’s edit history.
This is a modification on Benoit's answer that has two steps.
First step, block select text search and replace -- with lots of spaces.
'<,'>s/--/ --/
Now all the comments should have lots of spaces, but still be uneven.
Second step, block select the text again and use another regex to match all the characters you want to keep (say the first 20 characters or so) plus all the spaces following, and to replace it with a copy of those first 20 characters:
'<,'>s/\(.\{20}\)\s*/\1/
Not quite as easy as Benoit's, but I couldn't figure out how to make his second step work.

Replace colon with tab to make columns

I have word lists where the word or expression in Spanish is separated by its translation with a colon (":"). I want to make two columns, one for Spanish, the other for English. In vim I tried with
:%s/:/^I^I^I/g
But it does not give the desired output. The different columns are not aligned.
When deleting the colon by hand and inserting the number of tabs with the same amount of tab strokes, it always ends up aligned.
Any idea how to solve this, preferably in vim?
On a Linux/*nix system I use column(1)
:%!column -s':' -t
followed by
:%retab!
I'd probable do a
:s/:/^I/g
followed by a
:set ts=25
Where 25 is the length of the longest word expected + 2. So, if you expect the longest word of your input (on the left side of the colon) to be 12 characters, I'd choose something around 14 instead.
With Tabular.vim it's quite easy, just type :Tab /:\zs and it does the rest.
When in insert mode a setting of yours makes tab to reach a nth column. This is set with the 'softtabstop' settings if I am right.
For those tasks you could use a plugin like Align.vim or Tabularize.
Another option is to insert a huge number of spaces, and then use Visual Block with < operator as many times as necessary, if you have to do it once. Otherwise prefer reusable methods.

Resources