Justify text, how to divide the spaces between the words? - vim

I'm creating a new function to justify text. I know there are a few plugins but they don't do what I want to do so I decided to create a function myself.
before:
text_text_text_text_____
after:
text__text___text___text
What I did first is to find the number of words and the number of non space characters (for every line in my text):
let #e = '' | redir #e | silent exe i.'s/'.cols.'\(\S\+\)/&/gne' | redir END
if matchstr(#e, 'match') != '' | let nrwords = matchstr(#e, '\d\+\ze') | else | continue | endif
let #e = '' | redir #e | silent exe i.'s/'.cols.'\S/&/gne' | redir END
if matchstr(#e, 'match') != '' | let nonspaces = matchstr(#e, '\d\+\ze') | else | let nonspaces = 0 | endif
Then to find the spaces:
let spaces = textwidth_I_want - nonspaces
I have to divide the spaces between the words:
let SpacesBetweenWords = spaces/(str2float(nrwords)-1)
However often it is a float number.
p.e. spaces = 34
nr.words-1 = 10
SpacesBetweenWords = 3.4
What I want to do is to divide the spaces between the word, like this:
Spaces between words:
4 3 3 4 3 3 4 3 3 4
and put them in a list 'SpaceList'
and then insert them between the words
for m in range(1,len(SpaceList))
exe i 's/^'.cols.'\(\S\+\zs\s\+\ze\S\+\)\{'.m.'}/'.repeat(' ', SpaceList[m-1]).'/'
endfor
(cols = my block selection OR entire line)
It is easy to create a list with all integers p.e. in my example
3 3 3 3 3 3 3 3 3 3 (Spaces = 30)
but there are still 4 spaces to divide between the words.
My problem is "how can I divide these spaces between the number of words"?

The result is 3.4, it means, you cannot make all distances between words with same length. You have to either adjust the textwidth_I_want value to make the result an integer, or you set the distance as 3, and calculate how many spaces you still need (10*(3.4-3)=4). You can add these 4 spaces to 4 distances.

Found the solution using python
let r = float2nr(SpacesBetweenWords)
let places = nrwords-1
"the extra spaces to put between words
let extraspaces2divide = float2nr(spaces-r*places)
"find randomly with the help of python a serie of (unique) numbers for every space to divide, numbers between 1 and the number of words
exe "py import vim,random; Listvalue = random.sample(xrange(1,".(places+1).",1),".extraspaces2divide.");vim.command(\"let randomlist = '%s'\"% Listvalue)"
"the result of python is a string like this [1, 3, 6, 9]: remove the brackets and spaces and split the result to a list
let randomlist = substitute(randomlist, '[\[\] ]', '', 'g')
"this list is the serie of random numbers (= the place between words) where to add an extra space
let list = sort(split(randomlist, ','))
for s in range(1,nrwords-1)
if (index(list, ''.s.'') != -1)
let r2 = r+1
else
let r2 = r
endif
call add(SpaceList, r2)
endfor

Related

Is there a way to get the rightmost column of a visual block selection?

Assume that virtualedit=. Consider the following text file.
1 2 1 1 1 1 1 1
1 3 1 1 1 1 1 1
1 4 1 1 1 1 1 1 1 1
1 1 1 1 7 1 1 1
If I visual-block-select lines 2 through 4 and use $ to make the selection non-rectangular, the following code will fail to find the rightmost column in the selection. More explicitly, I use the key sequence 2G^vjj$, where ^v is Control-V.
" rightmostCol becomes 15, not 19 as desired.
let rightmostCol = virtcol("'>")
Is there some other function I can call, or expression I can pass to virtcol, to programmatically get the column number of the rightmost column in my Visual Block selection?
Note that simply finding the length of the longest line in the visual block is incorrect, because the visual selection could have also been rectangular, and those cases should not be treated the same way.
Edit: If it was possible to determine whether the visual block selection is rectangular or not, that would also work.
There is no direct way to distinguish those cases through marks. One solution would be to analyze the text yourself, adding the longest line length to the leftmost visual mark position. It is important to check both marks since they could be reversed in case the selection was made towards the start.
function! RightmostVirtualColumn()
let reg_v = #v
silent normal! gv"vy
let max = 0
for line in split(getreg('v'), '\n')
let len = strlen(line)
let max = max([len, max])
endfor
let #v = reg_v
let max += min([virtcol("'<"), virtcol("'>")]) - 1
return max
endfunction
You can write a VimScript function:
function! GetRightmostCol()
let start=line("'<")
let end=line("'>")
let line=start
let len=len(getline(line))
while line<=end
let len=len<len(getline(line))?len(getline(line)):len
let line+=1
endwhile
return len
endfunction
Alternatively approach using register 0:
function! GetRightmostCol()
let lines=split(getreg(0),"\n")
let len=0
for line in lines
let len=len<len(line)?len(line):len
let line+=1
endfor
return len
endfunction
Then use the function in the assignment:
:let rightmostCol = GetRightmostCol()

How would I undo the actions of string.gmatch for a certain section of string in lua

So I am using lua and splitting a string by spaces to write a sort of sub-language. And I am trying to have it not split anything inside parenthesis, I am already at the stage where I can detect whether there is parenthesis. But I want to reverse the gmatching of the string inside the parenthesis as I want to preserve the string contained within.
local function split(strng)
local __s={}
local all_included={}
local flag_table={}
local uncompiled={}
local flagged=false
local flagnum=0
local c=0
for i in string.gmatch(strng,'%S+') do
c=c+1
table.insert(all_included,i)
if(flagged==false)then
if(string.find(i,'%('or'%['or'%{'))then
flagged=true
flag_table[tostring(c)]=1
table.insert(uncompiled,i)
print'flagged'
else
table.insert(__s,i)
end
elseif(flagged==true)then
table.insert(uncompiled,i)
if(string.find(i,'%)' or '%]' or '%}'))then
flagged=false
local __=''
for i=1,#uncompiled do
__=__ .. uncompiled[i]
end
table.insert(__s,__)
print'unflagged'
end
end
end
return __s;
end
This is my splitting code
I would just not use gmatch for this at all.
local input = " this is a string (containg some (well, many) annoying) parentheses and should be split. The string contains double spaces. What should be done? And what about trailing spaces? "
local pos = 1
local words = {}
local last_start = pos
while pos <= #input do
local char = string.byte(input, pos)
if char == string.byte(" ") then
table.insert(words, string.sub(input, last_start, pos - 1))
last_start = pos + 1
elseif char == string.byte("(") then
local depth = 1
while depth ~= 0 and pos + 1 < #input do
local char = string.byte(input, pos + 1)
if char == string.byte(")") then
depth = depth - 1
elseif char == string.byte("(") then
depth = depth + 1
end
pos = pos + 1
end
end
pos = pos + 1
end
table.insert(words, string.sub(input, last_start))
for k, v in pairs(words) do
print(k, "'" .. v .. "'")
end
Output:
1 ''
2 'this'
3 'is'
4 'a'
5 'string'
6 '(containg some (well, many) annoying)'
7 'parentheses'
8 'and'
9 'should'
10 'be'
11 'split.'
12 'The'
13 'string'
14 'contains'
15 ''
16 'double'
17 ''
18 ''
19 'spaces.'
20 'What'
21 'should'
22 'be'
23 'done?'
24 'And'
25 'what'
26 'about'
27 'trailing'
28 'spaces?'
29 ''
Thinking about trailing spaces and other such problems is left as an exercise for the reader. I tried to highlight some of the possible problems with the example that I used. Also, I only looked at one kind of parenthesis since I do not want to think how this (string} should be ]parsed.
Oh and if nested parenthesis are not a concerned: Most of the code above can be replaced with a call to string.find(input, ")", pos, true) to find the closing parenthesis.
Please note that you cannot or or and patterns as attempted in your code.
"%(" or "%[" equals "%("
Lua will interpret that expression left to right. "%( is a true value Lua will reduce the expression to "%(", which logically is the same as the full expression.
So string.find(i,'%('or'%['or'%{') will only find ('s in i.
As a similar but slightly different approach to Uli's answer, I would first split by parentheses. Then you can split the the odd-numbered fields on whitespace:
split = require("split") -- https://luarocks.org/modules/telemachus/split
split__by_parentheses = function(input)
local fields = {}
local level = 0
local field = ""
for i = 1, #input do
local char = input:sub(i, i)
if char == "(" then
if level == 0 then
-- add non-parenthesized field to list
fields[#fields+1] = field
field = ""
end
level = level + 1
end
field = field .. char
if char == ")" then
level = level - 1
assert(level >= 0, 'Mismatched parentheses')
if level == 0 then
-- add parenthesized field to list
fields[#fields+1] = field
field = ""
end
end
end
assert(level == 0, 'Mismatched parentheses')
fields[#fields+1] = field
return fields
end
input = " this is a string (containg some (well, many) annoying) parentheses and should be split. The string contains double spaces. What should be done? And what about trailing spaces? "
fields = split__by_parentheses(input)
for i, field in ipairs(fields) do
print(("%d\t'%s'"):format(i, field))
if i % 2 == 1 then
for j, word in ipairs(split.split(field)) do
print(("\t%d\t%s"):format(j, word))
end
end
end
outputs
1 ' this is a string '
1
2 this
3 is
4 a
5 string
6
2 '(containg some (well, many) annoying)'
3 ' parentheses and should be split. The string contains double spaces. What should be done? And what about trailing spaces? '
1
2 parentheses
3 and
4 should
5 be
6 split.
7 The
8 string
9 contains
10 double
11 spaces.
12 What
13 should
14 be
15 done?
16 And
17 what
18 about
19 trailing
20 spaces?
21

cell array of strings to matrix

A = {'a','b','c','b','a',...}
A is a <1X400> cell array and I want to create a matrix from A such that if the cell is a, the matrix shows 1, if it is b, it shows as 2 in the matrix and 3 for c.
Thank you.
Specific Case
For a simple specific case as listed in the question, you can use char to convert all the cell elements to characters and then subtract 96 from it, which is ascii equivalent of 'a'-1 -
A_numeric = char(A)-96
Sample run -
>> A
A =
'a' 'b' 'c' 'b' 'a'
>> A_numeric = char(A)-96
A_numeric =
1
2
3
2
1
Generic Case
For a generic substitution case, you need to do a bit more of work like so -
%// Inputs
A = {'correct','boss','cat','boss','correct','cat'}
newcellval = {'correct','cat','boss'}
newnumval = [8,2,5]
[unqcell,~,idx] = unique(A,'stable')
[~,newcell_idx,unqcell_idx] = intersect(newcellval,unqcell,'stable')
A_numeric = newnumval(changem(idx,newcell_idx,unqcell_idx))
Sample input-output -
>> A,newcellval,newnumval
A =
'correct' 'boss' 'cat' 'boss' 'correct' 'cat'
newcellval =
'correct' 'cat' 'boss'
newnumval =
8 2 5
>> A_numeric
A_numeric =
8 5 2 5 8 2
That's easy:
result = cell2mat(A)-'a'+1
For a generic association of letters to numbers 1,2,3...:
letters2numbers = 'abc'; %// 'a'->1, 'b'->2 etc.
[~, result] = ismember(cell2mat(A), letters2numbers)
For a generic association of strings to numbers 1,2,3...:
strings2numbers = {'hi', 'hello', 'hey', 'good morning', 'howdy'};
A = {'hello', 'hi', 'hello', 'howdy', 'bye'};
[~, result] = ismember(A, strings2numbers)
In this example,
result =
2 1 2 5 0
use a For Loop which iterate over A and convert character to number
for loop = 1:length(A)
outMat(loop) = char(A(loop)) - 96
end
I hope it works.

Vim: How to number paragraphs automatically and how to refer to this numbering?

Let us say I have the following three paragraphs of text (separated
from each other by empty lines—number 3 and 7, here):
This is my first paragraph line 1
This is my first paragraph line 2
This is my second paragraph line 4
This is my second paragraph line 5
This is my second paragraph line 6
This is my third paragraph line 8
This is my third paragraph line 9
Question 1: How can I number these paragraphs automatically,
to obtain this result:
1 This is my first paragraph line 1
This is my first paragraph line 2
2 This is my second paragraph line 4
This is my second paragraph line 5
This is my second paragraph line 6
3 This is my third paragraph line 8
This is my third paragraph line 9
(I succeeded to do this, but only via a clumsy macro.)
Question 2: Is it possible to refer to these paragraphs? For
instance, is it possible to index a text file as answered (by Prince
Goulash and Herbert Sitz) in the earlier question, but this time
with the paragraph numbers and not the line numbers?
Thanks in advance.
Here's one way to do the ref numbers, with a pair of functions:
function! MakeRefMarkers()
" Remove spaces from empty lines:
%s/^ \+$//
" Mark all spots for ref number:
%s/^\_$\_.\zs\(\s\|\S\)/_parref_/
" Initialize ref val:
let s:i = 0
" Replace with ref nums:
%s/^_parref_/\=GetRef()/
endfunction
function! GetRef()
let s:i += 1
return s:i . '. '
endfunction
Then just do it by calling MakeRefMarkers(). It doesn't remove existing ref numbers if they're there, that would require another step. And it doesn't catch first paragraph if it's first line in file (i.e, without preceding blank line). But it does handle situations where there's more than one blank line between paragraphs.
Question One
Here is a function to enumerate paragraphs. Simply do :call EnumeratePara() anywhere in your file. The variable indent can be adjusted as you wish. Let me know if anything needs correcting or explaining.
function! EnumeratePara()
let indent = 5
let lnum = 1
let para = 1
let next_is_new_para = 1
while lnum <= line("$")
let this = getline(lnum)
if this =~ "^ *$"
let next_is_new_para=1
elseif next_is_new_para == 1 && this !~ "^ *$"
call cursor(lnum, 1)
sil exe "normal i" . para . repeat(" ", indent-len(para))
let para+=1
let next_is_new_para = 0
else
call cursor(lnum, 1)
sil exe "normal i" . repeat(" ", indent)
endif
let lnum += 1
endwhile
endfunction
Question Two
This isn't a very elegant approach, but it seems to work. First of all, here's a function that maps each line in the file to a paragraph number:
function! MapLinesToParagraphs()
let lnum = 1
let para_lines = []
let next_is_new_para = 1
let current_para = 0
while lnum <= line("$")
let this = getline(lnum)
if this =~ "^ *$"
let next_is_new_para = 1
elseif next_is_new_para == 1
let current_para += 1
let next_is_new_para = 0
endif
call add(para_lines, current_para)
let lnum += 1
endwhile
return para_lines
endfunction
So that para_lines[i] will give the paragraph of line i.
Now we can use the existing IndexByWord() function, and use MapLinesToParagraph() to convert the line numbers into paragraph numbers before we return them:
function! IndexByParagraph(wordlist)
let temp_dict = {}
let para_lines = MapLinesToParagraphs()
for word in a:wordlist
redir => result
sil! exe ':g/' . word . '/#'
redir END
let tmp_list = split(strtrans(result), "\\^\# *")
let res_list = []
call map(tmp_list, 'add(res_list, str2nr(matchstr(v:val, "^[0-9]*")))')
call map(res_list, 'para_lines[v:val]')
let temp_dict[word] = res_list
endfor
let result_list = []
for key in sort(keys(temp_dict))
call add(result_list, key . ' : ' . string(temp_dict[key])[1:-2])
endfor
return join(result_list, "\n")
endfunction
I have not tested these functions very thoroughly, but they seem to work okay, at least in your example text. Let me know how you get on!
Both problems could be solved much easier than it is suggested
by the other two answers.
1. In order to solve the first problem of numbering paragraphs,
the following two steps are ample.
Indent the paragraphs (using tabs, here):
:v/^\s*$/s/^/\t/
Insert paragraph numbering (see also my answer to
the question on substitution with counter):
:let n=[0] | %s/^\s*\n\zs\ze\s*\S\|\%1l/\=map(n,'v:val+1')
2. The second problem of creating index requires some scripting in
order to be solved by Vim means only. Below is the listing of a small
function, WordParIndex() that is supposed to be run after paragraphs
are numbered according to the first problem's description.
function! WordParIndex()
let [p, fq] = [0, {}]
let [i, n] = [1, line('$')]
while i <= n
let l = getline(i)
if l !~ '^\s*$'
let [p; ws] = ([p] + split(l, '\s\+'))[l=~'^\S':]
for w in ws
let t = get(fq, w, [p])
let fq[w] = t[-1] != p ? t + [p] : t
endfor
endif
let i += 1
endwhile
return fq
endfunction
The return value of the WordParIndex() function is the target index
dictionary. To append its text representation at the end of a buffer,
run
:call map(WordParIndex(), 'append(line("$"),v:key.": ".join(v:val,","))')
My approach would be macro based:
Yank the number "0" somehow and move to the start of the first paragraph.
Record a macro to
Indent the paragraph with >}
Paste the stored number at the correct position p
Increment the number by one with <ctrl>-a
Yank the pasted number with yiw
Move to the next paragraph with }l or /^\S
Execute the macro as many times as needed to reach the end of the document.
The method of pasting a number, incrementing it, and then reyanking it inside a macro is quite a useful technique that comes in handy whenever you need to number things. And it's simple enough to just do it in a throw-away fashion. I mainly use it for carpet logging, but it has other uses as your question demonstrates.

how do i get vim's foldtext to show the number of lines on the right side of the screen?

I am trying to get the number of lines to show up on the right side of the screen, instead of near the left with the other text. Is this possible? My current .vimrc foldtext function concatenates the first two lines and keeps the current indent, followed by some dashes and then the number of lines:
function! MyFoldText()
let line = getline(v:foldstart)
let line2 = getline(v:foldstart + 1)
let sub = substitute(line . "|" . line2, '/\*\|\*/\|{{{\d\=', '', 'g')
let ind = indent(v:foldstart)
let lines = v:foldend-v:foldstart + 1
let i = 0
let spaces = ''
while i < (ind - ind/4)
let spaces .= ' '
let i = i+1
endwhile
return spaces . sub . ' --------(' . lines . ' lines)'
endfunction
So, using '|' as a screen edge, instead of
| line1 | line2 --------(5 lines)-----------------|
the foldtext would be like this
| line1 | line2 -------------------------(5 lines)|
p.s.
It would also be nice to get a few extra fixes, such as pulling current tabstop setting instead of hardcoding it as 4, and getting it to show the next actual code (skipping comments, whitespace, brackets, etc), instead of just concatenating the first two lines.
Something like the line below is what I use, sort of tailored to your code. You will need to set offset to some value that fits your situation; I think you might want offset of around 8 or 9:
let offset = 8
return spaces . sub . repeat('-', winwidth(0)-strlen(spaces . sub) - offset) . '('. lines .')'
Here is an example from the help of the EightHeader plugin which does exactly what you want:
If you don't like the default 'foldtext' you can customize it by setting to
EightHeaderFolds().
For example the closed folds looks like this by default:
```+-- 45 lines: Fold level one
+--- 67 lines: Fold level two
If you would like to change it to this kind:
Fold level one................45 lines
Fold level two..............67 lines
... then you can use this function:
let &foldtext = "EightHeaderFolds( '\\=s:fullwidth-2', 'left', [ repeat( ' ', v:foldlevel - 1 ), '.', '' ], '\\= s:foldlines . \" lines\"', '' )"

Resources