Integrate Piece Tree -> IncrementalTextDocument | TextDocuments Incremental Document Support#478
Conversation
server/src/main.ts
Outdated
| let updatedDoc: UpdateableDocument = edits.reduce( | ||
| (workingTextDoc: UpdateableDocument, edit) => { | ||
| let newText = TextDocument.applyEdits(workingTextDoc, [edit]); | ||
| workingTextDoc.update({ text: newText }, version); |
There was a problem hiding this comment.
Currently every update on the TextDocument will flush the _lineOffset
while applyEdits relies on _lineOffset https://github.com/Microsoft/vscode-languageserver-node/blob/9de8809400b7c443bc25f211dfaddcb74675b2d7/types/src/main.ts#L2500-L2501
It means for every content change we try to apply, it will go through the whole document, character by character, to find all line breaks. In real world scenarios it can be really costly.
There was a problem hiding this comment.
Yup, I definitely observed this limitation.
These black-box functions (applyEdits / update) are the only ways to currently update text documents. As we discussed in #476, I can use your npm module in this PR and swap out the implementations for these core functions as well, so that these calls don't rescan the entire document.
Apologies, when I originally wrote this up, I wasn't aware that you had already published the package to npm.
There was a problem hiding this comment.
No worries and you are doing a great job here. I'm just a bit surprised to see that lineOffsets were being calculated every time when we do full sync (the good part is we calculate only when reading line info).
Based on your proposal and this PR, I think we can see two major performance improvement
- We don't need to send full document from client to server anymore when the sync mode is incremental
- The line offset calculation is incremental when the sync mode is incremental, which is actually the major time spent in loading a file in VS Code.
Let me know if you need any help from me, I can push directly to your PR with piece tree related changes (if you are okay with that).
There was a problem hiding this comment.
If you've made some sort of local progress on adding your piece tree as a dependency and using it, then feel free to push it to this PR. Otherwise, I was planning on adding it as a dependency + using it in FullTextDocument / TextDocument namespace today.
Feel free to let me know, and we can proceed from there.
There was a problem hiding this comment.
@Jma353 go for it, I don't have anything yet, look forward to your change.
There was a problem hiding this comment.
For sure - I'll update this today or tomorrow.
types/src/main.ts
Outdated
|
|
||
| // Prepare Piece Tree | ||
| const ptBuilder = new PieceTreeTextBufferBuilder(); | ||
| ptBuilder.acceptChunk(content); |
There was a problem hiding this comment.
We don't need to. If we do simple slice, then there is no difference as SlicedString in v8 is just a mapping to part of the original string. If we create copies from that, the initialization will be costly.
It would be great that if the document data we get from client comes as chunks instead of a single string, and then we can benefit from that.
… TextDocument#create docstring
|
@dbaeumer given that I add piece tree |
| @@ -2611,6 +2616,15 @@ export interface TextDocumentContentChangeEvent { | |||
| text: string; | |||
There was a problem hiding this comment.
Should we update the comments for text? In FullSync mode, text is always the content of the full document from client side but now in incremntal mode, it's the change for the corresponding range. Another thing we need to make sure that Client sends only content change but not full text in incremtnal mode.
There was a problem hiding this comment.
It's already guaranteed that when it's incremental, it sends the change event but not the full doc. So we only need to update the documentation.
There was a problem hiding this comment.
Will update the documentation in the next commit to the PR :)
|
@Jma353 the code looks good to me and thanks for your help! I'll love to hear feedback from others as well, considering some of them are in vacation, I think you can push changes and even better do some benchmark. |
|
@rebornix I'll submit doc change + will setup a lightweight benchmark this weekend. In general, I feel as though an explicit win of this is just the idea that we can support incremental sync-ing in the first place. Sending full files between processes isn't cheap. |
types/src/main.ts
Outdated
| */ | ||
| export function create(uri: string, languageId: string, version: number, content: string): TextDocument { | ||
| return new FullTextDocument(uri, languageId, version, content); | ||
| export function create(uri: string, languageId: string, version: number, content: string, isFullSync: boolean = true): TextDocument { |
There was a problem hiding this comment.
Can we make isFullSync a enum or string or type. It more feels like a enum in case we have another sync expierence later on.
There was a problem hiding this comment.
The boolean looks strange, and the return type is always TextDocument. It's unclear to an API user when to set isFullSync and when not.
What about defining a separate constructor?
When doing that I would also officially introduce the UpdatableTextDocument. The default implementation of a UpdatableTextDocument should be the new piece tree document as it is optimised for updates.
export interface UpdatableTextDocument extends TextDocument {
update(event: TextDocumentContentChangeEvent, version: number)
}
export namespace UpdatableTextDocument {
export function create(uri: string, languageId: string, version: number, content: string) : UpdatableTextDocument {
return new IncrementalTextDocument(uri, languageId, version, content);
}
}
|
Other than the above comment things look good to me. @aeschli do you have any input / comments? |
|
My comment is about The When doing that I would also officially introduce the export interface UpdatableTextDocument extends TextDocument {
update(event: TextDocumentContentChangeEvent, version: number)
}
export namespace UpdatableTextDocument {
export function create(uri: string, languageId: string, version: number, content: string) : UpdatableTextDocument {
return new IncrementalTextDocument(uri, languageId, version, content);
}
}
|
|
I though about this again and I am in favour of making this more explicit. However I don't like the name @Jma353 are you willing to do these changes? |
|
I prefer |
|
These changes seem reasonable. I appreciate the feedback all! Apologies for the slowness in my response (been busy with some other things). I will prioritize iterating on this by EOW. |
|
@Jma353 @rebornix Me and Dirk had some more discussion and came up with one more suggestion, which has the smallest impact on the API:
Sorry for the back and forth. If you are very busy then I'm happy to merge and make the changes. Just let me know. |
|
I can change these things @aeschli - thanks for the thoughtful feedback. Expect an update with said changes by Friday, this way you don't have to do any additional refinements on your part. |
|
@aeschli did you mean
I can remove changes from |
|
I have updated the PR according to feedback, to limit API changes. Here is a summary:
|
|
Considering @Jma353 's great effort is going to be merged into the project, I'll add some scripts to VS Code in June to ship piece tree directly from there instead of having my own repository, similar to how we ship Monaco. |
|
@rebornix as in have piece tree available via A.k.a. |
|
@Jma353 I didn't go that route finally but aligned with how we ship update vscode-textbuffer is public now https://github.com/microsoft/vscode-textbuffer |
|
@Jma353 I am correct that you still need to adopt the new |
|
I replaced |
|
@rebornix I see travis failure relating to the new module. Thanks for swapping out npm modules btw :) |
|
@Jma353 thanks it's green green now. |
This pull request is the result of a dialogue in #476.
This PR does 2 things:
vscode-piece-treenpmmodule. I use this module to implementIncrementalTextDocument, which is the Incremental sync document representation (asFullTextDocumentis the Full sync document representation). This is a class internal toservermodule.TextDocumentsdocument manager to supportIncrementaltext synchronization mode. This manager defaults toFull, but has an optional constructor parameter that allows the consumer of the API to specifyIncrementaldocument sync. WhenIncrementaldocument sync is specified, this document manager passesisFullSync = falsetoTextDocument#create, which results inIncrementalTextDocumentbeing used per document.This PR also bootstraps a test suite for
TextDocuments(specifically involvingonChangeevents) + a test suite forIncrementalTextDocumentviaTextDocumentsdocument manager.Output of
servertests: