Skip to content

Commit

Permalink
Merge pull request #4483 from MikhailArkhipov/progress
Browse files Browse the repository at this point in the history
Add analysis progress reporting to LS
  • Loading branch information
Mikhail Arkhipov authored Jul 6, 2018
2 parents 2401fff + 50e9ed0 commit 6638913
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 36 deletions.
5 changes: 4 additions & 1 deletion Python/Product/Analysis/LanguageServer/Messages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,12 @@ public sealed class ParseCompleteEventArgs : EventArgs {
public int version { get; set; }
}

public sealed class AnalysisQueuedEventArgs : EventArgs {
public Uri uri { get; set; }
}

public sealed class AnalysisCompleteEventArgs : EventArgs {
public Uri uri { get; set; }
public int version { get; set; }
}

}
23 changes: 18 additions & 5 deletions Python/Product/Analysis/LanguageServer/Server.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public void Analyze(CancellationToken cancel) {

private readonly EditorFiles _editorFiles;
private bool _traceLogging;
private bool _analysisUpdates;
private ReloadModulesQueueItem _reloadModulesQueueItem;
// If null, all files must be added manually
private string _rootDir;
Expand Down Expand Up @@ -322,8 +323,18 @@ private void ParseComplete(Uri uri, int version) {

public event EventHandler<AnalysisCompleteEventArgs> OnAnalysisComplete;
private void AnalysisComplete(Uri uri, int version) {
TraceMessage($"Analysis complete for {uri} at version {version}");
OnAnalysisComplete?.Invoke(this, new AnalysisCompleteEventArgs { uri = uri, version = version });
if (_analysisUpdates) {
TraceMessage($"Analysis complete for {uri} at version {version}");
OnAnalysisComplete?.Invoke(this, new AnalysisCompleteEventArgs { uri = uri, version = version });
}
}

public event EventHandler<AnalysisQueuedEventArgs> OnAnalysisQueued;
private void AnalysisQueued(Uri uri) {
if (_analysisUpdates) {
TraceMessage($"Analysis queued for {uri}");
OnAnalysisQueued?.Invoke(this, new AnalysisQueuedEventArgs { uri = uri });
}
}

public void SetSearchPaths(IEnumerable<string> searchPaths) => Analyzer.SetSearchPaths(searchPaths.MaybeEnumerate());
Expand All @@ -345,9 +356,10 @@ private async Task DoInitializeAsync(InitializeParams @params) {

ThrowIfDisposed();
_clientCaps = @params.capabilities;
_traceLogging = _clientCaps?.python?.traceLogging ?? false;
Analyzer.EnableDiagnostics = _clientCaps?.python?.liveLinting ?? false;
_traceLogging = @params.initializationOptions.traceLogging;
_analysisUpdates = @params.initializationOptions.analysisUpdates;

Analyzer.EnableDiagnostics = _clientCaps?.python?.liveLinting ?? false;
_reloadModulesQueueItem = new ReloadModulesQueueItem(Analyzer);

if (@params.initializationOptions.displayOptions != null) {
Expand Down Expand Up @@ -552,6 +564,7 @@ private void EnqueueItem(IDocument doc, AnalysisPriority priority = AnalysisPrio
cookieTask = _parseQueue.Enqueue(doc, Analyzer.LanguageVersion);
}

AnalysisQueued(doc.DocumentUri);
// The call must be fire and forget, but should not be yielding.
// It is called from DidChangeTextDocument which must fully finish
// since otherwise Complete() may come before the change is enqueued
Expand Down Expand Up @@ -610,7 +623,7 @@ private void OnPythonEntryNewAnalysis(IPythonProjectEntry pythonProjectEntry) {

var version = 0;
var parse = pythonProjectEntry.GetCurrentParse();
if (_clientCaps?.python?.analysisUpdates ?? false) {
if (_analysisUpdates) {
if (parse?.Cookie is VersionCookie vc && vc.Versions.Count > 0) {
foreach (var kv in vc.GetAllParts(pythonProjectEntry.DocumentUri)) {
AnalysisComplete(kv.Key, kv.Value.Version);
Expand Down
24 changes: 12 additions & 12 deletions Python/Product/Analysis/LanguageServer/Structures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,18 @@ public struct Interpreter {
/// should be loaded into the Python analysis engine.
/// </summary>
public string[] includeFiles = Array.Empty<string>();

/// <summary>
/// Client expects analysis progress updates, including notifications
/// when analysis is complete for a particular document version.
/// </summary>
public bool analysisUpdates;

/// <summary>
/// Enables an even higher level of logging via the logMessage event.
/// This will likely have a performance impact.
/// </summary>
public bool traceLogging;
}

[Serializable]
Expand Down Expand Up @@ -484,18 +496,6 @@ public struct RenameCapabilities { public bool dynamicRegistration; }
/// </summary>
[Serializable]
public class PythonClientCapabilities {
/// <summary>
/// Client expects analysis progress updates, including notifications
/// when analysis is complete for a particular document version.
/// </summary>
public bool? analysisUpdates;

/// <summary>
/// Enables an even higher level of logging via the logMessage event.
/// This will likely have a performance impact.
/// </summary>
public bool? traceLogging;

/// <summary>
/// Disables automatic analysis of all files under the root URI.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,13 @@ await _server.Initialize(new LS.InitializeParams {
maxDocumentationTextLength = 1024,
trimDocumentationText = true,
maxDocumentationLines = 100
}
},
analysisUpdates = true,
traceLogging = request.traceLogging,
},
capabilities = new LS.ClientCapabilities {
python = new LS.PythonClientCapabilities {
analysisUpdates = true,
manualFileLoad = !request.analyzeAllFiles,
traceLogging = request.traceLogging,
liveLinting = request.liveLinting
},
textDocument = new LS.TextDocumentClientCapabilities {
Expand Down
24 changes: 24 additions & 0 deletions Python/Product/VSCode/AnalysisVsc/Core/Shell/IProgressService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;
using System.Threading.Tasks;

namespace Microsoft.DsTools.Core.Services.Shell {
/// <summary>
/// Progress reporting service
/// </summary>
public interface IProgressService {
/// <summary>
/// Displays progress message in the application UI.
/// </summary>
IProgress BeginProgress();
}

public interface IProgress: IDisposable {
/// <summary>
/// Updates progress message in the application UI.
/// </summary>
Task Report(string message);
}
}
4 changes: 1 addition & 3 deletions Python/Product/VSCode/AnalysisVsc/Core/Shell/IUIService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

namespace Microsoft.DsTools.Core.Services.Shell {
/// <summary>
/// Basic shell provides access to services such as
/// composition container, export provider, global VS IDE
/// services and so on.
/// Service that represents the application user interface.
/// </summary>
public interface IUIService {
/// <summary>
Expand Down
6 changes: 4 additions & 2 deletions Python/Product/VSCode/AnalysisVsc/LanguageServer.Lifetime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ public Task<InitializeResult> Initialize(JToken token) {
}

[JsonRpcMethod("initialized")]
public Task Initialized(JToken token)
=> _server.Initialized(token.ToObject<InitializedParams>());
public async Task Initialized(JToken token) {
await _server.Initialized(token.ToObject<InitializedParams>());
_rpc.NotifyAsync("python/languageServerStarted").DoNotWait();
}

[JsonRpcMethod("shutdown")]
public async Task Shutdown() {
Expand Down
46 changes: 39 additions & 7 deletions Python/Product/VSCode/AnalysisVsc/LanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,15 @@ public sealed partial class LanguageServer : IDisposable {
private readonly RestTextConverter _textConverter = new RestTextConverter();
private IUIService _ui;
private ITelemetryService _telemetry;
private IProgressService _progress;
private JsonRpc _rpc;
private bool _filesLoaded;
private Task _progressReportingTask;

public CancellationToken Start(IServiceContainer services, JsonRpc rpc) {
_ui = services.GetService<IUIService>();
_telemetry = services.GetService<ITelemetryService>();
_progress = services.GetService<IProgressService>();
_rpc = rpc;

_server.OnLogMessage += OnLogMessage;
Expand All @@ -58,6 +61,8 @@ public CancellationToken Start(IServiceContainer services, JsonRpc rpc) {
_server.OnApplyWorkspaceEdit += OnApplyWorkspaceEdit;
_server.OnRegisterCapability += OnRegisterCapability;
_server.OnUnregisterCapability += OnUnregisterCapability;
_server.OnAnalysisQueued += OnAnalysisQueued;
_server.OnAnalysisComplete += OnAnalysisComplete;

_disposables
.Add(() => _server.OnLogMessage -= OnLogMessage)
Expand All @@ -66,18 +71,45 @@ public CancellationToken Start(IServiceContainer services, JsonRpc rpc) {
.Add(() => _server.OnPublishDiagnostics -= OnPublishDiagnostics)
.Add(() => _server.OnApplyWorkspaceEdit -= OnApplyWorkspaceEdit)
.Add(() => _server.OnRegisterCapability -= OnRegisterCapability)
.Add(() => _server.OnUnregisterCapability -= OnUnregisterCapability);
.Add(() => _server.OnUnregisterCapability -= OnUnregisterCapability)
.Add(() => _server.OnAnalysisQueued -= OnAnalysisQueued)
.Add(() => _server.OnAnalysisComplete -= OnAnalysisComplete);

return _sessionTokenSource.Token;
}

private void OnAnalysisQueued(object sender, AnalysisQueuedEventArgs e) => HandleAnalysisQueueEvent();
private void OnAnalysisComplete(object sender, AnalysisCompleteEventArgs e) => HandleAnalysisQueueEvent();

private void HandleAnalysisQueueEvent()
=> _progressReportingTask = _progressReportingTask ?? ProgressWorker();

private async Task ProgressWorker() {
await Task.Delay(1000);

var remaining = _server.EstimateRemainingWork();
if (remaining > 0) {
using (var p = _progress.BeginProgress()) {
while (remaining > 0) {
var items = remaining > 1 ? "items" : "item";
// TODO: in localization this needs to be two different messages
// since not all languages allow sentence construction.
await p.Report($"Analyzing workspace, {remaining} {items} remaining...");
await Task.Delay(100);
remaining = _server.EstimateRemainingWork();
}
}
}
_progressReportingTask = null;
}

public void Dispose() {
_disposables.TryDispose();
_server.Dispose();
}

[JsonObject]
class PublishDiagnosticsParams {
private class PublishDiagnosticsParams {
[JsonProperty]
public Uri uri;
[JsonProperty]
Expand Down Expand Up @@ -115,7 +147,7 @@ public async Task DidChangeConfiguration(JToken token) {

var rootSection = token["settings"];
var pythonSection = rootSection?["python"];
if(pythonSection == null) {
if (pythonSection == null) {
return;
}

Expand Down Expand Up @@ -189,8 +221,8 @@ private static IEnumerable<DidChangeTextDocumentParams> SplitDidChangeTextDocume
changes.Push(CreateDidChangeTextDocumentParams(@params, version, contentChanges));
contentChanges = new Stack<TextDocumentContentChangedEvent>();
version--;
}
}

contentChanges.Push(contentChange);
previousRange = range;
}
Expand All @@ -202,7 +234,7 @@ private static IEnumerable<DidChangeTextDocumentParams> SplitDidChangeTextDocume
return changes;
}

private static DidChangeTextDocumentParams CreateDidChangeTextDocumentParams(DidChangeTextDocumentParams @params, int version, Stack<TextDocumentContentChangedEvent> contentChanges)
private static DidChangeTextDocumentParams CreateDidChangeTextDocumentParams(DidChangeTextDocumentParams @params, int version, Stack<TextDocumentContentChangedEvent> contentChanges)
=> new DidChangeTextDocumentParams {
_enqueueForAnalysis = @params._enqueueForAnalysis,
contentChanges = contentChanges.ToArray(),
Expand Down Expand Up @@ -339,7 +371,7 @@ private T GetSetting<T>(JToken section, string settingName, T defaultValue) {
var value = section?[settingName];
try {
return value != null ? value.ToObject<T>() : defaultValue;
} catch(JsonException ex) {
} catch (JsonException ex) {
_server.LogMessage(MessageType.Warning, $"Exception retrieving setting '{settingName}': {ex.Message}");
}
return defaultValue;
Expand Down
1 change: 1 addition & 0 deletions Python/Product/VSCode/AnalysisVsc/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public static void Main(string[] args) {
rpc.JsonSerializer.Converters.Add(new UriConverter());

services.AddService(new UIService(rpc));
services.AddService(new ProgressService(rpc));
services.AddService(new TelemetryService(rpc));
var token = server.Start(services, rpc);
rpc.StartListening();
Expand Down
41 changes: 41 additions & 0 deletions Python/Product/VSCode/AnalysisVsc/Services/ProgressService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Python Tools for Visual Studio
// Copyright(c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the License); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

using System.Threading.Tasks;
using Microsoft.DsTools.Core.Services.Shell;
using Microsoft.PythonTools.Analysis.Infrastructure;
using StreamJsonRpc;

namespace Microsoft.PythonTools.VsCode.Services {
public sealed class ProgressService : IProgressService {
private readonly JsonRpc _rpc;
public ProgressService(JsonRpc rpc) {
_rpc = rpc;
}

public IProgress BeginProgress() => new Progress(_rpc);

private class Progress : IProgress {
private readonly JsonRpc _rpc;
public Progress(JsonRpc rpc) {
_rpc = rpc;
_rpc.NotifyAsync("python/beginProgress").DoNotWait();
}
public Task Report(string message) => _rpc.NotifyAsync("python/reportProgress", message);
public void Dispose() => _rpc.NotifyAsync("python/endProgress").DoNotWait();
}
}
}
6 changes: 3 additions & 3 deletions Python/Tests/Analysis/LanguageServerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,13 @@ await s.Initialize(new InitializeParams {
typeName = typeof(AstPythonInterpreterFactory).FullName,
properties = properties
},
testEnvironment = true
testEnvironment = true,
analysisUpdates = true,
traceLogging = true,
},
capabilities = new ClientCapabilities {
python = new PythonClientCapabilities {
analysisUpdates = true,
liveLinting = true,
traceLogging = true
}
}
});
Expand Down

0 comments on commit 6638913

Please sign in to comment.