Create a vocabulary notebook [Vim] - vim

I want to use vim to read foreign texts and collect vocabularies quickly. Simply by firing a hotkey, the current word (vocab) and sentence (usage) should be piped to my notebook. This will be done by a vim script (should be simple).
# New words (2019-12-07)
word: "Nullstellensatz"
usage: "A general solution of the ideal membership problem provides an effective Nullstellensatz, at least for the weak form."
time: 2019-12-07T22:19:02-04:00
source: /home/username/Hilbert's-Nullstellensatz.md
word: "disponibles"
usage: "Ces outils sont disponibles pour toute forme de consultation, privée ou publique."
time: 2019-12-07T22:21:49-04:00
source: /home/username/CONFIANCE-DANS-LES-PLATEFORMES.md
So far, I have figured out how to select word and sentence by viw and vis in normal mode, and to pipe selected text to an external file by :'<,'>w!cat >> notebook.md.
remaining problems
I have successfully piped the words and sentences to a file, but they stay in the same line. How can I insert a \n, word: and usage: using vim commands?
How to get the current time, and pipe that into my target file?
How to read the current file name, and pipe that into my target file?

It is way nicer using redir than :w!cat (:help :redir).
function! GrabWord()
normal mz"zyiw"yyis`z
redir >> /path/to/notebook.md " CHANGE PATH
echo 'word: ' . json_encode(#z)
echo 'usage: ' . json_encode(#y)
echo 'time: ' . strftime('%FT%T%z')
echo 'source: ' . json_encode(expand('%:p'))
echo ''
redir END
endfunction
nnoremap <silent> Q :silent call GrabWord()<CR>
I threw in a json_encode so strings including backslashes or double quotes would not break.

Related

How to insert original line number in g/pattern/move

vim: insert original line number in g/pattern/move $
I'm debugging some event order in a log and like to check two set of events sequence by the line number of the showing log. Usually, I used g/pattern/move $ for some interesting info. But I cannot find a way to insert the original line number of them. Please help.
I tried :
g/pattern/move $; printf("%d",line("."))
but it does not work.
Can't help thinking of something very straightforward, for example:
g/pattern/call append(line('$'), line('.') . ' ' . getline('.'))
A slightly different way but I have following mapping in my _vimrc
nnoremap <F3> :redir! #f<cr>:silent g//<cr>:redir! END<cr>:enew!<cr>:put! f<cr>:let #f=#/<cr>:g/^$/d<cr>:let #/=#f<cr>gg
It opens a new buffer with all your search matches, including the linenumbers where the match occured.
I have figured out a way to insert at first the line number on the lines that have the pattern and after that moving the same lines to the end of the file:
:%s,\v^\ze.*pattern,\=line('.') . ' ' ,g | g/pattern/m$
We have two commands:
:%s,\v^\ze.*pattern,\=line('.') . ' ' ,g
, ....................... we are using comma as delimiter
\v ...................... very magic substitution
^ ....................... Regular expression for beginning of line
\ze ..................... indicates that all after it will not be substituted
\=line('.') ............. gets the line number
. ' ' .................. concatenates one space after the number
The second command is separated with |
g/pattern/m$
m$ ....................... moves the pattern to the end of file

VIM: delete strings with the same pattern

I need to find all pairs of strings that have the same pattern.
For example:
another string, that is not interesting
la-di-da-di __pattern__ -di-la-di-la
la-di-da-da-di-la __pattern__ -la-da-li-la
and yet another usual string
So I want to delete strings with __pattern__ inside.
I don't know how to do it just with builtin commands and now I have the function, that doesn't work properly:
function! DelDup(pattern)
echom a:pattern
redir => l:count
execute "normal! :%s/a:pattern//n\<cr>"
redir END
echo l:count
endfunction
Here I try to run ":%s/a:pattern//n" to find the count of occurrences of pattern in the text.
And at the same time I try to put it into the variable "l:count".
Then I tried to echo the count I got, but nothing happens when I try to do it.
So the last my problem in function writing is that I can't write the command execution result to variable.
If you have another solution -- please describe it to me.
Update:
Excuse me for bad description. I want to delete only strings, that has pattern-twins in text.
I'm not sure if I understand your question correctly, but I'm assuming you want to remove all lines where there are at least 2 matches. If that's the case you can use the following command:
:g/\(__pattern__.*\)\{2,}/d
How this works is that it deletes all the lines where there is a match (:g/../d).
The pattern is made up of a group (\(..\)) which needs to be matched at least 2 times (\{2,}). And the pattern has a .* at the end so it matches everything between the matches of the pattern.
There are many ways to count occurrences of a pattern, and I'm quite sure there exist a Q/A on the subject. Let's do it yet another way and chain with the next step. (Yes this is completely obfuscated, but it permits to obtain programmatically the information without the need to parse the localized result of :substitute after redirection.)
" declare a list that contain all matches
let matches = []
" replace each occurrence of the "pattern" with:
" the result of the expression "\=" that can be
" interpreted as the last ([-1]) element of the
" list "matches" returned by the function (add)
" that adds the current match (submatch(0)) to the
" list
:%s/thepattern/\=add(matches, submatch(0))[-1]/gn
" The big caveat of this command is that it modifies
" the current buffer.
" We need something like the following to leave it unmodified:
:g/thepattern/call substitute(getline('.'), 'thepattern', '\=add(counter, submatch(0))[-1]', 'g')
" Note however that this flavour won't work with multi-lines patterns
" Now you can test the number of matches or do anything fancy with it
if len(matches) > 1
" replaces matches with nothing
:%s/thepattern//g
endif
Only if you want to define this as a function you'll need to play with:
exe 'normal :%s/'.escape(a:pattern, '/\').'/replacement..../flags....'

how to save complex grep output in gvim

In my gvim file I have something like below
Before line
name1/name2/name3/
next line
name2/name3 is the pattern of my interest.
I want to delete the line containing the pattern and the next line of matched pattern.
I want to save the both lines in other file. I am able to search and delete the lines using global
:g/name2\/name3/,+1d
but not able to redirect it into other file.
How can I save the grep output to some other file.
You can use write >> f to append to the file f (or write! >> f if you want to create f if it doesn't exist).
:g/name2\/name3/,+ write! >> f | ,+d
Alternatively you could use :redir >> (which avoids repeating the ,+ range) :
:g/name2\/name3/,+1d | redir! >> f | silent echon #" | redir END
Maybe there is a better solution, but after you deleted the lines they are in the " register (you can check with :reg).
Just open a new buffer with :new, paste the register with p and save the file with :w myfilename

In Vim, how to remove all lines that are duplicate somewhere

I have a file that contains lines as follows:
one one
one one
two two two
one one
three three
one one
three three
four
I want to remove all occurrences of the duplicate lines from the file and leave only the non-duplicate lines. So, in the example above, the result should be:
two two two
four
I saw this answer to a similar looking question. I tried to modify the ex one-liner as given below:
:syn clear Repeat | g/^\(.*\)\n\ze\%(.*\n\)*\1$/exe 'syn match Repeat "^' . escape(getline ('.'), '".\^$*[]') . '$"' | d
But it does not remove all occurrences of the duplicate lines, it removes only some occurrences.
How can I do this in vim? or specifically How can I do this with ex in vim?
To clarify, I am not looking for sort u.
If you have access to UNIX-style commands, you could do:
:%!sort | uniq -u
The -u option to the uniq command performs the task you require. From the uniq command's help text:
-u, --unique
only print unique lines
I should note however that this answer assumes that you don't mind that the output doesn't match any sort order that your input file might have already.
if you are on linux box with awk available, this line works for your needs:
:%!awk '{a[$0]++}END{for(x in a)if(a[x]==1)print x}'
Assuming you are on an UNIX derivative, the command below should do what you want:
:sort | %!uniq -u
uniq only works on sorted lines so we must sort them first with Vim's buit-in :sort command to save some typing (it works on the whole buffer by default so we don't need to pass it a range and it's a built-in command so we don't need the !).
Then we filter the whole buffer through uniq -u.
My PatternsOnText plugin version 1.30 now has a
:DeleteAllDuplicateLinesIgnoring
command. Without any arguments, it'll work as outlined in your question.
It does not preserve the order of the remaining lines, but this seems to work:
:sort|%s/^\(.*\)\n\%(\1\n\)\+//
(This version is #Peter Rincker's idea, with a little correction from me.) On vim 7.3, the following even shorter version works:
:sort | %s/^\(.*\n\)\1\+//
Unfortunately, due to differences between the regular-expression engines, this no longer works in vim 7.4 (including patches 1-52).
Taking the code from here and modifying it to delete the lines instead of highlighting them, you'll get this:
function! DeleteDuplicateLines() range
let lineCounts = {}
let lineNum = a:firstline
while lineNum <= a:lastline
let lineText = getline(lineNum)
if lineText != ""
if has_key(lineCounts, lineText)
execute lineNum . 'delete _'
if lineCounts[lineText] > 0
execute lineCounts[lineText] . 'delete _'
let lineCounts[lineText] = 0
let lineNum -= 1
endif
else
let lineCounts[lineText] = lineNum
let lineNum += 1
endif
else
let lineNum += 1
endif
endwhile
endfunction
command! -range=% DeleteDuplicateLines <line1>,<line2>call DeleteDuplicateLines()
This is not any simpler than #Ingo Karkat's answer, but it is a little more flexible. Like that answer, this leaves the remaining lines in the original order.
function! RepeatedLines(...)
let first = a:0 ? a:1 : 1
let last = (a:0 > 1) ? a:2 : line('$')
let lines = []
for line in range(first, last - 1)
if index(lines, line) != -1
continue
endif
let newlines = []
let text = escape(getline(line), '\')
execute 'silent' (line + 1) ',' last
\ 'g/\V' . text . '/call add(newlines, line("."))'
if !empty(newlines)
call add(lines, line)
call extend(lines, newlines)
endif
endfor
return sort(lines)
endfun
:for x in reverse(RepeatedLines()) | execute x 'd' | endfor
A few notes:
My function accepts arguments instead of handling a range. It defaults to the entire buffer.
This illustrates some of the functions for manipulating lists. :help list-functions
I use /\V (very no magic) so the only character I need to escape in a search pattern is the backslash itself. :help /\V
Add line number so that you can restore the order before sort
:%s/^/=printf("%d ", line("."))/g
sort
:sort /^\d+/
Remove duplicate lines
:%s/^(\d+ )(.*)\n(\d+ \2\n)+//g
Restore order
:sort
Remove line number added in #1
:%s/^\d+ //g
please use perl ,perl can do it easily !
use strict;use warnings;use diagnostics;
#read input file
open(File1,'<input.txt') or die "can not open file:$!\n";my #data1=<File1>;close(File1);
#save row and count number of row in hash
my %rownum;
foreach my $line1 (#data1)
{
if (exists($rownum{$line1}))
{
$rownum{$line1}++;
}
else
{
$rownum{$line1}=1;
}
}
#if number of row in hash =1 print it
open(File2,'>output.txt') or die "can not open file:$!\n";
foreach my $line1 (#data1)
{
if($rownum{$line1}==1)
{
print File2 $line1;
}
}
close(File2);

VIM How to insert output of a shell command at a different place in the file

I have come up with a perl script that outputs a template for documenting functions and structures given the definition of a function/struct from my C code.
To use it , i visually select the definition of the struct, yank and paste it right above the original definition, and invoke the script on this pasted struct. It replaces it with document for that struct.
Now is there a way that will avoid that yank paste? I am looking for a way to invoke a shell command but the output from that should be pasted somewhere else in the file, not necessarily on top of it.
IOW
:'a,'b!perl ~/bin/document.pl
replaces text between mark a and markb, I want to add the output of document.pl above mark a.
One possible solution would be to modify the perl script in a way that it also outputs its input at the end. Then you would end up with the desired result.
If you have zsh as your shell, you can use co-process:
'a,'b!coproc perl ~/bin/document.pl ; tee >&p | cat <&p
To get output before your text (this command puts it after), you should use a slightly more complex command:
'a,'b!coproc perl ~/bin/document.pl ; tee >&p | cat <(<&p) -
System-independent solution, using vim and temporary buffer:
'a,'byank a | new | 0put a | $d | execute "%!perl ~/bin/document.pl" | %d a | bw! | 'a-1put a
Try with something like this:
function! MyFunc() range
" Preserve the register.
let old_reg = #a
exec a:firstline.','.a:lastline.'yank a'
" Change to do what you need with register a.
" Insert output before a:firstline
exec (a:firstline - 1).'read !your magic with '.#a
" Restore the register
let #a = old_reg
endfunction
" :2,5MyOwn will process lines from 2 to 5 and insert the output before line 2
command! -bar -range -nargs=? MyOwn <line1>,<line2>call MyFunc()

Resources