-
Notifications
You must be signed in to change notification settings - Fork 842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make it clearer the responsibilities/gurantees for orders of multiple text edits #888
Comments
Actually no. For content changes the order is important and it is not in
|
Oh sorry, I misunderstood. My understanding was that in VS Code the changes all use offsets from the original state, and it doesn't look like the LSP client changes them in any way. Does this mean VS Code is also intended to guarantee edits can be applied sequentially? (If so, it doesn't seem to be documented, and microsoft/vscode#11487 (comment) suggests it's not guaranteed). |
IMO this is clearly stated with:
|
I read that as the separate events, rather than the items within the array of a single event. However, even so, it does not state guarantees around the ordering or semantics of the offsets. If the items in the array do come through in offset-descending order (as they seem to, and the bug fix above prevents other extensions messing with) then applying them in-order is unambigious. If they come through in offset-ascending order (I see nothing to say that they cannot) then it is ambigious (can they be applied sequentially, or do all offsets refer to the original state?). Right now I've added code to sort the edits in offset-descending order before applying sequentially because it's not clear (I appreciate this isn't an LSP question, it's VS Code API, though my requests for clarification there keep going unanswered and this seems kinda important to clarify). |
I just found microsoft/vscode#23173 (comment), which seems to state that they can be applied sequentially. This seems different to what I was told in the past :-( I don't know why this isn't explicit in the docs though. New implementors don't read through all GH issues, they just look at the API docs. |
There is no need to sort something since every event and every change in the text document content changes move the document exactly one state forward. Sorting is only necessary if an event or edit has n edits and moves the state of a document exactly one state forward (although n edits are applied). I always thought that this is clear describing it as state changes but I can add a sentence as well that the event should be applied in the order they are received. |
@dbaeumer I'm just a bit confused because I've previously had responses that seem to differ in VS Code, so it's not clear what the rules are where. For example: This came from an edit coming the other way - the language server provided edits to be applied sequentially (and each offset took into account the previous edit), but VS Code interpreted all offsets as being from the initial state. The responses there made me believe that VS Code worked with non-sequential edits, for ex:
This led me to believe that VS Code may also send me back edits using this assumption (and therefore it's not safe to just apply sequentially) but now I'm still not sure. |
The reason for this is to keep the API simple for clients. In both cases (events and edits) clients don't need to sort anything. For events clients simply need to apply the changes in the order they appear, for edits the client / tools takes care of doing the right ordering. The provider of a text edits (e.g the extension or server) can provide them in any order. To achieve this a single event change therefore describes the document transformation from S' to S''. In contrast for edits n edits describe the transformation from S' to S''. |
@dbaeumer I'm still confused. What's written in the issue I linked above doesn't seem to match what you're saying. I think what you're saying makes sense, but the edits I have to send to VS Code are not such that they could be applied in-sequence. VS Code requires the builder of the edits to provide offsets based on the initial state, not the state before each change. const changes = new vs.WorkspaceEdit();
changes.replace(document.uri, new vs.Range(new vs.Position(0, 0), new vs.Position(0, 0)), "LINE 1\n");
changes.replace(document.uri, new vs.Range(new vs.Position(1, 0), new vs.Position(1, 0)), "LINE 2\n");
// This code does *not* insert at line 3, it inserts at line 5 because of the two newlines inserted above.
changes.replace(document.uri, new vs.Range(new vs.Position(2, 0), new vs.Position(2, 0)), "LINE 3\n");
await vs.workspace.applyEdit(changes); This seems different to what you're describing here. Sure, this is a different part of the API and it's ok for it to behave differently - but that's where the confusion comes from if the specs are not explicit. |
Actually the LSP and the VS Code API have the same constraints when it comes to events and text edits. And what you are seeing is expected. The thing is that you could even added the changes in reverse order and the outcome would still be the same. As you are mentioning that doesn't mean that your edit at position p is in the final document at position p. Consider you have a line
then you end up with Side note: in Eclipse (which uses the same model) you can track edit positions if you want to know at which positions in state S' the edit ended up. But VS Code doesn't have this support. |
@dbaeumer I'm getting more confused :-)
Isn't this different to what's described above? These edits are not the same as if they are applied sequentially, in-order (if they were, then LINE3 would be inserted at line 3, not line 5).
This sounds a bit like something I requested at microsoft/vscode#54147. Right now I have my own implementation of it (which subscribes to edits and translates the tracked positions). |
The sequence in which you provide the edits is irrelevant. They are all described on state S. const changes = new vs.WorkspaceEdit();
changes.replace(document.uri, new vs.Range(new vs.Position(0, 0), new vs.Position(0, 0)), "LINE 1\n");
changes.replace(document.uri, new vs.Range(new vs.Position(1, 0), new vs.Position(1, 0)), "LINE 2\n");
// This code does *not* insert at line 3, it inserts at line 5 because of the two newlines inserted above.
changes.replace(document.uri, new vs.Range(new vs.Position(2, 0), new vs.Position(2, 0)), "LINE 3\n");
await vs.workspace.applyEdit(changes); and this const changes = new vs.WorkspaceEdit();
changes.replace(document.uri, new vs.Range(new vs.Position(2, 0), new vs.Position(2, 0)), "LINE 3\n");
changes.replace(document.uri, new vs.Range(new vs.Position(1, 0), new vs.Position(1, 0)), "LINE 2\n");
changes.replace(document.uri, new vs.Range(new vs.Position(0, 0), new vs.Position(0, 0)), "LINE 1\n");
await vs.workspace.applyEdit(changes); produce the same result. This is done since these edits are usually generated traversing tree (sometime even to independent walkers) and require that they sequence things will make this very complicated. The only exception to this rule is insertion at the same position. Here the first in the array wins. But as said and you experienced inserting something at line two might not end on line two if you manipulate text before line two. Same for thing on a specific line. |
Right - that's my point. For the
My point is that it's confusing that these are different - they are both descriptions of edits to a document but for one the offsets are all based on the original state and the other they're based on the state after applying the previous edits. The docs do not make it very clear where order is/is not important. When two very similar things behave differently, I think it's important for the specs to be absolutely clear. |
The reason for the difference is to make live easy for clients.
I tried to make this clear by describing the state changes that happen. But I will add a sentence on how to apply the changes. |
Right - I'm not questioning the reason - just explaining why I think it's confusing and the docs should be explicit. People building extensions will likely just read the API docs (and not all GH issues), so they may have the same confusion I have (or worse, just implement things wrong and then have puzzling bug reports from users in the future). I just think the specs should be as explicit as possible, including calling out anything that might be incorrectly assumed. Tracking down bugs in these sorts of things can be time consuming. |
I was kind of struggling with the interpretation of text edits as well before reading this thread. I think the spec could better motivate the differences between workspace edits and content change edits, or encode the rule you've just mentioned @dbaeumer in your last comment. I think it's a good decision to model this in such a way that it makes life easier for clients. One thing that remains unclear to me is why clients have a better time sending the content changes events with offsets referring to the original document. Is it because several changes might fire up within some timeframe and then be batched and sent to the language server? Otherwise, I would expect clients to process user changes sequentially and with offsets depending on the last applied changes within the same event. |
My guess is that this usually occurs with multi-cursor-edits, so if the user has three cursors and types "a" it's trivial to make three edits that are at those three offsets that insert "a". Always sending the edits in reverse-descending order would mean it doesn't matter so it's easier on everyone - and that's what I think VS Code intends to do (based on the fix for microsoft/vscode#88310), but since it doesn't documented I'm not sure we should be relying on it. I don't know why both the VS Code API and the LSP specs don't describe this in explicit detail, it's a frustrating sort of bug to track down, and when you do track it down, it's not very clear where the bug lies. |
@DanTup That explanation makes sense, though I've just re-read the whole thing again and realized that my question is wrong: content changes don't contain offsets referring to the original document, it's only text edits coming from |
Yep, though those edits may have been generated with data from a server. In the Dart extension even today we still have to check edits from the server to see if they're in reverse order. If they're not (which occurs for a few types of edits) we apply them sequentially with separate edits, which means the undo buffer becomes multi-step. I only discovered this after much troubleshooting - which is exactly why I think the specs should be more explicit and helpful, to avoid others having similar hard-to-track-down bugs in future (spec updates are way cheaper than diagnosing intermiddent bugs like this). |
For text edits the spec already contains:
I added the following for content changes:
I want to point out one additional thing: the spec intentionally leaves it open how a client applies text edits since this highly depends on the internal data structure used by the client. One easy approach is of course to reverse sort them and then apply them. |
I still think this should be explicit in pointing out that offsets for all edits are relative to the original document, such as: "A FWIW, I was mostly complaining about the VS Code API docs here: https://code.visualstudio.com/api/references/vscode-api#TextDocumentChangeEvent It has no such text, and even now I am still unclear what the rules around its edits are. Is ordering important? If not, why was the recent fix made to stop extensions reversing the order?
Similarly, I think this text should also appear on the
Also, since this differs from the above and the
|
This was discussed a little in #279, including this comment from @dbaeumer:
My understanding of these three is that the client must send these edits in offset-descending order (despite the fact that semantically the order should not matter, because the offsets are based on the original document). However I don't think this is clear from the spec:
It's possible this says the same thing, but it's not very clear to me (so it might not be to some other devs). I think it should be very explicit whether it is required for the client to send the edits in reverse-offset order (or if they're not, that a server should handle them in the wrong order).
VS Code had a bug around this (microsoft/vscode#88570) where some extensions were mutating the edits and they arrived to other extensions in the wrong order (I would presume that the Node LSP client was also effected unless it's manually sorting the edits before sending) and it's still not clear where the responsibilities lie (I asked for clarification, but the issue was just closed). I think the less ambiguity in the specs, the less likely bugs like this will crop up.
The text was updated successfully, but these errors were encountered: