From 44ce8eb05162ead54153727180a9cd47d195f9df Mon Sep 17 00:00:00 2001 From: Cory Rivera Date: Tue, 16 Feb 2021 14:29:01 -0800 Subject: [PATCH] Improve intellisense behavior for MSSQL kernel. (#1062) * Improve intellisense behavior for MSSQL code. * Improve parsing for line endings. * Add unit tests for intellisense changes. * Use Theory attribute instead of separate tests. Also replaced Equals() assertion with Be(). --- .../MsSqlServiceClientTests.cs | 52 +++++++++++++++++++ .../MsSqlServiceClient.cs | 27 +++++----- 2 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 src/Microsoft.DotNet.Interactive.SqlServer.Tests/MsSqlServiceClientTests.cs diff --git a/src/Microsoft.DotNet.Interactive.SqlServer.Tests/MsSqlServiceClientTests.cs b/src/Microsoft.DotNet.Interactive.SqlServer.Tests/MsSqlServiceClientTests.cs new file mode 100644 index 0000000000..8772e28fce --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.SqlServer.Tests/MsSqlServiceClientTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Xunit; +using FluentAssertions; +using System.IO; + +namespace Microsoft.DotNet.Interactive.SqlServer.Tests +{ + public class MsSqlServiceClientTests + { + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + public void Should_parse_doc_change_correctly_with_different_line_endings(string lineEnding) + { + string oldText = string.Join(lineEnding, "abc", "def", "", "abc", "abcdef"); + int oldTextLineCount = 5; + int oldTextLastCharacterNum = 6; + string newText = string.Join(lineEnding, "abc", "def"); + var testUri = new Uri("untitled://test"); + + var docChange = MsSqlServiceClient.GetDocumentChangeForText(testUri, newText, oldText); + + docChange.ContentChanges.Length + .Should() + .Be(1); + docChange.ContentChanges[0].Range.End.Line + .Should() + .Be(oldTextLineCount - 1); + docChange.ContentChanges[0].Range.End.Character + .Should() + .Be(oldTextLastCharacterNum); + docChange.ContentChanges[0].Range.Start.Line + .Should() + .Be(0); + docChange.ContentChanges[0].Range.Start.Character + .Should() + .Be(0); + docChange.ContentChanges[0].Text + .Should() + .Be(newText); + docChange.TextDocument.Uri + .Should() + .Be(testUri.ToString()); + docChange.TextDocument.Version + .Should() + .Be(1); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.SqlServer/MsSqlServiceClient.cs b/src/Microsoft.DotNet.Interactive.SqlServer/MsSqlServiceClient.cs index d0687c243e..d26117a1f8 100644 --- a/src/Microsoft.DotNet.Interactive.SqlServer/MsSqlServiceClient.cs +++ b/src/Microsoft.DotNet.Interactive.SqlServer/MsSqlServiceClient.cs @@ -107,7 +107,7 @@ public async Task DisconnectAsync(Uri ownerUri) public async Task> ProvideCompletionItemsAsync(Uri fileUri, RequestCompletions command) { - string oldFileContents = await UpdateFileContentsAsync(fileUri, command.Code); + await UpdateFileContentsAsync(fileUri, command.Code); TextDocumentIdentifier docId = new TextDocumentIdentifier() { Uri = fileUri.ToString() }; Position position = new Position() { Line = command.LinePosition.Line, Character = command.LinePosition.Character }; @@ -129,20 +129,18 @@ public async Task> ProvideCompletionItemsAsync(Uri f } /// - /// Updates the contents of the file at the provided path with the provided string, - /// and then returns the old file contents as a string. If the file contents have - /// changed, then a text change notification is also sent to the tools service. + /// Updates the contents of the file at the provided path with the provided string. + /// If the file contents have changed, then a text change notification is also sent + /// to the tools service. /// - private async Task UpdateFileContentsAsync(Uri fileUri, string newContents) + private async Task UpdateFileContentsAsync(Uri fileUri, string newContents) { string oldFileContents = await File.ReadAllTextAsync(fileUri.LocalPath); if (!oldFileContents.Equals(newContents)) { await File.WriteAllTextAsync(fileUri.LocalPath, newContents); - await SendTextChangeNotificationAsync(fileUri, newContents, oldFileContents); } - return oldFileContents; } public async Task ExecuteQueryStringAsync(Uri ownerUri, string queryString) @@ -158,10 +156,16 @@ public async Task ExecuteQueryExecuteSubsetAsync(Query public async Task SendTextChangeNotificationAsync(Uri ownerUri, string newText, string oldText) { - var oldTextLines = oldText.Split('\n'); + var textChangeParams = GetDocumentChangeForText(ownerUri, newText, oldText); + await _rpc.NotifyWithParameterObjectAsync("textDocument/didChange", textChangeParams); + } + + public static DidChangeTextDocumentParams GetDocumentChangeForText(Uri ownerUri, string newText, string oldText) + { + var oldTextLines = oldText.Split('\n').Select(text => text.EndsWith('\r') ? text[0..^1] : text).ToArray(); var lastLineNum = Math.Max(0, oldTextLines.Length - 1); - var lastLine = oldTextLines[lastLineNum]; - var lastCharacterNum = Math.Max(0, lastLine.Length - 1); + var lastLine = oldTextLines.Length > 0 ? oldTextLines[lastLineNum] : string.Empty; + var lastCharacterNum = lastLine.Length; var startPosition = new Position() { Line = 0, Character = 0 }; var endPosition = new Position() { Line = lastLineNum, Character = lastCharacterNum }; @@ -170,9 +174,8 @@ public async Task SendTextChangeNotificationAsync(Uri ownerUri, string newText, var changeRange = new Range() { Start = startPosition, End = endPosition }; var docChange = new TextDocumentChangeEvent() { Text = newText, Range = changeRange }; var changes = new TextDocumentChangeEvent[] { docChange }; - var textChangeParams = new DidChangeTextDocumentParams() { TextDocument = textDoc, ContentChanges = changes }; - await _rpc.NotifyWithParameterObjectAsync("textDocument/didChange", textChangeParams); + return new DidChangeTextDocumentParams() { TextDocument = textDoc, ContentChanges = changes }; } public void HandleConnectionCompletion(ConnectionCompleteParams connParams)