├── .gitignore ├── CoffeeLite.sln ├── CoffeeLite ├── ClassificationTypes.cs ├── Classifier.cs ├── ClassifierProvider.cs ├── CoffeeLite.csproj ├── CoffeeOverview.cs ├── Extensions.cs ├── FileAndContentTypes.cs ├── MitLicense.txt ├── Properties │ └── AssemblyInfo.cs ├── TaggerBraces.cs ├── TaggerBracesProvider.cs ├── VisualFormatNames.cs ├── VisualFormats.cs └── source.extension.vsixmanifest ├── CoffeeParser ├── CoffeeParser.csproj ├── Extensions.cs ├── Parser.cs ├── Properties │ └── AssemblyInfo.cs ├── Token.cs └── TokenInfo.cs ├── CoffeeParserTest ├── CoffeeParserTest.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── TestAssignment.cs └── Utils.cs ├── Libs └── nunit.framework.dll └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | *.suo 4 | *.csproj.user -------------------------------------------------------------------------------- /CoffeeLite.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoffeeParser", "CoffeeParser\CoffeeParser.csproj", "{A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoffeeParserTest", "CoffeeParserTest\CoffeeParserTest.csproj", "{BBF8E343-B34C-4227-A65D-CCA4CDCEAA1E}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoffeeLite", "CoffeeLite\CoffeeLite.csproj", "{B03E0173-346A-43B8-8E36-3630DF9C904B}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Parser", "Parser\Parser.csproj", "{6B5B29BF-DC7E-4A9C-A660-C75F5867BBC1}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|Mixed Platforms = Debug|Mixed Platforms 16 | Debug|x86 = Debug|x86 17 | Release|Any CPU = Release|Any CPU 18 | Release|Mixed Platforms = Release|Mixed Platforms 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 25 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 26 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB}.Debug|x86.ActiveCfg = Debug|Any CPU 27 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 30 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB}.Release|Mixed Platforms.Build.0 = Release|Any CPU 31 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB}.Release|x86.ActiveCfg = Release|Any CPU 32 | {BBF8E343-B34C-4227-A65D-CCA4CDCEAA1E}.Debug|Any CPU.ActiveCfg = Debug|x86 33 | {BBF8E343-B34C-4227-A65D-CCA4CDCEAA1E}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 34 | {BBF8E343-B34C-4227-A65D-CCA4CDCEAA1E}.Debug|Mixed Platforms.Build.0 = Debug|x86 35 | {BBF8E343-B34C-4227-A65D-CCA4CDCEAA1E}.Debug|x86.ActiveCfg = Debug|x86 36 | {BBF8E343-B34C-4227-A65D-CCA4CDCEAA1E}.Debug|x86.Build.0 = Debug|x86 37 | {BBF8E343-B34C-4227-A65D-CCA4CDCEAA1E}.Release|Any CPU.ActiveCfg = Release|x86 38 | {BBF8E343-B34C-4227-A65D-CCA4CDCEAA1E}.Release|Mixed Platforms.ActiveCfg = Release|x86 39 | {BBF8E343-B34C-4227-A65D-CCA4CDCEAA1E}.Release|Mixed Platforms.Build.0 = Release|x86 40 | {BBF8E343-B34C-4227-A65D-CCA4CDCEAA1E}.Release|x86.ActiveCfg = Release|x86 41 | {BBF8E343-B34C-4227-A65D-CCA4CDCEAA1E}.Release|x86.Build.0 = Release|x86 42 | {B03E0173-346A-43B8-8E36-3630DF9C904B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {B03E0173-346A-43B8-8E36-3630DF9C904B}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {B03E0173-346A-43B8-8E36-3630DF9C904B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 45 | {B03E0173-346A-43B8-8E36-3630DF9C904B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 46 | {B03E0173-346A-43B8-8E36-3630DF9C904B}.Debug|x86.ActiveCfg = Debug|Any CPU 47 | {B03E0173-346A-43B8-8E36-3630DF9C904B}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {B03E0173-346A-43B8-8E36-3630DF9C904B}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {B03E0173-346A-43B8-8E36-3630DF9C904B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 50 | {B03E0173-346A-43B8-8E36-3630DF9C904B}.Release|Mixed Platforms.Build.0 = Release|Any CPU 51 | {B03E0173-346A-43B8-8E36-3630DF9C904B}.Release|x86.ActiveCfg = Release|Any CPU 52 | {6B5B29BF-DC7E-4A9C-A660-C75F5867BBC1}.Debug|Any CPU.ActiveCfg = Debug|x86 53 | {6B5B29BF-DC7E-4A9C-A660-C75F5867BBC1}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 54 | {6B5B29BF-DC7E-4A9C-A660-C75F5867BBC1}.Debug|Mixed Platforms.Build.0 = Debug|x86 55 | {6B5B29BF-DC7E-4A9C-A660-C75F5867BBC1}.Debug|x86.ActiveCfg = Debug|x86 56 | {6B5B29BF-DC7E-4A9C-A660-C75F5867BBC1}.Debug|x86.Build.0 = Debug|x86 57 | {6B5B29BF-DC7E-4A9C-A660-C75F5867BBC1}.Release|Any CPU.ActiveCfg = Release|x86 58 | {6B5B29BF-DC7E-4A9C-A660-C75F5867BBC1}.Release|Mixed Platforms.ActiveCfg = Release|x86 59 | {6B5B29BF-DC7E-4A9C-A660-C75F5867BBC1}.Release|Mixed Platforms.Build.0 = Release|x86 60 | {6B5B29BF-DC7E-4A9C-A660-C75F5867BBC1}.Release|x86.ActiveCfg = Release|x86 61 | {6B5B29BF-DC7E-4A9C-A660-C75F5867BBC1}.Release|x86.Build.0 = Release|x86 62 | EndGlobalSection 63 | GlobalSection(SolutionProperties) = preSolution 64 | HideSolutionNode = FALSE 65 | EndGlobalSection 66 | EndGlobal 67 | -------------------------------------------------------------------------------- /CoffeeLite/ClassificationTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.VisualStudio.Text.Classification; 6 | using System.ComponentModel.Composition; 7 | using Microsoft.VisualStudio.Utilities; 8 | 9 | namespace CoffeeSyntax { 10 | static class ClassificationTypes { 11 | 12 | [Export] 13 | [Name(VisualFormatNames.Coffee)] 14 | internal static ClassificationTypeDefinition CoffeeClassificationDefinition = null; 15 | 16 | [Export] 17 | [Name(VisualFormatNames.CoffeeString)] 18 | [BaseDefinition(VisualFormatNames.Coffee)] 19 | internal static ClassificationTypeDefinition StringClassificationDefinition = null; 20 | 21 | [Export] 22 | [Name(VisualFormatNames.CoffeeIdentifier)] 23 | [BaseDefinition(VisualFormatNames.Coffee)] 24 | internal static ClassificationTypeDefinition IdentifierClassificationDefinition = null; 25 | 26 | [Export] 27 | [Name(VisualFormatNames.CoffeeKeyword)] 28 | [BaseDefinition(VisualFormatNames.Coffee)] 29 | internal static ClassificationTypeDefinition KeywordClassificationDefinition = null; 30 | 31 | [Export] 32 | [Name(VisualFormatNames.CoffeeNumericLiteral)] 33 | [BaseDefinition(VisualFormatNames.Coffee)] 34 | internal static ClassificationTypeDefinition NumericLiteralClassificationDefinition = null; 35 | 36 | [Export] 37 | [Name(VisualFormatNames.CoffeeComment)] 38 | [BaseDefinition(VisualFormatNames.Coffee)] 39 | internal static ClassificationTypeDefinition CommentClassificationDefinition = null; 40 | 41 | [Export] 42 | [Name(VisualFormatNames.CoffeeThis)] 43 | [BaseDefinition(VisualFormatNames.Coffee)] 44 | internal static ClassificationTypeDefinition ThisClassificationDefinition = null; 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /CoffeeLite/Classifier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.VisualStudio.Text.Classification; 6 | using Microsoft.VisualStudio.Text; 7 | using CoffeeParser; 8 | 9 | namespace CoffeeSyntax { 10 | 11 | internal class Classifier : IClassifier { 12 | 13 | public Classifier(CoffeeOverview overview, IClassificationTypeRegistryService classificationRegistry) { 14 | this.overview = overview; 15 | this.clsCoffeeString = classificationRegistry.GetClassificationType(VisualFormatNames.CoffeeString); 16 | this.clsCoffeeIdentifier = classificationRegistry.GetClassificationType(VisualFormatNames.CoffeeIdentifier); 17 | this.clsCoffeeKeyword = classificationRegistry.GetClassificationType(VisualFormatNames.CoffeeKeyword); 18 | this.clsCoffeeNumericLiteral = classificationRegistry.GetClassificationType(VisualFormatNames.CoffeeNumericLiteral); 19 | this.clsCoffeeComment = classificationRegistry.GetClassificationType(VisualFormatNames.CoffeeComment); 20 | this.clsThis = classificationRegistry.GetClassificationType(VisualFormatNames.CoffeeThis); 21 | overview.MultiLinesChanged += (o, e) => { 22 | if (this.latestSnapshot != null && this.ClassificationChanged != null) { 23 | var spans = new NormalizedSnapshotSpanCollection( 24 | e.Added.Concat(e.Removed) 25 | .Select(x => x.Span.GetSpan(this.latestSnapshot))); 26 | foreach (var span in spans) { 27 | var args = new ClassificationChangedEventArgs(span); 28 | this.ClassificationChanged(this, args); 29 | } 30 | } 31 | }; 32 | } 33 | 34 | private CoffeeOverview overview; 35 | private IClassificationType clsCoffeeString; 36 | private IClassificationType clsCoffeeIdentifier; 37 | private IClassificationType clsCoffeeKeyword; 38 | private IClassificationType clsCoffeeNumericLiteral; 39 | private IClassificationType clsCoffeeComment; 40 | private IClassificationType clsThis; 41 | private ITextSnapshot latestSnapshot = null; 42 | 43 | public event EventHandler ClassificationChanged; 44 | 45 | private IEnumerable Split(SnapshotSpan span, IEnumerable remove) { 46 | var removeNorm = new NormalizedSnapshotSpanCollection(remove); 47 | foreach (var r in removeNorm.OrderBy(x => x.Start.Position)) { 48 | if (r.Start < span.End) { 49 | if (r.Start > span.Start) { 50 | yield return new SnapshotSpan(span.Start, r.Start); 51 | } 52 | if (r.End < span.End) { 53 | span = new SnapshotSpan(r.End, span.End); 54 | } else { 55 | yield break; 56 | } 57 | } 58 | } 59 | yield return span; 60 | } 61 | 62 | public IList GetClassificationSpans(SnapshotSpan span) { 63 | this.latestSnapshot = span.Snapshot; 64 | var ss = span.Snapshot; 65 | var line = ss.GetLineFromPosition(span.Start.Position); 66 | var mlOverlaps = this.overview.MultiLines.Where(x => x.GetSpan(ss).OverlapsWith(line.Extent)).ToArray(); 67 | var withoutMls = Split(line.Extent, mlOverlaps.Select(x => x.GetSpan(ss))); 68 | var query = 69 | from part in withoutMls 70 | let text = part.GetText() 71 | let tokens = Parser.Parse(text) 72 | from token in tokens 73 | let cls = this.MapToken(token.Token) 74 | where cls != null 75 | let s = new SnapshotSpan(ss, part.Start.Position + token.Start, token.Length) 76 | select new ClassificationSpan(s, cls); 77 | var queryMl = 78 | from part in mlOverlaps 79 | let cls = this.MapMultiline(part.Type) 80 | where cls != null 81 | select new ClassificationSpan(part.GetSpan(ss), cls); 82 | var ret = query.Concat(queryMl).ToArray(); 83 | return ret; 84 | } 85 | 86 | private IClassificationType MapMultiline(CoffeeOverview.MultiLineType type) { 87 | switch (type) { 88 | case CoffeeOverview.MultiLineType.Comment: 89 | return this.clsCoffeeComment; 90 | case CoffeeOverview.MultiLineType.HeredocDouble: 91 | case CoffeeOverview.MultiLineType.HeredocSingle: 92 | case CoffeeOverview.MultiLineType.StringDouble: 93 | case CoffeeOverview.MultiLineType.StringSingle: 94 | return this.clsCoffeeString; 95 | default: 96 | return null; 97 | } 98 | } 99 | 100 | private IClassificationType MapToken(Token token) { 101 | switch (token) { 102 | case Token.Identifier: 103 | return this.clsCoffeeIdentifier; 104 | case Token.Keyword: 105 | return this.clsCoffeeKeyword; 106 | case Token.NumericLiteral: 107 | return this.clsCoffeeNumericLiteral; 108 | case Token.StringLiteral: 109 | return this.clsCoffeeString; 110 | case Token.Comment: 111 | return this.clsCoffeeComment; 112 | case Token.This: 113 | return this.clsThis; 114 | default: 115 | return null; 116 | } 117 | } 118 | 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /CoffeeLite/ClassifierProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.VisualStudio.Text.Classification; 6 | using System.ComponentModel.Composition; 7 | using Microsoft.VisualStudio.Text; 8 | using Microsoft.VisualStudio.Utilities; 9 | 10 | namespace CoffeeSyntax { 11 | 12 | [Export(typeof(IClassifierProvider))] 13 | [ContentType("coffee")] 14 | internal class ClassifierProvider : IClassifierProvider { 15 | 16 | [Import] 17 | internal IClassificationTypeRegistryService ClassificationRegistry = null; 18 | 19 | public IClassifier GetClassifier(ITextBuffer textBuffer) { 20 | return textBuffer.Properties.GetOrCreateSingletonProperty(() => { 21 | var overview = textBuffer.Properties.GetOrCreateSingletonProperty(() => new CoffeeOverview(textBuffer)); 22 | return new Classifier(overview, this.ClassificationRegistry); 23 | }); 24 | } 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /CoffeeLite/CoffeeLite.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 10.0.20305 7 | 2.0 8 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 9 | {B03E0173-346A-43B8-8E36-3630DF9C904B} 10 | Library 11 | Properties 12 | CoffeeLite 13 | CoffeeLite 14 | v4.0 15 | 512 16 | false 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | False 38 | 39 | 40 | False 41 | 42 | 43 | False 44 | 45 | 46 | False 47 | 48 | 49 | False 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | Designer 79 | 80 | 81 | 82 | 83 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB} 84 | CoffeeParser 85 | 86 | 87 | 88 | 89 | Always 90 | true 91 | 92 | 93 | 94 | 95 | 102 | -------------------------------------------------------------------------------- /CoffeeLite/CoffeeOverview.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.VisualStudio.Text; 6 | 7 | namespace CoffeeSyntax { 8 | 9 | using MlInfo = Tuple; 10 | 11 | class CoffeeOverview { 12 | 13 | public enum MultiLineType { 14 | Comment, Backtick, StringSingle, StringDouble, HeredocSingle, HeredocDouble, Regex 15 | } 16 | 17 | public struct MultiLine { 18 | public MultiLine(ITrackingSpan span, MultiLineType type) 19 | : this() { 20 | this.Span = span; 21 | this.Type = type; 22 | } 23 | public ITrackingSpan Span { get; private set; } 24 | public MultiLineType Type { get; private set; } 25 | public SnapshotSpan GetSpan(ITextSnapshot ss) { 26 | return this.Span.GetSpan(ss); 27 | } 28 | public override string ToString() { 29 | return string.Format("{{ {0}, {1} }}", this.Span, this.Type); 30 | } 31 | } 32 | 33 | public class MultiLinesChangedEventArgs : EventArgs { 34 | public MultiLinesChangedEventArgs(IEnumerable added, IEnumerable removed) { 35 | this.Added = added; 36 | this.Removed = removed; 37 | } 38 | public IEnumerable Added { get; private set; } 39 | public IEnumerable Removed { get; private set; } 40 | } 41 | 42 | public CoffeeOverview(ITextBuffer textBuffer) { 43 | this.textBuffer = textBuffer; 44 | this.CalcMultiLines(); 45 | textBuffer.Changed += (o, e) => { 46 | if (this.IsMultiLineChange(e)) { 47 | this.CalcMultiLines(); 48 | } 49 | }; 50 | } 51 | 52 | private ITextBuffer textBuffer; 53 | 54 | public IEnumerable MultiLines { get; private set; } 55 | public event EventHandler MultiLinesChanged; 56 | 57 | private static MlInfo[] multiLinesDelimiters = 58 | { 59 | Tuple.Create("###", "###", MultiLineType.Comment, (string)null), 60 | //Tuple.Create("/*", "*/", MultiLineType.Comment, (string)null), 61 | Tuple.Create("`", "`", MultiLineType.Backtick, (string)null), 62 | Tuple.Create("'", "'", MultiLineType.StringSingle, "\\'"), 63 | Tuple.Create("\"", "\"", MultiLineType.StringDouble, "\\\""), 64 | Tuple.Create("'''", "'''", MultiLineType.HeredocSingle, (string)null), 65 | Tuple.Create("\"\"\"", "\"\"\"", MultiLineType.HeredocDouble, (string)null), 66 | Tuple.Create("///", "///", MultiLineType.Regex, (string)null), 67 | }; 68 | 69 | private static string[] multiLinesSigTexts = 70 | multiLinesDelimiters.SelectMany(x => new[] { x.Item1, x.Item2 }).Distinct().ToArray(); 71 | 72 | private bool IsMultiLineChange(TextContentChangedEventArgs e) { 73 | return e.Changes.Any(change => { 74 | return multiLinesSigTexts.Any(sigText => { 75 | var len1 = sigText.Length - 1; 76 | var beforeStart = Math.Max(0, change.OldPosition - len1); 77 | var beforeEnd = Math.Min(e.Before.Length, change.OldPosition + change.OldLength + len1); 78 | var textOld = beforeEnd > beforeStart ? e.Before.GetText(beforeStart, beforeEnd - beforeStart) : ""; 79 | var afterStart = Math.Max(0, change.NewPosition - len1); 80 | var afterEnd = Math.Min(e.After.Length, change.NewPosition + change.NewLength + len1); 81 | var textNew = afterEnd > afterStart ? e.After.GetText(afterStart, afterEnd - afterStart) : ""; 82 | return textOld.Contains(sigText) || textNew.Contains(sigText); 83 | }); 84 | }); 85 | } 86 | 87 | private void CalcMultiLines() { 88 | var spans = new List(); 89 | var ss = this.textBuffer.CurrentSnapshot; 90 | var text = ss.GetText(); 91 | MlInfo curMultiline = null; 92 | int startPos = -1; 93 | bool inSingleLineComment = false; 94 | for (int i = 0, n = text.Length; i < n; i++) { 95 | if (inSingleLineComment) { 96 | if (text[i].In('\r', '\n')) { 97 | inSingleLineComment = false; 98 | } 99 | } else { 100 | if (curMultiline == null) { 101 | if (text[i] == '#' && !text.IsAt(i, "###") && !text.IsAt(i - 1, "###") && !text.IsAt(i - 1, "###")) { 102 | inSingleLineComment = true; 103 | } else { 104 | curMultiline = multiLinesDelimiters.FirstOrDefault(x => text.IsAt(i, x.Item1)); 105 | if (curMultiline != null) { 106 | startPos = i; 107 | i += curMultiline.Item1.Length - 1 + curMultiline.Item2.Length - 1; 108 | } 109 | } 110 | } else { 111 | if (curMultiline.Item4 != null && text.IsAt(i, curMultiline.Item4)) { 112 | i += curMultiline.Item4.Length - 1; 113 | } else if (text.IsAt(i - (curMultiline.Item2.Length - 1), curMultiline.Item2)) { 114 | var trackingSpan = ss.CreateTrackingSpan(startPos, i - startPos + 1, SpanTrackingMode.EdgeExclusive); 115 | spans.Add(new MultiLine(trackingSpan, curMultiline.Item3)); 116 | curMultiline = null; 117 | } 118 | } 119 | } 120 | } 121 | if (curMultiline != null) { 122 | if (ss.GetLineNumberFromPosition(startPos) != ss.GetLineNumberFromPosition(text.Length - 1)) { 123 | var trackingSpan = ss.CreateTrackingSpan(startPos, text.Length - startPos, SpanTrackingMode.EdgeExclusive); 124 | spans.Add(new MultiLine(trackingSpan, curMultiline.Item3)); 125 | } 126 | } 127 | this.RaiseMultiLinesChanged(spans, this.MultiLines); 128 | this.MultiLines = spans; 129 | } 130 | 131 | private void RaiseMultiLinesChanged(IEnumerable added, IEnumerable removed) { 132 | if (this.MultiLinesChanged != null) { 133 | var eventArgs = new MultiLinesChangedEventArgs(added, removed); 134 | this.MultiLinesChanged(this, eventArgs); 135 | } 136 | } 137 | 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /CoffeeLite/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace CoffeeSyntax { 7 | static class Extensions { 8 | 9 | public static bool IsAt(this string s, int ofs, string isAtOfs) { 10 | int n = s.Length; 11 | for (int i = 0; i < isAtOfs.Length; i++, ofs++) { 12 | if (ofs < 0 || ofs >= n) { 13 | return false; 14 | } 15 | if (s[ofs] != isAtOfs[i]) { 16 | return false; 17 | } 18 | } 19 | return true; 20 | } 21 | 22 | public static TResult NullThru(this T o, Func fn, TResult @default = default(TResult)) { 23 | return o == null ? @default : fn(o); 24 | } 25 | 26 | public static TValue ValueOrDefault(this IDictionary d, TKey key, TValue @default = default(TValue)) { 27 | TValue ret; 28 | if (d.TryGetValue(key, out ret)) { 29 | return ret; 30 | } 31 | return @default; 32 | } 33 | 34 | public static bool In(this T item, params T[] isIn) { 35 | return isIn.Contains(item); 36 | } 37 | 38 | public static IEnumerable Distinct(this IEnumerable en, Func selector, Func result) { 39 | var seen = new Dictionary(); 40 | foreach (T item in en) { 41 | var compare = selector(item); 42 | T already; 43 | if (seen.TryGetValue(compare, out already)) { 44 | var r = result(already, item); 45 | seen[compare] = r; 46 | } else { 47 | seen.Add(compare, item); 48 | } 49 | } 50 | return seen.Values; 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CoffeeLite/FileAndContentTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.ComponentModel.Composition; 6 | using Microsoft.VisualStudio.Text.Classification; 7 | using Microsoft.VisualStudio.Utilities; 8 | 9 | namespace CoffeeSyntax { 10 | 11 | internal static class FileAndContentTypes { 12 | 13 | [Export] 14 | [Name("coffee")] 15 | [BaseDefinition("text")] 16 | internal static ContentTypeDefinition coffeeContentTypeDefinition = null; 17 | 18 | [Export] 19 | [FileExtension(".coffee")] 20 | [ContentType("coffee")] 21 | internal static FileExtensionToContentTypeDefinition coffeeFileExtensionDefinition = null; 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /CoffeeLite/MitLicense.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Chris Bacon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /CoffeeLite/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CoffeeLite")] 9 | [assembly: AssemblyDescription("CoffeeScript syntax highlighter for Visual Studio")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CoffeeLite")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("0.1.2.0")] 33 | [assembly: AssemblyFileVersion("0.1.2.0")] 34 | -------------------------------------------------------------------------------- /CoffeeLite/TaggerBraces.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.VisualStudio.Text.Tagging; 6 | using Microsoft.VisualStudio.Text.Editor; 7 | using Microsoft.VisualStudio.Text; 8 | using CoffeeParser; 9 | 10 | namespace CoffeeSyntax { 11 | class TaggerBraces : ITagger { 12 | 13 | public TaggerBraces(CoffeeOverview overview, ITextView view, ITextBuffer sourceBuffer) { 14 | this.overview = overview; 15 | this.view = view; 16 | this.sourceBuffer = sourceBuffer; 17 | this.currentChar = null; 18 | this.view.Caret.PositionChanged += (o, e) => { 19 | this.UpdateAtCaretPosition(e.NewPosition); 20 | }; 21 | this.view.LayoutChanged += (o, e) => { 22 | if (e.NewSnapshot != e.OldSnapshot) { 23 | this.UpdateAtCaretPosition(this.view.Caret.Position); 24 | } 25 | }; 26 | } 27 | 28 | private CoffeeOverview overview; 29 | private ITextView view; 30 | private ITextBuffer sourceBuffer; 31 | private SnapshotPoint? currentChar; 32 | 33 | public event EventHandler TagsChanged; 34 | 35 | private void UpdateAtCaretPosition(CaretPosition caretPosition) { 36 | this.currentChar = caretPosition.Point.GetPoint(this.sourceBuffer, caretPosition.Affinity); 37 | if (this.currentChar != null) { 38 | if (this.TagsChanged != null) { 39 | var curSs = this.sourceBuffer.CurrentSnapshot; 40 | var args = new SnapshotSpanEventArgs(new SnapshotSpan(curSs, 0, curSs.Length)); 41 | this.TagsChanged(this, args); 42 | } 43 | } 44 | } 45 | 46 | private Tuple[] braces = 47 | { 48 | Tuple.Create('(', ')'), 49 | Tuple.Create('{', '}'), 50 | Tuple.Create('[', ']'), 51 | }; 52 | 53 | private bool ShouldTag(SnapshotPoint pt) { 54 | var ss = pt.Snapshot; 55 | if (this.overview.MultiLines.Any(x => x.GetSpan(ss).Contains(pt))) { 56 | return false; 57 | } 58 | var line = pt.GetContainingLine(); 59 | var tokens = Parser.Parse(line.GetText()); 60 | return !tokens.Any(x => 61 | x.Token.In(Token.Comment) && 62 | pt.Position >= line.Start.Position + x.Start && pt.Position < line.Start.Position + x.Start + x.Length); 63 | } 64 | 65 | public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) { 66 | if (spans.Count == 0) { 67 | yield break; 68 | } 69 | if (this.currentChar == null || this.currentChar.Value.Position >= this.currentChar.Value.Snapshot.Length) { 70 | yield break; 71 | } 72 | foreach (var span in spans) { 73 | var ss = span.Snapshot; 74 | var cc = ss == this.currentChar.Value.Snapshot ? 75 | this.currentChar.Value : this.currentChar.Value.TranslateTo(ss, PointTrackingMode.Positive); 76 | if (this.ShouldTag(cc)) { 77 | char cNext = cc.GetChar(); 78 | var brace1 = braces.FirstOrDefault(x => x.Item1 == cNext); 79 | if (brace1 != null) { 80 | var match = this.FindMatchForwards(ss, brace1.Item1, brace1.Item2, cc.Position); 81 | if (match != null) { 82 | var tag = new TextMarkerTag("blue"); 83 | yield return new TagSpan(new SnapshotSpan(cc, 1), tag); 84 | yield return new TagSpan(new SnapshotSpan(ss, match.Value, 1), tag); 85 | } 86 | } 87 | } 88 | var ccPrev = cc.Position > 0 ? (cc - 1) : cc; 89 | if (this.ShouldTag(ccPrev)) { 90 | char cPrev = ccPrev.GetChar(); 91 | var brace2 = braces.FirstOrDefault(x => x.Item2 == cPrev); 92 | if (brace2 != null) { 93 | var match = this.FindMatchBackwards(ss, brace2.Item2, brace2.Item1, Math.Max(0, cc.Position - 1)); 94 | if (match != null) { 95 | var tag = new TextMarkerTag("blue"); 96 | yield return new TagSpan(new SnapshotSpan(ccPrev, 1), tag); 97 | yield return new TagSpan(new SnapshotSpan(ss, match.Value, 1), tag); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | private int? FindMatchForwards(ITextSnapshot ss, char cInc, char cDec, int startPos) { 105 | var mlsByPosition = this.overview.MultiLines 106 | .Select(x => new {x.Span.GetStartPoint(ss).Position, x.Span.GetSpan(ss).Length}); 107 | int lineNumber = ss.GetLineNumberFromPosition(startPos); 108 | var line = ss.GetLineFromLineNumber(lineNumber); 109 | var lineText = line.GetText(); 110 | int lineOfs = startPos - line.Start.Position; 111 | int foundPos = startPos; 112 | int depth = 0; 113 | 114 | for (; ; ) { 115 | var infos = Parser.Parse(lineText); 116 | var ignores = infos 117 | .Where(x => x.Token.In(Token.StringLiteral, Token.Comment)) 118 | .Select(x => new { Position = x.Start + line.Start.Position, x.Length }) 119 | .Concat(mlsByPosition) 120 | .Distinct(x => x.Position, (a, b) => a.Length > b.Length ? a : b) 121 | .ToDictionary(x => x.Position, x => x.Length); 122 | while (lineOfs < lineText.Length) { 123 | int skipLength = ignores.ValueOrDefault(foundPos); 124 | if (skipLength == 0) { 125 | char c = lineText[lineOfs]; 126 | if (c == cInc) { 127 | depth++; 128 | } else if (c == cDec) { 129 | depth--; 130 | if (depth == 0) { 131 | return foundPos; 132 | } 133 | } 134 | lineOfs++; 135 | foundPos++; 136 | } else { 137 | lineOfs += skipLength; 138 | foundPos += skipLength; 139 | } 140 | } 141 | if (foundPos >= ss.Length) { 142 | return null; 143 | } 144 | while (lineOfs >= line.Length) { 145 | lineOfs -= line.LengthIncludingLineBreak; 146 | lineNumber++; 147 | line = ss.GetLineFromLineNumber(lineNumber); 148 | lineText = line.GetText(); 149 | } 150 | if (lineOfs < 0) { 151 | foundPos -= lineOfs; 152 | lineOfs = 0; 153 | } 154 | if (foundPos >= ss.Length) { 155 | return null; 156 | } 157 | } 158 | 159 | } 160 | 161 | private int? FindMatchBackwards(ITextSnapshot ss, char cInc, char cDec, int startPos) { 162 | var mlsByPosition = this.overview.MultiLines 163 | .Select(x => new { x.Span.GetStartPoint(ss).Position, x.Span.GetSpan(ss).Length }); 164 | int lineNumber = ss.GetLineNumberFromPosition(startPos); 165 | var line = ss.GetLineFromLineNumber(lineNumber); 166 | var lineText = line.GetText(); 167 | int lineOfs = startPos - line.Start.Position; 168 | int foundPos = startPos; 169 | int depth = 0; 170 | 171 | for (; ; ) { 172 | var infos = Parser.Parse(lineText); 173 | var ignores = infos 174 | .Where(x => x.Token == Token.StringLiteral) 175 | .Select(x => new { Position = x.Start + line.Start.Position, x.Length }) 176 | .Concat(mlsByPosition) 177 | .Distinct(x => x.Position, (a, b) => a.Length > b.Length ? a : b) 178 | .ToDictionary(x => x.Position + x.Length - 1, x => x.Length); 179 | while (lineOfs >= 0) { 180 | int skipLength = ignores.ValueOrDefault(foundPos); 181 | if (skipLength == 0) { 182 | char c = lineText[lineOfs]; 183 | if (c == cInc) { 184 | depth++; 185 | } else if (c == cDec) { 186 | depth--; 187 | if (depth == 0) { 188 | return foundPos; 189 | } 190 | } 191 | lineOfs--; 192 | foundPos--; 193 | } else { 194 | lineOfs -= skipLength; 195 | foundPos -= skipLength; 196 | } 197 | } 198 | if (foundPos < 0) { 199 | return null; 200 | } 201 | while (lineOfs < 0) { 202 | lineNumber--; 203 | line = ss.GetLineFromLineNumber(lineNumber); 204 | lineText = line.GetText(); 205 | lineOfs += line.LengthIncludingLineBreak; 206 | } 207 | if (lineOfs >= line.Length) { 208 | foundPos -= lineOfs - line.Length + 1; 209 | lineOfs = line.Length - 1; 210 | } 211 | if (foundPos < 0) { 212 | return null; 213 | } 214 | } 215 | 216 | } 217 | 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /CoffeeLite/TaggerBracesProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.VisualStudio.Text.Tagging; 6 | using Microsoft.VisualStudio.Text.Editor; 7 | using Microsoft.VisualStudio.Text; 8 | using System.ComponentModel.Composition; 9 | using Microsoft.VisualStudio.Utilities; 10 | 11 | namespace CoffeeSyntax { 12 | 13 | [Export(typeof(IViewTaggerProvider))] 14 | [ContentType("coffee")] 15 | [TagType(typeof(TextMarkerTag))] 16 | class TaggerBracesProvider : IViewTaggerProvider { 17 | 18 | public ITagger CreateTagger(ITextView textView, ITextBuffer textBuffer) where T : ITag { 19 | if (textView == null) { 20 | return null; 21 | } 22 | if (textView.TextBuffer != textBuffer) { 23 | return null; 24 | } 25 | 26 | var overview = textBuffer.Properties.GetOrCreateSingletonProperty(() => new CoffeeOverview(textBuffer)); 27 | return new TaggerBraces(overview, textView, textBuffer) as ITagger; 28 | } 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /CoffeeLite/VisualFormatNames.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace CoffeeSyntax { 7 | static class VisualFormatNames { 8 | 9 | public const string Coffee = "coffee"; 10 | public const string CoffeeString = "coffee.string"; 11 | public const string CoffeeIdentifier = "coffee.identifier"; 12 | public const string CoffeeKeyword = "coffee.keyword"; 13 | public const string CoffeeNumericLiteral = "coffee.numericliteral"; 14 | public const string CoffeeComment = "coffee.comment"; 15 | public const string CoffeeThis = "coffee.this"; 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CoffeeLite/VisualFormats.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.VisualStudio.Text.Classification; 6 | using System.Windows.Media; 7 | using System.ComponentModel.Composition; 8 | using Microsoft.VisualStudio.Utilities; 9 | 10 | namespace CoffeeSyntax { 11 | static class VisualFormats { 12 | 13 | [Export(typeof(EditorFormatDefinition))] 14 | [ClassificationType(ClassificationTypeNames = VisualFormatNames.CoffeeString)] 15 | [Name(VisualFormatNames.CoffeeString)] 16 | [UserVisible(true)] 17 | [DisplayName("Coffee string")] 18 | [Order(Before = Priority.Default)] 19 | sealed class StringFormat : ClassificationFormatDefinition { 20 | public StringFormat() { 21 | this.ForegroundColor = Colors.DarkRed; 22 | } 23 | } 24 | 25 | [Export(typeof(EditorFormatDefinition))] 26 | [ClassificationType(ClassificationTypeNames = VisualFormatNames.CoffeeIdentifier)] 27 | [Name(VisualFormatNames.CoffeeIdentifier)] 28 | [UserVisible(true)] 29 | [DisplayName("Coffee identifier")] 30 | [Order(Before = Priority.Default)] 31 | sealed class IdentifierFormat : ClassificationFormatDefinition { 32 | public IdentifierFormat() { 33 | this.ForegroundColor = Colors.Black; 34 | } 35 | } 36 | 37 | [Export(typeof(EditorFormatDefinition))] 38 | [ClassificationType(ClassificationTypeNames = VisualFormatNames.CoffeeKeyword)] 39 | [Name(VisualFormatNames.CoffeeKeyword)] 40 | [UserVisible(true)] 41 | [DisplayName("Coffee keyword")] 42 | [Order(Before = Priority.Default)] 43 | sealed class KeywordFormat : ClassificationFormatDefinition { 44 | public KeywordFormat() { 45 | this.ForegroundColor = Colors.Blue; 46 | } 47 | } 48 | 49 | [Export(typeof(EditorFormatDefinition))] 50 | [ClassificationType(ClassificationTypeNames = VisualFormatNames.CoffeeNumericLiteral)] 51 | [Name(VisualFormatNames.CoffeeNumericLiteral)] 52 | [UserVisible(true)] 53 | [DisplayName("Coffee numeric literal")] 54 | [Order(Before = Priority.Default)] 55 | sealed class NumericLiteralFormat : ClassificationFormatDefinition { 56 | public NumericLiteralFormat() { 57 | this.ForegroundColor = Colors.Black; 58 | } 59 | } 60 | 61 | [Export(typeof(EditorFormatDefinition))] 62 | [ClassificationType(ClassificationTypeNames = VisualFormatNames.CoffeeComment)] 63 | [Name(VisualFormatNames.CoffeeComment)] 64 | [UserVisible(true)] 65 | [DisplayName("Coffee comment")] 66 | [Order(Before = Priority.Default)] 67 | sealed class CommentFormat : ClassificationFormatDefinition { 68 | public CommentFormat() { 69 | this.ForegroundColor = Colors.Green; 70 | } 71 | } 72 | 73 | [Export(typeof(EditorFormatDefinition))] 74 | [ClassificationType(ClassificationTypeNames = VisualFormatNames.CoffeeThis)] 75 | [Name(VisualFormatNames.CoffeeThis)] 76 | [UserVisible(true)] 77 | [DisplayName("Coffee this")] 78 | [Order(Before = Priority.Default)] 79 | sealed class CommentThis : ClassificationFormatDefinition { 80 | public CommentThis() { 81 | this.ForegroundColor = Colors.Orange; 82 | } 83 | } 84 | 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /CoffeeLite/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | CoffeeLite 5 | Chris Bacon 6 | 0.1.4 7 | Syntax highlighting for CoffeeScript 8 | 1033 9 | https://github.com/chrisdunelm/CoffeeLite 10 | MitLicense.txt 11 | 12 | 13 | Ultimate 14 | Premium 15 | Pro 16 | IntegratedShell 17 | 18 | 19 | 20 | 21 | 22 | 23 | |%CurrentProject%| 24 | 25 | 26 | -------------------------------------------------------------------------------- /CoffeeParser/CoffeeParser.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB} 9 | Library 10 | Properties 11 | CoffeeParser 12 | CoffeeParser 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 57 | -------------------------------------------------------------------------------- /CoffeeParser/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace CoffeeParser { 7 | static class Extensions { 8 | 9 | public static bool In(this T item, params T[] isIn) { 10 | return isIn.Contains(item); 11 | } 12 | 13 | public static IEnumerable Concat(this IEnumerable a, T b) { 14 | return a.Concat(new[] { b }); 15 | } 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CoffeeParser/Parser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace CoffeeParser { 7 | 8 | using ParseResult = Tuple; 9 | 10 | public static class Parser { 11 | 12 | private static char[] whitespace = { ' ', '\r', '\n', '\t' }; 13 | 14 | private static string[] keywords = { 15 | "and", 16 | "break", 17 | "by", 18 | "catch", 19 | "class", 20 | "continue", 21 | "debugger", 22 | "delete", 23 | "do", 24 | "else", 25 | "extends", 26 | "false", 27 | "finally", 28 | "for", 29 | "if", 30 | "in", 31 | "instanceof", 32 | "is", 33 | "isnt", 34 | "loop", 35 | "new", 36 | "no", 37 | "not", 38 | "null", 39 | "of", 40 | "off", 41 | "on", 42 | "or", 43 | "return", 44 | "super", 45 | "switch", 46 | "then", 47 | "this", 48 | "throw", 49 | "true", 50 | "try", 51 | "typeof", 52 | "undefined", 53 | "unless", 54 | "until", 55 | "when", 56 | "while", 57 | "yes", 58 | }; 59 | 60 | private static Func[] p = 61 | { 62 | ParseComment, 63 | ParseAt, 64 | ParseKeyword, 65 | ParseNumericLiteral, 66 | ParseIdentifier, 67 | ParseStringLiteral, 68 | }; 69 | 70 | private static ParseResult ParseComment(string s) { 71 | if (s[0] == '#') { 72 | return new ParseResult(s.Length, Token.Comment, s); 73 | } 74 | return null; 75 | } 76 | 77 | private static ParseResult ParseAt(string s) { 78 | if (s[0] == '@') { 79 | return new ParseResult(1, Token.This, "@"); 80 | } 81 | return null; 82 | } 83 | 84 | private static ParseResult ParseNumericLiteral(string s) { 85 | if (!char.IsDigit(s[0])) { 86 | return null; 87 | } 88 | int length = s 89 | .TakeWhile(c => char.IsNumber(c) || c == '.' || c == 'e' || c == 'E') 90 | .Count(); 91 | return new ParseResult(length, Token.NumericLiteral, s.Substring(0, length)); 92 | } 93 | 94 | private static ParseResult ParseIdentifier(string s) { 95 | if (!(char.IsLetter(s[0]) || s[0] == '_')) { 96 | return null; 97 | } 98 | int length = s 99 | .TakeWhile(c => char.IsLetterOrDigit(c) || c == '_') 100 | .Count(); 101 | return new ParseResult(length, Token.Identifier, s.Substring(0, length)); 102 | } 103 | 104 | //private static bool PredicateKeyword(string s) { 105 | // return keywords 106 | // .Any(x => s.Substring(0, Math.Min(x.Length,s.Length)) == x && (s.Length <= x.Length || whitespace.Contains(s[x.Length]))); 107 | //} 108 | 109 | private static ParseResult ParseKeyword(string s) { 110 | var keyword = keywords 111 | .FirstOrDefault(x => s.Substring(0, Math.Min(x.Length, s.Length)) == x && 112 | (s.Length <= x.Length || whitespace.Contains(s[x.Length]) || s[x.Length].In('.', ',' , '[', '(', ';', ')', ']'))); 113 | if (keyword == null) { 114 | return null; 115 | } 116 | return new ParseResult(keyword.Length, Token.Keyword, keyword); 117 | } 118 | 119 | private static ParseResult ParseStringLiteral(string s) { 120 | if (s[0] == '"' || s[0] == '\'') { 121 | bool inEscape = false; 122 | int length = 2 + s.Skip(1).TakeWhile(c => { 123 | if (c == '\\') { 124 | inEscape = true; 125 | } else { 126 | if (inEscape) { 127 | inEscape = false; 128 | } else { 129 | return c != s[0]; 130 | } 131 | } 132 | return true; 133 | }).Count(); 134 | if (length > s.Length) { 135 | // Cope with unterminated strings 136 | length = s.Length; 137 | } 138 | return new ParseResult(length, Token.StringLiteral, s.Substring(length)); 139 | } 140 | return null; 141 | } 142 | 143 | public static IEnumerable Parse(string coffee) { 144 | var ret = new List(); 145 | int ofs = 0; 146 | string s = coffee; 147 | while (s.Length > 0) { 148 | var r = p.Select(x => x(s)).Where(x => x != null).FirstOrDefault(); 149 | if (r != null) { 150 | ret.Add(new TokenInfo(ofs, r.Item1, r.Item2, r.Item3, null)); 151 | s = s.Substring(r.Item1); 152 | ofs += r.Item1; 153 | } else { 154 | s = s.Substring(1); 155 | ofs++; 156 | } 157 | } 158 | return PostProcess(ret); 159 | } 160 | 161 | private static IEnumerable PostProcess(IEnumerable tokens) { 162 | TokenInfo prevToken = null; 163 | foreach (var token in tokens.Concat(new TokenInfo(-1, -1, Token.None, null, null))) { 164 | if (prevToken != null) { 165 | if (prevToken.Token == Token.This && token.Token == Token.Identifier) { 166 | yield return new TokenInfo(prevToken.Start, prevToken.Length + token.Length, Token.This, prevToken.Value+token.Value, null); 167 | } else if (prevToken.Token == Token.Keyword && prevToken.Value == "this") { 168 | yield return new TokenInfo(prevToken.Start, prevToken.Length, Token.This, prevToken.Value, null); 169 | } else { 170 | if (prevToken.Token != Token.None) { 171 | yield return prevToken; 172 | } 173 | } 174 | } 175 | prevToken = token; 176 | } 177 | } 178 | 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /CoffeeParser/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CoffeeParser")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("EnviroMotive")] 12 | [assembly: AssemblyProduct("CoffeeParser")] 13 | [assembly: AssemblyCopyright("Copyright © EnviroMotive 2011")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d4b5c7e9-89af-4c59-8c15-52afda30c9c2")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /CoffeeParser/Token.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace CoffeeParser { 7 | public enum Token { 8 | 9 | None = 0, 10 | 11 | StringLiteral, 12 | NumericLiteral, 13 | Keyword, 14 | Identifier, 15 | Comment, 16 | This, 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CoffeeParser/TokenInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace CoffeeParser { 7 | public class TokenInfo { 8 | 9 | internal TokenInfo(int start, int length, Token token, string value, IEnumerable innerTokens) { 10 | this.Start = start; 11 | this.Length = length; 12 | this.Token = token; 13 | this.Value = value; 14 | this.InnerTokens = innerTokens ?? Enumerable.Empty(); 15 | } 16 | 17 | public int Start { get; private set; } 18 | public int Length { get; private set; } 19 | public Token Token { get; private set; } 20 | public string Value { get; private set; } 21 | public IEnumerable InnerTokens { get; private set; } 22 | 23 | public override string ToString() { 24 | return string.Format("{{ {0}: {1}[{2}] '{3}' }}", this.Token, this.Start, this.Length, this.Value); 25 | } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CoffeeParserTest/CoffeeParserTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {BBF8E343-B34C-4227-A65D-CCA4CDCEAA1E} 9 | Exe 10 | Properties 11 | CoffeeParserTest 12 | CoffeeParserTest 13 | v4.0 14 | Client 15 | 512 16 | 17 | 18 | x86 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | x86 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\Libs\nunit.framework.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | {A0BF6E7E-76FA-4315-A9A2-0F8FDD48D0EB} 57 | CoffeeParser 58 | 59 | 60 | 61 | 68 | -------------------------------------------------------------------------------- /CoffeeParserTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using CoffeeParser; 6 | 7 | namespace CoffeeParserTest { 8 | class Program { 9 | static void Main(string[] args) { 10 | 11 | var t = Parser.Parse("i = 0").ToArray(); 12 | 13 | Console.WriteLine("*** DONE ***"); 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CoffeeParserTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CoffeeParserTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("EnviroMotive")] 12 | [assembly: AssemblyProduct("CoffeeParserTest")] 13 | [assembly: AssemblyCopyright("Copyright © EnviroMotive 2011")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("dc072aca-acbe-4114-b606-4f2d154f5b6c")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /CoffeeParserTest/TestAssignment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using NUnit.Framework; 6 | using CoffeeParser; 7 | 8 | namespace CoffeeParserTest { 9 | 10 | [TestFixture] 11 | public class TestAssignment { 12 | 13 | [Test] 14 | public void A1() { 15 | var t = Parser.Parse("i = 0").ForTest(); 16 | var e = new[] { 17 | Utils.TI2(0, 1, Token.Identifier), 18 | Utils.TI2(4, 1, Token.NumericLiteral), 19 | }; 20 | Assert.That(t, NUnit.Framework. Is.EquivalentTo(e)); 21 | } 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /CoffeeParserTest/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using CoffeeParser; 6 | 7 | namespace CoffeeParserTest { 8 | 9 | struct TokenInfo2 { 10 | public int Start, Length; 11 | public Token Token; 12 | public override string ToString() { 13 | return string.Format("{0}:{1}[{2}]", this.Token, this.Start, this.Length); 14 | } 15 | } 16 | 17 | static class Utils { 18 | 19 | public static TokenInfo2 TI2(int start, int length, Token token) { 20 | return new TokenInfo2 { 21 | Start = start, 22 | Length = length, 23 | Token = token, 24 | }; 25 | } 26 | 27 | public static IEnumerable ForTest(this IEnumerable tis) { 28 | return tis.Select(x => new TokenInfo2 { 29 | Start = x.Start, 30 | Length = x.Length, 31 | Token = x.Token, 32 | }).ToArray(); 33 | } 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Libs/nunit.framework.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisdunelm/CoffeeLite/48a045993b2519be024317b8cb95882bc9f8fc24/Libs/nunit.framework.dll -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | CoffeeLite 2 | ========== 3 | 4 | A Visual Studio 2010 extension that provides CoffeeScript syntax highlighting. 5 | 6 | Features 7 | -------- 8 | * Highlighting of keywords, comments, string literals and numeric literals. 9 | * Matching brace highlighting. 10 | 11 | Installation 12 | ------------ 13 | Make sure you have Visual Studio 2010 installed. 14 | 15 | _Either:_ 16 | Download the binary _.vsix_ file. When double-clicked this will install the extension into 17 | Visual Studio. 18 | 19 | _Or:_ 20 | Get the source, open the solution in Visual Studio, build it. This generates the required 21 | _.vsix_ file. 22 | 23 | This extension can be managed using the _"Tools/Extension manager"_ menu option. --------------------------------------------------------------------------------