How to select the current parameter of a function in vim - python

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.

Related

Toggle multiline or single line in parenthesis Vim

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.

How do I let Sympy parse an expression while only regarding arbitrary previously defined strings as symbols?

So what I am trying to do is write a script that lets me input some function and a list of the variables inside it, then processes it into some other formular, computes a result, and then outputs both the new formular and the result as Latex code. Everything works fine as long as I only input variables which do not contain "^", "{", or "}". The problem is, I want to use, or, at the very least, output the names exactly as they are written in my Latex document, and as such they do often contain these characters.
I am aware that there is a built-in Latex-Parser in Sympy, but as I understood it requires some other package (antlr4), and I would like to try to avoid that, since I am planning to distribute the script to my fellow students, and don't want to add another requirement for running the script.
So what I thought of is that I could use the list of variable names (which I input anyway together with their values to allow the program to compute a final result): I tried to define a "transformation", as it is described on the Sympy documentation on parsing. It looks like this:
#Defining the transformation
def can_split(symbol):
#Check if symbol is in one of the lists of inputted values (the two lists contain tuples of variable names[0] and their corresponding values[1])
if symbol not in ([i[0] for i in uncertainValues]+[i[0] for i in certainValues]):
return _token_splittable(symbol)
return False
#Read function definition from TKinter text field, split only by custom defined symbols
function=parse_expr(functionEntry.get("1.0", "end-1c"),transformations = (split_symbols_custom(can_split)))
The problem is that if I run this script, and input e. g. "a^b*c", and the variable names "a^b" and "c", which should normally be read as "the variable 'a^b' multiplied with the variable 'c'"I get the exception: "NameError: name 'a' is not defined".
If anyone could help me with this, or maybe propose another way to do this properly, I would be very thankful. Also, if there is more code or context needed to find a better solution, I'll provide more - I just felt everything would get too long-winding if I explained the whole idea. But as I said, I'll be glad to do that if it helps.
Quick but dirty workaround:
For now I ended up using the dirty method of replacing all problematic characters with unique strings at input, and replacing them with their symbols again before outputting.

Python Conditional Regex

My program is given an object with parameters, and I need to get the parameters' values.
The object my program is given will look like this:
Object = """{{objectName|
parameter1=random text|
parameter2=that may or may not|
parameter3=contain any letter (well, almost)|
parameter4=this is some [[problem|problematic text]], Houston, we have a problem!|
otherParameters=(order of parameters is random, but their name is fixed)}}"""
(all parameters might or might not exist)
I am trying to get the properties values.
In the first 3 lines, its pretty easy. a simple regex will find it:
if "parameter1" in Object:
parameter1 = re.split(r"parameter1=(.*?)[\|\}]", Object)[1]
if "parameter2" in Object:
parameter2 = re.split(r"parameter2=(.*?)[\|\}]", Object)[1]
and so on.
The problem is with parameter4, the above regex (property4=(.*?)[\|\}]) will only return this is some [[problem, since the regex stops at the vertical bar.
Now here is the thing: vertical bar will only appear as part of the text inside "[[]]".
For example, parameter1=a[[b|c]]d might appear, but parameter1=a|bc| will never appear.
I need a regex which will stop at vertical bar, unless it is inside double square brackets. So for example, for parameter4, I will get this is some [[problem|problematic text]], Houston, we have a problem!
Worked here when I removed the "?":
parameter4 = re.split(r"parameter4=(.*)[\|\}]", object_)[1]
I also changed the name of the variable to "object_" because "object" is a built-in object in Python
Best.
Apparently, there is no perfect solution.
For other readers possibly reading this question in the future, the closest solution is, as pointed by Wiktor Stribiżew in the comments, parameter4=([^[}|]*(?:\[\[.*?]][^[}|]*)*).
This regex will only work if the param text does not contain any single [, } and | but may contain [[...]] sub-strings.
If you want to understand this regex better, you might want to have a look here: https://regex101.com/r/bWVvKg/2

PyYAML variables in multiline

I'm trying to get a multi-line comment to use variables in PyYAML but not sure if this is even possible.
So, in YAML, you can assign a variable like:
current_host: &hostname myhost
But it doesn't seem to expand in the following:
test: |
Hello, this is my string
which is running on *hostname
Is this at all possible or am I going to have to use Python to parse it?
The anchors (&some_id) and references (*some_id) mechanism is essentially meant to provide the possibility to share complete nodes between parts of the tree representation that is a YAML text. This is e.g. necessary in order to have one and the same complex item (sequence/list resp. mapping/dict) that occurs in a list two times load as one and same item (instead of two copies with the same values).
So yes, you need to do the parsing in Python. You could start with the mechanism I provided in this answer and change the test
if node.value and node.value.startswith(self.d['escape'])
to find the escape character in any place in the scalar and take appropriate action.
You can find the answer here.
Just use a + between lines and your strings need to be enclosed in 's.

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

Categories