I have an add-in for Visual Studio for Java like profile files. A sample file can be found here.
The problem occurs when the \ character is inserted on a specific line. When backwards slash is used, the next line is interpreted as a value (or as a key, or as a key and a value). A simple example is:
1. part1_key\
2. part2_key\
3. part3_key = value
4. key = part1_value\
5. part2_value
The syntax highlighter works when the file is loaded, but when a line is modified, only that line is evaluated and highlighted. So, when the \ is inserted on line 4 for example, the line 4 is highlighted, but the line 5 is not updated. The line 5 is updated only when it's modified (add or remove a character, or a space).
I've created a ITaggerProvider which creates an ITagger whose type is Key, Value or Comment. The ITagger class is as follows:
internal sealed class PropertiesTokenTagger : ITagger<PropertiesTokenTag> {
private readonly Regex keyValuePattern = new Regex(#"(?<!^\s*|\\)([ \t]*[=:][ \t]*|[ \t]+)");
private readonly Regex separatorPattern = new Regex(#"^([=:]|[ \t]+)");
private readonly Regex commentPattern = new Regex(#"^\s*[#!]");
private readonly Regex escapedLineEndPattern = new Regex(#"\\$");
public event EventHandler<SnapshotSpanEventArgs> TagsChanged {
add { }
remove { }
}
public IEnumerable<ITagSpan<PropertiesTokenTag>> GetTags(NormalizedSnapshotSpanCollection spans) {
// sadly `spans` gets one line at a time, so previouslyEscapedValue will not get the chance to be used
foreach (var curSpan in spans) {
var containingLine = curSpan.Start.GetContainingLine();
var lineStartLoc = containingLine.Start.Position;
var lineText = containingLine.GetText();
var previousIsNotComment = false;
var previousLine = curSpan.Snapshot.Lines.LastOrDefault(l => l.End <= curSpan.Start);
if (previousLine != null && escapedLineEndPattern.IsMatch(previousLine.GetText())) {
var previousToken = GetTags(new NormalizedSnapshotSpanCollection(previousLine.Extent)).ToList();
if (previousToken.Count > 0) {
var propertiesTokenTypes = previousToken.Last().Tag.Type;
if (propertiesTokenTypes == PropertiesValue) {
var valueSpan = new SnapshotSpan(curSpan.Snapshot, new Span(lineStartLoc, lineText.Length));
yield return new TagSpan<PropertiesTokenTag>(valueSpan, new PropertiesTokenTag(PropertiesValue));
continue;
}
if (propertiesTokenTypes == PropertiesKey && separatorPattern.IsMatch(lineText)) {
var valueSpan = new SnapshotSpan(curSpan.Snapshot, new Span(lineStartLoc, lineText.Length));
yield return new TagSpan<PropertiesTokenTag>(valueSpan, new PropertiesTokenTag(PropertiesValue));
continue;
}
previousIsNotComment = propertiesTokenTypes != PropertiesComment;
}
}
if (commentPattern.IsMatch(lineText) && !previousIsNotComment) {
var commentSpan = new SnapshotSpan(curSpan.Snapshot, new Span(lineStartLoc, lineText.Length));
yield return new TagSpan<PropertiesTokenTag>(commentSpan, new PropertiesTokenTag(PropertiesComment));
continue;
}
if (keyValuePattern.IsMatch(lineText)) {
var splitPosition = keyValuePattern.Split(lineText)[0].Length;
var keySpan = new SnapshotSpan(curSpan.Snapshot, new Span(lineStartLoc, splitPosition));
yield return new TagSpan<PropertiesTokenTag>(keySpan, new PropertiesTokenTag(PropertiesKey));
var valueSpan = new SnapshotSpan(curSpan.Snapshot, new Span(lineStartLoc + splitPosition + 1, lineText.Length - splitPosition - 1));
yield return new TagSpan<PropertiesTokenTag>(valueSpan, new PropertiesTokenTag(PropertiesValue));
} else {
var keySpan = new SnapshotSpan(curSpan.Snapshot, new Span(lineStartLoc, lineText.Length));
yield return new TagSpan<PropertiesTokenTag>(keySpan, new PropertiesTokenTag(PropertiesKey));
}
}
}
From what I see, the problem is the fact that GetTags method is called from an external source, and I cannot call manually this method in order to update other lines. This method return a List of TagSpans which intersects the NormalizedSnapshotSpanCollection parameter, so if I try to return a tag for another line other than current line, that tag won't be processed.
LE: I've done it, the solution was to raise the TagsChanged event from outside GetTags method, and also in aggregator from ITagger.
Related
I'm working with TwincatAds.Reactive 6.0.190 in .NET 6 WPF Desktop application.
I'm also using MVVM pattern.
My goal is to create a Class that is going to observe for a PLC Variable changes, collect those variables to a dictionary, and later on use those values in the ViewModel.
Here's the method where I'm attaching the notification and action where I'm handling the notification.
public void AttachNotification(IEnumerable<(string key, Type type)> Symbols)
{
_observerValueNotification = Observer.Create<ValueNotification>(val =>
{
// Does handle really start from 2?
var handle = val.Handle;
if (val.UserData is object[] objects)
{
string tag = objects[handle - 2].ToString();
if (!_values.Any(x => x.Key == tag))
_values.Add(new SymbolModel { Key = tag, Value = val.Value });
else
{
var symbol = _values.First(x => x.Key == tag);
symbol.Value = val.Value;
}
}
ValuesChanged?.Invoke(_values);
});
if (_plcWrapper.AdsClient != null)
{
// Get Symbols from SymbolLoader
List<AnySymbolSpecifier> list = new();
List<string> userData = new();
foreach (var (key, type) in Symbols)
{
list.Add(new AnySymbolSpecifier(key, new AnyTypeSpecifier(type)));
userData.Add(key);
}
_subscription2 = _plcWrapper.AdsClient.WhenNotificationEx(list, NotificationSettings.ImmediatelyOnChange, userData.ToArray())
.Subscribe(_observerValueNotification);
}
}
I'm using ValueNotification simply because, I'd like to use this pattern also for complex PLC Variables like Structs.
As You can see, in the WhenNotificationEx method I'm using UserData[] to provide some sort of identification of what Variable has changed when handling the change.
My idea was to use Handle property from ValueNotification as an indexer in UserData[] to identify what variable I'm dealing with, but for some reason Handle starts from 2.
My question is, is it expected behaviour, does the Handle value really always start from 2?
I've decided that relying on the Handle being index in the UserData array is quite unpredictable as Handle is being created by the Twincat Ads server.
Solved the issue by creating own extension method to the WhenNotificationEx. Turned out IDisposableHandleBag has exactly what I was looking for, which is SourceResultHandles property, where AnySymbolSpecifier and ResultHandle are both stored!
Here's created extension method
public static Dictionary<string, uint> Handles { get; private set; } = new();
public static IObservable<ValueNotification> WhenNotificationWithHandle(this IAdsConnection connection, IList<AnySymbolSpecifier> symbols, NotificationSettings settings)
{
IAdsConnection connection2 = connection;
IList<AnySymbolSpecifier> symbols2 = symbols;
NotificationSettings settings2 = settings;
if (connection2 == null)
{
throw new ArgumentNullException("connection");
}
if (symbols2 == null)
{
throw new ArgumentNullException("symbols");
}
if (symbols2.Count == 0)
{
throw new ArgumentOutOfRangeException("symbols", "Symbol list is empty!");
}
IDisposableHandleBag<AnySymbolSpecifier> bag = null;
EventLoopScheduler scheduler = new EventLoopScheduler();
IObservable<int> whenSymbolChangeObserver = connection2.WhenSymbolVersionChanges(scheduler);
IDisposable whenSymbolChanges = null;
Action<EventHandler<AdsNotificationExEventArgs>> addHandler = delegate (EventHandler<AdsNotificationExEventArgs> h)
{
connection2.AdsNotificationEx += h;
bag = ((IAdsHandleCacheProvider)connection2).CreateNotificationExHandleBag(symbols2, relaxSubErrors: false, settings2, null);
bag.CreateHandles();
// Collect Handles
Handles.Clear();
foreach (var item in bag.SourceResultHandles)
Handles.Add(item.source.InstancePath, item.result.Handle);
whenSymbolChanges = whenSymbolChangeObserver.Subscribe((Action<int>)delegate
{
bag.CreateHandles();
Handles.Clear();
foreach (var item in bag.SourceResultHandles)
Handles.Add(item.source.InstancePath, item.result.Handle);
}, (Action<Exception>)delegate
{
TcTraceSource traceAds = AdsModule.TraceAds;
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(101, 1);
defaultInterpolatedStringHandler.AppendLiteral("The AdsServer '");
defaultInterpolatedStringHandler.AppendFormatted(connection2.Address);
defaultInterpolatedStringHandler.AppendLiteral("' doesn't support SymbolVersionChanged Notifications! Handle recreation is not active!");
traceAds.TraceInformation(defaultInterpolatedStringHandler.ToStringAndClear());
});
};
Action<EventHandler<AdsNotificationExEventArgs>> removeHandler = delegate (EventHandler<AdsNotificationExEventArgs> h)
{
if (whenSymbolChanges != null)
{
whenSymbolChanges.Dispose();
}
scheduler.Dispose();
if (bag != null)
{
bag.Dispose();
bag = null;
Handles.Clear();
}
connection2.AdsNotificationEx -= h;
};
return from ev in Observable.FromEventPattern<EventHandler<AdsNotificationExEventArgs>, AdsNotificationExEventArgs>(addHandler, removeHandler)
where bag.Contains(ev.EventArgs.Handle)
select new ValueNotification(ev.EventArgs, ev.EventArgs.Value);
}
I'm working on a Visual Studio 2015 extension to add support for a new language and I have an error tagger. I know that it is working somewhat because I'm getting the red marks next to the scrollbar on the lines where the errors are. Additionally, I'm using the same parser in other classes (such as my Classifier) and I know that it works correctly. Here's my code for the Tagger. Any ideas?
public IEnumerable<ITagSpan<ErrorTag>> GetTags(NormalizedSnapshotSpanCollection spans)
{
var parser = new NodeParser(true);
var tags = new List<ITagSpan<ErrorTag>>();
foreach (var span in spans)
{
//Reparse the whole thing. It's just must simpler
var spanToParse = new SnapshotSpan(span.Snapshot, new Span(0, span.Snapshot.Length));
IList<LexicalElement> lexicalElements;
parser.Parse(spanToParse.GetText(), "Editor", out lexicalElements);
try
{
tags.AddRange(GetTagsFromElements(lexicalElements, span));
}
catch (FormatException)
{
}
}
return tags;
}
private IEnumerable<ITagSpan<ErrorTag>> GetTagsFromElements(IEnumerable<LexicalElement> elements, SnapshotSpan span)
{
foreach (var element in elements.Where(element => element.Name.Contains("_")))
{
var startIndex = span.Start.Position + element.StartCursor.Location;
var endIndex = span.Start.Position + element.EndCursor.Location;
if (endIndex > span.End.Position)
continue;
var errorSpan = new Span(startIndex, endIndex - startIndex);
if(errorSpan.IntersectsWith(span))
yield return new TagSpan<ErrorTag>(new SnapshotSpan(span.Snapshot,
new Span(span.Start.Position + element.StartCursor.Location,
element.EndCursor.Location - element.StartCursor.Location)), new ErrorTag(PredefinedErrorTypeNames.SyntaxError));
}
}
Here's my tag provider:
[Export(typeof(ITaggerProvider))]
[ContentType("ConfigNode")]
[TagType(typeof(ErrorTag))]
class SyntaxErrorTaggerProvider : ITaggerProvider
{
public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
{
//create a single tagger for each buffer.
Func<ITagger<T>> sc = delegate () { return new SyntaxErrorTagger(buffer) as ITagger<T>; };
return buffer.Properties.GetOrCreateSingletonProperty(sc);
}
}
I am looking to run a RegEx search to find all occurrences of certain keywords in the Editor Window and then draw some adornments and add some tags to them.
Is there any way to run a RegEx on an ITextViewLine.
This is how my calling function looks:
private void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
{
foreach (ITextViewLine line in e.NewOrReformattedLines)
{
this.CreateVisuals(line);
}
}
private void CreateVisuals(ITextViewLine line)
{
IWpfTextViewLineCollection textViewLines = _wpfTextView.TextViewLines;
// Run RegEx match here and do some stuff for all matches
}
As suggested by #stribizhev I tried using FormattedSpan as follows:
private void CreateVisuals(ITextViewLine line)
{
var textViewLines = _wpfTextView.TextViewLines;
var snapshot = textViewLines.FormattedSpan;
var text = snapshot.ToString();
var todoRegex = new Regex(#"\/\/\s*TODO\b");
var match = todoRegex.Match(text);
if (match.Success)
{
int matchStart = line.Start.Position + match.Index;
var span = new SnapshotSpan(_wpfTextView.TextSnapshot, Span.FromBounds(matchStart, matchStart + match.Length));
DrawAdornment(textViewLines, span);
}
}
But this causes a NullReference at the call to DrawAdornment telling me that span is unset.
And moreover by putting breakpoints on all lines in the CreateVisuals function I saw that the highlighting only starts when the line containing the TODO scrolls out of view or becomes the first line in the viewport.
The input I tried was:
using System;
public class Class1
{
public Class1()
{
// TODO: It's a good thing to have todos
}
}
The code is able to put adornments sometimes but they are shifted slightly to the right and appear on three different lines.
I finally got it to work. There are two ways to do it.
My way (easier):
private void CreateVisuals()
{
var textViewLines = _wpfTextView.TextViewLines;
var text = textViewLines.FormattedSpan.Snapshot.GetText();
var todoRegex = new Regex(#"\/\/\s*TODO\b");
var match = todoRegex.Match(text);
while (match.Success)
{
var matchStart = match.Index;
var span = new SnapshotSpan(_wpfTextView.TextSnapshot, Span.FromBounds(matchStart, matchStart + match.Length));
DrawAdornment(textViewLines, span);
match = match.NextMatch();
}
The tough(er) way: (From this article)
/// <summary>
/// This will get the text of the ITextView line as it appears in the actual user editable
/// document.
/// jared parson: https://gist.github.com/4320643
/// </summary>
public static bool TryGetText(IWpfTextView textView, ITextViewLine textViewLine, out string text)
{
var extent = textViewLine.Extent;
var bufferGraph = textView.BufferGraph;
try
{
var collection = bufferGraph.MapDownToSnapshot(extent, SpanTrackingMode.EdgeInclusive, textView.TextSnapshot);
var span = new SnapshotSpan(collection[0].Start, collection[collection.Count - 1].End);
//text = span.ToString();
text = span.GetText();
return true;
}
catch
{
text = null;
return false;
}
}
Regex todoLineRegex = new Regex(#"\/\/\s*TODO\b");
private void CreateVisuals(ITextViewLine line)
{
IWpfTextViewLineCollection textViewLines = _view.TextViewLines;
string text = null;
if (TryGetText(_view, line, out text))
{
var match = todoLineRegex.Match(text);
if (match.Success)
{
int matchStart = line.Start.Position + span.Index;
var span = new SnapshotSpan(_view.TextSnapshot, Span.FromBounds(matchStart, matchStart + match.Length));
DrawAdornment(textViewLines, span);
}
}
}
I'm implementing Dynamic Placeholders in Sitecore 7 as described in the articles
http://trueclarity.wordpress.com/2012/06/19/dynamic-placeholder-keys-in-sitecore/
http://www.techphoria414.com/Blog/2011/August/Dynamic_Placeholder_Keys_Prototype
It is working correctly such that I can add the same Rendering to the layout and the renderings will go in the appropriate Dynamic Placeholder. However when I click to add a Rendering to the Dynamic Placeholder, the placeholder settings aren't being used.
What I am expecting is to be prompted with the allowed renderings that may be placed on the Dynamic Placeholder. Instead the Rendering/Layout tree is presented to manually select the rendering - giving Content Editors the ability to add disallowed renderings to the placeholder.
I have debugged the code and the correct Placeholder Settings Item is being found for the Dynamic Placeholder and the list of allowed Renderings are being retrieved however despite being set in the args the list is not presented for the User. See code below.
public class GetDynamicKeyAllowedRenderings : GetAllowedRenderings
{
//string that ends in a GUID
public const string DynamicKeyRegex = #"(.+){[\d\w]{8}\-([\d\w]{4}\-){3}[\d\w]{12}}";
public new void Process(GetPlaceholderRenderingsArgs args)
{
Assert.IsNotNull(args, "args");
// get the placeholder key
string placeholderKey = args.PlaceholderKey;
var regex = new Regex(DynamicKeyRegex);
Match match = regex.Match(placeholderKey);
// if the placeholder key text followed by a Guid
if (match.Success && match.Groups.Count > 0)
{
// Is a dynamic placeholder
placeholderKey = match.Groups[1].Value;
}
else
{
return;
}
Item placeholderItem = null;
if (ID.IsNullOrEmpty(args.DeviceId))
{
placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase,
args.LayoutDefinition);
}
else
{
using (new DeviceSwitcher(args.DeviceId, args.ContentDatabase))
{
placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase,
args.LayoutDefinition);
}
}
// Retrieve the allowed renderings for the Placeholder
List<Item> collection = null;
if (placeholderItem != null)
{
bool allowedControlsSpecified;
args.HasPlaceholderSettings = true;
collection = this.GetRenderings(placeholderItem, out allowedControlsSpecified);
if (allowedControlsSpecified)
{
args.CustomData["allowedControlsSpecified"] = true;
}
}
if (collection != null)
{
if (args.PlaceholderRenderings == null)
{
args.PlaceholderRenderings = new List<Item>();
}
args.PlaceholderRenderings.AddRange(collection);
}
}
}
As this code was developed for Sitecore 6.5 / 6.6 I wonder if in the jump to Sitecore 7.0 brought a change that affects the latter half of the code
I have found the source of the issue by decompiling the Sitecore 7 Kernel and viewing the default GetAllowedRenderings class. If Allowed Renderings are found the ShowTree Option needs to be set to false. See below
public class GetDynamicKeyAllowedRenderings : GetAllowedRenderings
{
//string that ends in a GUID
public const string DynamicKeyRegex = #"(.+){[\d\w]{8}\-([\d\w]{4}\-){3}[\d\w]{12}}";
public new void Process(GetPlaceholderRenderingsArgs args)
{
Assert.IsNotNull(args, "args");
// get the placeholder key
string placeholderKey = args.PlaceholderKey;
var regex = new Regex(DynamicKeyRegex);
Match match = regex.Match(placeholderKey);
// if the placeholder key text followed by a Guid
if (match.Success && match.Groups.Count > 0)
{
// Is a dynamic placeholder
placeholderKey = match.Groups[1].Value;
}
else
{
return;
}
// Same as Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings from here but with fake placeholderKey
// i.e. the placeholder without the Guid
Item placeholderItem = null;
if (ID.IsNullOrEmpty(args.DeviceId))
{
placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase,
args.LayoutDefinition);
}
else
{
using (new DeviceSwitcher(args.DeviceId, args.ContentDatabase))
{
placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase,
args.LayoutDefinition);
}
}
List<Item> collection = null;
if (placeholderItem != null)
{
bool allowedControlsSpecified;
args.HasPlaceholderSettings = true;
collection = this.GetRenderings(placeholderItem, out allowedControlsSpecified);
if (allowedControlsSpecified)
{
// Hide the Layout/Rendering tree to show the Allowed Renderings
args.Options.ShowTree = false;
}
}
if (collection != null)
{
if (args.PlaceholderRenderings == null)
{
args.PlaceholderRenderings = new List<Item>();
}
args.PlaceholderRenderings.AddRange(collection);
}
}
}
This is a change brought in by Sitecore 7 it seems.
I'm not a developer so maybe the answer is out there for a different solution but I can't really translate it from python or something else.
I'm trying to use the AWS .NET SDK to find an instance and then get the instance's tags. I've gotten as far as being able to determine if an instance is up and running or not. I also see how I can create and delete tags (not in code example below). But I don't see an easy way to actually check if a tag exists and get the value of the tag if it does exist.
Sorry if I'm missing the obvious but this is all new to me. Here's an example of the code I'm using to check if an instance is running.
instanceID = "i-myInstanceID";
do {
var myrequest = new DescribeInstanceStatusRequest();
DescribeInstanceStatusResponse myresponse = ec2.DescribeInstanceStatus(myrequest);
int isCount = myresponse.DescribeInstanceStatusResult.InstanceStatuses.Count;
for (int isc=0; isc < isCount; isc++) {
InstanceStatus instanceStatus = myresponse.DescribeInstanceStatusResult.InstanceStatuses[isc];
if (instanceStatus.InstanceId.Contains(instanceID)) {
Console.WriteLine("It looks like instance "+instanceID+" is running.");
idIdx = isc;
foundID = true;
break;
}
}
if ((foundID==false) && (secondCounter==1)) {
Console.Write("Looking for instance "+instanceID);
} else {
Console.Write(".");
}
Thread.Sleep(1000);
secondCounter++;
if (secondCounter > 5) {
break;
}
} while (foundID == false) ;
First send a DescribeInstancesRequest to get the list of Instances:
public DescribeInstancesResult GetInstances(Ec2Key ec2Key)
{
_logger.Debug("GetInstances Start.");
AmazonEC2 ec2 = CreateAmazonEc2Client(ec2Key);
var ec2Request = new DescribeInstancesRequest();
DescribeInstancesResponse describeInstancesResponse = ec2.DescribeInstances(ec2Request);
DescribeInstancesResult result = describeInstancesResponse.DescribeInstancesResult;
_logger.Debug("GetInstances End.");
return result;
}
Then loop through the instances until you find the one you want, and then use the Tag.GetTagValueByKey method:
// This just calls the above code
DescribeInstancesResult ec2Instances = _ec2ResourceAccess.GetInstances(ec2Key);
var returnInstances = new List<Ec2UtilityInstance>();
foreach (var reservation in ec2Instances.Reservation)
{
foreach (var runningInstance in reservation.RunningInstance)
{
var returnInstance = new Ec2UtilityInstance();
returnInstance.InstanceId = runningInstance.InstanceId;
returnInstance.InstanceName = runningInstance.Tag.GetTagValueByKey("Name");
returnInstance.Status = (Ec2UtilityInstanceStatus)Enum.Parse(typeof(Ec2UtilityInstanceStatus), runningInstance.InstanceState.Name, true);
returnInstance.DefaultIp = runningInstance.Tag.GetTagValueByKey("DefaultIp");
returnInstance.InstanceType = runningInstance.InstanceType;
returnInstance.ImageId = runningInstance.ImageId;
returnInstances.Add(returnInstance);
}
}
Here is the link for full source that this was taken from:
https://github.com/escherrer/EC2Utilities
Common\Manager
and
Common\ResourceAccess