Update: This is a possible duplicate of #601. Thanks to @mike.bronner for noticing it!
Hi! Another newbie here (oh, BTW, thanks for setting this Discourse forum up; I have just found it today by pure chance, and was actually going to email a request for such a forum… duh!); to make matters worse, I’m not a JavaScript programmer, either, so bear with me if nothing said below makes any sense…
I believe that this feature request hasn’t been raised, so here it goes: would it be possible to expose an API that allows easy conversion between the ranges used by the LSP, which are based on (line; character) pairs, and Nova’s own concept of a Range, which is based on a pair of Positions?
Here is the problem: for all the LSP functionality that Nova already implements, such conversion happens automatically. Let’s take a simple example: dealing with hovers. The specs show that one can receive from the server an optional range to identify where the hover appears. Nova correctly converts this LSP range into the internal range, and hovers appear where they are supposed to appear.
It’s also obvious that Nova can convert between the internal Position to the (Line, Column) display it in real-time on the footer; in fact, as far as I could figure out visually, the editor itself uses (Line, Column) for pretty much everything.
Nevertheless, we’re stuck with Range
s that are based on a linear representation of the position. Note that I’m agnostic about what solution is ‘best’ (especially when having to take into account Unicode characters); my issue is about the best way to convert one representation into the other.
Currently, I’m pretty much using @apexskier’s algorithm: to figure out the Line, Column based on a position, load the whole document into memory; split it by end-of-line; each line will obviously have different lengths, so add the length to a counter, and loop over all the lines while your position is less than the counter. Eventually, you’ll reach the line which contains the position, and doing a bit of arithmetic you’ll get the correct Line and Columns corresponding to the position.
Because Range
s have a pair of positions (start and end), to optimise the algorithm, @apexskier tracks two separate counters at the same time — one for the start position, another for the end position. This means that the algorithm is O(N) (where N is the number of lines, obviously) for all conversion requests, and, although you can break out of the loop as soon as both positions have been located, for very long files, this will only make a difference for the changes made at the top.
Unfortunately, as it happens, some Language Servers (this is the case of the Go Language Server, gopls
) will emit the TextEdit
’s in reverse order, that is, starting from the bottom of the file and slowly moving upwards (not required by the specs, but this guarantees that all operations will be applied consistently); in other words, the longer operations will be the first ones to be processed — and this very likely causes timeouts at some point.
A few quick & dirty, non-optimised experiments show that even a handful of operations (less than a dozen!) will take endlessly long to be processed (in human terms, that is: ‘a few seconds’). As far as I can figure it out, as each chunk gets processed, there is a redraw of all the text, which, although it cutely shows the text being automatically corrected, step by step, takes a bit too long. Almost all the time taken is by endlessly looping over and over the same document, which is highly inefficient.
Therefore, it would be awesome if the API could expose a simple way to convert Nova range positions into LSP Range
s (and vice-versa). After all, other functions (such as openFile()
or openNewTextDocument()
) already use line
and column
— not positions. Thus, it’s reasonable to admit that those internal conversion functions can be relatively easy to expose in the API.