-
Notifications
You must be signed in to change notification settings - Fork 675
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
Conversation
|
||
var displayText = result.ToString(); | ||
if (displayOptions.trimDocumentationText && displayText.Length > displayOptions.maxDocumentationTextLength) { | ||
displayText = displayText.Substring(0, |
There was a problem hiding this comment.
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}"; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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
- Test mode, all sync
- 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.
- 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(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 executeReloadModulesAsync
immediately. If there is any code that awaitsDidChangeConfiguration
to ensure that modules are reloaded, it will be brokenAnalysisQueue
will reorder project entries, but it won't reorderAnalysisQueueWorkItem
cause they all are considered different. This is good in the way thatProjectEntry.Analyze
will be called always afterReloadModulesAsync
, but has a side effect that several concurrent calls ofReloadModulesAsync
will be executed one by one instead of swallowing all but first and last call.ReloadModulesAsync
may start new tasks and await on them whileAnalysisQueue
is thread-bound.AnalysisSynchronizationContext
doesn't support reentrancy andAnalysisQueue
thread is blocked until method is completed, which means that if anywhere on the code path we have missedConfigureAwait(false)
or provide any other way forAnalysisSynchronizationContext
to be captured, we'll get a deadlock.
Also, ReloadModulesAsync
should handle CancellationToken
by itself, but that is a separate issue.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 lastReloadModulesAsync
to be processedAnalysisQueue
will properly reorderReloadModulesAsync
call if it is already scheduledReloadModulesAsync
will be started in thread-pool so reentrancy shouldn't be a problem.
There was a problem hiding this 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?
Is there enough test coverage for the documentation changes? Aside from spaces removed around operators, there doesn't seem to be much changed. |
Yes, I updated tests. Need to add tests for the new functionality (tooltips and docs for modules, see #4050). |
Fixes #3849
Fixes #3869
Fixes #3882
Fixes #3977
Fixes #4031