not2qubit
not2qubit

Reputation: 17017

Where does sublime store the last cursor position of a previosuly opened file?

I would like to export a list of last opened files (tabs) and the last cursor position for each file, in Sublime editor. I can easily parse the session file called Session.sublime_session located in the installation sub-path: <install path>/Data/Local/ (in Windows), to get the file names. But looking in that file, there are no obvious line numbers to be found. Or did I miss something?

Where is the last cursor position stored?


UPDATE

I now see that it is stored in the JSON field called selection, and is counted by number of characters into the buffer.

...
"selection":
    [
        [
            4353,
            4353
        ]
    ],
...

So the question now become, how can I calculate the line number from this?

Perhaps by writing a regex that counts EOL's (\ns) after reading in X number of bytes. (What if were using different EOL's or usinf UTF-8 vs ASCII?)

Upvotes: 0

Views: 178

Answers (2)

not2qubit
not2qubit

Reputation: 17017

With the great help from the answers in this Unix SE question, I managed to patch together something that works pretty good. But it depends on both jq (for windows) and (Cygwin) Bash.

jq-win64.exe -r '.windows[]|.groups[].sheets[]| "\(.file):\(.settings.selection[0][0])"' Session.sublime_session |sort | sed 's/^\/./\/cygdrive\L&\E/'

I then used this as a basis to extract the number of EOL's and used that to determine the exact cursor line number for each file.

Hint: use head -c <number> <file> | wc -l.

Upvotes: 0

OdatNurd
OdatNurd

Reputation: 22791

In Sublime, selections are represented as a list of instances of the Region class, which is essentially a pair of file offsets into the file. In particular, each number is a number of characters since the start of the file, so the first character is at position 0, the second is at position 1, and so on.

The range of the selection always runs from the first value to the second value; if both values are the same then the selection is just a regular caret (i.e. no visible selection); otherwise it's the span of characters that the selection covers. It's also possible for the second number to be smaller than the first number, which means that the selection is "backwards". In all cases the second value is the place where the caret is currently sitting for that selection.

The important distinction here is that the offset is in characters; regardless of whether the encoding of the underlying text is a single byte per character, two bytes per character or even something like UTF-8 where some characters require more than one byte to encode, the position is always represented in characters.

The other important thing to keep in mind is that there are different styles of line termination that can exist; in particular Linux uses a line feed while Windows uses a combination of carriage return + line feed (2 characters). Regardless of the line terminators in the file, Sublime always normalizes to a single character per line terminator for consistency (so plugins don't need to care, for example).

So, in order to turn a character position into a row/column, you need to know the encoding of the file and the line terminator type so that you can correctly interpret the characters, and then count characters in the source data one by one, incrementing the line number by 1 (and resetting the column to 0) every time you run into a line terminator.

This is what the view.rowcol() method in the API does, generally speaking; the internal implementation most likely takes advantage of having precomputed line information based on the structure of the data in memory, for example.

Information on all of the classes referenced above can be found in the Sublime API Documentation

Upvotes: 1

Related Questions