diff --git a/MultilineGreyText/InlineGreyTextTagger.cs b/MultilineGreyText/InlineGreyTextTagger.cs
index d55d30a..a947baa 100644
--- a/MultilineGreyText/InlineGreyTextTagger.cs
+++ b/MultilineGreyText/InlineGreyTextTagger.cs
@@ -34,14 +34,20 @@ public InlineGreyTextTagger(IWpfTextView view){
/// Adornment corresponding to given data. May be null.
public void UpdateAdornment(UIElement text){
ClearAdornment();
- stackPanel.Children.Add(text);
+ stackPanel.Children.Add(text);
+ stackPanel.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
+ stackPanel.UpdateLayout();
}
public void ClearAdornment(){
stackPanel.Children.Clear();
+ stackPanel = new StackPanel();
}
public void FormatText(TextRunProperties props){
+ if(props == null){
+ return;
+ }
foreach (TextBlock block in stackPanel.Children){
block.FontFamily = props.Typeface.FontFamily;
block.FontSize = props.FontRenderingEmSize;
@@ -68,7 +74,10 @@ public virtual IEnumerable> GetTags(NormalizedSn
}
ITextSnapshot requestedSnapshot = spans[0].Snapshot;
- stackPanel.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
+ double width = view.FormattedLineSource.ColumnWidth * ((stackPanel.Children[0] as TextBlock).Inlines.First() as Run).Text.Length;
+ stackPanel.Measure(new Size(width, double.PositiveInfinity));
+ stackPanel.MinWidth = width;
+ stackPanel.MaxWidth = width;
var caretLine = view.Caret.ContainingTextViewLine;
SnapshotPoint point = view.Caret.Position.BufferPosition.TranslateTo(requestedSnapshot, PointTrackingMode.Positive);
var line = requestedSnapshot.GetLineFromPosition(point);
diff --git a/MultilineGreyText/MultilineGreyTextTagger.cs b/MultilineGreyText/MultilineGreyTextTagger.cs
index 43c6fba..fadb70f 100644
--- a/MultilineGreyText/MultilineGreyTextTagger.cs
+++ b/MultilineGreyText/MultilineGreyTextTagger.cs
@@ -70,39 +70,39 @@ private InlineGreyTextTagger GetTagger(){
}
public void SetSuggestion(String newSuggestion, bool inline, int caretPoint){
- ClearSuggestion();
- inlineSuggestion = inline;
-
- int lineN = GetCurrentTextLine();
-
- if (lineN < 0) return;
-
- String untrim = buffer.CurrentSnapshot.GetLineFromLineNumber(lineN).GetText();
- String line = untrim.TrimStart();
- int offset = untrim.Length - line.Length;
-
- caretPoint = Math.Max(0, caretPoint - offset);
-
- String combineSuggestion = line + newSuggestion;
- if (line.Length - caretPoint > 0){
- String currentText = line.Substring(0, caretPoint);
- combineSuggestion = currentText + newSuggestion;
- userEndingText = line.TrimEnd().Substring(caretPoint);
- var userIndex = newSuggestion.IndexOf(userEndingText);
- if(userIndex < 0){
- return;
- }
- userIndex += currentText.Length;
-
- this.userIndex = userIndex;
- isTextInsertion = true;
- insertionPoint = line.Length - caretPoint;
- }else{
- isTextInsertion = false;
- }
-
- suggestion = new Tuple(combineSuggestion, combineSuggestion.Split('\n'));
- Update();
+ ClearSuggestion();
+ inlineSuggestion = inline;
+
+ int lineN = GetCurrentTextLine();
+
+ if (lineN < 0) return;
+
+ String untrim = buffer.CurrentSnapshot.GetLineFromLineNumber(lineN).GetText();
+ String line = untrim.TrimStart();
+ int offset = untrim.Length - line.Length;
+
+ caretPoint = Math.Max(0, caretPoint - offset);
+
+ String combineSuggestion = line + newSuggestion;
+ if (line.Length - caretPoint > 0){
+ String currentText = line.Substring(0, caretPoint);
+ combineSuggestion = currentText + newSuggestion;
+ userEndingText = line.Substring(caretPoint).TrimEnd();
+ var userIndex = newSuggestion.IndexOf(userEndingText);
+ if (userIndex < 0){
+ return;
+ }
+ userIndex += currentText.Length;
+
+ this.userIndex = userIndex;
+ isTextInsertion = true;
+ insertionPoint = line.Length - caretPoint;
+ }else{
+ isTextInsertion = false;
+ }
+
+ suggestion = new Tuple(combineSuggestion, combineSuggestion.Split('\n'));
+ Update();
}
private void CaretUpdate(object sender, CaretPositionChangedEventArgs e){
@@ -163,7 +163,7 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection s
var snapshotLine = currentSnapshot.GetLineFromLineNumber(currentTextLineN);
var height = view.LineHeight * (currentSuggestion.Item2.Length - 1);
-
+
if(currentTextLineN == 0 && currentSnapshot.Lines.Count() == 1 && String.IsNullOrEmpty(currentSnapshot.GetText())){
height += view.LineHeight;
}
@@ -241,7 +241,6 @@ void AddInsertionTextBlock(int start, int end, string line){
GetTagger().UpdateAdornment(CreateTextBox(remainder, greyBrush));
}
-
//Updates the grey text
public void UpdateAdornment(IWpfTextView view, string userText, int suggestionStart){
stackPanel.Children.Clear();
@@ -456,12 +455,16 @@ public bool CompleteText(){
//replaces text in the editor
void ReplaceText(string text, int lineN){
ClearSuggestion();
-
SnapshotSpan span = this.snapshot.GetLineFromLineNumber(lineN).Extent;
- ITextEdit edit = view.TextBuffer.CreateEdit();
-
- edit.Replace(span, text);
- edit.Apply();
+ ITextEdit edit = view.BufferGraph.TopBuffer.CreateEdit();
+ var spanLength = span.Length;
+ edit.Replace(span, text);
+ var newSnapshot = edit.Apply();
+
+ if(spanLength == 0 && text.Length > 0){
+ view.Caret.MoveToPreviousCaretPosition();
+ view.Caret.MoveToNextCaretPosition();
+ }
}
//sets up the suggestion for display
diff --git a/MultilineGreyText/RefactCompletionCommandHandler.cs b/MultilineGreyText/RefactCompletionCommandHandler.cs
index 4ee9e3e..e066990 100644
--- a/MultilineGreyText/RefactCompletionCommandHandler.cs
+++ b/MultilineGreyText/RefactCompletionCommandHandler.cs
@@ -45,6 +45,7 @@ public LanguageClientMetadata(string[] contentTypes, string clientName = null){
private int version = 0;
private bool hasCompletionUpdated = false;
+ private Task completionTask = null;
//The command Handler processes keyboard input.
internal RefactCompletionCommandHandler(IVsTextView textViewAdapter, ITextView textView, RefactCompletionHandlerProvider provider){
@@ -79,12 +80,22 @@ void LoadLsp(String file, ITextDocument doc){
}
//Adds file to LSP
- void ConnectFileToLSP(){
+ async Task ConnectFileToLSP(){
if (!client.ContainsFile(filePath)){
- client.AddFile(filePath, doc.TextBuffer.CurrentSnapshot.GetText());
-
- //listen for changes
- ((ITextBuffer2)doc.TextBuffer).ChangedHighPriority += ChangeEvent;
+ await client.AddFile(filePath, doc.TextBuffer.CurrentSnapshot.GetText());
+ }else{
+ version++;
+ TextDocumentContentChangeEvent[] contentChanges = new TextDocumentContentChangeEvent[1];
+ var snapshot = doc.TextBuffer.CurrentSnapshot;
+ contentChanges[0] = new TextDocumentContentChangeEvent {
+ Text = snapshot.GetText(),
+ Range = new Range {
+ Start = new Position(0, 0),
+ End = new Position(snapshot.Lines.Count(), 0)
+ },
+ RangeLength = snapshot.Lines.Count()
+ };
+ await this.client.InvokeTextDocumentDidChangeAsync(fileURI, version, contentChanges);
}
}
@@ -98,34 +109,6 @@ private MultilineGreyTextTagger GetTagger(){
}
}
- //Send changes to LSP
- private void ChangeEvent(object sender, TextContentChangedEventArgs args){
- version++;
-
- //converts the changelist to be readable by LSP
- TextDocumentContentChangeEvent[] contentChanges = args.Changes.Reverse().Select(change => {
- int startLine, startColumn;
- textViewAdapter.GetLineAndColumn(change.OldSpan.Start, out startLine, out startColumn);
- int endLine, endColumn;
- textViewAdapter.GetLineAndColumn(change.OldSpan.End, out endLine, out endColumn);
-
- return new TextDocumentContentChangeEvent{
- Text = change.NewText,
- Range = new Range{
- Start = new Position(startLine, startColumn),
- End = new Position(endLine, endColumn)
- },
- RangeLength = change.OldSpan.Length
- };
- }).ToArray();
-
- //sends changes to LSP
- if (contentChanges.Length > 0){
- contentChanges[0].Text = m_textView.TextBuffer.CurrentSnapshot.GetText();
- this.client.InvokeTextDocumentDidChangeAsync(fileURI, version, contentChanges);
- }
- }
-
//required by interface just boiler plate
public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText){
return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
@@ -137,7 +120,7 @@ public bool IsInline(int lineN){
}
//gets recommendations from LSP
- public void GetLSPCompletions(){
+ public async void GetLSPCompletions(){
if (!General.Instance.PauseCompletion){
SnapshotPoint? caretPoint = m_textView.Caret.Position.Point.GetPoint(textBuffer => (!textBuffer.ContentType.IsOfType("projection")), PositionAffinity.Predecessor);
@@ -159,27 +142,27 @@ public void GetLSPCompletions(){
return;
}
- if (!client.ContainsFile(filePath)){
- ConnectFileToLSP();
- }
+ await ConnectFileToLSP();
hasCompletionUpdated = false;
bool multiline = !IsInline(lineN);
- var refactRes = client.RefactCompletion(m_textView.TextBuffer.Properties, filePath, lineN, multiline ? 0 : characterN, multiline);
- ShowRefactSuggestion(refactRes, new Tuple(lineN, characterN));
+ if(completionTask == null || completionTask.IsCompleted){
+ completionTask = client.RefactCompletion(m_textView.TextBuffer.Properties, filePath, lineN, multiline ? 0 : characterN, multiline);
+ var s = await completionTask;
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+ if (completionTask == null || completionTask.IsCompleted){
+ ShowRefactSuggestion(s, lineN, characterN);
+ }
+ }
}
}
}
}
//sends lsp reccomendations to grey text tagger to be dispalyed
- public async void ShowRefactSuggestion(Task res, Object position){
- var p = position as Tuple;
- int lineN = p.Item1;
- int characterN = p.Item2;
+ public void ShowRefactSuggestion(String s, int lineN, int characterN){
- String s = await res;
- if (res != null){
+ if (!string.IsNullOrEmpty(s)){
//the caret must be in a non-projection location
SnapshotPoint? caretPoint = m_textView.Caret.Position.Point.GetPoint(textBuffer => (!textBuffer.ContentType.IsOfType("projection")), PositionAffinity.Predecessor);
if (!caretPoint.HasValue){
@@ -265,10 +248,10 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv
//gets lsp completions on added character or deletions
if (!typedChar.Equals(char.MinValue) || commandID == (uint)VSConstants.VSStd2KCmdID.RETURN){
- GetLSPCompletions();
+ _ = Task.Run(() => GetLSPCompletions());
handled = true;
}else if (commandID == (uint)VSConstants.VSStd2KCmdID.BACKSPACE || commandID == (uint)VSConstants.VSStd2KCmdID.DELETE){
- GetLSPCompletions();
+ _ = Task.Run(()=>GetLSPCompletions());
handled = true;
}
diff --git a/MultilineGreyText/RefactLanguageClient.cs b/MultilineGreyText/RefactLanguageClient.cs
index fd42eef..852a093 100644
--- a/MultilineGreyText/RefactLanguageClient.cs
+++ b/MultilineGreyText/RefactLanguageClient.cs
@@ -1,342 +1,364 @@
-using Microsoft.VisualStudio.LanguageServer.Client;
-using Microsoft.VisualStudio.Shell;
-using Microsoft.VisualStudio.Utilities;
-using StreamJsonRpc;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Reflection;
-using System.Threading;
-using System.Threading.Tasks;
-using Task = System.Threading.Tasks.Task;
-using Microsoft.VisualStudio.Threading;
-using Newtonsoft.Json.Linq;
-using System.ComponentModel.Composition;
-using Microsoft.VisualStudio.LanguageServer.Protocol;
-using Microsoft.VisualStudio.Shell.Interop;
-using static System.Net.Mime.MediaTypeNames;
-using System.Windows.Forms;
-using System.Windows.Media;
-using Microsoft.VisualStudio;
-using Community.VisualStudio.Toolkit;
-using System.Windows.Controls;
-using System.Windows;
-
-// VS uses LSP in the background but doesn't play nicely with custom LSP servers for
-// languages that already have a default LSP.
-
-
-namespace RefactAI{
-
- //the lsp client for refact
- //any means the lsp should start up for any file extension
- [ContentType("any")]
- [Export(typeof(ILanguageClient))]
- [RunOnContext(RunningContext.RunOnHost)]
-
- public class RefactLanguageClient : ILanguageClient, ILanguageClientCustomMessage2, IDisposable{
- //service provider is used to get the IVsServiceProvider which is needed for the status bar
- [Import]
- internal SVsServiceProvider ServiceProvider { get; set; }
-
- private Connection c;
- private Process serverProcess = null;
- private StatusBar statusBar;
-
- //lsp instance
- internal static RefactLanguageClient Instance{
- get;
- set;
- }
-
- //rpc for sending requests to the lsp
- internal JsonRpc Rpc{
- get;
- set;
- }
-
- //checks if lsp has started to load used to detect presence of lsp
- public bool loaded = false;
-
- //StartAsync used to start the lsp
- public event AsyncEventHandler StartAsync;
-
- //StopAsync used to stop the lsp
- public event AsyncEventHandler StopAsync;
-
- //name of lsp
- public string Name => "Refact Language Extension";
-
- //intialization options
- public object InitializationOptions => null;
-
- //files to watch
- public IEnumerable FilesToWatch => null;
-
- //middle layer used to intercep messages to/from lsp
- public object MiddleLayer => RefactMiddleLayer.Instance;
-
- //custom message target
- public object CustomMessageTarget => null;
-
- //show notification on initialize failed setting
- public bool ShowNotificationOnInitializeFailed => true;
-
- //files lsp is aware of
- internal HashSet files;
-
- //constructor
- public RefactLanguageClient(){
- Instance = this;
- files = new HashSet();
- statusBar = new StatusBar();
- }
-
- //gets/sets lsp configuration sections
- public IEnumerable ConfigurationSections{
- get{
- yield return "";
- }
- }
-
- //sends file to lsp and adds it to known file set
- public async void AddFile(String filePath, String text){
-
- //wait for the rpc
- while (Rpc == null) await Task.Delay(1);
-
- //dont send the file to the lsp if the lsp already knows about it
- if (ContainsFile(filePath)){
- return;
- }
-
- //add file to known file set
- files.Add(filePath);
-
- //message to send to lsp
- var openParam = new DidOpenTextDocumentParams{
- TextDocument = new TextDocumentItem{
- Uri = new Uri(filePath),
- LanguageId = filePath.Substring(filePath.LastIndexOf(".") + 1),
- Version = 0,
- Text = text
- }
- };
-
- //send message to lsp catch any communication errors
- try{
- await Rpc.NotifyWithParameterObjectAsync("textDocument/didOpen", openParam);
- }catch (Exception e){
- Debug.Write("InvokeTextDocumentDidChangeAsync Server Exception " + e.ToString());
- ShowStatusBarError("Server Exception: \n" + e.Message);
- }
- }
-
- //does lsp know about the file?
- public bool ContainsFile(String file){
- return files.Contains(file);
- }
-
- //activates the lsp using stdin/stdout to communicate with it
- public async Task ActivateAsync(CancellationToken token){
- files.Clear();
- ProcessStartInfo info = new ProcessStartInfo();
-
- info.FileName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Resources", @"refact-lsp.exe");
-
- info.Arguments = GetArgs();
- info.RedirectStandardInput = true;
- info.RedirectStandardOutput = true;
- info.UseShellExecute = false;
-
- //tells the lsp not to show the window
- //turning this off can be useful for debugging
- info.CreateNoWindow = true;
-
- //starts the lsp process
- Process process = new Process();
- process.StartInfo = info;
-
- if (process.Start()){
- //returns the connection for future use
- this.c = new Connection(process.StandardOutput.BaseStream, process.StandardInput.BaseStream);
- return c;
- }
-
- return null;
- }
-
- //get command line args for the lsp
- String GetArgs(){
- String args = "";
- args += "--basic-telemetry ";
-
- if (General.Instance.TelemetryCodeSnippets){
- args += "--snippet-telemetry ";
- }
-
- args += "--address-url " + (String.IsNullOrWhiteSpace(General.Instance.AddressURL) ? "Refact" : General.Instance.AddressURL) + " ";
- args += "--api-key " + (String.IsNullOrWhiteSpace(General.Instance.APIKey) ? "vs-classic-no-key" : General.Instance.APIKey) + " ";
- args += "--lsp-stdin-stdout 1";
-
- return args;
- }
-
- //used to start loading lsp
- public async Task OnLoadedAsync(){
- if (StartAsync != null){
- loaded = true;
- await StartAsync.InvokeAsync(this, EventArgs.Empty);
- statusBar = new StatusBar();
- }
- }
-
- //stops the lsp
- public async Task StopServerAsync(){
- if (StopAsync != null){
- await StopAsync.InvokeAsync(this, EventArgs.Empty);
- }
- }
-
- //returns the completed task when the lsp has finished loading
- public Task OnServerInitializedAsync(){
- return Task.CompletedTask;
- }
-
- //used to set up custom messages
- public Task AttachForCustomMessageAsync(JsonRpc rpc){
- this.Rpc = rpc;
- return Task.CompletedTask;
- }
-
- //server initialize failed
- public Task OnServerInitializeFailedAsync(ILanguageClientInitializationInfo initializationState){
- string message = "Oh no! Refact Language Client failed to activate, now we can't test LSP! :(";
- string exception = initializationState.InitializationException?.ToString() ?? string.Empty;
- message = $"{message}\n {exception}";
-
- var failureContext = new InitializationFailureContext(){
- FailureMessage = message,
- };
-
- ShowStatusBarError(message);
-
- return Task.FromResult(failureContext);
- }
-
- //manually sends change message to lsp
- public async void InvokeTextDocumentDidChangeAsync(Uri fileURI, int version, TextDocumentContentChangeEvent[] contentChanges){
- if (Rpc != null && ContainsFile(fileURI.ToString())){
- var changesParam = new DidChangeTextDocumentParams{
- ContentChanges = contentChanges,
- TextDocument = new VersionedTextDocumentIdentifier{
- Version = version,
- Uri = fileURI,
- }
- };
-
- try{
- await Rpc.NotifyWithParameterObjectAsync("textDocument/didChange", changesParam);
- }catch(Exception e){
- Debug.Write("InvokeTextDocumentDidChangeAsync Server Exception " + e.ToString());
- ShowStatusBarError("Server Exception: \n" + e.Message);
- }
- }
- }
-
- public async Task RefactCompletion(PropertyCollection props, String fileUri, int lineN, int character, bool multiline){
- //Make sure lsp has finished loading
- if(this.Rpc == null){
- return null;
- }
- if (!ContainsFile(fileUri)){
- return null;
- }
- //catching server errors
- try{
- //args to send for refact/getCompletions
- var argObj2 = new{
- text_document_position = new {
- textDocument = new { uri = fileUri },
- position = new { line = lineN, character = character },
- },
- parameters = new { max_new_tokens = 50, temperature = 0.2f },
- multiline = multiline,
- textDocument = new { uri = fileUri },
- position = new{ line = lineN, character = character }
- };
- ShowLoadingStatusBar();
-
- var res = await this.Rpc.InvokeWithParameterObjectAsync("refact/getCompletions", argObj2);
-
- //process results
- List suggestions = new List();
- foreach (var s in res["choices"]){
- suggestions.Add(s["code_completion"].ToString());
- }
-
- ShowDefaultStatusBar();
-
- return suggestions[0];
- }catch (Exception e){
- Debug.Write("Error " + e.ToString());
- ShowStatusBarError("Error: \n" + e.Message);
- return null;
- }
- }
-
- async void ShowDefaultStatusBar(){
- await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
- statusBar.ShowDefaultStatusBar();
- }
-
- async void ShowStatusBarError(String error){
- await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
- statusBar.ShowStatusBarError(error);
- }
-
- async void ShowLoadingStatusBar(){
- await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
- statusBar.ShowLoadingSymbol();
- }
-
- public void Dispose(){
- if(serverProcess != null){
- serverProcess.Kill();
- serverProcess.WaitForExit();
- serverProcess.Dispose();
- }
- }
-
- //ilanguage client middle layer
- internal class RefactMiddleLayer : ILanguageClientMiddleLayer{
- internal readonly static RefactMiddleLayer Instance = new RefactMiddleLayer();
-
- //returns true if the method should be handled by the middle layer
- public bool CanHandle(string methodName){
- return true;
- }
-
- //intercepts new files and adds them to the knonw file set
- public Task HandleNotificationAsync(string methodName, JToken methodParam, Func sendNotification){
- Task t = sendNotification(methodParam);
- if (methodName == "textDocument/didOpen"){
- RefactLanguageClient.Instance.files.Add(methodParam["textDocument"]["uri"].ToString());
- }
- return t;
- }
-
- //intercepts requests for completions sent to the lsp
- //returns an empty list to avoid showing default completions
- public async Task HandleRequestAsync(string methodName, JToken methodParam, Func> sendRequest){
- var result = await sendRequest(methodParam);
- if(methodName == "textDocument/completion"){
- return JToken.Parse("[]");
- }else{
- return result;
- }
- }
- }
- }
-}
+using Microsoft.VisualStudio.LanguageServer.Client;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Utilities;
+using StreamJsonRpc;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Task = System.Threading.Tasks.Task;
+using Microsoft.VisualStudio.Threading;
+using Newtonsoft.Json.Linq;
+using System.ComponentModel.Composition;
+using Microsoft.VisualStudio.LanguageServer.Protocol;
+using Microsoft.VisualStudio.Shell.Interop;
+using static System.Net.Mime.MediaTypeNames;
+using System.Windows.Forms;
+using System.Windows.Media;
+using Microsoft.VisualStudio;
+using Community.VisualStudio.Toolkit;
+using System.Windows.Controls;
+using System.Windows;
+using System.Linq;
+using System.Management;
+
+namespace RefactAI{
+
+ //the lsp client for refact
+ //any means the lsp should start up for any file extension
+ [ContentType("any")]
+ [Export(typeof(ILanguageClient))]
+ [RunOnContext(RunningContext.RunOnHost)]
+
+ public class RefactLanguageClient : ILanguageClient, ILanguageClientCustomMessage2, IDisposable{
+ //service provider is used to get the IVsServiceProvider which is needed for the status bar
+ [Import]
+ internal SVsServiceProvider ServiceProvider { get; set; }
+
+ private Connection c;
+ private Process serverProcess = null;
+ private StatusBar statusBar;
+
+ //lsp instance
+ internal static RefactLanguageClient Instance{
+ get;
+ set;
+ }
+
+ //rpc for sending requests to the lsp
+ internal JsonRpc Rpc{
+ get;
+ set;
+ }
+
+ //checks if lsp has started to load used to detect presence of lsp
+ public bool loaded = false;
+
+ //StartAsync used to start the lsp
+ public event AsyncEventHandler StartAsync;
+
+ //StopAsync used to stop the lsp
+ public event AsyncEventHandler StopAsync;
+
+ //name of lsp
+ public string Name => "Refact Language Extension";
+
+ //intialization options
+ public object InitializationOptions => null;
+
+ //files to watch
+ public IEnumerable FilesToWatch => null;
+
+ //middle layer used to intercep messages to/from lsp
+ public object MiddleLayer => RefactMiddleLayer.Instance;
+
+ //custom message target
+ public object CustomMessageTarget => null;
+
+ //show notification on initialize failed setting
+ public bool ShowNotificationOnInitializeFailed => true;
+
+ //files lsp is aware of
+ internal HashSet files;
+
+ //constructor
+ public RefactLanguageClient(){
+ Instance = this;
+ files = new HashSet();
+ statusBar = new StatusBar();
+ }
+
+ //gets/sets lsp configuration sections
+ public IEnumerable ConfigurationSections{
+ get{
+ yield return "";
+ }
+ }
+
+ //sends file to lsp and adds it to known file set
+ public async Task AddFile(String filePath, String text){
+
+ //wait for the rpc
+ while (Rpc == null) await Task.Delay(1);
+
+ //dont send the file to the lsp if the lsp already knows about it
+ if (ContainsFile(filePath)){
+ return;
+ }
+
+ //message to send to lsp
+ var openParam = new DidOpenTextDocumentParams{
+ TextDocument = new TextDocumentItem{
+ Uri = new Uri(filePath),
+ LanguageId = filePath.Substring(filePath.LastIndexOf(".") + 1),
+ Version = 0,
+ Text = text
+ }
+ };
+
+ //send message to lsp catch any communication errors
+ try{
+ await Rpc.NotifyWithParameterObjectAsync("textDocument/didOpen", openParam);
+ //add file to known file set
+ files.Add(filePath);
+ }catch (Exception e){
+ Debug.Write("AddFile Server Exception " + e.ToString());
+ ShowStatusBarError("Server Exception: \n" + e.Message);
+ }
+ }
+
+ //does lsp know about the file?
+ public bool ContainsFile(String file){
+ return files.Contains(file);
+ }
+
+ //activates the lsp using stdin/stdout to communicate with it
+ public async Task ActivateAsync(CancellationToken token){
+ files.Clear();
+ ProcessStartInfo info = new ProcessStartInfo();
+
+ info.FileName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Resources", @"refact-lsp.exe");
+
+ info.Arguments = GetArgs();
+ info.RedirectStandardInput = true;
+ info.RedirectStandardOutput = true;
+ info.UseShellExecute = false;
+
+ //tells the lsp not to show the window
+ //turning this off can be useful for debugging
+ info.CreateNoWindow = true;
+
+ //starts the lsp process
+ serverProcess = new Process();
+ serverProcess.StartInfo = info;
+
+ if (serverProcess.Start()){
+ //returns the connection for future use
+ this.c = new Connection(serverProcess.StandardOutput.BaseStream, serverProcess.StandardInput.BaseStream);
+ return c;
+ }
+
+ return null;
+ }
+
+ //get command line args for the lsp
+ String GetArgs(){
+ String args = "";
+ args += "--basic-telemetry ";
+
+ if (General.Instance.TelemetryCodeSnippets){
+ args += "--snippet-telemetry ";
+ }
+
+ args += "--address-url " + (String.IsNullOrWhiteSpace(General.Instance.AddressURL) ? "Refact" : General.Instance.AddressURL) + " ";
+ args += "--api-key " + (String.IsNullOrWhiteSpace(General.Instance.APIKey) ? "vs-classic-no-key" : General.Instance.APIKey) + " ";
+ args += "--lsp-stdin-stdout 1";
+
+ return args;
+ }
+
+ //used to start loading lsp
+ public async Task OnLoadedAsync(){
+ if (StartAsync != null){
+ loaded = true;
+ await StartAsync.InvokeAsync(this, EventArgs.Empty);
+ }
+ }
+
+ //stops the lsp
+ public async Task StopServerAsync(){
+ if (StopAsync != null){
+ await StopAsync.InvokeAsync(this, EventArgs.Empty);
+ }
+ }
+
+ //returns the completed task when the lsp has finished loading
+ public Task OnServerInitializedAsync(){
+ return Task.CompletedTask;
+ }
+
+ //used to set up custom messages
+ public Task AttachForCustomMessageAsync(JsonRpc rpc){
+ this.Rpc = rpc;
+ return Task.CompletedTask;
+ }
+
+ //server initialize failed
+ public Task OnServerInitializeFailedAsync(ILanguageClientInitializationInfo initializationState){
+ string message = "Oh no! Refact Language Client failed to activate, now we can't test LSP! :(";
+ string exception = initializationState.InitializationException?.ToString() ?? string.Empty;
+ message = $"{message}\n {exception}";
+
+ var failureContext = new InitializationFailureContext(){
+ FailureMessage = message,
+ };
+
+ ShowStatusBarError(message);
+
+ return Task.FromResult(failureContext);
+ }
+
+ //manually sends change message to lsp
+ public async Task InvokeTextDocumentDidChangeAsync(Uri fileURI, int version, TextDocumentContentChangeEvent[] contentChanges){
+ if (Rpc != null && ContainsFile(fileURI.ToString())){
+ var changesParam = new DidChangeTextDocumentParams{
+ ContentChanges = contentChanges,
+ TextDocument = new VersionedTextDocumentIdentifier{
+ Version = version,
+ Uri = fileURI,
+ }
+ };
+
+ try{
+ await Rpc.NotifyWithParameterObjectAsync("textDocument/didChange", changesParam);
+ }catch(Exception e){
+ Debug.Write("InvokeTextDocumentDidChangeAsync Server Exception " + e.ToString());
+ ShowStatusBarError("Server Exception: \n" + e.Message);
+ }
+ }
+ }
+
+ public async Task RefactCompletion(PropertyCollection props, String fileUri, int lineN, int character, bool multiline){
+ //Make sure lsp has finished loading
+ if(this.Rpc == null){
+ return null;
+ }
+ if (!ContainsFile(fileUri)){
+ return null;
+ }
+ //catching server errors
+ try{
+ //args to send for refact/getCompletions
+ var argObj2 = new{
+ text_document_position = new {
+ textDocument = new { uri = fileUri },
+ position = new { line = lineN, character = character },
+ },
+ parameters = new { max_new_tokens = 50, temperature = 0.2f },
+ multiline = multiline,
+ textDocument = new { uri = fileUri },
+ position = new{ line = lineN, character = character }
+ };
+ await this.Rpc.DispatchCompletion;
+ ShowLoadingStatusBar();
+
+ var res = await this.Rpc.InvokeWithParameterObjectAsync("refact/getCompletions", argObj2);
+ ShowDefaultStatusBar();
+
+ var choices = res["choices"];
+
+ if (!(choices != null && choices.Count() > 0)){
+ return null;
+ }
+ //process results
+ List suggestions = new List();
+ foreach (var s in res["choices"]){
+ var code_completion = s["code_completion"];
+ if (code_completion != null){
+ suggestions.Add(code_completion.ToString());
+ }
+ }
+
+ if (suggestions.Count > 0){
+ return suggestions[0];
+ }else{
+ return null;
+ }
+ }
+ catch (Exception e){
+ Debug.Write("Error " + e.ToString());
+ ShowStatusBarError("Error: \n" + e.Message);
+ return null;
+ }
+ }
+
+ async void ShowDefaultStatusBar(){
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+ if (!statusBar.IsInitialized()){
+ statusBar.InitStatusBar();
+ }
+ statusBar.ShowDefaultStatusBar();
+ }
+
+ async void ShowStatusBarError(String error){
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+ if (!statusBar.IsInitialized()){
+ statusBar.InitStatusBar();
+ }
+ statusBar.ShowStatusBarError(error);
+ }
+
+ async void ShowLoadingStatusBar(){
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+ if (!statusBar.IsInitialized()){
+ statusBar.InitStatusBar();
+ }
+ statusBar.ShowLoadingSymbol();
+ }
+
+ public void Dispose(){
+ if(serverProcess != null){
+ try{
+ serverProcess.Kill();
+ serverProcess.WaitForExit();
+ serverProcess.Dispose();
+ }catch(Exception e){
+ Debug.Write("Dispose" + e.ToString());
+ }
+ }
+ }
+
+ //ilanguage client middle layer
+ internal class RefactMiddleLayer : ILanguageClientMiddleLayer{
+ internal readonly static RefactMiddleLayer Instance = new RefactMiddleLayer();
+
+ //returns true if the method should be handled by the middle layer
+ public bool CanHandle(string methodName){
+ return true;
+ }
+
+ //intercepts new files and adds them to the knonw file set
+ public Task HandleNotificationAsync(string methodName, JToken methodParam, Func sendNotification){
+ Task t = sendNotification(methodParam);
+ if (methodName == "textDocument/didOpen"){
+ RefactLanguageClient.Instance.files.Add(methodParam["textDocument"]["uri"].ToString());
+ }
+ return t;
+ }
+
+ //intercepts requests for completions sent to the lsp
+ //returns an empty list to avoid showing default completions
+ public async Task HandleRequestAsync(string methodName, JToken methodParam, Func> sendRequest){
+ var result = await sendRequest(methodParam);
+ if(methodName == "textDocument/completion"){
+ return JToken.Parse("[]");
+ }else{
+ return result;
+ }
+ }
+ }
+ }
+}
diff --git a/MultilineGreyText/StatusBar.cs b/MultilineGreyText/StatusBar.cs
index 38ad2ff..053f6fb 100644
--- a/MultilineGreyText/StatusBar.cs
+++ b/MultilineGreyText/StatusBar.cs
@@ -15,19 +15,27 @@ internal class StatusBar{
Brush whiteBrush;
Brush errorBrush;
Brush transparentBrush;
+ bool isInitialized = false;
public StatusBar(){
stack = new StackPanel();
stack.Width = 75.0;
stack.Orientation = Orientation.Horizontal;
+ ShowDefaultStatusBar();
+ }
+
+ public bool IsInitialized(){
+ return isInitialized;
+ }
+
+ public void InitStatusBar(){
+ isInitialized = true;
panel = VisualTreeUtils.FindChild(Application.Current.MainWindow, childName: "StatusBarPanel") as Panel;
whiteBrush = new SolidColorBrush(Colors.White);
errorBrush = new SolidColorBrush(Colors.Red);
transparentBrush = new SolidColorBrush(Colors.Transparent);
- panel.Children.Add(stack);
- ShowDefaultStatusBar();
+ panel.Children.Insert(3, stack);
}
-
public void ShowDefaultStatusBar(){
stack.Children.Clear();
stack.Background = transparentBrush;