Skip to content

Commit

Permalink
Added IntelliSense for registry keys
Browse files Browse the repository at this point in the history
  • Loading branch information
madskristensen committed Dec 27, 2021
1 parent 01a2aea commit 57ce9b4
Show file tree
Hide file tree
Showing 16 changed files with 178 additions and 231 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ Syntax highligting makes it easy to parse the document. Here's what it looks lik
![Colorization](art/colorization.png)

## IntelliSense
Full completion provided for variables.
Full completion provided for variables and registry keys.

![Intellisense](art/intellisense.png)
![Intellisense](art/intellisense.gif)

## Validation
There's validation for both syntax errors and unknown variables.
Expand Down
Binary file added art/intellisense.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
129 changes: 129 additions & 0 deletions src/Language/AsyncCompletionSourceProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Core.Imaging;
using Microsoft.VisualStudio.Imaging;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Adornments;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
using Microsoft.Win32;

namespace PkgdefLanguage
{
[Export(typeof(IAsyncCompletionSourceProvider))]
[ContentType(Constants.LanguageName)]
[Name(Constants.LanguageName)]
public class AsyncCompletionSourceProvider : IAsyncCompletionSourceProvider
{
public IAsyncCompletionSource GetOrCreate(ITextView textView) =>
textView.Properties.GetOrCreateSingletonProperty(() => new AsyncCompletionSource());
}

public class AsyncCompletionSource : IAsyncCompletionSource
{
private static readonly ImageElement _referenceIcon = new(KnownMonikers.LocalVariable.ToImageId(), "Variable");

public Task<CompletionContext> GetCompletionContextAsync(IAsyncCompletionSession session, CompletionTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan applicableToSpan, CancellationToken cancellationToken)
{
Document document = session.TextView.TextBuffer.GetDocument();
ParseItem item = document.FindItemFromPosition(triggerLocation.Position);
IEnumerable<CompletionItem> items = null;

if (item?.Type == ItemType.Reference)
{
items = GetReferenceCompletion();
}
else if (item?.Type == ItemType.RegistryKey)
{
items = GetRegistryKeyCompletion(item, triggerLocation);
}

return Task.FromResult(items == null ? null : new CompletionContext(items.ToImmutableArray()));
}

private IEnumerable<CompletionItem> GetRegistryKeyCompletion(ParseItem item, SnapshotPoint triggerLocation)
{
ITextSnapshotLine line = triggerLocation.GetContainingLine();
var column = triggerLocation.Position - line.Start - 1;
var previousKey = item.Text.LastIndexOf('\\', column);

if (previousKey > -1)
{
IEnumerable<string> prevKeys = item.Text.Substring(0, previousKey).Split('\\').Skip(1);
RegistryKey root = VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration);
RegistryKey parent = root;

foreach (var subKey in prevKeys)
{
parent = parent.OpenSubKey(subKey);
}

return parent?.GetSubKeyNames()?.Select(s => new CompletionItem(s, this, _referenceIcon));
}

return null;
}

private IEnumerable<CompletionItem> GetReferenceCompletion()
{
foreach (var key in PredefinedVariables.Variables.Keys)
{
var completion = new CompletionItem(key, this, _referenceIcon, ImmutableArray<CompletionFilter>.Empty, "", $"${key}$", key, key, ImmutableArray<ImageElement>.Empty);
completion.Properties.AddProperty("description", PredefinedVariables.Variables[key]);
yield return completion;
}
}

public Task<object> GetDescriptionAsync(IAsyncCompletionSession session, CompletionItem item, CancellationToken token)
{
if (item.Properties.TryGetProperty("description", out string description))
{
return Task.FromResult<object>(description);
}

return Task.FromResult<object>(null);
}

public CompletionStartData InitializeCompletion(CompletionTrigger trigger, SnapshotPoint triggerLocation, CancellationToken token)
{
if (trigger.Character == '\n' || trigger.Reason == CompletionTriggerReason.Deletion)
{
return CompletionStartData.DoesNotParticipateInCompletion;
}

Document document = triggerLocation.Snapshot.TextBuffer.GetDocument();
ParseItem item = document?.FindItemFromPosition(triggerLocation.Position);

if (item?.Type == ItemType.Reference)
{
var tokenSpan = new SnapshotSpan(triggerLocation.Snapshot, item);
return new CompletionStartData(CompletionParticipation.ProvidesItems, tokenSpan);
}
else if (item?.Type == ItemType.RegistryKey && item.Text.IndexOf("$rootkey$", StringComparison.OrdinalIgnoreCase) > -1)
{
var column = triggerLocation.Position - item.Span.Start;
var start = item.Text.LastIndexOf('\\', column - 1) + 1;
var end = item.Text.IndexOf('\\', column);
end = end >= start ? end : item.Text.IndexOf(']', column);
end = end >= start ? end : item.Text.TrimEnd().Length;

if (end >= start)
{
var span = new SnapshotSpan(triggerLocation.Snapshot, item.Span.Start + start, end - start);
return new CompletionStartData(CompletionParticipation.ProvidesItems, span);
}
}

return CompletionStartData.DoesNotParticipateInCompletion;
}
}
}
2 changes: 1 addition & 1 deletion src/Language/BasicLanguageFeatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal sealed class BraceMatchingTaggerProvider : BraceMatchingBase
[Name(Constants.LanguageName)]
internal sealed class CompletionCommitManager : CompletionCommitManagerBase
{
public override IEnumerable<char> CommitChars => new char[] { ' ', '\'', '"', ',', '.', ';', ':' };
public override IEnumerable<char> CommitChars => new char[] { ' ', '\'', '"', ',', '.', ';', ':', '\\' };
}

[Export(typeof(IViewTaggerProvider))]
Expand Down
181 changes: 0 additions & 181 deletions src/Language/CompletionSource.cs

This file was deleted.

3 changes: 1 addition & 2 deletions src/Language/TokenLanguageFeatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ public class SyntaxHighligting : TokenClassificationBaseTagger
{ ItemType.String, PredefinedClassificationTypeNames.String },
{ ItemType.Literal, PredefinedClassificationTypeNames.Literal },
{ ItemType.Comment, PredefinedClassificationTypeNames.Comment },
{ ItemType.ReferenceBraces, PredefinedClassificationTypeNames.SymbolDefinition },
{ ItemType.ReferenceName, PredefinedClassificationTypeNames.SymbolReference },
{ ItemType.Reference, PredefinedClassificationTypeNames.SymbolReference },
{ ItemType.Operator, PredefinedClassificationTypeNames.Operator },
};
}
Expand Down
20 changes: 11 additions & 9 deletions src/Language/TokenTagger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,9 @@ private void ReParse(object sender = null, EventArgs e = null)
{
AddTagToList(list, item);

foreach (Reference variable in item.References)
foreach (ParseItem variable in item.References)
{
AddTagToList(list, variable.Open);
AddTagToList(list, variable.Value);
AddTagToList(list, variable.Close);
AddTagToList(list, variable);
}
}

Expand All @@ -75,10 +73,14 @@ private void ReParse(object sender = null, EventArgs e = null)
private void AddTagToList(Dictionary<ParseItem, ITagSpan<TokenTag>> list, ParseItem item)
{
var span = new SnapshotSpan(_buffer.CurrentSnapshot, item);
Func<SnapshotPoint, Task<object>> func = !item.IsValid ? GetTooltipAsync : null;
var tag = new TokenTag(item.Type, item is Entry, func, CreateErrorListItem(item).ToArray());
var tagSpan = new TagSpan<TokenTag>(span, tag);
list.Add(item, tagSpan);

var tag = new TokenTag(
tokenType: item.Type,
supportOutlining: item is Entry entry && entry.Properties.Any(),
getTooltipAsync: item.IsValid ? null : GetTooltipAsync,
errors: CreateErrorListItem(item).ToArray());

list.Add(item, new TagSpan<TokenTag>(span, tag));
}

private IEnumerable<ErrorListItem> CreateErrorListItem(ParseItem item)
Expand All @@ -104,7 +106,7 @@ private IEnumerable<ErrorListItem> CreateErrorListItem(ParseItem item)

private Task<object> GetTooltipAsync(SnapshotPoint triggerPoint)
{
ParseItem item = _document.GetTokenFromPosition(triggerPoint.Position);
ParseItem item = _document.FindItemFromPosition(triggerPoint.Position);

// Error messages
if (item?.IsValid == false)
Expand Down
4 changes: 2 additions & 2 deletions src/Parser/Document.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ public Task ProcessAsync()
});
}

public ParseItem GetTokenFromPosition(int position)
public ParseItem FindItemFromPosition(int position)
{
ParseItem item = Items.LastOrDefault(t => t.Span.Contains(position));
ParseItem reference = item?.References.FirstOrDefault(v => v.Value != null && v.Value.Span.Contains(position - 1))?.Value;
ParseItem reference = item?.References.FirstOrDefault(v => v != null && v.Span.Contains(position - 1));

// Return the reference if it exist; otherwise the item
return reference ?? item;
Expand Down
Loading

0 comments on commit 57ce9b4

Please sign in to comment.