Skip to content
This repository has been archived by the owner on Nov 27, 2020. It is now read-only.

Commit

Permalink
Added supoort for relative file paths, fixed image references via URL…
Browse files Browse the repository at this point in the history
…s; misc cleanup (F# seems to be broken);
  • Loading branch information
TomSmartBishop committed Mar 7, 2019
1 parent 51b370a commit 152bcba
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 126 deletions.
57 changes: 45 additions & 12 deletions ImageCommentsExtension/DataUriLoader.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,62 @@
using System;
using System.Text.RegularExpressions;
using System.IO;
using System.Linq;
using System.Net;

namespace LM.ImageComments
{
class DataUriLoader
public class WebLoader
{
public static Stream Load(Uri dataUri)
public static bool CanLoad(Uri dataUri)
{
return dataUri.Scheme.StartsWith("http");
}

public static string GetTempPath(Uri dataUri)
{
if (!dataUri.Scheme.StartsWith("http"))
return null;

var temp = Path.GetTempPath();
var invalids = Path.GetInvalidFileNameChars();
var localPath = new string(dataUri.LocalPath.Substring(1).Select(c => invalids.Contains(c) ? '-' : c).ToArray());
return Path.Combine(temp, localPath);
}

public static string Load(Uri dataUri)
{
if (dataUri == null)
throw new ArgumentException();
if (dataUri.Scheme != "data")
throw new ArgumentException();
var tempPath = GetTempPath(dataUri);

if (tempPath == null || File.Exists(tempPath))
return tempPath;

new WebClient().DownloadFile(dataUri, tempPath);

Regex regex = new Regex(@"data:(?<mime>[\w/]+);(?<encoding>\w+),(?<data>.*)", RegexOptions.Compiled);
Match match = regex.Match(dataUri.OriginalString);
return tempPath;
}
}

public class DataUriLoader
{
public static bool CanLoad(Uri dataUri)
{
return dataUri.Scheme == "data";
}

public static Stream Load(Uri dataUri)
{
var regex = new Regex(@"data:(?<mime>[\w/]+);(?<encoding>\w+),(?<data>.*)", RegexOptions.Compiled);
var match = regex.Match(dataUri.OriginalString);

string mimeType = match.Groups["mime"].Value;
string encoding = match.Groups["encoding"].Value;
string base64Data = match.Groups["data"].Value;
var mimeType = match.Groups["mime"].Value;
var encoding = match.Groups["encoding"].Value;
var base64Data = match.Groups["data"].Value;

if (encoding != "base64")
throw new NotSupportedException();

byte[] data = Convert.FromBase64String(base64Data);
var data = Convert.FromBase64String(base64Data);

return new MemoryStream(data);
}
Expand Down
12 changes: 9 additions & 3 deletions ImageCommentsExtension/ErrorTaggerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@
using System.Diagnostics;

[Export(typeof(IViewTaggerProvider))]
[ContentType("CSharp"), ContentType("C/C++"), ContentType("Basic"), ContentType("Python"), ContentType("F#")]
[
ContentType("CSharp"),
ContentType("C/C++"),
ContentType("Basic"),
ContentType("F#"),
ContentType("JScript"),
ContentType("Python")
]
[TagType(typeof(ErrorTag))]
internal class ErrorTaggerProvider : IViewTaggerProvider
{
Expand All @@ -25,8 +32,7 @@ public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where
}

Trace.Assert(textView is IWpfTextView);

ImageAdornmentManager imageAdornmentManager = textView.Properties.GetOrCreateSingletonProperty<ImageAdornmentManager>(() => new ImageAdornmentManager((IWpfTextView)textView));
ImageAdornmentManager imageAdornmentManager = textView.Properties.GetOrCreateSingletonProperty<ImageAdornmentManager>("ImageAdornmentManager", () => new ImageAdornmentManager((IWpfTextView)textView));
return imageAdornmentManager as ITagger<T>;
}
}
Expand Down
91 changes: 31 additions & 60 deletions ImageCommentsExtension/ImageAdornmentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ namespace LM.ImageComments.EditorComponent
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Controls;
using System.Windows.Media;
using System.Xml;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
Expand All @@ -24,6 +22,7 @@ public class ImageAdornmentManager : ITagger<ErrorTag>, IDisposable
private bool _initialised1;
private bool _initialised2;
private readonly List<ITagSpan<ErrorTag>> _errorTags;
public ITextDocumentFactoryService TextDocumentFactory { get; set; }

public static bool Enabled { get; set; }

Expand All @@ -46,9 +45,7 @@ public static void ToggleEnabled()
ThreadHelper.ThrowIfNotOnUIThread();

Enabled = !Enabled;
string message = string.Format("Image comments {0}. Scroll editor window(s) to update.",
Enabled ? "enabled" : "disabled");
UIMessage.Show(message);
UIMessage.Show($"Image comments enabled: {Enabled}. Scroll editor window(s) to update.");
}

public ImageAdornmentManager(IWpfTextView view)
Expand Down Expand Up @@ -81,20 +78,19 @@ private void LayoutChangedHandler(object sender, TextViewLayoutChangedEventArgs
return;

_errorTags.Clear();
if (TagsChanged != null)
{
TagsChanged(this, new SnapshotSpanEventArgs(new SnapshotSpan(_view.TextSnapshot, new Span(0, _view.TextSnapshot.Length))));
}
TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(new SnapshotSpan(_view.TextSnapshot, new Span(0, _view.TextSnapshot.Length))));

OnTagsChanged(new SnapshotSpan(_view.TextSnapshot, new Span(0, _view.TextSnapshot.Length)));

foreach (ITextViewLine line in _view.TextViewLines) // TODO [?]: implement more sensible handling of removing error tags, then use e.NewOrReformattedLines
foreach (var line in _view.TextViewLines) // TODO [?]: implement more sensible handling of removing error tags, then use e.NewOrReformattedLines
{
int lineNumber = line.Snapshot.GetLineFromPosition(line.Start.Position).LineNumber;
var lineNumber = line.Snapshot.GetLineFromPosition(line.Start.Position).LineNumber;
//TODO [?]: Limit rate of calls to the below when user is editing a line
try
{
CreateVisuals(line, lineNumber);
ITextDocument textDoc = null;
var success = TextDocumentFactory?.TryGetTextDocument(_view.TextBuffer, out textDoc);
CreateVisuals(line, lineNumber, success.HasValue && success.Value && textDoc!=null ? textDoc.FilePath : null);
}
catch (InvalidOperationException ex)
{
Expand All @@ -121,42 +117,40 @@ private void LayoutChangedHandler(object sender, TextViewLayoutChangedEventArgs
/// <summary>
/// Scans text line for matching image comment signature, then adds new or updates existing image adornment
/// </summary>
private void CreateVisuals(ITextViewLine line, int lineNumber)
private void CreateVisuals(ITextViewLine line, int lineNumber, string absFilename)
{
ThreadHelper.ThrowIfNotOnUIThread();

string lineText = line.Extent.GetText().Split(new string[] { "\r\n", "\r" }, StringSplitOptions.None)[0];
string matchedText;
int matchIndex = ImageCommentParser.Match(_contentTypeName, lineText, out matchedText);
var directory = absFilename!=null ? System.IO.Path.GetDirectoryName(absFilename) : null;
var lineText = line.Extent.GetText().Split(new string[] { "\r\n", "\r" }, StringSplitOptions.None)[0];
var matchIndex = ImageCommentParser.Match(_contentTypeName, lineText, out var matchedText);
if (matchIndex >= 0)
{
lineText = line.Extent.GetText().Split(new string[] { "\r\n", "\r" }, StringSplitOptions.None)[0];
// Get coordinates of text
int start = line.Extent.Start.Position + matchIndex;
int end = line.Start + (line.Extent.Length - 1);
var start = line.Extent.Start.Position + matchIndex;
var end = line.Start + (line.Extent.Length - 1);
var span = new SnapshotSpan(_view.TextSnapshot, Span.FromBounds(start, end));

Exception xmlParseException;
string imageUrl;
double scale;
Color bgColor = new Color();
ImageCommentParser.TryParse(matchedText, out imageUrl, out scale, ref bgColor, out xmlParseException);
var bgColor = new Color();
ImageCommentParser.TryParse(matchedText, out var imageUrl, out var scale, ref bgColor, out var xmlParseError);

if (xmlParseException != null)
if (xmlParseError != null)
{
if (Images.ContainsKey(lineNumber))
{
_layer.RemoveAdornment(Images[lineNumber]);
Images.Remove(lineNumber);
}

_errorTags.Add(new TagSpan<ErrorTag>(span, new ErrorTag("XML parse error", GetErrorMessage(xmlParseException))));
_errorTags.Add(new TagSpan<ErrorTag>(span,
new ErrorTag("XML parse error", $"Problem with comment format: {xmlParseError}")));

return;
}

MyImage image;
Exception imageLoadingException = null;
string loadingMessage = null;

// Check for and update existing image
MyImage existingImage = Images.ContainsKey(lineNumber) ? Images[lineNumber] : null;
Expand All @@ -165,7 +159,7 @@ private void CreateVisuals(ITextViewLine line, int lineNumber)
image = existingImage;
if (existingImage.Url != imageUrl || existingImage.BgColor!= bgColor) // URL different, so set new source
{
existingImage.TrySet(imageUrl, scale, bgColor, out imageLoadingException, () => CreateVisuals(line, lineNumber));
existingImage.TrySet(directory, imageUrl, scale, bgColor, out loadingMessage, () => CreateVisuals(line, lineNumber, absFilename));
} else if (existingImage.Url == imageUrl && Math.Abs(existingImage.Scale - scale) > 0.0001) // URL same but scale changed
{
existingImage.Scale = scale;
Expand All @@ -174,21 +168,21 @@ private void CreateVisuals(ITextViewLine line, int lineNumber)
else // No existing image, so create new one
{
image = new MyImage(_variableExpander);
image.TrySet(imageUrl, scale, bgColor, out imageLoadingException, () => CreateVisuals(line, lineNumber));
image.TrySet(directory, imageUrl, scale, bgColor, out loadingMessage, () => CreateVisuals(line, lineNumber, absFilename));
Images.Add(lineNumber, image);
}

// Position image and add as adornment
if (imageLoadingException == null && image.Source!= null)
if (loadingMessage == null && image.Source!= null)
{

Geometry g = _view.TextViewLines.GetMarkerGeometry(span);
if (g == null) // Exceptional case when image dimensions are massive (e.g. specifying very large scale factor)
var geometry = _view.TextViewLines.GetMarkerGeometry(span);
if (geometry == null) // Exceptional case when image dimensions are massive (e.g. specifying very large scale factor)
{
throw new InvalidOperationException("Couldn't get source code line geometry. Is the loaded image massive?");
}
double textLeft = g.Bounds.Left;
double textBottom = line.TextBottom;
var textLeft = geometry.Bounds.Left;
var textBottom = line.TextBottom;
Canvas.SetLeft(image, textLeft);
Canvas.SetTop(image, textBottom);

Expand All @@ -209,18 +203,11 @@ private void CreateVisuals(ITextViewLine line, int lineNumber)
if (Images.ContainsKey(lineNumber))
{
Images.Remove(lineNumber);

}

if(image.Source==null)
{
_errorTags.Add(new TagSpan<ErrorTag>(span, new ErrorTag("No image set")));
}
else
{
_errorTags.Add(new TagSpan<ErrorTag>(span,
new ErrorTag("Trouble loading image", GetErrorMessage(imageLoadingException))));
}
_errorTags.Add(new TagSpan<ErrorTag>(span, loadingMessage == null ?
new ErrorTag("No image set", "No image set") :
new ErrorTag("Trouble loading image", loadingMessage)));
}
}
else
Expand All @@ -232,20 +219,6 @@ private void CreateVisuals(ITextViewLine line, int lineNumber)
}
}

private static string GetErrorMessage(Exception exception)
{
Trace.WriteLine("Problem parsing comment text or loading image...\n" + exception);

string message;
if (exception is XmlException)
message = "Problem with comment format: " + exception.Message;
else if (exception is NotSupportedException)
message = exception.Message + "\nThis problem could be caused by a corrupt, invalid or unsupported image file.";
else
message = exception.Message;
return message;
}

private void UnsubscribeFromViewerEvents()
{
_view.LayoutChanged -= LayoutChangedHandler;
Expand All @@ -262,9 +235,7 @@ public IEnumerable<ITagSpan<ErrorTag>> GetTags(NormalizedSnapshotSpanCollection
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
protected void OnTagsChanged(SnapshotSpan span)
{
EventHandler<SnapshotSpanEventArgs> tagsChanged = TagsChanged;
if (tagsChanged != null)
tagsChanged(this, new SnapshotSpanEventArgs(span));
TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(span));
}

#endregion
Expand Down
18 changes: 15 additions & 3 deletions ImageCommentsExtension/ImageAdornmentManagerFactory.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.VisualStudio.Text;

namespace LM.ImageComments.EditorComponent
{
using System.ComponentModel.Composition;
Expand All @@ -9,10 +11,19 @@ namespace LM.ImageComments.EditorComponent
/// that instantiates the adornment on the event of a <see cref="IWpfTextView"/>'s creation
/// </summary>
[Export(typeof(IWpfTextViewCreationListener))]
[ContentType("CSharp"), ContentType("C/C++"), ContentType("Basic"), ContentType("F#")]
[
ContentType("CSharp"),
ContentType("C/C++"),
ContentType("Basic"),
ContentType("F#"),
ContentType("JScript"),
ContentType("Python")
]
[TextViewRole(PredefinedTextViewRoles.Document)]
internal sealed class ImageAdornmentManagerFactory : IWpfTextViewCreationListener
{
[Import] public ITextDocumentFactoryService TextDocumentFactory { get; set; }

/// <summary>
/// Defines the adornment layer for the adornment. This layer is ordered
/// after the selection layer in the Z-order
Expand All @@ -21,15 +32,16 @@ internal sealed class ImageAdornmentManagerFactory : IWpfTextViewCreationListene
[Name("ImageCommentLayer")]
[Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)]
[TextViewRole(PredefinedTextViewRoles.Document)]
public AdornmentLayerDefinition editorAdornmentLayer = null;
public AdornmentLayerDefinition EditorAdornmentLayer = null;

/// <summary>
/// Instantiates a ImageAdornment manager when a textView is created.
/// </summary>
/// <param name="textView">The <see cref="IWpfTextView"/> upon which the adornment should be placed</param>
public void TextViewCreated(IWpfTextView textView)
{
textView.Properties.GetOrCreateSingletonProperty<ImageAdornmentManager>(() => new ImageAdornmentManager(textView));
ImageAdornmentManager manager = textView.Properties.GetOrCreateSingletonProperty<ImageAdornmentManager>("ImageAdornmentManager", () => new ImageAdornmentManager(textView));
manager.TextDocumentFactory = TextDocumentFactory;
}
}
}
Loading

0 comments on commit 152bcba

Please sign in to comment.