Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ indent_size = 4

# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
indent_size = 4

# Xml config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
Expand Down Expand Up @@ -75,4 +75,67 @@ csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_indent_labels = one_less_than_current
[*.{cs,vb}]
#### Naming styles ####

# Naming rules

dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i

dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case

dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case

# Symbol specifications

dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =

dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =

dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =

# Naming styles

dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case

dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
end_of_line = crlf
23 changes: 22 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,27 @@
**/bower_components
**/node_modules
**/*.csproj.user
/.vs
**/PrecompiledWeb
*.user
*.vs
/src/ShaneSpace.VisualStudio.InvisibleCharacterVisualizer/.vs/ShaneSpace.VisualStudio.InvisibleCharacterVisualizer.csproj.dtbcache.json
*.suo
*.user
/bin-debug
/bin-release
*.log
*.log.*
/logs
/log
*.sqlite
*.db
/tmp
/temp
*.tmp
*.temp
.vscode
.idea
*.bak
*.backup
.DS_Store
Thumbs.db
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,13 @@ namespace ShaneSpace.VisualStudio.InvisibleCharacterVisualizer
[TagType(typeof(IntraTextAdornmentTag))]
internal sealed class ColorAdornmentTaggerProvider : IViewTaggerProvider
{
#pragma warning disable RCS1169 // Mark field as read-only.
#pragma warning disable SA1401 // Fields must be private
#pragma warning disable CS0649 // something

[Import]
internal IBufferTagAggregatorFactoryService BufferTagAggregatorFactoryService;

#pragma warning restore RCS1169 // Mark field as read-only.
#pragma warning restore SA1401 // Fields must be private
#pragma warning restore CS0649

public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer)
where T : ITag
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ public virtual void Dispose()
yield break;
}

ITextSnapshot snapshot = spans[0].Snapshot;
var snapshot = spans[0].Snapshot;

foreach (IMappingTagSpan<TDataTag> dataTagSpan in DataTagger.GetTags(spans))
foreach (var dataTagSpan in DataTagger.GetTags(spans))
{
NormalizedSnapshotSpanCollection dataTagSpans = dataTagSpan.Span.GetSpans(snapshot);
var dataTagSpans = dataTagSpan.Span.GetSpans(snapshot);

// Ignore data tags that are split by projection.
// This is theoretically possible but unlikely in current scenarios.
Expand All @@ -76,18 +76,18 @@ public virtual void Dispose()
continue;
}

SnapshotSpan span = dataTagSpans[0];
var span = dataTagSpans[0];

PositionAffinity? affinity = span.Length > 0 ? null : AdornmentAffinity;
var affinity = span.Length > 0 ? null : AdornmentAffinity;

yield return Tuple.Create(span, affinity, dataTagSpan.Tag);
}
}

private void HandleDataTagsChanged(object sender, TagsChangedEventArgs args)
{
NormalizedSnapshotSpanCollection changedSpans = args.Span.GetSpans(View.TextBuffer.CurrentSnapshot);
InvalidateSpans(changedSpans);
var changedSpans = args.Span.GetSpans(View.TextBuffer.CurrentSnapshot);
_ = InvalidateSpansAsync(changedSpans);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Threading;

namespace ShaneSpace.VisualStudio.InvisibleCharacterVisualizer
{
Expand All @@ -39,6 +42,10 @@ internal abstract class IntraTextAdornmentTagger<TData, TAdornment>
private readonly List<SnapshotSpan> _invalidatedSpans = new List<SnapshotSpan>();
private Dictionary<SnapshotSpan, TAdornment> _adornmentCache = new Dictionary<SnapshotSpan, TAdornment>();

/// <summary>
/// Initializes a new instance of the <see cref="IntraTextAdornmentTagger{TData, TAdornment}"/> class.
/// </summary>
/// <param name="view"></param>
protected IntraTextAdornmentTagger(IWpfTextView view)
{
View = view;
Expand All @@ -65,17 +72,17 @@ public virtual IEnumerable<ITagSpan<IntraTextAdornmentTag>> GetTags(NormalizedSn
}

// Translate the request to the snapshot that this tagger is current with.
ITextSnapshot requestedSnapshot = spans[0].Snapshot;
var requestedSnapshot = spans[0].Snapshot;

var translatedSpans = new NormalizedSnapshotSpanCollection(spans.Select(span => span.TranslateTo(Snapshot, SpanTrackingMode.EdgeExclusive)));

// Grab the adornments.
foreach (var tagSpan in GetAdornmentTagsOnSnapshot(translatedSpans))
{
// Translate each adornment to the snapshot that the tagger was asked about.
SnapshotSpan span = tagSpan.Span.TranslateTo(requestedSnapshot, SpanTrackingMode.EdgeExclusive);
var span = tagSpan.Span.TranslateTo(requestedSnapshot, SpanTrackingMode.EdgeExclusive);

IntraTextAdornmentTag tag = new IntraTextAdornmentTag(tagSpan.Tag.Adornment, tagSpan.Tag.RemovalCallback, tagSpan.Tag.Affinity);
var tag = new IntraTextAdornmentTag(tagSpan.Tag.Adornment, tagSpan.Tag.RemovalCallback, tagSpan.Tag.Affinity);
yield return new TagSpan<IntraTextAdornmentTag>(span, tag);
}
}
Expand Down Expand Up @@ -115,17 +122,23 @@ public virtual IEnumerable<ITagSpan<IntraTextAdornmentTag>> GetTags(NormalizedSn
/// <summary>
/// Causes intra-text adornments to be updated asynchronously.
/// </summary>
/// <param name="spans">The spans</param>
protected void InvalidateSpans(IList<SnapshotSpan> spans)
/// <param name="spans">The spans.</param>
/// <returns><placeholder>A <see cref="Task"/> representing the asynchronous operation.</placeholder></returns>
protected async Task InvalidateSpansAsync(IList<SnapshotSpan> spans)
{
var joinableTaskContext = new JoinableTaskContext();
var joinableTaskFactory = joinableTaskContext.Factory;

await joinableTaskFactory.SwitchToMainThreadAsync();

lock (_invalidatedSpans)
{
bool wasEmpty = _invalidatedSpans.Count == 0;
var wasEmpty = _invalidatedSpans.Count == 0;
_invalidatedSpans.AddRange(spans);

if (wasEmpty && _invalidatedSpans.Count > 0)
{
View.VisualElement.Dispatcher.BeginInvoke(new Action(AsyncUpdate));
Task.Run(() => AsyncUpdate());
}
}
}
Expand All @@ -136,14 +149,14 @@ protected void InvalidateSpans(IList<SnapshotSpan> spans)
/// <param name="span">The span</param>
protected void RaiseTagsChanged(SnapshotSpan span)
{
EventHandler<SnapshotSpanEventArgs> handler = TagsChanged;
var handler = TagsChanged;
handler?.Invoke(this, new SnapshotSpanEventArgs(span));
}

private void HandleBufferChanged(object sender, TextContentChangedEventArgs args)
{
List<SnapshotSpan> editedSpans = args.Changes.Select(change => new SnapshotSpan(args.After, change.NewSpan)).ToList();
InvalidateSpans(editedSpans);
var editedSpans = args.Changes.Select(change => new SnapshotSpan(args.After, change.NewSpan)).ToList();
_ = InvalidateSpansAsync(editedSpans);
}

private void AsyncUpdate()
Expand All @@ -158,8 +171,8 @@ private void AsyncUpdate()

foreach (var keyValuePair in _adornmentCache)
{
SnapshotSpan key = keyValuePair.Key.TranslateTo(Snapshot, SpanTrackingMode.EdgeExclusive);
TAdornment value = keyValuePair.Value;
var key = keyValuePair.Key.TranslateTo(Snapshot, SpanTrackingMode.EdgeExclusive);
var value = keyValuePair.Value;
if (translatedAdornmentCache.ContainsKey(key))
{
translatedAdornmentCache[key] = value;
Expand All @@ -185,15 +198,15 @@ private void AsyncUpdate()
return;
}

SnapshotPoint start = translatedSpans.Select(span => span.Start).Min();
SnapshotPoint end = translatedSpans.Select(span => span.End).Max();
var start = translatedSpans.Select(span => span.Start).Min();
var end = translatedSpans.Select(span => span.End).Max();

RaiseTagsChanged(new SnapshotSpan(start, end));
}

private void HandleLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
{
SnapshotSpan visibleSpan = View.TextViewLines.FormattedSpan;
var visibleSpan = View.TextViewLines.FormattedSpan;

// Filter out the adornments that are no longer visible.
var toRemove = new List<SnapshotSpan>(
Expand All @@ -202,7 +215,7 @@ in _adornmentCache
where !keyValuePair.Key.TranslateTo(visibleSpan.Snapshot, SpanTrackingMode.EdgeExclusive).IntersectsWith(visibleSpan)
select keyValuePair.Key);

foreach (SnapshotSpan span in toRemove)
foreach (var span in toRemove)
{
_adornmentCache.Remove(span);
}
Expand All @@ -216,7 +229,7 @@ private IEnumerable<TagSpan<IntraTextAdornmentTag>> GetAdornmentTagsOnSnapshot(N
yield break;
}

ITextSnapshot snapshot = spans[0].Snapshot;
var snapshot = spans[0].Snapshot;

System.Diagnostics.Debug.Assert(snapshot == Snapshot, "Snapshot does not equal snapshot");

Expand All @@ -226,7 +239,7 @@ private IEnumerable<TagSpan<IntraTextAdornmentTag>> GetAdornmentTagsOnSnapshot(N

// Mark which adornments fall inside the requested spans with Keep=false
// so that they can be removed from the cache if they no longer correspond to data tags.
HashSet<SnapshotSpan> toRemove = new HashSet<SnapshotSpan>();
var toRemove = new HashSet<SnapshotSpan>();
foreach (var ar in _adornmentCache)
{
if (spans.IntersectsWith(new NormalizedSnapshotSpanCollection(ar.Key)))
Expand All @@ -239,9 +252,9 @@ private IEnumerable<TagSpan<IntraTextAdornmentTag>> GetAdornmentTagsOnSnapshot(N
{
// Look up the corresponding adornment or create one if it's new.
TAdornment adornment;
SnapshotSpan snapshotSpan = spanDataPair.Item1;
PositionAffinity? affinity = spanDataPair.Item2;
TData adornmentData = spanDataPair.Item3;
var snapshotSpan = spanDataPair.Item1;
var affinity = spanDataPair.Item2;
var adornmentData = spanDataPair.Item3;
if (_adornmentCache.TryGetValue(snapshotSpan, out adornment))
{
if (UpdateAdornment(adornment, adornmentData))
Expand Down Expand Up @@ -274,7 +287,7 @@ private IEnumerable<TagSpan<IntraTextAdornmentTag>> GetAdornmentTagsOnSnapshot(N
yield return new TagSpan<IntraTextAdornmentTag>(snapshotSpan, new IntraTextAdornmentTag(adornment, null, affinity));
}

foreach (SnapshotSpan snapshotSpan in toRemove)
foreach (var snapshotSpan in toRemove)
{
_adornmentCache.Remove(snapshotSpan);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@ namespace ShaneSpace.VisualStudio.InvisibleCharacterVisualizer
{
internal sealed class InvisibleCharacterAdornment : Button
{
internal InvisibleCharacterAdornment(InvisibleCharacterTag nonAsciiTag)
/// <summary>
/// Initializes a new instance of the <see cref="InvisibleCharacterAdornment"/> class.
/// </summary>
internal InvisibleCharacterAdornment(InvisibleCharacterTag invisibleCharacterTag)
{
var text = nonAsciiTag.Text.ToHex();
var text = invisibleCharacterTag.Text.ToHex();

Content = new TextBlock
{
Text = text,
Background = Brushes.Red,
Foreground = Brushes.White,
Padding = new Thickness(2)
Padding = new Thickness(2),
};

// TODO MouseLeftButtonUp += (sender, args) => MessageBox.Show($"http://www.unicodemap.org/details/{text}/index.html");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ internal class InvisibleCharacterTag : ITag
{
internal readonly string Text;

/// <summary>
/// Initializes a new instance of the <see cref="InvisibleCharacterTag"/> class.
/// </summary>
internal InvisibleCharacterTag(string text)
{
Text = text;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ namespace ShaneSpace.VisualStudio.InvisibleCharacterVisualizer
{
internal sealed class InvisibleCharacterTagger : RegexTagger<InvisibleCharacterTag>
{
/// <summary>
/// Initializes a new instance of the <see cref="InvisibleCharacterTagger"/> class.
/// </summary>
/// <param name="buffer"></param>
internal InvisibleCharacterTagger(ITextBuffer buffer)
: base(buffer, new[] { new Regex($"[{string.Join(string.Empty, NonPrintableUnicodeCharacters.UnicodeRanges)}]", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase) })
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ namespace ShaneSpace.VisualStudio.InvisibleCharacterVisualizer
[TagType(typeof(InvisibleCharacterTag))]
internal sealed class InvisibleCharacterTaggerProvider : ITaggerProvider
{
/// <summary>
/// Creates a tag provider for the specified buffer.
/// </summary>
/// <param name="buffer">The <see cref="ITextBuffer" />.</param>
/// <typeparam name="T">The type of the tag.</typeparam>
/// <returns>buffer.</returns>
public ITagger<T> CreateTagger<T>(ITextBuffer buffer)
where T : ITag
{
Expand Down
Loading