From 3cb8d9d856c8fbf8931d3b1b79fb9b1baf242a84 Mon Sep 17 00:00:00 2001 From: tomaszgolebiowski <37900990+tomaszgolebiowski@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:34:05 +0100 Subject: [PATCH] Autocomplete (#166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - implemented the agent protocols for autocompletions - inline text changes that can be accepted with the `Tab` keyboard press (`lalt+,` for rotating suggestions if there is more than one) - explicitly trigger completion suggestions (`lalt+.`) - user option to enable/disable Autocompletions - telemetry to compute CAR - tests ## Test plan - Create a new solution - Start adding code - Check if 'grey code' with suggestions appears - Accept the suggestions by pressing `Tab` - Reject the suggestion by pressing `Esc` --------- Co-authored-by: Tomasz Gołębiowski --- agent/agent.version | 2 +- src/Cody.Core/Agent/IAgentService.cs | 18 +- src/Cody.Core/Agent/NotificationHandlers.cs | 8 +- .../Agent/Protocol/AutocompleteItem.cs | 11 + .../Agent/Protocol/AutocompleteParams.cs | 27 ++ .../Agent/Protocol/AutocompleteResult.cs | 12 + .../Protocol/CompletionBookkeepingEvent.cs | 9 + .../Agent/Protocol/CompletionItemInfo.cs | 14 + .../Agent/Protocol/CompletionItemParams.cs | 9 + .../Agent/Protocol/GetDocumentsParams.cs | 10 + .../Agent/Protocol/GetDocumentsResult.cs | 10 + .../Agent/Protocol/SelectedCompletionInfo.cs | 12 + src/Cody.Core/Cody.Core.csproj | 11 + src/Cody.Core/Common/StringExtensions.cs | 23 ++ .../DocumentSync/DocumentSyncCallback.cs | 24 +- .../Settings/IUserSettingsService.cs | 1 + src/Cody.Core/Settings/UserSettingsService.cs | 10 + src/Cody.Core/Trace/FileTraceListener.cs | 3 +- src/Cody.Core/Trace/LogioTraceListener.cs | 3 +- .../Options/GeneralOptionsControl.xaml | 12 +- .../ViewModels/GeneralOptionsViewModel.cs | 17 +- .../ChatLoggedBasicTests.cs | 1 + .../ChatNotLoggedStateTests.cs | 1 + .../Cody.VisualStudio.Tests.csproj | 3 + .../ConsoleApp1/ConsoleApp1/Manager.cs | 19 +- src/Cody.VisualStudio.Tests/TestsBase.cs | 12 +- src/Cody.VisualStudio/Client/AgentClient.cs | 11 +- .../Client/AgentJsonMessageFormatter.cs | 33 --- src/Cody.VisualStudio/Client/TraceJsonRpc.cs | 24 ++ .../Cody.VisualStudio.csproj | 11 +- src/Cody.VisualStudio/CodyPackage.cs | 7 +- .../Completions/CodyProposalCollection.cs | 13 + .../Completions/CodyProposalManager.cs | 38 +++ .../CodyProposalManagerProvider.cs | 27 ++ .../Completions/CodyProposalSource.cs | 279 ++++++++++++++++++ .../Completions/CodyProposalSourceProvider.cs | 124 ++++++++ .../Options/GeneralOptionsPage.cs | 14 +- .../Services/DocumentsSyncService.cs | 103 ++++--- .../Services/TestingSupportService.cs | 58 ++++ .../source.extension.vsixmanifest | 1 + 40 files changed, 908 insertions(+), 117 deletions(-) create mode 100644 src/Cody.Core/Agent/Protocol/AutocompleteItem.cs create mode 100644 src/Cody.Core/Agent/Protocol/AutocompleteParams.cs create mode 100644 src/Cody.Core/Agent/Protocol/AutocompleteResult.cs create mode 100644 src/Cody.Core/Agent/Protocol/CompletionBookkeepingEvent.cs create mode 100644 src/Cody.Core/Agent/Protocol/CompletionItemInfo.cs create mode 100644 src/Cody.Core/Agent/Protocol/CompletionItemParams.cs create mode 100644 src/Cody.Core/Agent/Protocol/GetDocumentsParams.cs create mode 100644 src/Cody.Core/Agent/Protocol/GetDocumentsResult.cs create mode 100644 src/Cody.Core/Agent/Protocol/SelectedCompletionInfo.cs create mode 100644 src/Cody.Core/Common/StringExtensions.cs delete mode 100644 src/Cody.VisualStudio/Client/AgentJsonMessageFormatter.cs create mode 100644 src/Cody.VisualStudio/Client/TraceJsonRpc.cs create mode 100644 src/Cody.VisualStudio/Completions/CodyProposalCollection.cs create mode 100644 src/Cody.VisualStudio/Completions/CodyProposalManager.cs create mode 100644 src/Cody.VisualStudio/Completions/CodyProposalManagerProvider.cs create mode 100644 src/Cody.VisualStudio/Completions/CodyProposalSource.cs create mode 100644 src/Cody.VisualStudio/Completions/CodyProposalSourceProvider.cs create mode 100644 src/Cody.VisualStudio/Services/TestingSupportService.cs diff --git a/agent/agent.version b/agent/agent.version index af21dcc1..eb9b96da 100644 --- a/agent/agent.version +++ b/agent/agent.version @@ -1 +1 @@ -vscode-v1.54.x \ No newline at end of file +vscode-v1.54.0 \ No newline at end of file diff --git a/src/Cody.Core/Agent/IAgentService.cs b/src/Cody.Core/Agent/IAgentService.cs index 717cb74a..eaf410cb 100644 --- a/src/Cody.Core/Agent/IAgentService.cs +++ b/src/Cody.Core/Agent/IAgentService.cs @@ -1,6 +1,7 @@ using Cody.Core.Agent.Protocol; -using System.Collections.Generic; using System; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Cody.Core.Agent @@ -61,6 +62,21 @@ public interface IAgentService [AgentCall("progress/cancel")] void CancelProgress(string id); + [AgentCall("autocomplete/execute")] + Task Autocomplete(AutocompleteParams autocomplete, CancellationToken cancellationToken); + + [AgentCall("autocomplete/clearLastCandidate")] + void ClearLastCandidate(); + + [AgentCall("autocomplete/completionSuggested")] + void CompletionSuggested(CompletionItemParams completionItem); + + [AgentCall("autocomplete/completionAccepted")] + void CompletionAccepted(CompletionItemParams completionItem); + + [AgentCall("testing/workspaceDocuments")] + Task GetWorkspaceDocuments(GetDocumentsParams documents); + //--------------------------------------------------------- // For notifications return type MUST be void! //--------------------------------------------------------- diff --git a/src/Cody.Core/Agent/NotificationHandlers.cs b/src/Cody.Core/Agent/NotificationHandlers.cs index 392c678d..d7465786 100644 --- a/src/Cody.Core/Agent/NotificationHandlers.cs +++ b/src/Cody.Core/Agent/NotificationHandlers.cs @@ -2,6 +2,7 @@ using Cody.Core.Infrastructure; using Cody.Core.Logging; using Cody.Core.Settings; +using Cody.Core.Trace; using Cody.Core.Workspace; using Newtonsoft.Json.Linq; using System; @@ -11,6 +12,8 @@ namespace Cody.Core.Agent { public class NotificationHandlers : INotificationHandler { + private static TraceLogger trace = new TraceLogger(nameof(NotificationHandlers)); + private readonly WebviewMessageHandler _messageFilter; private readonly IUserSettingsService _settingsService; private readonly IFileService _fileService; @@ -57,9 +60,10 @@ await agentClient.ReceiveMessageStringEncoded(new ReceiveMessageStringEncodedPar } [AgentCallback("debug/message")] - public void Debug(string channel, string message) + public void Debug(string channel, string message, string level) { - _logger.Debug($"[{channel} {message}]"); + //_logger.Debug($"[{channel} {message}]"); + trace.TraceEvent("AgentDebug", message); } [AgentCallback("webview/registerWebview")] diff --git a/src/Cody.Core/Agent/Protocol/AutocompleteItem.cs b/src/Cody.Core/Agent/Protocol/AutocompleteItem.cs new file mode 100644 index 00000000..77785dce --- /dev/null +++ b/src/Cody.Core/Agent/Protocol/AutocompleteItem.cs @@ -0,0 +1,11 @@ +using System; + +namespace Cody.Core.Agent.Protocol +{ + public class AutocompleteItem + { + public string Id { get; set; } + public string InsertText { get; set; } + public Range Range { get; set; } + } +} diff --git a/src/Cody.Core/Agent/Protocol/AutocompleteParams.cs b/src/Cody.Core/Agent/Protocol/AutocompleteParams.cs new file mode 100644 index 00000000..866b5354 --- /dev/null +++ b/src/Cody.Core/Agent/Protocol/AutocompleteParams.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Cody.Core.Agent.Protocol +{ + public class AutocompleteParams + { + public string Uri { get; set; } + public string FilePath { get; set; } + + public Position Position { get; set; } + + public TriggerKind? TriggerKind { get; set; } + + public SelectedCompletionInfo SelectedCompletionInfo { get; set; } + } + + public enum TriggerKind + { + [EnumMember(Value = nameof(Automatic))] //overwrites default camelCase naming convention + Automatic, + + [EnumMember(Value = nameof(Invoke))] + Invoke + } +} diff --git a/src/Cody.Core/Agent/Protocol/AutocompleteResult.cs b/src/Cody.Core/Agent/Protocol/AutocompleteResult.cs new file mode 100644 index 00000000..9e13305b --- /dev/null +++ b/src/Cody.Core/Agent/Protocol/AutocompleteResult.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace Cody.Core.Agent.Protocol +{ + public class AutocompleteResult + { + public AutocompleteItem[] Items { get; set; } + + public CompletionBookkeepingEvent CompletionEvent { get; set; } + } +} diff --git a/src/Cody.Core/Agent/Protocol/CompletionBookkeepingEvent.cs b/src/Cody.Core/Agent/Protocol/CompletionBookkeepingEvent.cs new file mode 100644 index 00000000..0bedbbe3 --- /dev/null +++ b/src/Cody.Core/Agent/Protocol/CompletionBookkeepingEvent.cs @@ -0,0 +1,9 @@ +using System; + +namespace Cody.Core.Agent.Protocol +{ + public class CompletionBookkeepingEvent + { + public CompletionItemInfo[] Items { get; set; } + } +} diff --git a/src/Cody.Core/Agent/Protocol/CompletionItemInfo.cs b/src/Cody.Core/Agent/Protocol/CompletionItemInfo.cs new file mode 100644 index 00000000..7080b29d --- /dev/null +++ b/src/Cody.Core/Agent/Protocol/CompletionItemInfo.cs @@ -0,0 +1,14 @@ +using System; + +namespace Cody.Core.Agent.Protocol +{ + public class CompletionItemInfo + { + public int LineCount { get; set; } + public int CharCount { get; set; } + + public string InsertText { get; set; } + + public string StopReason { get; set; } + } +} diff --git a/src/Cody.Core/Agent/Protocol/CompletionItemParams.cs b/src/Cody.Core/Agent/Protocol/CompletionItemParams.cs new file mode 100644 index 00000000..f57fcd4a --- /dev/null +++ b/src/Cody.Core/Agent/Protocol/CompletionItemParams.cs @@ -0,0 +1,9 @@ +using System; + +namespace Cody.Core.Agent.Protocol +{ + public class CompletionItemParams + { + public string CompletionID { get; set; } + } +} diff --git a/src/Cody.Core/Agent/Protocol/GetDocumentsParams.cs b/src/Cody.Core/Agent/Protocol/GetDocumentsParams.cs new file mode 100644 index 00000000..d289e4ae --- /dev/null +++ b/src/Cody.Core/Agent/Protocol/GetDocumentsParams.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace Cody.Core.Agent.Protocol +{ + public class GetDocumentsParams + { + public string[] Uris { get; set; } + } +} diff --git a/src/Cody.Core/Agent/Protocol/GetDocumentsResult.cs b/src/Cody.Core/Agent/Protocol/GetDocumentsResult.cs new file mode 100644 index 00000000..291831f4 --- /dev/null +++ b/src/Cody.Core/Agent/Protocol/GetDocumentsResult.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace Cody.Core.Agent.Protocol +{ + public class GetDocumentsResult + { + public ProtocolTextDocument[] Documents { get; set; } + } +} diff --git a/src/Cody.Core/Agent/Protocol/SelectedCompletionInfo.cs b/src/Cody.Core/Agent/Protocol/SelectedCompletionInfo.cs new file mode 100644 index 00000000..39890864 --- /dev/null +++ b/src/Cody.Core/Agent/Protocol/SelectedCompletionInfo.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace Cody.Core.Agent.Protocol +{ + public class SelectedCompletionInfo + { + public Range Range { get; set; } + + public string Text { get; set; } + } +} diff --git a/src/Cody.Core/Cody.Core.csproj b/src/Cody.Core/Cody.Core.csproj index 545e0acb..8647a044 100644 --- a/src/Cody.Core/Cody.Core.csproj +++ b/src/Cody.Core/Cody.Core.csproj @@ -38,6 +38,7 @@ + @@ -54,11 +55,20 @@ + + + + + + + + + @@ -81,6 +91,7 @@ + diff --git a/src/Cody.Core/Common/StringExtensions.cs b/src/Cody.Core/Common/StringExtensions.cs new file mode 100644 index 00000000..64dd8efe --- /dev/null +++ b/src/Cody.Core/Common/StringExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Cody.Core.Common +{ + public static class StringExtensions + { + public static string ToUri(this string path) + { + var uri = new Uri(path).AbsoluteUri; + return Regex.Replace(uri, "(file:///)(\\D+)(:)", m => m.Groups[1].Value + m.Groups[2].Value.ToLower() + "%3A"); + } + + public static string ConvertLineBreaks(this string text, string lineBrakeChars) + { + return Regex.Replace(text, @"\r\n?|\n", lineBrakeChars); + } + } +} diff --git a/src/Cody.Core/DocumentSync/DocumentSyncCallback.cs b/src/Cody.Core/DocumentSync/DocumentSyncCallback.cs index 97e5c02e..84df61ac 100644 --- a/src/Cody.Core/DocumentSync/DocumentSyncCallback.cs +++ b/src/Cody.Core/DocumentSync/DocumentSyncCallback.cs @@ -1,11 +1,11 @@ using Cody.Core.Agent; using Cody.Core.Agent.Protocol; +using Cody.Core.Common; using Cody.Core.Logging; using Cody.Core.Trace; using System; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; namespace Cody.Core.DocumentSync { @@ -22,12 +22,6 @@ public DocumentSyncCallback(IAgentService agentService, ILog logger) this.logger = logger; } - private string ToUri(string path) - { - var uri = new Uri(path).AbsoluteUri; - return Regex.Replace(uri, "(file:///)(\\D+)(:)", m => m.Groups[1].Value + m.Groups[2].Value.ToLower() + "%3A"); - } - public void OnChanged(string fullPath, DocumentRange visibleRange, DocumentRange selection, IEnumerable changes) { trace.TraceEvent("DidChange", "ch: '{0}', sel:{1}, vr:{2}, path:{3}", string.Join("", changes), selection, visibleRange, fullPath); @@ -52,7 +46,7 @@ public void OnChanged(string fullPath, DocumentRange visibleRange, DocumentRange var docState = new ProtocolTextDocument { - Uri = ToUri(fullPath), + Uri = fullPath.ToUri(), VisibleRange = vRange, Selection = new Range { @@ -69,7 +63,7 @@ public void OnChanged(string fullPath, DocumentRange visibleRange, DocumentRange }, ContentChanges = changes.Select(x => new ProtocolTextDocumentContentChangeEvent { - Text = x.Text, + Text = x.Text.ConvertLineBreaks("\n"), Range = new Range { Start = new Position @@ -87,6 +81,8 @@ public void OnChanged(string fullPath, DocumentRange visibleRange, DocumentRange }; agentService.DidChange(docState); + //var result = agentService.GetWorkspaceDocuments(new GetDocumentsParams { Uris = new[] { fullPath.ToUri() } }).Result; + //trace.TraceEvent("AfterDidChange", result.Documents.First().Content); } public void OnClosed(string fullPath) @@ -95,7 +91,7 @@ public void OnClosed(string fullPath) var docState = new ProtocolTextDocument { - Uri = ToUri(fullPath), + Uri = fullPath.ToUri(), }; // Only the 'uri' property is required, other properties are ignored. @@ -105,7 +101,7 @@ public void OnClosed(string fullPath) public void OnFocus(string fullPath) { trace.TraceEvent("DidFocus", "{0}", fullPath); - agentService.DidFocus(new CodyFilePath { Uri = ToUri(fullPath) }); + agentService.DidFocus(new CodyFilePath { Uri = fullPath.ToUri() }); } public void OnOpened(string fullPath, string content, DocumentRange visibleRange, DocumentRange selection) @@ -132,8 +128,8 @@ public void OnOpened(string fullPath, string content, DocumentRange visibleRange var docState = new ProtocolTextDocument { - Uri = ToUri(fullPath), - Content = content, + Uri = fullPath.ToUri(), + Content = content.ConvertLineBreaks("\n"), VisibleRange = vRange, Selection = new Range { @@ -156,7 +152,7 @@ public void OnOpened(string fullPath, string content, DocumentRange visibleRange public void OnSaved(string fullPath) { trace.TraceEvent("DidSave", "{0}", fullPath); - agentService.DidSave(new CodyFilePath { Uri = ToUri(fullPath) }); + agentService.DidSave(new CodyFilePath { Uri = fullPath.ToUri() }); } } } diff --git a/src/Cody.Core/Settings/IUserSettingsService.cs b/src/Cody.Core/Settings/IUserSettingsService.cs index f629682b..ad8125ae 100644 --- a/src/Cody.Core/Settings/IUserSettingsService.cs +++ b/src/Cody.Core/Settings/IUserSettingsService.cs @@ -10,6 +10,7 @@ public interface IUserSettingsService string ServerEndpoint { get; set; } string CustomConfiguration { get; set; } bool AcceptNonTrustedCert { get; set; } + bool AutomaticallyTriggerCompletions { get; set; } event EventHandler AuthorizationDetailsChanged; diff --git a/src/Cody.Core/Settings/UserSettingsService.cs b/src/Cody.Core/Settings/UserSettingsService.cs index 09d2b136..8d0e1c33 100644 --- a/src/Cody.Core/Settings/UserSettingsService.cs +++ b/src/Cody.Core/Settings/UserSettingsService.cs @@ -110,5 +110,15 @@ public bool AcceptNonTrustedCert } set => Set(nameof(AcceptNonTrustedCert), value.ToString()); } + + public bool AutomaticallyTriggerCompletions + { + get + { + var value = GetOrDefault(nameof(AutomaticallyTriggerCompletions), true.ToString()); + return bool.Parse(value); + } + set => Set(nameof(AutomaticallyTriggerCompletions), value.ToString()); + } } } diff --git a/src/Cody.Core/Trace/FileTraceListener.cs b/src/Cody.Core/Trace/FileTraceListener.cs index 506fcf3f..74343f43 100644 --- a/src/Cody.Core/Trace/FileTraceListener.cs +++ b/src/Cody.Core/Trace/FileTraceListener.cs @@ -34,7 +34,8 @@ protected string FormatTraceEvent(TraceEvent traceEvent) if (!string.IsNullOrEmpty(traceEvent.Message)) { - sb.AppendFormat(traceEvent.Message, traceEvent.MessageArgs); + if (traceEvent.MessageArgs != null && traceEvent.MessageArgs.Any()) sb.AppendFormat(traceEvent.Message, traceEvent.MessageArgs); + else sb.Append(traceEvent.Message); } if (traceEvent.Data != null) diff --git a/src/Cody.Core/Trace/LogioTraceListener.cs b/src/Cody.Core/Trace/LogioTraceListener.cs index fe6c303f..a3a43e9e 100644 --- a/src/Cody.Core/Trace/LogioTraceListener.cs +++ b/src/Cody.Core/Trace/LogioTraceListener.cs @@ -49,7 +49,8 @@ protected override void Write(TraceEvent traceEvent) if (!string.IsNullOrEmpty(traceEvent.Message)) { sb.Append(" "); - sb.AppendFormat(traceEvent.Message, traceEvent.MessageArgs); + if (traceEvent.MessageArgs != null && traceEvent.MessageArgs.Any()) sb.AppendFormat(traceEvent.Message, traceEvent.MessageArgs); + else sb.Append(traceEvent.Message); } if (traceEvent.Data != null) diff --git a/src/Cody.UI/Controls/Options/GeneralOptionsControl.xaml b/src/Cody.UI/Controls/Options/GeneralOptionsControl.xaml index d383d98e..d1bae16f 100644 --- a/src/Cody.UI/Controls/Options/GeneralOptionsControl.xaml +++ b/src/Cody.UI/Controls/Options/GeneralOptionsControl.xaml @@ -17,6 +17,7 @@ + @@ -60,10 +61,19 @@ Content="Accept non-trusted certificates (requires restart)" /> + +