diff --git a/src/OneWare.Essentials/EditorExtensions/SemanticToken.cs b/src/OneWare.Essentials/EditorExtensions/SemanticToken.cs new file mode 100644 index 0000000..d3f39fc --- /dev/null +++ b/src/OneWare.Essentials/EditorExtensions/SemanticToken.cs @@ -0,0 +1,12 @@ +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace OneWare.Essentials.EditorExtensions; + +public struct SemanticToken +{ + public int Line { get; init; } + public int StartCharacter { get; init; } + public int Length { get; init; } + public SemanticTokenType TokenType { get; init; } + public SemanticTokenModifier[] TokenModifiers { get; init; } +} \ No newline at end of file diff --git a/src/OneWare.Essentials/EditorExtensions/TextModificationSegment.cs b/src/OneWare.Essentials/EditorExtensions/TextModificationSegment.cs index a08e4b8..96eb2e4 100644 --- a/src/OneWare.Essentials/EditorExtensions/TextModificationSegment.cs +++ b/src/OneWare.Essentials/EditorExtensions/TextModificationSegment.cs @@ -11,7 +11,9 @@ public TextModificationSegment(int startOffset, int endOffset) EndOffset = endOffset; } - public IBrush? Brush { get; set; } - + public IBrush? Foreground { get; set; } + + public IBrush? Background { get; set; } + public TextDecorationCollection? Decorations { get; set; } } \ No newline at end of file diff --git a/src/OneWare.Essentials/EditorExtensions/TextModificationService.cs b/src/OneWare.Essentials/EditorExtensions/TextModificationService.cs index aeba133..6551dc7 100644 --- a/src/OneWare.Essentials/EditorExtensions/TextModificationService.cs +++ b/src/OneWare.Essentials/EditorExtensions/TextModificationService.cs @@ -1,5 +1,4 @@ -using Avalonia.Media; -using AvaloniaEdit.Document; +using AvaloniaEdit.Document; using AvaloniaEdit.Rendering; namespace OneWare.Essentials.EditorExtensions; @@ -70,16 +69,16 @@ protected override void ColorizeLine(DocumentLine line) foreach (var overlap in overlaps) { if (overlap.EndOffset > line.EndOffset) overlap.EndOffset = line.EndOffset; - ChangeLinePart(overlap.StartOffset, overlap.EndOffset, (x) => ApplyChanges(x, overlap.Brush, overlap.Decorations)); + ChangeLinePart(overlap.StartOffset, overlap.EndOffset, (x) => ApplyChanges(x, overlap)); } } } } - private void ApplyChanges(VisualLineElement element, IBrush? color, TextDecorationCollection? decorations) + private void ApplyChanges(VisualLineElement element, TextModificationSegment segment) { - // This is where you do anything with the line - if (color != null) element.TextRunProperties.SetForegroundBrush(color); - if (decorations != null) element.TextRunProperties.SetTextDecorations(decorations); + if (segment.Foreground != null) element.TextRunProperties.SetForegroundBrush(segment.Foreground); + if (segment.Background != null) element.TextRunProperties.SetBackgroundBrush(segment.Background); + if (segment.Decorations != null) element.TextRunProperties.SetTextDecorations(segment.Decorations); } } \ No newline at end of file diff --git a/src/OneWare.Essentials/EditorExtensions/WordHighlightRenderer.cs b/src/OneWare.Essentials/EditorExtensions/WordHighlightRenderer.cs index 1823c06..fd99235 100644 --- a/src/OneWare.Essentials/EditorExtensions/WordHighlightRenderer.cs +++ b/src/OneWare.Essentials/EditorExtensions/WordHighlightRenderer.cs @@ -6,13 +6,13 @@ namespace OneWare.Essentials.EditorExtensions { public class WordHighlightRenderer : IBackgroundRenderer { - public const string BracketHighlight = "Bracket highlight"; - public static readonly Color DefaultBackground = Color.FromArgb(20, 0, 200, 255); public static readonly Color DefaultBorder = Color.FromArgb(150, 0, 200, 255); + private readonly TextView _textView; private Brush? _backgroundBrush; private Pen? _borderPen; + public WordSearchResult? Result { get; set; } public WordHighlightRenderer(TextView textView) diff --git a/src/OneWare.Essentials/Helpers/RangeHelper.cs b/src/OneWare.Essentials/Helpers/RangeHelper.cs index da46c6a..2460f82 100644 --- a/src/OneWare.Essentials/Helpers/RangeHelper.cs +++ b/src/OneWare.Essentials/Helpers/RangeHelper.cs @@ -7,12 +7,14 @@ namespace OneWare.Essentials.Helpers; public static class RangeHelper { - public static TextModificationSegment GenerateTextModification(this Range range, TextDocument document, IBrush brush) + public static TextModificationSegment GenerateTextModification(this Range range, TextDocument document, IBrush? foreground, IBrush? background = null, TextDecorationCollection? decorations = null) { var offset = document.GetStartAndEndOffset(range); return new TextModificationSegment(offset.startOffset, offset.endOffset) { - Brush = brush + Foreground = foreground, + Background = background, + Decorations = decorations }; } } \ No newline at end of file diff --git a/src/OneWare.Essentials/Helpers/SemanticTokenHelper.cs b/src/OneWare.Essentials/Helpers/SemanticTokenHelper.cs new file mode 100644 index 0000000..e6eb583 --- /dev/null +++ b/src/OneWare.Essentials/Helpers/SemanticTokenHelper.cs @@ -0,0 +1,60 @@ +using System.Collections.Immutable; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OneWare.Essentials.EditorExtensions; + +namespace OneWare.Essentials.Helpers; + +public static class SemanticTokenHelper +{ + public static List ParseSemanticTokens(ImmutableArray data, SemanticTokensLegend legend) + { + var tokens = new List(); + var line = 0; + var character = 0; + + var types = legend.TokenTypes.ToArray(); + var modifiers = legend.TokenModifiers.ToArray(); + + for (var i = 0; i < data.Length; i += 5) + { + var deltaLine = data[i]; + var deltaStartCharacter = data[i + 1]; + var length = data[i + 2]; + var tokenType = data[i + 3]; + var tokenModifiersBitset = data[i + 4]; + + line += deltaLine; + if (deltaLine == 0) + { + character += deltaStartCharacter; + } + else + { + character = deltaStartCharacter; + } + + if(tokenType < 0 || tokenType >= types!.Length) + throw new InvalidOperationException("Invalid token type"); + + var tokenModifiers = new List(); + for (var bit = 0; bit < modifiers.Length; bit++) + { + if ((tokenModifiersBitset & (1 << bit)) != 0) + { + tokenModifiers.Add(modifiers[bit]); + } + } + + tokens.Add(new SemanticToken + { + Line = line, + StartCharacter = character, + Length = length, + TokenType = types![tokenType], + TokenModifiers = tokenModifiers.ToArray() + }); + } + + return tokens; + } +} \ No newline at end of file diff --git a/src/OneWare.Essentials/LanguageService/ILanguageService.cs b/src/OneWare.Essentials/LanguageService/ILanguageService.cs index 5e8cb36..aaef372 100644 --- a/src/OneWare.Essentials/LanguageService/ILanguageService.cs +++ b/src/OneWare.Essentials/LanguageService/ILanguageService.cs @@ -1,4 +1,5 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OneWare.Essentials.EditorExtensions; using OneWare.Essentials.ViewModels; using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; @@ -54,6 +55,7 @@ public interface ILanguageService public Task?> RequestDeclarationAsync(string fullPath, Position pos); public Task RequestSymbolsAsync(string fullPath); public Task?> RequestDocumentColorAsync(string fullPath); + public Task?> RequestSemanticTokensFullAsync(string fullPath); public Task RequestFormattingAsync(string fullPath); public Task RequestRangeFormattingAsync(string fullPath, Range range); public Task ExecuteCommandAsync(Command cmd); diff --git a/src/OneWare.Essentials/LanguageService/ITypeAssistance.cs b/src/OneWare.Essentials/LanguageService/ITypeAssistance.cs index 8342cd8..3a211c9 100644 --- a/src/OneWare.Essentials/LanguageService/ITypeAssistance.cs +++ b/src/OneWare.Essentials/LanguageService/ITypeAssistance.cs @@ -22,6 +22,7 @@ public interface ITypeAssistance void Format(); void TextEntering(TextInputEventArgs e); void TextEntered(TextInputEventArgs e); + void CaretPositionChanged(int offset); Task?> GetQuickMenuAsync(int offset); Task GetHoverInfoAsync(int offset); Task GetActionOnControlWordAsync(int offset); diff --git a/src/OneWare.Essentials/LanguageService/LanguageServiceBase.cs b/src/OneWare.Essentials/LanguageService/LanguageServiceBase.cs index 59caa9e..a1ea250 100644 --- a/src/OneWare.Essentials/LanguageService/LanguageServiceBase.cs +++ b/src/OneWare.Essentials/LanguageService/LanguageServiceBase.cs @@ -1,5 +1,6 @@ using Avalonia.Threading; using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OneWare.Essentials.EditorExtensions; using OneWare.Essentials.Enums; using OneWare.Essentials.Extensions; using OneWare.Essentials.Models; @@ -156,6 +157,11 @@ public virtual void RefreshTextDocument(string fullPath, string newText) return Task.FromResult?>(null); } + public virtual Task?> RequestSemanticTokensFullAsync(string fullPath) + { + return Task.FromResult?>(null); + } + public virtual Task RequestFormattingAsync(string fullPath) { return Task.FromResult(null); diff --git a/src/OneWare.Essentials/LanguageService/LanguageServiceLsp.cs b/src/OneWare.Essentials/LanguageService/LanguageServiceLsp.cs index a78de90..85c0e2c 100644 --- a/src/OneWare.Essentials/LanguageService/LanguageServiceLsp.cs +++ b/src/OneWare.Essentials/LanguageService/LanguageServiceLsp.cs @@ -14,6 +14,7 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Window; using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; +using OneWare.Essentials.EditorExtensions; using OneWare.Essentials.Helpers; using OneWare.Essentials.Models; using OneWare.Essentials.Services; @@ -187,6 +188,20 @@ private async Task InitAsync(Stream input, Stream output, Action(false), + // Full = new Supports(true) + // }, + // TokenTypes = new Container(SemanticTokenType.Type, SemanticTokenType.Function, SemanticTokenType.Variable, SemanticTokenType.Class, SemanticTokenType.Keyword), + // Formats = new Container(SemanticTokenFormat.Relative), + // AugmentsSyntaxTokens = true, + // MultilineTokenSupport = true + // }); // options.WithCapability(new DidChangeWatchedFilesCapability() // { // @@ -456,6 +471,32 @@ public override Task ExecuteCommandAsync(Command cmd) return Client.ExecuteCommand(cmd); } + public override async Task?> RequestSemanticTokensFullAsync(string fullPath) + { + if (Client?.ServerSettings.Capabilities.SemanticTokensProvider == null) return null; + try + { + var semanticTokens = await Client.RequestSemanticTokensFull(new SemanticTokensParams() + { + TextDocument = new TextDocumentIdentifier + { + Uri = fullPath + }, + }); + if (semanticTokens == null) return null; + + var legend = Client.ServerSettings.Capabilities.SemanticTokensProvider.Legend; + var parsedTokens = SemanticTokenHelper.ParseSemanticTokens(semanticTokens.Data, legend); + return parsedTokens; + } + catch (Exception e) + { + ContainerLocator.Container.Resolve()?.Error(e.Message, e); + } + + return null; + } + public override async Task RequestCompletionAsync(string fullPath, Position pos, CompletionTriggerKind triggerKind, string? triggerChar) { var cts = new CancellationTokenSource(); diff --git a/src/OneWare.Essentials/LanguageService/TypeAssistanceBase.cs b/src/OneWare.Essentials/LanguageService/TypeAssistanceBase.cs index af4d560..f8b69ed 100644 --- a/src/OneWare.Essentials/LanguageService/TypeAssistanceBase.cs +++ b/src/OneWare.Essentials/LanguageService/TypeAssistanceBase.cs @@ -106,7 +106,7 @@ protected virtual Task TextEnteredAsync(TextInputEventArgs e) return Task.CompletedTask; } - + public virtual void TextEntering(TextInputEventArgs e) { } @@ -116,6 +116,11 @@ public void TextEntered(TextInputEventArgs e) _ = TextEnteredAsync(e); } + public virtual void CaretPositionChanged(int offset) + { + + } + public virtual Task?> GetQuickMenuAsync(int offset) { return Task.FromResult?>(null); diff --git a/src/OneWare.Essentials/LanguageService/TypeAssistanceLanguageService.cs b/src/OneWare.Essentials/LanguageService/TypeAssistanceLanguageService.cs index 6b3f676..b494c45 100644 --- a/src/OneWare.Essentials/LanguageService/TypeAssistanceLanguageService.cs +++ b/src/OneWare.Essentials/LanguageService/TypeAssistanceLanguageService.cs @@ -40,15 +40,17 @@ public abstract class TypeAssistanceLanguageService : TypeAssistanceBase private TimeSpan _lastCompletionItemResolveTime = DateTime.Now.TimeOfDay; private TimeSpan _lastEditTime = DateTime.Now.TimeOfDay; - private TimeSpan _lastRefreshTime = DateTime.Now.TimeOfDay; - + private TimeSpan _lastCaretChangeTime = DateTime.Now.TimeOfDay; + private TimeSpan _lastDocumentChangedRefreshTime = DateTime.Now.TimeOfDay; + private TimeSpan _lastCaretChangedRefreshTime = DateTime.Now.TimeOfDay; + private static ISettingsService SettingsService => ContainerLocator.Container.Resolve(); - - private readonly TimeSpan _timerTimeSpan = TimeSpan.FromMilliseconds(200); - protected virtual TimeSpan RefreshTime => TimeSpan.FromMilliseconds(500); - + + private readonly TimeSpan _timerTimeSpan = TimeSpan.FromMilliseconds(100); + protected virtual TimeSpan DocumentChangedRefreshTime => TimeSpan.FromMilliseconds(500); + protected virtual TimeSpan CaretChangedRefreshTime => TimeSpan.FromMilliseconds(100); + protected ILanguageService Service { get; } - protected SymbolInformationOrDocumentSymbolContainer? LastDocumentSymbols { get; private set; } protected TypeAssistanceLanguageService(IEditor evm, ILanguageService langService) : base(evm) { @@ -70,9 +72,8 @@ public override void Open() CodeBox.Document.Changed += DocumentChanged; Editor.FileSaved -= FileSaved; Editor.FileSaved += FileSaved; - } - + public override void Attach() { _dispatcherTimer?.Stop(); @@ -93,16 +94,28 @@ public override void Detach() protected virtual void Timer_Tick(object? sender, EventArgs e) { - if (_lastEditTime > _lastRefreshTime && _lastEditTime <= DateTime.Now.TimeOfDay - RefreshTime) + if (_lastEditTime > _lastDocumentChangedRefreshTime && _lastEditTime <= DateTime.Now.TimeOfDay - DocumentChangedRefreshTime) { - _lastRefreshTime = DateTime.Now.TimeOfDay; + _lastDocumentChangedRefreshTime = DateTime.Now.TimeOfDay; + + //Execute slower actions after no edit was done for 500ms CodeUpdated(); } + + if (_lastCaretChangeTime > _lastCaretChangedRefreshTime && _lastCaretChangeTime <= DateTime.Now.TimeOfDay - CaretChangedRefreshTime) + { + _lastCaretChangedRefreshTime = DateTime.Now.TimeOfDay; + + //Execute slower actions after the caret was not changed for 100ms + _ = GetDocumentHighlightAsync(); + } if (_lastCompletionItemChangedTime > _lastCompletionItemResolveTime && - _lastCompletionItemChangedTime <= DateTime.Now.TimeOfDay - RefreshTime) + _lastCompletionItemChangedTime <= DateTime.Now.TimeOfDay - CaretChangedRefreshTime) { _lastCompletionItemResolveTime = DateTime.Now.TimeOfDay; + + //Execute slower actions after the caret was not changed for 100ms _ = ResolveCompletionAsync(); } } @@ -110,7 +123,7 @@ protected virtual void Timer_Tick(object? sender, EventArgs e) protected virtual void CodeUpdated() { Service.RefreshTextDocument(CurrentFile.FullPath, CodeBox.Text); - _ = UpdateSymbolsAsync(); + _ = UpdateSemanticTokensAsync(); } private void Server_Activated(object? sender, EventArgs e) @@ -122,7 +135,7 @@ private void Server_Deactivated(object? sender, EventArgs e) { OnAssistanceDeactivated(); } - + protected override void OnAssistanceActivated() { if (!IsOpen) return; @@ -130,19 +143,21 @@ protected override void OnAssistanceActivated() base.OnAssistanceActivated(); Service.DidOpenTextDocument(CurrentFile.FullPath, Editor.CurrentDocument.Text); - _ = UpdateSymbolsAsync(); + _ = UpdateSemanticTokensAsync(); } protected override void OnAssistanceDeactivated() { if (!IsOpen) return; base.OnAssistanceDeactivated(); + Editor.Editor.ModificationService.ClearModification("caretHighlight"); + Editor.Editor.ModificationService.ClearModification("semanticTokens"); } protected virtual void DocumentChanged(object? sender, DocumentChangeEventArgs e) { if (!IsOpen || !Service.IsLanguageServiceReady) return; - + var c = ConvertChanges(e); var changes = new Container(c); Service.RefreshTextDocument(CurrentFile.FullPath, changes); @@ -152,7 +167,8 @@ protected virtual void DocumentChanged(object? sender, DocumentChangeEventArgs e private void FileSaved(object? sender, EventArgs e) { - if (Service.IsLanguageServiceReady) Service.DidSaveTextDocument(CurrentFile.FullPath, Editor.CurrentDocument.Text); + if (Service.IsLanguageServiceReady) + Service.DidSaveTextDocument(CurrentFile.FullPath, Editor.CurrentDocument.Text); } public override void Close() @@ -178,15 +194,16 @@ public override void Close() var pos = CodeBox.Document.GetLocation(offset); - var error = ContainerLocator.Container.Resolve().GetErrorsForFile(CurrentFile).OrderBy(x => x.Type) - .FirstOrDefault(error => pos.Line >= error.StartLine - && pos.Line <= error.EndLine + var error = ContainerLocator.Container.Resolve().GetErrorsForFile(CurrentFile) + .OrderBy(x => x.Type) + .FirstOrDefault(error => pos.Line >= error.StartLine + && pos.Line <= error.EndLine && pos.Column >= error.StartColumn && pos.Column <= error.EndColumn); var info = ""; - - if(error != null) info += error.Description + "\n"; - + + if (error != null) info += error.Description + "\n"; + var hover = await Service.RequestHoverAsync(CurrentFile.FullPath, new Position(pos.Line - 1, pos.Column - 1)); if (hover != null) @@ -205,7 +222,7 @@ public override void Close() if (!Service.IsLanguageServiceReady || offset > CodeBox.Document.TextLength) return menuItems; var location = CodeBox.Document.GetLocation(offset); var pos = CodeBox.Document.GetPositionFromOffset(offset); - + //Quick Fixes var error = GetErrorAtLocation(location); if (error != null && error.Diagnostic != null) @@ -224,18 +241,20 @@ public override void Close() { if (ca.IsCodeAction && ca.CodeAction != null) { - if(ca.CodeAction.Command != null) quickfixes.Add(new MenuItemViewModel(ca.CodeAction.Title) - { - Header = ca.CodeAction.Title, - Command = new RelayCommand(ExecuteCommand), - CommandParameter = ca.CodeAction.Command - }); - else if(ca.CodeAction.Edit != null) quickfixes.Add(new MenuItemViewModel(ca.CodeAction.Title) - { - Header = ca.CodeAction.Title, - Command = new AsyncRelayCommand(Service.ApplyWorkspaceEditAsync), - CommandParameter = ca.CodeAction.Edit - }); + if (ca.CodeAction.Command != null) + quickfixes.Add(new MenuItemViewModel(ca.CodeAction.Title) + { + Header = ca.CodeAction.Title, + Command = new RelayCommand(ExecuteCommand), + CommandParameter = ca.CodeAction.Command + }); + else if (ca.CodeAction.Edit != null) + quickfixes.Add(new MenuItemViewModel(ca.CodeAction.Title) + { + Header = ca.CodeAction.Title, + Command = new AsyncRelayCommand(Service.ApplyWorkspaceEditAsync), + CommandParameter = ca.CodeAction.Edit + }); } } @@ -256,7 +275,7 @@ public override void Close() Header = "Rename...", Command = new AsyncRelayCommand(StartRenameSymbolAsync), CommandParameter = prepareRefactor, - IconObservable = Application.Current?.GetResourceObservable("VsImageLib.Rename16X") + IconObservable = Application.Current?.GetResourceObservable("VsImageLib.Rename16X") }); var definition = await Service.RequestDefinitionAsync(CurrentFile.FullPath, @@ -337,7 +356,7 @@ public override void Close() protected async Task StartRenameSymbolAsync(RangeOrPlaceholderRange? range) { if (range == null) return; - + if (range.IsRange && range.Range != null) { await Task.Delay(10); @@ -366,7 +385,8 @@ private async Task RenameSymbolAsync(RangeOrPlaceholderRange range, string newNa { if (Regex.IsMatch(newName, @"\W")) { - ContainerLocator.Container.Resolve()?.Error($"Can't rename symbol to {newName}! Only letters, numbers and underscore allowed!"); + ContainerLocator.Container.Resolve() + ?.Error($"Can't rename symbol to {newName}! Only letters, numbers and underscore allowed!"); return; } @@ -382,7 +402,7 @@ await Service.ApplyWorkspaceEditAsync(new ApplyWorkspaceEditParams ContainerLocator.Container.Resolve()?.Error("Placeholder Range renaming not supported yet!"); } } - + public override async Task GetActionOnControlWordAsync(int offset) { if (!Service.IsLanguageServiceReady || offset > CodeBox.Document.TextLength) return null; @@ -403,11 +423,11 @@ await Service.ApplyWorkspaceEditAsync(new ApplyWorkspaceEditParams protected virtual async Task GoToLocationAsync(Location? location) { if (location == null) return; - + var path = Path.GetFullPath(location.Uri.GetFileSystemPath()); var file = ContainerLocator.Container.Resolve().SearchFullPath(path) as IFile; file ??= ContainerLocator.Container.Resolve().GetTemporaryFile(path); - + var dockable = await ContainerLocator.Container.Resolve().OpenFileAsync(file); if (dockable is IEditor evm) { @@ -426,7 +446,8 @@ protected override async Task TextEnteredAsync(TextInputEventArgs args) { try { - if (SettingsService.GetSettingValue("TypeAssistance_EnableAutoFormatting")) TextEnteredAutoFormat(args); + if (SettingsService.GetSettingValue("TypeAssistance_EnableAutoFormatting")) + TextEnteredAutoFormat(args); if (!Service.IsLanguageServiceReady || args.Text == null) return; @@ -436,28 +457,29 @@ protected override async Task TextEnteredAsync(TextInputEventArgs args) var beforeTriggerChar = CodeBox.CaretOffset > 1 ? CodeBox.Text[CodeBox.CaretOffset - 2] : ' '; var signatureHelpTrigger = Service.GetSignatureHelpTriggerChars(); - + if (signatureHelpTrigger.Contains(triggerChar)) //Function Parameter / Overload insight { Completion?.Close(); - await ShowSignatureHelpAsync(SignatureHelpTriggerKind.TriggerCharacter, triggerChar, false, null); + await ShowSignatureHelpAsync(SignatureHelpTriggerKind.TriggerCharacter, triggerChar, false, + null); } var completionTriggerChars = Service.GetCompletionTriggerChars(); if (completionTriggerChars.Contains(triggerChar)) { _completionBusy = true; - await ShowCompletionAsync(CompletionTriggerKind.TriggerCharacter, triggerChar); + await ShowCompletionAsync(CompletionTriggerKind.TriggerCharacter, triggerChar); } else if (CharBeforeNormalCompletion(beforeTriggerChar) && triggerChar.All(char.IsLetter)) { _completionBusy = true; await ShowCompletionAsync(CompletionTriggerKind.Invoked, triggerChar); } - + _completionBusy = false; } - + await base.TextEnteredAsync(args); } catch (Exception e) @@ -476,7 +498,7 @@ protected virtual void TextEnteredAutoFormat(TextInputEventArgs e) var minIndex = CodeBox.Document.GetLineByOffset(startIndex).Offset; var maxIndex = CodeBox.Document.GetLineByOffset(endIndex).EndOffset; var newLine = TextUtilities.GetNewLineFromDocument(CodeBox.Document, CodeBox.TextArea.Caret.Line); - + if (endIndex >= CodeBox.Text.Length) return; while (startIndex > minIndex && CodeBox.Text[endIndex] != '\n' && CodeBox.Text[startIndex] != '{' && CodeBox.Text[startIndex] != '(') startIndex--; @@ -495,7 +517,8 @@ protected virtual void TextEnteredAutoFormat(TextInputEventArgs e) caretLine++; } - replaceString = replaceString.Insert(1 + newLine.Length, $" {newLine} "); // <-- empty char for indentation + replaceString = + replaceString.Insert(1 + newLine.Length, $" {newLine} "); // <-- empty char for indentation CodeBox.Document.BeginUpdate(); CodeBox.Document.Replace(startIndex, endIndex - startIndex + 1, replaceString); @@ -507,18 +530,92 @@ protected virtual void TextEnteredAutoFormat(TextInputEventArgs e) } } - protected virtual async Task UpdateSymbolsAsync() + public override void CaretPositionChanged(int offset) { - LastDocumentSymbols = await Service.RequestSymbolsAsync(CurrentFile.FullPath); - + base.CaretPositionChanged(offset); + + _lastCaretChangeTime = DateTime.Now.TimeOfDay; + } + + private async Task GetDocumentHighlightAsync() + { + var result = await Service.RequestDocumentHighlightAsync(CurrentFile.FullPath, + new Position(CodeBox.TextArea.Caret.Line - 1, CodeBox.TextArea.Caret.Column - 1)); + + if (result is not null) + { + var segments = result.Select(x => + x.Range.GenerateTextModification(Editor.CurrentDocument, null, + SolidColorBrush.Parse("#3300c8ff"))) + .ToArray(); + Editor.Editor.ModificationService.SetModification("caretHighlight", segments); + } + else + { + Editor.Editor.ModificationService.ClearModification("caretHighlight"); + } + } + + protected virtual async Task UpdateSemanticTokensAsync() + { + var tokens = await Service.RequestSemanticTokensFullAsync(CurrentFile.FullPath); + + var languageManager = ContainerLocator.Container.Resolve(); + + if (tokens != null) + { + var segments = tokens.Select(x => + { + var offset = + Editor.CurrentDocument.GetOffsetFromPosition(new Position(x.Line, x.StartCharacter)) - 1; + + if (!languageManager.CurrentEditorThemeColors.TryGetValue(x.TokenType.ToString(), out var b)) + return null; + + return new TextModificationSegment(offset, offset + x.Length) + { + Foreground = b + }; + }).Where(x => x is not null) + .Cast() + .ToArray(); + + Editor.Editor.ModificationService.SetModification("semanticTokens", segments); + } + else + { + Editor.Editor.ModificationService.ClearModification("semanticTokens"); + } + + // LastDocumentSymbols = await Service.RequestSymbolsAsync(CurrentFile.FullPath); + // + // var languageManager = ContainerLocator.Container.Resolve(); + // // if (LastDocumentSymbols is not null) // { // var segments = LastDocumentSymbols // .Where(x => x.IsDocumentSymbolInformation && x.SymbolInformation != null) - // .Select(x => x.SymbolInformation!.Location.Range.GenerateTextModification(Editor.CurrentDocument, Brushes.Chocolate)) + // .Select(x => + // { + // if (x.IsDocumentSymbol && x.DocumentSymbol != null) + // { + // if (!languageManager.CurrentEditorThemeColors.TryGetValue(x.DocumentSymbol.Kind, + // out var brush)) return null; + // return x.DocumentSymbol.Range.GenerateTextModification(Editor.CurrentDocument, brush); + // } + // if (x.IsDocumentSymbolInformation && x.SymbolInformation != null) + // { + // if (!languageManager.CurrentEditorThemeColors.TryGetValue(x.SymbolInformation.Kind, out var brush)) return null; + // if (!x.SymbolInformation.Location.Uri.GetFileSystemPath().EqualPaths(CurrentFile.FullPath)) return null; + // return x.SymbolInformation.Location.Range.GenerateTextModification(Editor.CurrentDocument, brush); + // } + // return null; + // }) + // .Where(x => x is not null) + // .Cast() // .ToArray(); // - // Editor.Editor.ModificationService.SetModification("symbol", segments); + // Editor.Editor.ModificationService.SetModification("symbols", segments); // } // else // { @@ -526,14 +623,16 @@ protected virtual async Task UpdateSymbolsAsync() // } } - protected virtual async Task ShowSignatureHelpAsync(SignatureHelpTriggerKind triggerKind, string? triggerChar, bool retrigger, SignatureHelp? activeSignatureHelp) + protected virtual async Task ShowSignatureHelpAsync(SignatureHelpTriggerKind triggerKind, string? triggerChar, + bool retrigger, SignatureHelp? activeSignatureHelp) { var signatureHelp = await Service.RequestSignatureHelpAsync(CurrentFile.FullPath, - new Position(CodeBox.TextArea.Caret.Line - 1, CodeBox.TextArea.Caret.Column - 1), triggerKind, triggerChar, retrigger, activeSignatureHelp); + new Position(CodeBox.TextArea.Caret.Line - 1, CodeBox.TextArea.Caret.Column - 1), triggerKind, + triggerChar, retrigger, activeSignatureHelp); if (signatureHelp != null && IsOpen) { var overloadProvider = ConvertOverloadProvider(signatureHelp); - + OverloadInsight = new OverloadInsightWindow(CodeBox) { Provider = overloadProvider, @@ -541,29 +640,30 @@ protected virtual async Task ShowSignatureHelpAsync(SignatureHelpTriggerKind tri AdditionalOffset = new Vector(0, -(SettingsService.GetSettingValue("Editor_FontSize") * 1.4)) }; - OverloadInsight.SetValue(TextBlock.FontSizeProperty, SettingsService.GetSettingValue("Editor_FontSize")); + OverloadInsight.SetValue(TextBlock.FontSizeProperty, + SettingsService.GetSettingValue("Editor_FontSize")); if (overloadProvider.Count > 0) OverloadInsight.Show(); } } private CompositeDisposable _completionDisposable = new(); - + protected virtual async Task ShowCompletionAsync(CompletionTriggerKind triggerKind, string? triggerChar) { //Console.WriteLine($"Completion request kind: {triggerKind} char: {triggerChar}"); var completionOffset = CodeBox.CaretOffset; - + var lspCompletionItems = await Service.RequestCompletionAsync(CurrentFile.FullPath, new Position(CodeBox.TextArea.Caret.Line - 1, CodeBox.TextArea.Caret.Column - 1), triggerKind, triggerKind == CompletionTriggerKind.Invoked ? null : triggerChar); - + var customCompletionItems = await GetCustomCompletionItemsAsync(); - + if ((lspCompletionItems is not null || customCompletionItems.Count > 0) && IsOpen) { Completion?.Close(); _completionDisposable = new CompositeDisposable(); - + Completion = new CompletionWindow(CodeBox) { CloseWhenCaretAtBeginning = false, @@ -573,7 +673,7 @@ protected virtual async Task ShowCompletionAsync(CompletionTriggerKind triggerKi StartOffset = completionOffset, EndOffset = completionOffset }; - + Observable.FromEventPattern(Completion, nameof(Completion.Closed)).Take(1).Subscribe(x => { _completionDisposable.Dispose(); @@ -586,16 +686,18 @@ protected virtual async Task ShowCompletionAsync(CompletionTriggerKind triggerKi Completion.Close(); } }).DisposeWith(_completionDisposable); - + Completion.CompletionList.CompletionData.AddRange(customCompletionItems); - + if (lspCompletionItems is not null) { if (triggerKind is CompletionTriggerKind.TriggerCharacter && triggerChar != null) { completionOffset++; } - Completion.CompletionList.CompletionData.AddRange(ConvertCompletionData(lspCompletionItems, completionOffset)); + + Completion.CompletionList.CompletionData.AddRange(ConvertCompletionData(lspCompletionItems, + completionOffset)); } //Calculate CompletionWindow width @@ -606,7 +708,7 @@ protected virtual async Task ShowCompletionAsync(CompletionTriggerKind triggerKi var calculatedWith = length * SettingsService.GetSettingValue("Editor_FontSize") + 50; Completion.Width = calculatedWith > 400 ? 500 : calculatedWith; - + if (Completion.CompletionList.CompletionData.Count > 0) { Completion.Show(); @@ -617,7 +719,7 @@ protected virtual async Task ShowCompletionAsync(CompletionTriggerKind triggerKi } } } - + public virtual Task> GetCustomCompletionItemsAsync() { return Task.FromResult(new List()); @@ -631,21 +733,25 @@ public override IEnumerable GetTypeAssistanceQuickOptions() { Header = "Restart Language Server", Command = new RelayCommand(() => _ = Service.RestartAsync()), - IconObservable = Application.Current!.GetResourceObservable("VsImageLib.RefreshGrey16X") + IconObservable = Application.Current!.GetResourceObservable("VsImageLib.RefreshGrey16X") } }; } public virtual void ExecuteCommand(Command? cmd) { - if(cmd == null) return; + if (cmd == null) return; if (Service.IsLanguageServiceReady && IsOpen) _ = Service.ExecuteCommandAsync(cmd); } public virtual async Task ResolveCompletionAsync() { //Resolve selected item - if (Service.IsLanguageServiceReady && Completion is { IsOpen: true, CompletionList: {SelectedItem: CompletionData{CompletionItemLsp: not null} selectedItem} }) + if (Service.IsLanguageServiceReady && Completion is + { + IsOpen: true, + CompletionList: { SelectedItem: CompletionData { CompletionItemLsp: not null } selectedItem } + }) { var resolvedCi = await Service.ResolveCompletionItemAsync(selectedItem.CompletionItemLsp); if (resolvedCi != null && IsOpen && Completion.IsOpen) @@ -663,7 +769,8 @@ public virtual async Task ResolveCompletionAsync() protected virtual bool CharBeforeNormalCompletion(char c) { - return char.IsWhiteSpace(c) || c is ';' or '#' or '(' or ':' or '+' or '-' or '=' or '*' or '/' or '&' or ','; + return char.IsWhiteSpace(c) || + c is ';' or '#' or '(' or ':' or '+' or '-' or '=' or '*' or '/' or '&' or ','; } protected virtual OverloadProvider ConvertOverloadProvider(SignatureHelp signatureHelp) @@ -681,12 +788,15 @@ protected virtual OverloadProvider ConvertOverloadProvider(SignatureHelp signatu m1 += p.Label.Label; if (i < s.Parameters.Count() - 1) m1 += ", "; } + m1 += p.Label.Label; if (i < s.Parameters.Count() - 1) m1 += ", "; } + m1 += ")\n```"; var m2 = string.Empty; - if (s.Documentation != null && s.Documentation.HasMarkupContent) m2 += s.Documentation.MarkupContent?.Value; + if (s.Documentation != null && s.Documentation.HasMarkupContent) + m2 += s.Documentation.MarkupContent?.Value; if (s.Documentation != null && s.Documentation.HasString) m2 += s.Documentation.String; overloadOptions.Add((m1, m2.Length > 0 ? m2 : null)); } @@ -713,9 +823,13 @@ void AfterComplete() { _ = ShowSignatureHelpAsync(SignatureHelpTriggerKind.Invoked, null, false, null); } - - var description = comp.Documentation != null ? (comp.Documentation.MarkupContent != null ? comp.Documentation.MarkupContent.Value : comp.Documentation.String) : null; - + + var description = comp.Documentation != null + ? (comp.Documentation.MarkupContent != null + ? comp.Documentation.MarkupContent.Value + : comp.Documentation.String) + : null; + return new CompletionData(comp.InsertText ?? comp.FilterText ?? "", comp.Label, description, icon, 0, comp, offset, AfterComplete); } @@ -723,20 +837,22 @@ void AfterComplete() public ErrorListItem? GetErrorAtLocation(TextLocation location) { foreach (var error in ContainerLocator.Container.Resolve().GetErrorsForFile(CurrentFile)) - if (location.Line >= error.StartLine && location.Column >= error.StartColumn && (location.Line < error.EndLine || location.Line == error.EndLine && location.Column <= error.EndColumn)) + if (location.Line >= error.StartLine && location.Column >= error.StartColumn && + (location.Line < error.EndLine || + location.Line == error.EndLine && location.Column <= error.EndColumn)) return error; return null; } - + protected IEnumerable ConvertChanges(DocumentChangeEventArgs e) { var l = new List(); var map = e.OffsetChangeMap; - + foreach (var c in map) { var location = CodeBox.Document.GetLocation(c.Offset); - + //calculate newlines var newlines = e.RemovedText.Text.Count(x => x == '\n'); var lastIndexNewLine = e.RemovedText.Text.LastIndexOf('\n'); diff --git a/src/OneWare.Essentials/Services/ILanguageManager.cs b/src/OneWare.Essentials/Services/ILanguageManager.cs index 5386d7e..c685527 100644 --- a/src/OneWare.Essentials/Services/ILanguageManager.cs +++ b/src/OneWare.Essentials/Services/ILanguageManager.cs @@ -1,15 +1,18 @@ using System.ComponentModel; +using Avalonia.Media; using OneWare.Essentials.LanguageService; using OneWare.Essentials.Models; using OneWare.Essentials.ViewModels; using TextMateSharp.Registry; using TextMateSharp.Themes; +using IFile = OneWare.Essentials.Models.IFile; namespace OneWare.Essentials.Services; public interface ILanguageManager : INotifyPropertyChanged { public IRawTheme CurrentEditorTheme { get; } + public Dictionary CurrentEditorThemeColors { get; } public IRegistryOptions RegistryOptions { get; } public event EventHandler? LanguageSupportAdded; public void RegisterTextMateLanguage(string id, string grammarPath, params string[] extensions);