Skip to content

Improved tooltip and member doc display and other fixes #4024

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 16 commits into from
Apr 12, 2018
Merged

Improved tooltip and member doc display and other fixes #4024

merged 16 commits into from
Apr 12, 2018

Conversation

MikhailArkhipov
Copy link

@MikhailArkhipov MikhailArkhipov commented Apr 9, 2018

Fixes #3849
Fixes #3869
Fixes #3882
Fixes #3977
Fixes #4031

  • Improves startup time in VS Code via async startup mode
  • Option not to trim/wrap text in tooltips
  • Remove line breaks in docs letting host to decide on flow/wrap
  • Somewhat reworked tooltip and completion member display (less duplication, closer to PEP style)
  • Added doc extraction for modules


var displayText = result.ToString();
if (displayOptions.trimDocumentationText && displayText.Length > displayOptions.maxDocumentationTextLength) {
displayText = displayText.Substring(0,
Copy link
Contributor

Choose a reason for hiding this comment

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

You can use result.Remove to avoid allocation of a displayText

// Return module information
var contents = "{0} : module".FormatUI(modRef.Name);
if (!string.IsNullOrEmpty(modRef.Module?.Documentation)) {
contents += $"{Environment.NewLine}{Environment.NewLine}{modRef.Module.Documentation}";
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need FormatUI here as well?

Copy link
Author

Choose a reason for hiding this comment

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

It just does CurrentCulture... I am actually not sure that current culture is right. Here we should have invariant I believe. I.e. if we have programming language docs then numbers must remain 5.0 and should not be displayed as 5,0 in a culture-specific manner.

_analyzer = CreateAnalyzer(@params.initializationOptions.interpreter).Result;
_testEnvironment = @params.initializationOptions.testEnvironment;
// Test environment needs predictable initialization.
if (@params.initializationOptions.asyncStartup && !_testEnvironment) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need testEnvironment if we already have asyncStartup?

Copy link
Author

Choose a reason for hiding this comment

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

There are effectively 3 modes

  1. Test mode, all sync
  2. VS mode, as it used to work since I am not 100% confident VS part can handle all-async as I don't know all the entry points.
  3. VSC mode to minimize delays. VSC only need synchronization at the LS entry points. This is what asyncStartup controls.

VSC passes true for asyncStartup and true/false for tests. asyncStartup == false does not make it completely sync, VS still uses awaits in places. VSC creates analyzer async while VS does not.

if VS only uses LS entry points then we can just do (3). But bunch of methods are internal with visibility so I don't know.

@@ -168,6 +196,8 @@ public sealed class Server : ServerBase, IDisposable {
}

public override void DidChangeTextDocument(DidChangeTextDocumentParams @params) {
_analyzerCreationTcs.Task.Wait();
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we ever call this from UI thread?

Copy link
Author

@MikhailArkhipov MikhailArkhipov Apr 10, 2018

Choose a reason for hiding this comment

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

It is out-of-proc so technically there is no UI thread. OutOfProcProjectAnalyzer calls it in UpdateContent. In VS, however, this task completes in initialization so only VS Code may hit the wait.

@@ -144,6 +171,7 @@ public sealed class Server : ServerBase, IDisposable {

public override async Task DidOpenTextDocument(DidOpenTextDocumentParams @params) {
TraceMessage($"Opening document {@params.textDocument.uri}");
await _analyzerCreationTcs.Task;
Copy link
Contributor

Choose a reason for hiding this comment

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

If there was an exception CreateAnalyzer, _analyzerCreationTcs will hold that exception. Is it correct to raise it every time API method is called?

Copy link
Author

Choose a reason for hiding this comment

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

If analyzer creation failed, then the whole thing is broken. VSC restarts server automatically up to 3 times I believe.

} else {
_analyzer = await CreateAnalyzer(@params.initializationOptions.interpreter);
OnAnalyzerCreated(@params);
_analyzerCreationTcs.TrySetResult(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Behavior here is different from when @params.initializationOptions.asyncStartup && !_testEnvironment is true. If CreateAnalyzer raises an exception, _analyzerCreationTcs will not complete. Is it by design?

Copy link
Author

Choose a reason for hiding this comment

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

Let me try-catch that. Although if it throws, nothing is going to work, but at least exception may give client an idea that server may need to be restarted.

if (_analyzer == null) {
LogMessage(MessageType.Error, "change configuration notification sent to uninitialized server");
return;
}

await _analyzer.ReloadModulesAsync();
_queue.Enqueue(new AnalysisQueueWorkItem(() => _analyzer.ReloadModulesAsync().WaitAndUnwrapExceptions()), AnalysisPriority.Normal);
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks dangerous.

  • _queue.Enqueue won't execute ReloadModulesAsync immediately. If there is any code that awaits DidChangeConfiguration to ensure that modules are reloaded, it will be broken
  • AnalysisQueue will reorder project entries, but it won't reorder AnalysisQueueWorkItem cause they all are considered different. This is good in the way that ProjectEntry.Analyze will be called always after ReloadModulesAsync, but has a side effect that several concurrent calls of ReloadModulesAsync will be executed one by one instead of swallowing all but first and last call.
  • ReloadModulesAsync may start new tasks and await on them while AnalysisQueue is thread-bound. AnalysisSynchronizationContext doesn't support reentrancy and AnalysisQueue thread is blocked until method is completed, which means that if anywhere on the code path we have missed ConfigureAwait(false) or provide any other way for AnalysisSynchronizationContext to be captured, we'll get a deadlock.

Also, ReloadModulesAsync should handle CancellationToken by itself, but that is a separate issue.

Copy link
Author

Choose a reason for hiding this comment

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

Well, I am open to suggestions. Currently it is broken b/c ReloadModulesAsync is called from two threads while

        /// This method should be called on the analysis thread and is usually invoked
        /// when the interpreter signals that it's modules have changed.

So basically change is to queue item before calling _queue.Enqueue in DidChangeConfiguration. Nothing awaits on DidChangeConfiguration in reality, it is out of proc call, we can make it void.

ReloadModulesAsync should never be called in parallel AFAIU

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, I am open to suggestions

Let me think a bit :)

Copy link
Author

Choose a reason for hiding this comment

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

I think analyzer should probably handle reload internally in its worker thread rather than caller know what is permitted and what not. This will be a bigger change.

Copy link
Author

Choose a reason for hiding this comment

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

If it is queued, then _reloadLock.WaitAsync() prob not needed as there are no concurrent calls (apart of one at the creation at CreateAsync. I think this one can be queued too theoretically.

Now, we can also avoid doing anything in DidChangeConfiguration. Changing interpreter will actually restart the server.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, here is a version of small fix: AlexanderSher@bf068bd

  • DidChangeConfiguration will wait for last ReloadModulesAsync to be processed
  • AnalysisQueue will properly reorder ReloadModulesAsync call if it is already scheduled
  • ReloadModulesAsync will be started in thread-pool so reentrancy shouldn't be a problem.

Copy link
Contributor

@AlexanderSher AlexanderSher left a comment

Choose a reason for hiding this comment

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

@huguesv , do you have any comments?

@huguesv
Copy link
Contributor

huguesv commented Apr 11, 2018

Is there enough test coverage for the documentation changes? Aside from spaces removed around operators, there doesn't seem to be much changed.

@MikhailArkhipov
Copy link
Author

Yes, I updated tests. Need to add tests for the new functionality (tooltips and docs for modules, see #4050).

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