Editing Code

In this section, we’ll discuss editing existing code in more detail.

Cursor Movement

You can either move your cursor with directions:

up
down
left
right

Or, you can use the go to command. As a shorthand, if you just say a selector without any corresponding verb, Serenade will assume you meant go to. This default makes navigating code a bit more efficient:

next character
previous word
line fifty
word three

You can also specify that you’d like to go to the start or end of a selector:

end of next line

Change

The change command essentially combines the delete and type commands:

change <selector> to <text>

For instance:

change argument to null

Like the add command, change will not be executed automatically, so you’ll always have to follow up with a use command.

It turns out that you rarely change the variable styling with the change command, so the style defaults to the style of whatever was there before. For instance, suppose we had the following code:

def |fooBar():
    return 50

If we say:

change word to bar baz

That would produce:

def barBaz():
    return 50

The same variable styling was maintained, without you needing to specify camel case explicitly (though you can still specify a text style if you’d like).

Phrase

A powerful way to refer to a part of your code is to describe the exact text you want to select. You can do this with the phrase selector. For instance, suppose you have the following code and cursor position (designated with the | character):

|@app.route('/')
def helloWorld():
    return 'Hello, World!'

You could say:

go to end of phrase route

To move your cursor to:

@app.route|('/')
def helloWorld():
    return 'Hello, World!'

The phrase selector supports the same text formatting rules we’ve already seen. So, you could say:

change phrase camel case hello world to say

To produce:

@app.route|('/')
def say():
    return 'Hello, World!'

If you don’t include any text formatting rules, then phrase will match any text style. So, to achieve the same thing with fewer words, you could also say:

change phrase hello world to say

Lists, Indexes, and Ranges

All objects are part of some list, so you can narrow down the object you are referring to by using their place in the list. Consider the following code and cursor position:

import m

def calc(y):
     return y ** y

|def a(bing, bar, baz):
   return bar * baz + baz

If you say:

delete second parameter

That would result in:

import m

def calc(y):
     return y ** y

def a(bing, |baz):
   return bar * baz + baz

For some objects, their corresponding list is just every instance of that object in the file. Functions are one example—we could say:

delete first function

That would result in:

import m
|
def a(bing, baz):
   return bar * baz + baz

Or, we could say:

delete line two

That would result in:

import m
|def a(bing, baz):
   return bar * baz + baz

For other objects, we try to pick lists that are more useful. We saw this in our first example with parameter, where the corresponding list was the list of parameters to the function. For word, term, and character, the corresponding lists consist of the objects that share a line. For example:

delete second term

Would result in:

import m
def |baz):
   return bar * baz + baz

We ignore lists where our indexing doesn’t fit. For example, you could say:

delete word four

That would use the next line because there’s only three words on the current line:

import m
def a(bing, baz):
   return bar * baz |baz

Finally, you can also say a range, like:

delete words two to four

That would result in:

import m
def a(bing, baz):
   return +| baz

Closest Match, Next, and Previous

Descriptions of objects in commands may match multiple objects. We resolve this by picking the closest one. We judge closeness by looking and line distance, and then by character distance. For example, if you had the following code and cursor position:

def foo(bar, baz):
    return| bar * baz + baz

you could say:

go to phrase baz

That would put the cursor on the second baz:

def foo(bar, baz):
    return bar * |baz + baz

We measure distance by looking at the closest endpoint of the object. For example, suppose you had the following code:

if bar == baz:
    ret = 0
|   ret += baz
    if bar % 3 == 2:
      print(ret)

If you said:

delete if

Then the nested if would be deleted, beacuse the cursor is closer to the start of the inner if than the outer if. So, that command would produce:

if bar == baz:
    ret = 0
    ret += baz|

If we’re evenly between two objects, we break ties by picking the right one. For instance, if you said

copy word

For the below code, that would copy ():

str|()

You can use next and previous to talk about neighboring objects. For example, given the following code:

123 + baz + bar ** |456 + 78

You could say:

cut previous number

To cut 123. Or, you could say:

cut next number

To cut 78.

You can also select multiple objects with next and previous. Given that same code, you could say:

copy next two terms

To copy + 78. Similarly:

copy previous two terms

Would copy bar **. Finally, just:

copy two terms

Would copy 456 +, since your cursor is at the start of 456.

Finally, when your cursor is at the start of an object, you can drop next from commands like go to next .... For instance, go to word would have the same effect as go to next word, which would move the cursor to +. In fact, go to is optional; if you just say word, that will be interpreted as go to word. With this shortcut, it’s easier to skim a file. For example, if you’re trying to take a quick glance at each method in a file, you can keep saying:

method