Skip to content

vscode: redesign inlay hints to be capable of handling multiple editors for one source file #3378

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

Merged
merged 10 commits into from
Mar 7, 2020

Conversation

Veetaha
Copy link
Contributor

@Veetaha Veetaha commented Feb 29, 2020

Fixes: #3008 (inlay corruption with multiple editors for one file).
Fixes: #3319 (unnecessary requests for inlay hints when switching unrelated source files or output/log/console windows)
Also, I don't know how, but the problem described in #3057 doesn't appear for me anymore (maybe it was some fix on the server-side, idk), the inlay hints are displaying right away. Though the last time I checked this it was caused by the server returning an empty array of hints and responding with a very big latency, I am not sure that this redesign actually fixed #3057....

We didn't handle the case when one rust source file is open in multiple editors in vscode (e.g. by manually adding another editor for the same file or by opening an inline git diff view or just any kind of preview within the same file).

The git diff preview is actually quite special because it causes memory leaks in vscode (microsoft/vscode#91782). It is not removed from visibleEditors once it is closed. However, this bug doesn't affect the inlay hints anymore, because we don't issue a request and set inlay hints for each editor in isolation. Editors are grouped by their respective files and we issue requests only for files and then update all duplicate editors using the results (so we just update the decorations for already closed git diff preview read-only editors).

Also, note on a hack I had to use. vscode.TextEdtior identity is not actually public, its id field is not exposed to us. I created a dedicated upstream issue for this (microsoft/vscode#91788).

Regarding #3319: the newly designed hints client doesn't issue requests for type hints when switching the visible editors if it has them already cached (though it does rerender things anyway, but this could be optimized in future if so wanted).

Before

bug_demo

After

multi-cursor-replace

@Veetaha
Copy link
Contributor Author

Veetaha commented Feb 29, 2020

@matklad , @lnicola I still cannot add reviewers 😢

@Veetaha
Copy link
Contributor Author

Veetaha commented Feb 29, 2020

cc @SomeoneToIgnore

if (contentChanges.length === 0) return;
if (!isRustTextDocument(document)) return;

hintsUpdater.refreshRustDocument(document);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should refresh all visible editors: change to one file might affect hints in another file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! 995ad75

// FIXME: ctx.config may have not been refreshed at this point of time, i.e.
// it's on onDidChangeConfiguration() handler may've not executed yet
// (order of invokation is unspecified)
// To fix this we should expose an event emitter from our `Config` itself.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess a better approach is to just make ctx.config always reflect the most recent config? Ie, we should rid of config.onDidChange handler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we will get rid of cache

Copy link
Contributor Author

@Veetaha Veetaha Feb 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though, we won't get rid of config.onDidChange, since it triggerses (option requires reload) message and updates logging setEnabled(). Switching to event emitter for config is actually very simple, vscode exposes vscode.EventEmitter for this.

this.setParameterDecorations(editor, newParameterDecorations);
/**
* This class encapsulates a map of file uris to respective inlay hints
* request cancellation token source (cts) and an array of editors.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't read into this yet, but I think it should be possible to solve this without storing editors. I would think that the current queryHints logic is enough here, it should handle per-document in-flight requests correctly (modulo bugs).

It seems it should be enought to tweak refresh function logic such that it first groups editors by the document they are displaying, and then refreshes each group, without maintaing any additional state or datastructures.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state keeps track of editors with already rendered inlay hints (some cache to prevent requests when opening duplicate editors) and prevents interaction with disposed editors (see
this line) (vscode issues a warning for this otherwise).

Copy link
Contributor Author

@Veetaha Veetaha Feb 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it also prevents requests for inlay hints when the user browses unrelated files (i.e. #3319), windows (output/console/preview windows etc.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the following work as a way to get rid of the extra state here?

class RustSourceFile {
    constructor(
        public readonly document: vscode.TextDocument,
        public inlaysRequest: null | vscode.CancellationTokenSource = null,
        public decorations: null | {
            type: vscode.DecorationOptions[];
            param: vscode.DecorationOptions[];
        } = null
    ) { }

    get editors(): vscode.TextEditor[] {
        return vscode.window.visibleTextEditors.filter((it) => it.document.uri.toString == this.document.uri.toString)
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will remove the extra state, though we will looze the info about which editors have decorations applied and which don't. So removing the state like this we will still rerender the decorations while the user is browsing editors/outputs/terminal widnows, but we will at least have decorations cache in order not to issue requests for inlay hints to the lsp server.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can think of dirty hack where we add boolean properties to editors to put the state in them instead of our custom map.

Copy link
Contributor Author

@Veetaha Veetaha Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can think of dirty hack where we add boolean property areDecorationsApplied to editors to put the state in them instead of our custom map.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or better use a symbol. This will do work and maybe would be more efficient in terms of performance. However, the stability of a reference to the editor (i.e. pointer to it) is not documented by vscode, though in practice we can rely on this.

Copy link
Contributor Author

@Veetaha Veetaha Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the following example will make this more clear:

const IS_DECORATED = Symbol("inlay hints decorations");

get editorsToRender() {
    return vscode.window.visibleTextEditrs.fitler(
        suspect => !suspect[IS_DECORATED] && <uris equal>...
    );
}

rerender(editor) {
    editor.setDecorations(...);
    editor[IS_DECORATED] = true;
}

@matklad
Copy link
Member

matklad commented Mar 2, 2020 via email

@Veetaha
Copy link
Contributor Author

Veetaha commented Mar 2, 2020

It's "bad" in terms of rerendering inlays decorations when not modifying the code, but it's "good" in terms of not storing the state and, anyway, inlay hints are updated on each character typed, so rerendering them on browsing unrelated editors would be shadowed by this fact.
I wish vscode guys took over the implementation themselves for us here, so that they could access their private APIs and this state could already be accessible by them...

@matklad
Copy link
Member

matklad commented Mar 2, 2020 via email

@Veetaha
Copy link
Contributor Author

Veetaha commented Mar 2, 2020

Don't forget that the extension communicates with the editor via IPC too, anyway. Let me try to simplify this with a boolean-per-editor and if you don't like it we will remove the state at all...

@matklad
Copy link
Member

matklad commented Mar 3, 2020

Let me try to simplify this with a boolean-per-editor and if you don't like it we will remove the state at all...

I pretty strongly feel that any state management is premature optimization, and a non-insignificant amount of complexity.

bors bot added a commit that referenced this pull request Mar 3, 2020
3388: Remove inlay hint in diff views r=matklad a=vbfox

If the left side of a diff view that contain the old version of the file apply inlays they are misplaced and produce a weird display:

![image](https://user-images.githubusercontent.com/131878/75628802-b1ac1900-5bdc-11ea-8c26-6722d8e38371.png)

After the change:

![image](https://user-images.githubusercontent.com/131878/75628831-e91ac580-5bdc-11ea-9039-c6b4ffbdb2be.png)

The detection is done by blacklisting the url schemes used by git and subversion scm extensions, whitelisting `file` is also possible but neither is perfect as VSCode now support both pluggable scm extensions and pluggable remote filesystems. But I suspect that the list of scm extensions is more easily manageable.

**Note**: I can rebase on #3378 if needed as it touches the same lines of code

Co-authored-by: Julien Roncaglia <julien@roncaglia.fr>
@kjeremy
Copy link
Contributor

kjeremy commented Mar 3, 2020

@Veetaha this may be off topic (I haven't really looked at how this feature works) but have you looked at CodeLensProvider and friends?

My motivation is something "easily digestible" for #2797

@Veetaha
Copy link
Contributor Author

Veetaha commented Mar 3, 2020

@kjeremy, haven't looked into it. I am not sure I caught your intent, do you mean we should make changes to the LSP server side first before merging this PR?

@kjeremy
Copy link
Contributor

kjeremy commented Mar 3, 2020

@Veetaha what I mean is that the code lense feature is similar to our inlay hints feature and already supported by vscode. We should see how the client side typescript solves the cases that we also need to solve.

caveat: I'm not terribly familiar with the ts side of inlay hints so could be completely off the mark :)

@Veetaha Veetaha force-pushed the fix/inlay-change-revert-no-updated-2 branch from 995ad75 to ef52fd5 Compare March 7, 2020 12:08
@Veetaha Veetaha requested a review from matklad March 7, 2020 12:09
@matklad
Copy link
Member

matklad commented Mar 7, 2020

r=me

@matklad
Copy link
Member

matklad commented Mar 7, 2020

bors d+

@bors
Copy link
Contributor

bors bot commented Mar 7, 2020

✌️ Veetaha can now approve this pull request. To approve and merge a pull request, simply reply with bors r+. More detailed instructions are available here.

@Veetaha
Copy link
Contributor Author

Veetaha commented Mar 7, 2020

@kjeremy I've looked at CodeLens implementation and it uses codelens LRU cache along with registering the CodelensController (in our analogy this is HintsUpdater) using the private api. So that each instance of the codelens controller is attached to a single editor and it has access to its full lifecycle (i.e. it is disposed along with closing the editor), whilst we have to bookeep the opened editors.

@Veetaha
Copy link
Contributor Author

Veetaha commented Mar 7, 2020

Also, @matklad, please note that the first request seems to get an invalid (maybe partial) inlay hits response. Is this the expected behaviour? (I also find myself re-setting up those debug logs in the following demo a lot, but I guess we should remove them anyway)
demo

@Veetaha
Copy link
Contributor Author

Veetaha commented Mar 7, 2020

I'll merge this because partial inlay hints results at bootstrap are definitely another issue that is server-side.
bors r+

@bors
Copy link
Contributor

bors bot commented Mar 7, 2020

@bors bors bot merged commit aff82cf into rust-lang:master Mar 7, 2020
@Veetaha Veetaha deleted the fix/inlay-change-revert-no-updated-2 branch March 7, 2020 12:57
@kjeremy
Copy link
Contributor

kjeremy commented Mar 7, 2020

@Veetaha I really appreciate you looking at how codelens works and why we can't go that route. I wonder if we can file an issue to open up the relevant API. It may be useful to others and when we upstream this. Would that have made this work easier?

@Veetaha
Copy link
Contributor Author

Veetaha commented Mar 7, 2020

It's actually hard to tell, there are some more topics I'd need to go through like viewport optimization and the universal shape of inlays data.
I have an idea of fetching inlays for a whole array of visible files in one request (since we currently issue a request per source file), but it does have its own disadvantages (like we can't update inlays asynchronously independent on other visible source files, though I think this won't be perceivable due to small amount of visible files on average anyway).

However, inlays do resemble codelens to some degree (except there is no need to actually resolve an inlay hint), so some of their design may be used for them. I don't fill confident enough in LSP, but I might try to think of an api proposal once I get to it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants