Skip to content

Commit

Permalink
add VS2010 syntax colorizer to the source control
Browse files Browse the repository at this point in the history
  • Loading branch information
gasparnagy committed Jul 2, 2010
1 parent c4a768f commit bd3128d
Show file tree
Hide file tree
Showing 8 changed files with 543 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;

namespace GherkinFileClassifier
{
internal static class GherkinFileClassificationDefinition
{
[Export]
[Name("gherkin")]
[BaseDefinition("text")]
internal static ContentTypeDefinition diffContentTypeDefinition = null;

[Export]
[FileExtension(".feature")]
[ContentType("gherkin")]
internal static FileExtensionToContentTypeDefinition patchFileExtensionDefinition = null;

[Export(typeof(ClassificationTypeDefinition))]
[Name("gherkin.tag")]
internal static ClassificationTypeDefinition GherkinTagClassifierType = null;
}
}
118 changes: 118 additions & 0 deletions IdeIntegration/Vs2010Integration/GherkinFileClassifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Windows.Media;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;

namespace GherkinFileClassifier
{

#region Provider definition
/// <summary>
/// This class causes a classifier to be added to the set of classifiers. Since
/// the content type is set to "text", this classifier applies to all text files
/// </summary>
[Export(typeof(IClassifierProvider))]
[ContentType("gherkin")]
internal class GherkinFileClassifierProvider : IClassifierProvider
{
/// <summary>
/// Import the classification registry to be used for getting a reference
/// to the custom classification type later.
/// </summary>
[Import]
internal IClassificationTypeRegistryService ClassificationRegistry = null; // Set via MEF

[Import]
internal IBufferTagAggregatorFactoryService BufferTagAggregatorFactoryService { get; set; }

public IClassifier GetClassifier(ITextBuffer buffer)
{
return buffer.Properties.GetOrCreateSingletonProperty(() =>
new GherkinFileClassifier(buffer, ClassificationRegistry, BufferTagAggregatorFactoryService));
}
}
#endregion //provider def

#region Classifier
/// <summary>
/// Classifier that classifies all text as an instance of the OrinaryClassifierType
/// </summary>
class GherkinFileClassifier : IClassifier
{
private readonly IClassificationTypeRegistryService classificationTypeRegistryService;

internal IBufferTagAggregatorFactoryService BufferTagAggregatorFactoryService { get; private set; }

internal GherkinFileClassifier(ITextBuffer buffer, IClassificationTypeRegistryService classificationTypeRegistryService, IBufferTagAggregatorFactoryService bufferTagAggregatorFactoryService)
{
BufferTagAggregatorFactoryService = bufferTagAggregatorFactoryService;
this.classificationTypeRegistryService = classificationTypeRegistryService;

buffer.Changed += new EventHandler<TextContentChangedEventArgs>(buffer_Changed);
}

private IList<ClassificationSpan> lastClassification = null;

void buffer_Changed(object sender, TextContentChangedEventArgs e)
{
var chStart = e.Changes.Min(ch => ch.NewPosition);
var chEnd = e.Changes.Max(ch => ch.NewPosition + ch.NewLength);
var chSpan = new Span(chStart, chEnd);

SnapshotSpan allText = new SnapshotSpan(e.After, 0, e.After.Length);
var newClassification = SyntaxColorer.GetClassificationSpans(allText, classificationTypeRegistryService);

bool fullRefresh = false;

if (lastClassification != null)
{
var overlappingClassifications = lastClassification.Where(cs => cs.Span.OverlapsWith(chSpan)).Select(cs => cs.ClassificationType);
fullRefresh |= overlappingClassifications.Any(cl => cl.Classification.Equals("string", StringComparison.InvariantCultureIgnoreCase));
}
if (!fullRefresh)
{
var overlappingClassifications = newClassification.Where(cs => cs.Span.OverlapsWith(chSpan)).Select(cs => cs.ClassificationType);
fullRefresh |= overlappingClassifications.Any(cl => cl.Classification.Equals("string", StringComparison.InvariantCultureIgnoreCase));
}

lastClassification = newClassification;

if (fullRefresh)
{
if (ClassificationChanged != null)
ClassificationChanged(this, new ClassificationChangedEventArgs(
new SnapshotSpan(e.After, 0, e.After.Length)));
}
}

/// <summary>
/// This method scans the given SnapshotSpan for potential matches for this classification.
/// In this instance, it classifies everything and returns each span as a new ClassificationSpan.
/// </summary>
/// <param name="span">The span currently being classified</param>
/// <returns>A list of ClassificationSpans that represent spans identified to be of this classification</returns>
public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span)
{
if (lastClassification == null)
{
SnapshotSpan allText = new SnapshotSpan(span.Snapshot, 0, span.Snapshot.Length);
lastClassification = SyntaxColorer.GetClassificationSpans(allText, classificationTypeRegistryService);
}

return lastClassification;
}

#pragma warning disable 67
// This event gets raised if a non-text change would affect the classification in some way,
// for example typing /* would cause the clssification to change in C# without directly
// affecting the span.
public event EventHandler<ClassificationChangedEventArgs> ClassificationChanged;
#pragma warning restore 67
}
#endregion //Classifier
}
22 changes: 22 additions & 0 deletions IdeIntegration/Vs2010Integration/GherkinFileClassifierFormat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.ComponentModel.Composition;
using System.Windows.Media;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;

namespace GherkinFileClassifier
{
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = "gherkin.tag")]
[Name("gherkin.tag")]
[UserVisible(true)] //this should be visible to the end user
[Order(Before = Priority.Default)] //set the priority to be after the default classifiers
internal sealed class GherkinFileClassifierFormat : ClassificationFormatDefinition
{
public GherkinFileClassifierFormat()
{
this.DisplayName = "Gherkin Tag"; //human readable version of the name
this.ForegroundColor = Colors.Gray;
this.IsItalic = true;
}
}
}
33 changes: 33 additions & 0 deletions IdeIntegration/Vs2010Integration/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("GherkinFileClassifier")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("GherkinFileClassifier")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
198 changes: 198 additions & 0 deletions IdeIntegration/Vs2010Integration/SyntaxColoringListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using java.util;
using gherkin;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;

namespace GherkinFileClassifier
{
public class ListeningDoneException : Exception
{
}

public class SyntaxColorer
{
public static IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan snapshotSpan, IClassificationTypeRegistryService registry)
{
// string fileContent = snapshotSpan.Snapshot.GetText(0, snapshotSpan.End.Position);
string fileContent = snapshotSpan.Snapshot.GetText();
var gherkinListener = new SyntaxColoringListener(snapshotSpan, registry);

I18n languageService = new I18n("en");

try
{
Lexer lexer = languageService.lexer(gherkinListener);
lexer.scan(fileContent, null, 0);
return gherkinListener.Classifications;
}
catch (Exception ex)
{
/*
var errorClassificationType = registry.GetClassificationType("error");
int startIndex = 0;
if (gherkinListener.Classifications.Any())
{
var last = gherkinListener.Classifications.Last();
startIndex = last.Span.Start + last.Span.Length;
}
gherkinListener.Classifications.Add(new ClassificationSpan(
new SnapshotSpan(snapshotSpan.Snapshot, startIndex, snapshotSpan.Snapshot.Length - startIndex),
errorClassificationType));
*/

return gherkinListener.Classifications;
}

}
}

public class SyntaxColoringListener : Listener
{
public SnapshotSpan SnapshotSpan { get; set; }
public List<ClassificationSpan> Classifications { get; private set; }
private int startLine;
private int endLine;

private readonly IClassificationType keywordClassificationType;
private readonly IClassificationType commentClassificationType;
private readonly IClassificationType tagClassificationType;
private readonly IClassificationType multilineTextClassificationType;

public SyntaxColoringListener(SnapshotSpan snapshotSpan, IClassificationTypeRegistryService registry)
{
SnapshotSpan = snapshotSpan;
startLine = SnapshotSpan.Start.GetContainingLine().LineNumber;
endLine = SnapshotSpan.End.GetContainingLine().LineNumber;

Classifications = new List<ClassificationSpan>();

keywordClassificationType = registry.GetClassificationType("keyword");
commentClassificationType = registry.GetClassificationType("comment");
tagClassificationType = registry.GetClassificationType("gherkin.tag");
multilineTextClassificationType = registry.GetClassificationType("string");
}

private int? GetEditorLine(int line)
{
var editorLine = line - 1;
// if (editorLine > endLine)
// throw new ListeningDoneException();
// if (editorLine < startLine)
// return null;
return editorLine;
}

private void AddClassification(IClassificationType classificationType, int startIndex, int length)
{
Classifications.Add(
new ClassificationSpan(
new SnapshotSpan(SnapshotSpan.Snapshot, new Span(startIndex, length)),
classificationType));
}

private void ColorizeLine(int line, IClassificationType classificationType)
{
var editorLine = GetEditorLine(line);
if (editorLine == null)
return;

var snapshotLine = SnapshotSpan.Snapshot.GetLineFromLineNumber(editorLine.Value);
AddClassification(classificationType, snapshotLine.Start, snapshotLine.LengthIncludingLineBreak);
}

private void ColorizeLinePart(string lineTextPart, int line, IClassificationType classificationType)
{
var editorLine = GetEditorLine(line);
if (editorLine == null)
return;

var snapshotLine = SnapshotSpan.Snapshot.GetLineFromLineNumber(editorLine.Value);

int startIndex = snapshotLine.GetText().IndexOf(lineTextPart);
if (startIndex < 0)
return;

AddClassification(classificationType, snapshotLine.Start + startIndex, lineTextPart.Length);
}

public void location(string str, int line)
{

}

public void pyString(string text, int line)
{
int lineCount = text.Count(c => c.Equals('\n')) + 1 + 2;
for (int currentLine = line; currentLine < line + lineCount; currentLine++)
{
ColorizeLine(currentLine, multilineTextClassificationType);
}
}

public void feature(string keyword, string str2, string str3, int line)
{
RegisterKeyword(keyword, line);
}

public void background(string keyword, string str2, string str3, int line)
{
RegisterKeyword(keyword, line);
}

public void scenario(string keyword, string str2, string str3, int line)
{
RegisterKeyword(keyword, line);
}

public void scenarioOutline(string keyword, string str2, string str3, int line)
{
RegisterKeyword(keyword, line);
}

public void examples(string keyword, string str2, string str3, int line)
{
RegisterKeyword(keyword, line);
}

public void step(string keyword, string text, int line)
{
RegisterKeyword(keyword, line);
}

private void RegisterKeyword(string keyword, int line)
{
ColorizeLinePart(keyword, line, keywordClassificationType);
}

public void comment(string commentText, int line)
{
ColorizeLine(line, commentClassificationType);
}

public void tag(string tagName, int line)
{
ColorizeLinePart(tagName, line, tagClassificationType);
}

public void eof()
{

}

public void syntaxError(string str1, string str2, List l, int line)
{

}

public void row(List l, int line)
{

}
}
}
Loading

0 comments on commit bd3128d

Please sign in to comment.