Sublime Forum

How to apply an operation line by line to an entire view

#1

Hi,

I’d like to apply the following operation on all lines of a view:

  1. Read the line,
  2. Apply a function my_function to this line,
  3. Replace the line.

It seems I need to select the whole document as a region, then get all lines. The following code is a starting point.

import sublime
import sublime_plugin

class ExampleCommand(sublime_plugin.TextCommand):

    def run(self, edit):

        # Get the whole document as a region.
        region = sublime.Region()

        # Get a list of all lines
        lines = self.view.lines(region)

        for line in lines:
            # Here, line should be a region, right ? How to get the content ?
            out_line = my_function(line)
            self.view.replace(edit, line, string)

Yet, I’ve some points to check:

  • How to select the whole document as a region ?
  • The lines method seems to return a list of regions. How to get the content ?
  • Does this modification affects the cursor position ?

Thanks for your help :slight_smile:
This is my second post in 1 day, but it should be the last one for a long time …

0 Likes

#2

A region is something that represents an area of the buffer, so it has a defined start and end point. Each end point in the buffer represents a point, which is the character offset in the file. So the first character in the file is 0, the second character is 1, and so on through the whole file.

The sublime.Region() constructor looks like this:

So, if you provide it a single value, like sublime.Region(0), you end up with a region that starts at the first character and ends at the first character; such a region is what we’d refer to as empty.

Otherwise, you pass it two points and the region spans between them. The first character in the file is always 0, so to get the end point you need to know how many characters there are in the file (because a point is a character position). view.size() returns the number of characters in the file.

So to create a region that covers the whole file:

region = sublime.Region(0, self.view.size())

For this you want the substr method:

image

You can pass this either a single point to get a single character, or a region to get the whole thing as a string.

So for example to get the content of the entire file as a string (assuming the region from above):

file_content = self.view.substr(region)

Generally, Sublime seems to try to keep the position of the cursors as close as possible to what they were before the change. Whether or not the cursor location is actually changing sort of depends on your point of view and on the modification that you make though.

For example, if the cursor is at the end of a line and your modification makes that line shorter, the cursor is still at the end of the line. So on the one hand, the position hasn’t changed (still the end of the line) while on the other hand, since the line got shorter the position is now closer to the start of the file, so in that sense it did change.

You can also see something like that in play if you use the default plugin stub that inserts Hello World into the buffer. If you do it, the text appears, but the cursor will still visibly be at the same position it previously was (unless it’s somewhere in the first line of the file, in which case it gets moved over because text was inserted).

Just to save you from a potential calamity that befalls many when working with regions and modifications (including those in the know who sometimes forget about it), what you’re trying to do above won’t work the way you’re trying to do it. Or rather, it won’t do what you expect.

Without making this answer tediously long (more so), the code above gathers the regions that represent each line, but then it modifies the file which also inherently changes the regions that represent each line, which makes your regions wrong.

For example if the first line used to be 1 character long but after the modification it’s 2 characters long, every other line in the file is now starting at 1 position farther along, so when you try to grab the content of line 2 you’re not getting the text you expect, and the problem compounds as you go forward.

There are a variety of ways around that, but the easiest fix is to make your for loop modify the file from the bottom-up instead; that way as you process each line, the content for it is still at the same point it was when you created the regions:

for line in reversed(lines):

If you’re interested in answers that are tediously long, this video contains a segment of a recent live stream that shows another way to do this as well. The live stream that the clip comes from continues past where this one cuts off to show other ways to do the same thing as well in a more targeted way along with some other plugin development stuff (plus, because live stream, dumb bugs).

0 Likes