Toggle multiline or single line in parenthesis Vim - python

When coding Python I usually find myself wanting to break lines containing long lists or functions containing several arguments into multiple lines.
Between this:
# Example 1
foo(this_is_a_long_variable_1, this_is_a_long_variable_2, this_is_a_long_variable_3, this_is_a_long_variable_4)
# Example 2
def bar():
return [this_is_a_long_variable_1, this_is_a_long_variable_2, this_is_a_long_variable_3, this_is_a_long_variable_4]
and this:
# Example 1
foo(
this_is_a_long_variable_1,
this_is_a_long_variable_2,
this_is_a_long_variable_3,
this_is_a_long_variable_4,
)
# Example 2
def bar():
return [
this_is_a_long_variable_1,
this_is_a_long_variable_2,
this_is_a_long_variable_3,
this_is_a_long_variable_4,
]
What is the best way to do this?
From what I can gather I want to connect a special action to object-select and the actions themselves should be relatively okay to do by regex replace with some special handling of adding an extra comma before the end of the block.
But I have never really done anything this advanced before with Vim and don't really know where to start.

First, the low-level pieces…
Change the content of the parentheses to this:
foo(
)
with:
ci(<CR><Esc>
leaving the cursor on ).
Put the result of the following expression on the line above the cursor:
:put!=getreg('\"')->split(', *')->map('v:val . \",\"')<CR>
The expression in details:
getreg('\"') gets the content of the default register, here it is what used to be between the parentheses,
split(', *') splits it into individual arguments,
map('v:val . \",\"') appends a , to each item.
NOTE: the command above makes use of the new-ish "method" notation. In older Vims, it should look like this:
:put!=map(split(getreg('\"'), ', *'), 'v:val . \",\"')
Format what we just put:
='[
Second, putting it together…
Now that we have a working solution, we may want to make it a bit easier on the fingers with a simple visual mode mapping, which makes sense because it will keep it simple and agnostic:
xnoremap <key> c<CR><Esc><Cmd>put!=getreg('\"')->split(', *')->map('v:val . \",\"')<CR>='[
Which we can use like this:
vi(<key>
vi[<key>
vip<key>
etc.
NOTE: the mapping above makes use of the new-ish <Cmd> "special text". In older Vims, it should look like this:
xnoremap <key> c<CR><Esc>:<C-u>put!=map(split(getreg('\"'), ', *'), 'v:val . \",\"')<CR>='[

To complement romainl answer I'll give the reverse command to be able to toggle between single line and multi-line.
Single line -> Multi line:
xnoremap <Key1> c<CR><Esc>:put!=map(split(getreg('\"'), ', *'), 'v:val . \",\"')<CR>='[
Multi-line -> Single line
xnoremap <Key2> :s/\%V\n\s*\(.*,\)$/\1 / <Bar> s/\%V\(.*\),\s\n/\1/ <CR>:noh<CR>
Usage:
va(<Key2>
Explanation:
:s/match/replace/flags is search and replace which follows regular regexp matching
\%V is to only search in selection
s/\%V\n\s*\(.*,\)$/\1/ for lines with a comma at the end move these to the preceding line and remove extra whitespace
<Bar> this is a pipe
s/\%V\(.*\),\s\n/\1/ remove the extra last comma, space and newline
However, note that neither can handle complex cases, such as lists containing other lists.

Related

Python multiline comments

I am struggling with multi line comments in Python, I understood I can use # at the start of every line of multi line comments but there is also another way of using """ at the start and end of the comment; however, in my interpreter the """ method gives an output instead of ignoring the comment.
>>> """this should
be a multi
line comment"""
And my interpreter gives the following output:
'this should\nbe a multi\nline comment'
Can someone explain it?
Triple quoted strings are used as comments by many developers but it is actually not a comment. It is similar to regular strings in python but it allows the string to be multi-line. You will find no official reference for triple quoted strings to be a comment.
In python, there is only one type of comment that starts with hash # and can contain only a single line of text.
According to PEP 257, it can however be used as a docstring, which is again not really a comment.
def foo():
"""
Developer friendly text for describing the purpose of function
Some test cases used by different unit testing libraries
"""
<body of the function>
You can just assign them to a variable as you do with single quoted strings:
x = """a multi-line text
enclosed by
triple quotes
"""
Furthermore, if you try it in a repl, triple quoted strings get printed, had it really been a comment, should it have been printed?:
>>> #comment
>>> """triple quoted"""
'triple quoted'
This is very easy to accomplish in python.
# This is a single-line comment
'''
This is a multi-line comment
'''
Just put the comments in ''' and put whatever you want inside of them!

Python Regex Problems with Whitespace

I'm trying to do a python regular expression that looks for lines formatted as such ([edit:] without new lines; the original is all on one line):
<MediaLine Label="main-video" xmlns="ms-rtcp-metrics">
<OtherTags...></OtherTags>
</MediaLine>
I wish to create a capture group of the body of this XML element (so the OtherTags...) for later processing.
Now the problem lies in the first line, where Label="main-video", and I would like to not capture Label="main-audio"
My initial solution is as such:
m = re.search(r'<MediaLine(.*?)</MediaLine>', line)
This works, in that it filters out all other non-MediaLine elements, but doesn't account for video vs audio. So to build on it, I try simply adding
m = re.search(r'<MediaLine Label(.*?)</MediaLine>', line)
but this won't create a single match, let alone being specific enough to filter audio/video. My problem seems to come down to the space between line and Label. The two variations I can think of trying both fail:
m = re.search(r'<MediaLine L(.*?)</MediaLine>', line)
m = re.search(r'<MediaLine\sL(.*?)</MediaLine>', line)
However, the following works, without being able to distinguish audio/video:
m = re.search(r'<MediaLine\s(.*?)</MediaLine>', line)
Why is the 'L' the point of failure? Where am I going wrong? Thanks for any help.
And to add to this preemptively, my goal is an expression like this:
m = re.search("<MediaLine Label=\"main-video\"(?:.*?)>(?P<payload>.*?)</MediaLine>", line)
result = m.group('payload')
By default, . doesn’t match a newline, so your initial solution didn't work either. To make . match a newline, you need to use the re.DOTALL flag (aka re.S):
>>> m = re.search("<MediaLine Label=\"main-video\"(?:.*?)>(?P<payload>.*)</MediaLine>", line, re.DOTALL)
>>> m.group('payload')
'\n <OtherTags...></OtherTags>\n'
Notice there’s also an extra ? in the first group, so that it’s not greedy.
As another comment observes, the best thing to parse XML is an XML parser. But if your particular XML is sufficiently strict in the tags and attributes that it has, then a regular expression can get the job done. It will just be messier.

Trying to replace \t with \s with regex in Python, but as a result "Unhashable type:list" error

I am new to programming and have already checked other people's questions to make sure that I am using a good method to replace tabs with spaces, know my regex is correct, and also understand what exactly my error is ("Unhashable type 'list'). But even still, I'm at a loss of what to do. Any help would be great!
I have a large file that I have broken up into lines. Ultimately I will need to access the first 3 elements of each line. Currently when I print a line, without the additional re.sub line of code, I get something like this: ['blah\tblah\tblah'], when I want ['blah blah blah'].
My code to do this is
f = open(text.txt)
raw = f.read()
raw = raw.lower()
lines = raw.splitlines()
lines = re.sub(r'\t', lines, '\s')
print lines[0:2] #just to see the first few examples
f.close()
When I print the first few lines without the regex sub bit, it works fine. And then when I add that line in attempt to change the lines, I get the error. I understand that lists are changeable and thus can't be a hashed... but I'm not trying to work with a hash. I'm just trying to replace \t with \s in a large text file to make the program easier to work with. I don't think there is a problem with how I am changing \t's to \s's, because according to this error, any way I change it will break my code. What do I do?! Any help is super appreciated. :')
You need to change the order of params present inside the re.sub function. And also note that you can't use regex \s as a second param in re.sub function. Syntax of re.sub must be re.sub(regex,replacement,string) .
lines = raw.splitlines()
lines = [re.sub(r'\t', ' ', line) for line in lines]
raw.splitlines() returns a list which was then assigned to a variable called lines. So you need to apply the re.sub function to each item present in the list, since re.sub won't directly be applied on a list.

Ending a comment in Python

I have already read this: Why doesn't Python have multiline comments?
So in my IDLE , I wrote a comment:
Hello#World
Anything after the d of world is also a part of the comment.In c++ , I am aware of a way to close the comment like:
/*Mycomment*/
Is there a way to end a comment in Python?
NOTE: I would not prefer not to use the triple quotes.
You've already read there are no multiline comments, only single line. Comments cause Python to ignore everything until the end of the line. You "close" them with a newline!
I don't particularly like it, but some people use multiline strings as comments. Since you're just throwing away the value, you can approximate a comment this way. The only time it's really doing anything is when it's the first line in a function or class block, in which case it is treated as a docstring.
Also, this may be more of a shell scripting convention, but what's so bad about using multiple single line comments?
#####################################################################
# It is perfectly fine and natural to write "multi-line" comments #
# using multiple single line comments. Some people even draw boxes #
# with them! #
#####################################################################
You can't close a comment in python other than by ending the line.
There are number of things you can do to provide a comment in the middle of an expression or statement, if that's really what you want to do.
First, with functions you annotate arguments -- an annotation can be anything:
def func(arg0: "arg0 should be a str or int", arg1: (tuple, list)):
...
If you start an expression with ( the expression continues beyond newlines until a matching ) is encountered. Thus
assert (
str
# some comment
.
# another comment
join
) == str.join
You can emulate comments by using strings. They are not exactly comments, since they execute, but they don't return anything.
print("Hello", end = " ");"Comment";print("World!")
if you start with triple quotes, end with triple quotes

How to select the current parameter of a function in vim

Using VIM to code in Python I often have to change or delete the parameters of functions.
I expected to find a command like ci, to select the text between two commas. Somehow like selecting between matching parentheses or brackets with ci" and ci(.
def function1(a,b,delete_me, c):
pass
def function2(a,b,delete_me):
pass
def function3(a,b,delete_me=[1,2,3]):
pass
How can I achieve the expected result efficient with VIM giving the cursor points to the desired parameter already?
Parameter matching is difficult (especially with constructs like nested function calls and arrays) as it depends on the underlying syntax. That's why there's no built-in inner / outer parameter text object.
I use the argtextobj.vim plugin, which works fairly well.
I don't think there's a builtin command for this but here are some examples which might help.
Selecting text:
ve -- end of word boundary
vE -- end of line
Deleting:
de -- delete until end of word
dE -- delete until end of line
Some regex magic could also do the work, if you have to remove a bunch of identical things.
:%s/\,d.*\]//g -- Replace anything with that begins with ,d and ends in ] with ""
def function3(a,b,delete_me=[1,2,3]):
pass
Turns into
def function3(a,b):
pass
For simple parameters, ciw (change inside word) is equivalent.
In your third example, if your cursor is at the start of the parameter then cf] (change to find ]) will work.

Categories