diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..412eeda78
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,22 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+# Custom for Visual Studio
+*.cs diff=csharp
+*.sln merge=union
+*.csproj merge=union
+*.vbproj merge=union
+*.fsproj merge=union
+*.dbproj merge=union
+
+# Standard to msysgit
+*.doc diff=astextplain
+*.DOC diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot diff=astextplain
+*.DOT diff=astextplain
+*.pdf diff=astextplain
+*.PDF diff=astextplain
+*.rtf diff=astextplain
+*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..b9d6bd92f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,215 @@
+#################
+## Eclipse
+#################
+
+*.pydevproject
+.project
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.classpath
+.settings/
+.loadpath
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# CDT-specific
+.cproject
+
+# PDT-specific
+.buildpath
+
+
+#################
+## Visual Studio
+#################
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+
+[Dd]ebug/
+[Rr]elease/
+x64/
+build/
+[Bb]in/
+[Oo]bj/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+*_i.c
+*_p.c
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.log
+*.scc
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+*.ncrunch*
+.*crunch*.local.xml
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.Publish.xml
+*.pubxml
+
+# NuGet Packages Directory
+## TODO: If you have NuGet Package Restore enabled, uncomment the next line
+#packages/
+
+# Windows Azure Build Output
+csx
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+sql/
+*.Cache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.[Pp]ublish.xml
+*.pfx
+*.publishsettings
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file to a newer
+# Visual Studio version. Backup files are not needed, because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+App_Data/*.mdf
+App_Data/*.ldf
+
+#############
+## Windows detritus
+#############
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Mac crap
+.DS_Store
+
+
+#############
+## Python
+#############
+
+*.py[co]
+
+# Packages
+*.egg
+*.egg-info
+dist/
+build/
+eggs/
+parts/
+var/
+sdist/
+develop-eggs/
+.installed.cfg
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+
+#Translations
+*.mo
+
+#Mr Developer
+.mr.developer.cfg
diff --git a/CssSorter.dll b/CssSorter.dll
new file mode 100644
index 000000000..fb8ae8306
Binary files /dev/null and b/CssSorter.dll differ
diff --git a/EditorExtensions/Adornments/Color/ColorAdornment.cs b/EditorExtensions/Adornments/Color/ColorAdornment.cs
new file mode 100644
index 000000000..406924b46
--- /dev/null
+++ b/EditorExtensions/Adornments/Color/ColorAdornment.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using MadsKristensen.EditorExtensions;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace IntraTextAdornmentSample
+{
+ internal sealed class ColorAdornment : Border
+ {
+ internal ColorAdornment(ColorTag colorTag, IWpfTextView view)
+ {
+ this.Padding = new Thickness(0);
+ this.BorderThickness = new Thickness(1);
+ this.Margin = new Thickness(0, 0, 2, 3);
+ this.Width = OptionHelpers.FontSize;
+ this.Height = this.Width;
+ this.Cursor = System.Windows.Input.Cursors.Arrow;
+ this.MouseUp += delegate { ColorAdornmentMouseUp(view); };
+
+ Update(colorTag);
+ }
+
+ private static void ColorAdornmentMouseUp(IWpfTextView view)
+ {
+ try
+ {
+ CssCompletionController.FromView(view).OnShowMemberList(filterList: true);
+ }
+ catch
+ { }
+ }
+
+ internal void Update(ColorTag colorTag)
+ {
+ this.Background = new SolidColorBrush(colorTag.Color);
+ if (!HasContrastToBackground(colorTag.Color))
+ {
+ this.BorderThickness = new Thickness(1);
+ this.BorderBrush = _borderColor;
+ }
+ else
+ {
+ this.BorderThickness = new Thickness(0);
+ this.BorderBrush = this.Background;
+ }
+ }
+
+ private static SolidColorBrush _borderColor = OptionHelpers.BackgroundColor.Invert().ToBrush();
+
+ private static bool HasContrastToBackground(Color color)
+ {
+ // The color is very transparent (alpha channel)
+ if (color.A < 13)
+ {
+ return false;
+ }
+
+ var b = OptionHelpers.BackgroundColor;
+ double bBrightness = b.Red * 299 + b.Green * 587 + b.Blue * 114;
+ double cBrightness = color.R * 299 + color.G * 587 + color.B * 114;
+ double distance = Math.Abs(cBrightness - bBrightness) / 1000;
+
+ return distance > 20;
+ }
+ }
+}
diff --git a/EditorExtensions/Adornments/Color/ColorAdornmentTagger.cs b/EditorExtensions/Adornments/Color/ColorAdornmentTagger.cs
new file mode 100644
index 000000000..fd8c13c67
--- /dev/null
+++ b/EditorExtensions/Adornments/Color/ColorAdornmentTagger.cs
@@ -0,0 +1,97 @@
+//***************************************************************************
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// This code is licensed under the Visual Studio SDK license terms.
+// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
+// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
+// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
+// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
+//
+//***************************************************************************
+
+// This controls whether the adornments are positioned next to the hex values or instead of them.
+#define HIDING_TEXT
+
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Tagging;
+
+namespace IntraTextAdornmentSample
+{
+ ///
+ /// Provides color swatch adornments in place of color constants.
+ ///
+ ///
+ ///
+ /// This is a sample usage of the utility class.
+ ///
+ ///
+ internal sealed class ColorAdornmentTagger : IntraTextAdornmentTagger
+ {
+ internal static ITagger GetTagger(IWpfTextView view, Lazy> colorTagger)
+ {
+ return view.Properties.GetOrCreateSingletonProperty(
+ () => new ColorAdornmentTagger(view, colorTagger.Value));
+ }
+
+ private ITagAggregator colorTagger;
+
+ private ColorAdornmentTagger(IWpfTextView view, ITagAggregator colorTagger)
+ : base(view)
+ {
+ this.colorTagger = colorTagger;
+ }
+
+ public void Dispose()
+ {
+ this.colorTagger.Dispose();
+
+ base.view.Properties.RemoveProperty(typeof(ColorAdornmentTagger));
+ }
+
+ // To produce adornments that don't obscure the text, the adornment tags
+ // should have zero length spans. Overriding this method allows control
+ // over the tag spans.
+ protected override IEnumerable> GetAdornmentData(NormalizedSnapshotSpanCollection spans)
+ {
+ if (spans.Count == 0)
+ yield break;
+
+ ITextSnapshot snapshot = spans[0].Snapshot;
+
+ var colorTags = this.colorTagger.GetTags(spans);
+
+ foreach (IMappingTagSpan dataTagSpan in colorTags)
+ {
+ NormalizedSnapshotSpanCollection colorTagSpans = dataTagSpan.Span.GetSpans(snapshot);
+
+ // Ignore data tags that are split by projection.
+ // This is theoretically possible but unlikely in current scenarios.
+ if (colorTagSpans.Count != 1)
+ continue;
+
+ SnapshotSpan adornmentSpan = new SnapshotSpan(colorTagSpans[0].Start, 0);
+
+ yield return Tuple.Create(adornmentSpan, (PositionAffinity?)PositionAffinity.Successor, dataTagSpan.Tag);
+ }
+ }
+
+ protected override ColorAdornment CreateAdornment(ColorTag dataTag, SnapshotSpan span)
+ {
+ return new ColorAdornment(dataTag, view);
+ }
+
+ protected override bool UpdateAdornment(ColorAdornment adornment, ColorTag dataTag)
+ {
+ if (adornment != null)
+ {
+ adornment.Update(dataTag);
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/EditorExtensions/Adornments/Color/ColorAdornmentTaggerProvider.cs b/EditorExtensions/Adornments/Color/ColorAdornmentTaggerProvider.cs
new file mode 100644
index 000000000..c14470778
--- /dev/null
+++ b/EditorExtensions/Adornments/Color/ColorAdornmentTaggerProvider.cs
@@ -0,0 +1,50 @@
+//***************************************************************************
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// This code is licensed under the Visual Studio SDK license terms.
+// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
+// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
+// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
+// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
+//
+//***************************************************************************
+
+using System;
+using System.ComponentModel.Composition;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+
+namespace IntraTextAdornmentSample
+{
+ [Export(typeof(IViewTaggerProvider))]
+ [ContentType("css")]
+ [TagType(typeof(IntraTextAdornmentTag))]
+ internal sealed class ColorAdornmentTaggerProvider : IViewTaggerProvider
+ {
+ #pragma warning disable 649 // "field never assigned to" -- field is set by MEF.
+ [Import]
+ internal IBufferTagAggregatorFactoryService BufferTagAggregatorFactoryService;
+ #pragma warning restore 649
+
+ public ITagger CreateTagger(ITextView textView, ITextBuffer buffer) where T : ITag
+ {
+ if (textView == null)
+ throw new ArgumentNullException("textView");
+
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+
+ if (buffer != textView.TextBuffer)
+ return null;
+
+ return ColorAdornmentTagger.GetTagger(
+ (IWpfTextView)textView,
+ new Lazy>(
+ () => BufferTagAggregatorFactoryService.CreateTagAggregator(textView.TextBuffer)))
+ as ITagger;
+ }
+ }
+}
diff --git a/EditorExtensions/Adornments/Color/ColorTag.cs b/EditorExtensions/Adornments/Color/ColorTag.cs
new file mode 100644
index 000000000..e21cd83cc
--- /dev/null
+++ b/EditorExtensions/Adornments/Color/ColorTag.cs
@@ -0,0 +1,37 @@
+//***************************************************************************
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// This code is licensed under the Visual Studio SDK license terms.
+// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
+// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
+// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
+// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
+//
+//***************************************************************************
+
+using System.Windows.Media;
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Tagging;
+
+namespace IntraTextAdornmentSample
+{
+ ///
+ /// Data tag indicating that the tagged text represents a color.
+ ///
+ ///
+ /// Note that this tag has nothing directly to do with adornments or other UI.
+ /// This sample's adornments will be produced based on the data provided in these tags.
+ /// This separation provides the potential for other extensions to consume color tags
+ /// and provide alternative UI or other derived functionality over this data.
+ ///
+ internal class ColorTag : ITag
+ {
+ internal ColorTag(Color color)
+ {
+ this.Color = color;
+ }
+
+ internal readonly Color Color;
+ }
+}
diff --git a/EditorExtensions/Adornments/Color/ColorTagger.cs b/EditorExtensions/Adornments/Color/ColorTagger.cs
new file mode 100644
index 000000000..9bde2a3ca
--- /dev/null
+++ b/EditorExtensions/Adornments/Color/ColorTagger.cs
@@ -0,0 +1,139 @@
+using MadsKristensen.EditorExtensions;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.Less.Core;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.Web.Editor;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Windows.Threading;
+
+namespace IntraTextAdornmentSample
+{
+ internal sealed class ColorTagger : ITagger
+ {
+ private ITextBuffer _buffer;
+ private CssTree _tree;
+
+ internal ColorTagger(ITextBuffer buffer)
+ {
+ _buffer = buffer;
+ _buffer.ChangedLowPriority += BufferChanged;
+ }
+
+ public event EventHandler TagsChanged;
+
+ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans)
+ {
+ if (spans.Count == 0 || spans[0].Length == 0 || spans[0].Length >= _buffer.CurrentSnapshot.Length || !EnsureInitialized())
+ yield break;
+
+ IEnumerable items = GetColors(spans[0]);
+
+ foreach (var item in items.Where(i => (i.Start + i.Length) <= _buffer.CurrentSnapshot.Length))
+ {
+ SnapshotSpan span = new SnapshotSpan(_buffer.CurrentSnapshot, item.Start, item.Length);
+ ColorModel colorModel = ColorParser.TryParseColor(item, ColorParser.Options.AllowAlpha | ColorParser.Options.AllowNames);
+ if (colorModel != null)
+ {
+ yield return new TagSpan(span, new ColorTag(colorModel.Color));
+ }
+ }
+ }
+
+ private IEnumerable GetColors(SnapshotSpan span)
+ {
+ List items = new List();
+
+ // TODO: Refine this so it goes directly to the individual HexColorValue, FunctionColor and TokenItem
+ ParseItem complexItem = _tree.StyleSheet.ItemFromRange(span.Start, span.Length);
+ if (complexItem == null || (!(complexItem is AtDirective) && !(complexItem is RuleBlock) && !(complexItem is LessVariableDeclaration)))
+ return items;
+
+ IEnumerable declarations = new ParseItem[0];
+
+ var lessVar = complexItem as LessVariableDeclaration;
+
+ if (lessVar != null)
+ {
+ declarations = new List() { lessVar.Value };
+
+ }
+ else
+ {
+ var visitorRules = new CssItemCollector();
+ complexItem.Accept(visitorRules);
+
+ declarations = from d in visitorRules.Items
+ where d.Values.TextAfterEnd > span.Start && d.Values.TextStart < span.End && d.Length > 2
+ select d;
+ }
+
+ foreach (var declaration in declarations.Where(d => d != null))
+ {
+ var visitorHex = new CssItemCollector();
+ declaration.Accept(visitorHex);
+ items.AddRange(visitorHex.Items);
+
+ var visitorFunc = new CssItemCollector();
+ declaration.Accept(visitorFunc);
+ items.AddRange(visitorFunc.Items);
+
+ var visitorName = new CssItemCollector();
+ declaration.Accept(visitorName);
+ items.AddRange(visitorName.Items.Where(i => (i.PreviousSibling == null || (i.PreviousSibling.Text != "@" && i.PreviousSibling.Text != "$")) && i.TokenType == CssTokenType.Identifier && Color.FromName(i.Text).IsNamedColor));
+ }
+
+ return items;
+ }
+
+ public bool EnsureInitialized()
+ {
+ if (_tree == null && WebEditor.Host != null)
+ {
+ try
+ {
+ CssEditorDocument document = CssEditorDocument.FromTextBuffer(_buffer);
+ _tree = document.Tree;
+ }
+ catch (ArgumentNullException)
+ {
+ }
+ }
+
+ return _tree != null;
+ }
+
+ private void BufferChanged(object sender, TextContentChangedEventArgs e)
+ {
+ if (e.Changes.Count == 0)
+ return;
+
+ var temp = TagsChanged;
+ if (temp == null)
+ return;
+
+ // Combine all changes into a single span so that
+ // the ITagger<>.TagsChanged event can be raised just once for a compound edit
+ // with many parts.
+
+ ITextSnapshot snapshot = e.After;
+
+ int start = e.Changes[0].NewPosition;
+ int end = e.Changes[e.Changes.Count - 1].NewEnd;
+
+ SnapshotSpan totalAffectedSpan = new SnapshotSpan(
+ snapshot.GetLineFromPosition(start).Start,
+ snapshot.GetLineFromPosition(end).End);
+
+ //temp(this, new SnapshotSpanEventArgs(totalAffectedSpan));
+
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => temp(this, new SnapshotSpanEventArgs(totalAffectedSpan))), DispatcherPriority.ApplicationIdle);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Adornments/Color/ColorTaggerProvider.cs b/EditorExtensions/Adornments/Color/ColorTaggerProvider.cs
new file mode 100644
index 000000000..f1ab791fa
--- /dev/null
+++ b/EditorExtensions/Adornments/Color/ColorTaggerProvider.cs
@@ -0,0 +1,22 @@
+using System;
+using System.ComponentModel.Composition;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+
+namespace IntraTextAdornmentSample
+{
+ [Export(typeof(ITaggerProvider))]
+ [ContentType("css")]
+ [TagType(typeof(ColorTag))]
+ internal sealed class ColorTaggerProvider : ITaggerProvider
+ {
+ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag
+ {
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+
+ return buffer.Properties.GetOrCreateSingletonProperty(() => new ColorTagger(buffer)) as ITagger;
+ }
+ }
+}
diff --git a/EditorExtensions/Adornments/Color/IntraTextAdornmentTagger.cs b/EditorExtensions/Adornments/Color/IntraTextAdornmentTagger.cs
new file mode 100644
index 000000000..21e86c890
--- /dev/null
+++ b/EditorExtensions/Adornments/Color/IntraTextAdornmentTagger.cs
@@ -0,0 +1,268 @@
+//***************************************************************************
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// This code is licensed under the Visual Studio SDK license terms.
+// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
+// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
+// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
+// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
+//
+//***************************************************************************
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Tagging;
+
+namespace IntraTextAdornmentSample
+{
+ ///
+ /// Helper class for interspersing adornments into text.
+ ///
+ ///
+ /// To avoid an issue around intra-text adornment support and its interaction with text buffer changes,
+ /// this tagger reacts to text and color tag changes with a delay. It waits to send out its own TagsChanged
+ /// event until the WPF Dispatcher is running again and it takes care to report adornments
+ /// that are consistent with the latest sent TagsChanged event by storing that particular snapshot
+ /// and using it to query for the data tags.
+ ///
+ internal abstract class IntraTextAdornmentTagger
+ : ITagger
+ where TAdornment : UIElement
+ {
+ protected readonly IWpfTextView view;
+ private Dictionary adornmentCache = new Dictionary();
+ protected ITextSnapshot snapshot { get; private set; }
+ private readonly List invalidatedSpans = new List();
+ private bool _isProcessing;
+
+ protected IntraTextAdornmentTagger(IWpfTextView view)
+ {
+ this.view = view;
+ this.snapshot = view.TextBuffer.CurrentSnapshot;
+
+ //this.view.LayoutChanged += HandleLayoutChanged;
+ this.view.TextBuffer.Changed += HandleBufferChanged;
+ }
+
+ /// The span of text that this adornment will elide.
+ /// Adornment corresponding to given data. May be null.
+ protected abstract TAdornment CreateAdornment(TData data, SnapshotSpan span);
+
+ /// True if the adornment was updated and should be kept. False to have the adornment removed from the view.
+ protected abstract bool UpdateAdornment(TAdornment adornment, TData data);
+
+ /// Spans to provide adornment data for. These spans do not necessarily correspond to text lines.
+ ///
+ /// If adornments need to be updated, call or .
+ /// This will, indirectly, cause to be called.
+ ///
+ ///
+ /// A sequence of:
+ /// * adornment data for each adornment to be displayed
+ /// * the span of text that should be elided for that adornment (zero length spans are acceptable)
+ /// * and affinity of the adornment (this should be null if and only if the elided span has a length greater than zero)
+ ///
+ protected abstract IEnumerable> GetAdornmentData(NormalizedSnapshotSpanCollection spans);
+
+ private void HandleBufferChanged(object sender, TextContentChangedEventArgs args)
+ {
+ if (!_isProcessing)
+ {
+ _isProcessing = true;
+ var editedSpans = args.Changes.Select(change => new SnapshotSpan(args.After, change.NewSpan)).ToList();
+ InvalidateSpans(editedSpans);
+ }
+
+ _isProcessing = false;
+ }
+
+ ///
+ /// Causes intra-text adornments to be updated asynchronously.
+ ///
+ protected void InvalidateSpans(IList spans)
+ {
+ lock (this.invalidatedSpans)
+ {
+ bool wasEmpty = this.invalidatedSpans.Count == 0;
+ this.invalidatedSpans.AddRange(spans);
+
+ if (wasEmpty && this.invalidatedSpans.Count > 0)
+ this.view.VisualElement.Dispatcher.BeginInvoke(new Action(AsyncUpdate));
+ }
+ }
+
+ private void AsyncUpdate()
+ {
+ // Store the snapshot that we're now current with and send an event
+ // for the text that has changed.
+ if (this.snapshot != this.view.TextBuffer.CurrentSnapshot)
+ {
+ this.snapshot = this.view.TextBuffer.CurrentSnapshot;
+
+ Dictionary translatedAdornmentCache = new Dictionary();
+
+ foreach (var keyValuePair in this.adornmentCache)
+ {
+ var key = keyValuePair.Key.TranslateTo(this.snapshot, SpanTrackingMode.EdgeExclusive);
+ if (!translatedAdornmentCache.ContainsKey(key))
+ translatedAdornmentCache.Add(key, keyValuePair.Value);
+ }
+
+ this.adornmentCache = translatedAdornmentCache;
+ }
+
+ List translatedSpans;
+ lock (this.invalidatedSpans)
+ {
+ translatedSpans = this.invalidatedSpans.Select(s => s.TranslateTo(this.snapshot, SpanTrackingMode.EdgeInclusive)).ToList();
+ this.invalidatedSpans.Clear();
+ }
+
+ if (translatedSpans.Count == 0)
+ return;
+
+ var start = translatedSpans.Select(span => span.Start).Min();
+ var end = translatedSpans.Select(span => span.End).Max();
+
+ RaiseTagsChanged(new SnapshotSpan(start, end));
+ }
+
+ ///
+ /// Causes intra-text adornments to be updated synchronously.
+ ///
+ protected void RaiseTagsChanged(SnapshotSpan span)
+ {
+ var handler = this.TagsChanged;
+ if (handler != null)
+ handler(this, new SnapshotSpanEventArgs(span));
+ }
+
+ private void HandleLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
+ {
+ if (!_isProcessing)
+ {
+ _isProcessing = true;
+ SnapshotSpan visibleSpan = this.view.TextViewLines.FormattedSpan;
+
+ // Filter out the adornments that are no longer visible.
+ HashSet toRemove = new HashSet(
+ from keyValuePair
+ in this.adornmentCache
+ where !keyValuePair.Key.TranslateTo(visibleSpan.Snapshot, SpanTrackingMode.EdgeExclusive).IntersectsWith(visibleSpan)
+ select keyValuePair.Key);
+
+ foreach (var span in toRemove)
+ this.adornmentCache.Remove(span);
+ }
+
+ _isProcessing = false;
+ }
+
+
+ // Produces tags on the snapshot that the tag consumer asked for.
+ public virtual IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans)
+ {
+ if (spans == null || spans.Count == 0)
+ yield break;
+
+ // Translate the request to the snapshot that this tagger is current with.
+
+ ITextSnapshot requestedSnapshot = spans[0].Snapshot;
+
+ var translatedSpans = new NormalizedSnapshotSpanCollection(spans.Select(span => span.TranslateTo(this.snapshot, SpanTrackingMode.EdgeExclusive)));
+
+ // Grab the adornments.
+ var tags = GetAdornmentTagsOnSnapshot(translatedSpans).GetEnumerator();
+ while (tags.MoveNext())
+ {
+ var current = tags.Current;
+ SnapshotSpan span = current.Span.TranslateTo(requestedSnapshot, SpanTrackingMode.EdgeExclusive);
+
+ IntraTextAdornmentTag tag = new IntraTextAdornmentTag(current.Tag.Adornment, current.Tag.RemovalCallback, current.Tag.Affinity);
+ yield return new TagSpan(span, tag);
+ }
+ }
+
+ // Produces tags on the snapshot that this tagger is current with.
+ private IEnumerable> GetAdornmentTagsOnSnapshot(NormalizedSnapshotSpanCollection spans)
+ {
+ if (spans.Count == 0)
+ yield break;
+
+ ITextSnapshot snapshot = spans[0].Snapshot;
+
+ // Since WPF UI objects have state (like mouse hover or animation) and are relatively expensive to create and lay out,
+ // this code tries to reuse controls as much as possible.
+ // The controls are stored in this.adornmentCache between the calls.
+
+ // 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 toRemove = new HashSet();
+ foreach (var ar in this.adornmentCache)
+ if (spans.IntersectsWith(new NormalizedSnapshotSpanCollection(ar.Key)))
+ toRemove.Add(ar.Key);
+
+ foreach (var spanDataPair in GetAdornmentData(spans).Distinct(new Comparer()))
+ {
+ // 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;
+ if (this.adornmentCache.TryGetValue(snapshotSpan, out adornment))
+ {
+ if (UpdateAdornment(adornment, adornmentData))
+ toRemove.Remove(snapshotSpan);
+ }
+ else
+ {
+ adornment = CreateAdornment(adornmentData, snapshotSpan);
+
+ if (adornment == null)
+ continue;
+
+ // Get the adornment to measure itself. Its DesiredSize property is used to determine
+ // how much space to leave between text for this adornment.
+ // Note: If the size of the adornment changes, the line will be reformatted to accommodate it.
+ // Note: Some adornments may change size when added to the view's visual tree due to inherited
+ // dependency properties that affect layout. Such options can include SnapsToDevicePixels,
+ // UseLayoutRounding, TextRenderingMode, TextHintingMode, and TextFormattingMode. Making sure
+ // that these properties on the adornment match the view's values before calling Measure here
+ // can help avoid the size change and the resulting unnecessary re-format.
+ adornment.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
+
+ this.adornmentCache.Add(snapshotSpan, adornment);
+ }
+
+ yield return new TagSpan(snapshotSpan, new IntraTextAdornmentTag(adornment, null, affinity));
+ }
+
+ foreach (var snapshotSpan in toRemove)
+ this.adornmentCache.Remove(snapshotSpan);
+ }
+
+ public event EventHandler TagsChanged;
+
+ private class Comparer : IEqualityComparer>
+ {
+ public bool Equals(Tuple x, Tuple y)
+ {
+ if (x == null && y == null)
+ return true;
+ if (x == null || y == null)
+ return false;
+ return x.Item1.Equals(y.Item1);
+ }
+
+ public int GetHashCode(Tuple obj)
+ {
+ return obj.Item1.GetHashCode();
+ }
+ }
+
+ }
+}
diff --git a/EditorExtensions/Classifications/Base64TaggerProvider.cs b/EditorExtensions/Classifications/Base64TaggerProvider.cs
new file mode 100644
index 000000000..c848726a5
--- /dev/null
+++ b/EditorExtensions/Classifications/Base64TaggerProvider.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+using System.Text.RegularExpressions;
+using MadsKristensen.EditorExtensions.QuickInfo;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.Web.Editor;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ITaggerProvider))]
+ [TagType(typeof(IOutliningRegionTag))]
+ [ContentType("CSS")]
+ internal sealed class Base64TaggerProvider : ITaggerProvider
+ {
+ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag
+ {
+ return buffer.Properties.GetOrCreateSingletonProperty(() => new Base64Tagger(buffer)) as ITagger;
+ //return new Base64Tagger(buffer) as ITagger;
+ }
+ }
+
+ internal sealed class Base64Tagger : ITagger
+ {
+ private ITextBuffer buffer;
+ private ITextSnapshot snapshot;
+ private string text;
+ private CssTree _tree;
+
+ public Base64Tagger(ITextBuffer buffer)
+ {
+ this.buffer = buffer;
+ this.snapshot = buffer.CurrentSnapshot;
+ this.text = snapshot.GetText();
+ this.buffer.Changed += BufferChanged;
+ }
+
+ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans)
+ {
+ if (spans.Count == 0 || !EnsureInitialized())
+ yield break;
+
+ var visitor = new CssItemCollector();
+ _tree.StyleSheet.Accept(visitor);
+
+ foreach (UrlItem url in visitor.Items.Where(u => u.UrlString != null && u.Start >= spans[0].Start))
+ {
+ if (url.UrlString.Text.IndexOf("base64,") > -1 && buffer.CurrentSnapshot.Length >= url.UrlString.AfterEnd)
+ {
+ var image = ImageQuickInfo.CreateImage(url.UrlString.Text.Trim('"', '\''));
+ var span = new SnapshotSpan(new SnapshotPoint(buffer.CurrentSnapshot, url.UrlString.Start), url.UrlString.Length);
+ var tag = new OutliningRegionTag(true, true, url.UrlString.Length + " characters", image);
+ yield return new TagSpan(span, tag);
+ }
+ }
+ }
+
+ public bool EnsureInitialized()
+ {
+ if (_tree == null)
+ {
+ try
+ {
+ CssEditorDocument document = CssEditorDocument.FromTextBuffer(buffer);
+ _tree = document.Tree;
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ return _tree != null;
+ }
+
+ public event EventHandler TagsChanged;
+
+ void BufferChanged(object sender, TextContentChangedEventArgs e)
+ {
+ // If this isn't the most up-to-date version of the buffer, then ignore it for now (we'll eventually get another change event).
+ if (e.After != buffer.CurrentSnapshot)
+ return;
+ //this.ReParse();
+ }
+ }
+}
diff --git a/EditorExtensions/Classifications/BraceMatching.cs b/EditorExtensions/Classifications/BraceMatching.cs
new file mode 100644
index 000000000..cec029711
--- /dev/null
+++ b/EditorExtensions/Classifications/BraceMatching.cs
@@ -0,0 +1,242 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+
+namespace MadsKristensen.WebEssentials.Css.BraceMatching
+{
+ [Export(typeof(IViewTaggerProvider))]
+ [ContentType("CSS")]
+ [ContentType("LESS")]
+ [TagType(typeof(TextMarkerTag))]
+ internal class BraceMatchingTaggerProvider : IViewTaggerProvider
+ {
+ public ITagger CreateTagger(ITextView textView, ITextBuffer buffer) where T : ITag
+ {
+ if (textView == null)
+ return null;
+
+ //provide highlighting only on the top-level buffer
+ if (textView.TextBuffer != buffer)
+ return null;
+
+ return new BraceMatchingTagger(textView, buffer) as ITagger;
+ }
+ }
+
+ public class BraceMatchingTagger : ITagger
+ {
+ internal BraceMatchingTagger(ITextView view, ITextBuffer sourceBuffer)
+ {
+ //here the keys are the open braces, and the values are the close braces
+ m_braceList = new Dictionary();
+ m_braceList.Add('{', '}');
+ m_braceList.Add('[', ']');
+ m_braceList.Add('(', ')');
+ this.View = view;
+ this.SourceBuffer = sourceBuffer;
+ this.CurrentChar = null;
+
+ this.View.Caret.PositionChanged += CaretPositionChanged;
+ this.View.LayoutChanged += ViewLayoutChanged;
+ }
+
+
+ ITextView View { get; set; }
+ ITextBuffer SourceBuffer { get; set; }
+ SnapshotPoint? CurrentChar { get; set; }
+ private Dictionary m_braceList;
+
+ public event EventHandler TagsChanged;
+
+ void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
+ {
+ if (e.NewSnapshot != e.OldSnapshot) //make sure that there has really been a change
+ {
+ UpdateAtCaretPosition(View.Caret.Position);
+ }
+ }
+
+ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans)
+ {
+ if (spans.Count == 0) //there is no content in the buffer
+ yield break;
+
+ //don't do anything if the current SnapshotPoint is not initialized or at the end of the buffer
+ if (!CurrentChar.HasValue || CurrentChar.Value.Position >= CurrentChar.Value.Snapshot.Length)
+ yield break;
+
+ //hold on to a snapshot of the current character
+ SnapshotPoint currentChar = CurrentChar.Value;
+
+ //if the requested snapshot isn't the same as the one the brace is on, translate our spans to the expected snapshot
+ if (spans[0].Snapshot != currentChar.Snapshot)
+ {
+ currentChar = currentChar.TranslateTo(spans[0].Snapshot, PointTrackingMode.Positive);
+ }
+
+ //get the current char and the previous char
+ char currentText = currentChar.GetChar();
+ SnapshotPoint lastChar = currentChar == 0 ? currentChar : currentChar - 1; //if currentChar is 0 (beginning of buffer), don't move it back
+ char lastText = lastChar.GetChar();
+ SnapshotSpan pairSpan = new SnapshotSpan();
+
+ if (m_braceList.ContainsKey(currentText)) //the key is the open brace
+ {
+ char closeChar;
+ m_braceList.TryGetValue(currentText, out closeChar);
+ if (BraceMatchingTagger.FindMatchingCloseChar(currentChar, currentText, closeChar, View.TextViewLines.Count, out pairSpan) == true)
+ {
+ yield return new TagSpan(new SnapshotSpan(currentChar, 1), new TextMarkerTag("MarkerFormatDefinition/HighlightWordFormatDefinition"));
+ yield return new TagSpan(pairSpan, new TextMarkerTag("MarkerFormatDefinition/HighlightWordFormatDefinition"));
+ }
+ }
+ else if (m_braceList.ContainsValue(lastText)) //the value is the close brace, which is the *previous* character
+ {
+ var open = from n in m_braceList
+ where n.Value.Equals(lastText)
+ select n.Key;
+ if (BraceMatchingTagger.FindMatchingOpenChar(lastChar, (char)open.ElementAt(0), lastText, View.TextViewLines.Count, out pairSpan) == true)
+ {
+ yield return new TagSpan(new SnapshotSpan(lastChar, 1), new TextMarkerTag("MarkerFormatDefinition/HighlightWordFormatDefinition"));
+ yield return new TagSpan(pairSpan, new TextMarkerTag("MarkerFormatDefinition/HighlightWordFormatDefinition"));
+ }
+ }
+ }
+
+ private static bool FindMatchingCloseChar(SnapshotPoint startPoint, char open, char close, int maxLines, out SnapshotSpan pairSpan)
+ {
+ pairSpan = new SnapshotSpan(startPoint.Snapshot, 1, 1);
+ ITextSnapshotLine line = startPoint.GetContainingLine();
+ string lineText = line.GetText();
+ int lineNumber = line.LineNumber;
+ int offset = startPoint.Position - line.Start.Position + 1;
+
+ int stopLineNumber = startPoint.Snapshot.LineCount - 1;
+ if (maxLines > 0)
+ stopLineNumber = Math.Min(stopLineNumber, lineNumber + maxLines);
+
+ int openCount = 0;
+ while (true)
+ {
+ //walk the entire line
+ while (offset < line.Length)
+ {
+ char currentChar = lineText[offset];
+ if (currentChar == close) //found the close character
+ {
+ if (openCount > 0)
+ {
+ openCount--;
+ }
+ else //found the matching close
+ {
+ pairSpan = new SnapshotSpan(startPoint.Snapshot, line.Start + offset, 1);
+ return true;
+ }
+ }
+ else if (currentChar == open) // this is another open
+ {
+ openCount++;
+ }
+ offset++;
+ }
+
+ //move on to the next line
+ if (++lineNumber > stopLineNumber)
+ break;
+
+ line = line.Snapshot.GetLineFromLineNumber(lineNumber);
+ lineText = line.GetText();
+ offset = 0;
+ }
+
+ return false;
+ }
+
+ private static bool FindMatchingOpenChar(SnapshotPoint startPoint, char open, char close, int maxLines, out SnapshotSpan pairSpan)
+ {
+ pairSpan = new SnapshotSpan(startPoint, startPoint);
+
+ ITextSnapshotLine line = startPoint.GetContainingLine();
+
+ int lineNumber = line.LineNumber;
+ int offset = startPoint - line.Start - 1; //move the offset to the character before this one
+
+ //if the offset is negative, move to the previous line
+ if (offset < 0 && lineNumber > 0)
+ {
+ line = line.Snapshot.GetLineFromLineNumber(--lineNumber);
+ offset = line.Length - 1;
+ }
+
+ string lineText = line.GetText();
+
+ int stopLineNumber = 0;
+ if (maxLines > 0)
+ stopLineNumber = Math.Max(stopLineNumber, lineNumber - maxLines);
+
+ int closeCount = 0;
+
+ while (true)
+ {
+ // Walk the entire line
+ while (offset >= 0)
+ {
+ char currentChar = lineText[offset];
+
+ if (currentChar == open)
+ {
+ if (closeCount > 0)
+ {
+ closeCount--;
+ }
+ else // We've found the open character
+ {
+ pairSpan = new SnapshotSpan(line.Start + offset, 1); //we just want the character itself
+ return true;
+ }
+ }
+ else if (currentChar == close)
+ {
+ closeCount++;
+ }
+ offset--;
+ }
+
+ // Move to the previous line
+ if (--lineNumber < stopLineNumber)
+ break;
+
+ line = line.Snapshot.GetLineFromLineNumber(lineNumber);
+ lineText = line.GetText();
+ offset = line.Length - 1;
+ }
+ return false;
+ }
+
+ void CaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
+ {
+ UpdateAtCaretPosition(e.NewPosition);
+ }
+
+ void UpdateAtCaretPosition(CaretPosition caretPosition)
+ {
+ CurrentChar = caretPosition.Point.GetPoint(SourceBuffer, caretPosition.Affinity);
+
+ if (!CurrentChar.HasValue)
+ return;
+
+ var tempEvent = TagsChanged;
+ if (tempEvent != null)
+ tempEvent(this, new SnapshotSpanEventArgs(new SnapshotSpan(SourceBuffer.CurrentSnapshot, 0,
+ SourceBuffer.CurrentSnapshot.Length)));
+ }
+
+ }
+
+}
diff --git a/EditorExtensions/Classifications/CssHighlightWordTagger.cs b/EditorExtensions/Classifications/CssHighlightWordTagger.cs
new file mode 100644
index 000000000..75a427a91
--- /dev/null
+++ b/EditorExtensions/Classifications/CssHighlightWordTagger.cs
@@ -0,0 +1,225 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Operations;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IViewTaggerProvider))]
+ [ContentType("CSS")]
+ [TagType(typeof(TextMarkerTag))]
+ internal class CssHighlightWordTaggerProvider : IViewTaggerProvider
+ {
+ [Import]
+ internal ITextSearchService TextSearchService { get; set; }
+
+ [Import]
+ internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
+
+ public ITagger CreateTagger(ITextView textView, ITextBuffer buffer) where T : ITag
+ {
+ if (textView.TextBuffer != buffer)
+ return null;
+
+ ITextStructureNavigator textStructureNavigator = TextStructureNavigatorSelector.GetTextStructureNavigator(buffer);
+ //return new HighlightWordTagger(textView, buffer, TextSearchService, textStructureNavigator) as ITagger;
+ return buffer.Properties.GetOrCreateSingletonProperty(() => new CssHighlightWordTagger(textView, buffer, TextSearchService, textStructureNavigator)) as ITagger;
+ }
+ }
+
+ internal class HighlightWordTag : TextMarkerTag
+ {
+ public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
+ }
+
+ internal class CssHighlightWordTagger : ITagger
+ {
+ ITextView _view { get; set; }
+ ITextBuffer _buffer { get; set; }
+ ITextSearchService _textSearchService { get; set; }
+ ITextStructureNavigator _textStructureNavigator { get; set; }
+ NormalizedSnapshotSpanCollection _wordSpans { get; set; }
+ SnapshotSpan? _currentWord { get; set; }
+ SnapshotPoint _requestedPoint { get; set; }
+ object _syncLock = new object();
+ private CssTree _tree;
+
+ public CssHighlightWordTagger(ITextView view, ITextBuffer sourceBuffer, ITextSearchService textSearchService, ITextStructureNavigator textStructureNavigator)
+ {
+ this._view = view;
+ this._buffer = sourceBuffer;
+ this._textSearchService = textSearchService;
+ this._textStructureNavigator = textStructureNavigator;
+ this._wordSpans = new NormalizedSnapshotSpanCollection();
+ this._currentWord = null;
+ this._view.Caret.PositionChanged += CaretPositionChanged;
+ this._view.LayoutChanged += ViewLayoutChanged;
+ }
+
+ private void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
+ {
+ if (e.NewSnapshot != e.OldSnapshot)
+ {
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => UpdateAtCaretPosition(_view.Caret.Position)), DispatcherPriority.ApplicationIdle, null);
+ }
+ }
+
+ private void CaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
+ {
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => UpdateAtCaretPosition(e.NewPosition)), DispatcherPriority.ApplicationIdle, null);
+ }
+
+ public event EventHandler TagsChanged;
+
+ private void UpdateAtCaretPosition(CaretPosition caretPosition)
+ {
+ if (!WESettings.GetBoolean(WESettings.Keys.EnableCssSelectorHighligting))
+ return;
+
+ SnapshotPoint? point = caretPosition.Point.GetPoint(_buffer, caretPosition.Affinity);
+
+ if (!point.HasValue || !EnsureInitialized())
+ return;
+
+ ParseItem item = _tree.StyleSheet.ItemBeforePosition(point.Value.Position);
+ if (item == null)
+ return;
+
+ ParseItem validItem = item.FindType();
+
+ if (validItem == null)
+ validItem = item.FindType();
+
+ if (validItem == null)
+ validItem = item.FindType();
+
+ // If the new caret position is still within the current word (and on the same snapshot), we don't need to check it
+ if (_currentWord.HasValue
+ && _currentWord.Value.Snapshot == _view.TextSnapshot
+ && point.Value >= _currentWord.Value.Start
+ && point.Value <= _currentWord.Value.End)
+ {
+ return;
+ }
+
+ _requestedPoint = point.Value;
+ Task.Run(new Action(() => UpdateWordAdornments(validItem)));
+ //UpdateWordAdornments(validItem);
+ }
+
+ void UpdateWordAdornments(ParseItem item)
+ {
+ SnapshotPoint currentRequest = _requestedPoint;
+ List wordSpans = new List();
+ SnapshotSpan currentWord;
+
+ if (item != null)
+ {
+ currentWord = new SnapshotSpan(new SnapshotPoint(_buffer.CurrentSnapshot, item.Start), item.Length);// word.Span;
+ //If this is the current word, and the caret moved within a word, we're done.
+ if (_currentWord.HasValue && currentWord == _currentWord)
+ return;
+
+ //Find the new spans
+ FindData findData = new FindData(item.Text, currentWord.Snapshot);
+ findData.FindOptions = FindOptions.WholeWord | FindOptions.MatchCase;
+
+ wordSpans.AddRange(_textSearchService.FindAll(findData));
+
+ if (wordSpans.Count == 1)
+ wordSpans.Clear();
+ }
+ else
+ {
+ TextExtent word = _textStructureNavigator.GetExtentOfWord(currentRequest);
+ currentWord = word.Span;
+ }
+
+ //If another change hasn't happened, do a real update
+ if (currentRequest == _requestedPoint)
+ {
+ //Task.Run(new Action(() => SynchronousUpdate(currentRequest, new NormalizedSnapshotSpanCollection(wordSpans), currentWord)));
+ SynchronousUpdate(currentRequest, new NormalizedSnapshotSpanCollection(wordSpans), currentWord);
+ }
+ }
+
+ private void SynchronousUpdate(SnapshotPoint currentRequest, NormalizedSnapshotSpanCollection newSpans, SnapshotSpan? newCurrentWord)
+ {
+ lock (_syncLock)
+ {
+ if (currentRequest != _requestedPoint)
+ return;
+
+ _wordSpans = newSpans;
+ _currentWord = newCurrentWord;
+
+ var tempEvent = TagsChanged;
+ if (tempEvent != null)
+ tempEvent(this, new SnapshotSpanEventArgs(new SnapshotSpan(_buffer.CurrentSnapshot, 0, _buffer.CurrentSnapshot.Length)));
+ }
+ }
+
+ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans)
+ {
+ if (_currentWord == null)
+ yield break;
+
+ // Hold on to a "snapshot" of the word spans and current word, so that we maintain the same
+ // collection throughout
+ SnapshotSpan currentWord = _currentWord.Value;
+ NormalizedSnapshotSpanCollection wordSpans = _wordSpans;
+
+ if (spans.Count == 0 || _wordSpans.Count == 0)
+ yield break;
+
+ // If the requested snapshot isn't the same as the one our words are on, translate our spans to the expected snapshot
+ if (spans[0].Snapshot != wordSpans[0].Snapshot)
+ {
+ wordSpans = new NormalizedSnapshotSpanCollection(
+ wordSpans.Select(span => span.TranslateTo(spans[0].Snapshot, SpanTrackingMode.EdgeExclusive)));
+
+ currentWord = currentWord.TranslateTo(spans[0].Snapshot, SpanTrackingMode.EdgeExclusive);
+ }
+
+ // First, yield back the word the cursor is under (if it overlaps)
+ // Note that we'll yield back the same word again in the wordspans collection;
+ // the duplication here is expected.
+ if (spans.OverlapsWith(new NormalizedSnapshotSpanCollection(currentWord)))
+ yield return new TagSpan(currentWord, new HighlightWordTag());
+
+ // Second, yield all the other words in the file
+ foreach (SnapshotSpan span in NormalizedSnapshotSpanCollection.Overlap(spans, wordSpans))
+ {
+ yield return new TagSpan(span, new HighlightWordTag());
+ }
+ }
+
+ private bool EnsureInitialized()
+ {
+ if (_tree == null)
+ {
+ try
+ {
+ CssEditorDocument document = CssEditorDocument.FromTextBuffer(_buffer);
+ _tree = document.Tree;
+ }
+ catch (ArgumentNullException)
+ {
+ }
+ }
+
+ return _tree != null;
+ }
+ }
+}
diff --git a/EditorExtensions/Classifications/JavaScriptArrayOutlining.cs b/EditorExtensions/Classifications/JavaScriptArrayOutlining.cs
new file mode 100644
index 000000000..3e4866eb1
--- /dev/null
+++ b/EditorExtensions/Classifications/JavaScriptArrayOutlining.cs
@@ -0,0 +1,244 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ITaggerProvider))]
+ [TagType(typeof(IOutliningRegionTag))]
+ [ContentType("JavaScript")]
+ internal sealed class ArrayOutliningTaggerProvider : ITaggerProvider
+ {
+ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag
+ {
+ if (WESettings.GetBoolean(WESettings.Keys.JavaScriptOutlining))
+ {
+ return buffer.Properties.GetOrCreateSingletonProperty(() => new JavaScriptArrayOutliningTagger(buffer)) as ITagger;
+ }
+
+ return null;
+ }
+ }
+
+ internal sealed class JavaScriptArrayOutliningTagger : ITagger
+ {
+ string startHide = "["; //the characters that start the outlining region
+ string endHide = "]"; //the characters that end the outlining region
+ string ellipsis = "..."; //the characters that are displayed when the region is collapsed
+ ITextBuffer buffer;
+ ITextSnapshot snapshot;
+ List regions;
+
+ public JavaScriptArrayOutliningTagger(ITextBuffer buffer)
+ {
+ this.buffer = buffer;
+ this.snapshot = buffer.CurrentSnapshot;
+ this.regions = new List();
+ this.buffer.ChangedLowPriority += BufferChanged;
+
+ Task.Run(() => this.ReParse());
+ }
+
+ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans)
+ {
+ if (spans.Count == 0)
+ yield break;
+
+ List currentRegions = this.regions;
+ ITextSnapshot currentSnapshot = this.snapshot;
+ SnapshotSpan entire = new SnapshotSpan(spans[0].Start, spans[spans.Count - 1].End).TranslateTo(currentSnapshot, SpanTrackingMode.EdgeExclusive);
+ int startLineNumber = entire.Start.GetContainingLine().LineNumber;
+ int endLineNumber = entire.End.GetContainingLine().LineNumber;
+
+ foreach (var region in currentRegions)
+ {
+ if (region.StartLine <= endLineNumber && region.EndLine >= startLineNumber)
+ {
+ var startLine = currentSnapshot.GetLineFromLineNumber(region.StartLine);
+ string lineText = startLine.GetText().Trim();
+
+ if (!lineText.Contains("function"))
+ {
+ var endLine = currentSnapshot.GetLineFromLineNumber(region.EndLine);
+ var contentSpan = new SnapshotSpan(startLine.Start + region.StartOffset, endLine.End);
+ //the region starts at the beginning of the "[", and goes until the *end* of the line that contains the "]".
+ yield return new TagSpan(contentSpan, new OutliningRegionTag(false, true, ellipsis, contentSpan.GetText()));
+ }
+ }
+ }
+ }
+
+ public event EventHandler TagsChanged;
+
+ void BufferChanged(object sender, TextContentChangedEventArgs e)
+ {
+ // If this isn't the most up-to-date version of the buffer, then ignore it for now (we'll eventually get another change event).
+ if (e.After != buffer.CurrentSnapshot)
+ return;
+
+ Task.Run(() => this.ReParse());
+ }
+
+ void ReParse()
+ {
+ ITextSnapshot newSnapshot = buffer.CurrentSnapshot;
+ List newRegions = new List();
+
+ //keep the current (deepest) partial region, which will have
+ // references to any parent partial regions.
+ PartialRegion currentRegion = null;
+
+ foreach (var line in newSnapshot.Lines)
+ {
+ int regionStart = -1;
+ string text = line.GetText();
+
+ //lines that contain a "[" denote the start of a new region.
+ if ((regionStart = text.IndexOf(startHide, StringComparison.Ordinal)) != -1)
+ {
+ int currentLevel = (currentRegion != null) ? currentRegion.Level : 1;
+ int newLevel;
+ if (!TryGetLevel(text, regionStart, out newLevel))
+ newLevel = currentLevel + 1;
+
+ //levels are the same and we have an existing region;
+ //end the current region and start the next
+ if (currentLevel == newLevel && currentRegion != null)
+ {
+ newRegions.Add(new Region()
+ {
+ Level = currentRegion.Level,
+ StartLine = currentRegion.StartLine,
+ StartOffset = currentRegion.StartOffset,
+ EndLine = line.LineNumber
+ });
+
+ currentRegion = new PartialRegion()
+ {
+ Level = newLevel,
+ StartLine = line.LineNumber,
+ StartOffset = regionStart,
+ PartialParent = currentRegion.PartialParent
+ };
+ }
+ //this is a new (sub)region
+ else
+ {
+ currentRegion = new PartialRegion()
+ {
+ Level = newLevel,
+ StartLine = line.LineNumber,
+ StartOffset = regionStart,
+ PartialParent = currentRegion
+ };
+ }
+ }
+ //lines that contain "]" denote the end of a region
+ else if ((regionStart = text.IndexOf(endHide, StringComparison.Ordinal)) != -1)
+ {
+ int currentLevel = (currentRegion != null) ? currentRegion.Level : 1;
+ int closingLevel;
+ if (!TryGetLevel(text, regionStart, out closingLevel))
+ closingLevel = currentLevel;
+
+ //the regions match
+ if (currentRegion != null &&
+ currentLevel == closingLevel)
+ {
+ newRegions.Add(new Region()
+ {
+ Level = currentLevel,
+ StartLine = currentRegion.StartLine,
+ StartOffset = currentRegion.StartOffset,
+ EndLine = line.LineNumber
+ });
+
+ currentRegion = currentRegion.PartialParent;
+ }
+ }
+ }
+
+ //determine the changed span, and send a changed event with the new spans
+ List oldSpans =
+ new List(this.regions.Select(r => AsSnapshotSpan(r, this.snapshot)
+ .TranslateTo(newSnapshot, SpanTrackingMode.EdgeExclusive)
+ .Span));
+ List newSpans =
+ new List(newRegions.Select(r => AsSnapshotSpan(r, newSnapshot).Span));
+
+ NormalizedSpanCollection oldSpanCollection = new NormalizedSpanCollection(oldSpans);
+ NormalizedSpanCollection newSpanCollection = new NormalizedSpanCollection(newSpans);
+
+ //the changed regions are regions that appear in one set or the other, but not both.
+ NormalizedSpanCollection removed =
+ NormalizedSpanCollection.Difference(oldSpanCollection, newSpanCollection);
+
+ int changeStart = int.MaxValue;
+ int changeEnd = -1;
+
+ if (removed.Count > 0)
+ {
+ changeStart = removed[0].Start;
+ changeEnd = removed[removed.Count - 1].End;
+ }
+
+ if (newSpans.Count > 0)
+ {
+ changeStart = Math.Min(changeStart, newSpans[0].Start);
+ changeEnd = Math.Max(changeEnd, newSpans[newSpans.Count - 1].End);
+ }
+
+ this.snapshot = newSnapshot;
+ this.regions = newRegions;
+
+ if (changeStart <= changeEnd)
+ {
+ ITextSnapshot snap = this.snapshot;
+ if (this.TagsChanged != null)
+ this.TagsChanged(this, new SnapshotSpanEventArgs(
+ new SnapshotSpan(this.snapshot, Span.FromBounds(changeStart, changeEnd))));
+ }
+ }
+
+ static bool TryGetLevel(string text, int startIndex, out int level)
+ {
+ level = -1;
+ if (text.Length > startIndex + 3)
+ {
+ if (int.TryParse(text.Substring(startIndex + 1), out level))
+ return true;
+ }
+
+ return false;
+ }
+
+ static SnapshotSpan AsSnapshotSpan(Region region, ITextSnapshot snapshot)
+ {
+ var startLine = snapshot.GetLineFromLineNumber(region.StartLine);
+ var endLine = (region.StartLine == region.EndLine) ? startLine
+ : snapshot.GetLineFromLineNumber(region.EndLine);
+ return new SnapshotSpan(startLine.Start + region.StartOffset, endLine.End);
+ }
+
+ }
+
+ //class PartialRegion
+ //{
+ // public int StartLine { get; set; }
+ // public int StartOffset { get; set; }
+ // public int Level { get; set; }
+ // public PartialRegion PartialParent { get; set; }
+ //}
+
+ //class Region : PartialRegion
+ //{
+ // public int EndLine { get; set; }
+ //}
+
+}
diff --git a/EditorExtensions/Classifications/JavaScriptHighlightWordTagger.cs b/EditorExtensions/Classifications/JavaScriptHighlightWordTagger.cs
new file mode 100644
index 000000000..16fcc6b85
--- /dev/null
+++ b/EditorExtensions/Classifications/JavaScriptHighlightWordTagger.cs
@@ -0,0 +1,213 @@
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Operations;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MadsKristensen.WebEssentials.Structures.Js
+{
+ [Export(typeof(IViewTaggerProvider))]
+ [ContentType("JavaScript")]
+ [TagType(typeof(TextMarkerTag))]
+ internal class HighlightWordTaggerProvider : IViewTaggerProvider
+ {
+ [Import]
+ internal ITextSearchService TextSearchService { get; set; }
+
+ [Import]
+ internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
+
+ public ITagger CreateTagger(ITextView textView, ITextBuffer buffer) where T : ITag
+ {
+ if (textView.TextBuffer != buffer)
+ return null;
+
+ ITextStructureNavigator textStructureNavigator = TextStructureNavigatorSelector.GetTextStructureNavigator(buffer);
+ return buffer.Properties.GetOrCreateSingletonProperty(() => new JavaScriptHighlightWordTagger(textView, buffer, TextSearchService, textStructureNavigator)) as ITagger;
+ }
+ }
+
+ internal class HighlightWordTag : TextMarkerTag
+ {
+ public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
+ }
+
+ internal class JavaScriptHighlightWordTagger : ITagger
+ {
+ ITextView _view { get; set; }
+ ITextBuffer _buffer { get; set; }
+ ITextSearchService _textSearchService { get; set; }
+ ITextStructureNavigator _textStructureNavigator { get; set; }
+ NormalizedSnapshotSpanCollection _wordSpans { get; set; }
+ SnapshotSpan? _currentWord { get; set; }
+ SnapshotPoint _requestedPoint { get; set; }
+ object _updateLock = new object();
+ private bool _inProgress;
+
+ public JavaScriptHighlightWordTagger(ITextView view, ITextBuffer sourceBuffer, ITextSearchService textSearchService, ITextStructureNavigator textStructureNavigator)
+ {
+ this._view = view;
+ this._buffer = sourceBuffer;
+ this._textSearchService = textSearchService;
+ this._textStructureNavigator = textStructureNavigator;
+ this._wordSpans = new NormalizedSnapshotSpanCollection();
+ this._currentWord = null;
+ this._view.Caret.PositionChanged += CaretPositionChanged;
+ }
+
+ void CaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
+ {
+ if (!_inProgress)
+ {
+ _inProgress = true;
+
+ Task.Run(() =>
+ {
+ UpdateAtCaretPosition(e.NewPosition);
+ _inProgress = false;
+ });
+ }
+ }
+
+ public event EventHandler TagsChanged;
+
+ void UpdateAtCaretPosition(CaretPosition caretPosition)
+ {
+ SnapshotPoint? point = caretPosition.Point.GetPoint(_buffer, caretPosition.Affinity);
+
+ if (!point.HasValue)
+ return;
+
+ // If the new caret position is still within the current word (and on the same snapshot), we don't need to check it
+ if (_currentWord.HasValue
+ && _currentWord.Value.Snapshot == _view.TextSnapshot
+ && point.Value >= _currentWord.Value.Start
+ && point.Value <= _currentWord.Value.End)
+ {
+ return;
+ }
+
+ _requestedPoint = point.Value;
+
+ UpdateWordAdornments();
+ }
+
+ void UpdateWordAdornments()
+ {
+ SnapshotPoint currentRequest = _requestedPoint;
+ List wordSpans = new List();
+ //Find all words in the buffer like the one the caret is on
+ TextExtent word = _textStructureNavigator.GetExtentOfWord(currentRequest);
+ bool foundWord = true;
+ //If we've selected something not worth highlighting, we might have missed a "word" by a little bit
+ if (!WordExtentIsValid(currentRequest, word))
+ {
+ //Before we retry, make sure it is worthwhile
+ if (word.Span.Start != currentRequest
+ || currentRequest == currentRequest.GetContainingLine().Start
+ || char.IsWhiteSpace((currentRequest - 1).GetChar()))
+ {
+ foundWord = false;
+ }
+ else
+ {
+ // Try again, one character previous.
+ //If the caret is at the end of a word, pick up the word.
+ word = _textStructureNavigator.GetExtentOfWord(currentRequest - 1);
+
+ //If the word still isn't valid, we're done
+ if (!WordExtentIsValid(currentRequest, word))
+ foundWord = false;
+ }
+ }
+
+ if (!foundWord)
+ {
+ //If we couldn't find a word, clear out the existing markers
+ SynchronousUpdate(currentRequest, new NormalizedSnapshotSpanCollection(), null);
+ return;
+ }
+
+ SnapshotSpan currentWord = word.Span;
+ //If this is the current word, and the caret moved within a word, we're done.
+ if (_currentWord.HasValue && currentWord == _currentWord)
+ return;
+
+ //Find the new spans
+ FindData findData = new FindData(currentWord.GetText(), currentWord.Snapshot);
+ findData.FindOptions = FindOptions.WholeWord | FindOptions.MatchCase;
+
+ wordSpans.AddRange(_textSearchService.FindAll(findData));
+
+ //If another change hasn't happened, do a real update
+ if (currentRequest == _requestedPoint)
+ SynchronousUpdate(currentRequest, new NormalizedSnapshotSpanCollection(wordSpans), currentWord);
+ }
+ static bool WordExtentIsValid(SnapshotPoint currentRequest, TextExtent word)
+ {
+ return word.IsSignificant
+ && currentRequest.Snapshot.GetText(word.Span).Any(c => char.IsLetter(c));
+ }
+
+ void SynchronousUpdate(SnapshotPoint currentRequest, NormalizedSnapshotSpanCollection newSpans, SnapshotSpan? newCurrentWord)
+ {
+ lock (_updateLock)
+ {
+ if (currentRequest != _requestedPoint)
+ return;
+
+ _wordSpans = newSpans;
+ _currentWord = newCurrentWord;
+
+ var tempEvent = TagsChanged;
+ if (tempEvent != null)
+ tempEvent(this, new SnapshotSpanEventArgs(new SnapshotSpan(_buffer.CurrentSnapshot, 0, _buffer.CurrentSnapshot.Length)));
+ }
+ }
+
+ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans)
+ {
+ if (_currentWord == null)
+ yield break;
+
+ // Hold on to a "snapshot" of the word spans and current word, so that we maintain the same
+ // collection throughout
+ SnapshotSpan currentWord = _currentWord.Value;
+ NormalizedSnapshotSpanCollection wordSpans = _wordSpans;
+
+ if (spans.Count == 0 || _wordSpans.Count == 0)
+ yield break;
+
+ // If the requested snapshot isn't the same as the one our words are on, translate our spans to the expected snapshot
+ if (spans[0].Snapshot != wordSpans[0].Snapshot)
+ {
+ wordSpans = new NormalizedSnapshotSpanCollection(
+ wordSpans.Select(span => span.TranslateTo(spans[0].Snapshot, SpanTrackingMode.EdgeExclusive)));
+
+ currentWord = currentWord.TranslateTo(spans[0].Snapshot, SpanTrackingMode.EdgeExclusive);
+ }
+
+ NormalizedSnapshotSpanCollection words = NormalizedSnapshotSpanCollection.Overlap(spans, wordSpans);
+
+ if (words.Count == 1)
+ yield break;
+
+ // First, yield back the word the cursor is under (if it overlaps)
+ // Note that we'll yield back the same word again in the wordspans collection;
+ // the duplication here is expected.
+ if (spans.OverlapsWith(new NormalizedSnapshotSpanCollection(currentWord)))
+ yield return new TagSpan(currentWord, new HighlightWordTag());
+
+ // Second, yield all the other words in the file
+ foreach (SnapshotSpan span in words)
+ {
+ yield return new TagSpan(span, new HighlightWordTag());
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/Classifications/JavaScriptOutlining.cs b/EditorExtensions/Classifications/JavaScriptOutlining.cs
new file mode 100644
index 000000000..c5ad13bd4
--- /dev/null
+++ b/EditorExtensions/Classifications/JavaScriptOutlining.cs
@@ -0,0 +1,247 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ITaggerProvider))]
+ [TagType(typeof(IOutliningRegionTag))]
+ [ContentType("JavaScript")]
+ internal sealed class OutliningTaggerProvider : ITaggerProvider
+ {
+ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag
+ {
+ if (WESettings.GetBoolean(WESettings.Keys.JavaScriptOutlining))
+ {
+ return buffer.Properties.GetOrCreateSingletonProperty(() => new JavaScriptOutliningTagger(buffer)) as ITagger;
+ }
+
+ return null;
+ }
+ }
+
+ internal sealed class JavaScriptOutliningTagger : ITagger
+ {
+ string startHide = "{"; //the characters that start the outlining region
+ string endHide = "}"; //the characters that end the outlining region
+ string ellipsis = "..."; //the characters that are displayed when the region is collapsed
+ ITextBuffer buffer;
+ ITextSnapshot snapshot;
+ List regions;
+
+ public JavaScriptOutliningTagger(ITextBuffer buffer)
+ {
+ this.buffer = buffer;
+ this.snapshot = buffer.CurrentSnapshot;
+ this.regions = new List();
+ this.buffer.ChangedLowPriority += BufferChanged;
+
+ Task.Run(() => this.ReParse());
+ }
+
+ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans)
+ {
+ if (spans.Count == 0)
+ yield break;
+
+ List currentRegions = this.regions;
+ ITextSnapshot currentSnapshot = this.snapshot;
+ SnapshotSpan entire = new SnapshotSpan(spans[0].Start, spans[spans.Count - 1].End).TranslateTo(currentSnapshot, SpanTrackingMode.EdgeExclusive);
+ int startLineNumber = entire.Start.GetContainingLine().LineNumber;
+ int endLineNumber = entire.End.GetContainingLine().LineNumber;
+
+ foreach (var region in currentRegions)
+ {
+ if (region.StartLine <= endLineNumber && region.EndLine >= startLineNumber)
+ {
+ var startLine = currentSnapshot.GetLineFromLineNumber(region.StartLine);
+ string lineText = startLine.GetText().Trim();
+
+ if (!lineText.Contains("function"))
+ {
+ var endLine = currentSnapshot.GetLineFromLineNumber(region.EndLine);
+ var contentSpan = new SnapshotSpan(startLine.Start + region.StartOffset, endLine.End);
+ //the region starts at the beginning of the "[", and goes until the *end* of the line that contains the "]".
+ yield return new TagSpan(contentSpan, new OutliningRegionTag(false, false, ellipsis, contentSpan.GetText()));
+ }
+ }
+ }
+ }
+
+ public event EventHandler TagsChanged;
+
+ void BufferChanged(object sender, TextContentChangedEventArgs e)
+ {
+ // If this isn't the most up-to-date version of the buffer, then ignore it for now (we'll eventually get another change event).
+ if (e.After != buffer.CurrentSnapshot)
+ return;
+
+ Task.Run(() => this.ReParse());
+ }
+
+ void ReParse()
+ {
+ ITextSnapshot newSnapshot = buffer.CurrentSnapshot;
+ List newRegions = new List();
+
+ //keep the current (deepest) partial region, which will have
+ // references to any parent partial regions.
+ PartialRegion currentRegion = null;
+
+ foreach (var line in newSnapshot.Lines)
+ {
+ int regionStart = -1;
+ string text = line.GetText();
+
+ if (text.IndexOf(startHide) > -1 && text.IndexOf(endHide) > -1)
+ continue;
+
+ //lines that contain a "[" denote the start of a new region.
+ if ((regionStart = text.IndexOf(startHide, StringComparison.Ordinal)) != -1)
+ {
+ int currentLevel = (currentRegion != null) ? currentRegion.Level : 1;
+ int newLevel;
+ if (!TryGetLevel(text, regionStart, out newLevel))
+ newLevel = currentLevel + 1;
+
+ //levels are the same and we have an existing region;
+ //end the current region and start the next
+ if (currentLevel == newLevel && currentRegion != null)
+ {
+ newRegions.Add(new Region()
+ {
+ Level = currentRegion.Level,
+ StartLine = currentRegion.StartLine,
+ StartOffset = currentRegion.StartOffset,
+ EndLine = line.LineNumber
+ });
+
+ currentRegion = new PartialRegion()
+ {
+ Level = newLevel,
+ StartLine = line.LineNumber,
+ StartOffset = regionStart,
+ PartialParent = currentRegion.PartialParent
+ };
+ }
+ //this is a new (sub)region
+ else
+ {
+ currentRegion = new PartialRegion()
+ {
+ Level = newLevel,
+ StartLine = line.LineNumber,
+ StartOffset = regionStart,
+ PartialParent = currentRegion
+ };
+ }
+ }
+ //lines that contain "]" denote the end of a region
+ else if ((regionStart = text.IndexOf(endHide, StringComparison.Ordinal)) != -1)
+ {
+ int currentLevel = (currentRegion != null) ? currentRegion.Level : 1;
+ int closingLevel;
+ if (!TryGetLevel(text, regionStart, out closingLevel))
+ closingLevel = currentLevel;
+
+ //the regions match
+ if (currentRegion != null &&
+ currentLevel == closingLevel)
+ {
+ newRegions.Add(new Region()
+ {
+ Level = currentLevel,
+ StartLine = currentRegion.StartLine,
+ StartOffset = currentRegion.StartOffset,
+ EndLine = line.LineNumber
+ });
+
+ currentRegion = currentRegion.PartialParent;
+ }
+ }
+ }
+
+ //determine the changed span, and send a changed event with the new spans
+ List oldSpans =
+ new List(this.regions.Select(r => AsSnapshotSpan(r, this.snapshot)
+ .TranslateTo(newSnapshot, SpanTrackingMode.EdgeExclusive)
+ .Span));
+ List newSpans =
+ new List(newRegions.Select(r => AsSnapshotSpan(r, newSnapshot).Span));
+
+ NormalizedSpanCollection oldSpanCollection = new NormalizedSpanCollection(oldSpans);
+ NormalizedSpanCollection newSpanCollection = new NormalizedSpanCollection(newSpans);
+
+ //the changed regions are regions that appear in one set or the other, but not both.
+ NormalizedSpanCollection removed =
+ NormalizedSpanCollection.Difference(oldSpanCollection, newSpanCollection);
+
+ int changeStart = int.MaxValue;
+ int changeEnd = -1;
+
+ if (removed.Count > 0)
+ {
+ changeStart = removed[0].Start;
+ changeEnd = removed[removed.Count - 1].End;
+ }
+
+ if (newSpans.Count > 0)
+ {
+ changeStart = Math.Min(changeStart, newSpans[0].Start);
+ changeEnd = Math.Max(changeEnd, newSpans[newSpans.Count - 1].End);
+ }
+
+ this.snapshot = newSnapshot;
+ this.regions = newRegions;
+
+ if (changeStart <= changeEnd)
+ {
+ ITextSnapshot snap = this.snapshot;
+ if (this.TagsChanged != null)
+ this.TagsChanged(this, new SnapshotSpanEventArgs(
+ new SnapshotSpan(this.snapshot, Span.FromBounds(changeStart, changeEnd))));
+ }
+ }
+
+ static bool TryGetLevel(string text, int startIndex, out int level)
+ {
+ level = -1;
+ if (text.Length > startIndex + 3)
+ {
+ if (int.TryParse(text.Substring(startIndex + 1), out level))
+ return true;
+ }
+
+ return false;
+ }
+
+ static SnapshotSpan AsSnapshotSpan(Region region, ITextSnapshot snapshot)
+ {
+ var startLine = snapshot.GetLineFromLineNumber(region.StartLine);
+ var endLine = (region.StartLine == region.EndLine) ? startLine
+ : snapshot.GetLineFromLineNumber(region.EndLine);
+ return new SnapshotSpan(startLine.Start + region.StartOffset, endLine.End);
+ }
+
+ }
+
+ //class PartialRegion
+ //{
+ // public int StartLine { get; set; }
+ // public int StartOffset { get; set; }
+ // public int Level { get; set; }
+ // public PartialRegion PartialParent { get; set; }
+ //}
+
+ //class Region : PartialRegion
+ //{
+ // public int EndLine { get; set; }
+ //}
+
+}
diff --git a/EditorExtensions/Classifications/JavaScriptRegionTagger.cs b/EditorExtensions/Classifications/JavaScriptRegionTagger.cs
new file mode 100644
index 000000000..47f828953
--- /dev/null
+++ b/EditorExtensions/Classifications/JavaScriptRegionTagger.cs
@@ -0,0 +1,244 @@
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ITaggerProvider))]
+ [TagType(typeof(IOutliningRegionTag))]
+ [ContentType("javascript")]
+ //[ContentType("TypeScript")]
+ [ContentType("LESS")]
+ internal sealed class RegionTaggerProvider : ITaggerProvider
+ {
+ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag
+ {
+ return buffer.Properties.GetOrCreateSingletonProperty(() => new RegionTagger(buffer)) as ITagger;
+ }
+ }
+
+ internal sealed class RegionTagger : ITagger
+ {
+ string startHide = "//#region"; //the characters that start the outlining region
+ string endHide = "//#endregion"; //the characters that end the outlining region
+ string hoverText = "Collapsed content"; //the contents of the tooltip for the collapsed span
+ ITextBuffer buffer;
+ ITextSnapshot snapshot;
+ List regions;
+ private static Regex regex = new Regex(@"\/\/\#region(.*)?", RegexOptions.Compiled);
+
+ public RegionTagger(ITextBuffer buffer)
+ {
+ this.buffer = buffer;
+ this.snapshot = buffer.CurrentSnapshot;
+ this.regions = new List();
+ this.buffer.Changed += BufferChanged;
+
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => ReParse()), DispatcherPriority.ApplicationIdle, null);
+
+ this.buffer.Changed += BufferChanged;
+ }
+
+ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans)
+ {
+ if (spans.Count == 0 || !WESettings.GetBoolean(WESettings.Keys.EnableJavascriptRegions))
+ yield break;
+
+ List currentRegions = this.regions;
+ ITextSnapshot currentSnapshot = this.snapshot;
+
+ SnapshotSpan entire = new SnapshotSpan(spans[0].Start, spans[spans.Count - 1].End).TranslateTo(currentSnapshot, SpanTrackingMode.EdgeExclusive);
+ int startLineNumber = entire.Start.GetContainingLine().LineNumber;
+ int endLineNumber = entire.End.GetContainingLine().LineNumber;
+ foreach (var region in currentRegions)
+ {
+ if (region.StartLine <= endLineNumber && region.EndLine >= startLineNumber)
+ {
+ var startLine = currentSnapshot.GetLineFromLineNumber(region.StartLine);
+ var endLine = currentSnapshot.GetLineFromLineNumber(region.EndLine);
+
+ var snapshot = new SnapshotSpan(startLine.Start + region.StartOffset, endLine.End);
+ Match match = regex.Match(snapshot.GetText());
+
+ //the region starts at the beginning of the "[", and goes until the *end* of the line that contains the "]".
+ yield return new TagSpan(
+ snapshot,
+ new OutliningRegionTag(false, true, " " + match.Groups[1].Value.Trim() + " ", hoverText));
+ }
+ }
+ }
+
+ public event EventHandler TagsChanged;
+
+ void BufferChanged(object sender, TextContentChangedEventArgs e)
+ {
+ // If this isn't the most up-to-date version of the buffer, then ignore it for now (we'll eventually get another change event).
+ if (e.After != buffer.CurrentSnapshot)
+ return;
+
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => ReParse()), DispatcherPriority.ApplicationIdle, null);
+ }
+
+ void ReParse()
+ {
+ ITextSnapshot newSnapshot = buffer.CurrentSnapshot;
+ List newRegions = new List();
+
+ //keep the current (deepest) partial region, which will have
+ // references to any parent partial regions.
+ PartialRegion currentRegion = null;
+
+ foreach (var line in newSnapshot.Lines)
+ {
+ int regionStart = -1;
+ string text = line.GetText();
+
+ //lines that contain a "[" denote the start of a new region.
+ if ((regionStart = text.IndexOf(startHide, StringComparison.Ordinal)) != -1)
+ {
+ int currentLevel = (currentRegion != null) ? currentRegion.Level : 1;
+ int newLevel;
+ if (!TryGetLevel(text, regionStart, out newLevel))
+ newLevel = currentLevel + 1;
+
+ //levels are the same and we have an existing region;
+ //end the current region and start the next
+ if (currentLevel == newLevel && currentRegion != null)
+ {
+ newRegions.Add(new Region()
+ {
+ Level = currentRegion.Level,
+ StartLine = currentRegion.StartLine,
+ StartOffset = currentRegion.StartOffset,
+ EndLine = line.LineNumber
+ });
+
+ currentRegion = new PartialRegion()
+ {
+ Level = newLevel,
+ StartLine = line.LineNumber,
+ StartOffset = regionStart,
+ PartialParent = currentRegion.PartialParent
+ };
+ }
+ //this is a new (sub)region
+ else
+ {
+ currentRegion = new PartialRegion()
+ {
+ Level = newLevel,
+ StartLine = line.LineNumber,
+ StartOffset = regionStart,
+ PartialParent = currentRegion
+ };
+ }
+ }
+ //lines that contain "]" denote the end of a region
+ else if ((regionStart = text.IndexOf(endHide, StringComparison.Ordinal)) != -1)
+ {
+ int currentLevel = (currentRegion != null) ? currentRegion.Level : 1;
+ int closingLevel;
+ if (!TryGetLevel(text, regionStart, out closingLevel))
+ closingLevel = currentLevel;
+
+ //the regions match
+ if (currentRegion != null &&
+ currentLevel == closingLevel)
+ {
+ newRegions.Add(new Region()
+ {
+ Level = currentLevel,
+ StartLine = currentRegion.StartLine,
+ StartOffset = currentRegion.StartOffset,
+ EndLine = line.LineNumber
+ });
+
+ currentRegion = currentRegion.PartialParent;
+ }
+ }
+ }
+
+ //determine the changed span, and send a changed event with the new spans
+ List oldSpans =
+ new List(this.regions.Select(r => AsSnapshotSpan(r, this.snapshot)
+ .TranslateTo(newSnapshot, SpanTrackingMode.EdgeExclusive)
+ .Span));
+ List newSpans =
+ new List(newRegions.Select(r => AsSnapshotSpan(r, newSnapshot).Span));
+
+ NormalizedSpanCollection oldSpanCollection = new NormalizedSpanCollection(oldSpans);
+ NormalizedSpanCollection newSpanCollection = new NormalizedSpanCollection(newSpans);
+
+ //the changed regions are regions that appear in one set or the other, but not both.
+ NormalizedSpanCollection removed =
+ NormalizedSpanCollection.Difference(oldSpanCollection, newSpanCollection);
+
+ int changeStart = int.MaxValue;
+ int changeEnd = -1;
+
+ if (removed.Count > 0)
+ {
+ changeStart = removed[0].Start;
+ changeEnd = removed[removed.Count - 1].End;
+ }
+
+ if (newSpans.Count > 0)
+ {
+ changeStart = Math.Min(changeStart, newSpans[0].Start);
+ changeEnd = Math.Max(changeEnd, newSpans[newSpans.Count - 1].End);
+ }
+
+ this.snapshot = newSnapshot;
+ this.regions = newRegions;
+
+ if (changeStart <= changeEnd)
+ {
+ ITextSnapshot snap = this.snapshot;
+ if (this.TagsChanged != null)
+ this.TagsChanged(this, new SnapshotSpanEventArgs(
+ new SnapshotSpan(this.snapshot, Span.FromBounds(changeStart, changeEnd))));
+ }
+ }
+
+ static bool TryGetLevel(string text, int startIndex, out int level)
+ {
+ level = -1;
+ if (text.Length > startIndex + 3)
+ {
+ if (int.TryParse(text.Substring(startIndex + 1), out level))
+ return true;
+ }
+
+ return false;
+ }
+
+ static SnapshotSpan AsSnapshotSpan(Region region, ITextSnapshot snapshot)
+ {
+ var startLine = snapshot.GetLineFromLineNumber(region.StartLine);
+ var endLine = (region.StartLine == region.EndLine) ? startLine
+ : snapshot.GetLineFromLineNumber(region.EndLine);
+ return new SnapshotSpan(startLine.Start + region.StartOffset, endLine.End);
+ }
+ }
+
+ class PartialRegion
+ {
+ public int StartLine { get; set; }
+ public int StartOffset { get; set; }
+ public int Level { get; set; }
+ public PartialRegion PartialParent { get; set; }
+ }
+
+ class Region : PartialRegion
+ {
+ public int EndLine { get; set; }
+ }
+}
diff --git a/EditorExtensions/Classifications/Markdown/MarkdownClassificationTypes.cs b/EditorExtensions/Classifications/Markdown/MarkdownClassificationTypes.cs
new file mode 100644
index 000000000..af93c3bdb
--- /dev/null
+++ b/EditorExtensions/Classifications/Markdown/MarkdownClassificationTypes.cs
@@ -0,0 +1,77 @@
+using Microsoft.VisualStudio.Text.Classification;
+using Microsoft.VisualStudio.Utilities;
+using System.ComponentModel.Composition;
+using System.Windows;
+
+namespace MadsKristensen.EditorExtensions
+{
+ class MarkdownClassificationTypes
+ {
+ public const string MarkdownBold = "md_bold";
+ public const string MarkdownItalic = "md_italic";
+ public const string MarkdownHeader = "md_header";
+ public const string MarkdownCode = "md_code";
+
+ [Export, Name(MarkdownClassificationTypes.MarkdownBold), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
+ internal static ClassificationTypeDefinition MarkdownClassificationBold = null;
+
+ [Export, Name(MarkdownClassificationTypes.MarkdownItalic), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
+ internal static ClassificationTypeDefinition MarkdownClassificationItalic = null;
+
+ [Export, Name(MarkdownClassificationTypes.MarkdownHeader), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
+ internal static ClassificationTypeDefinition MarkdownClassificationHeader = null;
+
+ [Export, Name(MarkdownClassificationTypes.MarkdownCode), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
+ internal static ClassificationTypeDefinition MarkdownClassificationCode = null;
+ }
+
+ [Export(typeof(EditorFormatDefinition))]
+ [ClassificationType(ClassificationTypeNames = MarkdownClassificationTypes.MarkdownBold)]
+ [Name(MarkdownClassificationTypes.MarkdownBold)]
+ [Order(After = Priority.Default)]
+ internal sealed class MarkdownBoldFormatDefinition : ClassificationFormatDefinition
+ {
+ public MarkdownBoldFormatDefinition()
+ {
+ IsBold = true;
+ }
+ }
+
+ [Export(typeof(EditorFormatDefinition))]
+ [ClassificationType(ClassificationTypeNames = MarkdownClassificationTypes.MarkdownItalic)]
+ [Name(MarkdownClassificationTypes.MarkdownItalic)]
+ [Order(After = Priority.Default)]
+ internal sealed class MarkdownItalicFormatDefinition : ClassificationFormatDefinition
+ {
+ public MarkdownItalicFormatDefinition()
+ {
+ IsItalic = true;
+ }
+ }
+
+ [Export(typeof(EditorFormatDefinition))]
+ [ClassificationType(ClassificationTypeNames = MarkdownClassificationTypes.MarkdownHeader)]
+ [Name(MarkdownClassificationTypes.MarkdownHeader)]
+ [Order(After = Priority.Default)]
+ internal sealed class MarkdownHeaderFormatDefinition : ClassificationFormatDefinition
+ {
+ public MarkdownHeaderFormatDefinition()
+ {
+ IsBold = true;
+ TextDecorations = new TextDecorationCollection();
+ TextDecorations.Add(new TextDecoration());
+ }
+ }
+
+ [Export(typeof(EditorFormatDefinition))]
+ [ClassificationType(ClassificationTypeNames = MarkdownClassificationTypes.MarkdownCode)]
+ [Name(MarkdownClassificationTypes.MarkdownCode)]
+ [Order(After = Priority.Default)]
+ internal sealed class MarkdownCodeFormatDefinition : ClassificationFormatDefinition
+ {
+ public MarkdownCodeFormatDefinition()
+ {
+ ForegroundColor = System.Windows.Media.Colors.Green;
+ }
+ }
+}
diff --git a/EditorExtensions/Classifications/Markdown/MarkdownClassifier.cs b/EditorExtensions/Classifications/Markdown/MarkdownClassifier.cs
new file mode 100644
index 000000000..4df5d7a61
--- /dev/null
+++ b/EditorExtensions/Classifications/Markdown/MarkdownClassifier.cs
@@ -0,0 +1,70 @@
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Classification;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IClassifierProvider))]
+ [ContentType(MarkdownContentTypeDefinition.MarkdownContentType)]
+ public class MarkdownClassifierProvider : IClassifierProvider
+ {
+ [Import]
+ internal IClassificationTypeRegistryService Registry { get; set; }
+
+ public IClassifier GetClassifier(ITextBuffer textBuffer)
+ {
+ return textBuffer.Properties.GetOrCreateSingletonProperty(() => new MarkdownClassifier(Registry));
+ }
+ }
+
+ public class MarkdownClassifier : IClassifier
+ {
+ private static readonly Regex _reItalic = new Regex(@"(? GetClassificationSpans(SnapshotSpan span)
+ {
+ string text = span.GetText();
+
+ var bolds = FindMatches(span, text, _reBold, _bold);
+ var italics = FindMatches(span, text, _reItalic, _italic);
+ var headers = FindMatches(span, text, _reHeader, _header);
+ var codes = FindMatches(span, text, _reCode, _code);
+
+ return bolds.Concat(italics).Concat(headers).Concat(codes).ToList();
+ }
+
+ private IEnumerable FindMatches(SnapshotSpan span, string text, Regex regex, IClassificationType type)
+ {
+ Match match = regex.Match(text);
+
+ while (match.Success)
+ {
+ var result = new SnapshotSpan(span.Snapshot, span.Start + match.Index, match.Length);
+ yield return new ClassificationSpan(result, type);
+
+ match = regex.Match(text, match.Index + match.Length);
+ }
+ }
+
+ public event EventHandler ClassificationChanged;
+ }
+
+}
diff --git a/EditorExtensions/Classifications/Markdown/MarkdownContentTypeDefinition.cs b/EditorExtensions/Classifications/Markdown/MarkdownContentTypeDefinition.cs
new file mode 100644
index 000000000..9d46e7229
--- /dev/null
+++ b/EditorExtensions/Classifications/Markdown/MarkdownContentTypeDefinition.cs
@@ -0,0 +1,39 @@
+using Microsoft.VisualStudio.Utilities;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ ///
+ /// Exports the ScSS content type and file extension
+ ///
+ public class MarkdownContentTypeDefinition
+ {
+ public const string MarkdownContentType = "markdown";
+
+ ///
+ /// Exports the Markdown HTML content type
+ ///
+ [Export(typeof(ContentTypeDefinition))]
+ [Name(MarkdownContentType)]
+ [BaseDefinition("html")]
+ public ContentTypeDefinition IMarkdownContentType { get; set; }
+
+ ///
+ /// Exports the markdown file extension
+ ///
+ [Export(typeof(FileExtensionToContentTypeDefinition))]
+ [ContentType(MarkdownContentType)]
+ [FileExtension(".md")]
+ public FileExtensionToContentTypeDefinition IMDFileExtension { get; set; }
+
+ [Export(typeof(FileExtensionToContentTypeDefinition))]
+ [ContentType(MarkdownContentType)]
+ [FileExtension(".mdown")]
+ public FileExtensionToContentTypeDefinition IMDownFileExtension { get; set; }
+
+ [Export(typeof(FileExtensionToContentTypeDefinition))]
+ [ContentType(MarkdownContentType)]
+ [FileExtension(".markdown")]
+ public FileExtensionToContentTypeDefinition IMarkDownFileExtension { get; set; }
+ }
+}
diff --git a/EditorExtensions/Classifications/ModernizrClassifier.cs b/EditorExtensions/Classifications/ModernizrClassifier.cs
new file mode 100644
index 000000000..0f8b3ed86
--- /dev/null
+++ b/EditorExtensions/Classifications/ModernizrClassifier.cs
@@ -0,0 +1,160 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Classification;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.Web.Editor;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal static class ModernizrClassificationTypes
+ {
+ internal const string _modernizr = "modernizr";
+
+ [Export, Name(ModernizrClassificationTypes._modernizr), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
+ internal static ClassificationTypeDefinition ModernizrClassificationType = null;
+ }
+
+ [Export(typeof(IClassifierProvider))]
+ [ContentType("css")]
+ internal sealed class ModernizrClassifierProvider : IClassifierProvider
+ {
+ [Import, System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal IClassificationTypeRegistryService Registry { get; set; }
+
+ public IClassifier GetClassifier(ITextBuffer buffer)
+ {
+ return buffer.Properties.GetOrCreateSingletonProperty(() => { return new ModernizrClassifier(Registry, buffer); });
+ }
+ }
+
+ internal sealed class ModernizrClassifier : IClassifier
+ {
+ private IClassificationTypeRegistryService _registry;
+ private ITextBuffer _buffer;
+ private CssTree _tree;
+ internal SortedRangeList Cache = new SortedRangeList();
+ private IClassificationType _modernizrClassification;
+
+ internal ModernizrClassifier(IClassificationTypeRegistryService registry, ITextBuffer buffer)
+ {
+ _registry = registry;
+ _buffer = buffer;
+ _modernizrClassification = _registry.GetClassificationType(ModernizrClassificationTypes._modernizr);
+ }
+
+ public IList GetClassificationSpans(SnapshotSpan span)
+ {
+ List spans = new List();
+
+ if (!EnsureInitialized())
+ return spans;
+
+ foreach (SimpleSelector selector in Cache)
+ {
+ int start = span.Start.Position;
+ int end = span.End.Position;
+
+ if (selector.Start >= start && selector.AfterEnd <= end)
+ {
+ var snapShotSpan = new SnapshotSpan(span.Snapshot, selector.Start, selector.Length);
+ var classSpan = new ClassificationSpan(snapShotSpan, _modernizrClassification);
+ spans.Add(classSpan);
+ }
+ }
+
+ return spans;
+ }
+
+ public bool EnsureInitialized()
+ {
+ if (_tree == null && WebEditor.Host != null)
+ {
+ try
+ {
+ CssEditorDocument document = CssEditorDocument.FromTextBuffer(_buffer);
+ _tree = document.Tree;
+ _tree.TreeUpdated += TreeUpdated;
+ _tree.ItemsChanged += TreeItemsChanged;
+ UpdateCache(_tree.StyleSheet);
+ }
+ catch (ArgumentNullException)
+ {
+ }
+ }
+
+ return _tree != null;
+ }
+
+ private void UpdateCache(ParseItem item)
+ {
+ var visitor = new CssItemCollector(true);
+ item.Accept(visitor);
+
+ foreach (SimpleSelector ss in visitor.Items)
+ {
+ string text = ss.Text;
+
+ if (ModernizrProvider.IsModernizr(text))
+ {
+ if (!Cache.Contains(ss))
+ Cache.Add(ss);
+ }
+ }
+ }
+
+ private void TreeUpdated(object sender, CssTreeUpdateEventArgs e)
+ {
+ Cache.Clear();
+ UpdateCache(e.Tree.StyleSheet);
+ }
+
+ private void TreeItemsChanged(object sender, CssItemsChangedEventArgs e)
+ {
+ foreach (ParseItem item in e.DeletedItems)
+ {
+ var matches = Cache.Where(s => s.Start >= item.Start && s.AfterEnd <= item.AfterEnd);
+ foreach (var match in matches.Reverse())
+ {
+ Cache.Remove(match);
+ }
+ }
+
+ foreach (ParseItem item in e.InsertedItems)
+ {
+ UpdateCache(item);
+ }
+ }
+
+ public event EventHandler ClassificationChanged;
+
+ public void RaiseClassificationChanged(SnapshotSpan span)
+ {
+ var handler = this.ClassificationChanged;
+ if (handler != null)
+ {
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => handler(this, new ClassificationChangedEventArgs(span))), DispatcherPriority.ApplicationIdle);
+ }
+ }
+ }
+
+ [Export(typeof(EditorFormatDefinition))]
+ [UserVisible(true)]
+ [ClassificationType(ClassificationTypeNames = ModernizrClassificationTypes._modernizr)]
+ [Name(ModernizrClassificationTypes._modernizr)]
+ [Order(After = Priority.Default)]
+ internal sealed class ModernizrFormatDefinition : ClassificationFormatDefinition
+ {
+ public ModernizrFormatDefinition()
+ {
+ IsBold = true;
+ DisplayName = "CSS Modernizr selector";
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Classifications/SCSS/ScssClassifierProvider.cs b/EditorExtensions/Classifications/SCSS/ScssClassifierProvider.cs
new file mode 100644
index 000000000..960db0504
--- /dev/null
+++ b/EditorExtensions/Classifications/SCSS/ScssClassifierProvider.cs
@@ -0,0 +1,27 @@
+//using System.ComponentModel.Composition;
+//using Microsoft.CSS.Editor;
+//using Microsoft.VisualStudio.Text;
+//using Microsoft.VisualStudio.Text.Classification;
+//using Microsoft.VisualStudio.Utilities;
+//using Microsoft.Web.Editor;
+
+//namespace MadsKristensen.EditorExtensions
+//{
+// [Export(typeof(IClassifierProvider))]
+// [ContentType(ScssContentTypeDefinition.ScssContentType)]
+// internal sealed class ScssClassificationProvider : IClassifierProvider
+// {
+// [Import]
+// public IClassificationTypeRegistryService ClassificationRegistryService { get; set; }
+
+// public IClassifier GetClassifier(ITextBuffer textBuffer)
+// {
+// var classifier = ServiceManager.GetService(textBuffer);
+
+// if (classifier == null)
+// classifier = new CssClassifier(textBuffer, new CssClassificationNameProvider(), ClassificationRegistryService);
+
+// return classifier;
+// }
+// }
+//}
diff --git a/EditorExtensions/Classifications/SCSS/ScssContentTypeDefinition.cs b/EditorExtensions/Classifications/SCSS/ScssContentTypeDefinition.cs
new file mode 100644
index 000000000..5a5ca43ce
--- /dev/null
+++ b/EditorExtensions/Classifications/SCSS/ScssContentTypeDefinition.cs
@@ -0,0 +1,31 @@
+//using Microsoft.VisualStudio.Utilities;
+//using System.ComponentModel.Composition;
+
+//namespace MadsKristensen.EditorExtensions
+//{
+// ///
+// /// Exports the ScSS content type and file extension
+// ///
+// public class ScssContentTypeDefinition
+// {
+// public const string ScssLanguageName = "scss";
+// public const string ScssContentType = "scss";
+// public const string ScssFileExtension = ".scss";
+
+// ///
+// /// Exports the SaSS CSS content type
+// ///
+// [Export(typeof(ContentTypeDefinition))]
+// [Name(ScssContentType)]
+// [BaseDefinition("LESS")]
+// public ContentTypeDefinition IScssContentType { get; set; }
+
+// ///
+// /// Exports the SaSS file extension
+// ///
+// [Export(typeof(FileExtensionToContentTypeDefinition))]
+// [ContentType(ScssContentType)]
+// [FileExtension(ScssFileExtension)]
+// public FileExtensionToContentTypeDefinition IScssFileExtension { get; set; }
+// }
+//}
diff --git a/EditorExtensions/Classifications/ValueTaggerProvider.cs b/EditorExtensions/Classifications/ValueTaggerProvider.cs
new file mode 100644
index 000000000..f8223f802
--- /dev/null
+++ b/EditorExtensions/Classifications/ValueTaggerProvider.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Windows.Threading;
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Classification;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IViewTaggerProvider))]
+ [ContentType("css")]
+ [TagType(typeof(TextMarkerTag))]
+ internal class VendorTaggerProvider : IViewTaggerProvider
+ {
+ [Import]
+ internal IClassifierAggregatorService AggregatorFactory = null;
+
+ public ITagger CreateTagger(ITextView textView, ITextBuffer buffer) where T : ITag
+ {
+ if (textView == null || !WESettings.GetBoolean(WESettings.Keys.SyncVendorValues))
+ {
+ return null;
+ }
+
+ if (textView.TextBuffer != buffer)
+ {
+ return null;
+ }
+
+ Func> sc = delegate() { return new VendorTagger(textView, buffer, this) as ITagger; };
+ return buffer.Properties.GetOrCreateSingletonProperty>(sc);
+ }
+ }
+
+ internal class VendorTagger : ITagger
+ {
+ ITextView View { get; set; }
+ ITextBuffer Buffer { get; set; }
+ SnapshotPoint? CurrentChar { get; set; }
+ VendorTaggerProvider Provider { get; set; }
+ readonly IClassifier _classifier;
+ private VendorClassifier _vendorClassifier;
+ private bool _pendingUpdate = false;
+
+ internal VendorTagger(ITextView view, ITextBuffer buffer, VendorTaggerProvider provider)
+ {
+ View = view;
+ Buffer = buffer;
+ CurrentChar = null;
+ Provider = provider;
+ _classifier = provider.AggregatorFactory.GetClassifier(buffer);
+ buffer.Properties.TryGetProperty(typeof(VendorClassifier), out _vendorClassifier);
+
+ View.Caret.PositionChanged += CaretPositionChanged;
+ View.LayoutChanged += ViewLayoutChanged;
+ }
+
+ public event EventHandler TagsChanged;
+
+ void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
+ {
+ if (e.NewSnapshot != e.OldSnapshot)
+ {
+ UpdateAtCaretPosition(View.Caret.Position);
+ }
+ }
+
+ void CaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
+ {
+ UpdateAtCaretPosition(e.NewPosition);
+ }
+
+ void UpdateAtCaretPosition(CaretPosition caretPosition)
+ {
+ if (!_pendingUpdate)
+ {
+ _pendingUpdate = true;
+ CurrentChar = caretPosition.Point.GetPoint(this.Buffer, caretPosition.Affinity);
+ if (!CurrentChar.HasValue)
+ {
+ return;
+ }
+
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => Update()), DispatcherPriority.ContextIdle);
+ }
+ }
+
+ private void Update()
+ {
+ var tempEvent = TagsChanged;
+ if (tempEvent != null)
+ {
+ SnapshotSpan span = new SnapshotSpan(this.Buffer.CurrentSnapshot, 0, this.Buffer.CurrentSnapshot.Length);
+ tempEvent(this, new SnapshotSpanEventArgs(span));
+ _pendingUpdate = false;
+ }
+ }
+
+ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans)
+ {
+ if (spans.Count == 0 || Buffer.CurrentSnapshot.Length == 0)
+ yield break;
+
+ if (!CurrentChar.HasValue || CurrentChar.Value.Position >= CurrentChar.Value.Snapshot.Length)
+ yield break;
+
+ SnapshotPoint currentChar = CurrentChar.Value;
+ if (spans[0].Snapshot != currentChar.Snapshot)
+ {
+ currentChar = currentChar.TranslateTo(spans[0].Snapshot, PointTrackingMode.Positive);
+ }
+
+ var allTags = _vendorClassifier.GetClassificationSpans(spans[0]).Where(s => s.ClassificationType.Classification == ClassificationTypes._value);
+ foreach (var tagSpan in allTags)
+ {
+ if (tagSpan.Span.Contains(currentChar))
+ {
+ Declaration dec = _vendorClassifier.Cache.FirstOrDefault(e => currentChar.Position > e.Start && currentChar.Position < e.AfterEnd);
+ if (dec != null && dec.PropertyName.Text.Length > 0 && !dec.IsVendorSpecific())
+ {
+ foreach (Declaration vendor in _vendorClassifier.Cache.Where(d => d.Parent == dec.Parent && _vendorClassifier.GetStandardName(d) == dec.PropertyName.Text))
+ {
+ // Manage quotes for -ms-filter
+ string value = Buffer.CurrentSnapshot.GetText(vendor.Colon.AfterEnd, vendor.AfterEnd - vendor.Colon.AfterEnd);
+ int quotes = value.StartsWith("'") || value.StartsWith("\"") ? 1 : 0;
+ SnapshotSpan vendorSpan = new SnapshotSpan(Buffer.CurrentSnapshot, vendor.Colon.AfterEnd + quotes, vendor.AfterEnd - vendor.Colon.AfterEnd - (quotes * 2));
+ yield return new TagSpan(vendorSpan, new TextMarkerTag("vendorhighlight"));
+ }
+
+ SnapshotSpan s = tagSpan.Span;
+ yield return new TagSpan(s, new TextMarkerTag("vendorhighlight"));
+ yield break;
+ }
+ }
+ }
+ }
+ }
+
+ [Export(typeof(EditorFormatDefinition))]
+ [Name("vendorhighlight")]
+ [UserVisible(true)]
+ internal class HighlightWordFormatDefinition : MarkerFormatDefinition
+ {
+ public HighlightWordFormatDefinition()
+ {
+ this.DisplayName = "CSS Property Value Highlight";
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Classifications/VendorClassifier.cs b/EditorExtensions/Classifications/VendorClassifier.cs
new file mode 100644
index 000000000..2532e1fb8
--- /dev/null
+++ b/EditorExtensions/Classifications/VendorClassifier.cs
@@ -0,0 +1,235 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Classification;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.Web.Editor;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal static class ClassificationTypes
+ {
+ internal const string _declaration = "vendor.declaration";
+ internal const string _value = "vendor.value";
+
+ [Export, Name(ClassificationTypes._declaration), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
+ internal static ClassificationTypeDefinition VendorDeclarationClassificationType = null;
+
+ [Export, Name(ClassificationTypes._value), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
+ internal static ClassificationTypeDefinition VendorValueClassificationType = null;
+ }
+
+ [Export(typeof(IClassifierProvider))]
+ [ContentType("css")]
+ internal sealed class VendorClassifierProvider : IClassifierProvider
+ {
+ [Import, System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal IClassificationTypeRegistryService Registry { get; set; }
+
+ public IClassifier GetClassifier(ITextBuffer buffer)
+ {
+ return buffer.Properties.GetOrCreateSingletonProperty(() => { return new VendorClassifier(Registry, buffer); });
+ }
+ }
+
+ internal sealed class VendorClassifier : IClassifier
+ {
+ private IClassificationTypeRegistryService _registry;
+ private ITextBuffer _buffer;
+ private CssTree _tree;
+ internal SortedRangeList Cache = new SortedRangeList();
+ private IClassificationType _decClassification;
+ private IClassificationType _valClassification;
+
+ internal VendorClassifier(IClassificationTypeRegistryService registry, ITextBuffer buffer)
+ {
+ _registry = registry;
+ _buffer = buffer;
+ _decClassification = _registry.GetClassificationType(ClassificationTypes._declaration);
+ _valClassification = _registry.GetClassificationType(ClassificationTypes._value);
+ }
+
+ public IList GetClassificationSpans(SnapshotSpan span)
+ {
+ List spans = new List();
+ if (!WESettings.GetBoolean(WESettings.Keys.SyncVendorValues) || !EnsureInitialized())
+ return spans;
+
+ var declarations = Cache.Where(d => span.End <= d.AfterEnd && d.Start >= span.Start);
+ foreach (Declaration dec in Cache.Where(d => d.PropertyName.Text.Length > 0 && span.Snapshot.Length >= d.AfterEnd))
+ {
+ if (dec.IsVendorSpecific())
+ {
+ var ss = new SnapshotSpan(span.Snapshot, dec.Start, dec.Length);
+ var s = new ClassificationSpan(ss, _decClassification);
+ spans.Add(s);
+ }
+
+ int start = dec.Colon.AfterEnd;
+ int length = dec.AfterEnd - start;
+ if (span.Snapshot.Length > start + length)
+ {
+ var ss2 = new SnapshotSpan(span.Snapshot, start, length);
+ var s2 = new ClassificationSpan(ss2, _valClassification);
+ spans.Add(s2);
+ }
+ }
+
+ return spans;
+ }
+
+ public string GetStandardName(Declaration dec)
+ {
+ string name = dec.PropertyName.Text;
+ if (name.Length > 0 && name[0] == '-')
+ {
+ int index = name.IndexOf('-', 1) + 1;
+ name = index > -1 ? name.Substring(index) : name;
+ }
+
+ return name;
+ }
+
+ public bool EnsureInitialized()
+ {
+ if (_tree == null && WebEditor.Host != null)
+ {
+ try
+ {
+ CssEditorDocument document = CssEditorDocument.FromTextBuffer(_buffer);
+ _tree = document.Tree;
+ _tree.TreeUpdated += TreeUpdated;
+ _tree.ItemsChanged += TreeItemsChanged;
+ UpdateDeclarationCache(_tree.StyleSheet);
+ }
+ catch (ArgumentNullException)
+ {
+ }
+ }
+
+ return _tree != null;
+ }
+
+ private void UpdateDeclarationCache(ParseItem item)
+ {
+ var visitor = new CssItemCollector(true);
+ item.Accept(visitor);
+
+ HashSet rules = new HashSet();
+
+ foreach (Declaration dec in visitor.Items)
+ {
+ RuleBlock rule = dec.Parent as RuleBlock;
+ if (rule == null || rules.Contains(rule))
+ continue;
+
+ var vendors = rule.Declarations.Where(d => d.IsValid && d.IsVendorSpecific());
+ foreach (Declaration vendor in vendors)
+ {
+ string name = GetStandardName(vendor);
+ Declaration standard = rule.Declarations.FirstOrDefault(d => d.IsValid && d.PropertyName.Text == name);
+
+ if (standard != null)
+ {
+ if (!Cache.Contains(standard))
+ Cache.Add(standard);
+
+ if (GetValueText(standard) == GetValueText(vendor) && !Cache.Contains(vendor))
+ Cache.Add(vendor);
+ }
+ }
+
+ rules.Add(rule);
+ }
+ }
+
+ private void TreeUpdated(object sender, CssTreeUpdateEventArgs e)
+ {
+ Cache.Clear();
+ UpdateDeclarationCache(e.Tree.StyleSheet);
+ }
+
+ private void TreeItemsChanged(object sender, CssItemsChangedEventArgs e)
+ {
+ foreach (ParseItem item in e.DeletedItems)
+ {
+ if (Cache.Contains(item))
+ Cache.Remove((Declaration)item);
+ }
+
+ foreach (ParseItem item in e.InsertedItems)
+ {
+ UpdateDeclarationCache(item);
+ UpdateVendorValues(item);
+ }
+ }
+
+ private void UpdateVendorValues(ParseItem item)
+ {
+ if (!WESettings.GetBoolean(WESettings.Keys.SyncVendorValues))
+ return;
+
+ Declaration dec = item.FindType();
+ if (dec != null && Cache.Contains(dec) && !dec.IsVendorSpecific())
+ {
+ // Find all vendor specifics that isn't the standard property.
+ var matches = Cache.Where(d => d.IsValid && d != dec && d.Parent == dec.Parent && GetStandardName(d) == dec.PropertyName.Text && d.PropertyName.Text != dec.PropertyName.Text);
+
+ // Undo sometimes messes with the positions, so we have to make this check before proceeding.
+ if (!matches.Any() || dec.Text.Length < dec.Colon.AfterEnd - dec.Start || dec.Colon.AfterEnd < dec.Start)
+ return;
+
+ string text = dec.Text.Substring(dec.Colon.AfterEnd - dec.Start, dec.AfterEnd - dec.Colon.AfterEnd);
+ using (ITextEdit edit = _buffer.CreateEdit())
+ {
+ foreach (Declaration match in matches.Reverse())
+ {
+ SnapshotSpan span = new SnapshotSpan(_buffer.CurrentSnapshot, match.Colon.AfterEnd, match.AfterEnd - match.Colon.AfterEnd);
+ if (span.GetText() != text)
+ edit.Replace(span, text);
+ }
+
+ edit.Apply();
+ }
+ }
+ }
+
+ private string GetValueText(Declaration dec)
+ {
+ int start = dec.Colon.AfterEnd;
+ int length = dec.AfterEnd - start;
+ return _buffer.CurrentSnapshot.GetText(start, length);
+ }
+
+ public event EventHandler ClassificationChanged;
+
+ public void RaiseClassificationChanged(SnapshotSpan span)
+ {
+ var handler = this.ClassificationChanged;
+ if (handler != null)
+ {
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => handler(this, new ClassificationChangedEventArgs(span))), DispatcherPriority.ApplicationIdle);
+ }
+ }
+ }
+
+ [Export(typeof(EditorFormatDefinition))]
+ [UserVisible(true)]
+ [ClassificationType(ClassificationTypeNames = ClassificationTypes._declaration)]
+ [Name(ClassificationTypes._declaration)]
+ [Order(After = Priority.Default)]
+ internal sealed class VendorDeclarationFormatDefinition : ClassificationFormatDefinition
+ {
+ public VendorDeclarationFormatDefinition()
+ {
+ ForegroundOpacity = 0.5;
+ DisplayName = "CSS Vendor Property";
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/ArrowsCommandTarget.cs b/EditorExtensions/Commands/ArrowsCommandTarget.cs
new file mode 100644
index 000000000..4b741d5ef
--- /dev/null
+++ b/EditorExtensions/Commands/ArrowsCommandTarget.cs
@@ -0,0 +1,273 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Editor;
+using Microsoft.VisualStudio.OLE.Interop;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.ComponentModel.Composition;
+using System.Globalization;
+using Editor = Microsoft.Web.Editor;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IVsTextViewCreationListener))]
+ [ContentType(Microsoft.Web.Editor.CssContentTypeDefinition.CssContentType)]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ class NumberTextViewCreationListener : IVsTextViewCreationListener
+ {
+ [Import, System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; }
+
+ public void VsTextViewCreated(IVsTextView textViewAdapter)
+ {
+ var textView = EditorAdaptersFactoryService.GetWpfTextView(textViewAdapter);
+ textView.Properties.GetOrCreateSingletonProperty(() => new NumberTarget(textViewAdapter, textView));
+ }
+ }
+
+ class NumberTarget : IOleCommandTarget
+ {
+ private ITextView _textView;
+ private IOleCommandTarget _nextCommandTarget;
+ private CssTree _tree;
+
+ public NumberTarget(IVsTextView adapter, ITextView textView)
+ {
+ this._textView = textView;
+ adapter.AddCommandFilter(this, out _nextCommandTarget);
+ }
+
+ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (pguidCmdGroup == typeof(VSConstants.VSStd2KCmdID).GUID)
+ {
+ switch (nCmdID)
+ {
+ case 2400:
+ if (Move(Direction.Down))
+ return VSConstants.S_OK;
+ break;
+
+ case 2401:
+ if (Move(Direction.Up))
+ return VSConstants.S_OK;
+ break;
+ }
+ }
+
+ return _nextCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
+ }
+
+ private enum Direction
+ {
+ Up,
+ Down
+ }
+
+ private bool Move(Direction direction)
+ {
+ if (!EnsureInitialized())
+ return false;
+
+ int position = _textView.Caret.Position.BufferPosition.Position;
+
+ ParseItem item = _tree.StyleSheet.ItemBeforePosition(position);
+ if (item == null)
+ return false;
+
+ NumericalValue unit = item.FindType();
+ if (unit != null)
+ {
+ return HandleUnits(direction, unit);
+ }
+
+ HexColorValue hex = item.FindType();
+ if (hex != null)
+ {
+ return HandleHex(direction, hex);
+ }
+
+ return false;
+ }
+
+ private bool HandleUnits(Direction direction, NumericalValue item)
+ {
+ float value;
+ if (!float.TryParse(item.Number.Text, out value))
+ return false;
+
+ if (!AreWithinLimits(direction, value, item))
+ return true;
+
+ var span = new SnapshotSpan(_textView.Selection.SelectedSpans[0].Snapshot, item.Number.Start, item.Number.Length);
+ float delta = GetDelta(item.Number.Text);
+ string format = item.Number.Text.Contains(".") ? "#.#0" : string.Empty;
+ if (NumberDecimalPlaces(item.Number.Text) == 1)
+ format = "F1";
+
+ if (direction == Direction.Down)
+ UpdateSpan(span, (value - delta).ToString(format, CultureInfo.InvariantCulture), "Decrease value");
+ else
+ UpdateSpan(span, (value + delta).ToString(format, CultureInfo.InvariantCulture), "Increase value");
+
+ return true;
+ }
+
+ private static int NumberDecimalPlaces(string value)
+ {
+ if (value.IndexOf('.') == -1)
+ return 0;
+
+ int s = value.IndexOf(".") + 1; // the first numbers plus decimal point
+ return value.Length - s; //total length minus beginning numbers and decimal = number of decimal points
+ }
+
+ private static bool AreWithinLimits(Direction direction, float number, NumericalValue item)
+ {
+ UnitType type = GetUnitType(item);
+ switch (type)
+ {
+ case UnitType.Angle:
+ return (direction == Direction.Up) ? number < 360 : number > -360;
+
+ //case UnitType.Percentage:
+ // return (direction == Direction.Up) ? number < 100 : number > 0;
+
+ // Larger than zero
+ case UnitType.Grid:
+ case UnitType.Frequency:
+ case UnitType.Resolution:
+ case UnitType.Time:
+ return (direction == Direction.Down) ? number > 0 : true;
+
+ case UnitType.Percentage:
+ case UnitType.Length:
+ case UnitType.Viewport:
+ return true;
+ }
+
+ FunctionColor func = item.FindType();
+ if (func != null)
+ {
+ if (func.FunctionName.Text.StartsWith("rgb", StringComparison.Ordinal))
+ {
+ if (direction == Direction.Up)
+ return number < 255;
+ else
+ return number > 0;
+ }
+
+ if (func.FunctionName.Text.StartsWith("hsl", StringComparison.Ordinal))
+ {
+ if (direction == Direction.Up)
+ return number < 360;
+ else
+ return number > 0;
+ }
+ }
+
+ return true;
+ }
+
+ private static UnitType GetUnitType(ParseItem valueItem)
+ {
+ UnitValue unitValue = valueItem as UnitValue;
+
+ return (unitValue != null) ? unitValue.UnitType : UnitType.Unknown;
+ }
+
+ private bool HandleHex(Direction direction, HexColorValue item)
+ {
+ var model = ColorParser.TryParseColor(item.Text, ColorParser.Options.None);
+
+ if (model != null)
+ {
+ var span = new SnapshotSpan(_textView.Selection.SelectedSpans[0].Snapshot, item.Start, item.Length);
+
+ if (direction == Direction.Down && model.HslLightness > 0)
+ {
+ model.Format = Editor.ColorFormat.RgbHex3;
+ UpdateSpan(span, Editor.ColorFormatter.FormatColor(model.Darken()), "Darken color");
+ }
+ else if (direction == Direction.Up && model.HslLightness < 1)
+ {
+ model.Format = Editor.ColorFormat.RgbHex3;
+ UpdateSpan(span, Editor.ColorFormatter.FormatColor(model.Brighten()), "Brighten color");
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private static float GetDelta(string value)
+ {
+ int decimals = NumberDecimalPlaces(value);
+ if (decimals > 0)
+ {
+ if (decimals > 1)
+ return 0.01F;
+ else
+ return 0.1F;
+ }
+
+ return 1F;
+ }
+
+ private void UpdateSpan(SnapshotSpan span, string result, string undoTitle)
+ {
+ if (result.Length > 1)
+ result = result.TrimStart('0');
+
+ using (ITextEdit edit = _textView.TextBuffer.CreateEdit())
+ {
+ EditorExtensionsPackage.DTE.UndoContext.Open(undoTitle);
+ edit.Replace(span, result);
+ edit.Apply();
+ EditorExtensionsPackage.DTE.UndoContext.Close();
+ }
+ }
+
+ public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
+ {
+ if (pguidCmdGroup == typeof(VSConstants.VSStd2KCmdID).GUID)
+ {
+ for (int i = 0; i < cCmds; i++)
+ {
+ switch (prgCmds[i].cmdID)
+ {
+ case 2401: // Up
+ case 2400: // Down
+ prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED);
+ return VSConstants.S_OK;
+ }
+ }
+ }
+
+ return _nextCommandTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
+ }
+
+ public bool EnsureInitialized()
+ {
+ if (_tree == null && Microsoft.Web.Editor.WebEditor.Host != null)
+ {
+ try
+ {
+ CssEditorDocument document = CssEditorDocument.FromTextBuffer(_textView.TextBuffer);
+ _tree = document.Tree;
+ }
+ catch (ArgumentNullException)
+ {
+ }
+ }
+
+ return _tree != null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/CommandTargetBase.cs b/EditorExtensions/Commands/CommandTargetBase.cs
new file mode 100644
index 000000000..4d2c7dcb5
--- /dev/null
+++ b/EditorExtensions/Commands/CommandTargetBase.cs
@@ -0,0 +1,67 @@
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.OLE.Interop;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.Linq;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal abstract class CommandTargetBase : IOleCommandTarget
+ {
+ private IOleCommandTarget _nextCommandTarget;
+ protected IWpfTextView TextView;
+
+ public Guid CommandGroup { get; set; }
+ public uint[] CommandIds { get; set; }
+
+ public CommandTargetBase(IVsTextView adapter, IWpfTextView textView, Guid commandGroup, params uint[] commandIds)
+ {
+ this.CommandGroup = commandGroup;
+ this.CommandIds = commandIds;
+ this.TextView = textView;
+ adapter.AddCommandFilter(this, out _nextCommandTarget);
+ }
+
+ protected abstract bool IsEnabled();
+ protected abstract bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut);
+
+ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (pguidCmdGroup == this.CommandGroup && this.CommandIds.Contains(nCmdID))
+ {
+ bool result = Execute(nCmdID, nCmdexecopt, pvaIn, pvaOut);
+
+ if (result)
+ {
+ return VSConstants.S_OK;
+ }
+ }
+
+ return _nextCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
+ }
+
+ public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
+ {
+ if (pguidCmdGroup == CommandGroup)
+ {
+ for (int i = 0; i < cCmds; i++)
+ {
+ if (CommandIds.Contains(prgCmds[i].cmdID))
+ {
+ if (IsEnabled())
+ {
+ prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED);
+ return VSConstants.S_OK;
+ }
+
+ prgCmds[0].cmdf = (uint)OLECMDF.OLECMDF_SUPPORTED;// | (uint)OLECMDF.OLECMDF_INVISIBLE;
+ //return VSConstants.S_OK;
+ }
+ }
+ }
+
+ return _nextCommandTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/Css/CssAddMissingStandardCommandTarget.cs b/EditorExtensions/Commands/Css/CssAddMissingStandardCommandTarget.cs
new file mode 100644
index 000000000..cb822fa46
--- /dev/null
+++ b/EditorExtensions/Commands/Css/CssAddMissingStandardCommandTarget.cs
@@ -0,0 +1,105 @@
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.CSS.Editor.Schemas;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class CssAddMissingStandard : CommandTargetBase
+ {
+ private DTE2 _dte;
+ private readonly string[] _supported = new[] { "CSS", "LESS" };
+
+ public CssAddMissingStandard(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, GuidList.guidCssCmdSet, PkgCmdIDList.addMissingStandard)
+ {
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ ITextBuffer buffer = TextView.TextBuffer;
+ CssEditorDocument doc = new CssEditorDocument(buffer);
+ ICssSchemaInstance rootSchema = CssSchemaManager.SchemaManager.GetSchemaRoot(null);
+
+ StringBuilder sb = new StringBuilder(buffer.CurrentSnapshot.Length);
+ sb.Append(buffer.CurrentSnapshot.GetText());
+
+ EditorExtensionsPackage.DTE.UndoContext.Open("Add Missing Standard Property");
+
+ string result = AddMissingStandardDeclaration(sb, doc, rootSchema);
+ Span span = new Span(0, buffer.CurrentSnapshot.Length);
+ buffer.Replace(span, result);
+
+ var selection = EditorExtensionsPackage.DTE.ActiveDocument.Selection as TextSelection;
+ selection.GotoLine(1);
+
+ EditorExtensionsPackage.DTE.ExecuteCommand("Edit.FormatDocument");
+ EditorExtensionsPackage.DTE.UndoContext.Close();
+
+ return true;
+ }
+
+ private string AddMissingStandardDeclaration(StringBuilder sb, CssEditorDocument doc, ICssSchemaInstance rootSchema)
+ {
+ var visitor = new CssItemCollector(true);
+ doc.Tree.StyleSheet.Accept(visitor);
+
+ //var items = visitor.Items.Where(d => d.IsValid && d.IsVendorSpecific());
+ foreach (RuleBlock rule in visitor.Items.Reverse())
+ {
+ HashSet list = new HashSet();
+ foreach (Declaration dec in rule.Declarations.Where(d => d.IsValid && d.IsVendorSpecific()).Reverse())
+ {
+ ICssSchemaInstance schema = CssSchemaManager.SchemaManager.GetSchemaForItem(rootSchema, dec);
+ ICssCompletionListEntry entry = VendorHelpers.GetMatchingStandardEntry(dec, schema);
+
+ if (entry != null && !list.Contains(entry.DisplayText) && !rule.Declarations.Any(d => d.PropertyName != null && d.PropertyName.Text == entry.DisplayText))
+ {
+ int index = dec.Text.IndexOf(":", StringComparison.Ordinal);
+ string standard = entry.DisplayText + dec.Text.Substring(index);
+
+ sb.Insert(dec.AfterEnd, standard);
+ list.Add(entry.DisplayText);
+ }
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ private string GetVendorDeclarations(IEnumerable prefixes, Declaration declaration)
+ {
+ StringBuilder sb = new StringBuilder();
+ string separator = true ? Environment.NewLine : " ";
+
+ foreach (var entry in prefixes)
+ {
+ sb.Append(entry + declaration.Text + separator);
+ }
+
+ return sb.ToString();
+ }
+
+ protected override bool IsEnabled()
+ {
+ var buffer = ProjectHelpers.GetCurentTextBuffer();
+
+ if (buffer != null && _supported.Contains(buffer.ContentType.DisplayName.ToUpperInvariant()))
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/Css/CssAddVendorStandardCommandTarget.cs b/EditorExtensions/Commands/Css/CssAddVendorStandardCommandTarget.cs
new file mode 100644
index 000000000..3fb975d34
--- /dev/null
+++ b/EditorExtensions/Commands/Css/CssAddVendorStandardCommandTarget.cs
@@ -0,0 +1,100 @@
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Schemas;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class CssAddMissingVendor : CommandTargetBase
+ {
+ private DTE2 _dte;
+ private readonly string[] _supported = new[] { "CSS", "LESS" };
+
+ public CssAddMissingVendor(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, GuidList.guidCssCmdSet, PkgCmdIDList.addMissingVendor)
+ {
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ ITextBuffer buffer = ProjectHelpers.GetCurentTextBuffer();
+ CssEditorDocument doc = new CssEditorDocument(buffer);
+ ICssSchemaInstance rootSchema = CssSchemaManager.SchemaManager.GetSchemaRoot(null);
+
+ StringBuilder sb = new StringBuilder(buffer.CurrentSnapshot.Length);
+ sb.Append(buffer.CurrentSnapshot.GetText());
+
+ EditorExtensionsPackage.DTE.UndoContext.Open("Add Missing Vendor Specifics");
+
+ string result = AddMissingVendorDeclarations(sb, doc, rootSchema);
+ Span span = new Span(0, buffer.CurrentSnapshot.Length);
+ buffer.Replace(span, result);
+
+ var selection = EditorExtensionsPackage.DTE.ActiveDocument.Selection as TextSelection;
+ selection.GotoLine(1);
+
+ EditorExtensionsPackage.DTE.ExecuteCommand("Edit.FormatDocument");
+ EditorExtensionsPackage.DTE.UndoContext.Close();
+
+ return true;
+ }
+
+ private string AddMissingVendorDeclarations(StringBuilder sb, CssEditorDocument doc, ICssSchemaInstance rootSchema)
+ {
+ var visitor = new CssItemCollector(true);
+ doc.Tree.StyleSheet.Accept(visitor);
+
+ var items = visitor.Items.Where(d => d.IsValid && !d.IsVendorSpecific() && d.PropertyName.Text != "filter");
+
+ foreach (Declaration dec in items.Reverse())
+ {
+ ICssSchemaInstance schema = CssSchemaManager.SchemaManager.GetSchemaForItem(rootSchema, dec);
+ var missingEntries = dec.GetMissingVendorSpecifics(schema);
+
+ if (missingEntries.Any())
+ {
+ var missingPrefixes = missingEntries.Select(e => e.Substring(0, e.IndexOf('-', 1) + 1));
+ string vendors = GetVendorDeclarations(missingPrefixes, dec);
+
+ sb.Insert(dec.Start, vendors);
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ private string GetVendorDeclarations(IEnumerable prefixes, Declaration declaration)
+ {
+ StringBuilder sb = new StringBuilder();
+ string separator = true ? Environment.NewLine : " ";
+
+ foreach (var entry in prefixes)
+ {
+ sb.Append(entry + declaration.Text + separator);
+ }
+
+ return sb.ToString();
+ }
+
+ protected override bool IsEnabled()
+ {
+ var buffer = ProjectHelpers.GetCurentTextBuffer();
+
+ if (buffer != null && _supported.Contains(buffer.ContentType.DisplayName.ToUpperInvariant()))
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/Css/CssCreationListener.cs b/EditorExtensions/Commands/Css/CssCreationListener.cs
new file mode 100644
index 000000000..f4c5f2b00
--- /dev/null
+++ b/EditorExtensions/Commands/Css/CssCreationListener.cs
@@ -0,0 +1,53 @@
+using Microsoft.VisualStudio.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text.Classification;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.Web.Editor;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IVsTextViewCreationListener))]
+ [ContentType(CssContentTypeDefinition.CssContentType)]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ class CssSortPropertiesViewCreationListener : IVsTextViewCreationListener
+ {
+ [Import, System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; }
+
+ [Import]
+ internal IClassifierAggregatorService AggregatorService { get; set; }
+
+ [Import]
+ internal ICompletionBroker CompletionBroker { get; set; }
+
+ [Import]
+ internal IQuickInfoBroker QuickInfoBroker { get; set; }
+
+ public void VsTextViewCreated(IVsTextView textViewAdapter)
+ {
+ var textView = EditorAdaptersFactoryService.GetWpfTextView(textViewAdapter);
+
+ textView.Properties.GetOrCreateSingletonProperty(() => new CssSortProperties(textViewAdapter, textView));
+ textView.Properties.GetOrCreateSingletonProperty(() => new CssExtractToFile(textViewAdapter, textView));
+ textView.Properties.GetOrCreateSingletonProperty(() => new CssAddMissingStandard(textViewAdapter, textView));
+ textView.Properties.GetOrCreateSingletonProperty(() => new CssAddMissingVendor(textViewAdapter, textView));
+ textView.Properties.GetOrCreateSingletonProperty(() => new CssRemoveDuplicates(textViewAdapter, textView));
+ textView.Properties.GetOrCreateSingletonProperty(() => new MinifySelection(textViewAdapter, textView));
+ textView.Properties.GetOrCreateSingletonProperty(() => new CssFindReferences(textViewAdapter, textView));
+ textView.Properties.GetOrCreateSingletonProperty(() => new F1Help(textViewAdapter, textView));
+ textView.Properties.GetOrCreateSingletonProperty(() => new CssSelectBrowsers(textViewAdapter, textView));
+ textView.Properties.GetOrCreateSingletonProperty(() => new RetriggerTarget(textViewAdapter, textView, CompletionBroker));
+
+ uint cssFormatProperties;
+ EditorExtensionsPackage.PriorityCommandTarget.RegisterPriorityCommandTarget(0, new CssFormatProperties(textView), out cssFormatProperties);
+ textView.Closed += delegate { EditorExtensionsPackage.PriorityCommandTarget.UnregisterPriorityCommandTarget(cssFormatProperties); };
+
+ uint cssSpeedTyping;
+ EditorExtensionsPackage.PriorityCommandTarget.RegisterPriorityCommandTarget(0, new SpeedTypingTarget(this, textViewAdapter, textView), out cssSpeedTyping);
+ textView.Closed += delegate { EditorExtensionsPackage.PriorityCommandTarget.UnregisterPriorityCommandTarget(cssSpeedTyping); };
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/Css/CssExtractToFileCommandTarget.cs b/EditorExtensions/Commands/Css/CssExtractToFileCommandTarget.cs
new file mode 100644
index 000000000..62979a973
--- /dev/null
+++ b/EditorExtensions/Commands/Css/CssExtractToFileCommandTarget.cs
@@ -0,0 +1,118 @@
+using EnvDTE80;
+using Microsoft.VisualBasic;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Projection;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Windows;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class CssExtractToFile : CommandTargetBase
+ {
+ private DTE2 _dte;
+ private List _possible = new List() { ".CSS", ".LESS", ".JS", ".TS" };
+
+ public CssExtractToFile(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, GuidList.guidExtractCmdSet, PkgCmdIDList.ExtractSelection)
+ {
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (TextView == null)
+ return false;
+
+ string content = TextView.Selection.SelectedSpans[0].GetText();
+ string extension = Path.GetExtension(_dte.ActiveDocument.FullName).ToLowerInvariant();
+
+ if (!_possible.Contains(extension.ToUpperInvariant()))
+ {
+ extension = ".css";
+ }
+
+ string name = Interaction.InputBox("Specify the name of the file", "Web Essentials", "file1" + extension).Trim();
+
+ if (!string.IsNullOrEmpty(name))
+ {
+
+ if (string.IsNullOrEmpty(Path.GetExtension(name)))
+ {
+ name = name + extension;
+ }
+
+ string fileName = Path.Combine(Path.GetDirectoryName(_dte.ActiveDocument.FullName), name);
+
+ if (!File.Exists(fileName))
+ {
+ _dte.UndoContext.Open("Extract to file...");
+
+ using (StreamWriter writer = new StreamWriter(fileName, false, new UTF8Encoding(true)))
+ {
+ writer.Write(content);
+ }
+
+ ProjectHelpers.AddFileToActiveProject(fileName);
+ TextView.TextBuffer.Delete(TextView.Selection.SelectedSpans[0].Span);
+ _dte.ItemOperations.OpenFile(fileName);
+
+ _dte.UndoContext.Close();
+ }
+ else
+ {
+ MessageBox.Show("The file already exist", "Web Essentials", MessageBoxButton.OK, MessageBoxImage.Warning);
+ }
+ }
+
+ return true;
+ }
+
+ private bool IsValidTextBuffer(IWpfTextView view)
+ {
+ var projection = view.TextBuffer as IProjectionBuffer;
+
+ if (projection != null)
+ {
+ int position = view.Caret.Position.BufferPosition.Position;
+ var snapshotPoint = view.Caret.Position.BufferPosition;
+
+ var buffers = projection.SourceBuffers.Where(s =>
+ s.ContentType.IsOfType("css") ||
+ s.ContentType.IsOfType("javascript"));
+
+ foreach (ITextBuffer buffer in buffers)
+ {
+ SnapshotPoint? point = view.BufferGraph.MapDownToBuffer(snapshotPoint, PointTrackingMode.Negative, buffer, PositionAffinity.Predecessor);
+
+ if (point.HasValue)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ protected override bool IsEnabled()
+ {
+ var item = _dte.Solution.FindProjectItem(_dte.ActiveDocument.FullName);
+ bool hasProject = item != null && item.ContainingProject != null && !string.IsNullOrEmpty(item.ContainingProject.FullName);
+
+ if (hasProject && TextView != null && IsValidTextBuffer(TextView) && TextView.Selection.SelectedSpans.Count > 0)
+ {
+ return TextView.Selection.SelectedSpans[0].Length > 0;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/Css/CssFindReferencesCommandTarget.cs b/EditorExtensions/Commands/Css/CssFindReferencesCommandTarget.cs
new file mode 100644
index 000000000..7bb93b0b5
--- /dev/null
+++ b/EditorExtensions/Commands/Css/CssFindReferencesCommandTarget.cs
@@ -0,0 +1,98 @@
+using CssSorter;
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.IO;
+using System.Linq;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class CssFindReferences : CommandTargetBase
+ {
+ private DTE2 _dte;
+ private CssTree _tree;
+
+ public CssFindReferences(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, typeof(VSConstants.VSStd97CmdID).GUID, (uint)VSConstants.VSStd97CmdID.FindReferences)
+ {
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (!EnsureInitialized())
+ return false;
+
+ int position = TextView.Caret.Position.BufferPosition.Position;
+ ParseItem item = _tree.StyleSheet.ItemBeforePosition(position);
+
+ if (item != null && item.Parent != null)
+ {
+ Find2 find = (Find2)EditorExtensionsPackage.DTE.Find;
+ string types = find.FilesOfType;
+ bool matchCase = find.MatchCase;
+ bool matchWord = find.MatchWholeWord;
+
+ find.WaitForFindToComplete = false;
+ find.Action = EnvDTE.vsFindAction.vsFindActionFindAll;
+ find.Backwards = false;
+ find.MatchInHiddenText = true;
+ find.MatchWholeWord = true;
+ find.MatchCase = true;
+ find.PatternSyntax = EnvDTE.vsFindPatternSyntax.vsFindPatternSyntaxLiteral;
+ find.ResultsLocation = EnvDTE.vsFindResultsLocation.vsFindResults1;
+ find.SearchSubfolders = true;
+ find.FilesOfType = "*.css;*.less;*.scss;*.sass";
+ find.Target = EnvDTE.vsFindTarget.vsFindTargetSolution;
+ find.FindWhat = SearchText(item);
+ find.Execute();
+
+ find.FilesOfType = types;
+ find.MatchCase = matchCase;
+ find.MatchWholeWord = matchWord;
+ }
+
+ return true;
+ }
+
+ private string SearchText(ParseItem item)
+ {
+ if (item.Parent is Declaration)
+ {
+ return item.Text;
+ }
+ else if (item.Parent is AtDirective)
+ {
+ return "@" + item.Text;
+ }
+
+ return item.Parent.Text;
+ }
+
+ public bool EnsureInitialized()
+ {
+ if (_tree == null)
+ {
+ try
+ {
+ CssEditorDocument document = CssEditorDocument.FromTextBuffer(TextView.TextBuffer);
+ _tree = document.Tree;
+ }
+ catch (ArgumentNullException)
+ { }
+ }
+
+ return _tree != null;
+ }
+
+ protected override bool IsEnabled()
+ {
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/Css/CssFormatCommandTarget.cs b/EditorExtensions/Commands/Css/CssFormatCommandTarget.cs
new file mode 100644
index 000000000..793165d62
--- /dev/null
+++ b/EditorExtensions/Commands/Css/CssFormatCommandTarget.cs
@@ -0,0 +1,56 @@
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.OLE.Interop;
+using Microsoft.VisualStudio.Text.Editor;
+using System;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class CssFormatProperties : IOleCommandTarget
+ {
+ private ITextView _textView;
+
+ public CssFormatProperties(ITextView textView)
+ {
+ this._textView = textView;
+ }
+
+ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (pguidCmdGroup == typeof(VSConstants.VSStd2KCmdID).GUID)
+ {
+ switch (nCmdID)
+ {
+ case (uint)VSConstants.VSStd2KCmdID.FORMATSELECTION:
+ case (uint)VSConstants.VSStd2KCmdID.FORMATDOCUMENT:
+ if (_textView.TextBuffer.ContentType.IsOfType("SCSS"))
+ {
+ return VSConstants.S_OK;
+ }
+
+ break;
+ }
+ }
+
+ return (int)(Constants.MSOCMDERR_E_FIRST);
+ }
+
+ public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
+ {
+ if (pguidCmdGroup == typeof(VSConstants.VSStd2KCmdID).GUID)
+ {
+ for (int i = 0; i < cCmds; i++)
+ {
+ switch (prgCmds[i].cmdID)
+ {
+ case (uint)VSConstants.VSStd2KCmdID.FORMATSELECTION:
+ case (uint)VSConstants.VSStd2KCmdID.FORMATDOCUMENT:
+ prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED);
+ return VSConstants.S_OK;
+ }
+ }
+ }
+
+ return (int)(Constants.OLECMDERR_E_NOTSUPPORTED);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/Css/CssRemoveDuplicatesCommandTarget.cs b/EditorExtensions/Commands/Css/CssRemoveDuplicatesCommandTarget.cs
new file mode 100644
index 000000000..35204db17
--- /dev/null
+++ b/EditorExtensions/Commands/Css/CssRemoveDuplicatesCommandTarget.cs
@@ -0,0 +1,98 @@
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class CssRemoveDuplicates : CommandTargetBase
+ {
+ private DTE2 _dte;
+ private readonly string[] _supported = new[] { "CSS", "LESS" };
+
+ public CssRemoveDuplicates(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, GuidList.guidCssCmdSet, PkgCmdIDList.cssRemoveDuplicates)
+ {
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ ITextBuffer buffer = ProjectHelpers.GetCurentTextBuffer();
+ CssEditorDocument doc = new CssEditorDocument(buffer);
+
+ StringBuilder sb = new StringBuilder(buffer.CurrentSnapshot.Length);
+ sb.Append(buffer.CurrentSnapshot.GetText());
+
+ EditorExtensionsPackage.DTE.UndoContext.Open("Remove Duplicate Properties");
+
+ string result = RemoveDuplicateProperties(sb, doc);
+ Span span = new Span(0, buffer.CurrentSnapshot.Length);
+ buffer.Replace(span, result);
+
+ var selection = EditorExtensionsPackage.DTE.ActiveDocument.Selection as TextSelection;
+ selection.GotoLine(1);
+
+ EditorExtensionsPackage.DTE.ExecuteCommand("Edit.FormatDocument");
+ EditorExtensionsPackage.DTE.UndoContext.Close();
+
+ return true;
+ }
+
+ private string RemoveDuplicateProperties(StringBuilder sb, CssEditorDocument doc)
+ {
+ var visitor = new CssItemCollector(true);
+ doc.Tree.StyleSheet.Accept(visitor);
+
+ foreach (RuleBlock rule in visitor.Items.Reverse())
+ {
+ HashSet list = new HashSet();
+
+ foreach (Declaration dec in rule.Declarations.Reverse())
+ {
+ if (list.Contains(dec.Text))
+ {
+ sb.Remove(dec.Start, dec.Length);
+ continue;
+ }
+
+ list.Add(dec.Text);
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ private string GetVendorDeclarations(IEnumerable prefixes, Declaration declaration)
+ {
+ StringBuilder sb = new StringBuilder();
+ string separator = true ? Environment.NewLine : " ";
+
+ foreach (var entry in prefixes)
+ {
+ sb.Append(entry + declaration.Text + separator);
+ }
+
+ return sb.ToString();
+ }
+
+ protected override bool IsEnabled()
+ {
+ var buffer = ProjectHelpers.GetCurentTextBuffer();
+
+ if (buffer != null && _supported.Contains(buffer.ContentType.DisplayName.ToUpperInvariant()))
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/Css/CssSaveListener.cs b/EditorExtensions/Commands/Css/CssSaveListener.cs
new file mode 100644
index 000000000..1c2081d89
--- /dev/null
+++ b/EditorExtensions/Commands/Css/CssSaveListener.cs
@@ -0,0 +1,70 @@
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IWpfTextViewCreationListener))]
+ [ContentType(Microsoft.Web.Editor.CssContentTypeDefinition.CssContentType)]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ public class CssSaveListener : IWpfTextViewCreationListener
+ {
+ private ITextDocument _document;
+
+ public void TextViewCreated(IWpfTextView textView)
+ {
+ textView.TextDataModel.DocumentBuffer.Properties.TryGetProperty(typeof(ITextDocument), out _document);
+
+ if (_document != null)
+ {
+ _document.FileActionOccurred += document_FileActionOccurred;
+ }
+ }
+
+ void document_FileActionOccurred(object sender, TextDocumentFileActionEventArgs e)
+ {
+ if (!WESettings.GetBoolean(WESettings.Keys.EnableCssMinification))
+ return;
+
+ if (e.FileActionType == FileActionTypes.ContentSavedToDisk && e.FilePath.EndsWith(".css"))
+ {
+ string minFile = e.FilePath.Insert(e.FilePath.Length - 3, "min.");
+
+ if (File.Exists(minFile) && EditorExtensionsPackage.DTE.Solution.FindProjectItem(minFile) != null)
+ {
+ Task.Run(() =>
+ {
+ Minify(e.FilePath, minFile);
+ });
+ }
+ }
+ }
+
+ public static void Minify(string file, string minFile)
+ {
+ if (file.EndsWith(".min.css"))
+ return;
+
+ try
+ {
+ string content = MinifyFileMenu.MinifyString(".css", File.ReadAllText(file));
+ //Minifier minifier = new Minifier();
+ //string content = minifier.MinifyStyleSheet(File.ReadAllText(file));
+
+ ProjectHelpers.CheckOutFileFromSourceControl(minFile);
+ using (StreamWriter writer = new StreamWriter(minFile, false, new UTF8Encoding(true)))
+ {
+ writer.Write(content);
+ }
+ }
+ catch
+ {
+ Logger.Log("Error minifying: " + file);
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/Css/CssSelectBrowsers.cs b/EditorExtensions/Commands/Css/CssSelectBrowsers.cs
new file mode 100644
index 000000000..a0493b1a9
--- /dev/null
+++ b/EditorExtensions/Commands/Css/CssSelectBrowsers.cs
@@ -0,0 +1,71 @@
+using EnvDTE80;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Projection;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class CssSelectBrowsers : CommandTargetBase
+ {
+ private DTE2 _dte;
+ private List _possible = new List() { ".CSS", ".LESS", ".SCSS" };
+
+ public CssSelectBrowsers(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, GuidList.guidMinifyCmdSet, PkgCmdIDList.SelectBrowsers)
+ {
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ BrowserSelector selector = new BrowserSelector();
+ selector.ShowDialog();
+
+ return true;
+ }
+
+ private bool IsValidTextBuffer(IWpfTextView view)
+ {
+ var projection = view.TextBuffer as IProjectionBuffer;
+
+ if (projection != null)
+ {
+ int position = view.Caret.Position.BufferPosition.Position;
+ var snapshotPoint = view.Caret.Position.BufferPosition;
+
+ var buffers = projection.SourceBuffers.Where(s => s.ContentType.IsOfType("css"));
+
+ foreach (ITextBuffer buffer in buffers)
+ {
+ SnapshotPoint? point = view.BufferGraph.MapDownToBuffer(snapshotPoint, PointTrackingMode.Negative, buffer, PositionAffinity.Predecessor);
+
+ if (point.HasValue)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ protected override bool IsEnabled()
+ {
+ var item = _dte.Solution.FindProjectItem(_dte.ActiveDocument.FullName);
+ bool hasProject = item != null && item.ContainingProject != null && !string.IsNullOrEmpty(item.ContainingProject.FullName);
+
+ if (hasProject && TextView != null && IsValidTextBuffer(TextView))
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/Css/CssSortPropertiesCommandTarget.cs b/EditorExtensions/Commands/Css/CssSortPropertiesCommandTarget.cs
new file mode 100644
index 000000000..b88a4bd6b
--- /dev/null
+++ b/EditorExtensions/Commands/Css/CssSortPropertiesCommandTarget.cs
@@ -0,0 +1,69 @@
+using CssSorter;
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.IO;
+using System.Linq;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class CssSortProperties : CommandTargetBase
+ {
+ private DTE2 _dte;
+ private readonly string[] _supported = new[] { "CSS", "LESS" };
+ //private static uint[] _commandIds = new uint[] { PkgCmdIDList.sortCssProperties };
+
+ public CssSortProperties(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, GuidList.guidCssCmdSet, PkgCmdIDList.sortCssProperties)
+ {
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ TextDocument doc = _dte.ActiveDocument.Object("TextDocument") as TextDocument;
+ EditPoint edit = doc.StartPoint.CreateEditPoint();
+ string text = SortProperties(edit.GetText(doc.EndPoint));
+
+ _dte.UndoContext.Open("Sort All Properties");
+
+ edit.ReplaceText(doc.EndPoint, text, (int)vsFindOptions.vsFindOptionsNone);
+ EditorExtensionsPackage.DTE.ExecuteCommand("Edit.FormatDocument");
+ doc.Selection.MoveToPoint(doc.StartPoint);
+
+ _dte.UndoContext.Close();
+
+ return true;
+ }
+
+ private string SortProperties(string text)
+ {
+ Sorter sorter = new Sorter();
+
+ if (Path.GetExtension(_dte.ActiveDocument.FullName) == ".css")
+ {
+ return sorter.SortStyleSheet(text);
+ }
+ else if (Path.GetExtension(_dte.ActiveDocument.FullName) == ".less")
+ {
+ return sorter.SortLess(text);
+ }
+
+ return text;
+ }
+
+ protected override bool IsEnabled()
+ {
+ var buffer = ProjectHelpers.GetCurentTextBuffer();
+
+ if (buffer != null && _supported.Contains(buffer.ContentType.DisplayName.ToUpperInvariant()))
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/Css/F1HelpCommandTarget.cs b/EditorExtensions/Commands/Css/F1HelpCommandTarget.cs
new file mode 100644
index 000000000..36e02cc7b
--- /dev/null
+++ b/EditorExtensions/Commands/Css/F1HelpCommandTarget.cs
@@ -0,0 +1,111 @@
+using EnvDTE80;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.CSS.Editor.Schemas;
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class F1Help : CommandTargetBase
+ {
+ private CssTree _tree;
+
+ public F1Help(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, typeof(VSConstants.VSStd97CmdID).GUID, (uint)VSConstants.VSStd97CmdID.F1Help)
+ { }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (!EnsureInitialized())
+ return false;
+
+ int position = TextView.Caret.Position.BufferPosition.Position;
+ ParseItem item = _tree.StyleSheet.ItemBeforePosition(position);
+
+ if (item == null)
+ return false;
+
+ return SchemaLookup(item);
+ }
+
+ private delegate ICssCompletionListEntry Reference(string name);
+
+ private bool SchemaLookup(ParseItem item)
+ {
+ if (item is ClassSelector || item is IdSelector || item is ItemName || item.Parent is RuleBlock || item.Parent is StyleSheet)
+ return false;
+
+ ICssSchemaInstance schema = CssSchemaManager.SchemaManager.GetSchemaRootForBuffer(TextView.TextBuffer);
+
+ Declaration dec = item.FindType();
+ if (dec != null && dec.PropertyName != null)
+ return OpenReferenceUrl(schema.GetProperty, dec.PropertyName.Text, "http://realworldvalidator.com/css/properties/");
+
+ PseudoClassFunctionSelector pseudoClassFunction = item.FindType();
+ if (pseudoClassFunction != null)
+ return OpenReferenceUrl(schema.GetPseudo, pseudoClassFunction.Colon.Text + pseudoClassFunction.Function.FunctionName.Text + ")", "http://realworldvalidator.com/css/pseudoclasses/");
+
+ PseudoElementFunctionSelector pseudoElementFunction = item.FindType();
+ if (pseudoElementFunction != null)
+ return OpenReferenceUrl(schema.GetPseudo, pseudoElementFunction.DoubleColon.Text + pseudoElementFunction.Function.FunctionName.Text + ")", "http://realworldvalidator.com/css/pseudoelements/");
+
+ PseudoElementSelector pseudoElement = item.FindType();
+ if (pseudoElement != null && pseudoElement.PseudoElement != null)
+ return OpenReferenceUrl(schema.GetPseudo, pseudoElement.DoubleColon.Text + pseudoElement.PseudoElement.Text, "http://realworldvalidator.com/css/pseudoelements/");
+
+ PseudoClassSelector pseudoClass = item.FindType();
+ if (pseudoClass != null && pseudoClass.PseudoClass != null)
+ return OpenReferenceUrl(schema.GetPseudo, pseudoClass.Colon.Text + pseudoClass.PseudoClass.Text, "http://realworldvalidator.com/css/pseudoclasses/");
+
+ AtDirective directive = item.FindType();
+ if (directive != null)
+ return OpenReferenceUrl(schema.GetAtDirective, directive.At.Text + directive.Keyword.Text, "http://realworldvalidator.com/css/atdirectives/");
+
+ return false;
+ }
+
+ private bool OpenReferenceUrl(Reference reference, string name, string baseUrl)
+ {
+ ICssCompletionListEntry entry = reference.Invoke(name);
+ if (entry != null)
+ {
+ string standardsReference = entry.GetAttribute("standard-reference");
+ string text = entry.DisplayText;
+ Uri url;
+
+ if (Uri.TryCreate(baseUrl + text, UriKind.Absolute, out url))
+ {
+ System.Diagnostics.Process.Start(url.ToString());
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public bool EnsureInitialized()
+ {
+ if (_tree == null && Microsoft.Web.Editor.WebEditor.Host != null)
+ {
+ try
+ {
+ CssEditorDocument document = CssEditorDocument.FromTextBuffer(TextView.TextBuffer);
+ _tree = document.Tree;
+ }
+ catch (ArgumentNullException)
+ { }
+ }
+
+ return _tree != null;
+ }
+
+ protected override bool IsEnabled()
+ {
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/Css/RetriggerCommandTarget.cs b/EditorExtensions/Commands/Css/RetriggerCommandTarget.cs
new file mode 100644
index 000000000..f99d9156d
--- /dev/null
+++ b/EditorExtensions/Commands/Css/RetriggerCommandTarget.cs
@@ -0,0 +1,62 @@
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.OLE.Interop;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.ComponentModel.Composition;
+using System.Runtime.InteropServices;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class RetriggerTarget : IOleCommandTarget
+ {
+ private ITextView _textView;
+ private IOleCommandTarget _nextCommandTarget;
+ private ICompletionBroker _broker;
+
+ public RetriggerTarget(IVsTextView adapter, ITextView textView, ICompletionBroker broker)
+ {
+ _textView = textView;
+ _broker = broker;
+ adapter.AddCommandFilter(this, out _nextCommandTarget);
+ }
+
+ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR)
+ {
+ char typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
+
+ switch (typedChar)
+ {
+ case '!':
+ case '(':
+ case '/':
+ Retrigger();
+ break;
+ }
+ }
+
+ return _nextCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
+ }
+
+ private void Retrigger()
+ {
+ Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
+ {
+ CssCompletionController.FromView(_textView).OnShowMemberList(true);
+ }), DispatcherPriority.Normal, null);
+ }
+
+ public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
+ {
+ return _nextCommandTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/Css/SpeedTyping.cs b/EditorExtensions/Commands/Css/SpeedTyping.cs
new file mode 100644
index 000000000..6cf01315c
--- /dev/null
+++ b/EditorExtensions/Commands/Css/SpeedTyping.cs
@@ -0,0 +1,389 @@
+using EnvDTE80;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.OLE.Interop;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Classification;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.Windows.Forms;
+using System.Windows.Input;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class SpeedTypingTarget : IOleCommandTarget
+ {
+ private IWpfTextView _textView;
+ private ICompletionBroker _broker;
+ private IQuickInfoBroker _QuickInfobroker;
+ private IClassifierAggregatorService _aggregator;
+ private DTE2 _dte;
+ private CssTree _tree;
+
+ public SpeedTypingTarget(CssSortPropertiesViewCreationListener componentContext, IVsTextView adapter, IWpfTextView textView)
+ {
+ this._dte = EditorExtensionsPackage.DTE;
+ this._textView = textView;
+ this._aggregator = componentContext.AggregatorService;
+ this._broker = componentContext.CompletionBroker;
+ this._QuickInfobroker = componentContext.QuickInfoBroker;
+ }
+
+ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ //foreach (OutputWindowPane item in WebEssentialsPackage.dte.ToolWindows.OutputWindow.OutputWindowPanes)
+ //{
+ // item.OutputString(nCmdID.ToString() + Environment.NewLine);
+ //}
+
+ if (pguidCmdGroup == VSConstants.VSStd2K && WESettings.GetBoolean(WESettings.Keys.EnableSpeedTyping))
+ {
+ switch ((VSConstants.VSStd2KCmdID)nCmdID)
+ {
+ case VSConstants.VSStd2KCmdID.RETURN:
+ if (Keyboard.IsKeyDown(Key.RightShift) || Keyboard.IsKeyDown(Key.LeftShift))
+ {
+ if (Jump()) return
+ VSConstants.S_OK;
+ }
+ else
+ {
+ CommitStatementCompletion();
+ if (Process(true, true, true) == VSConstants.S_OK) return VSConstants.S_OK;
+ }
+ break;
+
+ case VSConstants.VSStd2KCmdID.TAB:
+ var completion = CommitStatementCompletion();
+ if (Process(false, true, false) == VSConstants.S_OK || completion) return VSConstants.S_OK;
+ break;
+ }
+ }
+
+ return (int)(Constants.MSOCMDERR_E_FIRST);
+ }
+
+ private bool Jump()
+ {
+ if (!EnsureInitialized())
+ return false;
+
+ int position = _textView.Caret.Position.BufferPosition.Position;
+ ParseItem item = _tree.StyleSheet.ItemBeforePosition(position);
+
+ if (item != null)
+ {
+ RuleBlock rule = item.FindType();
+ Declaration dec = item.FindType();
+
+ if (rule != null && dec != null)
+ {
+ CommitStatementCompletion();
+
+ var line = _textView.TextSnapshot.GetLineFromPosition(position);
+ string text = line.Extent.GetText().TrimEnd();
+
+ if (!string.IsNullOrWhiteSpace(text) && !text.EndsWith(";"))
+ {
+ using (ITextEdit edit = _textView.TextBuffer.CreateEdit())
+ {
+ edit.Replace(line.Extent, text + ";");
+ edit.Apply();
+ }
+ }
+
+ EditorExtensionsPackage.ExecuteCommand("Edit.FormatSelection");
+
+ SnapshotPoint point = new SnapshotPoint(_textView.TextBuffer.CurrentSnapshot, rule.AfterEnd);
+ _textView.Caret.MoveTo(point);
+ _textView.ViewScroller.EnsureSpanVisible(new SnapshotSpan(point, 0));
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ //private int JumpOut()
+ //{
+ // int result = VSConstants.S_FALSE;
+ // var span = _textView.Selection.SelectedSpans[0];
+ // var position = span.Start.Position;
+ // var line = span.Start.GetContainingLine();
+ // var classifications = _aggregator.GetClassifier(_textView.TextBuffer).GetClassificationSpans(line.Extent);
+
+ // _dte.UndoContext.Open("Jump out of brace");
+ // try
+ // {
+ // foreach (var classification in classifications)
+ // {
+ // if (IsPropertyValue(classification) && IsPropertyValueEligible(line, position))
+ // {
+ // CommitStatementCompletion();
+ // line = _textView.TextSnapshot.GetLineFromPosition(position);
+ // using (ITextEdit edit = _textView.TextBuffer.CreateEdit())
+ // {
+ // edit.Replace(line.Extent, line.Extent.GetText().TrimEnd() + ";");
+ // edit.Apply();
+ // }
+ // }
+ // else if (IsSelector(classification) || (IsPropertyName(classification) && !line.Extent.GetText().Contains(":")))
+ // {
+ // return VSConstants.S_FALSE;
+ // }
+ // }
+
+ // var text = _textView.TextSnapshot.GetText();
+ // int start = text.LastIndexOf('{', position - 1);
+ // int middle = text.IndexOf('{', position - 1);
+ // int end = text.IndexOf('}', position - 1);
+ // int emptyLines = ResolveEmptyLines(end);
+
+ // string blanks = string.Empty;
+ // if (emptyLines < 3)
+ // {
+ // for (int i = 0; i < (3 - emptyLines); i++)
+ // {
+ // blanks += "\n";
+ // }
+ // }
+
+ // if ((end < middle || middle == -1) && start < position && end > position)
+ // {
+ // using (ITextEdit edit = _textView.TextBuffer.CreateEdit())
+ // {
+ // edit.Replace(_textView.TextSnapshot.GetLineFromPosition(end).Extent, "}" + blanks);
+
+ // if (string.IsNullOrWhiteSpace(line.GetText()))
+ // {
+ // edit.Delete(line.ExtentIncludingLineBreak);
+ // end = end - line.ExtentIncludingLineBreak.Length;
+ // }
+
+ // edit.Apply();
+ // result = VSConstants.S_OK;
+ // }
+
+ // _textView.Caret.MoveTo(new SnapshotPoint(_textView.TextSnapshot, end + 3));
+ // _broker.DismissAllSessions(_textView);
+ // DismissQuickInfo();
+ // }
+ // }
+ // finally
+ // {
+ // _dte.UndoContext.Close();
+ // }
+
+ // return result;
+ //}
+
+ //private int ResolveEmptyLines(int end)
+ //{
+ // if (end == -1 || _textView.TextSnapshot.GetLineNumberFromPosition(end) == _textView.TextSnapshot.LineCount)
+ // return 0;
+
+ // int emptyLines = 0;
+ // int currentLine = _textView.TextSnapshot.GetLineFromPosition(end).LineNumber + 1;
+ // while ((currentLine + emptyLines) < _textView.TextSnapshot.LineCount)
+ // {
+ // if (string.IsNullOrWhiteSpace(_textView.TextSnapshot.GetLineFromLineNumber(currentLine + emptyLines).GetText()))
+ // {
+ // emptyLines++;
+ // }
+ // else
+ // {
+ // break;
+ // }
+ // }
+ // return emptyLines;
+ //}
+
+ private int Process(bool selector, bool name, bool value)
+ {
+ if (!EnsureInitialized())
+ return VSConstants.S_FALSE;
+
+ var span = _textView.Selection.SelectedSpans[0];
+ var line = span.Start.GetContainingLine();
+ var position = span.Start.Position;// -(line.Length - line.GetText().TrimEnd().Length);
+ var classifications = _aggregator.GetClassifier(_textView.TextBuffer).GetClassificationSpans(line.Extent);
+
+ foreach (var classification in classifications)
+ {
+ if (selector && IsSelector(classification) && IsSelectorEligible(line, position))
+ {
+ return InsertBraces(line);
+ }
+ else if (name && IsPropertyName(classification) && IsPropertyNameEligible(line))
+ {
+ DismissQuickInfo();
+ return InsertColon(position);
+ }
+ else if (value && IsPropertyValue(classification) && IsPropertyValueEligible(line, position))
+ {
+ DismissQuickInfo();
+ return InsertSemiColon(line);
+ }
+ }
+
+ return VSConstants.S_FALSE;
+ }
+
+ private static bool IsSelector(ClassificationSpan classification)
+ {
+ return classification.ClassificationType.Classification == "CSS Selector";
+ }
+
+ private static bool IsPropertyName(ClassificationSpan classification)
+ {
+ return classification.ClassificationType.Classification == "CSS Property Name";
+ }
+
+ private static bool IsPropertyValue(ClassificationSpan classification)
+ {
+ return classification.ClassificationType.Classification == "CSS Property Value";
+ }
+
+ private bool IsSelectorEligible(ITextSnapshotLine line, int position)
+ {
+ string text = line.GetText();
+ if (text.IndexOf('{') > -1)
+ return false;
+
+ if (text.Trim().EndsWith(",", StringComparison.Ordinal))
+ return false;
+
+ if (line.LineNumber + 1 < line.Snapshot.LineCount)
+ {
+ var next = line.Snapshot.GetLineFromLineNumber(line.LineNumber + 1);
+ if (next.GetText().Trim().StartsWith("{", StringComparison.Ordinal))
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool IsPropertyValueEligible(ITextSnapshotLine line, int position)
+ {
+ string text = line.GetText();
+ int diff = text.Length - text.TrimEnd().Length;
+
+ if (line.End.Position - diff > position)
+ return false;
+
+ if (text.IndexOf(';') > -1)
+ return false;
+
+ return true;
+ }
+
+ private bool IsPropertyNameEligible(ITextSnapshotLine line)
+ {
+ return !line.GetText().Contains(":");
+ }
+
+ private bool CommitStatementCompletion()
+ {
+ bool value = _broker.IsCompletionActive(_textView);
+
+ if (_broker.IsCompletionActive(_textView))
+ {
+ _broker.GetSessions(_textView)[0].Commit();
+ }
+
+ return value;
+ }
+
+ private void DismissQuickInfo()
+ {
+ if (_QuickInfobroker.IsQuickInfoActive(_textView))
+ _QuickInfobroker.GetSessions(_textView)[0].Dismiss();
+ }
+
+ private int InsertBraces(ITextSnapshotLine line)
+ {
+ string text = line.GetText();
+
+ using (ITextEdit edit = _textView.TextBuffer.CreateEdit())
+ {
+ _dte.UndoContext.Open("Insert braces");
+ edit.Replace(line.Extent, text.TrimEnd() + " {\n\t\n}");
+ edit.Apply();
+ _dte.UndoContext.Close();
+ }
+
+ SendKeys.Send("{LEFT}{LEFT}^( )");
+ return VSConstants.S_OK;
+ }
+
+ private int InsertColon(int position)
+ {
+ using (ITextEdit edit = _textView.TextBuffer.CreateEdit())
+ {
+ _dte.UndoContext.Open("Insert braces");
+ edit.Insert(position, ":");
+ edit.Apply();
+ _dte.UndoContext.Close();
+ }
+
+ SendKeys.Send(" ");
+
+ return VSConstants.S_OK;
+ }
+
+ private int InsertSemiColon(ITextSnapshotLine line)
+ {
+ string text = line.GetText();
+
+ using (ITextEdit edit = _textView.TextBuffer.CreateEdit())
+ {
+ _dte.UndoContext.Open("Insert braces");
+ edit.Replace(line.Extent, text.TrimEnd() + ";\n\t");
+ edit.Apply();
+ _dte.UndoContext.Close();
+ }
+
+ //SendKeys.Send("^( )");
+ return VSConstants.S_OK;
+ }
+
+ public bool EnsureInitialized()
+ {
+ if (_tree == null)
+ {
+ try
+ {
+ CssEditorDocument document = CssEditorDocument.FromTextBuffer(_textView.TextBuffer);
+ _tree = document.Tree;
+ }
+ catch (ArgumentNullException)
+ {
+ }
+ }
+
+ return _tree != null;
+ }
+
+ public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
+ {
+ //if (WESettings.GetBoolean(WESettings.Keys.EnableSpeedTyping))
+ //{
+ // for (int i = 0; i < cCmds; i++)
+ // {
+ // switch ((VSConstants.VSStd2KCmdID)prgCmds[i].cmdID)
+ // {
+ // case VSConstants.VSStd2KCmdID.RETURN:
+ // prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED);
+ // return VSConstants.S_OK;
+ // }
+ // }
+ //}
+
+ //return _nextCommandTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
+ return (int)(Constants.OLECMDERR_E_NOTSUPPORTED);
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/FeatureEnabler.cs b/EditorExtensions/Commands/FeatureEnabler.cs
new file mode 100644
index 000000000..4124c49f3
--- /dev/null
+++ b/EditorExtensions/Commands/FeatureEnabler.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.VisualStudio.JavaScript.Web.Extensions.Shared;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IFeatureEnabler))]
+ internal class FeatureEnabler : IFeatureEnabler
+ {
+ private static string[] _enabledFeatures =
+ {
+ FeatureManager.Features.DocCommentExtension,
+ FeatureManager.Features.DocCommentScaffolding
+ };
+
+ public IEnumerable EnabledFeatures
+ {
+ get
+ {
+ return _enabledFeatures;
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/HTML/ContractSelectionTarget.cs b/EditorExtensions/Commands/HTML/ContractSelectionTarget.cs
new file mode 100644
index 000000000..97c2e06ac
--- /dev/null
+++ b/EditorExtensions/Commands/HTML/ContractSelectionTarget.cs
@@ -0,0 +1,91 @@
+using Microsoft.Html.Core;
+using Microsoft.Html.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class ContactSelection : CommandTargetBase
+ {
+ private IWpfTextView _view;
+ private ITextBuffer _buffer;
+
+ public ContactSelection(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, GuidList.guidFormattingCmdSet, PkgCmdIDList.ContractSelection)
+ {
+ _view = textView;
+ _buffer = textView.TextBuffer;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ HtmlEditorDocument document = HtmlEditorDocument.FromTextView(_view);
+ var tree = document.HtmlEditorTree;
+
+ int start = _view.Selection.Start.Position.Position;
+ int end = _view.Selection.End.Position.Position;
+
+ ElementNode tag = null;
+ AttributeNode attr = null;
+
+ tree.GetPositionElement(start + 1, out tag, out attr);
+
+ if (tag == null)
+ return false;
+
+ if (tag.EndTag != null && tag.StartTag.Start == start && tag.EndTag.End == end)
+ {
+ Select(tag.InnerRange.Start, tag.InnerRange.Length);
+ }
+ else if (tag.Children.Count > 0)
+ {
+ var current = NodeAtCaret(tree);
+ var child = ChildNode(current, tag);
+
+ if (tag.Children.Contains(child))
+ Select(child.Start, child.OuterRange.Length);
+ else
+ Select(tag.Children[0].Start, tag.Children[0].End - tag.Children[0].Start);
+ }
+
+ return true;
+ }
+
+ private ElementNode ChildNode(ElementNode deepChild, ElementNode parent)
+ {
+ if (deepChild.Parent != parent)
+ {
+ return ChildNode(deepChild.Parent, parent);
+ }
+
+ return deepChild;
+ }
+
+ private ElementNode NodeAtCaret(HtmlEditorTree tree)
+ {
+ int start = _view.Caret.Position.BufferPosition.Position;
+
+ ElementNode tag = null;
+ AttributeNode attr = null;
+
+ tree.GetPositionElement(start, out tag, out attr);
+
+ return tag;
+ }
+
+ private void Select(int start, int length)
+ {
+ var span = new SnapshotSpan(_buffer.CurrentSnapshot, start, length);
+ _view.Selection.Select(span, false);
+ }
+
+ protected override bool IsEnabled()
+ {
+ return true;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/HTML/ExpandSelectionTarget.cs b/EditorExtensions/Commands/HTML/ExpandSelectionTarget.cs
new file mode 100644
index 000000000..816342793
--- /dev/null
+++ b/EditorExtensions/Commands/HTML/ExpandSelectionTarget.cs
@@ -0,0 +1,70 @@
+using Microsoft.Html.Core;
+using Microsoft.Html.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class ExpandSelection : CommandTargetBase
+ {
+ private IWpfTextView _view;
+ private ITextBuffer _buffer;
+
+ public ExpandSelection(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, GuidList.guidFormattingCmdSet, PkgCmdIDList.ExpandSelection)
+ {
+ _view = textView;
+ _buffer = textView.TextBuffer;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ HtmlEditorDocument document = HtmlEditorDocument.FromTextView(_view);
+ var tree = document.HtmlEditorTree;
+
+ int start = _view.Selection.Start.Position.Position;
+ int end = _view.Selection.End.Position.Position;
+
+ ElementNode tag = null;
+ AttributeNode attr = null;
+
+ tree.GetPositionElement(start, out tag, out attr);
+
+ if (tag == null)
+ return false;
+
+ if (tag.EndTag != null && tag.StartTag.End == start && tag.EndTag.Start == end)
+ {
+ Select(tag.Start, tag.OuterRange.Length);
+ }
+ else if (tag.EndTag != null && tag.StartTag.Start < start && tag.EndTag.End > end)
+ {
+ Select(tag.InnerRange.Start, tag.InnerRange.Length);
+ }
+ else if (tag.IsSelfClosing() && tag.Start < start && tag.End > end)
+ {
+ Select(tag.Start, tag.OuterRange.Length);
+ }
+ else if (tag.Parent != null)
+ {
+ Select(tag.Parent.Start, tag.Parent.OuterRange.Length);
+ }
+
+ return true;
+ }
+
+ private void Select(int start, int length)
+ {
+ var span = new SnapshotSpan(_buffer.CurrentSnapshot, start, length);
+ _view.Selection.Select(span, false);
+ }
+
+ protected override bool IsEnabled()
+ {
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/HTML/HtmlCreationListener.cs b/EditorExtensions/Commands/HTML/HtmlCreationListener.cs
new file mode 100644
index 000000000..4b728d98f
--- /dev/null
+++ b/EditorExtensions/Commands/HTML/HtmlCreationListener.cs
@@ -0,0 +1,54 @@
+using Microsoft.VisualStudio.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using Microsoft.VisualStudio.Utilities;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IVsTextViewCreationListener))]
+ [ContentType("HTML")]
+ [ContentType("HTMLX")]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ class HtmlViewCreationListener : IVsTextViewCreationListener
+ {
+ [Import, System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; }
+
+ [Import]
+ internal ICompletionBroker CompletionBroker { get; set; }
+
+ public void VsTextViewCreated(IVsTextView textViewAdapter)
+ {
+ var textView = EditorAdaptersFactoryService.GetWpfTextView(textViewAdapter);
+
+ textView.Properties.GetOrCreateSingletonProperty(() => new ZenCoding(textViewAdapter, textView, CompletionBroker));
+
+ //uint zenCoding;
+ //EditorExtensionsPackage.PriorityCommandTarget.RegisterPriorityCommandTarget(0, new ZenCoding(textViewAdapter, textView, CompletionBroker), out zenCoding);
+ //textView.Closed += delegate { EditorExtensionsPackage.PriorityCommandTarget.UnregisterPriorityCommandTarget(zenCoding); };
+ }
+ }
+
+ [Export(typeof(IVsTextViewCreationListener))]
+ [ContentType("HTMLX")]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ class HtmlxViewCreationListener : IVsTextViewCreationListener
+ {
+ [Import, System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; }
+
+ [Import]
+ internal ICompletionBroker CompletionBroker { get; set; }
+
+ public void VsTextViewCreated(IVsTextView textViewAdapter)
+ {
+ var textView = EditorAdaptersFactoryService.GetWpfTextView(textViewAdapter);
+
+ textView.Properties.GetOrCreateSingletonProperty(() => new SurroundWith(textViewAdapter, textView, CompletionBroker));
+ textView.Properties.GetOrCreateSingletonProperty(() => new ExpandSelection(textViewAdapter, textView));
+ textView.Properties.GetOrCreateSingletonProperty(() => new ContactSelection(textViewAdapter, textView));
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/HTML/SurroundWithTarget.cs b/EditorExtensions/Commands/HTML/SurroundWithTarget.cs
new file mode 100644
index 000000000..9eb449c4d
--- /dev/null
+++ b/EditorExtensions/Commands/HTML/SurroundWithTarget.cs
@@ -0,0 +1,90 @@
+using Microsoft.Html.Core;
+using Microsoft.Html.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class SurroundWith : CommandTargetBase
+ {
+ private ICompletionBroker _broker;
+ private IWpfTextView _view;
+ private ITextBuffer _buffer;
+
+ public SurroundWith(IVsTextView adapter, IWpfTextView textView, ICompletionBroker broker)
+ : base(adapter, textView, GuidList.guidFormattingCmdSet, PkgCmdIDList.SurroundWith)
+ {
+ _broker = broker;
+ _view = textView;
+ _buffer = textView.TextBuffer;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (_view.Selection.IsEmpty)
+ {
+ return HandleElement();
+ }
+ else
+ {
+ int start = _view.Selection.Start.Position.Position;
+ int end = _view.Selection.End.Position.Position;
+ Update(start, end);
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool HandleElement()
+ {
+ HtmlEditorDocument document = HtmlEditorDocument.FromTextView(_view);
+ var tree = document.HtmlEditorTree;
+
+ int position = _view.Caret.Position.BufferPosition.Position;
+
+ ElementNode tag = null;
+ AttributeNode attr = null;
+
+ tree.GetPositionElement(position, out tag, out attr);
+
+ if (tag != null && (tag.EndTag != null || tag.IsSelfClosing()))
+ {
+ int start = tag.Start;
+ int end = tag.End;
+
+ Update(start, end);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void Update(int start, int end)
+ {
+ EditorExtensionsPackage.DTE.UndoContext.Open("Surround with...");
+
+ using (var edit = _buffer.CreateEdit())
+ {
+ edit.Insert(end, "
");
+ edit.Insert(start, "");
+ edit.Apply();
+ }
+
+ SnapshotPoint point = new SnapshotPoint(_buffer.CurrentSnapshot, start + 1);
+
+ _view.Caret.MoveTo(point);
+ _view.Selection.Select(new SnapshotSpan(_buffer.CurrentSnapshot, point, 1), false);
+
+ EditorExtensionsPackage.DTE.UndoContext.Close();
+ }
+
+ protected override bool IsEnabled()
+ {
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/HTML/ZenCodingCommandTarget.cs b/EditorExtensions/Commands/HTML/ZenCodingCommandTarget.cs
new file mode 100644
index 000000000..2831334f1
--- /dev/null
+++ b/EditorExtensions/Commands/HTML/ZenCodingCommandTarget.cs
@@ -0,0 +1,301 @@
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Projection;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Windows.Threading;
+using ZenCoding;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class ZenCoding : CommandTargetBase
+ {
+ private ICompletionBroker _broker;
+ private ITrackingSpan _trackingSpan;
+
+ private static Regex _bracket = new Regex(@"<([a-z0-9]*)\b[^>]*>([^<]*)\1>", RegexOptions.IgnoreCase);
+ private static Regex _quotes = new Regex("(=\"()\")", RegexOptions.IgnoreCase);
+
+ public ZenCoding(IVsTextView adapter, IWpfTextView textView, ICompletionBroker broker)
+ : base(adapter, textView, typeof(VSConstants.VSStd2KCmdID).GUID, 4, 5)
+ {
+ _broker = broker;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (commandId == 4 && !_broker.IsCompletionActive(TextView))
+ {
+ if (WESettings.GetBoolean(WESettings.Keys.EnableHtmlZenCoding))
+ {
+ if (InvokeZenCoding())
+ {
+ return true;
+ }
+ else if (MoveToNextEmptySlot())
+ {
+ return true;
+ }
+ }
+ }
+ else if (commandId == 5 && !_broker.IsCompletionActive(TextView))
+ {
+ if (WESettings.GetBoolean(WESettings.Keys.EnableHtmlZenCoding) && MoveToPrevEmptySlot())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private bool MoveToNextEmptySlot()
+ {
+ if (_trackingSpan == null)
+ return false;
+
+ int position = TextView.Caret.Position.BufferPosition.Position + 1;
+ Span ts = _trackingSpan.GetSpan(TextView.TextBuffer.CurrentSnapshot);
+
+ if (ts.Contains(position))
+ {
+ Span span = new Span(position, ts.End - position);
+ SetCaret(span, false);
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool MoveToPrevEmptySlot()
+ {
+ if (_trackingSpan == null)
+ return false;
+
+ int position = TextView.Caret.Position.BufferPosition.Position;
+
+ if (position > 0)
+ {
+ Span ts = _trackingSpan.GetSpan(TextView.TextBuffer.CurrentSnapshot);
+
+ if (ts.Contains(position - 1))
+ {
+ Span span = new Span(ts.Start, position - ts.Start - 1);
+ SetCaret(span, true);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private bool InvokeZenCoding()
+ {
+ Span zenSpan = GetText();
+
+ if (zenSpan.Length == 0 || TextView.Selection.SelectedSpans[0].Length > 0 || !IsValidTextBuffer())
+ return false;
+
+ string zenSyntax = TextView.TextBuffer.CurrentSnapshot.GetText(zenSpan);
+
+ Parser parser = new Parser();
+ string result = parser.Parse(zenSyntax, ZenType.HTML);
+
+ if (!string.IsNullOrEmpty(result))
+ {
+ EditorExtensionsPackage.DTE.UndoContext.Open("ZenCoding");
+
+ ITextSelection selection = UpdateTextBuffer(zenSpan, result);
+ Span newSpan = new Span(zenSpan.Start, selection.SelectedSpans[0].Length);
+
+ if (result.Count(c => c == '>') > 2)
+ {
+ _trackingSpan = TextView.TextBuffer.CurrentSnapshot.CreateTrackingSpan(newSpan, SpanTrackingMode.EdgeExclusive);
+ }
+
+ selection.Clear();
+
+ EditorExtensionsPackage.DTE.UndoContext.Close();
+
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => SetCaret(newSpan, false)), DispatcherPriority.Normal, null);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool IsValidTextBuffer()
+ {
+ var projection = TextView.TextBuffer as IProjectionBuffer;
+
+ if (projection != null)
+ {
+ int position = TextView.Caret.Position.BufferPosition.Position;
+ var snapshotPoint = TextView.Caret.Position.BufferPosition;
+
+ var buffers = projection.SourceBuffers.Where(
+ s =>
+ !s.ContentType.IsOfType("html")
+ && !s.ContentType.IsOfType("htmlx")
+ && !s.ContentType.IsOfType("inert")
+ && !s.ContentType.IsOfType("CSharp")
+ && !s.ContentType.IsOfType("VisualBasic")
+ && !s.ContentType.IsOfType("RoslynCSharp")
+ && !s.ContentType.IsOfType("RoslynVisualBasic"));
+
+
+ foreach (ITextBuffer buffer in buffers)
+ {
+ SnapshotPoint? point = TextView.BufferGraph.MapDownToBuffer(snapshotPoint, PointTrackingMode.Negative, buffer, PositionAffinity.Predecessor);
+
+ if (point.HasValue)
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+ //private bool IsValidTextBuffer()
+ //{
+ // IProjectionBuffer projection = _textView.TextBuffer as IProjectionBuffer;
+
+ // if (projection != null)
+ // {
+ // int position = _textView.Caret.Position.BufferPosition.Position;
+ // var buffers = projection.SourceBuffers.Where(s => s.ContentType.IsOfType("css") || s.ContentType.IsOfType("javascript"));
+
+ // foreach (ITextBuffer buffer in buffers)
+ // {
+ // IProjectionSnapshot snapshot = buffer.CurrentSnapshot as IProjectionSnapshot;
+ // bool containsPosition = snapshot.GetSourceSpans().Any(s => s.Contains(position));
+
+ // if (containsPosition)
+ // {
+ // return false;
+ // }
+ // }
+ // }
+
+ // return true;
+ //}
+
+ private bool SetCaret(Span zenSpan, bool isReverse)
+ {
+ string text = TextView.TextBuffer.CurrentSnapshot.GetText();
+ Span quote = FindTabSpan(zenSpan, isReverse, text, _quotes);
+ Span bracket = FindTabSpan(zenSpan, isReverse, text, _bracket);
+
+ if (!isReverse && bracket.Start > 0 && (bracket.Start < quote.Start || quote.Start == 0))
+ {
+ quote = bracket;
+ }
+ else if (isReverse && bracket.Start > 0 && (bracket.Start > quote.Start || quote.Start == 0))
+ {
+ quote = bracket;
+ }
+
+ if (zenSpan.Contains(quote.Start))
+ {
+ MoveTab(quote);
+ return true;
+ }
+ else if (!isReverse)
+ {
+ MoveTab(new Span(zenSpan.End, 0));
+ return true;
+ }
+
+ return false;
+ }
+
+ private void MoveTab(Span quote)
+ {
+ TextView.Caret.MoveTo(new SnapshotPoint(TextView.TextBuffer.CurrentSnapshot, quote.Start));
+ SnapshotSpan span = new SnapshotSpan(TextView.TextBuffer.CurrentSnapshot, quote);
+ TextView.Selection.Select(span, false);
+ }
+
+ private static Span FindTabSpan(Span zenSpan, bool isReverse, string text, Regex regex)
+ {
+ MatchCollection matches = regex.Matches(text);
+
+ if (!isReverse)
+ {
+ foreach (Match match in matches)
+ {
+ Group group = match.Groups[2];
+
+ if (group.Index >= zenSpan.Start)
+ {
+ return new Span(group.Index, group.Length);
+ }
+ }
+ }
+ else
+ {
+ for (int i = matches.Count - 1; i >= 0; i--)
+ {
+ Group group = matches[i].Groups[2];
+
+ if (group.Index < zenSpan.End)
+ {
+ return new Span(group.Index, group.Length);
+ }
+ }
+ }
+
+ return new Span();
+ }
+
+ private ITextSelection UpdateTextBuffer(Span zenSpan, string result)
+ {
+ TextView.TextBuffer.Replace(zenSpan, result);
+
+ SnapshotPoint point = new SnapshotPoint(TextView.TextBuffer.CurrentSnapshot, zenSpan.Start);
+ SnapshotSpan snapshot = new SnapshotSpan(point, result.Length);
+ TextView.Selection.Select(snapshot, false);
+
+ EditorExtensionsPackage.ExecuteCommand("Edit.FormatSelection");
+
+ return TextView.Selection;
+ }
+
+ private Span GetText()
+ {
+ int position = TextView.Caret.Position.BufferPosition.Position;
+
+ if (position >= 0)
+ {
+ var line = TextView.TextBuffer.CurrentSnapshot.GetLineFromPosition(position);
+ string text = line.GetText().TrimEnd();
+
+ if (string.IsNullOrWhiteSpace(text) || text.Length < position - line.Start || text.Length + line.Start > position)
+ return new Span();
+
+ string result = text.Substring(0, position - line.Start).TrimStart();
+
+ if (result.Length > 0 && !text.Contains("<") && !char.IsWhiteSpace(result.Last()))
+ {
+ return new Span(line.Start.Position + text.IndexOf(result), result.Length);
+ }
+ }
+
+ return new Span();
+ }
+
+ protected override bool IsEnabled()
+ {
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/JavaScript/JavaScriptCreationListener.cs b/EditorExtensions/Commands/JavaScript/JavaScriptCreationListener.cs
new file mode 100644
index 000000000..d3f532bf5
--- /dev/null
+++ b/EditorExtensions/Commands/JavaScript/JavaScriptCreationListener.cs
@@ -0,0 +1,42 @@
+using Microsoft.VisualStudio.Editor;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Operations;
+using Microsoft.VisualStudio.TextManager.Interop;
+using Microsoft.VisualStudio.Utilities;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IVsTextViewCreationListener))]
+ [ContentType("JavaScript")]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ class JavaScriptSortPropertiesViewCreationListener : IVsTextViewCreationListener
+ {
+ [Import, System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; }
+
+ [Import(typeof(ITextStructureNavigatorSelectorService))]
+ internal ITextStructureNavigatorSelectorService Navigator { get; set; }
+
+ public void VsTextViewCreated(IVsTextView textViewAdapter)
+ {
+ var textView = EditorAdaptersFactoryService.GetWpfTextView(textViewAdapter);
+
+ textView.Properties.GetOrCreateSingletonProperty(() => new MinifySelection(textViewAdapter, textView));
+ textView.Properties.GetOrCreateSingletonProperty(() => new JavaScriptFindReferences(textViewAdapter, textView, Navigator));
+ textView.Properties.GetOrCreateSingletonProperty(() => new CssExtractToFile(textViewAdapter, textView));
+
+ ITextDocument document;
+ textView.TextDataModel.DocumentBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document);
+
+ if (document != null)
+ {
+ JsHintProjectRunner runner = new JsHintProjectRunner(document);
+ textView.Closed += (s, e) => runner.Dispose();
+
+ textView.TextBuffer.Properties.GetOrCreateSingletonProperty(() => runner);
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/JavaScript/JavaScriptFindReferencesCommandTarget.cs b/EditorExtensions/Commands/JavaScript/JavaScriptFindReferencesCommandTarget.cs
new file mode 100644
index 000000000..80fcac941
--- /dev/null
+++ b/EditorExtensions/Commands/JavaScript/JavaScriptFindReferencesCommandTarget.cs
@@ -0,0 +1,65 @@
+using EnvDTE80;
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Operations;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class JavaScriptFindReferences : CommandTargetBase
+ {
+ private DTE2 _dte;
+ private ITextStructureNavigator _navigator;
+
+ public JavaScriptFindReferences(IVsTextView adapter, IWpfTextView textView, ITextStructureNavigatorSelectorService navigator)
+ : base(adapter, textView, typeof(VSConstants.VSStd97CmdID).GUID, (uint)VSConstants.VSStd97CmdID.FindReferences)
+ {
+ _navigator = navigator.GetTextStructureNavigator(textView.TextBuffer);
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ int position = TextView.Caret.Position.BufferPosition.Position;
+ SnapshotPoint? point = TextView.Caret.Position.Point.GetPoint(TextView.TextBuffer, PositionAffinity.Predecessor);
+
+ if (point.HasValue)
+ {
+ TextExtent wordExtent = _navigator.GetExtentOfWord(point.Value - 1);
+ string wordText = TextView.TextSnapshot.GetText(wordExtent.Span);
+
+ Find2 find = (Find2)EditorExtensionsPackage.DTE.Find;
+ string types = find.FilesOfType;
+ bool matchCase = find.MatchCase;
+ bool matchWord = find.MatchWholeWord;
+
+ find.WaitForFindToComplete = false;
+ find.Action = EnvDTE.vsFindAction.vsFindActionFindAll;
+ find.Backwards = false;
+ find.MatchInHiddenText = true;
+ find.MatchWholeWord = true;
+ find.MatchCase = true;
+ find.PatternSyntax = EnvDTE.vsFindPatternSyntax.vsFindPatternSyntaxLiteral;
+ find.ResultsLocation = EnvDTE.vsFindResultsLocation.vsFindResults1;
+ find.SearchSubfolders = true;
+ find.FilesOfType = "*.js";
+ find.Target = EnvDTE.vsFindTarget.vsFindTargetSolution;
+ find.FindWhat = wordText;
+ find.Execute();
+
+ find.FilesOfType = types;
+ find.MatchCase = matchCase;
+ find.MatchWholeWord = matchWord;
+ }
+
+ return true;
+ }
+
+ protected override bool IsEnabled()
+ {
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/JavaScript/JavaScriptSaveListener.cs b/EditorExtensions/Commands/JavaScript/JavaScriptSaveListener.cs
new file mode 100644
index 000000000..934104ddb
--- /dev/null
+++ b/EditorExtensions/Commands/JavaScript/JavaScriptSaveListener.cs
@@ -0,0 +1,128 @@
+using Microsoft.Ajax.Utilities;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IWpfTextViewCreationListener))]
+ [ContentType("JavaScript")]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ public class JavaScriptSaveListener : IWpfTextViewCreationListener
+ {
+ private ITextDocument _document;
+
+ public void TextViewCreated(IWpfTextView textView)
+ {
+ textView.TextDataModel.DocumentBuffer.Properties.TryGetProperty(typeof(ITextDocument), out _document);
+
+ if (_document != null)
+ {
+ _document.FileActionOccurred += document_FileActionOccurred;
+ }
+ }
+
+ void document_FileActionOccurred(object sender, TextDocumentFileActionEventArgs e)
+ {
+ if (!WESettings.GetBoolean(WESettings.Keys.EnableJsMinification))
+ return;
+
+ ITextDocument document = (ITextDocument)sender;
+
+ if (document.TextBuffer != null && e.FileActionType == FileActionTypes.ContentSavedToDisk && e.FilePath.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
+ {
+ string minFile = e.FilePath.Insert(e.FilePath.Length - 2, "min.");
+ string bundleFile = e.FilePath + ".bundle";
+
+ if (!File.Exists(bundleFile) && File.Exists(minFile) && EditorExtensionsPackage.DTE.Solution.FindProjectItem(minFile) != null)
+ {
+ Task.Run(() =>
+ {
+ Minify(e.FilePath, minFile, false);
+ });
+ }
+ }
+ }
+
+ public static void Minify(string sourceFile, string minFile, bool isBundle)
+ {
+ if (sourceFile.EndsWith(".min.js"))
+ return;
+
+ try
+ {
+ CodeSettings settings = new CodeSettings()
+ {
+ EvalTreatment = EvalTreatment.MakeImmediateSafe,
+ TermSemicolons = true,
+ PreserveImportantComments = WESettings.GetBoolean(WESettings.Keys.KeepImportantComments)
+ };
+
+ if (WESettings.GetBoolean(WESettings.Keys.GenerateJavaScriptSourceMaps))
+ {
+ MinifyFileWithSourceMap(sourceFile, minFile, settings, isBundle);
+ }
+ else
+ {
+ MinifyFile(sourceFile, minFile, settings, isBundle);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(ex);
+ }
+ }
+
+ private static void MinifyFileWithSourceMap(string file, string minFile, CodeSettings settings, bool isBundle)
+ {
+ string mapPath = minFile + ".map";
+ ProjectHelpers.CheckOutFileFromSourceControl(mapPath);
+
+ using (TextWriter writer = new StreamWriter(mapPath, false, new UTF8Encoding(false)))
+ using (V3SourceMap sourceMap = new V3SourceMap(writer))
+ {
+ settings.SymbolsMap = sourceMap;
+
+ sourceMap.StartPackage(Path.GetFileName(minFile), Path.GetFileName(mapPath));
+
+ // This fails when debugger is attached. Bug raised with Ron Logan
+ MinifyFile(file, minFile, settings, isBundle);
+
+ sourceMap.EndPackage();
+
+ if (!isBundle)
+ {
+ MarginBase.AddFileToProject(file, mapPath);
+ }
+ }
+ }
+
+ private static void MinifyFile(string file, string minFile, CodeSettings settings, bool isBundle)
+ {
+ Minifier minifier = new Minifier();
+
+ if (!isBundle)
+ {
+ minifier.FileName = Path.GetFileName(file);
+ }
+
+ string content = minifier.MinifyJavaScript(File.ReadAllText(file), settings);
+
+ if (WESettings.GetBoolean(WESettings.Keys.GenerateJavaScriptSourceMaps))
+ {
+ content += Environment.NewLine + "//@ sourceMappingURL=" + Path.GetFileName(minFile) + ".map";
+ }
+
+ ProjectHelpers.CheckOutFileFromSourceControl(minFile);
+ using (StreamWriter writer = new StreamWriter(minFile, false, new UTF8Encoding(true)))
+ {
+ writer.Write(content);
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/JavaScript/JsHintCompiler.cs b/EditorExtensions/Commands/JavaScript/JsHintCompiler.cs
new file mode 100644
index 000000000..dbc488730
--- /dev/null
+++ b/EditorExtensions/Commands/JavaScript/JsHintCompiler.cs
@@ -0,0 +1,84 @@
+using MadsKristensen.EditorExtensions;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Windows.Threading;
+
+///
+/// Summary description for Lint
+///
+[ComVisible(true)]
+public class JsHintCompiler : ScriptRunnerBase
+{
+ private string _settings;
+ private JsHintOptions _options;
+
+ public JsHintCompiler(Dispatcher dispatcher)
+ : base(dispatcher)
+ { }
+
+ protected override string CreateHtml(string source, string state)
+ {
+ if (_options == null)
+ {
+ _options = new JsHintOptions();
+ _options.LoadSettingsFromStorage();
+ JsHintOptions.Changed += delegate { _options.LoadSettingsFromStorage(); GenerateSettings(); };
+
+ GenerateSettings();
+ }
+
+ source = source
+ .Replace("\\", "\\\\")
+ .Replace("\n", "\\n")
+ .Replace("\r", "\\r")
+ .Replace("'", "\\'");
+
+ string script = ReadResourceFile("MadsKristensen.EditorExtensions.Resources.Scripts.jshint.js") +
+ "JSHINT('" + source + "', {" + _settings + "});" +
+ "window.external.Execute(JSON.stringify(JSHINT.errors), '" + state.Replace("\\", "\\\\") + "')";
+
+ return "";
+ }
+
+ private void GenerateSettings()
+ {
+ Type type = _options.GetType();
+ PropertyInfo[] properties = type.GetProperties();
+ List list = new List();
+
+ foreach (PropertyInfo item in properties)
+ {
+ if (!item.Name.StartsWith("JsHint_"))
+ continue;
+
+ object value = item.GetValue(_options, null);
+ int intValue;
+ bool boolValue;
+
+ if (int.TryParse(value.ToString(), out intValue) && intValue > -1)
+ {
+ list.Add(item.Name.Replace("JsHint_", string.Empty) + ":" + intValue);
+ }
+ else if (bool.TryParse(value.ToString(), out boolValue) && boolValue == true)
+ {
+ list.Add(item.Name.Replace("JsHint_", string.Empty) + ":true");
+ }
+ }
+
+ _settings = string.Join(", ", list);
+ }
+}
+
+public class Result
+{
+ public string id { get; set; }
+ public string raw { get; set; }
+ public string evidence { get; set; }
+ public int line { get; set; }
+ public int character { get; set; }
+ public string a { get; set; }
+ public string b { get; set; }
+ public string reason { get; set; }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/JavaScript/JsHintProjectRunner.cs b/EditorExtensions/Commands/JavaScript/JsHintProjectRunner.cs
new file mode 100644
index 000000000..c542b685f
--- /dev/null
+++ b/EditorExtensions/Commands/JavaScript/JsHintProjectRunner.cs
@@ -0,0 +1,64 @@
+using Microsoft.VisualStudio.Text;
+using System;
+using System.IO;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class JsHintProjectRunner : IDisposable
+ {
+ private ITextDocument _document;
+ private JsHintRunner _runner;
+ private bool _isDisposed;
+
+ public JsHintProjectRunner(ITextDocument document)
+ {
+ _document = document;
+ _document.FileActionOccurred += DocumentSavedHandler;
+ _runner = new JsHintRunner(_document.FilePath);
+
+ if (WESettings.GetBoolean(WESettings.Keys.EnableJsHint))
+ {
+ Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => _runner.RunCompiler()), DispatcherPriority.ApplicationIdle, null);
+ }
+ }
+
+ private void DocumentSavedHandler(object sender, TextDocumentFileActionEventArgs e)
+ {
+ if (!WESettings.GetBoolean(WESettings.Keys.EnableJsHint))
+ return;
+
+ ITextDocument document = (ITextDocument)sender;
+
+ if (document.TextBuffer != null && !_isDisposed && e.FileActionType == FileActionTypes.ContentSavedToDisk)
+ {
+ Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => _runner.RunCompiler()), DispatcherPriority.ApplicationIdle, null);
+ }
+ }
+
+ public static void RunOnAllFilesInProject()
+ {
+ string dir = ProjectHelpers.GetRootFolder();
+
+ if (dir != null && Directory.Exists(dir))
+ {
+ foreach (string file in Directory.GetFiles(dir, "*.js", SearchOption.AllDirectories))
+ {
+ JsHintRunner runner = new JsHintRunner(file);
+ runner.RunCompiler();
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ if (!_isDisposed)
+ {
+ //_document.Dispose();
+ _runner.Dispose();
+ }
+
+ _isDisposed = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/JavaScript/JsHintRunner.cs b/EditorExtensions/Commands/JavaScript/JsHintRunner.cs
new file mode 100644
index 000000000..bc1e2b9ab
--- /dev/null
+++ b/EditorExtensions/Commands/JavaScript/JsHintRunner.cs
@@ -0,0 +1,246 @@
+using EnvDTE;
+using Microsoft.VisualStudio.Shell;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Web.Script.Serialization;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class JsHintRunner : IDisposable
+ {
+ private ErrorListProvider _provider;
+ private static Dictionary _providers = new Dictionary();
+ private string _fileName;
+ private bool _isDisposed;
+
+ static JsHintRunner()
+ {
+ EditorExtensionsPackage.DTE.Events.SolutionEvents.AfterClosing += SolutionEvents_AfterClosing;
+ }
+
+ static void SolutionEvents_AfterClosing()
+ {
+ Reset();
+ EditorExtensionsPackage.DTE.Events.SolutionEvents.AfterClosing -= SolutionEvents_AfterClosing;
+ }
+
+ public JsHintRunner(string fileName)
+ {
+ _fileName = fileName;
+
+ if (_providers.ContainsKey(fileName))
+ {
+ _provider = _providers[fileName];
+ }
+ else
+ {
+ _provider = new ErrorListProvider(EditorExtensionsPackage.Instance);
+ _providers.Add(fileName, _provider);
+ }
+ }
+
+ public void RunCompiler()
+ {
+ if (!_isDisposed && !ShouldIgnore(_fileName))
+ {
+ EditorExtensionsPackage.DTE.StatusBar.Text = "Web Essentials: Running JSHint...";
+ JsHintCompiler lint = new JsHintCompiler(Dispatcher.CurrentDispatcher);
+
+ System.Threading.Tasks.Task.Run(() =>
+ {
+ using (StreamReader reader = new StreamReader(_fileName))
+ {
+ string content = reader.ReadToEnd();
+
+ lint.Completed += LintCompletedHandler;
+ lint.Compile(content, _fileName);
+ }
+ });
+ }
+ }
+
+ public static void Reset()
+ {
+ foreach (string key in _providers.Keys)
+ {
+ _providers[key].Tasks.Clear();
+ _providers[key].Dispose();
+ }
+
+ _providers.Clear();
+ }
+
+ public static bool ShouldIgnore(string file)
+ {
+ if (!Path.GetExtension(file).Equals(".js", StringComparison.OrdinalIgnoreCase) ||
+ file.EndsWith(".min.js") ||
+ file.EndsWith(".debug.js") ||
+ file.EndsWith(".intellisense.js") ||
+ !File.Exists(file) ||
+ EditorExtensionsPackage.DTE.Solution.FindProjectItem(file) == null)
+ {
+ return true;
+ }
+
+ string name = Path.GetFileName(file);
+
+ foreach (string regex in _ignoreList)
+ {
+ if (Regex.IsMatch(name, regex, RegexOptions.IgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static List _ignoreList = new List()
+ {
+ @"jquery-([0-9\.]+)\.js",
+ @"jquery-ui-([0-9\.]+)\.js",
+ @"knockout-([0-9\.]+)\.js",
+ @"modernizr-([0-9\.]+)\.js",
+ @"backbone\.js",
+ @"angular\.js",
+ @"amplify\.js",
+ @"dojo\.js",
+ @"ember\.js",
+ @"handlebars-([0-9a-z\.]+)\.js",
+ @"mustache\.js",
+ @"underscore\.js",
+ @"yepnope\.js",
+ @"ext-core\.js",
+ @"highlight\.js",
+ @"history\.js",
+ @"require\.js",
+ @"sammy\.js",
+ @"json2\.js",
+ @"_references\.js",
+ @"MicrosoftAjax([a-z]+)\.js",
+ @"scriptaculous\.js ",
+ @"prototype\.js ",
+ @"qunit-([0-9a-z\.]+)\.js",
+ @"swfobject\.js",
+ @"bootstrap\.js",
+ @"webfont\.js",
+ @"zepto\.js",
+ };
+
+ private void LintCompletedHandler(object sender, CompilerEventArgs e)
+ {
+ using (JsHintCompiler lint = (JsHintCompiler)sender)
+ {
+ if (!_isDisposed)
+ {
+ System.Threading.Tasks.Task.Run(() =>
+ {
+ ReadResult(e);
+ });
+ }
+
+ lint.Completed -= LintCompletedHandler;
+ }
+
+ EditorExtensionsPackage.DTE.StatusBar.Clear();
+ }
+
+ private void ReadResult(CompilerEventArgs e)
+ {
+ try
+ {
+ JavaScriptSerializer serializer = new JavaScriptSerializer();
+ Result[] results = serializer.Deserialize(e.Result);
+
+ _provider.SuspendRefresh();
+ _provider.Tasks.Clear();
+
+ foreach (Result error in results.Where(r => r != null))
+ {
+ ErrorTask task = CreateTask(e.State, error);
+ _provider.Tasks.Add(task);
+ }
+
+ _provider.ResumeRefresh();
+ }
+ catch
+ {
+ Logger.Log("Error reading JSHint result");
+ }
+ }
+
+ private ErrorTask CreateTask(string data, Result error)
+ {
+ ErrorTask task = new ErrorTask()
+ {
+ Line = error.line,
+ Column = error.character,
+ ErrorCategory = GetOutputLocation(),
+ Category = TaskCategory.Html,
+ Document = data,
+ Priority = TaskPriority.Low,
+ Text = GetErrorMessage(error),
+ };
+
+ task.AddHierarchyItem();
+
+ task.Navigate += task_Navigate;
+ return task;
+ }
+
+ private static TaskErrorCategory GetOutputLocation()
+ {
+ var location = (WESettings.Keys.FullErrorLocation)WESettings.GetInt(WESettings.Keys.JsHintErrorLocation);
+
+ if (location == WESettings.Keys.FullErrorLocation.Errors)
+ return TaskErrorCategory.Error;
+
+ if (location == WESettings.Keys.FullErrorLocation.Warnings)
+ return TaskErrorCategory.Warning;
+
+ return TaskErrorCategory.Message;
+ }
+
+ private string GetErrorMessage(Result error)
+ {
+ string raw = error.raw;
+ if (raw == "Missing radix parameter.")
+ raw = "When using the parseInt function, remember to specify the radix parameter. Example: parseInt('3', 10)";
+
+ return "JSHint (r10): " + raw.Replace("{a}", error.a).Replace("{b}", error.b);
+ }
+
+ private void task_Navigate(object sender, EventArgs e)
+ {
+ Task task = sender as Task;
+
+ _provider.Navigate(task, new Guid(EnvDTE.Constants.vsViewKindPrimary));
+
+ if (task.Column > 0)
+ {
+ var doc = (TextDocument)EditorExtensionsPackage.DTE.ActiveDocument.Object("textdocument");
+ doc.Selection.MoveToLineAndOffset(task.Line, task.Column, false);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (!_isDisposed)
+ {
+ if (_providers.ContainsKey(_fileName))
+ {
+ _providers.Remove(_fileName);
+ }
+
+ _provider.Tasks.Clear();
+ _provider.Dispose();
+ }
+
+ _isDisposed = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/JavaScriptSmartIndentCommandTarget.cs b/EditorExtensions/Commands/JavaScriptSmartIndentCommandTarget.cs
new file mode 100644
index 000000000..ce51120cc
--- /dev/null
+++ b/EditorExtensions/Commands/JavaScriptSmartIndentCommandTarget.cs
@@ -0,0 +1,113 @@
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.OLE.Interop;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.ComponentModel.Composition;
+using System.Windows.Forms;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IVsTextViewCreationListener))]
+ [ContentType("JavaScript")]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ class JavaScriptSmartIndentTextViewCreationListener : IVsTextViewCreationListener
+ {
+ [Import, System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; }
+
+ [Import]
+ internal ICompletionBroker CompletionBroker { get; set; }
+
+ public void VsTextViewCreated(IVsTextView textViewAdapter)
+ {
+ var textView = EditorAdaptersFactoryService.GetWpfTextView(textViewAdapter);
+ textView.Properties.GetOrCreateSingletonProperty(() => new JavaScriptSmartIndent(textViewAdapter, textView, CompletionBroker));
+ }
+ }
+
+ class JavaScriptSmartIndent : IOleCommandTarget
+ {
+ private ITextView _textView;
+ private IOleCommandTarget _nextCommandTarget;
+ private ICompletionBroker _broker;
+
+ public JavaScriptSmartIndent(IVsTextView adapter, ITextView textView, ICompletionBroker broker)
+ {
+ _textView = textView;
+ _broker = broker;
+ adapter.AddCommandFilter(this, out _nextCommandTarget);
+ }
+
+ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (pguidCmdGroup == typeof(VSConstants.VSStd2KCmdID).GUID)
+ {
+ if (nCmdID == 3 && !_broker.IsCompletionActive(_textView))
+ {
+ if (Indent())
+ {
+ return VSConstants.S_OK;
+ }
+ }
+
+ }
+
+ return _nextCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
+ }
+
+ private bool Indent()
+ {
+ int position = _textView.Caret.Position.BufferPosition.Position;
+
+
+ if (position == 0 || position == _textView.TextBuffer.CurrentSnapshot.Length || _textView.Selection.SelectedSpans[0].Length > 0)
+ return false;
+
+ char before = _textView.TextBuffer.CurrentSnapshot.GetText(position - 1, 1)[0];
+ char after = _textView.TextBuffer.CurrentSnapshot.GetText(position, 1)[0];
+
+ if (before == '{' && after == '}')
+ {
+ EditorExtensionsPackage.DTE.UndoContext.Open("Smart indent");
+
+ _textView.TextBuffer.Insert(position, Environment.NewLine + '\t');
+ SnapshotPoint point = new SnapshotPoint(_textView.TextBuffer.CurrentSnapshot, position);
+ _textView.Selection.Select(new SnapshotSpan(point, 4), true);
+
+ EditorExtensionsPackage.ExecuteCommand("Edit.FormatSelection");
+
+ _textView.Selection.Clear();
+ SendKeys.Send("{ENTER}");
+
+ EditorExtensionsPackage.DTE.UndoContext.Close();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
+ {
+ if (pguidCmdGroup == typeof(VSConstants.VSStd2KCmdID).GUID)
+ {
+ for (int i = 0; i < cCmds; i++)
+ {
+ switch (prgCmds[i].cmdID)
+ {
+ case 3:
+ prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED);
+ return VSConstants.S_OK;
+ }
+ }
+ }
+
+ return _nextCommandTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/JsDocRegistry.cs b/EditorExtensions/Commands/JsDocRegistry.cs
new file mode 100644
index 000000000..4792bff06
--- /dev/null
+++ b/EditorExtensions/Commands/JsDocRegistry.cs
@@ -0,0 +1,86 @@
+using Microsoft.Win32;
+using System;
+using System.IO;
+using System.Reflection;
+
+namespace MadsKristensen.EditorExtensions
+{
+ public static class JsDocComments
+ {
+ private static string _fileName = "JsDocComments.js";
+
+ public static void Register()
+ {
+ try
+ {
+ string userPath = GetUserFilePath();
+
+ if (!File.Exists(userPath))
+ {
+ string assembly = Assembly.GetExecutingAssembly().Location;
+ string folder = Path.GetDirectoryName(assembly).ToLowerInvariant();
+ string file = Path.Combine(folder, "resources\\scripts\\JsDocComments.js");
+
+ if (!File.Exists(file))
+ return;
+
+ File.Copy(file, userPath);
+ UpdateRegistry(userPath);
+ }
+ }
+ catch
+ {
+ Logger.Log("Error registering JSDoc comments with Visual Studio");
+ }
+ }
+
+ private static string GetUserFilePath()
+ {
+ string user = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ string folder = Path.Combine(user, "Web Essentials");
+
+ if (!Directory.Exists(folder))
+ {
+ Directory.CreateDirectory(folder);
+ }
+
+ return Path.Combine(folder, _fileName);
+ }
+
+ private static void UpdateRegistry(string file)
+ {
+ using (RegistryKey key = EditorExtensionsPackage.Instance.UserRegistryRoot.OpenSubKey("JavaScriptLanguageService", true))
+ {
+ if (key != null)
+ {
+ string value = (string)key.GetValue("ReferenceGroups");
+ if (value.Contains(file))
+ return;
+
+ string newValue = value;
+ int index = value.IndexOf(_fileName);
+
+ if (index > -1)
+ {
+ int start = value.LastIndexOf('|', index);
+ int length = index - start + _fileName.Length;
+ string oldPath = value.Substring(start, length);
+ newValue = value.Replace(oldPath, "|" + file);
+ }
+ else
+ {
+ int startWeb = value.IndexOf("Implicit (Web)");
+ int semicolon = value.IndexOf(';', startWeb);
+
+ if (semicolon > -1)
+ {
+ newValue = value.Insert(semicolon, "|" + file);
+ }
+ }
+
+ key.SetValue("ReferenceGroups", newValue);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/LESS/LessCreationListener.cs b/EditorExtensions/Commands/LESS/LessCreationListener.cs
new file mode 100644
index 000000000..91ee8dbae
--- /dev/null
+++ b/EditorExtensions/Commands/LESS/LessCreationListener.cs
@@ -0,0 +1,27 @@
+using Microsoft.Less.Core;
+using Microsoft.VisualStudio.Editor;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.Web.Editor;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IVsTextViewCreationListener))]
+ [ContentType(LessContentTypeDefinition.LessContentType)]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ class LessViewCreationListener : IVsTextViewCreationListener
+ {
+ [Import, System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; }
+
+ public void VsTextViewCreated(IVsTextView textViewAdapter)
+ {
+ var textView = EditorAdaptersFactoryService.GetWpfTextView(textViewAdapter);
+
+ textView.Properties.GetOrCreateSingletonProperty(() => new LessExtractVariableCommandTarget(textViewAdapter, textView));
+ textView.Properties.GetOrCreateSingletonProperty(() => new LessExtractMixinCommandTarget(textViewAdapter, textView));
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/LESS/LessExtractMixinCommandTarget.cs b/EditorExtensions/Commands/LESS/LessExtractMixinCommandTarget.cs
new file mode 100644
index 000000000..6a789d619
--- /dev/null
+++ b/EditorExtensions/Commands/LESS/LessExtractMixinCommandTarget.cs
@@ -0,0 +1,61 @@
+using EnvDTE80;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class LessExtractMixinCommandTarget : CommandTargetBase
+ {
+ private DTE2 _dte;
+
+ public LessExtractMixinCommandTarget(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, GuidList.guidExtractCmdSet, PkgCmdIDList.ExtractMixin)
+ {
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (TextView == null)
+ return false;
+
+ CssEditorDocument document = CssEditorDocument.FromTextBuffer(TextView.TextBuffer);
+
+ int position = TextView.Caret.Position.BufferPosition.Position;
+ ParseItem item = document.Tree.StyleSheet.ItemBeforePosition(position);
+
+ ParseItem rule = LessExtractVariableCommandTarget.FindParent(item);
+ int mixinStart = rule.Start;
+ string name = Microsoft.VisualBasic.Interaction.InputBox("Name of the Mixin", "Web Essentials");
+
+ if (!string.IsNullOrEmpty(name))
+ {
+ EditorExtensionsPackage.DTE.UndoContext.Open("Extract to mixin");
+
+ var selection = TextView.Selection.SelectedSpans[0];
+ string text = selection.GetText();
+ TextView.TextBuffer.Replace(selection.Span, "." + name + "();");
+ TextView.TextBuffer.Insert(rule.Start, "." + name + "() {" + Environment.NewLine + text + Environment.NewLine + "}" + Environment.NewLine + Environment.NewLine);
+
+ TextView.Selection.Select(new SnapshotSpan(TextView.TextBuffer.CurrentSnapshot, mixinStart, 1), false);
+ EditorExtensionsPackage.ExecuteCommand("Edit.FormatSelection");
+ TextView.Selection.Clear();
+
+ EditorExtensionsPackage.DTE.UndoContext.Close();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ protected override bool IsEnabled()
+ {
+ return TextView.Selection.SelectedSpans[0].Length > 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/LESS/LessExtractVariableCommandTarget.cs b/EditorExtensions/Commands/LESS/LessExtractVariableCommandTarget.cs
new file mode 100644
index 000000000..6a7ee5c38
--- /dev/null
+++ b/EditorExtensions/Commands/LESS/LessExtractVariableCommandTarget.cs
@@ -0,0 +1,73 @@
+using EnvDTE80;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.Less.Core;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class LessExtractVariableCommandTarget : CommandTargetBase
+ {
+ private DTE2 _dte;
+
+ public LessExtractVariableCommandTarget(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, GuidList.guidExtractCmdSet, PkgCmdIDList.ExtractVariable)
+ {
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (TextView == null)
+ return false;
+
+ CssEditorDocument document = CssEditorDocument.FromTextBuffer(TextView.TextBuffer);
+
+ int position = TextView.Caret.Position.BufferPosition.Position;
+ ParseItem item = document.Tree.StyleSheet.ItemBeforePosition(position);
+
+ ParseItem rule = FindParent(item);
+ string text = item.Text;
+ string name = Microsoft.VisualBasic.Interaction.InputBox("Name of the variable", "Web Essentials");
+
+ if (!string.IsNullOrEmpty(name))
+ {
+ EditorExtensionsPackage.DTE.UndoContext.Open("Extract to variable");
+
+ Span span = TextView.Selection.SelectedSpans[0].Span;
+ TextView.TextBuffer.Replace(span, "@" + name);
+ TextView.TextBuffer.Insert(rule.Start, "@" + name + ": " + text + ";" + Environment.NewLine + Environment.NewLine);
+
+ EditorExtensionsPackage.DTE.UndoContext.Close();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public static ParseItem FindParent(ParseItem item)
+ {
+ ParseItem parent = item.Parent;
+
+ while (true)
+ {
+ if (parent.Parent == null || parent.Parent is LessStyleSheet || parent.Parent is AtDirective)
+ break;
+
+ parent = parent.Parent;
+ }
+
+ return parent;
+ }
+
+ protected override bool IsEnabled()
+ {
+ var span = TextView.Selection.SelectedSpans[0];
+ return span.Length > 0 && !span.GetText().Contains("\r");
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/MoveRuleCommandTarget.cs b/EditorExtensions/Commands/MoveRuleCommandTarget.cs
new file mode 100644
index 000000000..981d41a40
--- /dev/null
+++ b/EditorExtensions/Commands/MoveRuleCommandTarget.cs
@@ -0,0 +1,382 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.CSS.Editor.Schemas;
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Editor;
+using Microsoft.VisualStudio.OLE.Interop;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Xml;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IVsTextViewCreationListener))]
+ [ContentType(Microsoft.Web.Editor.CssContentTypeDefinition.CssContentType)]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ class MoveRuleTextViewCreationListener : IVsTextViewCreationListener
+ {
+ [Import, System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; }
+
+ public void VsTextViewCreated(IVsTextView textViewAdapter)
+ {
+ var textView = EditorAdaptersFactoryService.GetWpfTextView(textViewAdapter);
+ textView.Properties.GetOrCreateSingletonProperty(() => new MoveRuleTarget(textViewAdapter, textView));
+ }
+ }
+
+ class MoveRuleTarget : IOleCommandTarget
+ {
+ private ITextView _textView;
+ private IOleCommandTarget _nextCommandTarget;
+ private CssTree _tree;
+
+ public MoveRuleTarget(IVsTextView adapter, ITextView textView)
+ {
+ this._textView = textView;
+ adapter.AddCommandFilter(this, out _nextCommandTarget);
+ }
+
+ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (pguidCmdGroup == typeof(VSConstants.VSStd2KCmdID).GUID)
+ {
+ switch (nCmdID)
+ {
+ case 2400:
+ if (Move(Direction.Down))
+ return VSConstants.S_OK;
+ break;
+
+ case 2401:
+ if (Move(Direction.Up))
+ return VSConstants.S_OK;
+ break;
+ }
+ }
+
+ return _nextCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
+ }
+
+ private enum Direction
+ {
+ Up,
+ Down
+ }
+
+ private bool Move(Direction direction)
+ {
+ if (!EnsureInitialized())
+ return false;
+
+ int position = _textView.Caret.Position.BufferPosition.Position;
+
+ ParseItem item = _tree.StyleSheet.ItemBeforePosition(position);
+
+ Declaration dec = item.FindType();
+ if (dec != null)
+ {
+ return HandleDeclaration(direction, dec);
+ }
+
+ Selector selector = item.FindType();
+ if (selector != null)
+ {
+ return HandleSelector(direction, selector);
+ }
+
+ return false;
+ }
+
+ private bool HandleDeclaration(Direction direction, Declaration declaration)
+ {
+ RuleBlock rule = declaration.FindType();
+ if (rule == null || rule.Text.IndexOfAny(new[] { '\r', '\n' }) == -1 || (direction == Direction.Up && rule.Declarations.First() == declaration) || (direction == Direction.Down && rule.Declarations.Last() == declaration))
+ return false;
+
+ Declaration sibling = null;
+ string text = null;
+
+ if (direction == Direction.Up)
+ {
+ sibling = rule.Declarations.ElementAt(rule.Declarations.IndexOf(declaration) - 1);
+ text = declaration.Text + sibling.Text;
+ }
+ else
+ {
+ sibling = rule.Declarations.ElementAt(rule.Declarations.IndexOf(declaration) + 1);
+ text = sibling.Text + declaration.Text;
+ }
+
+ EditorExtensionsPackage.DTE.UndoContext.Open("Move CSS declaration");
+
+ using (ITextEdit edit = _textView.TextBuffer.CreateEdit())
+ {
+ int start = Math.Min(declaration.Start, sibling.Start);
+ int end = Math.Max(declaration.AfterEnd, sibling.AfterEnd);
+ edit.Replace(start, end - start, text);
+ edit.Apply();
+
+ if (direction == Direction.Up)
+ _textView.Caret.MoveTo(new SnapshotPoint(_textView.TextBuffer.CurrentSnapshot, sibling.Start + 1));
+
+ EditorExtensionsPackage.DTE.ExecuteCommand("Edit.FormatSelection");
+ EditorExtensionsPackage.DTE.UndoContext.Close();
+ }
+
+ return true;
+ }
+
+ private bool HandleSelector(Direction direction, Selector selector)
+ {
+ //new WriteBrowserXml().Parse();
+ RuleSet rule = selector.FindType();
+ if (rule == null)
+ return false;
+
+ if (direction == Direction.Up)
+ {
+ rule = rule.PreviousSibling as RuleSet;
+ if (rule == null)
+ return false;
+ }
+
+ EditorExtensionsPackage.DTE.UndoContext.Open("Move CSS rule");
+
+ using (ITextEdit edit = _textView.TextBuffer.CreateEdit())
+ {
+ int position = SwapItemWithNextSibling(rule, edit);
+ if (position > -1)
+ {
+ if (direction == Direction.Down)
+ _textView.Caret.MoveTo(new SnapshotPoint(_textView.TextBuffer.CurrentSnapshot, position + 1));
+ else
+ _textView.Caret.MoveTo(new SnapshotPoint(_textView.TextBuffer.CurrentSnapshot, rule.Start + 1));
+
+ // TODO: Format both rules
+ EditorExtensionsPackage.DTE.ExecuteCommand("Edit.FormatSelection");
+ }
+
+ EditorExtensionsPackage.DTE.UndoContext.Close();
+ }
+
+ return true;
+ }
+
+ private int SwapItemWithNextSibling(ParseItem item, ITextEdit edit)
+ {
+ RuleSet next = item.NextSibling as RuleSet;
+ if (next == null)
+ return -1;
+
+ ITextSnapshot snapshot = _textView.TextBuffer.CurrentSnapshot;
+ string whitespace = snapshot.GetText(item.AfterEnd, next.Start - item.AfterEnd);
+ string text = next.Text + whitespace + item.Text;
+
+ edit.Replace(item.Start, next.AfterEnd - item.Start, text);
+ edit.Apply();
+
+ return item.Start + next.Length + whitespace.Length;
+ }
+
+ public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
+ {
+ if (pguidCmdGroup == typeof(VSConstants.VSStd2KCmdID).GUID)
+ {
+ for (int i = 0; i < cCmds; i++)
+ {
+ switch (prgCmds[i].cmdID)
+ {
+ case 2401: // Up
+ case 2400: // Down
+ prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED);
+ return VSConstants.S_OK;
+ }
+ }
+ }
+
+ return _nextCommandTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
+ }
+
+ public bool EnsureInitialized()
+ {
+ if (_tree == null && Microsoft.Web.Editor.WebEditor.Host != null)
+ {
+ try
+ {
+ CssEditorDocument document = CssEditorDocument.FromTextBuffer(_textView.TextBuffer);
+ _tree = document.Tree;
+ }
+ catch (ArgumentNullException)
+ {
+ }
+ }
+
+ return _tree != null;
+ }
+ }
+
+ public class WriteBrowserXml
+ {
+ private const string _fileName = @"C:\Users\madsk\Documents\visual studio 2012\Projects\RealWorldValidator\RealWorldValidator\App_Data\browsers.xml";
+
+ public void Parse()
+ {
+ ICssSchemaInstance root = CssSchemaManager.SchemaManager.GetSchemaRoot(null);
+ IEnumerable schemas = GetAllSchemas(root);
+ using (XmlWriter writer = XmlWriter.Create(_fileName))
+ {
+ writer.WriteStartElement("css");
+
+ // @-Directives
+ List directives = new List(root.AtDirectives);
+ directives.AddRange(schemas.SelectMany(s => s.AtDirectives));
+ directives = RemoveDuplicates(directives);
+
+ writer.WriteStartElement("atDirectives");
+ WriteSection(writer, directives);
+ //WriteSection(writer, root.AtDirectives);
+ //foreach (var schema in schemas)
+ // WriteSection(writer, schema.AtDirectives);
+ writer.WriteEndElement();
+
+ // Pseudos
+ List pseudos = new List(root.PseudoClassesAndElements);
+ pseudos.AddRange(schemas.SelectMany(s => s.PseudoClassesAndElements));
+ pseudos = RemoveDuplicates(pseudos);
+
+ writer.WriteStartElement("pseudoClasses");
+ WriteSection(writer, pseudos.Where(p => p.DisplayText[1] != ':'));
+ writer.WriteEndElement();
+
+ writer.WriteStartElement("pseudoElements");
+ WriteSection(writer, pseudos.Where(p => p.DisplayText[1] == ':'));
+ writer.WriteEndElement();
+
+ // Properties
+ List properties = new List(root.Properties);
+ properties.AddRange(schemas.SelectMany(s => s.Properties));
+ properties = RemoveDuplicates(properties);
+
+ writer.WriteStartElement("properties");
+ WriteProperties(writer, properties, root);
+ //WriteProperties(writer, root.Properties, root);
+ //foreach (var schema in schemas)
+ // WriteProperties(writer, schema.Properties, schema);
+ writer.WriteEndElement();
+
+ writer.WriteEndElement();
+ }
+ }
+
+ private List RemoveDuplicates(List list)
+ {
+ for (int i = list.Count() - 1; i > -1; i--)
+ {
+ if (list.Count(p => p.DisplayText == list.ElementAt(i).DisplayText) > 1)
+ list.RemoveAt(i);
+ }
+
+ return list;
+ }
+
+ string[] vs = new[] { "@-we-palette", "@unspecified", "@global", "@specific" };
+
+ private IEnumerable GetAllSchemas(ICssSchemaInstance rootSchema)
+ {
+ foreach (ICssCompletionListEntry directive in rootSchema.AtDirectives)
+ {
+ if (vs.Contains(directive.DisplayText))
+ continue;
+
+ ICssSchemaInstance schema = rootSchema.GetAtDirectiveSchemaInstance(directive.DisplayText);
+ if (schema != null && schema.Properties.Count() != rootSchema.Properties.Count())
+ yield return schema;
+ }
+ }
+
+ private void WriteSection(XmlWriter writer, IEnumerable entries)
+ {
+ foreach (ICssCompletionListEntry entry in entries.OrderBy(e => e.DisplayText))
+ {
+ writer.WriteStartElement("entry");
+ writer.WriteAttributeString("name", entry.DisplayText);
+ writer.WriteAttributeString("version", entry.GetAttribute("version"));
+ WriteBrowserSupport(writer, entry);
+
+ if (!string.IsNullOrEmpty(entry.GetAttribute("standard-reference")))
+ writer.WriteAttributeString("ref", entry.GetAttribute("standard-reference"));
+
+ if (!string.IsNullOrEmpty(entry.GetAttribute("syntax")))
+ writer.WriteAttributeString("syntax", entry.GetAttribute("syntax"));
+
+ if (!string.IsNullOrEmpty(entry.GetAttribute("description")))
+ writer.WriteElementString("desc", entry.GetAttribute("description"));
+
+ writer.WriteEndElement();
+ }
+ }
+
+ private void WriteProperties(XmlWriter writer, IEnumerable entries, ICssSchemaInstance schema)
+ {
+ foreach (ICssCompletionListEntry entry in entries.OrderBy(e => e.DisplayText))
+ {
+ writer.WriteStartElement("entry");
+ writer.WriteAttributeString("name", entry.DisplayText);
+ writer.WriteAttributeString("restriction", entry.GetAttribute("restriction"));
+ writer.WriteAttributeString("version", entry.GetAttribute("version"));
+ WriteBrowserSupport(writer, entry);
+
+ if (!string.IsNullOrEmpty(entry.GetAttribute("standard-reference")))
+ writer.WriteAttributeString("ref", entry.GetAttribute("standard-reference"));
+
+ if (!string.IsNullOrEmpty(entry.GetAttribute("syntax")))
+ writer.WriteAttributeString("syntax", entry.GetAttribute("syntax"));
+
+ if (!string.IsNullOrEmpty(entry.GetAttribute("description")))
+ writer.WriteElementString("desc", entry.GetAttribute("description"));
+
+ var values = schema.GetPropertyValues(entry.DisplayText);
+ if (values.Count() > 2)
+ {
+ writer.WriteStartElement("values");
+ foreach (ICssCompletionListEntry value in values.OrderBy(v => v.DisplayText))
+ {
+ if (value.DisplayText == "initial" || value.DisplayText == "inherit")
+ continue;
+
+ writer.WriteStartElement("value");
+ writer.WriteAttributeString("name", value.DisplayText);
+ writer.WriteAttributeString("version", value.GetAttribute("version") != string.Empty ? value.GetAttribute("version") : entry.GetAttribute("version"));
+ WriteBrowserSupport(writer, value);
+
+ if (!string.IsNullOrEmpty(value.GetAttribute("description")))
+ writer.WriteElementString("desc", value.GetAttribute("description"));
+
+ writer.WriteEndElement();
+ }
+ writer.WriteEndElement();
+ }
+
+ writer.WriteEndElement();
+ }
+ }
+
+ private static void WriteBrowserSupport(XmlWriter writer, ICssCompletionListEntry entry)
+ {
+ string attr = entry.GetAttribute("browsers");
+
+ if (string.IsNullOrEmpty(attr))
+ attr = "all";
+
+ writer.WriteAttributeString("browsers", attr);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/PasteJsonAsTypeScript.cs b/EditorExtensions/Commands/PasteJsonAsTypeScript.cs
new file mode 100644
index 000000000..a96cf0e84
--- /dev/null
+++ b/EditorExtensions/Commands/PasteJsonAsTypeScript.cs
@@ -0,0 +1,136 @@
+using Microsoft.VisualStudio.Web.PasteJson;
+using System;
+using System.ComponentModel.Composition;
+using System.Diagnostics;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IPasteJsonCodeGenerator))]
+ [ExportMetadata("CodeGeneratorType", "TypeScript")]
+ internal class CSharpCodeGenerator : IPasteJsonCodeGenerator
+ {
+ private Regex _validIdentifierRegex = new Regex(@"[^\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Nl}\p{Mn}\p{Mc}\p{Cf}\p{Pc}\p{Lm}]");
+
+ public string GenerateStartClass(string className)
+ {
+ return string.Format("interface {0}", className) + Environment.NewLine + "{";
+ }
+
+ public string GenerateProperty(PasteJsonUtil.LangIndependentType langIndependentType, string propertyName)
+ {
+ return string.Format("{1}: {0};", GeneratePropertyTypeName(langIndependentType), propertyName);
+ }
+
+ public string GenerateObjectProperty(string typeOfObject, string propertyName)
+ {
+ return string.Format("{1}: {0};", typeOfObject, propertyName);
+ }
+
+ public string GenerateArrayProperty(int dimensions, string typeOfArray, string propertyName)
+ {
+ if (dimensions > 0)
+ {
+ string mutliDimension = GetArrayDeclaration(dimensions);
+ return string.Format("{2}: {0}{1};", typeOfArray, mutliDimension, propertyName);
+ }
+ return null;
+ }
+
+ public string GenerateArrayProperty(int dimensions, PasteJsonUtil.LangIndependentType langIndependentType, string propertyName)
+ {
+ if (dimensions > 0)
+ {
+ string mutliDimension = GetArrayDeclaration(dimensions);
+ return string.Format("{2}: {0}{1};", GeneratePropertyTypeName(langIndependentType), mutliDimension, propertyName);
+ }
+ return null;
+ }
+
+ private string GetArrayDeclaration(int dimension)
+ {
+ StringBuilder arrayDeclaration = new StringBuilder();
+ for (int loop = 1; loop <= dimension; loop++)
+ {
+ arrayDeclaration.Append(GeneratePropertyTypeName(PasteJsonUtil.LangIndependentType.Array));
+ }
+ return arrayDeclaration.ToString();
+ }
+
+ public string GenerateEndClass(string className)
+ {
+ return "}";
+ }
+
+ public string MakeValidName(string name)
+ {
+ return _validIdentifierRegex.Replace(name, "");
+ }
+
+ private string GeneratePropertyTypeName(PasteJsonUtil.LangIndependentType langIndependentType)
+ {
+ string returnType = string.Empty;
+ Debug.Assert(langIndependentType != PasteJsonUtil.LangIndependentType.Object, "I should not be expecting Object");
+
+ switch (langIndependentType)
+ {
+ case PasteJsonUtil.LangIndependentType.Array:
+ returnType = "[]";
+ break;
+ case PasteJsonUtil.LangIndependentType.Boolean:
+ returnType = "bool";
+ break;
+ case PasteJsonUtil.LangIndependentType.Date:
+ returnType = "Date";
+ break;
+ case PasteJsonUtil.LangIndependentType.Double:
+ returnType = "number";
+ break;
+ case PasteJsonUtil.LangIndependentType.Float:
+ returnType = "number";
+ break;
+ case PasteJsonUtil.LangIndependentType.Integer:
+ returnType = "number";
+ break;
+ case PasteJsonUtil.LangIndependentType.Long:
+ returnType = "number";
+ break;
+ case PasteJsonUtil.LangIndependentType.NullableBoolean:
+ returnType = "bool";
+ break;
+ case PasteJsonUtil.LangIndependentType.NullableDate:
+ returnType = "Date";
+ break;
+ case PasteJsonUtil.LangIndependentType.NullableDouble:
+ returnType = "number";
+ break;
+ case PasteJsonUtil.LangIndependentType.NullableFloat:
+ returnType = "number";
+ break;
+ case PasteJsonUtil.LangIndependentType.NullableInteger:
+ returnType = "number";
+ break;
+ case PasteJsonUtil.LangIndependentType.NullableLong:
+ returnType = "number";
+ break;
+ case PasteJsonUtil.LangIndependentType.NullOrUndefined:
+ returnType = "any";
+ break;
+ case PasteJsonUtil.LangIndependentType.String:
+ returnType = "string";
+ break;
+ case PasteJsonUtil.LangIndependentType.Unknown:
+ returnType = "any";
+ break;
+ case PasteJsonUtil.LangIndependentType.Uri:
+ returnType = "string";
+ break;
+ default:
+ Debug.Assert(true, "Property Type:" + langIndependentType.ToString() + " is not supported !!");
+ break;
+ }
+ return returnType;
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/Shared/EncodeSelectionCommandTarget.cs b/EditorExtensions/Commands/Shared/EncodeSelectionCommandTarget.cs
new file mode 100644
index 000000000..193558618
--- /dev/null
+++ b/EditorExtensions/Commands/Shared/EncodeSelectionCommandTarget.cs
@@ -0,0 +1,81 @@
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.Web;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class EncodeSelection : CommandTargetBase
+ {
+ private DTE2 _dte;
+ private static uint[] _commandIds = new uint[] {
+ PkgCmdIDList.htmlEncode,
+ PkgCmdIDList.htmlDecode,
+ PkgCmdIDList.attrEncode,
+ PkgCmdIDList.urlEncode,
+ PkgCmdIDList.urlDecode,
+ PkgCmdIDList.urlPathEncode,
+ PkgCmdIDList.jsEncode,
+ };
+
+ private delegate string Replacement(string original);
+
+ public EncodeSelection(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, GuidList.guidEditorExtensionsCmdSet, _commandIds)
+ {
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ switch (commandId)
+ {
+ case PkgCmdIDList.htmlEncode:
+ return Replace(HttpUtility.HtmlEncode);
+ case PkgCmdIDList.htmlDecode:
+ return Replace(HttpUtility.HtmlDecode);
+ case PkgCmdIDList.attrEncode:
+ return Replace(HttpUtility.HtmlAttributeEncode);
+ case PkgCmdIDList.urlEncode:
+ return Replace(HttpUtility.UrlEncode);
+ case PkgCmdIDList.urlDecode:
+ return Replace(HttpUtility.UrlDecode);
+ case PkgCmdIDList.urlPathEncode:
+ return Replace(HttpUtility.UrlPathEncode);
+ case PkgCmdIDList.jsEncode:
+ return Replace(HttpUtility.JavaScriptStringEncode);
+ }
+
+ return true;
+ }
+
+ private bool Replace(Replacement callback)
+ {
+ TextDocument document = GetTextDocument();
+ string replacement = callback(document.Selection.Text);
+
+ _dte.UndoContext.Open(callback.Method.Name);
+ document.Selection.Insert(replacement, 0);
+ _dte.UndoContext.Close();
+
+ return true;
+ }
+
+ private TextDocument GetTextDocument()
+ {
+ return _dte.ActiveDocument.Object("TextDocument") as TextDocument;
+ }
+
+ protected override bool IsEnabled()
+ {
+ if (TextView != null && TextView.Selection.SelectedSpans.Count > 0)
+ {
+ return TextView.Selection.SelectedSpans[0].Length > 0;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/Shared/MinifySelectionCommandTarget.cs b/EditorExtensions/Commands/Shared/MinifySelectionCommandTarget.cs
new file mode 100644
index 000000000..99c104569
--- /dev/null
+++ b/EditorExtensions/Commands/Shared/MinifySelectionCommandTarget.cs
@@ -0,0 +1,48 @@
+using EnvDTE80;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class MinifySelection : CommandTargetBase
+ {
+ private DTE2 _dte;
+
+ public MinifySelection(IVsTextView adapter, IWpfTextView textView)
+ : base(adapter, textView, GuidList.guidMinifyCmdSet, PkgCmdIDList.MinifySelection)
+ {
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ protected override bool Execute(uint commandId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ if (TextView != null)
+ {
+ _dte.UndoContext.Open("Minify");
+
+ string content = TextView.Selection.SelectedSpans[0].GetText();
+ string extension = Path.GetExtension(_dte.ActiveDocument.FullName).ToLowerInvariant();
+ string result = MinifyFileMenu.MinifyString(extension, content);
+
+ TextView.TextBuffer.Replace(TextView.Selection.SelectedSpans[0].Span, result);
+
+ _dte.UndoContext.Close();
+ }
+
+ return true;
+ }
+
+ protected override bool IsEnabled()
+ {
+ if (TextView != null && TextView.Selection.SelectedSpans.Count > 0)
+ {
+ return TextView.Selection.SelectedSpans[0].Length > 0;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Commands/Shared/TextCreationListener.cs b/EditorExtensions/Commands/Shared/TextCreationListener.cs
new file mode 100644
index 000000000..9d5c8c5e0
--- /dev/null
+++ b/EditorExtensions/Commands/Shared/TextCreationListener.cs
@@ -0,0 +1,28 @@
+using Microsoft.VisualStudio.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using Microsoft.VisualStudio.Utilities;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IVsTextViewCreationListener))]
+ [ContentType("text")]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ class TextViewCreationListener : IVsTextViewCreationListener
+ {
+ [Import, System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; }
+
+ [Import]
+ internal ICompletionBroker CompletionBroker { get; set; }
+
+ public void VsTextViewCreated(IVsTextView textViewAdapter)
+ {
+ var textView = EditorAdaptersFactoryService.GetWpfTextView(textViewAdapter);
+
+ textView.Properties.GetOrCreateSingletonProperty(() => new EncodeSelection(textViewAdapter, textView));
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/TypeThrough/CSSTypeThroughControllerProvider.cs b/EditorExtensions/Commands/TypeThrough/CSSTypeThroughControllerProvider.cs
new file mode 100644
index 000000000..8c37932ad
--- /dev/null
+++ b/EditorExtensions/Commands/TypeThrough/CSSTypeThroughControllerProvider.cs
@@ -0,0 +1,84 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.Web.Editor;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IIntellisenseControllerProvider))]
+ [ContentType(CssContentTypeDefinition.CssContentType)]
+ [Name("CSS Type Through Completion Controller")]
+ [Order(Before = "Default Completion Controller")]
+ [TextViewRole(PredefinedTextViewRoles.Editable)]
+ internal class CssTypeThroughControllerProvider : IIntellisenseControllerProvider
+ {
+ public IIntellisenseController TryCreateIntellisenseController(ITextView view, IList subjectBuffers)
+ {
+ if (subjectBuffers[0].ContentType.IsOfType(CssContentTypeDefinition.CssContentType))
+ {
+ var completionController = ServiceManager.GetService(subjectBuffers[0]);
+
+ if (completionController == null)
+ completionController = new CssTypeThroughController(view, subjectBuffers);
+
+ return completionController;
+ }
+
+ return null;
+ }
+ }
+
+ internal class CssTypeThroughController : TypeThroughController
+ {
+ public CssTypeThroughController(ITextView textView, IList subjectBuffers)
+ : base(textView, subjectBuffers)
+ {
+ }
+
+ protected override bool CanComplete(ITextBuffer textBuffer, int position)
+ {
+ var document = CssEditorDocument.FromTextBuffer(textBuffer);
+
+ var item = document.StyleSheet.ComplexItemFromRange(position, 0);
+ if (item is Comment)
+ return false;
+
+ var tokenItem = document.StyleSheet.ItemFromRange(position, 0);
+ var ti = tokenItem as TokenItem;
+ if (ti != null)
+ {
+ if ((ti.TokenType == CssTokenType.String || ti.TokenType == CssTokenType.MultilineString) && !ti.IsUnclosed)
+ return false;
+ }
+
+ return true;
+ }
+
+ protected override char GetCompletionCharacter(char typedCharacter)
+ {
+ switch (typedCharacter)
+ {
+ //case '\"':
+ //case '\'':
+ // return typedCharacter;
+
+ //case '[':
+ // return ']';
+
+ //case '(':
+ // return ')';
+
+ case '{':
+ return '}';
+ }
+
+ return '\0';
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/TypeThrough/JavaScriptTypeThroughControllerProvider.cs b/EditorExtensions/Commands/TypeThrough/JavaScriptTypeThroughControllerProvider.cs
new file mode 100644
index 000000000..d3b7004b6
--- /dev/null
+++ b/EditorExtensions/Commands/TypeThrough/JavaScriptTypeThroughControllerProvider.cs
@@ -0,0 +1,76 @@
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.Web.Editor;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IIntellisenseControllerProvider))]
+ [ContentType("JavaScript")]
+ [ContentType("TypeScript")]
+ [Name("JavaScript Type Through Completion Controller")]
+ [Order(Before = "Default Completion Controller")]
+ [TextViewRole(PredefinedTextViewRoles.Editable)]
+ internal class JavaScriptTypeThroughControllerProvider : IIntellisenseControllerProvider
+ {
+ public IIntellisenseController TryCreateIntellisenseController(ITextView view, IList subjectBuffers)
+ {
+ if (subjectBuffers.Count > 0 && (subjectBuffers[0].ContentType.IsOfType("JavaScript") || subjectBuffers[0].ContentType.IsOfType("TypeScript")))
+ {
+ var completionController = ServiceManager.GetService(subjectBuffers[0]);
+
+ if (completionController == null)
+ completionController = new JavaScriptTypeThroughController(view, subjectBuffers);
+
+ return completionController;
+ }
+
+ return null;
+ }
+ }
+
+ internal class JavaScriptTypeThroughController : TypeThroughController
+ {
+ public JavaScriptTypeThroughController(ITextView textView, IList subjectBuffers)
+ : base(textView, subjectBuffers)
+ {
+ }
+
+ protected override bool CanComplete(ITextBuffer textBuffer, int position)
+ {
+ bool result = WESettings.GetBoolean(WESettings.Keys.JavaScriptAutoCloseBraces);
+
+ if (result)
+ {
+ var line = textBuffer.CurrentSnapshot.GetLineFromPosition(position);
+ result = line.Start.Position + line.GetText().TrimEnd('\r', '\n', ' ', ';', ',').Length == position + 1;
+ }
+
+ return result;
+ }
+
+ protected override char GetCompletionCharacter(char typedCharacter)
+ {
+ switch (typedCharacter)
+ {
+ //case '\"':
+ //case '\'':
+ // return typedCharacter;
+
+ case '[':
+ return ']';
+
+ case '(':
+ return ')';
+
+ case '{':
+ return '}';
+ }
+
+ return '\0';
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/TypeThrough/LESSTypeThroughControllerProvider.cs b/EditorExtensions/Commands/TypeThrough/LESSTypeThroughControllerProvider.cs
new file mode 100644
index 000000000..5a4996557
--- /dev/null
+++ b/EditorExtensions/Commands/TypeThrough/LESSTypeThroughControllerProvider.cs
@@ -0,0 +1,58 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.Less.Core;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.Web.Editor;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IIntellisenseControllerProvider))]
+ [ContentType(LessContentTypeDefinition.LessContentType)]
+ [Name("LESS Type Through Completion Controller")]
+ [Order(Before = "Default Completion Controller")]
+ [TextViewRole(PredefinedTextViewRoles.Editable)]
+ internal class LessTypeThroughControllerProvider : IIntellisenseControllerProvider
+ {
+ public IIntellisenseController TryCreateIntellisenseController(ITextView view, IList subjectBuffers)
+ {
+ var completionController = ServiceManager.GetService(subjectBuffers[0]);
+
+ if (completionController == null)
+ completionController = new LessTypeThroughController(view, subjectBuffers);
+
+ return completionController;
+ }
+ }
+
+ internal class LessTypeThroughController : CssTypeThroughController
+ {
+ public LessTypeThroughController(ITextView view, IList subjectBuffers) :
+ base(view, subjectBuffers)
+ {
+ }
+
+ protected override bool CanComplete(ITextBuffer textBuffer, int position)
+ {
+ var document = CssEditorDocument.FromTextBuffer(textBuffer);
+
+ var item = document.StyleSheet.ComplexItemFromRange(position, 0);
+ if (item is CppComment || item is CppCommentText)
+ return false;
+
+ var tokenItem = document.StyleSheet.ItemFromRange(position, 0);
+ var ti = tokenItem as TokenItem;
+ if (ti != null)
+ {
+ if (ti.Token.IsComment)
+ return false;
+ }
+
+ return base.CanComplete(textBuffer, position);
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/TypeThrough/ProvisionalText.cs b/EditorExtensions/Commands/TypeThrough/ProvisionalText.cs
new file mode 100644
index 000000000..38505bf7f
--- /dev/null
+++ b/EditorExtensions/Commands/TypeThrough/ProvisionalText.cs
@@ -0,0 +1,264 @@
+using System;
+using System.Linq;
+using System.ComponentModel.Composition;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Shapes;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Projection;
+using Microsoft.VisualStudio.Utilities;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IWpfTextViewCreationListener))]
+ [ContentType("CSS")]
+ [ContentType("JavaScript")]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ internal sealed class HtmlProvisionalTextHighlightFactory : IWpfTextViewCreationListener
+ {
+ [Export(typeof(AdornmentLayerDefinition))]
+ [Name("HtmlProvisionalTextHighlight")]
+ [Order(Before = PredefinedAdornmentLayers.Outlining)]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ public AdornmentLayerDefinition EditorAdornmentLayer { get; set; }
+
+ public void TextViewCreated(IWpfTextView textView)
+ {
+ }
+ }
+
+ public class ProvisionalText
+ {
+ public static bool IgnoreChange { get; set; }
+
+ public event EventHandler OnClose;
+ public char ProvisionalChar { get; private set; }
+ public ITrackingSpan TrackingSpan { get; private set; }
+
+ private ITextView _textView;
+ private IAdornmentLayer _layer;
+ private Path _highlightAdornment;
+ private Brush _highlightBrush;
+ private bool _overtype = false;
+ private bool _delete = false;
+ private bool _projectionsChanged = false;
+ private bool _adornmentRemoved = false;
+ private IProjectionBuffer _projectionBuffer;
+
+ public ProvisionalText(ITextView textView, Span textSpan)
+ {
+ IgnoreChange = false;
+
+ _textView = textView;
+
+ var wpfTextView = _textView as IWpfTextView;
+ _layer = wpfTextView.GetAdornmentLayer("HtmlProvisionalTextHighlight");
+
+ var textBuffer = _textView.TextBuffer;
+ var snapshot = textBuffer.CurrentSnapshot;
+ var provisionalCharSpan = new Span(textSpan.End - 1, 1);
+
+ TrackingSpan = snapshot.CreateTrackingSpan(textSpan, SpanTrackingMode.EdgeExclusive);
+ _textView.Caret.PositionChanged += OnCaretPositionChanged;
+
+ textBuffer.Changed += OnTextBufferChanged;
+ textBuffer.PostChanged += OnPostChanged;
+
+ var _projectionBuffer = _textView.TextBuffer as IProjectionBuffer;
+ if (_projectionBuffer != null)
+ {
+ _projectionBuffer.SourceSpansChanged += OnSourceSpansChanged;
+ }
+
+ Color highlightColor = SystemColors.HighlightColor;
+ Color baseColor = Color.FromArgb(96, highlightColor.R, highlightColor.G, highlightColor.B);
+ _highlightBrush = new SolidColorBrush(baseColor);
+
+ ProvisionalChar = snapshot.GetText(provisionalCharSpan)[0];
+ HighlightSpan(provisionalCharSpan.Start);
+ }
+
+ public Span CurrentSpan
+ {
+ get
+ {
+ return TrackingSpan.GetSpan(_textView.TextBuffer.CurrentSnapshot);
+ }
+ }
+
+ private void EndTracking()
+ {
+ if (_textView != null)
+ {
+ ClearHighlight();
+
+ if (_projectionBuffer != null)
+ {
+ _projectionBuffer.SourceSpansChanged -= OnSourceSpansChanged;
+ _projectionBuffer = null;
+ }
+
+ if (_projectionsChanged || _adornmentRemoved)
+ {
+ _projectionsChanged = false;
+ _adornmentRemoved = false;
+ }
+
+ _textView.TextBuffer.Changed -= OnTextBufferChanged;
+ _textView.TextBuffer.PostChanged -= OnPostChanged;
+
+ _textView.Caret.PositionChanged -= OnCaretPositionChanged;
+ _textView = null;
+
+ if (OnClose != null)
+ OnClose(this, EventArgs.Empty);
+ }
+ }
+
+ public bool IsPositionInSpan(int position)
+ {
+ if (_textView != null)
+ {
+ if (CurrentSpan.Contains(position) && position > CurrentSpan.Start)
+ return true;
+ }
+
+ return false;
+ }
+
+ private void OnSourceSpansChanged(object sender, ProjectionSourceSpansChangedEventArgs e)
+ {
+ _projectionsChanged = true;
+ Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => ResoreHighlight()));
+ }
+
+ void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
+ {
+ // If caret moves outside of the text tracking span, consider text final
+ var position = _textView.Caret.Position.BufferPosition;
+
+ if (!CurrentSpan.Contains(position) || position == CurrentSpan.Start)
+ {
+ EndTracking();
+ }
+ }
+
+ void OnPostChanged(object sender, EventArgs e)
+ {
+ if (_textView != null && !IgnoreChange && !_projectionsChanged)
+ {
+ if (_overtype || _delete)
+ {
+ _textView.TextBuffer.Replace(new Span(CurrentSpan.End - 1, 1), String.Empty);
+ EndTracking();
+ }
+ else
+ {
+ HighlightSpan(CurrentSpan.End - 1);
+ }
+ }
+ }
+
+ void OnTextBufferChanged(object sender, TextContentChangedEventArgs e)
+ {
+ // Zero changes typically means secondary buffer regeneration
+ if (e.Changes.Count == 0)
+ {
+ _projectionsChanged = true;
+ Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => ResoreHighlight()));
+ }
+
+ if (_textView != null && !IgnoreChange && !_projectionsChanged)
+ {
+ // If there is a change outside text span or change over provisional
+ // text, we are done here: commit provisional text and disconnect.
+
+ if (CurrentSpan.Length > 0 && e.Changes.Count == 1)
+ {
+ var change = e.Changes[0];
+
+ if (CurrentSpan.Contains(change.OldSpan))
+ {
+ // Check provisional text overtype
+ if (change.OldLength == 0 && change.NewLength == 1 && change.OldPosition == CurrentSpan.End - 2)
+ {
+ char ch = _textView.TextBuffer.CurrentSnapshot.GetText(change.NewPosition, 1)[0];
+
+ if (ch == ProvisionalChar)
+ _overtype = true;
+ }
+ else if (change.NewLength > 0 && change.NewText.Last() == ProvisionalChar)//(change.NewLength == 0 && change.OldLength > 0 && change.OldPosition == CurrentSpan.Start)
+ {
+ // Deleting open quote or brace should also delete provisional character
+ _delete = true;
+ }
+
+ return;
+ }
+ }
+
+ EndTracking();
+ }
+ }
+
+ private void ResoreHighlight()
+ {
+ if (_textView != null && (_projectionsChanged || _adornmentRemoved))
+ {
+ HighlightSpan(CurrentSpan.End - 1);
+ }
+
+ _projectionsChanged = false;
+ _adornmentRemoved = false;
+ }
+
+ void HighlightSpan(int bufferPosition)
+ {
+ ClearHighlight();
+
+ var wpfTextView = _textView as IWpfTextView;
+ var snapshotSpan = new SnapshotSpan(wpfTextView.TextBuffer.CurrentSnapshot, new Span(bufferPosition, 1));
+
+ Geometry highlightGeometry = wpfTextView.TextViewLines.GetTextMarkerGeometry(snapshotSpan);
+ if (highlightGeometry != null)
+ {
+ _highlightAdornment = new Path();
+ _highlightAdornment.Data = highlightGeometry;
+ _highlightAdornment.Fill = _highlightBrush;
+ }
+
+ if (_highlightAdornment != null)
+ {
+ _layer.AddAdornment(
+ AdornmentPositioningBehavior.TextRelative, snapshotSpan,
+ this, _highlightAdornment, new AdornmentRemovedCallback(OnAdornmentRemoved));
+ }
+ }
+
+ private bool _removing = false;
+
+ private void OnAdornmentRemoved(object tag, UIElement element)
+ {
+ if (_removing)
+ return;
+
+ _adornmentRemoved = true;
+ Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => ResoreHighlight()));
+ }
+
+ private void ClearHighlight()
+ {
+ if (_highlightAdornment != null)
+ {
+ _removing = true;
+
+ _layer.RemoveAdornment(_highlightAdornment);
+ _highlightAdornment = null;
+
+ _removing = false;
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/Commands/TypeThrough/TypeThroughController.cs b/EditorExtensions/Commands/TypeThrough/TypeThroughController.cs
new file mode 100644
index 000000000..2f54e8a6c
--- /dev/null
+++ b/EditorExtensions/Commands/TypeThrough/TypeThroughController.cs
@@ -0,0 +1,252 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace MadsKristensen.EditorExtensions
+{
+ public class TypeThroughController : IIntellisenseController
+ {
+ ITextBuffer _textBuffer;
+ ITextView _textView;
+
+ List _provisionalTexts = new List();
+ char _typedChar = '\0';
+ bool _processing = false;
+ int _caretPosition = 0;
+ int _bufferVersionWaterline;
+
+ public TypeThroughController(ITextView textView, IList subjectBuffers)
+ {
+ _textBuffer = subjectBuffers[0];
+ _textView = textView;
+
+ _textBuffer.Changed += TextBuffer_Changed;
+ _textBuffer.PostChanged += TextBuffer_PostChanged;
+
+ _bufferVersionWaterline = _textBuffer.CurrentSnapshot.Version.ReiteratedVersionNumber;
+ }
+
+ protected virtual bool CanComplete(ITextBuffer textBuffer, int position)
+ {
+ return true;
+ }
+
+ void TextBuffer_PostChanged(object sender, System.EventArgs e)
+ {
+ if (!_processing && _typedChar != '\0')
+ {
+ OnPostTypeChar(_typedChar);
+ _typedChar = '\0';
+ }
+ }
+
+ void TextBuffer_Changed(object sender, TextContentChangedEventArgs e)
+ {
+ if (_processing)
+ return;
+
+ _typedChar = '\0';
+
+ if (e.Changes.Count == 1 && e.AfterVersion.ReiteratedVersionNumber > _bufferVersionWaterline)
+ {
+ var change = e.Changes[0];
+
+ _bufferVersionWaterline = e.AfterVersion.ReiteratedVersionNumber;
+
+ // Change length may be > 1 in autoformatting languages.
+ // However, there will be only one non-ws character in the change.
+ // Be careful when is inserted: the change won't
+ // actually be in this buffer.
+
+ var snapshot = _textBuffer.CurrentSnapshot;
+ if (change.NewSpan.End <= snapshot.Length)
+ {
+ var text = _textBuffer.CurrentSnapshot.GetText(change.NewSpan);
+ text = text.Trim();
+
+ if (text.Length == 1)
+ {
+ // Allow completion of different characters inside spans, but not when
+ // character and its completion pair is the same. For example, we do
+ // want to complete () in foo(bar|) when user types ( after bar. However,
+ // we do not want to complete " when user is typing in a string which
+ // was already completed and instead " should be a terminating type-through.
+
+ var typedChar = text[0];
+ var completionChar = GetCompletionCharacter(typedChar);
+
+ var caretPosition = GetCaretPositionInBuffer();
+ if (caretPosition.HasValue)
+ {
+ bool compatible = true;
+
+ var innerText = GetInnerProvisionalText();
+ if (innerText != null)
+ compatible = IsCompatibleCharacter(innerText.ProvisionalChar, typedChar);
+
+ if (!IsPositionInProvisionalText(caretPosition.Value) || typedChar != completionChar || compatible)
+ {
+ _typedChar = typedChar;
+ _caretPosition = caretPosition.Value;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected virtual char GetCompletionCharacter(char typedCharacter)
+ {
+ switch (typedCharacter)
+ {
+ //case '\"':
+ //case '\'':
+ // return typedCharacter;
+
+ //case '[':
+ // return ']';
+
+ //case '(':
+ // return ')';
+
+ case '{':
+ case '}':
+ return '}';
+ }
+
+ return '\0';
+ }
+
+ protected virtual bool IsCompatibleCharacter(char primaryCharacter, char candidateCharacter)
+ {
+ if (primaryCharacter == '\"' || primaryCharacter == '\'')
+ return false; // no completion in strings
+
+ return true;
+ }
+
+ private void OnPostTypeChar(char typedCharacter)
+ {
+ // When language autoformats, like JS, caret may be in a very different
+ // place by now. Check if store caret position still makes sense and
+ // if not, reacquire it. In contained language scenario
+ // current caret position may be beyond projection boundary like when
+ // typing at the end of onclick="return foo(".
+
+ //var settings = WebEditor.GetSettings(_textBuffer.ContentType.TypeName);
+ //if (settings.GetBoolean(CommonSettings.InsertMatchingBracesKey))
+ if (WESettings.GetBoolean(WESettings.Keys.AutoCloseCurlyBraces))
+ {
+ char completionCharacter = GetCompletionCharacter(typedCharacter);
+ if (completionCharacter != '\0')
+ {
+ var viewCaretPosition = _textView.Caret.Position.BufferPosition;
+ _processing = true;
+
+ var bufferCaretPosition = GetCaretPositionInBuffer();
+ if (bufferCaretPosition.HasValue)
+ {
+ _caretPosition = bufferCaretPosition.Value;
+ }
+ else if (viewCaretPosition.Position == _textView.TextBuffer.CurrentSnapshot.Length)
+ {
+ _caretPosition = _textBuffer.CurrentSnapshot.Length;
+ }
+
+ if (_caretPosition > 0)
+ {
+ if (CanComplete(_textBuffer, _caretPosition - 1))
+ {
+ ProvisionalText.IgnoreChange = true;
+ _textView.TextBuffer.Replace(new Span(viewCaretPosition, 0), completionCharacter.ToString());
+ ProvisionalText.IgnoreChange = false;
+
+ _textView.Caret.MoveTo(new SnapshotPoint(_textView.TextBuffer.CurrentSnapshot, viewCaretPosition));
+
+ var provisionalText = new ProvisionalText(_textView, new Span(viewCaretPosition - 1, 2));
+ provisionalText.OnClose += new System.EventHandler(OnCloseProvisionalText);
+
+ _provisionalTexts.Add(provisionalText);
+ }
+ }
+ }
+
+ _processing = false;
+ }
+ }
+
+ private SnapshotPoint? GetCaretPositionInBuffer()
+ {
+ var viewCaretPosition = _textView.Caret.Position.BufferPosition.Position;
+ var snapshot = _textView.TextBuffer.CurrentSnapshot;
+
+ if (viewCaretPosition > snapshot.Length)
+ return null;
+
+ return _textView.BufferGraph.MapDownToBuffer(
+ new SnapshotPoint(_textView.TextBuffer.CurrentSnapshot, viewCaretPosition), PointTrackingMode.Positive,
+ _textBuffer, PositionAffinity.Predecessor);
+ }
+
+ private bool IsPositionInProvisionalText(int position)
+ {
+ foreach (var pt in _provisionalTexts)
+ {
+ if (pt.IsPositionInSpan(position))
+ return true;
+ }
+
+ return false;
+ }
+
+ private ProvisionalText GetInnerProvisionalText()
+ {
+ int minLength = Int32.MaxValue;
+ ProvisionalText innerText = null;
+
+ foreach (var pt in _provisionalTexts)
+ {
+ if (pt.CurrentSpan.Length < minLength)
+ {
+ minLength = pt.CurrentSpan.Length;
+ innerText = pt;
+ }
+ }
+
+ return innerText;
+ }
+
+ private void OnCloseProvisionalText(object sender, EventArgs e)
+ {
+ _provisionalTexts.Remove(sender as ProvisionalText);
+ }
+
+ #region IIntellisenseController Members
+
+ public void ConnectSubjectBuffer(ITextBuffer subjectBuffer)
+ {
+ if (_textBuffer != null)
+ {
+ _textBuffer.Changed += TextBuffer_Changed;
+ _textBuffer.PostChanged += TextBuffer_PostChanged;
+ }
+ }
+
+ public void Detach(ITextView textView)
+ {
+ }
+
+ public void DisconnectSubjectBuffer(ITextBuffer subjectBuffer)
+ {
+ if (_textBuffer != null)
+ {
+ _textBuffer.Changed -= TextBuffer_Changed;
+ _textBuffer.PostChanged -= TextBuffer_PostChanged;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/EditorExtensions/Completion/CompletionListEntry.cs b/EditorExtensions/Completion/CompletionListEntry.cs
new file mode 100644
index 000000000..a3cd2ae5b
--- /dev/null
+++ b/EditorExtensions/Completion/CompletionListEntry.cs
@@ -0,0 +1,77 @@
+using System;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class CompletionListEntry : ICssCompletionListEntry
+ {
+ private string _name;
+
+ public CompletionListEntry(string name, int sortingPriority = 0)
+ {
+ _name = name;
+ SortingPriority = sortingPriority;
+ }
+
+ public string Description
+ {
+ get { return string.Empty; }
+ }
+
+ public string DisplayText
+ {
+ get { return _name; }
+ }
+
+ public string GetSyntax(Version version)
+ {
+ return string.Empty;
+ }
+
+ public StandardGlyphGroup StandardGlyph
+ {
+ get { return StandardGlyphGroup.GlyphGroupEnumMember; }
+ }
+
+ public string GetAttribute(string name)
+ {
+ return string.Empty;
+ }
+
+ public string GetInsertionText(CssTextSource textSource, ITrackingSpan typingSpan)
+ {
+ return DisplayText;
+ }
+
+ public string GetVersionedAttribute(string name, Version version)
+ {
+ return GetAttribute(name);
+ }
+
+ public bool AllowQuotedString
+ {
+ get { return false; }
+ }
+
+ public bool IsBuilder
+ {
+ get { return false; }
+ }
+
+ public int SortingPriority { get; set; }
+
+
+ public bool IsSupported(BrowserVersion browser)
+ {
+ return true;
+ }
+
+ public bool IsSupported(Version cssVersion)
+ {
+ return true;
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/CompletionProviders/AnimationNameCompletionProvider.cs b/EditorExtensions/Completion/CompletionProviders/AnimationNameCompletionProvider.cs
new file mode 100644
index 000000000..f9c3b8473
--- /dev/null
+++ b/EditorExtensions/Completion/CompletionProviders/AnimationNameCompletionProvider.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionProvider))]
+ [Name("AnimationNameCompletionProvider")]
+ internal class AnimationNameCompletionProvider : ICssCompletionListProvider
+ {
+ public CssCompletionContextType ContextType
+ {
+ get { return CssCompletionContextType.PropertyValue; }
+ }
+
+ public IEnumerable GetListEntries(CssCompletionContext context)
+ {
+ HashSet entries = new HashSet();
+ Declaration dec = context.ContextItem.FindType();
+
+ if (dec == null || dec.PropertyName == null || (!dec.PropertyName.Text.EndsWith("animation-name", StringComparison.OrdinalIgnoreCase) && dec.PropertyName.Text != "animation"))
+ return entries;
+
+ StyleSheet stylesheet = context.ContextItem.StyleSheet;
+ var visitor = new CssItemCollector();
+ stylesheet.Accept(visitor);
+
+ foreach (KeyFramesDirective keyframes in visitor.Items)
+ {
+ if (!entries.Any(e => e.DisplayText.Equals(keyframes.Name.Text, StringComparison.OrdinalIgnoreCase)))
+ entries.Add(new CompletionListEntry(keyframes.Name.Text));
+ }
+
+ return entries;
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/CompletionProviders/ClassCompletionProvider.cs b/EditorExtensions/Completion/CompletionProviders/ClassCompletionProvider.cs
new file mode 100644
index 000000000..76710f230
--- /dev/null
+++ b/EditorExtensions/Completion/CompletionProviders/ClassCompletionProvider.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionProvider))]
+ [Name("ClassCompletionProvider")]
+ internal class ClassCompletionProvider : ICssCompletionListProvider
+ {
+ public CssCompletionContextType ContextType
+ {
+ get { return (CssCompletionContextType)602; }
+ }
+
+ public IEnumerable GetListEntries(CssCompletionContext context)
+ {
+ HashSet classNames = new HashSet();
+ HashSet entries = new HashSet();
+
+ StyleSheet stylesheet = context.ContextItem.StyleSheet;
+ var visitorRules = new CssItemCollector();
+ stylesheet.Accept(visitorRules);
+
+ foreach (ClassSelector item in visitorRules.Items)
+ {
+ if (item != context.ContextItem && !classNames.Contains(item.Text))
+ {
+ classNames.Add(item.Text);
+ entries.Add(new CompletionListEntry(item.Text));
+ }
+ }
+
+ return entries;
+ }
+
+ }
+}
diff --git a/EditorExtensions/Completion/CompletionProviders/ColorCompletionProvider.cs b/EditorExtensions/Completion/CompletionProviders/ColorCompletionProvider.cs
new file mode 100644
index 000000000..aefb808b7
--- /dev/null
+++ b/EditorExtensions/Completion/CompletionProviders/ColorCompletionProvider.cs
@@ -0,0 +1,49 @@
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Drawing;
+using System.Reflection;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionProvider))]
+ [Name("ColorCompletionProvider")]
+ [Order(Before = "Default PropertyValue")]
+ internal class ColorCompletionProvider : ICssCompletionListProvider, ICssCompletionPresenterProvider
+ {
+ public CssCompletionContextType ContextType
+ {
+ get { return CssCompletionContextType.PropertyValue; }
+ }
+
+ public IEnumerable GetListEntries(CssCompletionContext context)
+ {
+ yield break;
+ }
+
+ public CompletionPresenterInfo TryCreateCompletionPresenter(ICompletionSession session, CssCompletionContext context)
+ {
+ string text = context.Snapshot.GetText(context.SpanStart, context.SpanLength);
+ if (Color.FromName(text).IsKnownColor)
+ {
+ return CreatePresenter(session, context);
+ }
+
+ return new CompletionPresenterInfo(null, true);
+ }
+
+ private static CompletionPresenterInfo CreatePresenter(ICompletionSession session, CssCompletionContext context)
+ {
+ object[] parameters = new object[] { session, context };
+ BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
+ Type type = typeof(CssClassifier).Assembly.GetType("Microsoft.CSS.Editor.ColorPickerPresenter");
+ object colorPicker = Activator.CreateInstance(type, flags, null, parameters, null);
+
+ return new CompletionPresenterInfo((IIntellisensePresenter)colorPicker, false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Completion/CompletionProviders/FontCompletionProvider.cs b/EditorExtensions/Completion/CompletionProviders/FontCompletionProvider.cs
new file mode 100644
index 000000000..7e74e032e
--- /dev/null
+++ b/EditorExtensions/Completion/CompletionProviders/FontCompletionProvider.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Linq;
+using System.Windows.Forms;
+using System.Windows.Threading;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionProvider))]
+ [Name("FontCompletionProvider")]
+ internal class FontCompletionProvider : ICssCompletionListProvider, ICssCompletionCommitListener
+ {
+ private static List _emptyList = new List();
+ public CssCompletionContextType ContextType
+ {
+ get { return CssCompletionContextType.PropertyValue; }
+ }
+
+ public static bool IsFontFamilyContext(CssCompletionContext context)
+ {
+ if (context != null && context.ContextItem != null)
+ {
+ Declaration decl = context.ContextItem.Parent as Declaration;
+ string propertyName = (decl != null && decl.PropertyName != null) ? decl.PropertyName.Text : string.Empty;
+
+ // Currently, only "font-family" will show font names, so just hard-code that name.
+ if (propertyName == "font-family")
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public IEnumerable GetListEntries(CssCompletionContext context)
+ {
+ if (IsFontFamilyContext(context))
+ {
+ List entries = new List();
+ List idNames = new List();
+
+ StyleSheet stylesheet = context.ContextItem.StyleSheet;
+ var visitorRules = new CssItemCollector();
+ stylesheet.Accept(visitorRules);
+
+ foreach (FontFaceDirective item in visitorRules.Items)
+ {
+ var visitorDec = new CssItemCollector();
+ item.Block.Accept(visitorDec);
+
+ Declaration family = visitorDec.Items.FirstOrDefault(i => i.PropertyName.Text == "font-family");
+
+ if (family != null)
+ {
+ string value = string.Join(string.Empty, family.Values.Select(v => v.Text));
+ entries.Add(new FontFamilyCompletionListEntry(value.Trim('\'', '"')));
+ }
+ }
+
+ entries.Add(new FontFamilyCompletionListEntry("Pick from file..."));
+
+ return entries;
+ }
+
+ return _emptyList;
+ }
+
+ public void OnCommitted(ICssCompletionListEntry entry, ITrackingSpan contextSpan, SnapshotPoint caret, ITextView textView)
+ {
+ if (entry.DisplayText == "Pick from file...")
+ {
+ string fontFamily;
+ string atDirective = GetFontFromFile(entry.DisplayText, (IWpfTextView)textView, out fontFamily);
+
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => Replace(contextSpan, textView, atDirective, fontFamily)), DispatcherPriority.Normal);
+
+ }
+ }
+
+ private static void Replace(ITrackingSpan contextSpan, ITextView textView, string atDirective, string fontFamily)
+ {
+ EditorExtensionsPackage.DTE.UndoContext.Open("Embed font");
+ textView.TextBuffer.Insert(0, atDirective + Environment.NewLine + Environment.NewLine);
+ textView.TextBuffer.Insert(contextSpan.GetSpan(textView.TextBuffer.CurrentSnapshot).Start, fontFamily);
+ EditorExtensionsPackage.DTE.UndoContext.Close();
+
+ }
+
+ private static object _syncRoot = new object();
+ private string GetFontFromFile(string text, IWpfTextView view, out string fontFamily)
+ {
+ lock (_syncRoot)
+ {
+ fontFamily = text;
+ OpenFileDialog dialog = new OpenFileDialog();
+ dialog.InitialDirectory = Path.GetDirectoryName(EditorExtensionsPackage.DTE.ActiveDocument.FullName);
+ dialog.Filter = "Fonts (*.woff;*.eot;*.ttf;*.otf;*.svg)|*.woff;*.eot;*.ttf;*.otf;*.svg";
+ dialog.DefaultExt = ".woff";
+
+ if (dialog.ShowDialog() == DialogResult.OK)
+ {
+ FontDropHandler fdh = new FontDropHandler(view);
+ return fdh.GetCodeFromFile(dialog.FileName, out fontFamily);
+ }
+
+ return text;
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/CompletionProviders/FontFamilyCompletionProvider.cs b/EditorExtensions/Completion/CompletionProviders/FontFamilyCompletionProvider.cs
new file mode 100644
index 000000000..0aa82bb6d
--- /dev/null
+++ b/EditorExtensions/Completion/CompletionProviders/FontFamilyCompletionProvider.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionProvider))]
+ [Name("FontFamilyCompletionProvider")]
+ internal class FontFamilyCompletionProvider : ICssCompletionListProvider
+ {
+ private static List _entryCache = new List()
+ {
+ "Arial, 'DejaVu Sans', 'Liberation Sans', Freesans, sans-serif",
+ "'Arial Narrow', 'Nimbus Sans L', sans-serif",
+ "'Arial Black', Gadget, sans-serif",
+ "'Bookman Old Style', Bookman, 'URW Bookman L', 'Palatino Linotype', serif",
+ "'Century Gothic', futura, 'URW Gothic L', Verdana, sans-serif",
+ "'Comic Sans MS', cursive",
+ "Consolas, 'Lucida Console', 'DejaVu Sans Mono', monospace",
+ "'Courier New', Courier, 'Nimbus Mono L', monospace",
+ "Constantina, Georgia, 'Nimbus Roman No9 L', serif",
+ "Helvetica, Arial, 'DejaVu Sans', 'Liberation Sans', Freesans, sans-serif",
+ "Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif",
+ "'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', 'DejaVu Sans Condensed', sans-serif",
+ "Cambria, 'Palatino Linotype', 'Book Antiqua', 'URW Palladio L', serif",
+ "symbol, 'Standard Symbols L'",
+ "Cambria, 'Times New Roman', 'Nimbus Roman No9 L', 'Freeserif', Times, serif",
+ "Verdana, Geneva, 'DejaVu Sans', sans-serif",
+ "'Monotype Corsiva', 'Apple Chancery', 'ITC Zapf Chancery', 'URW Chancery L', cursive",
+ "'Monotype Sorts', dingbats, 'ITC Zapf Dingbats', fantasy"
+ };
+
+ public CssCompletionContextType ContextType
+ {
+ get { return CssCompletionContextType.PropertyValue; }
+ }
+
+ public IEnumerable GetListEntries(CssCompletionContext context)
+ {
+ Declaration dec = context.ContextItem.FindType();
+
+ if (dec == null || dec.PropertyName == null || dec.PropertyName.Text != "font-family")
+ yield break;
+
+ foreach (string item in _entryCache)
+ {
+ ICssCompletionListEntry entry = new CompletionListEntry(item, 1);
+ yield return entry;
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/CompletionProviders/IdCompletionProvider.cs b/EditorExtensions/Completion/CompletionProviders/IdCompletionProvider.cs
new file mode 100644
index 000000000..4a8b2d07c
--- /dev/null
+++ b/EditorExtensions/Completion/CompletionProviders/IdCompletionProvider.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionProvider))]
+ [Name("IdCompletionProvider")]
+ internal class IdCompletionProvider : ICssCompletionListProvider
+ {
+ public CssCompletionContextType ContextType
+ {
+ get { return (CssCompletionContextType)603; }
+ }
+
+ public IEnumerable GetListEntries(CssCompletionContext context)
+ {
+ List idNames = new List();
+ List entries = new List();
+
+ StyleSheet stylesheet = context.ContextItem.StyleSheet;
+ var visitorRules = new CssItemCollector();
+ stylesheet.Accept(visitorRules);
+
+ foreach (IdSelector item in visitorRules.Items)
+ {
+ if (item != context.ContextItem && !idNames.Contains(item.Text))
+ {
+ idNames.Add(item.Text);
+ entries.Add(new CompletionListEntry(item.Text));
+ }
+ }
+
+ return entries;
+ }
+
+ }
+}
diff --git a/EditorExtensions/Completion/CompletionProviders/ImportantCompletionProvider.cs b/EditorExtensions/Completion/CompletionProviders/ImportantCompletionProvider.cs
new file mode 100644
index 000000000..ce33b0aee
--- /dev/null
+++ b/EditorExtensions/Completion/CompletionProviders/ImportantCompletionProvider.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionProvider))]
+ [Name("ImportantCompletionProvider")]
+ internal class ImportantCompletionProvider : ICssCompletionListProvider
+ {
+ public CssCompletionContextType ContextType
+ {
+ get { return CssCompletionContextType.PropertyValue; }
+ }
+
+ public IEnumerable GetListEntries(CssCompletionContext context)
+ {
+ List entries = new List();
+ Declaration dec = context.ContextItem.FindType();
+ if (dec == null || dec.Colon == null || dec.Important != null || dec.Values.Count == 0)
+ return entries;
+
+ ParseItem before = dec.ItemBeforePosition(context.SpanStart);
+ if (before != null && before.Text == "!")
+ entries.Add(new CompletionListEntry("important", 1));
+
+ return entries;
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/CompletionProviders/RegionCompletionProvider.cs b/EditorExtensions/Completion/CompletionProviders/RegionCompletionProvider.cs
new file mode 100644
index 000000000..2c1f40f66
--- /dev/null
+++ b/EditorExtensions/Completion/CompletionProviders/RegionCompletionProvider.cs
@@ -0,0 +1,44 @@
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionProvider))]
+ [Name("RegionCompletionProvider")]
+ internal class RegionCompletionProvider : ICssCompletionListProvider, ICssCompletionCommitListener
+ {
+ private RegionCompletionListEntry _entry = new RegionCompletionListEntry();
+
+ public CssCompletionContextType ContextType
+ {
+ get { return (CssCompletionContextType)601; } //ItemName
+ }
+
+ public IEnumerable GetListEntries(CssCompletionContext context)
+ {
+ var line = context.Snapshot.GetLineFromPosition(context.ContextItem.Start);
+ string text = line.GetText().Trim();
+
+ if (text.Length == context.ContextItem.Length)
+ {
+ yield return _entry;
+ }
+ }
+
+ public void OnCommitted(ICssCompletionListEntry entry, ITrackingSpan contextSpan, SnapshotPoint caret, ITextView textView)
+ {
+ if (entry.DisplayText == "Add region...")
+ {
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => System.Windows.Forms.SendKeys.Send("{TAB}")), DispatcherPriority.Normal);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Completion/CompletionProviders/TagCompletionProvider.cs b/EditorExtensions/Completion/CompletionProviders/TagCompletionProvider.cs
new file mode 100644
index 000000000..c0fa109e0
--- /dev/null
+++ b/EditorExtensions/Completion/CompletionProviders/TagCompletionProvider.cs
@@ -0,0 +1,154 @@
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.VisualStudio.Utilities;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionProvider))]
+ [Name("TagCompletionProvider")]
+ internal class TagCompletionProvider : ICssCompletionListProvider
+ {
+ private static IEnumerable _entryCache = GetListEntriesCache();
+
+ public CssCompletionContextType ContextType
+ {
+ get { return (CssCompletionContextType)601; }
+ }
+
+ public IEnumerable GetListEntries(CssCompletionContext context)
+ {
+ return _entryCache;
+ }
+
+ private static IEnumerable GetListEntriesCache()
+ {
+ List entries = new List();
+
+ entries.Add(new CompletionListEntry("a"));
+ entries.Add(new CompletionListEntry("abbr"));
+ entries.Add(new CompletionListEntry("acronym"));
+ entries.Add(new CompletionListEntry("address"));
+ //entries.Add(new CompletionListEntry("applet"));
+ entries.Add(new CompletionListEntry("area"));
+ entries.Add(new CompletionListEntry("article"));
+ entries.Add(new CompletionListEntry("aside"));
+ entries.Add(new CompletionListEntry("audio"));
+ entries.Add(new CompletionListEntry("b"));
+ //entries.Add(new CompletionListEntry("base"));
+ //entries.Add(new CompletionListEntry("basefont"));
+ entries.Add(new CompletionListEntry("bdi"));
+ entries.Add(new CompletionListEntry("bdo"));
+ entries.Add(new CompletionListEntry("big"));
+ entries.Add(new CompletionListEntry("blockquote"));
+ entries.Add(new CompletionListEntry("body"));
+ //entries.Add(new CompletionListEntry("br"));
+ entries.Add(new CompletionListEntry("button"));
+ entries.Add(new CompletionListEntry("canvas"));
+ entries.Add(new CompletionListEntry("caption"));
+ entries.Add(new CompletionListEntry("center"));
+ entries.Add(new CompletionListEntry("cite"));
+ entries.Add(new CompletionListEntry("code"));
+ entries.Add(new CompletionListEntry("col"));
+ entries.Add(new CompletionListEntry("colgroup"));
+ //entries.Add(new CompletionListEntry("command"));
+ entries.Add(new CompletionListEntry("datalist"));
+ entries.Add(new CompletionListEntry("dd"));
+ entries.Add(new CompletionListEntry("del"));
+ entries.Add(new CompletionListEntry("details"));
+ entries.Add(new CompletionListEntry("dfn"));
+ //entries.Add(new CompletionListEntry("dir"));
+ entries.Add(new CompletionListEntry("div"));
+ entries.Add(new CompletionListEntry("dl"));
+ entries.Add(new CompletionListEntry("dt"));
+ entries.Add(new CompletionListEntry("em"));
+ entries.Add(new CompletionListEntry("embed"));
+ entries.Add(new CompletionListEntry("fieldset"));
+ entries.Add(new CompletionListEntry("figcaption"));
+ entries.Add(new CompletionListEntry("figure"));
+ //entries.Add(new CompletionListEntry("font"));
+ entries.Add(new CompletionListEntry("footer"));
+ entries.Add(new CompletionListEntry("form"));
+ //entries.Add(new CompletionListEntry("frame"));
+ //entries.Add(new CompletionListEntry("frameset"));
+ entries.Add(new CompletionListEntry("h1"));
+ entries.Add(new CompletionListEntry("h2"));
+ entries.Add(new CompletionListEntry("h3"));
+ entries.Add(new CompletionListEntry("h4"));
+ entries.Add(new CompletionListEntry("h5"));
+ entries.Add(new CompletionListEntry("h6"));
+ //entries.Add(new CompletionListEntry("head"));
+ entries.Add(new CompletionListEntry("header"));
+ entries.Add(new CompletionListEntry("hgroup"));
+ entries.Add(new CompletionListEntry("hr"));
+ entries.Add(new CompletionListEntry("html"));
+ entries.Add(new CompletionListEntry("i"));
+ entries.Add(new CompletionListEntry("iframe"));
+ entries.Add(new CompletionListEntry("img"));
+ entries.Add(new CompletionListEntry("input"));
+ entries.Add(new CompletionListEntry("ins"));
+ //entries.Add(new CompletionListEntry("keygen"));
+ entries.Add(new CompletionListEntry("kbd"));
+ entries.Add(new CompletionListEntry("label"));
+ entries.Add(new CompletionListEntry("legend"));
+ entries.Add(new CompletionListEntry("li"));
+ //entries.Add(new CompletionListEntry("link"));
+ entries.Add(new CompletionListEntry("map"));
+ entries.Add(new CompletionListEntry("mark"));
+ entries.Add(new CompletionListEntry("menu"));
+ //entries.Add(new CompletionListEntry("meta"));
+ entries.Add(new CompletionListEntry("meter"));
+ entries.Add(new CompletionListEntry("nav"));
+ //entries.Add(new CompletionListEntry("noframes"));
+ //entries.Add(new CompletionListEntry("noscript"));
+ entries.Add(new CompletionListEntry("object"));
+ entries.Add(new CompletionListEntry("ol"));
+ entries.Add(new CompletionListEntry("optgroup"));
+ entries.Add(new CompletionListEntry("option"));
+ entries.Add(new CompletionListEntry("output"));
+ entries.Add(new CompletionListEntry("p"));
+ entries.Add(new CompletionListEntry("param"));
+ entries.Add(new CompletionListEntry("pre"));
+ entries.Add(new CompletionListEntry("progress"));
+ entries.Add(new CompletionListEntry("q"));
+ entries.Add(new CompletionListEntry("rp"));
+ entries.Add(new CompletionListEntry("rt"));
+ entries.Add(new CompletionListEntry("ruby"));
+ entries.Add(new CompletionListEntry("s"));
+ entries.Add(new CompletionListEntry("samp"));
+ //entries.Add(new CompletionListEntry("script"));
+ entries.Add(new CompletionListEntry("section"));
+ entries.Add(new CompletionListEntry("select"));
+ entries.Add(new CompletionListEntry("small"));
+ //entries.Add(new CompletionListEntry("source"));
+ entries.Add(new CompletionListEntry("span"));
+ //entries.Add(new CompletionListEntry("strike"));
+ entries.Add(new CompletionListEntry("strong"));
+ entries.Add(new CompletionListEntry("style"));
+ entries.Add(new CompletionListEntry("sub"));
+ entries.Add(new CompletionListEntry("summary"));
+ entries.Add(new CompletionListEntry("sup"));
+ entries.Add(new CompletionListEntry("svg"));
+ entries.Add(new CompletionListEntry("table"));
+ entries.Add(new CompletionListEntry("tbody"));
+ entries.Add(new CompletionListEntry("td"));
+ entries.Add(new CompletionListEntry("textarea"));
+ entries.Add(new CompletionListEntry("tfoot"));
+ entries.Add(new CompletionListEntry("th"));
+ entries.Add(new CompletionListEntry("thead"));
+ entries.Add(new CompletionListEntry("time"));
+ //entries.Add(new CompletionListEntry("title"));
+ entries.Add(new CompletionListEntry("tr"));
+ entries.Add(new CompletionListEntry("track"));
+ entries.Add(new CompletionListEntry("tt"));
+ entries.Add(new CompletionListEntry("u"));
+ entries.Add(new CompletionListEntry("ul"));
+ entries.Add(new CompletionListEntry("var"));
+ entries.Add(new CompletionListEntry("video"));
+ //entries.Add(new CompletionListEntry("wbr"));
+
+ return entries;
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/CompletionProviders/UrlPickerCompletionProvider.cs b/EditorExtensions/Completion/CompletionProviders/UrlPickerCompletionProvider.cs
new file mode 100644
index 000000000..a636d2512
--- /dev/null
+++ b/EditorExtensions/Completion/CompletionProviders/UrlPickerCompletionProvider.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Threading;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionProvider))]
+ [Name("UrlPickerCompletionProvider")]
+ internal class UrlPickerCompletionProvider : ICssCompletionListProvider, ICssCompletionCommitListener
+ {
+ private static List _imageExtensions = new List() { "", ".png", ".jpg", "gif", ".svg", ".jpeg", ".bmp", ".tif", ".tiff" };
+ public CssCompletionContextType ContextType
+ {
+ get { return (CssCompletionContextType)604; }
+ }
+
+ public IEnumerable GetListEntries(CssCompletionContext context)
+ {
+ UrlItem urlItem = (UrlItem)context.ContextItem;
+
+ string url = urlItem.UrlString != null ? urlItem.UrlString.Text : string.Empty;
+ string directory = GetDirectory(url);
+
+ if (url.StartsWith("http") || url.Contains("//") || url.Contains(";base64,") || !Directory.Exists(directory))
+ yield break;
+
+ foreach (string item in Directory.GetFileSystemEntries(directory))
+ {
+ string entry = item.Substring(item.LastIndexOf("\\") + 1);
+
+ //if (_imageExtensions.Contains(Path.GetExtension(entry)))
+ yield return new UrlPickerCompletionListEntry(entry);
+ }
+ }
+
+ private static string GetDirectory(string url)
+ {
+ if (url == "/" || url.LastIndexOf('/') == 0)
+ return GetRootFolder();
+
+ return GetRelativeFolder(url);
+ }
+
+ private static string GetRelativeFolder(string url)
+ {
+ int end = Math.Max(0, url.LastIndexOf('/'));
+ return ProjectHelpers.ToAbsoluteFilePath(url.Substring(0, end));
+ }
+
+ private static string GetRootFolder()
+ {
+ string root = ProjectHelpers.GetRootFolder();
+ if (File.Exists(root))
+ return Path.GetDirectoryName(root);
+
+ return root;
+ }
+
+ public void OnCommitted(ICssCompletionListEntry entry, Microsoft.VisualStudio.Text.ITrackingSpan contextSpan, Microsoft.VisualStudio.Text.SnapshotPoint caret, Microsoft.VisualStudio.Text.Editor.ITextView textView)
+ {
+ if (Path.GetExtension(entry.DisplayText).Length == 0)
+ {
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => CssCompletionController.FromView(textView).OnShowMemberList(filterList: true)), DispatcherPriority.Normal);
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/ContextProviders/ClassContextProvider.cs b/EditorExtensions/Completion/ContextProviders/ClassContextProvider.cs
new file mode 100644
index 000000000..17e5093e9
--- /dev/null
+++ b/EditorExtensions/Completion/ContextProviders/ClassContextProvider.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionContextProvider))]
+ [Name("ClassCompletionContextProvider")]
+ internal class ClassCompletionContextProvider : ICssCompletionContextProvider
+ {
+ public ClassCompletionContextProvider()
+ {
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get
+ {
+ return new Type[] { typeof(ClassSelector), };
+ }
+ }
+
+ public CssCompletionContext GetCompletionContext(ParseItem item, int position)
+ {
+ return new CssCompletionContext((CssCompletionContextType)602, item.Start, item.Length, null);
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/ContextProviders/IdContextProvider.cs b/EditorExtensions/Completion/ContextProviders/IdContextProvider.cs
new file mode 100644
index 000000000..51424f6cd
--- /dev/null
+++ b/EditorExtensions/Completion/ContextProviders/IdContextProvider.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionContextProvider))]
+ [Name("IdCompletionContextProvider")]
+ internal class IdCompletionContextProvider : ICssCompletionContextProvider
+ {
+ public IdCompletionContextProvider()
+ {
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get
+ {
+ return new Type[] { typeof(IdSelector), };
+ }
+ }
+
+ public CssCompletionContext GetCompletionContext(ParseItem item, int position)
+ {
+ return new CssCompletionContext((CssCompletionContextType)603, item.Start, item.Length, null);
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/ContextProviders/LessPseudoContextProvider.cs b/EditorExtensions/Completion/ContextProviders/LessPseudoContextProvider.cs
new file mode 100644
index 000000000..ff818d58b
--- /dev/null
+++ b/EditorExtensions/Completion/ContextProviders/LessPseudoContextProvider.cs
@@ -0,0 +1,43 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.Less.Core;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionContextProvider))]
+ [Name("LessPseudoContextProvider")]
+ [Order(Before = "Default Pseudo")]
+ internal class LessPseudoContextProvider : ICssCompletionContextProvider
+ {
+ public IEnumerable ItemTypes
+ {
+ get
+ {
+ return new Type[]
+ {
+ typeof(PseudoClassFunctionSelector),
+ typeof(PseudoClassSelector),
+ typeof(PseudoElementFunctionSelector),
+ typeof(PseudoElementSelector)
+ };
+ }
+ }
+
+ public CssCompletionContext GetCompletionContext(ParseItem item, int position)
+ {
+ RuleSet rule = item.FindType();
+
+ if (rule != null && rule.Parent is LessRuleBlock)
+ {
+ return new CssCompletionContext(CssCompletionContextType.Invalid, item.Start, item.Length, item);
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Completion/ContextProviders/TagContextProvider.cs b/EditorExtensions/Completion/ContextProviders/TagContextProvider.cs
new file mode 100644
index 000000000..32a74710e
--- /dev/null
+++ b/EditorExtensions/Completion/ContextProviders/TagContextProvider.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionContextProvider))]
+ [Name("TagCompletionContextProvider")]
+ internal class TagCompletionContextProvider : ICssCompletionContextProvider
+ {
+ public TagCompletionContextProvider()
+ {
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get
+ {
+ return new Type[] { typeof(ItemName), };
+ }
+ }
+
+ public CssCompletionContext GetCompletionContext(ParseItem item, int position)
+ {
+ return new CssCompletionContext((CssCompletionContextType)601, item.Start, item.Length, null);
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/ContextProviders/UrlPickerContextProvider.cs b/EditorExtensions/Completion/ContextProviders/UrlPickerContextProvider.cs
new file mode 100644
index 000000000..66bf6672e
--- /dev/null
+++ b/EditorExtensions/Completion/ContextProviders/UrlPickerContextProvider.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssCompletionContextProvider))]
+ [Name("UrlPickerCompletionContextProvider")]
+ internal class UrlPickerCompletionContextProvider : ICssCompletionContextProvider
+ {
+ public UrlPickerCompletionContextProvider()
+ {
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get
+ {
+ return new Type[] { typeof(UrlItem), };
+ }
+ }
+
+ public CssCompletionContext GetCompletionContext(ParseItem item, int position)
+ {
+ UrlItem urlItem = (UrlItem)item;
+ int start = item.Start + 4;
+ int length = 0;
+
+ if (urlItem.UrlString != null)
+ {
+ start = urlItem.UrlString.Start;
+ length = urlItem.UrlString.Length;
+
+ int relative = position - start;
+ int lastSlash = urlItem.UrlString.Text.LastIndexOf('/');
+ if (lastSlash < relative)
+ {
+ start = start + lastSlash + 1;
+ length = length - (lastSlash + 1);
+ }
+ }
+
+ return new CssCompletionContext((CssCompletionContextType)604, start, length, null);
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/CustomCompletionListEntry.cs b/EditorExtensions/Completion/CustomCompletionListEntry.cs
new file mode 100644
index 000000000..34b3d3292
--- /dev/null
+++ b/EditorExtensions/Completion/CustomCompletionListEntry.cs
@@ -0,0 +1,72 @@
+using System;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class CustomCompletionListEntry : ICssCompletionListEntry
+ {
+ private string _insertion;
+
+ public CustomCompletionListEntry(string name, string insertion)
+ {
+ this.DisplayText = name;
+ _insertion = insertion;
+ }
+
+
+ public string Description { get; set; }
+
+ public string DisplayText { get; set; }
+
+ public string GetSyntax(Version version)
+ {
+ return string.Empty;
+ }
+
+ public StandardGlyphGroup StandardGlyph
+ {
+ get { return StandardGlyphGroup.GlyphGroupEnumMember; }
+ }
+
+ public string GetAttribute(string name)
+ {
+ return string.Empty;
+ }
+
+ public string GetInsertionText(CssTextSource textSource, ITrackingSpan typingSpan)
+ {
+ return _insertion;
+ }
+
+ public string GetVersionedAttribute(string name, Version version)
+ {
+ return GetAttribute(name);
+ }
+
+ public bool AllowQuotedString
+ {
+ get { return false; }
+ }
+
+ public bool IsBuilder
+ {
+ get { return false; }
+ }
+
+ public int SortingPriority { get; set; }
+
+
+ public bool IsSupported(BrowserVersion browser)
+ {
+ return true;
+ }
+
+ public bool IsSupported(Version cssVersion)
+ {
+ return true;
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/Filter/GradientCompletionListFilter.cs b/EditorExtensions/Completion/Filter/GradientCompletionListFilter.cs
new file mode 100644
index 000000000..168f7961d
--- /dev/null
+++ b/EditorExtensions/Completion/Filter/GradientCompletionListFilter.cs
@@ -0,0 +1,97 @@
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Reflection;
+
+namespace MadsKristensen.EditorExtensions.Completion
+{
+ [Export(typeof(ICssCompletionListFilter))]
+ [Name("Gradient Filter")]
+ internal class GradientCompletionListFilter : ICssCompletionListFilter
+ {
+ [Import]
+ private IGlyphService _glyphService = null;
+
+ public void FilterCompletionList(IList completions, CssCompletionContext context)
+ {
+ if (context.ContextType != CssCompletionContextType.PropertyValue)
+ return;
+
+ for (int i = 0; i < completions.Count; i++)
+ {
+ CssSchemaCompletionEntry entry = completions[i] as CssSchemaCompletionEntry;
+
+ if (entry != null && entry.DisplayText.Contains("gradient("))
+ {
+ var cce = CreateCompletionEntry(context, entry);
+ cce.FilterType = entry.FilterType;
+ cce.IsBuilder = entry.IsBuilder;
+
+ completions[i] = cce;
+ }
+ }
+ }
+
+ private CssSchemaCompletionEntry CreateCompletionEntry(CssCompletionContext context, CssSchemaCompletionEntry entry)
+ {
+ CustomCompletionListEntry interim = new CustomCompletionListEntry(entry.DisplayText, GetArguments(entry.DisplayText));
+ interim.Description = entry.Description;
+
+ object[] parameters = new object[]
+ {
+ interim,
+ entry.CompletionProvider,
+ CssTextSource.Document,
+ context.Snapshot.CreateTrackingSpan(context.SpanStart, context.SpanLength, SpanTrackingMode.EdgeExclusive),
+ _glyphService
+ };
+
+ BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance;
+ return (CssSchemaCompletionEntry)Activator.CreateInstance(typeof(CssSchemaCompletionEntry), flags, null, parameters, null);
+ }
+
+ private static string GetArguments(string functionName)
+ {
+ switch (functionName)
+ {
+ case "linear-gradient()":
+ case "-webkit-linear-gradient()":
+ case "-ms-linear-gradient()":
+ case "-moz-linear-gradient()":
+ case "-o-linear-gradient()":
+ return functionName.Replace("()", "(top, #1e5799 0%, #7db9e8 100%)");
+
+ case "-webkit-gradient()":
+ return functionName.Replace("()", "(linear, left top, left bottom, color-stop(0%,#1e5799), color-stop(100%,#7db9e8))");
+
+ case "radial-gradient()":
+ case "-webkit-radial-gradient()":
+ case "-ms-radial-gradient()":
+ case "-moz-radial-gradient()":
+ case "-o-radial-gradient()":
+ return functionName.Replace("()", "(50px 50px, circle closest-side, black, white)");
+
+ case "repeating-linear-gradient()":
+ case "-webkit-repeating-linear-gradient()":
+ case "-ms-repeating-linear-gradient()":
+ case "-moz-repeating-linear-gradient()":
+ case "-o-repeating-linear-gradient()":
+ return functionName.Replace("()", "(red, blue 20px, red 40px)");
+
+ case "repeating-radial-gradient()":
+ case "-webkit-repeating-radial-gradient()":
+ case "-ms-repeating-radial-gradient()":
+ case "-moz-repeating-radial-gradient()":
+ case "-o-repeating-radial-gradient()":
+ return functionName.Replace("()", "(red, blue 20px, red 40px)");
+ }
+
+ return functionName;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Completion/Filter/HideInheritInitialCompletionListFilter.cs b/EditorExtensions/Completion/Filter/HideInheritInitialCompletionListFilter.cs
new file mode 100644
index 000000000..a496624ec
--- /dev/null
+++ b/EditorExtensions/Completion/Filter/HideInheritInitialCompletionListFilter.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions.Completion
+{
+ [Export(typeof(ICssCompletionListFilter))]
+ [Name("Inherit/Initial Filter")]
+ internal class HideInheritInitialCompletionListFilter : ICssCompletionListFilter
+ {
+ public void FilterCompletionList(IList completions, CssCompletionContext context)
+ {
+ if (context.ContextType != CssCompletionContextType.PropertyValue || WESettings.GetBoolean(WESettings.Keys.ShowInitialInherit))
+ return;
+
+ foreach (CssCompletionEntry entry in completions)
+ {
+ if (entry.DisplayText == "initial" || entry.DisplayText == "inherit")
+ {
+ entry.FilterType = CompletionEntryFilterType.NeverVisible;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Completion/Filter/HideUnsupportedCompletionListFilter.cs b/EditorExtensions/Completion/Filter/HideUnsupportedCompletionListFilter.cs
new file mode 100644
index 000000000..cecee7a6e
--- /dev/null
+++ b/EditorExtensions/Completion/Filter/HideUnsupportedCompletionListFilter.cs
@@ -0,0 +1,41 @@
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.CSS.Editor.Schemas;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssSchemaFilterProvider))]
+ [Name("HideUnsupportedSchemaFilterProvider")]
+ internal class HideUnsupportedSchemaFilterProvider : ICssSchemaFilterProvider
+ {
+ public ICssSchemaFilter CreateFilter(ICssSchemaManager schemaManager, ITextBuffer textBuffer)
+ {
+ return textBuffer.Properties.GetOrCreateSingletonProperty(() => new HideUnsupportedSchemaFilter());
+ }
+ }
+
+ internal class HideUnsupportedSchemaFilter : ICssSchemaFilter
+ {
+ public bool IsSupported(Version cssVersion, ICssCompletionListEntry entry)
+ {
+ if (WESettings.GetBoolean(WESettings.Keys.ShowUnsupported))
+ return entry.IsSupported(cssVersion);
+
+ return entry.GetAttribute("browsers") != "none" || entry.DisplayText.Contains("gradient");
+ }
+
+ public string Name
+ {
+ get { return WESettings.GetBoolean(WESettings.Keys.ShowUnsupported) ? string.Empty : "WE"; }
+ }
+
+ public bool Equals(ICssSchemaFilter other)
+ {
+ return other.Name.Equals(Name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Completion/Filter/WebkitScrollbarCompletionListFilter.cs b/EditorExtensions/Completion/Filter/WebkitScrollbarCompletionListFilter.cs
new file mode 100644
index 000000000..695bb1f3f
--- /dev/null
+++ b/EditorExtensions/Completion/Filter/WebkitScrollbarCompletionListFilter.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions.Completion
+{
+ [Export(typeof(ICssCompletionListFilter))]
+ [Name("WebkitScrollbarCompletionListFilter")]
+ internal class WebkitScrollbarCompletionListFilter : ICssCompletionListFilter
+ {
+ private static readonly StringCollection _cache = new StringCollection()
+ {
+ ":horizontal",
+ ":vertical",
+ ":decrement",
+ ":increment",
+ ":start",
+ ":end",
+ ":double-button",
+ ":single-button",
+ ":no-button",
+ ":corner-present",
+ ":window-inactive",
+ };
+
+ public void FilterCompletionList(IList completions, CssCompletionContext context)
+ {
+ if (context.ContextType != CssCompletionContextType.PseudoClassOrElement)
+ return;
+
+ ParseItem prev = context.ContextItem.PreviousSibling;
+ bool hasScrollbar = false;
+
+ if (prev != null)
+ {
+ hasScrollbar = prev.Text.Contains(":-webkit-resizer") || prev.Text.Contains(":-webkit-scrollbar");
+ }
+
+ foreach (CssCompletionEntry entry in completions)
+ {
+ if (hasScrollbar)
+ {
+ entry.FilterType = _cache.Contains(entry.DisplayText) ? entry.FilterType : CompletionEntryFilterType.NeverVisible;
+ }
+ else
+ {
+ entry.FilterType = !_cache.Contains(entry.DisplayText) ? entry.FilterType : CompletionEntryFilterType.NeverVisible;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Completion/FontFamilyCompletionListEntry.cs b/EditorExtensions/Completion/FontFamilyCompletionListEntry.cs
new file mode 100644
index 000000000..4b98ee913
--- /dev/null
+++ b/EditorExtensions/Completion/FontFamilyCompletionListEntry.cs
@@ -0,0 +1,112 @@
+using System;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ ///
+ /// This represents a font family in the completion list
+ ///
+ internal class FontFamilyCompletionListEntry : ICssCompletionListEntry
+ {
+ private string _name;
+
+ public FontFamilyCompletionListEntry(string name)
+ {
+ _name = name ?? string.Empty;
+ }
+
+ public string DisplayText
+ {
+ get { return _name; }
+ }
+
+ public string Description
+ {
+ get { return string.Empty; }
+ }
+
+ public string GetSyntax(Version version)
+ {
+ return string.Empty;
+ }
+
+ public string GetAttribute(string name)
+ {
+ return string.Empty;
+ }
+
+ public string GetVersionedAttribute(string name, System.Version version)
+ {
+ return string.Empty;
+ }
+
+ public string GetInsertionText(CssTextSource textSource, ITrackingSpan typingSpan)
+ {
+ string text = DisplayText;
+ bool needsQuote = text.IndexOf(' ') != -1;
+ if (text == "Pick from file...")
+ {
+ return string.Empty;
+ }
+
+ if (needsQuote)
+ {
+ // Prefer to use single quotes, but if the inline style uses single quotes, then use double quotes.
+ char quote = (textSource == CssTextSource.InlineStyleSingleQuote) ? '"' : '\'';
+
+ if (typingSpan != null)
+ {
+ // If the user already typed a quote, then use it
+
+ string typingText = typingSpan.GetText(typingSpan.TextBuffer.CurrentSnapshot);
+
+ if (!string.IsNullOrEmpty(typingText) && (typingText[0] == '"' || typingText[0] == '\''))
+ {
+ quote = typingText[0];
+ }
+ }
+
+ if (text != null && text.IndexOf(quote) == -1)
+ {
+ text = quote.ToString() + text + quote.ToString();
+ }
+ }
+
+ return text;
+ }
+
+ public StandardGlyphGroup StandardGlyph
+ {
+ get { return StandardGlyphGroup.GlyphGroupEnumMember; }
+ }
+
+ public bool AllowQuotedString
+ {
+ get { return true; }
+ }
+
+ public bool IsBuilder
+ {
+ get { return DisplayText == "Pick from file..."; }
+ }
+
+ public int SortingPriority
+ {
+ get { return 0; }
+ }
+
+
+ public bool IsSupported(BrowserVersion browser)
+ {
+ return true;
+ }
+
+ public bool IsSupported(Version cssVersion)
+ {
+ return true;
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/RegionCompletionListEntry.cs b/EditorExtensions/Completion/RegionCompletionListEntry.cs
new file mode 100644
index 000000000..8c5c8fd08
--- /dev/null
+++ b/EditorExtensions/Completion/RegionCompletionListEntry.cs
@@ -0,0 +1,69 @@
+using System;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class RegionCompletionListEntry : ICssCompletionListEntry
+ {
+ public string Description
+ {
+ get { return string.Empty; }
+ }
+
+ public string DisplayText
+ {
+ get { return "Add region..."; }
+ }
+
+ public string GetSyntax(Version version)
+ {
+ return string.Empty;
+ }
+
+ public StandardGlyphGroup StandardGlyph
+ {
+ get { return StandardGlyphGroup.GlyphCSharpExpansion; }
+ }
+
+ public string GetAttribute(string name)
+ {
+ return string.Empty;
+ }
+
+ public string GetInsertionText(CssTextSource textSource, ITrackingSpan typingSpan)
+ {
+ return "region";//"/*#region MyRegion */\n\n\n\n/*#endregion*/";
+ }
+
+ public string GetVersionedAttribute(string name, Version version)
+ {
+ return GetAttribute(name);
+ }
+
+ public bool AllowQuotedString
+ {
+ get { return false; }
+ }
+
+ public bool IsBuilder
+ {
+ get { return true; }
+ }
+
+ public int SortingPriority { get; set; }
+
+
+ public bool IsSupported(BrowserVersion browser)
+ {
+ return true;
+ }
+
+ public bool IsSupported(Version cssVersion)
+ {
+ return true;
+ }
+ }
+}
diff --git a/EditorExtensions/Completion/UrlPickerCompletionListEntry.cs b/EditorExtensions/Completion/UrlPickerCompletionListEntry.cs
new file mode 100644
index 000000000..cf9d8cee5
--- /dev/null
+++ b/EditorExtensions/Completion/UrlPickerCompletionListEntry.cs
@@ -0,0 +1,89 @@
+using System;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class UrlPickerCompletionListEntry : ICssCompletionListEntry
+ {
+ private string _name;
+
+ public UrlPickerCompletionListEntry(string name)
+ {
+ _name = name;
+ }
+
+ public bool AllowQuotedString
+ {
+ get { return false; }
+ }
+
+ public string Description
+ {
+ get { return string.Empty; }
+ }
+
+ public string DisplayText
+ {
+ get { return _name; }
+ }
+
+ public string GetSyntax(Version version)
+ {
+ return string.Empty;
+ }
+
+ public int SortingPriority
+ {
+ get { return IsFolder ? 1 : 0; }
+ }
+
+ public bool IsBuilder
+ {
+ get { return false; }
+ }
+
+ public StandardGlyphGroup StandardGlyph
+ {
+ get { return IsFolder ? StandardGlyphGroup.GlyphClosedFolder : StandardGlyphGroup.GlyphBscFile; }
+ }
+
+ public string GetAttribute(string name)
+ {
+ return string.Empty;
+ }
+
+ public string GetInsertionText(CssTextSource textSource, ITrackingSpan typingSpan)
+ {
+ if (IsFolder)
+ {
+ return DisplayText + "/";
+ }
+
+ return DisplayText;
+ }
+
+ public string GetVersionedAttribute(string name, Version version)
+ {
+ return GetAttribute(name);
+ }
+
+ private bool IsFolder
+ {
+ get { return !DisplayText.Contains("."); }
+ }
+
+
+ public bool IsSupported(BrowserVersion browser)
+ {
+ return true;
+ }
+
+ public bool IsSupported(Version cssVersion)
+ {
+ return true;
+ }
+ }
+}
diff --git a/EditorExtensions/DropTargets/BundleDrop.cs b/EditorExtensions/DropTargets/BundleDrop.cs
new file mode 100644
index 000000000..61dc38798
--- /dev/null
+++ b/EditorExtensions/DropTargets/BundleDrop.cs
@@ -0,0 +1,83 @@
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Editor.DragDrop;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IDropHandlerProvider))]
+ [DropFormat("CF_VSSTGPROJECTITEMS")]
+ [Name("BundleDropDropHandler")]
+ [ContentType("XML")]
+ [Order(Before = "DefaultFileDropHandler")]
+ internal class BundleDropHandlerProvider : IDropHandlerProvider
+ {
+ public IDropHandler GetAssociatedDropHandler(IWpfTextView view)
+ {
+ return view.Properties.GetOrCreateSingletonProperty(() => new BundleDropHandler(view));
+ }
+ }
+
+ internal class BundleDropHandler : IDropHandler
+ {
+ private IWpfTextView _view;
+ private readonly List _allowedExtensions = new List { ".css", ".less", ".js", ".coffee", ".ts" };
+ private string _draggedFilename;
+ private string _format = Environment.NewLine + "\t/{0}";
+
+ public BundleDropHandler(IWpfTextView view)
+ {
+ this._view = view;
+ }
+
+ public DragDropPointerEffects HandleDataDropped(DragDropInfo dragDropInfo)
+ {
+ string reference = FileHelpers.RelativePath(ProjectHelpers.GetRootFolder(), _draggedFilename);
+
+ if (reference.StartsWith("http://localhost:"))
+ {
+ int index = reference.IndexOf('/', 20);
+ if (index > -1)
+ reference = reference.Substring(index + 1).ToLowerInvariant();
+ }
+
+ _view.TextBuffer.Insert(dragDropInfo.VirtualBufferPosition.Position.Position, string.Format(_format, reference));
+
+ return DragDropPointerEffects.Copy;
+ }
+
+ public void HandleDragCanceled()
+ {
+
+ }
+
+ public DragDropPointerEffects HandleDragStarted(DragDropInfo dragDropInfo)
+ {
+ return DragDropPointerEffects.All;
+ }
+
+ public DragDropPointerEffects HandleDraggingOver(DragDropInfo dragDropInfo)
+ {
+ return DragDropPointerEffects.All;
+ }
+
+ public bool IsDropEnabled(DragDropInfo dragDropInfo)
+ {
+ _draggedFilename = FontDropHandler.GetImageFilename(dragDropInfo);
+
+ if (!string.IsNullOrEmpty(_draggedFilename))
+ {
+ string fileExtension = Path.GetExtension(_draggedFilename).ToLowerInvariant();
+ if (this._allowedExtensions.Contains(fileExtension))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/EditorExtensions/DropTargets/FontDrop.cs b/EditorExtensions/DropTargets/FontDrop.cs
new file mode 100644
index 000000000..9c8aeb693
--- /dev/null
+++ b/EditorExtensions/DropTargets/FontDrop.cs
@@ -0,0 +1,251 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Linq;
+using System.Windows.Forms;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Editor.DragDrop;
+using Microsoft.VisualStudio.Utilities;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IDropHandlerProvider))]
+ [DropFormat("FileDrop")]
+ [DropFormat("CF_VSSTGPROJECTITEMS")]
+ [Name("FontDropHandler")]
+ [ContentType("CSS")]
+ [ContentType("LESS")]
+ [Order(Before = "DefaultFileDropHandler")]
+ internal class FontDropHandlerProvider : IDropHandlerProvider
+ {
+ public IDropHandler GetAssociatedDropHandler(IWpfTextView view)
+ {
+ return view.Properties.GetOrCreateSingletonProperty(() => new FontDropHandler(view));
+ }
+ }
+
+ internal class FontDropHandler : IDropHandler
+ {
+ IWpfTextView view;
+ private readonly Dictionary formats = new Dictionary()
+ {
+ {".ttf", " format('truetype')"},
+ {".woff", ""},
+ {".eot", ""},
+ {".otf", " format('opentype')"}
+ };
+ private string draggedFilename;
+ private string fontName = string.Empty;
+ string fontFace = "@font-face {{\n\tfont-family: {0};\n\tsrc: {1};\n}}";
+ string fontUrls = "url('{0}'){1}";
+ //ITextDocument document;
+
+ public FontDropHandler(IWpfTextView view)
+ {
+ this.view = view;
+ //view.TextDataModel.DocumentBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document);
+ }
+
+ public DragDropPointerEffects HandleDataDropped(DragDropInfo dragDropInfo)
+ {
+ if (File.Exists(draggedFilename))
+ {
+ //var files = GetRelativeFiles(draggedFilename);
+ //string[] sources = new string[files.Count()];
+
+ //for (int i = 0; i < files.Count(); i++)
+ //{
+ // string file = files.ElementAt(i);
+ // string extension = Path.GetExtension(file).ToLowerInvariant();
+ // string reference = RelativePath(document.FilePath, file);
+
+ // if (reference.StartsWith("http://localhost:"))
+ // {
+ // int index = reference.IndexOf('/', 24);
+ // if (index > -1)
+ // reference = reference.Substring(index + 1).ToLowerInvariant();
+ // }
+
+ // sources[i] = string.Format(fontUrls, reference, formats[extension]);
+ //}
+
+ //string sourceUrls = string.Join(", ", sources);
+ string fontFamily;
+ view.TextBuffer.Insert(dragDropInfo.VirtualBufferPosition.Position.Position, GetCodeFromFile(draggedFilename, out fontFamily));
+
+ return DragDropPointerEffects.Copy;
+ }
+ else if (draggedFilename.StartsWith("http://localhost:"))
+ {
+ //int index = draggedFilename.IndexOf('/', 24);
+ //if (index > -1)
+ // draggedFilename = draggedFilename.Substring(index).ToLowerInvariant();
+
+ //string extension = Path.GetExtension(draggedFilename).ToLowerInvariant();
+ //string sourceUrl = string.Format(fontUrls, draggedFilename, formats[extension]);
+
+ view.TextBuffer.Insert(dragDropInfo.VirtualBufferPosition.Position.Position, GetCodeFromLocalhost(draggedFilename));
+
+ return DragDropPointerEffects.Copy;
+ }
+ else
+ {
+ return DragDropPointerEffects.None;
+ }
+ }
+
+ public string GetCodeFromFile(string fileName, out string fontFamily)
+ {
+ var files = GetRelativeFiles(fileName);
+ string[] sources = new string[files.Count()];
+
+ for (int i = 0; i < files.Count(); i++)
+ {
+ string file = files.ElementAt(i);
+ string extension = Path.GetExtension(file).ToLowerInvariant();
+ string reference = FileHelpers.RelativePath(EditorExtensionsPackage.DTE.ActiveDocument.FullName, file);
+
+ if (reference.StartsWith("http://localhost:"))
+ {
+ int index = reference.IndexOf('/', 24);
+ if (index > -1)
+ reference = reference.Substring(index + 1).ToLowerInvariant();
+ }
+
+ sources[i] = string.Format(fontUrls, reference, formats[extension]);
+ }
+
+ string sourceUrls = string.Join(", ", sources);
+ fontFamily = fontName;
+ return string.Format(fontFace, fontName, sourceUrls);
+ }
+
+ private string GetCodeFromLocalhost(string fileName)
+ {
+ int index = draggedFilename.IndexOf('/', 24);
+ if (index > -1)
+ draggedFilename = draggedFilename.Substring(index).ToLowerInvariant();
+
+ string extension = Path.GetExtension(draggedFilename).ToLowerInvariant();
+ string sourceUrl = string.Format(fontUrls, draggedFilename, formats[extension]);
+
+ return string.Format(fontFace, "MyFontName", sourceUrl);
+ }
+
+ private IEnumerable GetRelativeFiles(string fileName)
+ {
+ var fi = new FileInfo(fileName);
+ fontName = fi.Name.Replace(fi.Extension, string.Empty);
+ foreach (var file in fi.Directory.GetFiles(fontName + ".*"))
+ {
+ string extension = file.Extension.ToLowerInvariant();
+ if (formats.ContainsKey(extension))
+ yield return file.FullName;
+ }
+ }
+
+ public void HandleDragCanceled()
+ {
+ }
+
+ public DragDropPointerEffects HandleDragStarted(DragDropInfo dragDropInfo)
+ {
+ return DragDropPointerEffects.All;
+ }
+
+ public DragDropPointerEffects HandleDraggingOver(DragDropInfo dragDropInfo)
+ {
+ return DragDropPointerEffects.All;
+ }
+
+ public bool IsDropEnabled(DragDropInfo dragDropInfo)
+ {
+ //if (!Path.GetExtension(document.FilePath).Equals(".css", StringComparison.OrdinalIgnoreCase))
+ // return false;
+
+ draggedFilename = GetImageFilename(dragDropInfo);
+
+ if (!string.IsNullOrEmpty(draggedFilename))
+ {
+ string fileExtension = Path.GetExtension(draggedFilename).ToLowerInvariant();
+ if (this.formats.ContainsKey(fileExtension))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static string GetImageFilename(DragDropInfo info)
+ {
+ DataObject data = new DataObject(info.Data);
+
+ if (info.Data.GetDataPresent("FileDrop"))
+ {
+ // The drag and drop operation came from the file system
+ StringCollection files = data.GetFileDropList();
+
+ if (files != null && files.Count == 1)
+ {
+ return files[0];
+ }
+ }
+ else if (info.Data.GetDataPresent("CF_VSSTGPROJECTITEMS"))
+ {
+ // The drag and drop operation came from the VS solution explorer
+ return data.GetText();
+ }
+
+ return null;
+ }
+
+ //public static string RelativePath(string absPath, string relTo)
+ //{
+ // string[] absDirs = absPath.Split('\\');
+ // string[] relDirs = relTo.Split('\\');
+
+ // // Get the shortest of the two paths
+ // int len = absDirs.Length < relDirs.Length ? absDirs.Length :
+ // relDirs.Length;
+
+ // // Use to determine where in the loop we exited
+ // int lastCommonRoot = -1;
+ // int index;
+
+ // // Find common root
+ // for (index = 0; index < len; index++)
+ // {
+ // if (absDirs[index] == relDirs[index]) lastCommonRoot = index;
+ // else break;
+ // }
+
+ // // If we didn't find a common prefix then throw
+ // if (lastCommonRoot == -1)
+ // {
+ // return relTo;
+ // }
+
+ // // Build up the relative path
+ // StringBuilder relativePath = new StringBuilder();
+
+ // // Add on the ..
+ // for (index = lastCommonRoot + 2; index < absDirs.Length; index++)
+ // {
+ // if (absDirs[index].Length > 0) relativePath.Append("..\\");
+ // }
+
+ // // Add on the folders
+ // for (index = lastCommonRoot + 1; index < relDirs.Length - 1; index++)
+ // {
+ // relativePath.Append(relDirs[index] + "\\");
+ // }
+ // relativePath.Append(relDirs[relDirs.Length - 1]);
+
+ // return relativePath.ToString().Replace("\\", "/");
+ //}
+ }
+}
diff --git a/EditorExtensions/DropTargets/ImageDrop.cs b/EditorExtensions/DropTargets/ImageDrop.cs
new file mode 100644
index 000000000..3b71a6d21
--- /dev/null
+++ b/EditorExtensions/DropTargets/ImageDrop.cs
@@ -0,0 +1,84 @@
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Editor.DragDrop;
+using Microsoft.VisualStudio.Utilities;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IDropHandlerProvider))]
+ [DropFormat("FileDrop")]
+ [DropFormat("CF_VSSTGPROJECTITEMS")]
+ [Name("ImageDropHandler")]
+ [ContentType("CSS")]
+ [ContentType("LESS")]
+ [Order(Before = "DefaultFileDropHandler")]
+ internal class ImageDropHandlerProvider : IDropHandlerProvider
+ {
+ public IDropHandler GetAssociatedDropHandler(IWpfTextView view)
+ {
+ return view.Properties.GetOrCreateSingletonProperty(() => new ImageDropHandler(view));
+ }
+ }
+
+ internal class ImageDropHandler : IDropHandler
+ {
+ IWpfTextView _view;
+ private readonly List _imageExtensions = new List { ".jpg", ".jpeg", ".bmp", ".png", ".gif", ".svg", ".tif", ".tiff" };
+ private string _imageFilename;
+ string _background = "background-image: url({0});";
+
+ public ImageDropHandler(IWpfTextView view)
+ {
+ this._view = view;
+ }
+
+ public DragDropPointerEffects HandleDataDropped(DragDropInfo dragDropInfo)
+ {
+ string reference = FileHelpers.RelativePath(EditorExtensionsPackage.DTE.ActiveDocument.FullName, _imageFilename);
+
+ if (reference.Contains("://"))
+ {
+ int index = reference.IndexOf('/', 12);
+ if (index > -1)
+ reference = reference.Substring(index).ToLowerInvariant();
+ }
+
+ _view.TextBuffer.Insert(dragDropInfo.VirtualBufferPosition.Position.Position, string.Format(_background, reference));
+
+ return DragDropPointerEffects.Copy;
+ }
+
+ public void HandleDragCanceled()
+ {
+
+ }
+
+ public DragDropPointerEffects HandleDragStarted(DragDropInfo dragDropInfo)
+ {
+ return DragDropPointerEffects.All;
+ }
+
+ public DragDropPointerEffects HandleDraggingOver(DragDropInfo dragDropInfo)
+ {
+ return DragDropPointerEffects.All;
+ }
+
+ public bool IsDropEnabled(DragDropInfo dragDropInfo)
+ {
+ _imageFilename = FontDropHandler.GetImageFilename(dragDropInfo);
+
+ if (!string.IsNullOrEmpty(_imageFilename))
+ {
+ string fileExtension = Path.GetExtension(_imageFilename).ToLowerInvariant();
+ if (this._imageExtensions.Contains(fileExtension))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/EditorExtensions/DropTargets/StylesheetDrop.cs b/EditorExtensions/DropTargets/StylesheetDrop.cs
new file mode 100644
index 000000000..448244ead
--- /dev/null
+++ b/EditorExtensions/DropTargets/StylesheetDrop.cs
@@ -0,0 +1,84 @@
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Editor.DragDrop;
+using Microsoft.VisualStudio.Utilities;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IDropHandlerProvider))]
+ [DropFormat("StylesheetDrop")]
+ [DropFormat("CF_VSSTGPROJECTITEMS")]
+ [Name("StylesheetDropDropHandler")]
+ [ContentType("CSS")]
+ [ContentType("LESS")]
+ [Order(Before = "DefaultFileDropHandler")]
+ internal class StylesheetDropHandlerProvider : IDropHandlerProvider
+ {
+ public IDropHandler GetAssociatedDropHandler(IWpfTextView view)
+ {
+ return view.Properties.GetOrCreateSingletonProperty(() => new StylesheetDropHandler(view));
+ }
+ }
+
+ internal class StylesheetDropHandler : IDropHandler
+ {
+ IWpfTextView _view;
+ private readonly List _imageExtensions = new List { ".css", ".less", ".sass", ".scss" };
+ private string _imageFilename;
+ string _background = "@import url('{0}');";
+
+ public StylesheetDropHandler(IWpfTextView view)
+ {
+ this._view = view;
+ }
+
+ public DragDropPointerEffects HandleDataDropped(DragDropInfo dragDropInfo)
+ {
+ string reference = FileHelpers.RelativePath(EditorExtensionsPackage.DTE.ActiveDocument.FullName, _imageFilename);
+
+ if (reference.StartsWith("http://localhost:"))
+ {
+ int index = reference.IndexOf('/', 24);
+ if (index > -1)
+ reference = reference.Substring(index).ToLowerInvariant();
+ }
+
+ _view.TextBuffer.Insert(dragDropInfo.VirtualBufferPosition.Position.Position, string.Format(_background, reference));
+
+ return DragDropPointerEffects.Copy;
+ }
+
+ public void HandleDragCanceled()
+ {
+
+ }
+
+ public DragDropPointerEffects HandleDragStarted(DragDropInfo dragDropInfo)
+ {
+ return DragDropPointerEffects.All;
+ }
+
+ public DragDropPointerEffects HandleDraggingOver(DragDropInfo dragDropInfo)
+ {
+ return DragDropPointerEffects.All;
+ }
+
+ public bool IsDropEnabled(DragDropInfo dragDropInfo)
+ {
+ _imageFilename = FontDropHandler.GetImageFilename(dragDropInfo);
+
+ if (!string.IsNullOrEmpty(_imageFilename))
+ {
+ string fileExtension = Path.GetExtension(_imageFilename).ToLowerInvariant();
+ if (this._imageExtensions.Contains(fileExtension))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/EditorExtensions/DropTargets/TypeScriptDrop.cs b/EditorExtensions/DropTargets/TypeScriptDrop.cs
new file mode 100644
index 000000000..dd0b7af1b
--- /dev/null
+++ b/EditorExtensions/DropTargets/TypeScriptDrop.cs
@@ -0,0 +1,86 @@
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Editor.DragDrop;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IDropHandlerProvider))]
+ [DropFormat("CF_VSSTGPROJECTITEMS")]
+ [Name("TypeScriptDropHandler")]
+ [ContentType("TypeScript")]
+ [Order(Before = "DefaultFileDropHandler")]
+ internal class TypeScriptDropHandlerProvider : IDropHandlerProvider
+ {
+ public IDropHandler GetAssociatedDropHandler(IWpfTextView view)
+ {
+ return view.Properties.GetOrCreateSingletonProperty(() => new TypeScriptDropHandler(view));
+ }
+ }
+
+ internal class TypeScriptDropHandler : IDropHandler
+ {
+ IWpfTextView _view;
+ private readonly List _imageExtensions = new List { ".ts", ".js" };
+ private string _imageFilename;
+ string _background = "/// ";
+
+ public TypeScriptDropHandler(IWpfTextView view)
+ {
+ this._view = view;
+ }
+
+ public DragDropPointerEffects HandleDataDropped(DragDropInfo dragDropInfo)
+ {
+ string reference = FileHelpers.RelativePath(EditorExtensionsPackage.DTE.ActiveDocument.FullName, _imageFilename);
+
+ if (reference.StartsWith("http://localhost:"))
+ {
+ int index = reference.IndexOf('/', 24);
+ if (index > -1)
+ reference = reference.Substring(index).ToLowerInvariant();
+ }
+
+ reference = reference.Trim('/');
+ string comment = string.Format(_background, reference);
+
+ _view.TextBuffer.Insert(0, comment + Environment.NewLine);
+
+ return DragDropPointerEffects.Copy;
+ }
+
+ public void HandleDragCanceled()
+ {
+
+ }
+
+ public DragDropPointerEffects HandleDragStarted(DragDropInfo dragDropInfo)
+ {
+ return DragDropPointerEffects.All;
+ }
+
+ public DragDropPointerEffects HandleDraggingOver(DragDropInfo dragDropInfo)
+ {
+ return DragDropPointerEffects.All;
+ }
+
+ public bool IsDropEnabled(DragDropInfo dragDropInfo)
+ {
+ _imageFilename = FontDropHandler.GetImageFilename(dragDropInfo);
+
+ if (!string.IsNullOrEmpty(_imageFilename))
+ {
+ string fileExtension = Path.GetExtension(_imageFilename).ToLowerInvariant();
+ if (this._imageExtensions.Contains(fileExtension))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/EditorExtensions/EditorExtensions.vsct b/EditorExtensions/EditorExtensions.vsct
new file mode 100644
index 000000000..4236bb5b9
--- /dev/null
+++ b/EditorExtensions/EditorExtensions.vsct
@@ -0,0 +1,753 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/EditorExtensions/EditorExtensionsPackage.cs b/EditorExtensions/EditorExtensionsPackage.cs
new file mode 100644
index 000000000..d620163c0
--- /dev/null
+++ b/EditorExtensions/EditorExtensionsPackage.cs
@@ -0,0 +1,227 @@
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.VisualStudio.ComponentModelHost;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+using System;
+using System.ComponentModel.Design;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ // The key for registering option pages in Text Editors -> CSS
+ //HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0_Config\Languages\Language Services\CSS\EditorToolsOptions\Format
+
+
+ [PackageRegistration(UseManagedResourcesOnly = true)]
+ [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
+ [Guid(GuidList.guidEditorExtensionsPkgString)]
+ [ProvideMenuResource("Menus.ctmenu", 1)]
+ [ProvideAutoLoad(UIContextGuids80.SolutionExists)]
+ [ProvideOptionPage(typeof(GeneralOptions), "Web Essentials", "General", 101, 101, true, new[] { "ZenCoding", "Mustache", "Handlebars", "Comments", "Bundling", "Bundle" })]
+ [ProvideOptionPage(typeof(CssOptions), "Web Essentials", "CSS", 101, 102, true, new[] { "Minify", "Minification", "W3C", "CSS3" })]
+ [ProvideOptionPage(typeof(JsHintOptions), "Web Essentials", "JSHint", 101, 103, true, new[] { "JSLint", "Lint" })]
+ //[ProvideOptionPage(typeof(TypeScriptOptions), "Web Essentials", "TypeScript", 101, 104, true, new[] { "Minify", "Minification" })]
+ [ProvideOptionPage(typeof(LessOptions), "Web Essentials", "LESS", 101, 105, true)]
+ [ProvideOptionPage(typeof(CoffeeScriptOptions), "Web Essentials", "CoffeeScript", 101, 106, true, new[] { "Iced", "JavaScript", "JS", "JScript" })]
+ [ProvideOptionPage(typeof(JavaScriptOptions), "Web Essentials", "JavaScript", 101, 107, true, new[] { "JScript", "JS", "Minify", "Minification", "EcmaScript" })]
+ //[ProvideOptionPage(typeof(ScssOptions), "Web Essentials", "SCSS", 101, 108, true)]
+ [ProvideSearchProvider(typeof(Microsoft.MSDNSearch.VSSearchProvider), "VS Gallery Search")]
+ public sealed class EditorExtensionsPackage : ExtensionPointPackage
+ {
+ private static DTE2 _dte;
+ private static IVsRegisterPriorityCommandTarget _pct;
+
+ public EditorExtensionsPackage()
+ {
+ }
+
+ internal static DTE2 DTE
+ {
+ get
+ {
+ if (_dte == null)
+ {
+ _dte = ServiceProvider.GlobalProvider.GetService(typeof(DTE)) as DTE2;
+ Debug.Assert(_dte != null);
+ }
+
+ return _dte;
+ }
+ }
+
+ internal static IVsRegisterPriorityCommandTarget PriorityCommandTarget
+ {
+ get
+ {
+ if (_pct == null)
+ {
+ _pct = ServiceProvider.GlobalProvider.GetService(typeof(SVsRegisterPriorityCommandTarget)) as IVsRegisterPriorityCommandTarget;
+ }
+
+ return _pct;
+ }
+ }
+
+ public static EditorExtensionsPackage Instance { get; private set; }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+ Instance = this;
+ JsDocComments.Register();
+
+ OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
+ if (null != mcs)
+ {
+ HandleMenuVisibility(mcs);
+
+ //EncodingMenu encoding = new EncodingMenu(DTE, mcs);
+ //encoding.SetupCommands();
+
+ TransformMenu transform = new TransformMenu(DTE, mcs);
+ transform.SetupCommands();
+
+ //CssSortPropertiesMenu cssTasks = new CssSortPropertiesMenu(DTE, mcs);
+ //cssTasks.SetupCommands();
+
+ //CssRemoveDuplicates cssRemoveDuplicates = new CssRemoveDuplicates(DTE, mcs);
+ //cssRemoveDuplicates.SetupCommands();
+
+ //CssAddMissingVendor cssAddMissingVendor = new CssAddMissingVendor(DTE, mcs);
+ //cssAddMissingVendor.SetupCommands();
+
+ //CssAddMissingStandard cssAddMissingStandard = new CssAddMissingStandard(DTE, mcs);
+ //cssAddMissingStandard.SetupCommands();
+
+ DiffMenu diffMenu = new DiffMenu(DTE, mcs);
+ diffMenu.SetupCommands();
+
+ MinifyFileMenu minifyMenu = new MinifyFileMenu(DTE, mcs);
+ minifyMenu.SetupCommands();
+
+ BundleFilesMenu bundleMenu = new BundleFilesMenu(DTE, mcs);
+ bundleMenu.SetupCommands();
+
+ JsHintMenu jsHintMenu = new JsHintMenu(DTE, mcs);
+ jsHintMenu.SetupCommands();
+
+ ProjectSettingsMenu projectSettingsMenu = new ProjectSettingsMenu(DTE, mcs);
+ projectSettingsMenu.SetupCommands();
+
+ SolutionColorsMenu solutionColorsMenu = new SolutionColorsMenu(DTE, mcs);
+ solutionColorsMenu.SetupCommands();
+
+ BuildMenu buildMenu = new BuildMenu(DTE, mcs);
+ buildMenu.SetupCommands();
+
+ MarkdownStylesheetMenu markdownMenu = new MarkdownStylesheetMenu(DTE, mcs);
+ markdownMenu.SetupCommands();
+
+ //CssExtractToFileMenu extractToFileMenu = new CssExtractToFileMenu(DTE, mcs);
+ //extractToFileMenu.SetupCommands();
+ }
+
+ // Hook up event handlers
+ Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
+ {
+ DTE.Events.BuildEvents.OnBuildDone += BuildEvents_OnBuildDone;
+ DTE.Events.SolutionEvents.Opened += delegate { Settings.UpdateCache(); Settings.UpdateStatusBar("applied"); };
+ DTE.Events.SolutionEvents.AfterClosing += delegate { DTE.StatusBar.Clear(); };
+
+ }), DispatcherPriority.ApplicationIdle, null);
+ }
+
+ private void BuildEvents_OnBuildDone(vsBuildScope Scope, vsBuildAction Action)
+ {
+ if (Action != vsBuildAction.vsBuildActionClean)
+ {
+ //if (WESettings.GetBoolean(WESettings.Keys.CompileTypeScriptOnBuild))
+ // _dte.Commands.Raise(GuidList.guidBuildCmdSetString, (int)PkgCmdIDList.cmdBuildTypeScript, null, null);
+ //new TypeScriptMargin().CompileProjectFiles(null);
+
+ if (WESettings.GetBoolean(WESettings.Keys.LessCompileOnBuild))
+ _dte.Commands.Raise(GuidList.guidBuildCmdSetString, (int)PkgCmdIDList.cmdBuildLess, null, null);
+ //LessProjectCompiler.CompileProject();
+
+ if (WESettings.GetBoolean(WESettings.Keys.CoffeeScriptCompileOnBuild))
+ _dte.Commands.Raise(GuidList.guidBuildCmdSetString, (int)PkgCmdIDList.cmdBuildCoffeeScript, null, null);
+
+ //BundleFilesMenu.UpdateBundles(null, true);
+ _dte.Commands.Raise(GuidList.guidBuildCmdSetString, (int)PkgCmdIDList.cmdBuildBundles, null, null);
+
+ if (WESettings.GetBoolean(WESettings.Keys.RunJsHintOnBuild))
+ {
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => JsHintProjectRunner.RunOnAllFilesInProject()), DispatcherPriority.ApplicationIdle, null);
+ }
+ }
+ else if (Action == vsBuildAction.vsBuildActionClean)
+ {
+ System.Threading.Tasks.Task.Run(() => JsHintRunner.Reset());
+ }
+ }
+
+ public static void ExecuteCommand(string commandName)
+ {
+ var command = EditorExtensionsPackage.DTE.Commands.Item(commandName);
+ if (command.IsAvailable)
+ {
+ EditorExtensionsPackage.DTE.ExecuteCommand(commandName);
+ }
+ }
+
+ private void HandleMenuVisibility(OleMenuCommandService mcs)
+ {
+ CommandID commandId = new CommandID(GuidList.guidCssIntellisenseCmdSet, (int)PkgCmdIDList.CssIntellisenseSubMenu);
+ OleMenuCommand menuCommand = new OleMenuCommand((s, e) => { }, commandId);
+ menuCommand.BeforeQueryStatus += menuCommand_BeforeQueryStatus;
+ mcs.AddCommand(menuCommand);
+ }
+
+ private readonly string[] _supported = new[] { "CSS", "LESS", "SCSS", "JAVASCRIPT", "PROJECTION", "TYPESCRIPT", "MARKDOWN" };
+
+ void menuCommand_BeforeQueryStatus(object sender, EventArgs e)
+ {
+ OleMenuCommand menu = (OleMenuCommand)sender;
+ var buffer = ProjectHelpers.GetCurentTextBuffer();
+
+ menu.Visible = buffer != null && _supported.Contains(buffer.ContentType.DisplayName.ToUpperInvariant());
+ }
+
+ public static T GetGlobalService(Type type = null) where T : class
+ {
+ return Microsoft.VisualStudio.Shell.Package.GetGlobalService(type ?? typeof(T)) as T;
+ }
+
+ public static IComponentModel ComponentModel
+ {
+ get { return GetGlobalService(typeof(SComponentModel)); }
+ }
+
+ //internal static IVsHierarchy GetIVsHierarchy(Project project)
+ //{
+ // IVsSolution solution = (IVsSolution)ServiceProvider.GlobalProvider.GetService(typeof(IVsSolution));
+ // if (solution == null)
+ // {
+ // return null;
+ // }
+
+ // IVsHierarchy hier;
+
+ // Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(solution.GetProjectOfUniqueName(project.UniqueName, out hier));
+
+ // if (hier == null)
+ // {
+ // return null;
+ // }
+ // else
+ // {
+ // return hier;
+ // }
+ //}
+ }
+}
diff --git a/EditorExtensions/ErrorTags/Filters/Backslash9CssErrorFilter.cs b/EditorExtensions/ErrorTags/Filters/Backslash9CssErrorFilter.cs
new file mode 100644
index 000000000..0940a2a10
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Filters/Backslash9CssErrorFilter.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssErrorFilter))]
+ [Name("Backslash9CssErrorFilter")]
+ [Order(After = "Default")]
+ internal class Backslash9CssErrorFilter : ICssErrorFilter
+ {
+ public void FilterErrorList(IList errors, ICssCheckerContext context)
+ {
+ for (int i = errors.Count - 1; i > -1; i--)
+ {
+ ICssError error = errors[i];
+ Declaration dec = error.Item.FindType();
+
+ if (dec != null && dec.Text.Contains("\\9"))
+ {
+ errors.RemoveAt(i);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Filters/CustomCssErrorFilter.cs b/EditorExtensions/ErrorTags/Filters/CustomCssErrorFilter.cs
new file mode 100644
index 000000000..a1f6d8ba1
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Filters/CustomCssErrorFilter.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssErrorFilter))]
+ [Name("Custom Errors")]
+ [Order(After = "Default")]
+ internal class CustomCssErrorFilter : ICssErrorFilter
+ {
+ private readonly Dictionary _messsages = new Dictionary()
+ {
+ { "cursorhand", "Consider using \"pointer\" instead." },
+ { "cursornormal", "Consider using \"default\" instead." }
+ };
+
+ public void FilterErrorList(IList errors, ICssCheckerContext context)
+ {
+ for (int i = errors.Count - 1; i > -1; i--)
+ {
+ ICssError error = errors[i];
+ if (error.Item.IsValid)
+ {
+ Declaration dec = error.Item.FindType();
+ if (dec != null && dec.IsValid && dec.PropertyName.Text == "cursor")
+ {
+ if (error.Item.Text == "hand")
+ {
+ errors.RemoveAt(i);
+ errors.Insert(i, CreateNewError(error, "cursorhand"));
+ }
+ else if (error.Item.Text == "normal")
+ {
+ errors.RemoveAt(i);
+ errors.Insert(i, CreateNewError(error, "cursornormal"));
+ }
+ }
+ }
+ }
+ }
+
+ private SimpleErrorTag CreateNewError(ICssError error, string messageKey)
+ {
+ string message = error.Text + " " + _messsages[messageKey];
+ return new SimpleErrorTag(error.Item, message, CssErrorFlags.TaskListError | CssErrorFlags.UnderlineRed);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Filters/MsFilterCssErrorFilter.cs b/EditorExtensions/ErrorTags/Filters/MsFilterCssErrorFilter.cs
new file mode 100644
index 000000000..29f23083e
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Filters/MsFilterCssErrorFilter.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssErrorFilter))]
+ [Name("MsFilterCssErrorFilter")]
+ [Order(After = "Default")]
+ internal class MsFilterCssErrorFilter : ICssErrorFilter
+ {
+ public void FilterErrorList(IList errors, ICssCheckerContext context)
+ {
+ for (int i = errors.Count - 1; i > -1; i--)
+ {
+ ICssError error = errors[i];
+ Declaration dec = error.Item.FindType();
+ if (dec != null && dec.IsValid && dec.PropertyName.Text == "-ms-filter")
+ {
+ errors.RemoveAt(i);
+ errors.Insert(i, CreateNewError(error));
+ }
+ }
+ }
+
+ private SimpleErrorTag CreateNewError(ICssError error)
+ {
+ string message = error.Text + " " + " The value must be wrapped in single or double qoutation marks.";
+ return new SimpleErrorTag(error.Item, message, CssErrorFlags.TaskListError | CssErrorFlags.UnderlineRed);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Filters/MsKeyframesCssErrorFilter.cs b/EditorExtensions/ErrorTags/Filters/MsKeyframesCssErrorFilter.cs
new file mode 100644
index 000000000..df4c4e6e6
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Filters/MsKeyframesCssErrorFilter.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssErrorFilter))]
+ [Name("MsKeyframesCssErrorFilter")]
+ [Order(After = "Default")]
+ internal class MsKeyframesCssErrorFilter : ICssErrorFilter
+ {
+ private static readonly string _message = " IE only supportes the standard @keyframes implementation.";
+
+ public void FilterErrorList(IList errors, ICssCheckerContext context)
+ {
+ for (int i = errors.Count - 1; i > -1; i--)
+ {
+ ICssError error = errors[i];
+ if (error.Item.IsValid)
+ {
+ AtDirective atDir = error.Item.FindType();
+ if (atDir != null && atDir.IsValid && atDir.Keyword.Text == "-ms-keyframes")
+ {
+ errors.RemoveAt(i);
+ ICssError tag = new SimpleErrorTag(error.Item, error.Text + _message, CssErrorFlags.TaskListError | CssErrorFlags.UnderlineRed);
+ errors.Insert(i, tag);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Filters/ScssErrorFilter.cs b/EditorExtensions/ErrorTags/Filters/ScssErrorFilter.cs
new file mode 100644
index 000000000..055314037
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Filters/ScssErrorFilter.cs
@@ -0,0 +1,33 @@
+//using System.Collections.Generic;
+//using System.ComponentModel.Composition;
+//using Microsoft.CSS.Core;
+//using Microsoft.VisualStudio.Utilities;
+//using System.IO;
+//using System;
+
+//namespace MadsKristensen.EditorExtensions
+//{
+// [Export(typeof(ICssErrorFilter))]
+// [Name("ScssErrorFilter")]
+// [Order(After = "Default")]
+// internal class ScssErrorFilter : ICssErrorFilter
+// {
+// public void FilterErrorList(IList errors, ICssCheckerContext context)
+// {
+// var document = EditorExtensionsPackage.DTE.ActiveDocument;
+
+// if (document == null || !Path.GetExtension(document.FullName).Equals(ScssContentTypeDefinition.ScssFileExtension, StringComparison.OrdinalIgnoreCase))
+// return;
+
+// for (int i = errors.Count - 1; i > -1; i--)
+// {
+// ICssError error = errors[i];
+
+// if ((error.Flags & CssErrorFlags.TaskListError) == CssErrorFlags.TaskListError)
+// {
+// errors.RemoveAt(i);
+// }
+// }
+// }
+// }
+//}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Providers/ColorValuesInRangeErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/ColorValuesInRangeErrorTagProvider.cs
new file mode 100644
index 000000000..3ad98f643
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/ColorValuesInRangeErrorTagProvider.cs
@@ -0,0 +1,86 @@
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Globalization;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("ColorValuesInRangeErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class ColorValuesInRangeErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ FunctionColor function = (FunctionColor)item;
+
+ if (!function.IsValid || context == null)
+ return ItemCheckResult.Continue;
+
+ if (function.FunctionName.Text.StartsWith("rgb"))
+ {
+ ValidateRgb(context, function);
+ }
+ //else if (function.FunctionName.Text.StartsWith("hsl"))
+ //{
+ // ValidateHsl(context, function);
+ //}
+
+ return ItemCheckResult.Continue;
+ }
+
+ private static void ValidateRgb(ICssCheckerContext context, FunctionColor function)
+ {
+ for (int i = 0; i < function.Arguments.Count; i++)
+ {
+ var argument = function.Arguments[i];
+ string text = argument.Text.Trim(',');
+
+ if (i < 3)
+ {
+ int value;
+ if (int.TryParse(text, out value) && (value < 0 || value > 255))
+ context.AddError(new SimpleErrorTag(argument, Resources.ValidationColorValuesInRange, CssErrorFlags.TaskListWarning | CssErrorFlags.UnderlineRed));
+ }
+ else
+ {
+ ValidateAlphaValue(context, argument, text);
+ }
+ }
+ }
+
+ private static void ValidateHsl(ICssCheckerContext context, FunctionColor function)
+ {
+ for (int i = 0; i < function.Arguments.Count; i++)
+ {
+ var argument = function.Arguments[i];
+ string text = argument.Text.Trim(',','%');
+
+ if (i < 3)
+ {
+ int value;
+ if (int.TryParse(text, out value) && (value < 0 || value > 100))
+ context.AddError(new SimpleErrorTag(argument, "Validation: Values must be between 0 and 100%", CssErrorFlags.TaskListWarning | CssErrorFlags.UnderlineRed));
+ }
+ else
+ {
+ ValidateAlphaValue(context, argument, text);
+ }
+ }
+ }
+
+ private static void ValidateAlphaValue(ICssCheckerContext context, ParseItem argument, string text)
+ {
+ double value;
+ if (double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out value) && (value < 0 || value > 1))
+ context.AddError(new SimpleErrorTag(argument, "Validation: The opacity value must be between 0 and 1", CssErrorFlags.TaskListWarning | CssErrorFlags.UnderlineRed));
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(FunctionColor) }; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Providers/DisplayInlineErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/DisplayInlineErrorTagProvider.cs
new file mode 100644
index 000000000..34e93107e
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/DisplayInlineErrorTagProvider.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel.Composition;
+using System.Globalization;
+using System.Linq;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Editor;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using Microsoft.VisualStudio.Utilities;
+using System.Reflection;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IWpfTextViewConnectionListener))]
+ [ContentType(Microsoft.Web.Editor.CssContentTypeDefinition.CssContentType)]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ class DisplayInlineTextViewCreationListener : IWpfTextViewConnectionListener
+ {
+ public void SubjectBuffersConnected(IWpfTextView textView, ConnectionReason reason, Collection subjectBuffers)
+ {
+ foreach (ITextBuffer buffer in subjectBuffers)
+ {
+ CssEditorDocument doc = CssEditorDocument.FromTextBuffer(buffer);
+ doc.Tree.ItemsChanged += Tree_ItemsChanged;
+ doc.Tree.TreeUpdated += Tree_TreeUpdated;
+ InitializeCache(doc.Tree.StyleSheet);
+ }
+ }
+
+ void Tree_TreeUpdated(object sender, CssTreeUpdateEventArgs e)
+ {
+ InitializeCache(e.Tree.StyleSheet);
+ }
+
+ private void InitializeCache(StyleSheet stylesheet)
+ {
+ _cache.Clear();
+
+ var visitor = new CssItemCollector(true);
+ stylesheet.Accept(visitor);
+
+ foreach (Declaration dec in visitor.Items.Where(d => d.PropertyName != null))
+ {
+ if (dec.PropertyName.Text == "display" && dec.Values.Any(v => v.Text == "inline"))
+ _cache.Add(dec);
+ }
+ }
+
+ public void SubjectBuffersDisconnected(IWpfTextView textView, ConnectionReason reason, Collection subjectBuffers)
+ {
+ foreach (ITextBuffer buffer in subjectBuffers)
+ {
+ CssEditorDocument doc = CssEditorDocument.FromTextBuffer(buffer);
+ doc.Tree.ItemsChanged -= Tree_ItemsChanged;
+ }
+ }
+
+ private HashSet _cache = new HashSet();
+
+ void Tree_ItemsChanged(object sender, CssItemsChangedEventArgs e)
+ {
+ CssTree tree = (CssTree)sender;
+
+ foreach (ParseItem item in e.InsertedItems)
+ {
+ var visitor = new CssItemCollector(true);
+ item.Accept(visitor);
+
+ foreach (Declaration dec in visitor.Items)
+ {
+ if (dec.PropertyName != null && dec.PropertyName.Text == "display" && dec.Values.Any(v => v.Text == "inline"))
+ {
+ _cache.Add(dec);
+
+ ParseItem rule = dec.Parent;
+ Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => Update(rule, tree)), DispatcherPriority.Normal);
+ }
+ }
+ }
+
+ foreach (ParseItem item in e.DeletedItems)
+ {
+ var visitor = new CssItemCollector(true);
+ item.Accept(visitor);
+
+ foreach (Declaration deleted in visitor.Items)
+ {
+ if (_cache.Contains(deleted))
+ {
+ _cache.Remove(deleted);
+
+ ParseItem rule = deleted.Parent;
+ Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => Update(rule, tree)), DispatcherPriority.Normal);
+ }
+ }
+ }
+ }
+
+ private static void Update(ParseItem rule, CssTree tree)
+ {
+ BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod;
+ object[] parameters = new object[3];
+ parameters[0] = new ParseItemList();
+ parameters[1] = new ParseItemList();
+ parameters[2] = new ParseItemList() { rule };
+
+ typeof(CssTree).InvokeMember("FireOnItemsChanged", flags, null, tree, parameters);
+ }
+ }
+
+ [Export(typeof(ICssItemChecker))]
+ [Name("DisplayInlineErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class DisplayInlineErrorTagProvider : ICssItemChecker
+ {
+ private static string[] invalidProperties = new[] { "margin-top", "margin-bottom", "height", "width" };
+
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ RuleBlock rule = (RuleBlock)item;
+
+ if (!rule.IsValid || context == null)
+ return ItemCheckResult.Continue;
+
+ bool isInline = rule.Declarations.Any(d => d.PropertyName != null && d.PropertyName.Text == "display" && d.Values.Any(v => v.Text == "inline"));
+ if (!isInline)
+ return ItemCheckResult.Continue;
+
+ IEnumerable invalids = rule.Declarations.Where(d => invalidProperties.Contains(d.PropertyName.Text));
+
+ foreach (Declaration invalid in invalids)
+ {
+ string error = string.Format(CultureInfo.InvariantCulture, Resources.BestPracticeInlineIncompat, invalid.PropertyName.Text);
+ context.AddError(new SimpleErrorTag(invalid.PropertyName, error));
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(RuleBlock) }; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Providers/DuplicatePropertyErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/DuplicatePropertyErrorTagProvider.cs
new file mode 100644
index 000000000..e489d7497
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/DuplicatePropertyErrorTagProvider.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+using System.Globalization;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("DuplicatePropertyErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class DuplicatePropertyErrorTagProvider : ICssItemChecker
+ {
+ // The rules of this error is specified here: https://github.com/stubbornella/csslint/wiki/Disallow-duplicate-properties
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ RuleBlock rule = (RuleBlock)item;
+
+ if (!rule.IsValid || context == null)
+ return ItemCheckResult.Continue;
+
+ Dictionary dic = new Dictionary();
+
+ foreach (Declaration declaration in rule.Declarations)
+ {
+ ParseItem prop = declaration.PropertyName;
+ if (prop == null || prop.Text == "filter")
+ continue;
+
+ string error = null;
+
+ if (!dic.ContainsKey(declaration.Text))
+ {
+ if (dic.ContainsValue(prop.Text) && dic.Last().Value != prop.Text)
+ {
+ // The same property name is specified, but not by the immidiate previous declaration
+ error = string.Format(CultureInfo.InvariantCulture, Resources.BestPracticeDuplicatePropertyInRule, prop.Text);
+ }
+
+ dic.Add(declaration.Text, prop.Text);
+ }
+ else
+ {
+ // The same property and value exist more than once in the rule. The exact declaration duplicate
+ error = string.Format(CultureInfo.InvariantCulture, Resources.BestPracticeDuplicatePropertyWithSameValueInRule, prop.Text);
+ }
+
+ if (error != null)
+ {
+ context.AddError(new SimpleErrorTag(prop, error));
+ }
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(RuleBlock) }; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Providers/DuplicateSelectorErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/DuplicateSelectorErrorTagProvider.cs
new file mode 100644
index 000000000..7aaccfd1b
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/DuplicateSelectorErrorTagProvider.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Globalization;
+using System.Linq;
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("DuplicateSelectorErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class DuplicateSelectorErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ RuleSet rule = (RuleSet)item;
+
+ if (!rule.IsValid || context == null)
+ return ItemCheckResult.Continue;
+
+ List cache = context.GetState(this) as List;
+
+ if (cache == null || (cache.Count > 0 && cache[0].Rule.Parent != rule.Parent))
+ {
+ cache = BuildCache(rule);
+ context.SetState(this, cache);
+ }
+
+ string ruleText = GetSelectorText(rule);
+ int start = rule.Start;
+ RuleResult dupe = null;
+ for (int i = 0; i < cache.Count; i++)
+ {
+ if (cache[i].Start >= start)
+ break;
+
+ if (ruleText == cache[i].Value)
+ {
+ dupe = cache[i];
+ break;
+ }
+ }
+
+ if (dupe != null)
+ {
+ int length = GetSelectorLength(rule);
+ int lineNo = FindLineNumber(dupe);
+
+ string errorMessage = string.Format(CultureInfo.InvariantCulture, Resources.BestPracticeDuplicateSelectors, lineNo);
+ SelectorErrorTag tag = new SelectorErrorTag(rule.Selectors, errorMessage);
+ context.AddError(tag);
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ // TODO: Is there a better way to find the line number?
+ private static int FindLineNumber(RuleResult ost)
+ {
+ string text = ost.Rule.StyleSheet.Text.Substring(0, ost.Start);
+ return text.Count(t => t == '\n') + 1;
+ }
+
+ private static string GetSelectorText(RuleSet rule)
+ {
+ var selectorsText = rule.Selectors.OrderBy(s => s.Text.Trim(',')).Select(s => s.Text.Trim(','));
+ return string.Concat(selectorsText);
+ }
+
+ private static int GetSelectorLength(RuleSet rule)
+ {
+ var selector = rule.Selectors.Last();
+ return selector.AfterEnd - rule.Start;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(RuleSet) }; }
+ }
+
+ private List BuildCache(RuleSet rule)
+ {
+ var visitor = new CssItemCollector();
+ rule.Parent.Accept(visitor);
+ List list = new List();
+
+ foreach (RuleSet rs in visitor.Items)
+ {
+ RuleResult result = new RuleResult(rs, rs.Start, GetSelectorText(rs));
+ list.Add(result);
+ }
+
+ return list;
+ }
+
+ private class RuleResult
+ {
+ public RuleResult(RuleSet rule, int start, string value)
+ {
+ Rule = rule;
+ Start = start;
+ Value = value;
+ }
+
+ public RuleSet Rule;
+ public int Start;
+ public string Value;
+ }
+ }
+}
diff --git a/EditorExtensions/ErrorTags/Providers/EmbedImagesErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/EmbedImagesErrorTagProvider.cs
new file mode 100644
index 000000000..78dd1e091
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/EmbedImagesErrorTagProvider.cs
@@ -0,0 +1,46 @@
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("EmbedImagesErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class EmbedImagesErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ UrlItem url = (UrlItem)item;
+
+ if (!WESettings.GetBoolean(WESettings.Keys.ValidateEmbedImages) || !url.IsValid || url.UrlString.Text.Contains("base64,") || context == null)
+ return ItemCheckResult.Continue;
+
+ string fileName = ImageQuickInfo.GetFileName(url.UrlString.Text);
+ if (fileName.Contains("://"))
+ return ItemCheckResult.Continue;
+
+ FileInfo file = new FileInfo(fileName);
+
+ if (file.Exists && file.Length < (1024 * 3))
+ {
+ Declaration dec = url.FindType();
+ if (dec != null && dec.PropertyName != null && dec.PropertyName.Text[0] != '*' && dec.PropertyName.Text[0] != '_')
+ {
+ string error = string.Format(Resources.PerformanceEmbedImageAsDataUri, file.Length);
+ context.AddError(new SimpleErrorTag(url.UrlString, error));
+ }
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(UrlItem) }; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Providers/HoverOrderErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/HoverOrderErrorTagProvider.cs
new file mode 100644
index 000000000..2cc31f619
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/HoverOrderErrorTagProvider.cs
@@ -0,0 +1,79 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("HoverOrderErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class HoverOrderErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ if (item.Text.TrimStart(':').StartsWith("-"))
+ return ItemCheckResult.Continue;
+
+ ParseItem next = item.NextSibling;
+ //ParseItem prev = item.PreviousSibling;
+ SimpleSelector sel = item.FindType();
+
+ //if (item.Text == ":hover" && prev != null && _invalids.Contains(prev.Text))
+ //{
+ // string error = string.Format(Resources.ValidationHoverOrder, prev.Text);
+ // context.AddError(new SimpleErrorTag(item, error, CssErrorFlags.TaskListError | CssErrorFlags.UnderlineRed));
+ //}
+
+ if (next != null)
+ {
+ if (next.Text.StartsWith(":") && item.IsPseudoElement() && !next.IsPseudoElement())
+ {
+ string error = string.Format(Resources.ValidationPseudoOrder, item.Text, next.Text);
+ context.AddError(new SimpleErrorTag(item, error, CssErrorFlags.TaskListError | CssErrorFlags.UnderlineRed));
+ }
+
+ else if (!next.Text.StartsWith(":") && item.AfterEnd == next.Start)
+ {
+ string error = string.Format(Resources.BestPracticePseudosAfterOtherSelectors, next.Text);
+ context.AddError(new SimpleErrorTag(next, error));
+ }
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ //public static bool IsPseudoElement(ParseItem item)
+ //{
+ // if (item.Text.StartsWith("::"))
+ // return true;
+
+ // var schema = CssSchemaManager.SchemaManager.GetSchemaRoot(null);
+ // return schema.GetPseudo(":" + item.Text) != null;
+ //}
+
+ private static List _invalids = new List()
+ {
+ ":before",
+ "::before",
+ ":after",
+ "::after",
+ };
+
+ public IEnumerable ItemTypes
+ {
+ get
+ {
+ return new[]
+ {
+ typeof(PseudoClassSelector),
+ typeof(PseudoClassFunctionSelector),
+ typeof(PseudoElementFunctionSelector),
+ typeof(PseudoElementSelector),
+ };
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/ErrorTags/Providers/Ie10PrefixErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/Ie10PrefixErrorTagProvider.cs
new file mode 100644
index 000000000..39e274743
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/Ie10PrefixErrorTagProvider.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("Ie10PrefixErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class Ie10PrefixErrorTagProvider : ICssItemChecker
+ {
+ private static readonly string _message = "Validation (WE): {0} no longer applies to Internet Explorer 10. Use the standard implementation instead.";
+
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ Declaration dec = (Declaration)item;
+
+ if (context == null || !dec.IsValid)
+ return ItemCheckResult.Continue;
+
+ string text = dec.PropertyName.Text;
+
+ if (text.StartsWith("-ms-transition", StringComparison.Ordinal) || text.StartsWith("-ms-animation", StringComparison.Ordinal))
+ {
+ string error = string.Format(_message, text);
+ ICssError tag = new SimpleErrorTag(dec.PropertyName, error);
+ context.AddError(tag);
+ return ItemCheckResult.CancelCurrentItem;
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(Declaration) }; }
+ }
+ }
+}
diff --git a/EditorExtensions/ErrorTags/Providers/InvalidVendorErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/InvalidVendorErrorTagProvider.cs
new file mode 100644
index 000000000..7273f1131
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/InvalidVendorErrorTagProvider.cs
@@ -0,0 +1,126 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Schemas;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("InvalidVendorDeclarationErrorTagProvider")]
+ [Order(After = "Ie10PrefixErrorTagProvider")]
+ internal class InvalidVendorDeclarationErrorTagProvider : ICssItemChecker
+ {
+ //private HashSet _deprecated = new HashSet()
+ //{
+ // "-moz-opacity",
+ // "-moz-outline",
+ // "-moz-outline-style",
+ //};
+
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ if (!WESettings.GetBoolean(WESettings.Keys.ValidateVendorSpecifics))
+ return ItemCheckResult.Continue;
+
+ Declaration dec = (Declaration)item;
+
+ if (!dec.IsValid || !dec.IsVendorSpecific() || context == null)
+ return ItemCheckResult.Continue;
+
+ ICssSchemaInstance rootSchema = CssSchemaManager.SchemaManager.GetSchemaRoot(null);
+ ICssSchemaInstance schema = CssSchemaManager.SchemaManager.GetSchemaForItem(rootSchema, item);
+
+ //if (_deprecated.Contains(dec.PropertyName.Text))
+ //{
+ // string message = string.Format(Resources.ValidationDeprecatedVendorDeclaration, dec.PropertyName.Text);
+ // context.AddError(new SimpleErrorTag(dec.PropertyName, message));
+ // return ItemCheckResult.CancelCurrentItem;
+ //}
+ if (schema.GetProperty(dec.PropertyName.Text) == null)
+ {
+ string message = string.Format(Resources.ValidationVendorDeclarations, dec.PropertyName.Text);
+ context.AddError(new SimpleErrorTag(dec.PropertyName, message, CssErrorFlags.TaskListWarning | CssErrorFlags.UnderlineRed));
+ return ItemCheckResult.CancelCurrentItem;
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(Declaration) }; }
+ }
+ }
+
+ [Export(typeof(ICssItemChecker))]
+ [Name("InvalidVendorPseudoErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class InvalidVendorPseudoErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ if (!WESettings.GetBoolean(WESettings.Keys.ValidateVendorSpecifics))
+ return ItemCheckResult.Continue;
+
+ if (!item.IsValid || context == null)
+ return ItemCheckResult.Continue;
+
+ ICssSchemaInstance rootSchema = CssSchemaManager.SchemaManager.GetSchemaRoot(null);
+ ICssSchemaInstance schema = CssSchemaManager.SchemaManager.GetSchemaForItem(rootSchema, item);
+
+ string normalized = item.Text.Trim(':');
+
+ if (normalized.Length > 0 && normalized[0] == '-' && schema.GetPseudo(item.Text) == null)
+ {
+ string message = string.Format(Resources.ValidationVendorPseudo, item.Text);
+ context.AddError(new SimpleErrorTag(item, message, CssErrorFlags.TaskListWarning | CssErrorFlags.UnderlineRed));
+ return ItemCheckResult.CancelCurrentItem;
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(PseudoClassSelector), typeof(PseudoElementSelector) }; }
+ }
+ }
+
+ [Export(typeof(ICssItemChecker))]
+ [Name("InvalidVendorDirectiveErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class InvalidVendorDirectiveErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ if (!WESettings.GetBoolean(WESettings.Keys.ValidateVendorSpecifics))
+ return ItemCheckResult.Continue;
+
+ AtDirective dir = item as AtDirective;
+
+ if (!dir.IsValid || !dir.IsVendorSpecific() || context == null)
+ return ItemCheckResult.Continue;
+
+
+ ICssSchemaInstance rootSchema = CssSchemaManager.SchemaManager.GetSchemaRoot(null);
+ ICssSchemaInstance schema = CssSchemaManager.SchemaManager.GetSchemaForItem(rootSchema, dir);
+
+ if (schema.GetAtDirective("@" + dir.Keyword.Text) == null)
+ {
+ string message = string.Format(Resources.ValidationVendorDirective, dir.Keyword.Text);
+ context.AddError(new SimpleErrorTag(dir.Keyword, message, CssErrorFlags.TaskListWarning | CssErrorFlags.UnderlineRed));
+ return ItemCheckResult.CancelCurrentItem;
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(AtDirective) }; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Providers/MissingStandardDirectiveErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/MissingStandardDirectiveErrorTagProvider.cs
new file mode 100644
index 000000000..5a3bd2b19
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/MissingStandardDirectiveErrorTagProvider.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Globalization;
+using System.Linq;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Intellisense;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("MissingStandardDirectiveErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class MissingStandardDirectiveErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ AtDirective directive = (AtDirective)item;
+
+ if (context == null || !directive.IsValid || !directive.IsVendorSpecific())
+ return ItemCheckResult.Continue;
+
+ ICssCompletionListEntry entry = VendorHelpers.GetMatchingStandardEntry(directive, context);
+
+ if (entry != null)
+ {
+ var visitor = new CssItemCollector();
+ directive.Parent.Accept(visitor);
+ if (!visitor.Items.Any(a => "@" + a.Keyword.Text == entry.DisplayText))
+ {
+ string message = string.Format(CultureInfo.InvariantCulture, Resources.BestPracticeAddMissingStandardDirective, entry.DisplayText);
+ context.AddError(new SimpleErrorTag(directive.Keyword, message));
+ return ItemCheckResult.CancelCurrentItem;
+ }
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(AtDirective) }; }
+ }
+ }
+}
diff --git a/EditorExtensions/ErrorTags/Providers/MissingVendorDirectiveErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/MissingVendorDirectiveErrorTagProvider.cs
new file mode 100644
index 000000000..6cfe2a944
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/MissingVendorDirectiveErrorTagProvider.cs
@@ -0,0 +1,45 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Schemas;
+using Microsoft.CSS.Editor.SyntaxCheck;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Globalization;
+using System.Linq;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("MissingVendorDirectiveErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class MissingVendorDirectiveErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ AtDirective directive = (AtDirective)item;
+
+ if (!directive.IsValid || directive.IsVendorSpecific() || context == null)
+ return ItemCheckResult.Continue;
+
+ ICssSchemaInstance schema = CssEditorChecker.GetSchemaForItem(context, item);
+ var missingEntries = directive.GetMissingVendorSpecifics(schema);
+
+ if (missingEntries.Any())
+ {
+ string error = string.Format(CultureInfo.InvariantCulture, Resources.BestPracticeAddMissingVendorSpecificDirective, directive.Keyword.Text, string.Join(", ", missingEntries));
+ ICssError tag = new SimpleErrorTag(directive.Keyword, error);
+ context.AddError(tag);
+ return ItemCheckResult.CancelCurrentItem;
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(AtDirective) }; }
+ }
+ }
+}
diff --git a/EditorExtensions/ErrorTags/Providers/MissingVendorErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/MissingVendorErrorTagProvider.cs
new file mode 100644
index 000000000..f0cc4f0c2
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/MissingVendorErrorTagProvider.cs
@@ -0,0 +1,53 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Schemas;
+using Microsoft.CSS.Editor.SyntaxCheck;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Globalization;
+using System.Linq;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("MissingVendorErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class MissingVendorErrorTagProvider : ICssItemChecker
+ {
+ private static string[] _vendorIgnoreList = new[] { "filter", "zoom", "behavior" };
+
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ Declaration dec = (Declaration)item;
+
+ if (!dec.IsValid || dec.IsVendorSpecific() || IgnoreProperty(dec) || context == null)
+ return ItemCheckResult.Continue;
+
+ ICssSchemaInstance schema = CssEditorChecker.GetSchemaForItem(context, item);
+ var missingEntries = dec.GetMissingVendorSpecifics(schema);
+
+ if (missingEntries.ToArray().Length > 0)
+ {
+ var missingPrefixes = missingEntries.Select(e => e.Substring(0, e.IndexOf('-', 1) + 1));
+ string error = string.Format(CultureInfo.InvariantCulture, Resources.BestPracticeAddMissingVendorSpecific, dec.PropertyName.Text, string.Join(", ", missingPrefixes));
+ ICssError tag = new SimpleErrorTag(dec.PropertyName, error);
+ context.AddError(tag);
+ return ItemCheckResult.CancelCurrentItem;
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ private static bool IgnoreProperty(Declaration declaration)
+ {
+ return _vendorIgnoreList.Contains(declaration.PropertyName.Text);
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(Declaration) }; }
+ }
+ }
+}
diff --git a/EditorExtensions/ErrorTags/Providers/MissingVendorPseudoErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/MissingVendorPseudoErrorTagProvider.cs
new file mode 100644
index 000000000..47a0bd591
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/MissingVendorPseudoErrorTagProvider.cs
@@ -0,0 +1,116 @@
+//using Microsoft.CSS.Core;
+//using Microsoft.CSS.Editor;
+//using Microsoft.VisualStudio.Utilities;
+//using System;
+//using System.Collections.Generic;
+//using System.ComponentModel.Composition;
+//using System.Linq;
+
+//namespace MadsKristensen.EditorExtensions
+//{
+// [Export(typeof(ICssItemChecker))]
+// [Name("MissingVendorPseudoErrorTagProvider")]
+// [Order(After = "Default Declaration")]
+// internal class MissingVendorPseudoErrorTagProvider : ICssItemChecker
+// {
+// public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+// {
+// string name = item.Text.TrimStart(':');
+// RuleSet rule = item.FindType();
+// var buffer = ProjectHelpers.GetCurentTextBuffer();
+
+// if (rule == null || buffer == null)
+// return ItemCheckResult.Continue;
+
+// ICssSchemaInstance root = CssSchemaManager.SchemaManager.GetSchemaRootForBuffer(buffer);
+// ICssSchemaInstance schema = CssSchemaManager.SchemaManager.GetSchemaForItem(root, item);
+// string selector = GetSelectorText(rule);
+// string standardName = StandardizeName(name);
+
+// //if (standardName == "selection" || standardName == "progress-bar")
+// // return ItemCheckResult.Continue;
+
+// IEnumerable missingPseudos = FindMissingPseudos(standardName, selector, schema).Where(p => p != item.Text);
+
+// if (missingPseudos.Any())
+// {
+// string message = string.Format("Browser compatibility: Add selector to the rule with missing pseudo element/class ({0})", string.Join(", ", missingPseudos));
+// context.AddError(new SimpleErrorTag(item, message));
+// }
+
+// return ItemCheckResult.Continue;
+// }
+
+// public static string StandardizeName(string name)
+// {
+// if (name.StartsWith("-"))
+// {
+// int index = name.IndexOf('-', 1);
+// if (index > -1)
+// return name.Substring(index + 1);
+// }
+
+// return name;
+// }
+
+// public static IEnumerable FindMissingPseudos(string name, string selector, ICssSchemaInstance schema)
+// {
+// ICssCompletionListEntry standard = FindPseudo(schema, name);
+
+// if (standard != null && !selector.Contains(standard.DisplayText))
+// yield return standard.DisplayText;
+
+// foreach (string prefix in VendorHelpers.GetPrefixes(schema))
+// {
+// string text = GetPseudoName(prefix, name);
+// ICssCompletionListEntry pseudo = FindPseudo(schema, text);
+
+// if (pseudo != null)
+// {
+// if (!selector.Contains(pseudo.DisplayText))
+// yield return pseudo.DisplayText;
+// }
+// }
+// }
+
+// private static ICssCompletionListEntry FindPseudo(ICssSchemaInstance schema, string text)
+// {
+// ICssCompletionListEntry pseudo = schema.GetPseudo(":" + text);
+
+// if (pseudo == null)
+// {
+// pseudo = schema.GetPseudo("::" + text);
+// }
+
+// return pseudo;
+// }
+
+// private static string GetPseudoName(string prefix, string name)
+// {
+// if (prefix == "-moz-")
+// {
+// if (name.EndsWith("input-placeholder"))
+// return "-moz-placeholder";
+// }
+// else
+// {
+// if (name == "placeholder")
+// return prefix + "input-placeholder";
+// }
+
+// return prefix + name;
+// }
+
+// public static string GetSelectorText(RuleSet rule)
+// {
+// IEnumerable text = rule.Selectors.Select(s => s.Text);
+
+// return string.Join(",", text);
+// }
+
+// public IEnumerable ItemTypes
+// {
+// get { return new[] { typeof(PseudoClassSelector) }; }
+// }
+// }
+//}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Providers/MsViewState.cs b/EditorExtensions/ErrorTags/Providers/MsViewState.cs
new file mode 100644
index 000000000..2cbb88f34
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/MsViewState.cs
@@ -0,0 +1,45 @@
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("MsViewState")]
+ [Order(After = "Default Declaration")]
+ internal class MsViewStateErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ var media = item as MediaExpression;
+
+ if (media == null || context == null || !IsWindowsWebApp())
+ return ItemCheckResult.Continue;
+
+ int index = media.Text.IndexOf("-ms-view-state", StringComparison.OrdinalIgnoreCase);
+
+ if (index > -1)
+ {
+ var property = item.StyleSheet.ItemAfterPosition(media.Start + index);
+
+ string message = "The -ms-view-state has been deprecated in Internet Explorer 11";
+ context.AddError(new SimpleErrorTag(property, message, CssErrorFlags.TaskListWarning | CssErrorFlags.UnderlineRed));
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ private bool IsWindowsWebApp()
+ {
+ // Add logic to determine if the current project is WWA
+ return true;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(MediaExpression) }; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Providers/OverQualifiedSelectorErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/OverQualifiedSelectorErrorTagProvider.cs
new file mode 100644
index 000000000..b8c627938
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/OverQualifiedSelectorErrorTagProvider.cs
@@ -0,0 +1,44 @@
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Globalization;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("OverQualifiedSelectorErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class OverQualifiedSelectorErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ Selector sel = (Selector)item;
+
+ if (!WESettings.GetBoolean(WESettings.Keys.ValidateOverQualifiedSelector) || !sel.IsValid || context == null)
+ return ItemCheckResult.Continue;
+
+ int index = sel.Text.IndexOf('#');
+
+ if (index > 0)
+ {
+ string idName = sel.ItemAfterPosition(sel.Start + index).Text;
+ string remove = sel.Text.Substring(0, index);
+ string errorMessage = string.Format(CultureInfo.InvariantCulture, Resources.PerformanceDontOverQualifySelectors, idName, remove);
+
+ SimpleErrorTag tag = new SimpleErrorTag(sel, errorMessage, index);
+
+ context.AddError(tag);
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(Selector) }; }
+ }
+ }
+}
diff --git a/EditorExtensions/ErrorTags/Providers/OverriddenPropertyErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/OverriddenPropertyErrorTagProvider.cs
new file mode 100644
index 000000000..a7330adc5
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/OverriddenPropertyErrorTagProvider.cs
@@ -0,0 +1,55 @@
+//using Microsoft.CSS.Core;
+//using Microsoft.VisualStudio.Utilities;
+//using System;
+//using System.Collections.Generic;
+//using System.ComponentModel.Composition;
+//using System.Linq;
+
+//namespace MadsKristensen.EditorExtensions
+//{
+// [Export(typeof(ICssItemChecker))]
+// [Name("OverriddenPropertyErrorTagProvider")]
+// [Order(After = "Default Declaration")]
+// internal class OverriddenPropertyErrorTagProvider : ICssItemChecker
+// {
+// public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+// {
+// Declaration dec = (Declaration)item;
+// RuleBlock rule = item.Parent as RuleBlock;
+
+// if (!dec.IsValid || context == null || rule == null)
+// return ItemCheckResult.Continue;
+
+// List list = new List();
+
+// foreach (Declaration declaration in rule.Declarations.Where(d => d.PropertyName != null))
+// {
+// if (declaration == dec) // Don't look beyond current declaration in the RuleBlock
+// break;
+
+// ParseItem prop = declaration.PropertyName;
+// if (prop == null || prop.Text == "border-radius" || dec.PropertyName.Text == "background" || dec.Values.Any(v => v.Text.StartsWith("-")))
+// continue;
+
+// if (prop.Length > dec.PropertyName.Length && prop.Text.StartsWith(dec.PropertyName.Text))
+// {
+// list.Add(prop.Text);
+// }
+// }
+
+// if (list.Count > 0)
+// {
+// string message = "Best practice: '{0}' is overriding previously declared properties in the same rule block ({1})";
+// string error = string.Format(message, dec.PropertyName.Text, string.Join(", ", list.ToArray()));
+// context.AddError(new SimpleErrorTag(dec.PropertyName, error));
+// }
+
+// return ItemCheckResult.Continue;
+// }
+
+// public IEnumerable ItemTypes
+// {
+// get { return new[] { typeof(Declaration) }; }
+// }
+// }
+//}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Providers/ShorthandErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/ShorthandErrorTagProvider.cs
new file mode 100644
index 000000000..c1fa565f2
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/ShorthandErrorTagProvider.cs
@@ -0,0 +1,60 @@
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("ShorthandErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class ShorthandErrorTagProvider : ICssItemChecker
+ {
+ private static Dictionary _cache = new Dictionary()
+ {
+ {"margin", new [] { "margin-top", "margin-right", "margin-bottom", "margin-left" }},
+ {"padding", new [] { "padding-top", "padding-right", "padding-bottom", "padding-left" }},
+ {"border", new [] { "border-width", "border-style", "border-color" }},
+ {"border-color", new [] { "border-left-color", "border-top-color", "border-right-color", "border-bottom-color" }},
+ {"border-style", new [] { "border-left-style", "border-top-style", "border-right-style", "border-bottom-style" }},
+ {"border-radius", new [] { "border-top-left-radius", "border-top-right-radius", "border-bottom-left-radius", "border-bottom-right-radius" }},
+ {"outline", new [] { "outline-width", "outline-style", "outline-color" }},
+ {"list-style", new [] { "list-style-type", "list-style-position", "list-style-image" }},
+ {"text-decoration", new [] { "text-decoration-color", "text-decoration-style", "text-decoration-line" }},
+ };
+
+
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ RuleBlock rule = (RuleBlock)item;
+
+ if (!rule.IsValid || context == null)
+ return ItemCheckResult.Continue;
+
+ IEnumerable properties = from d in rule.Declarations
+ where d.PropertyName != null && d.Values.Count < 2
+ select d.PropertyName.Text;
+
+
+ foreach (string shorthand in _cache.Keys)
+ {
+ if (_cache[shorthand].All(p => properties.Contains(p)))
+ {
+ Declaration dec = rule.Declarations.First(p => p.PropertyName != null && _cache[shorthand].Contains(p.PropertyName.Text));
+ string message = string.Format(Resources.PerformanceUseShorthand, string.Join(", ", _cache[shorthand]), shorthand);
+
+ context.AddError(new SimpleErrorTag(dec, message));
+ }
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(RuleBlock) }; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Providers/StarSelectorErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/StarSelectorErrorTagProvider.cs
new file mode 100644
index 000000000..abbe1ba90
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/StarSelectorErrorTagProvider.cs
@@ -0,0 +1,44 @@
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Globalization;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("StarSelectorErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class StarSelectorErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ SimpleSelector sel = (SimpleSelector)item;
+
+ if (!WESettings.GetBoolean(WESettings.Keys.ValidateStarSelector) || !sel.IsValid || context == null)
+ return ItemCheckResult.Continue;
+
+ if (sel.Text == "*")
+ {
+ //string afterStar = sel.Text.Length > index + 1 ? sel.Text.Substring(index + 1) : null;
+ //if (afterStar == null || !afterStar.Trim().StartsWith("html", StringComparison.OrdinalIgnoreCase))
+ //{
+ string errorMessage = string.Format(CultureInfo.InvariantCulture, Resources.PerformanceDontUseStarSelector);
+
+ SimpleErrorTag tag = new SimpleErrorTag(sel, errorMessage);
+
+ context.AddError(tag);
+ //}
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(SimpleSelector) }; }
+ }
+ }
+}
diff --git a/EditorExtensions/ErrorTags/Providers/UnknownTagErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/UnknownTagErrorTagProvider.cs
new file mode 100644
index 000000000..dd43890c3
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/UnknownTagErrorTagProvider.cs
@@ -0,0 +1,163 @@
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("UnknownTagErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class UnknownTagErrorTagProvider : ICssItemChecker
+ {
+ private HashSet _cache = new HashSet(){
+ "*",
+ "a",
+ "abbr",
+ "acronym",
+ "address",
+ "applet",
+ "area",
+ "article",
+ "aside",
+ "audio",
+ "b",
+ "base",
+ "basefont",
+ "bdi",
+ "bdo",
+ "big",
+ "blockquote",
+ "body",
+ "br",
+ "button",
+ "canvas",
+ "caption",
+ "center",
+ "cite",
+ "code",
+ "col",
+ "colgroup",
+ "command",
+ "datalist",
+ "dd",
+ "del",
+ "details",
+ "dfn",
+ "dir",
+ "div",
+ "dl",
+ "dt",
+ "em",
+ "embed",
+ "fieldset",
+ "figcaption",
+ "figure",
+ "font",
+ "footer",
+ "form",
+ "frame",
+ "frameset",
+ "h1",
+ "h2",
+ "h3",
+ "h4",
+ "h5",
+ "h6",
+ "head",
+ "header",
+ "hgroup",
+ "hr",
+ "html",
+ "i",
+ "iframe",
+ "img",
+ "input",
+ "ins",
+ "keygen",
+ "kbd",
+ "label",
+ "legend",
+ "li",
+ "link",
+ "map",
+ "mark",
+ "menu",
+ "meta",
+ "meter",
+ "nav",
+ "noframes",
+ "noscript",
+ "object",
+ "ol",
+ "optgroup",
+ "option",
+ "output",
+ "p",
+ "param",
+ "pre",
+ "progress",
+ "q",
+ "rp",
+ "rt",
+ "ruby",
+ "s",
+ "samp",
+ "script",
+ "section",
+ "select",
+ "small",
+ "source",
+ "span",
+ "strike",
+ "strong",
+ "style",
+ "sub",
+ "summary",
+ "sup",
+ "svg",
+ "table",
+ "tbody",
+ "td",
+ "textarea",
+ "tfoot",
+ "th",
+ "thead",
+ "time",
+ "title",
+ "tr",
+ "track",
+ "tt",
+ "u",
+ "ul",
+ "var",
+ "video",
+ "wbr"
+ };
+
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ ItemName itemName = (ItemName)item;
+
+ if (!itemName.IsValid || context == null || (item.PreviousSibling != null && item.PreviousSibling.Text == "["))
+ return ItemCheckResult.Continue;
+
+ if (!_cache.Contains(itemName.Text.ToLowerInvariant()))
+ {
+ string error = "Validation: \"" + itemName.Text + "\" isn't a valid HTML tag.";
+ ICssError tag = new SimpleErrorTag(itemName, error);
+ context.AddError(tag);
+
+ return ItemCheckResult.CancelCurrentItem;
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(ItemName) }; }
+ }
+ }
+}
diff --git a/EditorExtensions/ErrorTags/Providers/VendorOrderErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/VendorOrderErrorTagProvider.cs
new file mode 100644
index 000000000..94cdcae82
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/VendorOrderErrorTagProvider.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Globalization;
+using System.Linq;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.CSS.Editor.Schemas;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.CSS.Editor.SyntaxCheck;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("VendorOrderErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class VendorOrderErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ Declaration dec = (Declaration)item;
+
+ if (context == null || !dec.IsValid)
+ return ItemCheckResult.Continue;
+
+ RuleBlock rule = dec.FindType();
+ if (!rule.IsValid)
+ return ItemCheckResult.Continue;
+
+ if (!dec.IsVendorSpecific())
+ {
+ ICssSchemaInstance schema = CssEditorChecker.GetSchemaForItem(context, item);
+ bool hasVendor = VendorHelpers.HasVendorLaterInRule(dec, schema);
+ if (hasVendor)
+ {
+ context.AddError(new SimpleErrorTag(dec.PropertyName, Resources.BestPracticeStandardPropertyOrder));
+ return ItemCheckResult.CancelCurrentItem;
+ }
+ }
+ else
+ {
+ ICssCompletionListEntry entry = VendorHelpers.GetMatchingStandardEntry(dec, context);
+ if (entry != null && !rule.Declarations.Any(d => d.PropertyName != null && d.PropertyName.Text == entry.DisplayText))
+ {
+ if (entry.DisplayText != "filter" && entry.DisplayText != "zoom" && entry.DisplayText != "behavior")
+ {
+ string message = string.Format(CultureInfo.InvariantCulture, Resources.BestPracticeAddMissingStandardProperty, entry.DisplayText);
+ context.AddError(new SimpleErrorTag(dec.PropertyName, message));
+ return ItemCheckResult.CancelCurrentItem;
+ }
+ }
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(Declaration) }; }
+ }
+ }
+}
diff --git a/EditorExtensions/ErrorTags/Providers/W3cOnlyErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/W3cOnlyErrorTagProvider.cs
new file mode 100644
index 000000000..11d3c7c10
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/W3cOnlyErrorTagProvider.cs
@@ -0,0 +1,109 @@
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Globalization;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("W3cOnlyErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class W3cOnlyErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ if (!WESettings.GetBoolean(WESettings.Keys.OnlyW3cAllowed))
+ return ItemCheckResult.Continue;
+
+ if (item is Declaration)
+ {
+ HandleDeclaration(item, context);
+ }
+ else if (item is AtDirective)
+ {
+ HandleDirective(item, context);
+ }
+
+ else if (item is PseudoClassFunctionSelector || item is PseudoClassSelector || item is PseudoElementFunctionSelector || item is PseudoElementSelector)
+ {
+ HandlePseudo(item, context);
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ private static void HandleDeclaration(ParseItem item, ICssCheckerContext context)
+ {
+ Declaration dec = (Declaration)item;
+
+ if (dec == null || dec.PropertyName == null)
+ return;
+
+ if (dec.IsVendorSpecific())
+ {
+ string message = string.Format("Validation (W3C): \"{0}\" is not a valid W3C property", dec.PropertyName.Text);
+ context.AddError(new SimpleErrorTag(dec.PropertyName, message, CssErrorFlags.TaskListWarning | CssErrorFlags.UnderlineRed));
+ }
+
+ foreach (var value in dec.Values)
+ {
+ string text = value.Text;
+ if (!(value is NumericalValue) && text.StartsWith("-", StringComparison.Ordinal))
+ {
+ int index = text.IndexOf('(');
+
+ if (index > -1)
+ {
+ text = text.Substring(0, index);
+ }
+
+ string message = string.Format("Validation (W3C): \"{0}\" is not a valid W3C value", text);
+ context.AddError(new SimpleErrorTag(value, message, CssErrorFlags.TaskListWarning | CssErrorFlags.UnderlineRed));
+ }
+ }
+ }
+
+ private static void HandleDirective(ParseItem item, ICssCheckerContext context)
+ {
+ AtDirective dir = (AtDirective)item;
+
+ if (dir == null || dir.Keyword == null)
+ return;
+
+ if (dir.IsVendorSpecific())
+ {
+ string message = string.Format("Validation (W3C): \"@{0}\" is not a valid W3C @-directive", dir.Keyword.Text);
+ context.AddError(new SimpleErrorTag(dir.Keyword, message, CssErrorFlags.TaskListWarning | CssErrorFlags.UnderlineRed));
+ }
+ }
+
+ private static void HandlePseudo(ParseItem item, ICssCheckerContext context)
+ {
+ string text = item.Text.TrimStart(':');
+
+ if (text.StartsWith("-", StringComparison.Ordinal))
+ {
+ string message = string.Format("Validation (W3C): \"{0}\" is not a valid W3C pseudo class/element", item.Text);
+ context.AddError(new SimpleErrorTag(item, message, CssErrorFlags.TaskListWarning | CssErrorFlags.UnderlineRed));
+ }
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get
+ {
+ return new[]
+ {
+ typeof(Declaration),
+ typeof(AtDirective),
+ typeof(PseudoClassFunctionSelector),
+ typeof(PseudoClassSelector),
+ typeof(PseudoElementFunctionSelector),
+ typeof(PseudoElementSelector),
+ };
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/Providers/ZeroUnitErrorTagProvider.cs b/EditorExtensions/ErrorTags/Providers/ZeroUnitErrorTagProvider.cs
new file mode 100644
index 000000000..89733d6a3
--- /dev/null
+++ b/EditorExtensions/ErrorTags/Providers/ZeroUnitErrorTagProvider.cs
@@ -0,0 +1,46 @@
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(ICssItemChecker))]
+ [Name("ZeroUnitErrorTagProvider")]
+ [Order(After = "Default Declaration")]
+ internal class ZeroUnitErrorTagProvider : ICssItemChecker
+ {
+ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
+ {
+ if (!WESettings.GetBoolean(WESettings.Keys.ValidateZeroUnit))
+ return ItemCheckResult.Continue;
+
+ NumericalValue number = (NumericalValue)item;
+ UnitValue unit = number as UnitValue;
+
+ if (unit == null || context == null)
+ return ItemCheckResult.Continue;
+
+ if (number.Number.Text == "0" && unit.UnitType != UnitType.Unknown && unit.UnitType != UnitType.Time)
+ {
+ string message = string.Format(Resources.BestPracticeZeroUnit, unit.UnitToken.Text);
+ context.AddError(new SimpleErrorTag(number, message));
+ }
+
+ return ItemCheckResult.Continue;
+ }
+
+ private static UnitType GetUnitType(ParseItem valueItem)
+ {
+ UnitValue unitValue = valueItem as UnitValue;
+
+ return (unitValue != null) ? unitValue.UnitType : UnitType.Unknown;
+ }
+
+ public IEnumerable ItemTypes
+ {
+ get { return new[] { typeof(NumericalValue) }; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/ErrorTags/SelectorErrorTag.cs b/EditorExtensions/ErrorTags/SelectorErrorTag.cs
new file mode 100644
index 000000000..b8a954294
--- /dev/null
+++ b/EditorExtensions/ErrorTags/SelectorErrorTag.cs
@@ -0,0 +1,62 @@
+using Microsoft.CSS.Core;
+using System.Linq;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class SelectorErrorTag : ICssError
+ {
+ private SortedRangeList _range;
+ private string _errorMessage;
+
+ public SelectorErrorTag(SortedRangeList range, string errorMessage)
+ {
+ _range = range;
+ _errorMessage = errorMessage;
+ Flags = GetLocation();
+ }
+
+ private static CssErrorFlags GetLocation()
+ {
+ switch ((WESettings.Keys.ErrorLocation)WESettings.GetInt(WESettings.Keys.CssErrorLocation))
+ {
+ case WESettings.Keys.ErrorLocation.Warnings:
+ return CssErrorFlags.UnderlinePurple | CssErrorFlags.TaskListWarning;
+
+ default:
+ return CssErrorFlags.UnderlinePurple | CssErrorFlags.TaskListMessage;
+ }
+ }
+
+ public bool IsExposedToUser
+ {
+ get { return true; }
+ }
+
+ public ParseItem Item
+ {
+ get { return _range.First(); }
+ }
+
+ public string Text
+ {
+ get { return _errorMessage; }
+ }
+
+ public int AfterEnd
+ {
+ get { return _range.Last().AfterEnd; }
+ }
+
+ public int Length
+ {
+ get { return AfterEnd - Start; }
+ }
+
+ public int Start
+ {
+ get { return _range.First().Start; }
+ }
+
+ public CssErrorFlags Flags {get; set; }
+ }
+}
diff --git a/EditorExtensions/ErrorTags/SimpleErrorTag.cs b/EditorExtensions/ErrorTags/SimpleErrorTag.cs
new file mode 100644
index 000000000..15c82901b
--- /dev/null
+++ b/EditorExtensions/ErrorTags/SimpleErrorTag.cs
@@ -0,0 +1,79 @@
+using Microsoft.CSS.Core;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class SimpleErrorTag : ICssError
+ {
+ private ParseItem _item;
+ private string _errorMessage;
+ private int _length;
+
+ public SimpleErrorTag(ParseItem item, string errorMessage, CssErrorFlags flags = CssErrorFlags.TaskListMessage | CssErrorFlags.UnderlinePurple)
+ {
+ _item = item;
+ _errorMessage = errorMessage;
+ _length = AfterEnd - Start;
+ Flags = flags;
+ }
+
+ public SimpleErrorTag(ParseItem item, string errorMessage)
+ {
+ _item = item;
+ _errorMessage = errorMessage;
+ _length = AfterEnd - Start;
+ Flags = GetLocation();
+ }
+
+ public SimpleErrorTag(ParseItem item, string errorMessage, int length)
+ {
+ _item = item;
+ _errorMessage = errorMessage;
+ _length = length;
+ Flags = GetLocation();
+ }
+
+ private static CssErrorFlags GetLocation()
+ {
+ switch ((WESettings.Keys.ErrorLocation)WESettings.GetInt(WESettings.Keys.CssErrorLocation))
+ {
+ case WESettings.Keys.ErrorLocation.Warnings:
+ return CssErrorFlags.UnderlinePurple | CssErrorFlags.TaskListWarning;
+
+ default:
+ return CssErrorFlags.UnderlinePurple | CssErrorFlags.TaskListMessage;
+ }
+ }
+
+ public bool IsExposedToUser
+ {
+ get { return true; }
+ }
+
+ public ParseItem Item
+ {
+ get { return _item; }
+ }
+
+ public string Text
+ {
+ get { return _errorMessage; }
+ }
+
+ public int AfterEnd
+ {
+ get { return _item.AfterEnd; }
+ }
+
+ public int Length
+ {
+ get { return _length; }
+ }
+
+ public int Start
+ {
+ get { return _item.Start; }
+ }
+
+ public CssErrorFlags Flags {get; set; }
+ }
+}
diff --git a/EditorExtensions/ExtensionMethods/AtDirectiveExtensions.cs b/EditorExtensions/ExtensionMethods/AtDirectiveExtensions.cs
new file mode 100644
index 000000000..7b93e9ac9
--- /dev/null
+++ b/EditorExtensions/ExtensionMethods/AtDirectiveExtensions.cs
@@ -0,0 +1,64 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.CSS.Editor.Schemas;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal static class AtDirectiveExtensions
+ {
+ public static bool IsVendorSpecific(this AtDirective directive)
+ {
+ return directive.Keyword.Text[0] == '-';
+ }
+
+
+ public static bool TryGetStandardPropertyName(this AtDirective directive, out string standardName, ICssSchemaInstance schema)
+ {
+ standardName = null;
+
+ string propText = directive.Keyword.Text;
+ string prefix = VendorHelpers.GetPrefixes(schema).SingleOrDefault(p => propText.IndexOf(p, StringComparison.Ordinal) == 0);
+ if (prefix != null)
+ {
+ standardName = propText.Substring(prefix.Length);
+ return true;
+ }
+
+ return false;
+ }
+
+ public static IEnumerable GetMissingVendorSpecifics(this AtDirective directive, ICssSchemaInstance schema)
+ {
+ IEnumerable possible = GetPossibleVendorSpecifics(directive, schema);
+
+ var visitorRules = new CssItemCollector();
+ directive.Parent.Accept(visitorRules);
+
+ foreach (string item in possible)
+ {
+ if (!visitorRules.Items.Any(d => d.Keyword != null && "@" + d.Keyword.Text == item))
+ yield return item;
+ //if (!rule.Declarations.Any(d => d.PropertyName != null && d.PropertyName.Text == item))
+ // yield return item;
+ }
+ }
+
+ public static IEnumerable GetPossibleVendorSpecifics(this AtDirective directive, ICssSchemaInstance schema)
+ {
+ string text = directive.Keyword.Text;
+
+ foreach (string prefix in VendorHelpers.GetPrefixes(schema).Where(p => p != "-o-")) // Remove -o- since the parser doesn't recognize -o-keyframes
+ {
+ ICssCompletionListEntry entry = schema.GetAtDirective("@" + prefix + text);
+ if (entry != null)
+ {
+ yield return entry.DisplayText;
+ }
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/ExtensionMethods/ColorModelExtensions.cs b/EditorExtensions/ExtensionMethods/ColorModelExtensions.cs
new file mode 100644
index 000000000..5908df2f1
--- /dev/null
+++ b/EditorExtensions/ExtensionMethods/ColorModelExtensions.cs
@@ -0,0 +1,56 @@
+using Microsoft.Web.Editor;
+using System.Windows.Media;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal static class ColorModelExtensions
+ {
+ private const float _factor = 0.025F;
+
+ public static ColorModel Brighten(this ColorModel color)
+ {
+ if ((color.HslLightness + _factor) < 1)
+ {
+ color.HslLightness += _factor;
+ }
+
+ return color;
+ }
+
+ public static ColorModel Darken(this ColorModel color)
+ {
+ if ((color.HslLightness - _factor) > 0)
+ {
+ color.HslLightness -= _factor;
+ }
+
+ return color;
+ }
+
+ public static ColorModel Invert(this ColorModel color)
+ {
+ ColorModel model = new ColorModel()
+ {
+ Red = ~(byte)color.Red,
+ Green = ~(byte)color.Green,
+ Blue = ~(byte)color.Blue
+ };
+
+ return model;
+
+ }
+
+ public static SolidColorBrush ToBrush(this ColorModel color)
+ {
+ Color c = Color.FromRgb(
+ (byte)color.Red,
+ (byte)color.Green,
+ (byte)color.Blue
+ );
+
+ SolidColorBrush brush = new SolidColorBrush(c);
+ brush.Freeze();
+ return brush;
+ }
+ }
+}
diff --git a/EditorExtensions/ExtensionMethods/DeclarationExtensions.cs b/EditorExtensions/ExtensionMethods/DeclarationExtensions.cs
new file mode 100644
index 000000000..a45d5cf4c
--- /dev/null
+++ b/EditorExtensions/ExtensionMethods/DeclarationExtensions.cs
@@ -0,0 +1,62 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.CSS.Editor.Schemas;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal static class DeclarationExtensions
+ {
+ public static bool IsVendorSpecific(this Declaration declaration)
+ {
+ return declaration.PropertyName.Length > 0 ? declaration.PropertyName.Text[0] == '-' : false;
+ }
+
+ public static bool TryGetStandardPropertyName(this Declaration declaration, out string standardName, ICssSchemaInstance schema)
+ {
+ standardName = null;
+
+ if (declaration.IsVendorSpecific())
+ {
+ string propText = declaration.PropertyName.Text;
+ string prefix = VendorHelpers.GetPrefixes(schema).SingleOrDefault(p => propText.IndexOf(p, StringComparison.Ordinal) == 0);
+ if (prefix != null)
+ {
+ standardName = propText.Substring(prefix.Length);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static IEnumerable GetMissingVendorSpecifics(this Declaration declaration, ICssSchemaInstance schema)
+ {
+ RuleBlock rule = declaration.FindType();
+ IEnumerable possible = GetPossibleVendorSpecifics(declaration, schema);
+
+ foreach (string item in possible)
+ {
+ if (!rule.Declarations.Any(d => d.PropertyName != null && d.PropertyName.Text == item))
+ yield return item;
+ }
+ }
+
+ public static IEnumerable GetPossibleVendorSpecifics(this Declaration declaration, ICssSchemaInstance schema)
+ {
+ string text = declaration.PropertyName.Text;
+
+ foreach (string prefix in VendorHelpers.GetPrefixes(schema))
+ {
+ ICssCompletionListEntry entry = schema.GetProperty(prefix + text);
+ if (entry != null)
+ {
+ yield return entry.DisplayText;
+ }
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/ExtensionMethods/IVsExtensions.cs b/EditorExtensions/ExtensionMethods/IVsExtensions.cs
new file mode 100644
index 000000000..a19ca8b02
--- /dev/null
+++ b/EditorExtensions/ExtensionMethods/IVsExtensions.cs
@@ -0,0 +1,38 @@
+using EnvDTE;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+
+namespace MadsKristensen.EditorExtensions
+{
+ public static class IVsExtensions
+ {
+ public static void AddHierarchyItem(this ErrorTask task)
+ {
+ IVsHierarchy HierarchyItem;
+ IVsSolution solution = EditorExtensionsPackage.GetGlobalService(typeof(SVsSolution));
+ Project project = ProjectHelpers.GetActiveProject();
+
+ if (solution != null && project != null)
+ {
+ int flag = solution.GetProjectOfUniqueName(project.FullName, out HierarchyItem);
+
+ if (0 == flag)
+ {
+ task.HierarchyItem = HierarchyItem;
+ }
+ }
+ }
+
+ public static bool IsLink(this ProjectItem item)
+ {
+ try
+ {
+ return (bool)item.Properties.Item("IsLink").Value;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/ExtensionMethods/PseudoExtensions.cs b/EditorExtensions/ExtensionMethods/PseudoExtensions.cs
new file mode 100644
index 000000000..447b69966
--- /dev/null
+++ b/EditorExtensions/ExtensionMethods/PseudoExtensions.cs
@@ -0,0 +1,18 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Schemas;
+
+namespace MadsKristensen.EditorExtensions
+{
+ public static class PseudoExtensions
+ {
+ public static bool IsPseudoElement(this ParseItem item)
+ {
+ if (item.Text.StartsWith("::"))
+ return true;
+
+ var schema = CssSchemaManager.SchemaManager.GetSchemaRoot(null);
+ return schema.GetPseudo(":" + item.Text) != null;
+ }
+ }
+}
diff --git a/EditorExtensions/GlobalSuppressions.cs b/EditorExtensions/GlobalSuppressions.cs
new file mode 100644
index 000000000..37d942cc4
--- /dev/null
+++ b/EditorExtensions/GlobalSuppressions.cs
@@ -0,0 +1 @@
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1017:MarkAssembliesWithComVisible")]
diff --git a/EditorExtensions/Guids.cs b/EditorExtensions/Guids.cs
new file mode 100644
index 000000000..e1cdf3e43
--- /dev/null
+++ b/EditorExtensions/Guids.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace MadsKristensen.EditorExtensions
+{
+ static class GuidList
+ {
+ public const string guidEditorExtensionsPkgString = "5fb7364d-2e8c-44a4-95eb-2a382e30fec7";
+ public const string guidEditorExtensionsCmdSetString = "e396b698-e00e-444b-9f5f-3dcb1ef74e41";
+ public const string guidCssCmdSetString = "e396b698-e00e-444b-9f5f-3dcb1ef74e50";
+ public const string guidCssIntellisensCmdSetString = "e396b698-e00e-444b-9f5f-3dcb1ef74e51";
+ public const string guidWcfToolsCmdSetString = "1446a66d-7e3e-40ce-808e-89a7202d050d";
+ public const string guidDiffCmdSetString = "e396b698-e00e-444b-9f5f-3dcb1ef74e59";
+ public const string guidMinifyCmdSetString = "e396b698-e00e-444b-9f5f-3dcb1ef74e61";
+ public const string guidBundleCmdSetString = "e396b698-e00e-444b-9f5f-3dcb1ef74e63";
+ public const string guidExtractCmdSetString = "e396b698-e00e-444b-9f5f-3dcb1ef74e64";
+ public const string guidBuildCmdSetString = "e396b698-e00e-444b-9f5f-3dcb1ef74e65";
+ public const string guidFormattingCmdSetString = "e396b698-e00e-444b-9f5f-3dcb1ef74e66";
+
+ public static readonly Guid guidEditorExtensionsCmdSet = new Guid(guidEditorExtensionsCmdSetString);
+ public static readonly Guid guidCssCmdSet = new Guid(guidCssCmdSetString);
+ public static readonly Guid guidCssIntellisenseCmdSet = new Guid(guidCssIntellisensCmdSetString);
+ public static readonly Guid guidWcfToolsCmdSet = new Guid(guidWcfToolsCmdSetString);
+ public static readonly Guid guidDiffCmdSet = new Guid(guidDiffCmdSetString);
+ public static readonly Guid guidMinifyCmdSet = new Guid(guidMinifyCmdSetString);
+ public static readonly Guid guidBundleCmdSet = new Guid(guidBundleCmdSetString);
+ public static readonly Guid guidExtractCmdSet = new Guid(guidExtractCmdSetString);
+ public static readonly Guid guidBuildCmdSet = new Guid(guidBuildCmdSetString);
+ public static readonly Guid guidFormattingCmdSet = new Guid(guidFormattingCmdSetString);
+ };
+}
\ No newline at end of file
diff --git a/EditorExtensions/HTML/Base64ChromeAction.cs b/EditorExtensions/HTML/Base64ChromeAction.cs
new file mode 100644
index 000000000..4a55bae9d
--- /dev/null
+++ b/EditorExtensions/HTML/Base64ChromeAction.cs
@@ -0,0 +1,71 @@
+//using System;
+//using System.IO;
+//using System.Text;
+//using System.Xml;
+//using Microsoft.VisualStudio.Text;
+//using Microsoft.VisualStudio.Text.Editor;
+//using Microsoft.VisualStudio.Web.HTML.Chrome;
+
+//namespace MadsKristensen.EditorExtensions
+//{
+// [ChromeAction, System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
+// internal class Base64ChromeAction : IChromeAction
+// {
+// string IChromeAction.Name { get { return "Convert to dataURI"; } }
+
+// void IChromeAction.Execute(IWpfTextView view, string tagName, int tagPosition)
+// {
+// string line = view.TextBuffer.CurrentSnapshot.GetText(tagPosition, view.TextBuffer.CurrentSnapshot.Length - tagPosition);
+// int length = line.IndexOf('>') + 1;
+
+// if (length > 0)
+// {
+// string element = line.Substring(0, length);
+// XmlNode img = ConvertToXml(element);
+
+// if (img != null && img.Attributes["src"] != null)
+// {
+// XmlAttribute src = img.Attributes["src"];
+// string dataUri = ConvertToDataUri(src);
+
+// if (!string.IsNullOrEmpty(dataUri))
+// {
+// src.InnerText = dataUri;
+// view.TextBuffer.Replace(new Span(tagPosition, length), img.OuterXml);
+// }
+// }
+// }
+// }
+
+// private static string ConvertToDataUri(XmlAttribute src)
+// {
+// string fileName = ProjectHelpers.ToAbsoluteFilePath(src.InnerText);
+// if (!string.IsNullOrEmpty(fileName) && File.Exists(fileName))
+// {
+// return FileHelpers.ConvertToBase64(fileName);
+// }
+
+// return null;
+// }
+
+// private static XmlNode ConvertToXml(string element)
+// {
+// StringBuilder sb = new StringBuilder();
+
+// using (XmlWriter writer = XmlWriter.Create(sb))
+// {
+// writer.WriteRaw(element);
+// }
+
+// XmlDocument doc = new XmlDocument();
+// doc.LoadXml(sb.ToString());
+
+// return doc.SelectSingleNode("//img");
+// }
+
+// bool IChromeAction.IsAvailable(IWpfTextView view, string tagName, int tagPosition)
+// {
+// return tagName.Equals("img", StringComparison.OrdinalIgnoreCase);
+// }
+// }
+//}
\ No newline at end of file
diff --git a/EditorExtensions/HTML/MathMlSchemaFileInfoProvider.cs b/EditorExtensions/HTML/MathMlSchemaFileInfoProvider.cs
new file mode 100644
index 000000000..4c40f3325
--- /dev/null
+++ b/EditorExtensions/HTML/MathMlSchemaFileInfoProvider.cs
@@ -0,0 +1,33 @@
+//using System.Collections.Generic;
+//using System.ComponentModel.Composition;
+//using System.IO;
+//using Microsoft.VisualStudio.Utilities;
+//using Microsoft.VisualStudio.Web.HTML.Schemas;
+
+//namespace MadsKristensen.EditorExtensions
+//{
+// [Export(typeof(IHtmlSchemaFileInfoProvider))]
+// [Name("MathML")]
+// [Order(Before = "Default")]
+// internal class MathMlSchemaFileInfoProvider : IHtmlSchemaFileInfoProvider
+// {
+// private const string _file = @"C:\Users\madsk\Documents\mathml.xsd";
+
+// public IEnumerable GetSchemas(string defaultSchemaPath, string defaultRegistryPath)
+// {
+// if (!File.Exists(_file))
+// yield break;
+
+// SchemaFileInfo info = new SchemaFileInfo()
+// {
+// File = _file,
+// FriendlyName = "MathML",
+// Uri = "http://www.w3.org/1998/Math/MathML",
+// IsMobile = true,
+// IsNonBrowsable = true,
+// };
+
+// yield return info;
+// }
+// }
+//}
diff --git a/EditorExtensions/Helpers/CssItemCollector.cs b/EditorExtensions/Helpers/CssItemCollector.cs
new file mode 100644
index 000000000..4760a7094
--- /dev/null
+++ b/EditorExtensions/Helpers/CssItemCollector.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+using Microsoft.CSS.Core;
+
+namespace MadsKristensen.EditorExtensions
+{
+ ///
+ /// Creates a list of CSS ParseItems of a certain type
+ /// (when passed in as the visitor to any item's Accept() function)
+ ///
+ internal class CssItemCollector : ICssSimpleTreeVisitor where T : ParseItem
+ {
+ public IList Items { get; private set; }
+ private bool _includeChildren;
+
+ public CssItemCollector() : this(false) { }
+
+ public CssItemCollector(bool includeChildren)
+ {
+ _includeChildren = includeChildren;
+ Items = new List();
+ }
+
+ public VisitItemResult Visit(ParseItem parseItem)
+ {
+ var item = parseItem as T;
+
+ if (item != null)
+ {
+ Items.Add(item);
+ return (_includeChildren) ? VisitItemResult.Continue : VisitItemResult.SkipChildren;
+ }
+
+ return VisitItemResult.Continue;
+ }
+ }
+}
diff --git a/EditorExtensions/Helpers/FileHelpers.cs b/EditorExtensions/Helpers/FileHelpers.cs
new file mode 100644
index 000000000..9c44d7164
--- /dev/null
+++ b/EditorExtensions/Helpers/FileHelpers.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Globalization;
+using System.IO;
+using System.Text;
+
+namespace MadsKristensen.EditorExtensions
+{
+ public static class FileHelpers
+ {
+ public static string ConvertToBase64(string fileName)
+ {
+ string format = "data:{0};base64,{1}";
+ byte[] buffer = File.ReadAllBytes(fileName);
+ string extension = Path.GetExtension(fileName).Substring(1);
+ string contentType = GetMimeType(extension);
+
+ return string.Format(CultureInfo.InvariantCulture, format, contentType, Convert.ToBase64String(buffer));
+ }
+
+ private static string GetMimeType(string extension)
+ {
+ switch (extension)
+ {
+ case "png":
+ case "jpg":
+ case "jpeg":
+ case "gif":
+ return "image/" + extension;
+
+ case "woff":
+ return "font/x-woff";
+
+ case "otf":
+ return "font/otf";
+
+ case "eot":
+ return "application/vnd.ms-fontobject";
+
+ case "ttf":
+ return "application/octet-stream";
+
+ default:
+ return "text/plain";
+ }
+ }
+
+ public static string RelativePath(string absPath, string relTo)
+ {
+ string[] absDirs = absPath.Split('\\');
+ string[] relDirs = relTo.Split('\\');
+
+ // Get the shortest of the two paths
+ int len = absDirs.Length < relDirs.Length ? absDirs.Length :
+ relDirs.Length;
+
+ // Use to determine where in the loop we exited
+ int lastCommonRoot = -1;
+ int index;
+
+ // Find common root
+ for (index = 0; index < len; index++)
+ {
+ if (absDirs[index] == relDirs[index]) lastCommonRoot = index;
+ else break;
+ }
+
+ // If we didn't find a common prefix then throw
+ if (lastCommonRoot == -1)
+ {
+ return relTo;
+ }
+
+ // Build up the relative path
+ StringBuilder relativePath = new StringBuilder();
+
+ // Add on the ..
+ for (index = lastCommonRoot + 2; index < absDirs.Length; index++)
+ {
+ if (absDirs[index].Length > 0) relativePath.Append("..\\");
+ }
+
+ // Add on the folders
+ for (index = lastCommonRoot + 1; index < relDirs.Length - 1; index++)
+ {
+ relativePath.Append(relDirs[index] + "\\");
+ }
+ relativePath.Append(relDirs[relDirs.Length - 1]);
+
+ return relativePath.ToString().Replace("\\", "/");
+ }
+ }
+}
diff --git a/EditorExtensions/Helpers/OptionHelpers.cs b/EditorExtensions/Helpers/OptionHelpers.cs
new file mode 100644
index 000000000..d55c390fa
--- /dev/null
+++ b/EditorExtensions/Helpers/OptionHelpers.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Windows.Media;
+using VS = Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Shell.Interop;
+using Microsoft.Web.Editor;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal static class OptionHelpers
+ {
+ private static int _fontSize;
+ private static ColorModel _backgroundColor;
+ private static object _syncRoot = new object();
+
+ // TODO: Compensate for the current line highlighting
+ public static ColorModel BackgroundColor
+ {
+ get
+ {
+ if (_backgroundColor == null)
+ {
+ lock (_syncRoot)
+ {
+ if (_backgroundColor == null)
+ {
+ GetSize();
+ }
+ }
+ }
+
+ return _backgroundColor;
+ }
+ }
+
+ public static int FontSize
+ {
+ get
+ {
+ if (_fontSize == 0)
+ {
+ lock (_syncRoot)
+ {
+ if (_fontSize == 0)
+ {
+ GetSize();
+ }
+ }
+ }
+
+ return _fontSize;
+ }
+ }
+
+ private static void GetSize()
+ {
+ try
+ {
+ IVsFontAndColorStorage storage = (IVsFontAndColorStorage)EditorExtensionsPackage.GetGlobalService(typeof(IVsFontAndColorStorage));
+ var guid = new Guid("A27B4E24-A735-4d1d-B8E7-9716E1E3D8E0");
+ if (storage != null && storage.OpenCategory(ref guid, (uint)(__FCSTORAGEFLAGS.FCSF_READONLY | __FCSTORAGEFLAGS.FCSF_LOADDEFAULTS)) == VS.VSConstants.S_OK)
+ {
+ LOGFONTW[] Fnt = new LOGFONTW[] { new LOGFONTW() };
+ FontInfo[] Info = new FontInfo[] { new FontInfo() };
+ storage.GetFont(Fnt, Info);
+ _fontSize = (int)Info[0].wPointSize;
+ }
+
+ if (storage != null && storage.OpenCategory(ref guid, (uint)(__FCSTORAGEFLAGS.FCSF_NOAUTOCOLORS | __FCSTORAGEFLAGS.FCSF_LOADDEFAULTS)) == VS.VSConstants.S_OK)
+ {
+ var info = new ColorableItemInfo[1];
+ storage.GetItem("Plain Text", info);
+ _backgroundColor = ConvertFromWin32Color((int)info[0].crBackground);
+ }
+
+ }
+ catch { }
+ }
+
+ public static ColorModel ConvertFromWin32Color(int color)
+ {
+ int r = color & 0x000000FF;
+ int g = (color & 0x0000FF00) >> 8;
+ int b = (color & 0x00FF0000) >> 16;
+ return new ColorModel() { Red = r, Green = g, Blue = b };
+ }
+ }
+}
diff --git a/EditorExtensions/Helpers/ProjectHelpers.cs b/EditorExtensions/Helpers/ProjectHelpers.cs
new file mode 100644
index 000000000..6fd3b4042
--- /dev/null
+++ b/EditorExtensions/Helpers/ProjectHelpers.cs
@@ -0,0 +1,229 @@
+using EnvDTE;
+using Microsoft.VisualStudio.ComponentModelHost;
+using Microsoft.VisualStudio.Editor;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal static class ProjectHelpers
+ {
+ public static string GetRootFolder()
+ {
+ try
+ {
+ EnvDTE80.DTE2 dte = EditorExtensionsPackage.DTE;
+ Project activeProject = null;
+
+ if (dte.Solution.Projects.Count == 1 && !string.IsNullOrEmpty(dte.Solution.Projects.Item(1).FullName))
+ {
+ return dte.Solution.Projects.Item(1).Properties.Item("FullPath").Value.ToString();
+ }
+
+ Array activeSolutionProjects = dte.ActiveSolutionProjects as Array;
+
+ if (activeSolutionProjects != null && activeSolutionProjects.Length > 0)
+ {
+ activeProject = activeSolutionProjects.GetValue(0) as Project;
+ }
+
+ return activeProject.Properties.Item("FullPath").Value.ToString();
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(ex);
+ return string.Empty;
+ }
+ }
+
+ internal static bool AddFileToActiveProject(string fileName, string itemType = null)
+ {
+ Project project = GetActiveProject();
+
+ if (project != null)
+ {
+ string projectFilePath = project.Properties.Item("FullPath").Value.ToString();
+ string projectDirPath = Path.GetDirectoryName(projectFilePath);
+
+ if (fileName.StartsWith(projectDirPath, StringComparison.OrdinalIgnoreCase))
+ {
+ ProjectItem item = project.ProjectItems.AddFromFile(fileName);
+
+ if (itemType != null && item != null && !project.FullName.Contains("://"))
+ {
+ try
+ {
+ item.Properties.Item("ItemType").Value = itemType;
+ }
+ catch { }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public static Project GetActiveProject()
+ {
+ Project activeProject = null;
+
+ try
+ {
+ Array activeSolutionProjects = EditorExtensionsPackage.DTE.ActiveSolutionProjects as Array;
+
+ if (activeSolutionProjects != null && activeSolutionProjects.Length > 0)
+ {
+ activeProject = activeSolutionProjects.GetValue(0) as Project;
+ }
+ }
+ catch
+ {
+ Logger.Log("Error getting the active project");
+ }
+
+ return activeProject;
+ }
+
+ public static string ToAbsoluteFilePath(string relativeUrl, string rootFolder = null)
+ {
+ string imageUrl = relativeUrl.Trim(new[] { '\'', '"' });
+ string filePath = string.Empty;
+
+ if (imageUrl.StartsWith("/", StringComparison.Ordinal))
+ {
+ string root = rootFolder ?? ProjectHelpers.GetRootFolder();
+
+ if (root.Contains("://"))
+ {
+ filePath = root + imageUrl;
+ }
+ else if (!string.IsNullOrEmpty(root))
+ {
+ if (!Directory.Exists(root))
+ {
+ filePath = new FileInfo(root).Directory + imageUrl;
+ }
+ else
+ {
+ return root + imageUrl.Replace("/", "\\");
+ }
+ }
+ }
+ else
+ {
+ FileInfo fi = new FileInfo(EditorExtensionsPackage.DTE.ActiveDocument.FullName);
+ DirectoryInfo dir = fi.Directory;
+
+ while (imageUrl.Contains("../"))
+ {
+ imageUrl = imageUrl.Remove(imageUrl.IndexOf("../", StringComparison.Ordinal), 3);
+ dir = dir.Parent;
+ }
+
+ filePath = Path.Combine(dir.FullName, imageUrl.Replace("/", "\\"));
+ }
+
+ return filePath;
+ }
+
+ public static ITextBuffer GetCurentTextBuffer()
+ {
+ return GetCurentTextView().TextBuffer;
+ }
+
+ public static IWpfTextView GetCurentTextView()
+ {
+ var componentModel = GetComponentModel();
+ var editorAdapter = componentModel.GetService();
+ var textManager = (IVsTextManager)ServiceProvider.GlobalProvider.GetService(typeof(SVsTextManager));
+
+ IVsTextView activeView = null;
+ textManager.GetActiveView(1, null, out activeView);
+
+ return editorAdapter.GetWpfTextView(activeView);
+ }
+
+ public static IComponentModel GetComponentModel()
+ {
+ return (IComponentModel)ServiceProvider.GlobalProvider.GetService(typeof(SComponentModel));
+ }
+
+ public static IEnumerable GetSelectedItemPaths()
+ {
+ var items = (Array)EditorExtensionsPackage.DTE.ToolWindows.SolutionExplorer.SelectedItems;
+ foreach (UIHierarchyItem selItem in items)
+ {
+ var item = selItem.Object as ProjectItem;
+ if (item != null)
+ {
+ yield return item.Properties.Item("FullPath").Value.ToString();
+ }
+ }
+ }
+
+ public static bool CheckOutFileFromSourceControl(string fileName)
+ {
+ try
+ {
+ var dte = EditorExtensionsPackage.DTE;
+
+ if (File.Exists(fileName) && dte.Solution.FindProjectItem(fileName) != null)
+ {
+ if (dte.SourceControl.IsItemUnderSCC(fileName) && !dte.SourceControl.IsItemCheckedOut(fileName))
+ {
+ dte.SourceControl.CheckOutItem(fileName);
+ }
+
+ return true;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(ex);
+ }
+
+ return false;
+ }
+
+ public static string GetSolutionFolderPath()
+ {
+ EnvDTE.Solution solution = EditorExtensionsPackage.DTE.Solution;
+
+ if (solution == null || string.IsNullOrEmpty(solution.FullName))
+ return null;
+
+ return Path.GetDirectoryName(solution.FullName);
+ }
+
+ public static string GetProjectFolder(string fileNameOrFolder)
+ {
+ if (string.IsNullOrEmpty(fileNameOrFolder))
+ return GetRootFolder();
+
+ ProjectItem item = EditorExtensionsPackage.DTE.Solution.FindProjectItem(fileNameOrFolder);
+
+ if (item == null || item.ContainingProject == null || string.IsNullOrEmpty(item.ContainingProject.FullName)) // Solution items
+ return null;
+
+ return item.ContainingProject.Properties.Item("FullPath").Value.ToString();
+ }
+
+ public static IEnumerable GetSelectedItems()
+ {
+ var items = (Array)EditorExtensionsPackage.DTE.ToolWindows.SolutionExplorer.SelectedItems;
+ foreach (UIHierarchyItem selItem in items)
+ {
+ var item = selItem.Object as ProjectItem;
+ if (item != null)
+ {
+ yield return item;
+ }
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/Helpers/VendorHelpers.cs b/EditorExtensions/Helpers/VendorHelpers.cs
new file mode 100644
index 000000000..beb27b859
--- /dev/null
+++ b/EditorExtensions/Helpers/VendorHelpers.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Schemas;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.CSS.Editor.SyntaxCheck;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal static class VendorHelpers
+ {
+ private static object _syncRoot = new object();
+ private static Dictionary prefixes = new Dictionary();
+
+ public static string[] GetPrefixes(ICssSchemaInstance schema)
+ {
+ int version = schema.Version.GetHashCode();
+ int browser = schema.Filter.Name.GetHashCode();
+ int hash = version ^ browser;
+
+ if (!prefixes.ContainsKey(hash))
+ {
+ CssSchemaManager.SchemaManager.CurrentSchemaChanged += CurrentSchemaChanged;
+ var properties = schema.Properties;
+ List list = new List();
+
+ foreach (ICssCompletionListEntry property in properties)
+ {
+ string text = property.DisplayText;
+ if (text[0] == '-')
+ {
+ int end = text.IndexOf('-', 1);
+ if (end > -1)
+ {
+ string prefix = text.Substring(0, end + 1);
+ if (!list.Contains(prefix))
+ {
+ list.Add(prefix);
+ }
+ }
+ }
+ }
+
+ prefixes.Add(hash, list.ToArray());
+ }
+
+ return prefixes[hash];
+ }
+
+ private static void CurrentSchemaChanged(object sender, EventArgs e)
+ {
+ CssSchemaManager.SchemaManager.CurrentSchemaChanged -= CurrentSchemaChanged;
+ }
+
+ public static bool HasVendorLaterInRule(Declaration declaration, ICssSchemaInstance schema)
+ {
+ Declaration next = declaration.NextSibling as Declaration;
+
+ while (next != null)
+ {
+ if (next.IsValid && next.IsVendorSpecific())
+ {
+ foreach (string prefix in GetPrefixes(schema))
+ {
+ if (next.PropertyName.Text == prefix + declaration.PropertyName.Text)
+ return true;
+ }
+ }
+
+ next = next.NextSibling as Declaration;
+ }
+
+ return false;
+ }
+
+ public static IEnumerable GetMatchingVendorEntriesInRule(Declaration declaration, RuleBlock rule, ICssSchemaInstance schema)
+ {
+ foreach (Declaration d in rule.Declarations.Where(d => d.IsValid && d.IsVendorSpecific()))
+ foreach (string prefix in GetPrefixes(schema))
+ {
+ if (d.PropertyName.Text == prefix + declaration.PropertyName.Text)
+ {
+ yield return d;
+ break;
+ }
+ }
+ }
+
+ public static ICssCompletionListEntry GetMatchingStandardEntry(Declaration declaration, ICssSchemaInstance rootSchema)
+ {
+ string standardName;
+ if (declaration.TryGetStandardPropertyName(out standardName, rootSchema))
+ {
+ ICssSchemaInstance schema = CssSchemaManager.SchemaManager.GetSchemaForItem(rootSchema, declaration);
+ return schema.GetProperty(standardName);
+ }
+
+ return null;
+ }
+
+ public static ICssCompletionListEntry GetMatchingStandardEntry(Declaration declaration, ICssCheckerContext context)
+ {
+ string standardName;
+ if (declaration.TryGetStandardPropertyName(out standardName, CssEditorChecker.GetSchemaForItem(context, declaration)))
+ {
+ ICssSchemaInstance schema = CssEditorChecker.GetSchemaForItem(context, declaration);
+ return schema.GetProperty(standardName);
+ }
+
+ return null;
+ }
+
+ public static ICssCompletionListEntry GetMatchingStandardEntry(AtDirective directive, ICssCheckerContext context)
+ {
+ string standardName;
+ if (directive.TryGetStandardPropertyName(out standardName, CssEditorChecker.GetSchemaForItem(context, directive)))
+ {
+ ICssSchemaInstance schema = CssEditorChecker.GetSchemaForItem(context, directive);
+ return schema.GetAtDirective("@" + standardName);
+ }
+
+ return null;
+ }
+
+ public static ICssCompletionListEntry GetMatchingStandardEntry(AtDirective directive, ICssSchemaInstance rootSchema)
+ {
+ string standardName;
+ if (directive.TryGetStandardPropertyName(out standardName, rootSchema))
+ {
+ ICssSchemaInstance schema = CssSchemaManager.SchemaManager.GetSchemaForItem(rootSchema, directive);
+ return schema.GetAtDirective("@" + standardName);
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/EditorExtensions/Key.snk b/EditorExtensions/Key.snk
new file mode 100644
index 000000000..d9d7495d7
Binary files /dev/null and b/EditorExtensions/Key.snk differ
diff --git a/EditorExtensions/License.txt b/EditorExtensions/License.txt
new file mode 100644
index 000000000..865551175
--- /dev/null
+++ b/EditorExtensions/License.txt
@@ -0,0 +1,33 @@
+Microsoft Reciprocal License (Ms-RL)
+
+This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
+
+1. Definitions
+
+The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
+
+A "contribution" is the original software, or any additions or changes to the software.
+
+A "contributor" is any person that distributes its contribution under this license.
+
+"Licensed patents" are a contributor's patent claims that read directly on its contribution.
+
+2. Grant of Rights
+
+(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
+
+(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
+
+3. Conditions and Limitations
+
+(A) Reciprocal Grants- For any file you distribute that contains code from the software (in source code or binary format), you must provide recipients the source code to that file along with a copy of this license, which license will govern that file. You may license other files that are entirely your own work and do not contain code from the software under any terms you choose.
+
+(B) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
+
+(C) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
+
+(D) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
+
+(E) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
+
+(F) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
\ No newline at end of file
diff --git a/EditorExtensions/Logger.cs b/EditorExtensions/Logger.cs
new file mode 100644
index 000000000..184fea39e
--- /dev/null
+++ b/EditorExtensions/Logger.cs
@@ -0,0 +1,59 @@
+using Microsoft.VisualStudio.Shell.Interop;
+using System;
+
+namespace MadsKristensen.EditorExtensions
+{
+ public class Logger
+ {
+ private static IVsOutputWindowPane pane;
+ private static object _syncRoot = new object();
+
+ public static void Log(string message)
+ {
+ if (string.IsNullOrEmpty(message))
+ return;
+
+ try
+ {
+ if (EnsurePane())
+ {
+ pane.OutputString(DateTime.Now.ToString() + ": " + message + Environment.NewLine);
+ //pane.Activate();
+ }
+ }
+ catch
+ {
+ // Do nothing
+ }
+ }
+
+ public static void Log(Exception ex)
+ {
+ if (ex != null)
+ {
+ string message = ex.Message + Environment.NewLine + ex.StackTrace;
+
+ if (!string.IsNullOrEmpty(ex.StackTrace))
+ message += Environment.NewLine + ex.StackTrace;
+
+ Log(message);
+ }
+ }
+
+ private static bool EnsurePane()
+ {
+ if (pane == null)
+ {
+ lock (_syncRoot)
+ {
+ if (pane == null)
+ {
+ pane = EditorExtensionsPackage.Instance.GetOutputPane(new Guid("f1536ef8-92ec-443c-9ed7-fdadf150da44"), "Web Essentials");
+ }
+ }
+ }
+
+ return pane != null;
+ }
+ }
+}
diff --git a/EditorExtensions/Margin/CoffeeScriptCompiler.cs b/EditorExtensions/Margin/CoffeeScriptCompiler.cs
new file mode 100644
index 000000000..6d693a25e
--- /dev/null
+++ b/EditorExtensions/Margin/CoffeeScriptCompiler.cs
@@ -0,0 +1,39 @@
+using MadsKristensen.EditorExtensions;
+using System.Runtime.InteropServices;
+using System.Windows.Threading;
+
+[ComVisible(true)]
+public class CoffeeScriptCompiler : ScriptRunnerBase
+{
+ public CoffeeScriptCompiler(Dispatcher dispatcher)
+ : base(dispatcher)
+ { }
+
+ protected override string CreateHtml(string source, string state)
+ {
+ string clean = source
+ .Replace("\\", "\\\\")
+ .Replace("\n", "\\n")
+ .Replace("\r", "\\r")
+ .Replace("'", "\\'");
+
+ string bare = WESettings.GetBoolean(WESettings.Keys.WrapCoffeeScriptClosure) ? "false" : "true";
+
+ string compiler = "MadsKristensen.EditorExtensions.Resources.Scripts.CoffeeScript-1.4.js";
+ if (WESettings.GetBoolean(WESettings.Keys.EnableIcedCoffeeScript))
+ {
+ compiler = "MadsKristensen.EditorExtensions.Resources.Scripts.IcedCoffeeScript-1.3.3.js";
+ }
+
+ string script = ReadResourceFile(compiler) +
+ "try{" +
+ "var result = CoffeeScript.compile('" + clean + "', { bare: " + bare + ", runtime:'inline' });" +
+ "window.external.Execute(result, '" + state.Replace("\\", "\\\\") + "');" +
+ "}" +
+ "catch (err){" +
+ "window.external.Execute('ERROR:' + err, '" + state.Replace("\\", "\\\\") + "');" +
+ "}";
+
+ return "";
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Margin/CoffeeScriptMargin.cs b/EditorExtensions/Margin/CoffeeScriptMargin.cs
new file mode 100644
index 000000000..21c43404c
--- /dev/null
+++ b/EditorExtensions/Margin/CoffeeScriptMargin.cs
@@ -0,0 +1,178 @@
+using Microsoft.VisualStudio.Text;
+using System;
+using System.IO;
+using System.Text;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ class CoffeeScriptMargin : MarginBase
+ {
+ public const string MarginName = "CoffeeScriptMargin";
+ private CoffeeScriptCompiler _compiler;
+ private int _projectFileCount, _projectFileStep;
+
+ public CoffeeScriptMargin(string contentType, string source, bool showMargin, ITextDocument document)
+ : base(source, MarginName, contentType, showMargin, document)
+ {
+ _compiler = new CoffeeScriptCompiler(Dispatcher);
+ _compiler.Completed += _compiler_Completed; //+= (s, e) => { OnCompilationDone(e.Result, e.State); };
+ }
+
+ public CoffeeScriptMargin()
+ {
+ // Used for project compilation
+ }
+
+ public void CompileProject(EnvDTE.Project project)
+ {
+ if (string.IsNullOrEmpty(project.FullName))
+ return;
+
+ Logger.Log("Compiling CoffeeScript...");
+ _projectFileCount = 0;
+
+ try
+ {
+ string fullPath = project.Properties.Item("FullPath").Value.ToString();
+
+ if (project != null && !string.IsNullOrEmpty(fullPath))
+ {
+ string dir = Path.GetDirectoryName(fullPath);
+ var files = Directory.GetFiles(dir, "*.coffee", SearchOption.AllDirectories);
+
+ foreach (string file in files)
+ {
+ string jsFile = GetCompiledFileName(file, ".js", UseCompiledFolder);
+
+ if (EditorExtensionsPackage.DTE.Solution.FindProjectItem(file) != null &&
+ File.Exists(jsFile))
+ {
+ _projectFileCount++;
+
+ CoffeeScriptCompiler compiler = new CoffeeScriptCompiler(Dispatcher.CurrentDispatcher);
+ compiler.Completed += compiler_Completed;
+ compiler.Compile(File.ReadAllText(file), file);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(ex);
+ }
+ }
+
+ void compiler_Completed(object sender, CompilerEventArgs e)
+ {
+ _projectFileStep++;
+ string file = GetCompiledFileName(e.State, ".js", UseCompiledFolder);
+
+ ProjectHelpers.CheckOutFileFromSourceControl(file);
+
+ using (StreamWriter writer = new StreamWriter(file, false, new UTF8Encoding(true)))
+ {
+ writer.Write(e.Result);
+ }
+
+ MinifyFile(e.State, e.Result);
+
+ if (_projectFileStep == _projectFileCount)
+ Logger.Log("CoffeeScript compiled");
+ }
+
+ protected override void StartCompiler(string source)
+ {
+ string fileName = GetCompiledFileName(Document.FilePath, ".js", UseCompiledFolder);//Document.FilePath.Replace(".coffee", ".js");
+
+ if (_isFirstRun && File.Exists(fileName))
+ {
+ OnCompilationDone(File.ReadAllText(fileName), Document.FilePath);
+ return;
+ }
+
+ Logger.Log("CoffeeScript: Compiling " + Path.GetFileName(Document.FilePath));
+ _compiler.Compile(source, Document.FilePath);
+ }
+
+ private void _compiler_Completed(object sender, CompilerEventArgs e)
+ {
+ if (e.Result.StartsWith("ERROR:", StringComparison.OrdinalIgnoreCase))
+ {
+ CompilerError error = ParseError(e.Result);
+ CreateTask(error);
+ }
+
+ OnCompilationDone(e.Result, e.State);
+ }
+
+ private CompilerError ParseError(string error)
+ {
+ string message = error.Replace("ERROR:", string.Empty).Replace("Error:", string.Empty);
+ int index = message.IndexOf(':');
+ int line = 0;
+
+ if (index > -1)
+ {
+ int start = message.LastIndexOf(' ', index);
+ if (start > -1)
+ {
+ int length = index - start - 1;
+ string part = message.Substring(start + 1, length);
+ int.TryParse(part, out line);
+ }
+ }
+
+ CompilerError result = new CompilerError()
+ {
+ Message = "CoffeeScript: " + message,
+ FileName = Document.FilePath,
+ Line = line,
+ };
+
+ return result;
+ }
+
+ public override void MinifyFile(string fileName, string source)
+ {
+ if (WESettings.GetBoolean(WESettings.Keys.CoffeeScriptMinify))
+ {
+ string content = MinifyFileMenu.MinifyString(".js", source);
+ string minFile = GetCompiledFileName(fileName, ".min.js", UseCompiledFolder);//fileName.Replace(".coffee", ".min.js");
+ bool fileExist = File.Exists(minFile);
+
+ ProjectHelpers.CheckOutFileFromSourceControl(minFile);
+ using (StreamWriter writer = new StreamWriter(minFile, false, new UTF8Encoding(true)))
+ {
+ writer.Write(content);
+ }
+
+ if (!fileExist)
+ AddFileToProject(fileName, minFile);
+ }
+ }
+
+ public override bool UseCompiledFolder
+ {
+ get { return WESettings.GetBoolean(WESettings.Keys.CoffeeScriptCompileToFolder); }
+ }
+
+ public override bool IsSaveFileEnabled
+ {
+ get { return WESettings.GetBoolean(WESettings.Keys.GenerateJsFileFromCoffeeScript); }
+ }
+
+ protected override bool CanWriteToDisk(string source)
+ {
+ return !string.IsNullOrWhiteSpace(source);
+ }
+ }
+}
+
+//static class Iced
+//{
+// [Export]
+// [FileExtension(".iced")]
+// [ContentType("CoffeeScript")]
+// internal static FileExtensionToContentTypeDefinition IcedFileExtensionDefinition;
+//}
\ No newline at end of file
diff --git a/EditorExtensions/Margin/CompilerError.cs b/EditorExtensions/Margin/CompilerError.cs
new file mode 100644
index 000000000..c4b9548c0
--- /dev/null
+++ b/EditorExtensions/Margin/CompilerError.cs
@@ -0,0 +1,11 @@
+
+namespace MadsKristensen.EditorExtensions
+{
+ public class CompilerError
+ {
+ public int Line { get; set; }
+ public int Column { get; set; }
+ public string FileName { get; set; }
+ public string Message { get; set; }
+ }
+}
diff --git a/EditorExtensions/Margin/CompilerResult.cs b/EditorExtensions/Margin/CompilerResult.cs
new file mode 100644
index 000000000..38f5f3120
--- /dev/null
+++ b/EditorExtensions/Margin/CompilerResult.cs
@@ -0,0 +1,16 @@
+
+namespace MadsKristensen.EditorExtensions
+{
+ public class CompilerResult
+ {
+ public CompilerResult(string fileName)
+ {
+ FileName = fileName;
+ }
+
+ public bool IsSuccess { get; set; }
+ public string FileName { get; set; }
+ public string Result { get; set; }
+ public CompilerError Error { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Margin/EditorMarginFactory.cs b/EditorExtensions/Margin/EditorMarginFactory.cs
new file mode 100644
index 000000000..7786115c8
--- /dev/null
+++ b/EditorExtensions/Margin/EditorMarginFactory.cs
@@ -0,0 +1,55 @@
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using System.ComponentModel.Composition;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IWpfTextViewMarginProvider))]
+ [Name(LessMargin.MarginName)]
+ [Order(After = PredefinedMarginNames.RightControl)]
+ [MarginContainer(PredefinedMarginNames.Right)]
+ [ContentType("LESS")]
+ [ContentType("CoffeeScript")]
+ //[ContentType("TypeScript")]
+ [ContentType("Markdown")]
+ [TextViewRole(PredefinedTextViewRoles.Debuggable)]
+ internal sealed class MarginFactory : IWpfTextViewMarginProvider
+ {
+ public IWpfTextViewMargin CreateMargin(IWpfTextViewHost textViewHost, IWpfTextViewMargin containerMargin)
+ {
+ string source = textViewHost.TextView.TextBuffer.CurrentSnapshot.GetText();
+ ITextDocument document;
+
+ if (textViewHost.TextView.TextDataModel.DocumentBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document))
+ {
+ switch (textViewHost.TextView.TextBuffer.ContentType.DisplayName)
+ {
+ case "LESS":
+ bool showLess = WESettings.GetBoolean(WESettings.Keys.ShowLessPreviewWindow);
+ return new LessMargin("CSS", source, showLess, document);
+
+ //case "scss":
+ // return new ScssMargin("CSS", source, true, document);
+
+ case "CoffeeScript":
+ bool showCoffee = WESettings.GetBoolean(WESettings.Keys.ShowCoffeeScriptPreviewWindow);
+ return new CoffeeScriptMargin("JavaScript", source, showCoffee, document);
+
+ //case "TypeScript":
+ // if (!document.FilePath.EndsWith(".d.ts"))
+ // {
+ // bool showType = WESettings.GetBoolean(WESettings.Keys.ShowTypeScriptPreviewWindow);
+ // return new TypeScriptMargin("TypeScript", source, showType, document);
+ // }
+ // break;
+
+ case "markdown":
+ return new MarkdownMargin("text", source, true, document);
+ }
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Margin/LessCompiler.cs b/EditorExtensions/Margin/LessCompiler.cs
new file mode 100644
index 000000000..2d91e2e67
--- /dev/null
+++ b/EditorExtensions/Margin/LessCompiler.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+
+namespace MadsKristensen.EditorExtensions
+{
+ public class LessCompiler
+ {
+ public LessCompiler(Action callback)
+ {
+ Callback = callback;
+ }
+
+ public Action Callback { get; set; }
+
+ public void Compile(string fileName)
+ {
+ string output = Path.GetTempFileName();
+
+ ProcessStartInfo start = new ProcessStartInfo(@"cscript");
+ start.WindowStyle = ProcessWindowStyle.Hidden;
+ start.CreateNoWindow = true;
+ start.Arguments = "//nologo //s \"" + GetExecutablePath() + "\" \"" + fileName + "\" \"" + output + "\"";
+ start.EnvironmentVariables["output"] = output;
+ start.EnvironmentVariables["fileName"] = fileName;
+ start.UseShellExecute = false;
+ start.RedirectStandardError = true;
+
+ Process p = new Process();
+ p.StartInfo = start;
+ p.EnableRaisingEvents = true;
+ p.Exited += ProcessExited;
+ p.Start();
+ }
+
+ private void ProcessExited(object sender, EventArgs e)
+ {
+ using (Process process = (Process)sender)
+ {
+ string fileName = process.StartInfo.EnvironmentVariables["fileName"];
+ CompilerResult result = new CompilerResult(fileName);
+
+ try
+ {
+ ProcessResult(process, result);
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(ex);
+ Callback(result);
+ }
+
+ process.Exited -= ProcessExited;
+
+ Logger.Log(Path.GetFileName(fileName) + " compiled");
+ }
+ }
+
+ private void ProcessResult(Process process, CompilerResult result)
+ {
+ string output = process.StartInfo.EnvironmentVariables["output"];
+
+ if (File.Exists(output))
+ {
+ if (process.ExitCode == 0)
+ {
+ result.IsSuccess = true;
+ result.Result = File.ReadAllText(output);
+ }
+ else
+ {
+ using (StreamReader reader = process.StandardError)
+ {
+ result.Error = ParseError(reader.ReadToEnd());
+ }
+ }
+
+ File.Delete(output);
+ }
+
+ Callback(result);
+ }
+
+ private CompilerError ParseError(string error)
+ {
+ CompilerError result = new CompilerError();
+ string[] lines = error.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
+
+ for (int i = 0; i < lines.Length; i++)
+ {
+ string line = lines[i];
+
+ if (error.Contains("message:"))
+ {
+ string[] args = line.Split(new[] { ':' }, 2);
+
+ if (args[0].Trim() == "message")
+ result.Message = args[1].Trim();
+
+ if (args[0].Trim() == "filename")
+ result.FileName = args[1].Trim();
+
+ int lineNo = 0;
+ if (args[0].Trim() == "line" && int.TryParse(args[1], out lineNo))
+ result.Line = lineNo;
+
+ int columnNo = 0;
+ if (args[0].Trim() == "column" && int.TryParse(args[1], out columnNo))
+ result.Column = columnNo;
+ }
+ else
+ {
+ if (i == 1 || i == 2)
+ result.Message += " " + line;
+
+ if (i == 3)
+ {
+ string[] lineCol = line.Split(',');
+
+ int lineNo = 0;
+ if (int.TryParse(lineCol[0].Replace("on line", string.Empty).Trim(), out lineNo))
+ result.Line = lineNo;
+
+ int columnNo = 0;
+ if (int.TryParse(lineCol[0].Replace("column", string.Empty).Trim(':').Trim(), out columnNo))
+ result.Column = columnNo;
+
+ result.Message = result.Message.Trim();
+ }
+
+ }
+ }
+
+ return result;
+ }
+
+ private static string GetExecutablePath()
+ {
+ string assembly = Assembly.GetExecutingAssembly().Location;
+ string folder = Path.GetDirectoryName(assembly).ToLowerInvariant();
+ string file = Path.Combine(folder, "resources\\scripts\\lessc.wsf");
+
+ return file;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Margin/LessMargin.cs b/EditorExtensions/Margin/LessMargin.cs
new file mode 100644
index 000000000..fbc52b24a
--- /dev/null
+++ b/EditorExtensions/Margin/LessMargin.cs
@@ -0,0 +1,89 @@
+using EnvDTE;
+using Microsoft.VisualStudio.Text;
+using System.IO;
+using System.Text;
+
+namespace MadsKristensen.EditorExtensions
+{
+ public class LessMargin : MarginBase
+ {
+ public const string MarginName = "LessMargin";
+
+ public LessMargin(string contentType, string source, bool showMargin, ITextDocument document)
+ : base(source, MarginName, contentType, showMargin, document)
+ { }
+
+ protected override void StartCompiler(string source)
+ {
+ string fileName = GetCompiledFileName(Document.FilePath, ".css", UseCompiledFolder);// Document.FilePath.Replace(".less", ".css");
+
+ if (_isFirstRun && File.Exists(fileName))
+ {
+ OnCompilationDone(File.ReadAllText(fileName), Document.FilePath);
+ }
+ else
+ {
+ Logger.Log("LESS: Compiling " + Path.GetFileName(Document.FilePath));
+
+ System.Threading.Tasks.Task.Run(() =>
+ {
+ LessCompiler compiler = new LessCompiler(Completed);
+ compiler.Compile(Document.FilePath);
+ });
+ }
+ }
+
+ private void Completed(CompilerResult result)
+ {
+ if (result.IsSuccess)
+ {
+ OnCompilationDone(result.Result, result.FileName);
+ }
+ else
+ {
+ result.Error.Message = "LESS: " + result.Error.Message;
+
+ CreateTask(result.Error);
+
+ base.OnCompilationDone("ERROR:", Document.FilePath);
+ }
+ }
+
+ public override void MinifyFile(string fileName, string source)
+ {
+ if (WESettings.GetBoolean(WESettings.Keys.LessMinify) && !Path.GetFileName(fileName).StartsWith("_"))
+ {
+ string content = MinifyFileMenu.MinifyString(".css", source);
+ string minFile = GetCompiledFileName(fileName, ".min.css", UseCompiledFolder);// fileName.Replace(".less", ".min.css");
+ bool fileExist = File.Exists(minFile);
+
+ ProjectHelpers.CheckOutFileFromSourceControl(minFile);
+ using (StreamWriter writer = new StreamWriter(minFile, false, new UTF8Encoding(true)))
+ {
+ writer.Write(content);
+ }
+
+ if (!fileExist)
+ AddFileToProject(Document.FilePath, minFile);
+ }
+ }
+
+ public override bool UseCompiledFolder
+ {
+ get { return WESettings.GetBoolean(WESettings.Keys.LessCompileToFolder); }
+ }
+
+ public override bool IsSaveFileEnabled
+ {
+ get { return WESettings.GetBoolean(WESettings.Keys.GenerateCssFileFromLess) && !Path.GetFileName(Document.FilePath).StartsWith("_"); }
+ }
+
+ protected override bool CanWriteToDisk(string source)
+ {
+ //var parser = new Microsoft.CSS.Core.CssParser();
+ //StyleSheet stylesheet = parser.Parse(source, false);
+
+ return true;// !string.IsNullOrWhiteSpace(stylesheet.Text);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Margin/LessProjectCompiler.cs b/EditorExtensions/Margin/LessProjectCompiler.cs
new file mode 100644
index 000000000..cdcd99757
--- /dev/null
+++ b/EditorExtensions/Margin/LessProjectCompiler.cs
@@ -0,0 +1,116 @@
+using EnvDTE;
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class LessProjectCompiler
+ {
+ public static void CompileProject(Project project)
+ {
+ if (project != null && !string.IsNullOrEmpty(project.FullName))
+ {
+ Task.Run(() => Compile(project));
+ }
+ }
+
+ private static void Compile(Project project)
+ {
+ LessCompiler compiler = new LessCompiler(Completed);
+
+ string dir = Path.GetDirectoryName(project.Properties.Item("FullPath").Value.ToString());
+ var files = Directory.GetFiles(dir, "*.less", SearchOption.AllDirectories).Where(f => CanCompile(f));
+
+ foreach (string file in files)
+ {
+ compiler.Compile(file);
+ }
+ }
+
+ private static bool CanCompile(string fileName)
+ {
+ if (EditorExtensionsPackage.DTE.Solution.FindProjectItem(fileName) == null)
+ return false;
+
+ if (Path.GetFileName(fileName).StartsWith("_"))
+ return false;
+
+ string minFile = MarginBase.GetCompiledFileName(fileName, ".min.css", WESettings.GetBoolean(WESettings.Keys.LessCompileToFolder));
+ if (File.Exists(minFile) && WESettings.GetBoolean(WESettings.Keys.LessMinify))
+ return true;
+
+ string cssFile = MarginBase.GetCompiledFileName(fileName, ".css", WESettings.GetBoolean(WESettings.Keys.LessCompileToFolder));
+ if (!File.Exists(cssFile))
+ return false;
+
+
+ return true;
+ }
+
+ private static void Completed(CompilerResult result)
+ {
+ if (result.IsSuccess)
+ {
+ string cssFileName = MarginBase.GetCompiledFileName(result.FileName, ".css", WESettings.GetBoolean(WESettings.Keys.LessCompileToFolder));// result.FileName.Replace(".less", ".css");
+
+ if (File.Exists(cssFileName))
+ {
+ string old = File.ReadAllText(cssFileName);
+
+ if (old != result.Result)
+ {
+ ProjectHelpers.CheckOutFileFromSourceControl(cssFileName);
+ try
+ {
+ using (StreamWriter writer = new StreamWriter(cssFileName, false, new UTF8Encoding(true)))
+ {
+ writer.Write(result.Result);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(ex);
+ }
+ }
+ }
+
+ MinifyFile(result.FileName, result.Result);
+ }
+ else if (result.Error != null && !string.IsNullOrEmpty(result.Error.Message))
+ {
+ Logger.Log(result.Error.Message);
+ }
+ else
+ {
+ Logger.Log("Error compiling LESS file: " + result.FileName);
+ }
+ }
+
+ public static void MinifyFile(string lessFileName, string source)
+ {
+ if (WESettings.GetBoolean(WESettings.Keys.LessMinify))
+ {
+ string content = MinifyFileMenu.MinifyString(".css", source);
+ string minFile = MarginBase.GetCompiledFileName(lessFileName, ".min.css", WESettings.GetBoolean(WESettings.Keys.LessCompileToFolder)); //lessFileName.Replace(".less", ".min.css");
+ string old = File.ReadAllText(minFile);
+
+ if (old != content)
+ {
+ bool fileExist = File.Exists(minFile);
+
+ ProjectHelpers.CheckOutFileFromSourceControl(minFile);
+ using (StreamWriter writer = new StreamWriter(minFile, false, new UTF8Encoding(true)))
+ {
+ writer.Write(content);
+ }
+
+ if (!fileExist)
+ MarginBase.AddFileToProject(lessFileName, minFile);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Margin/MarginBase.cs b/EditorExtensions/Margin/MarginBase.cs
new file mode 100644
index 000000000..7371d5ddc
--- /dev/null
+++ b/EditorExtensions/Margin/MarginBase.cs
@@ -0,0 +1,448 @@
+using EnvDTE;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.IO;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ public abstract class MarginBase : DockPanel, IWpfTextViewMargin
+ {
+ private bool _isDisposed = false;
+ private IWpfTextViewHost _viewHost;
+ private string _marginName;
+ protected string _settingsKey;
+ private bool _showMargin;
+ protected bool _isFirstRun = true;
+ private Dispatcher _dispatcher;
+ private ErrorListProvider _provider;
+
+ public MarginBase()
+ {
+ _dispatcher = Dispatcher.CurrentDispatcher;
+ }
+
+ public MarginBase(string source, string name, string contentType, bool showMargin, ITextDocument document)
+ {
+ Document = document;
+ _marginName = name;
+ _settingsKey = _marginName + "_width";
+ _showMargin = showMargin;
+ _dispatcher = Dispatcher.CurrentDispatcher;
+ _provider = new ErrorListProvider(EditorExtensionsPackage.Instance);
+
+ Document.FileActionOccurred += Document_FileActionOccurred;
+
+ if (showMargin)
+ {
+ _dispatcher.BeginInvoke(
+ new Action(() => Initialize(contentType, source)), DispatcherPriority.ApplicationIdle, null);
+ }
+ }
+
+ protected virtual void Document_FileActionOccurred(object sender, TextDocumentFileActionEventArgs e)
+ {
+ if (e.FileActionType == FileActionTypes.ContentSavedToDisk)
+ {
+ _dispatcher.BeginInvoke(new Action(() =>
+ {
+ _provider.Tasks.Clear();
+ StartCompiler(File.ReadAllText(e.FilePath));
+ }), DispatcherPriority.ApplicationIdle, null);
+ }
+ }
+
+ public abstract bool IsSaveFileEnabled { get; }
+ public abstract bool UseCompiledFolder { get; }
+ protected ITextDocument Document { get; set; }
+
+ private void Initialize(string contentType, string source)
+ {
+ _viewHost = CreateTextViewHost(contentType);
+ CreateControls(_viewHost, source);
+ StartCompiler(source);
+ }
+
+ private IWpfTextViewHost CreateTextViewHost(string contentType)
+ {
+ var componentModel = ProjectHelpers.GetComponentModel();
+ var service = componentModel.GetService();
+ var type = service.GetContentType(contentType);
+
+ var textBufferFactory = componentModel.GetService();
+ var textViewFactory = componentModel.GetService();
+
+ ITextBuffer textBuffer = textBufferFactory.CreateTextBuffer(string.Empty, type);
+ ITextViewRoleSet roles = textViewFactory.CreateTextViewRoleSet(PredefinedTextViewRoles.Interactive, PredefinedTextViewRoles.Document);
+ IWpfTextView textView = textViewFactory.CreateTextView(textBuffer, roles);
+ IWpfTextViewHost host = textViewFactory.CreateTextViewHost(textView, false);
+
+ return host;
+ }
+
+ protected virtual void CreateControls(IWpfTextViewHost host, string source)
+ {
+ int width;
+
+ using (var key = EditorExtensionsPackage.Instance.UserRegistryRoot)
+ {
+ var raw = key.GetValue("WE_" + _settingsKey);
+ width = raw != null ? (int)raw : -1;
+ }
+
+ width = width == -1 ? 400 : width;
+
+ host.TextView.VisualElement.MinWidth = width;
+ host.TextView.VisualElement.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
+ host.TextView.Options.SetOptionValue(DefaultTextViewHostOptions.GlyphMarginId, false);
+ host.TextView.Options.SetOptionValue(DefaultTextViewHostOptions.LineNumberMarginId, true);
+ host.TextView.VisualElement.KeyDown += VisualElement_KeyUp;
+
+ //host.GetTextViewMargin(PredefinedMarginNames.BottomControl).VisualElement.Height = 0;
+
+ Grid grid = new Grid();
+ grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Star) });
+ grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(5, GridUnitType.Pixel) });
+ grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Auto) });
+ grid.RowDefinitions.Add(new RowDefinition());
+
+ grid.Children.Add(host.HostControl);
+ this.Children.Add(grid);
+
+ Grid.SetColumn(host.HostControl, 2);
+ Grid.SetRow(host.HostControl, 0);
+
+ GridSplitter splitter = new GridSplitter();
+ splitter.Width = 5;
+ splitter.ResizeDirection = GridResizeDirection.Columns;
+ splitter.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
+ splitter.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
+ splitter.DragCompleted += splitter_DragCompleted;
+
+ grid.Children.Add(splitter);
+ Grid.SetColumn(splitter, 1);
+ Grid.SetRow(splitter, 0);
+ }
+
+ void splitter_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
+ {
+ //Settings.SetValue(_settingsKey, (int)_viewHost.HostControl.ActualWidth);
+ //Settings.Save();
+ using (var key = EditorExtensionsPackage.Instance.UserRegistryRoot)
+ {
+ key.SetValue("WE_" + _settingsKey, (int)_viewHost.HostControl.ActualWidth);
+ }
+ }
+
+ protected void VisualElement_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
+ {
+ if (e.Key == Key.C && Keyboard.Modifiers == ModifierKeys.Control)
+ Clipboard.SetText(_viewHost.TextView.TextBuffer.CurrentSnapshot.GetText(_viewHost.TextView.Selection.Start.Position.Position, _viewHost.TextView.Selection.End.Position.Position - _viewHost.TextView.Selection.Start.Position.Position));
+ else if (e.Key == Key.PageDown)
+ _viewHost.TextView.ViewScroller.ScrollViewportVerticallyByPage(ScrollDirection.Down);
+ else if (e.Key == Key.PageUp)
+ _viewHost.TextView.ViewScroller.ScrollViewportVerticallyByPage(ScrollDirection.Up);
+ else if (e.Key == Key.Down)
+ _viewHost.TextView.ViewScroller.ScrollViewportVerticallyByLine(ScrollDirection.Down);
+ else if (e.Key == Key.Up)
+ _viewHost.TextView.ViewScroller.ScrollViewportVerticallyByLine(ScrollDirection.Up);
+ else if (e.Key == Key.Home)
+ _viewHost.TextView.ViewScroller.EnsureSpanVisible(new SnapshotSpan(_viewHost.TextView.TextBuffer.CurrentSnapshot, 0, 0));
+ else if (e.Key == Key.End)
+ _viewHost.TextView.ViewScroller.EnsureSpanVisible(new SnapshotSpan(_viewHost.TextView.TextBuffer.CurrentSnapshot, _viewHost.TextView.TextBuffer.CurrentSnapshot.Length, 0));
+ }
+
+ public void SetText(string text)
+ {
+ if (!_showMargin)
+ return;
+
+ if (!string.IsNullOrEmpty(text))
+ {
+ int position = _viewHost.TextView.TextViewLines.FirstVisibleLine.Extent.Start.Position;
+ using (var edit = _viewHost.TextView.TextBuffer.CreateEdit())
+ {
+ edit.Replace(new Span(0, _viewHost.TextView.TextBuffer.CurrentSnapshot.Length), text);
+ edit.Apply();
+ }
+
+ try
+ {
+ _viewHost.HostControl.Opacity = 1;
+ _viewHost.TextView.ViewScroller.ScrollViewportVerticallyByLines(ScrollDirection.Down, _viewHost.TextView.TextSnapshot.GetLineNumberFromPosition(position));
+ _viewHost.TextView.ViewScroller.ScrollViewportHorizontallyByPixels(-9999);
+ }
+ catch
+ {
+ // Threading issues when called from TypeScript
+ }
+ }
+ else
+ {
+ _viewHost.HostControl.Opacity = 0.3;
+ }
+ }
+
+ protected abstract void StartCompiler(string source);
+
+ protected void OnCompilationDone(string result, string state)
+ {
+ bool isSuccess = !result.StartsWith("ERROR:");
+
+ _dispatcher.BeginInvoke(new Action(() =>
+ {
+ if (isSuccess)
+ {
+ SetText(result);
+
+ if (!_isFirstRun)
+ {
+ if (IsSaveFileEnabled)
+ WriteCompiledFile(result, state);
+
+ MinifyFile(state, result);
+ }
+ }
+ else
+ {
+ result = result.Replace("ERROR:", string.Empty);
+ SetText("/*\r\n\r\nCompile Error. \r\nSee error list for details\r\n" + result + "\r\n\r\n*/");
+ }
+
+ _isFirstRun = false;
+ }), DispatcherPriority.Normal, null);
+ }
+
+ public abstract void MinifyFile(string fileName, string source);
+
+ protected void WriteCompiledFile(string content, string currentFileName)
+ {
+ string extension = Path.GetExtension(currentFileName);
+ string fileName = null;
+
+ switch (extension.ToLowerInvariant())
+ {
+ case ".less":
+ case ".scss":
+ fileName = GetCompiledFileName(currentFileName, ".css", UseCompiledFolder);
+ break;
+
+ case ".coffee":
+ case ".ts":
+ fileName = GetCompiledFileName(currentFileName, ".js", UseCompiledFolder);
+ break;
+
+ default: // For the Diff view
+ return;
+ }
+
+ bool fileExist = File.Exists(fileName);
+ bool fileWritten = false;
+
+ ProjectHelpers.CheckOutFileFromSourceControl(fileName);
+ fileWritten = WriteFile(content, fileName, fileExist, fileWritten);
+
+ if (!fileExist && fileWritten)
+ {
+ AddFileToProject(currentFileName, fileName);
+ }
+ }
+
+ public static string GetCompiledFileName(string sourceFileName, string compiledExtension, bool useFolder)
+ {
+ string sourceExtension = Path.GetExtension(sourceFileName);
+ string compiledFileName = Path.GetFileName(sourceFileName).Replace(sourceExtension, compiledExtension);
+ string sourceDir = Path.GetDirectoryName(sourceFileName);
+
+ if (useFolder)
+ {
+ string compiledDir = Path.Combine(sourceDir, compiledExtension.Replace(".min.", string.Empty).Replace(".", string.Empty));
+
+ if (!Directory.Exists(compiledDir))
+ {
+ Directory.CreateDirectory(compiledDir);
+ }
+
+ return Path.Combine(compiledDir, compiledFileName);
+ }
+
+ return Path.Combine(sourceDir, compiledFileName);
+ }
+
+ public static void AddFileToProject(string parentFileName, string fileName)
+ {
+ if (!File.Exists(fileName))
+ return;
+
+ var item = EditorExtensionsPackage.DTE.Solution.FindProjectItem(parentFileName);
+
+ if (item != null && item.ContainingProject != null && !string.IsNullOrEmpty(item.ContainingProject.FullName))
+ {
+ if (item.ProjectItems != null && Path.GetDirectoryName(parentFileName) == Path.GetDirectoryName(fileName))
+ {
+ // WAP
+ item.ProjectItems.AddFromFile(fileName);
+ }
+ else
+ { // Website
+ item.ContainingProject.ProjectItems.AddFromFile(fileName);
+ }
+ }
+ }
+
+ private bool WriteFile(string content, string fileName, bool fileExist, bool fileWritten)
+ {
+ try
+ {
+ if (fileExist || (!fileExist && CanWriteToDisk(content)))
+ {
+ using (StreamWriter writer = new StreamWriter(fileName, false, new UTF8Encoding(true)))
+ {
+ writer.Write(content);
+ fileWritten = true;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ var error = new CompilerError
+ {
+ FileName = Document.FilePath,
+ Column = 0,
+ Line = 0,
+ Message = "Could not write to " + Path.GetFileName(fileName)
+ };
+
+ CreateTask(error);
+
+ Logger.Log(ex);
+ }
+
+ return fileWritten;
+ }
+
+ protected void CreateTask(CompilerError error)
+ {
+ ErrorTask task = new ErrorTask()
+ {
+ Line = error.Line,
+ Column = error.Column,
+ ErrorCategory = TaskErrorCategory.Error,
+ Category = TaskCategory.Html,
+ Document = error.FileName,
+ Priority = TaskPriority.Low,
+ Text = error.Message,
+ };
+
+ task.AddHierarchyItem();
+
+ task.Navigate += task_Navigate;
+ _provider.Tasks.Add(task);
+ }
+
+ private void task_Navigate(object sender, EventArgs e)
+ {
+ Task task = sender as Task;
+
+ _provider.Navigate(task, new Guid(EnvDTE.Constants.vsViewKindPrimary));
+
+ if (task.Column > 0)
+ {
+ var doc = (TextDocument)EditorExtensionsPackage.DTE.ActiveDocument.Object("textdocument");
+ doc.Selection.MoveToLineAndOffset(task.Line, task.Column, false);
+ }
+ }
+
+ protected abstract bool CanWriteToDisk(string source);
+
+ private void ThrowIfDisposed()
+ {
+ if (_isDisposed)
+ throw new ObjectDisposedException("MarginBase");
+ }
+
+ #region IWpfTextViewMargin Members
+
+ ///
+ /// The that implements the visual representation
+ /// of the margin.
+ ///
+ public System.Windows.FrameworkElement VisualElement
+ {
+ // Since this margin implements Canvas, this is the object which renders
+ // the margin.
+ get
+ {
+ ThrowIfDisposed();
+ return this;
+ }
+ }
+
+ #endregion
+
+ #region ITextViewMargin Members
+
+ public double MarginSize
+ {
+ // Since this is a horizontal margin, its width will be bound to the width of the text view.
+ // Therefore, its size is its height.
+ get
+ {
+ ThrowIfDisposed();
+ return this.ActualHeight;
+ }
+ }
+
+ public bool Enabled
+ {
+ // The margin should always be enabled
+ get
+ {
+ ThrowIfDisposed();
+ return true;
+ }
+ }
+
+ ///
+ /// Returns an instance of the margin if this is the margin that has been requested.
+ ///
+ /// The name of the margin requested
+ /// An instance of EditorMargin1 or null
+ public ITextViewMargin GetTextViewMargin(string marginName)
+ {
+ return (marginName == this._marginName) ? (IWpfTextViewMargin)this : null;
+ }
+
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+ Dispose(_isDisposed);
+ }
+
+ protected virtual void Dispose(bool isDisposed)
+ {
+ if (!_isDisposed)
+ {
+ _isDisposed = true;
+
+ if (_viewHost != null)
+ {
+ _viewHost.Close();
+ }
+
+ Document.FileActionOccurred -= Document_FileActionOccurred;
+ _provider.Tasks.Clear();
+ _provider.Dispose();
+ }
+ }
+ #endregion
+
+ }
+}
diff --git a/EditorExtensions/Margin/MarkdownMargin.cs b/EditorExtensions/Margin/MarkdownMargin.cs
new file mode 100644
index 000000000..c5b304711
--- /dev/null
+++ b/EditorExtensions/Margin/MarkdownMargin.cs
@@ -0,0 +1,150 @@
+using EnvDTE;
+using EnvDTE80;
+using MarkdownSharp;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class MarkdownMargin : MarginBase
+ {
+ public const string MarginName = "MarkdownMargin";
+ private Markdown _compiler;
+ private WebBrowser _browser;
+ private const string _stylesheet = "WE-Markdown.css";
+
+ public MarkdownMargin(string contentType, string source, bool showMargin, ITextDocument document)
+ : base(source, MarginName, contentType, showMargin, document)
+ {
+ }
+
+ private void InitializeCompiler()
+ {
+ if (_compiler == null)
+ {
+ MarkdownOptions options = new MarkdownOptions();
+ options.AutoHyperlink = true;
+
+ _compiler = new Markdown(options);
+ }
+ }
+
+ protected override void StartCompiler(string source)
+ {
+ InitializeCompiler();
+
+ string result = _compiler.Transform(source);
+
+ string html = "" +
+ GetStylesheet() +
+ "" +
+ "" + result + "";
+
+ _browser.NavigateToString(html);
+ }
+
+ public static string GetStylesheet()
+ {
+ string folder = ProjectHelpers.GetSolutionFolderPath();
+
+ if (!string.IsNullOrEmpty(folder))
+ {
+ string file = Path.Combine(folder, _stylesheet);
+
+ if (File.Exists(file))
+ {
+ string linkFormat = "";
+ return string.Format(linkFormat, file);
+ }
+ }
+
+ return string.Empty;
+ }
+
+ public static void CreateStylesheet()
+ {
+ string file = Path.Combine(ProjectHelpers.GetSolutionFolderPath(), _stylesheet);
+
+ using (StreamWriter writer = new StreamWriter(file, false, new UTF8Encoding(true)))
+ {
+ writer.Write("body { background: yellow; }");
+ }
+
+ Solution2 solution = EditorExtensionsPackage.DTE.Solution as Solution2;
+ Project project = solution.Projects
+ .OfType()
+ .FirstOrDefault(p => p.Name.Equals(Settings._solutionFolder, StringComparison.OrdinalIgnoreCase));
+
+ if (project == null)
+ {
+ project = solution.AddSolutionFolder(Settings._solutionFolder);
+ }
+
+ project.ProjectItems.AddFromFile(file);
+ }
+
+ protected override void CreateControls(IWpfTextViewHost host, string source)
+ {
+ int width = WESettings.GetInt(_settingsKey);
+ width = width == -1 ? 400 : width;
+
+ _browser = new WebBrowser();
+ _browser.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
+
+ Grid grid = new Grid();
+ grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Star) });
+ grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(5, GridUnitType.Pixel) });
+ grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(width) });
+ grid.RowDefinitions.Add(new RowDefinition());
+
+ grid.Children.Add(_browser);
+ this.Children.Add(grid);
+
+ Grid.SetColumn(_browser, 2);
+ Grid.SetRow(_browser, 0);
+
+ GridSplitter splitter = new GridSplitter();
+ splitter.Width = 5;
+ splitter.ResizeDirection = GridResizeDirection.Columns;
+ splitter.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
+ splitter.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
+ splitter.DragCompleted += splitter_DragCompleted;
+
+ grid.Children.Add(splitter);
+ Grid.SetColumn(splitter, 1);
+ Grid.SetRow(splitter, 0);
+ }
+
+ void splitter_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
+ {
+ Settings.SetValue(_settingsKey, (int)this.ActualWidth);
+ Settings.Save();
+ }
+
+ public override void MinifyFile(string fileName, string source)
+ {
+ // Nothing to minify
+ }
+
+ public override bool UseCompiledFolder
+ {
+ get { return false; }
+ }
+
+ public override bool IsSaveFileEnabled
+ {
+ get { return false; }
+ }
+
+ protected override bool CanWriteToDisk(string source)
+ {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Margin/ScriptRunnerBase.cs b/EditorExtensions/Margin/ScriptRunnerBase.cs
new file mode 100644
index 000000000..cfa9cea2d
--- /dev/null
+++ b/EditorExtensions/Margin/ScriptRunnerBase.cs
@@ -0,0 +1,77 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+using System.Windows.Threading;
+
+[ComVisible(true)]
+public abstract class ScriptRunnerBase : IDisposable
+{
+ private WebBrowser _browser = new WebBrowser();
+ private bool _disposed;
+ private Dispatcher _dispatcher;
+
+ public ScriptRunnerBase(Dispatcher dispatcher)
+ {
+ _dispatcher = dispatcher;
+ }
+
+ protected abstract string CreateHtml(string source, string state);
+
+ public void Compile(string source, string state)
+ {
+ _dispatcher.BeginInvoke(new Action(() =>
+ {
+ _browser.ObjectForScripting = this;
+ _browser.ScriptErrorsSuppressed = true;
+ _browser.DocumentText = CreateHtml(source, state);
+
+ }), DispatcherPriority.ApplicationIdle, null);
+ }
+
+ public void Execute(string result, string state)
+ {
+ OnCompleted(result, state);
+ }
+
+ protected static string ReadResourceFile(string resourceFile)
+ {
+ using (Stream s = typeof(JsHintCompiler).Assembly.GetManifestResourceStream(resourceFile))
+ using (var reader = new StreamReader(s))
+ {
+ return reader.ReadToEnd();
+ }
+ }
+
+ public event EventHandler Completed;
+
+ protected void OnCompleted(string message, string data)
+ {
+ if (Completed != null)
+ {
+ Completed(this, new CompilerEventArgs() { Result = message, State = data });
+ }
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ Completed = null;
+
+ if (_browser != null)
+ {
+ _browser.Dispose();
+ }
+
+ _browser = null;
+ _disposed = true;
+ }
+ }
+}
+
+public class CompilerEventArgs : EventArgs
+{
+ public string Result { get; set; }
+ public string State { get; set; }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Margin/ScssMargin.cs b/EditorExtensions/Margin/ScssMargin.cs
new file mode 100644
index 000000000..e16c0fb69
--- /dev/null
+++ b/EditorExtensions/Margin/ScssMargin.cs
@@ -0,0 +1,158 @@
+//using EnvDTE;
+//using Microsoft.CSS.Core;
+//using Microsoft.VisualStudio.Text;
+//using SassAndCoffee.Ruby.Sass;
+//using System;
+//using System.IO;
+//using System.Linq;
+//using System.Threading.Tasks;
+
+//namespace MadsKristensen.EditorExtensions
+//{
+// ///
+// /// A class detailing the margin's visual definition including both size and content.
+// ///
+// class ScssMargin : MarginBase
+// {
+// public const string MarginName = "ScssMargin";
+// private SassCompiler _compiler;
+
+// public ScssMargin()
+// : base()
+// {
+// _compiler = new SassCompiler();
+// }
+
+// public ScssMargin(string contentType, string source, bool showMargin, ITextDocument document)
+// : base(source, MarginName, contentType, showMargin, document)
+// {
+// _compiler = new SassCompiler();
+// }
+
+// public void CompileProject()
+// {
+// Project project = ProjectHelpers.GetActiveProject();
+
+// if (project != null && !string.IsNullOrEmpty(project.FullName))
+// {
+// Task.Run(() =>
+// {
+// string dir = Path.GetDirectoryName(project.FullName);
+// var files = Directory.GetFiles(dir, "*.scss", SearchOption.AllDirectories).Where(f => CanCompile(f));
+
+// Parallel.ForEach(files, file =>
+// {
+// EditorExtensionsPackage.DTE.StatusBar.Text = "Web Essentials: Compiling " + Path.GetFileName(file);
+// string result = CompileFile(file);
+// base.WriteCompiledFile(result, file);
+// this.MinifyFile(file, result);
+// });
+
+// EditorExtensionsPackage.DTE.StatusBar.Clear();
+// });
+// }
+// }
+
+// private static bool CanCompile(string fileName)
+// {
+// if (EditorExtensionsPackage.DTE.Solution.FindProjectItem(fileName) == null)
+// return false;
+
+// if (Path.GetFileName(fileName).StartsWith("_"))
+// return false;
+
+// string minFile = MarginBase.GetCompiledFileName(fileName, ".min.css", WESettings.GetBoolean(WESettings.Keys.LessCompileToFolder));
+// if (File.Exists(minFile) && WESettings.GetBoolean(WESettings.Keys.LessMinify))
+// return true;
+
+// string cssFile = MarginBase.GetCompiledFileName(fileName, ".css", WESettings.GetBoolean(WESettings.Keys.LessCompileToFolder));
+// if (!File.Exists(cssFile))
+// return false;
+
+
+// return true;
+// }
+
+
+// protected override void StartCompiler(string source)
+// {
+// string fileName = GetCompiledFileName(Document.FilePath, ".css", UseCompiledFolder);
+
+// if (_isFirstRun && File.Exists(fileName))
+// {
+// OnCompilationDone(File.ReadAllText(fileName), Document.FilePath);
+// return;
+// }
+// else if (!Path.GetFileName(Document.FilePath).StartsWith("_"))
+// {
+// Task.Run(() =>
+// {
+// Compile(Document.FilePath);
+// });
+// }
+// }
+
+// private void Compile(string fileName)
+// {
+// EditorExtensionsPackage.DTE.StatusBar.Text = "Web Essentials: Compiling " + Path.GetFileName(fileName);
+
+// string result = CompileFile(fileName);
+// OnCompilationDone(result, Document.FilePath);
+
+// EditorExtensionsPackage.DTE.StatusBar.Clear();
+// }
+
+// private string CompileFile(string fileName)
+// {
+// try
+// {
+// string result = _compiler.Compile(fileName, false, null);
+
+// CssFormatter formatter = new CssFormatter();
+// result = formatter.Format(result);
+
+// return result;
+// }
+// catch (Exception ex)
+// {
+// return "ERROR: " + ex.Message;
+// }
+// }
+
+// public override void MinifyFile(string fileName, string source)
+// {
+// if (WESettings.GetBoolean(WESettings.Keys.ScssMinify))
+// {
+// string content = MinifyFileMenu.MinifyString(".css", source);
+// string minFile = GetCompiledFileName(fileName, ".min.css", UseCompiledFolder);
+// bool fileExist = File.Exists(minFile);
+
+// ProjectHelpers.CheckOutFileFromSourceControl(minFile);
+// File.WriteAllText(minFile, content);
+
+// if (!fileExist)
+// AddFileToProject(Document.FilePath, minFile);
+// }
+// }
+
+// public override bool CanTakeFocus
+// {
+// get { return true; }
+// }
+
+// public override bool IsSaveFileEnabled
+// {
+// get { return WESettings.GetBoolean(WESettings.Keys.GenerateCssFileFromScss) && !Path.GetFileName(Document.FilePath).StartsWith("_"); }
+// }
+
+// public override bool UseCompiledFolder
+// {
+// get { return WESettings.GetBoolean(WESettings.Keys.ScssCompileToFolder); }
+// }
+
+// protected override bool CanWriteToDisk(string source)
+// {
+// return !string.IsNullOrWhiteSpace(source);
+// }
+// }
+//}
\ No newline at end of file
diff --git a/EditorExtensions/Margin/TypeScriptMargin.cs b/EditorExtensions/Margin/TypeScriptMargin.cs
new file mode 100644
index 000000000..6d3e4fc53
--- /dev/null
+++ b/EditorExtensions/Margin/TypeScriptMargin.cs
@@ -0,0 +1,314 @@
+using Microsoft.Ajax.Utilities;
+using Microsoft.VisualStudio.Text;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class TypeScriptMargin : MarginBase
+ {
+ public const string MarginName = "TypeScriptMargin";
+ private string _executablePath;
+
+ public TypeScriptMargin(string contentType, string source, bool showMargin, ITextDocument document)
+ : base(source, MarginName, contentType, showMargin, document)
+ {
+ _executablePath = GetExecutablePath();
+ }
+
+ public TypeScriptMargin()
+ : base()
+ {
+ _executablePath = GetExecutablePath();
+ }
+
+ public void CompileProjectFiles(EnvDTE.Project project)
+ {
+ try
+ {
+ if (!File.Exists(_executablePath) || string.IsNullOrEmpty(project.FullName))
+ return;
+
+ string fullPath = project.Properties.Item("FullPath").Value.ToString();
+
+ if (project != null && !string.IsNullOrEmpty(fullPath))
+ {
+ string dir = Path.GetDirectoryName(fullPath);
+ var files = Directory.GetFiles(dir, "*.ts", SearchOption.AllDirectories);
+
+ Parallel.ForEach(files, file =>
+ {
+ if (!file.EndsWith(".d.ts") && EditorExtensionsPackage.DTE.Solution.FindProjectItem(file) != null)
+ {
+ StartProcess(file, CompileProjectExited);
+ }
+ });
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(ex);
+ }
+ }
+
+ private void CompileProjectExited(object sender, EventArgs e)
+ {
+ Process p = (Process)sender;
+ string file = p.StartInfo.EnvironmentVariables["file"];
+
+ p.Exited -= CompileProjectExited;
+ p.Dispose();
+
+ string js = file.Replace(".ts", ".js");
+
+ if (File.Exists(js))
+ {
+ try
+ {
+ string content = File.ReadAllText(js);
+ MinifyFile(file, content);
+ ResaveWithBom(js, content);
+ Logger.Log("TypeScript: Compiling " + Path.GetFileName(file));
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(ex);
+ }
+ }
+
+ if (WESettings.GetBoolean(WESettings.Keys.TypeScriptAddGeneratedFilesToProject))
+ {
+ AddFileToProject(file);
+ }
+ }
+
+ private void ResaveWithBom(string fileName, string content)
+ {
+ if (WESettings.GetBoolean(WESettings.Keys.TypeScriptResaveWithUtf8BOM))
+ {
+ using (StreamWriter writer = new StreamWriter(fileName, false, new UTF8Encoding(true)))
+ {
+ writer.Write(content);
+ }
+ }
+ }
+
+ public override void MinifyFile(string fileName, string source)
+ {
+ if (!WESettings.GetBoolean(WESettings.Keys.TypeScriptMinify))
+ return;
+
+ try
+ {
+ string filePath = fileName.Replace(".ts", ".js");
+ if (File.Exists(filePath))
+ {
+ Minifier minifier = new Minifier();
+ CodeSettings settings = new CodeSettings() { EvalTreatment = EvalTreatment.MakeImmediateSafe, PreserveImportantComments = false };
+
+ string content = minifier.MinifyJavaScript(source, settings);
+ string minFile = fileName.Replace(".ts", ".min.js");
+ bool fileExist = File.Exists(minFile);
+
+ using (StreamWriter writer = new StreamWriter(minFile, false, new UTF8Encoding(true)))
+ {
+ writer.Write(content);
+ }
+
+ if (!fileExist && WESettings.GetBoolean(WESettings.Keys.TypeScriptAddGeneratedFilesToProject))
+ AddFileToProject(fileName, minFile);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(ex);
+ }
+ }
+
+ private static void AddFileToProject(string file)
+ {
+ string[] files = GetChildren(file);
+
+ foreach (string generated in files)
+ {
+ if (EditorExtensionsPackage.DTE.Solution.FindProjectItem(generated) != null)
+ continue;
+
+ if (File.Exists(generated))
+ {
+ AddFileToProject(file, generated);
+ }
+ }
+ }
+
+ private static string[] GetChildren(string file)
+ {
+ return new string[] {
+ file.Replace(".ts", ".js"),
+ file.Replace(".ts", ".min.js"),
+ file.Replace(".ts", ".js.map")
+ };
+ }
+
+ protected override void StartCompiler(string source)
+ {
+ string fileName = Document.FilePath.Replace(".ts", ".js");
+
+ if (_isFirstRun && File.Exists(fileName))
+ {
+ OnCompilationDone(File.ReadAllText(fileName), Document.FilePath);
+ }
+ else if (!fileName.EndsWith(".d.ts") && WESettings.GetBoolean(WESettings.Keys.GenerateJsFileFromTypeScript))
+ {
+ if (EditorExtensionsPackage.DTE.Solution.SolutionBuild.BuildState == EnvDTE.vsBuildState.vsBuildStateInProgress)
+ return;
+
+ if (File.Exists(_executablePath))
+ {
+ _isFirstRun = false;
+ System.Threading.Tasks.Task.Run(() =>
+ {
+ StartProcess(Document.FilePath, CompilerExited);
+ });
+ }
+ else
+ {
+ base.OnCompilationDone("ERROR: The TypeScript compiler couldn't be found. Download http://www.typescriptlang.org/#Download", Document.FilePath);
+ }
+ }
+ else
+ {
+ base.OnCompilationDone("// JavaScript generation is disabled in Tools -> Options", Document.FilePath);
+ }
+ }
+
+ private static string GetExecutablePath()
+ {
+ string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
+ string path = Path.Combine(programFiles, @"Microsoft SDKs\TypeScript\tsc.exe");
+
+ if (!File.Exists(path))
+ {
+ path = Path.Combine(programFiles, @"Microsoft SDKs\TypeScript\0.8.1.1\tsc.exe");
+ }
+
+ if (!File.Exists(path))
+ {
+ path = Path.Combine(programFiles, @"Microsoft SDKs\TypeScript\0.8.1.0\tsc.exe");
+ }
+
+ if (!File.Exists(path))
+ {
+ path = Path.Combine(programFiles, @"Microsoft SDKs\TypeScript\0.8.0.0\tsc.exe");
+ }
+
+ return path;
+ }
+
+ private void StartProcess(string file, EventHandler eventHandler)
+ {
+ CheckOutChildren(file);
+
+ Logger.Log("Compiling TypeScript...");
+
+ ProcessStartInfo start = new ProcessStartInfo();
+ start.WindowStyle = ProcessWindowStyle.Hidden;
+ start.CreateNoWindow = true;
+ start.Arguments = "\"" + file + "\"" + GenerateArguments();
+ start.FileName = _executablePath;
+ start.UseShellExecute = false;
+ start.EnvironmentVariables.Add("file", file);
+ start.RedirectStandardError = true;
+
+ Process p = new Process();
+ p.StartInfo = start;
+ p.EnableRaisingEvents = true;
+ p.Exited += eventHandler;
+
+ p.Start();
+ }
+
+ private static void CheckOutChildren(string file)
+ {
+ var files = GetChildren(file).Where(f => File.Exists(f));
+
+ foreach (string child in files)
+ {
+ ProjectHelpers.CheckOutFileFromSourceControl(child);
+ }
+ }
+
+ private static string GenerateArguments()
+ {
+ string args = string.Empty;
+
+ if (WESettings.GetBoolean(WESettings.Keys.TypeScriptUseAmdModule))
+ args += " --module amd";
+
+ if (WESettings.GetBoolean(WESettings.Keys.TypeScriptCompileES3))
+ args += " --target ES3";
+ else
+ args += " --target ES5";
+
+ if (WESettings.GetBoolean(WESettings.Keys.TypeScriptProduceSourceMap))
+ args += " -sourcemap";
+
+ if (WESettings.GetBoolean(WESettings.Keys.TypeScriptKeepComments))
+ args += " -c";
+
+ return args;
+
+ }
+
+ private void CompilerExited(object sender, EventArgs e)
+ {
+ Process p = (Process)sender;
+
+ if (p.ExitCode == 0)
+ {
+ string fileName = Document.FilePath.Replace(".ts", ".js");
+
+ if (File.Exists(fileName))
+ {
+ string content = File.ReadAllText(fileName);
+
+ ResaveWithBom(fileName, content);
+
+ if (WESettings.GetBoolean(WESettings.Keys.TypeScriptAddGeneratedFilesToProject))
+ {
+ AddFileToProject(Document.FilePath);
+ }
+
+ Logger.Log("TypeScript: Compiling " + Path.GetFileName(fileName));
+ base.OnCompilationDone(content, Document.FilePath);
+ }
+ }
+ else
+ {
+ base.OnCompilationDone("ERROR: " + p.StandardError.ReadToEnd(), Document.FilePath);
+ }
+
+ p.Exited -= CompilerExited;
+ p.Dispose();
+ }
+
+ public override bool IsSaveFileEnabled
+ {
+ get { return false; }
+ }
+
+ public override bool UseCompiledFolder
+ {
+ get { return false; }
+ }
+
+ protected override bool CanWriteToDisk(string source)
+ {
+ return false;
+ }
+ }
+}
diff --git a/EditorExtensions/MenuItems/BuildMenu.cs b/EditorExtensions/MenuItems/BuildMenu.cs
new file mode 100644
index 000000000..b36afb26f
--- /dev/null
+++ b/EditorExtensions/MenuItems/BuildMenu.cs
@@ -0,0 +1,145 @@
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.VisualStudio.Shell;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Design;
+using System.IO;
+using System.Linq;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class BuildMenu
+ {
+ private static DTE2 _dte;
+ private OleMenuCommandService _mcs;
+
+ public BuildMenu(DTE2 dte, OleMenuCommandService mcs)
+ {
+ _dte = dte;
+ _mcs = mcs;
+ }
+
+ public void SetupCommands()
+ {
+ CommandID cmdBundles = new CommandID(GuidList.guidBuildCmdSet, (int)PkgCmdIDList.cmdBuildBundles);
+ OleMenuCommand menuBundles = new OleMenuCommand((s, e) => UpdateBundleFiles(), cmdBundles);
+ _mcs.AddCommand(menuBundles);
+
+ CommandID cmdLess = new CommandID(GuidList.guidBuildCmdSet, (int)PkgCmdIDList.cmdBuildLess);
+ OleMenuCommand menuLess = new OleMenuCommand((s, e) => BuildLess(), cmdLess);
+ _mcs.AddCommand(menuLess);
+
+ //CommandID cmdTS = new CommandID(GuidList.guidBuildCmdSet, (int)PkgCmdIDList.cmdBuildTypeScript);
+ //OleMenuCommand menuTS = new OleMenuCommand((s, e) => BuildTypeScript(), cmdTS);
+ //_mcs.AddCommand(menuTS);
+
+ CommandID cmdMinify = new CommandID(GuidList.guidBuildCmdSet, (int)PkgCmdIDList.cmdBuildMinify);
+ OleMenuCommand menuMinify = new OleMenuCommand((s, e) => Minify(), cmdMinify);
+ _mcs.AddCommand(menuMinify);
+
+ CommandID cmdCoffee = new CommandID(GuidList.guidBuildCmdSet, (int)PkgCmdIDList.cmdBuildCoffeeScript);
+ OleMenuCommand menuCoffee = new OleMenuCommand((s, e) => BuildCoffeeScript(), cmdCoffee);
+ _mcs.AddCommand(menuCoffee);
+ }
+
+ private void BuildCoffeeScript()
+ {
+ foreach (Project project in _dte.Solution.Projects)
+ {
+ CoffeeScriptMargin margin = new CoffeeScriptMargin();
+ margin.CompileProject(project);
+ }
+ }
+
+ private void UpdateBundleFiles()
+ {
+ //Logger.Log("Updating bundles...");
+ BundleFilesMenu.UpdateBundles(null, true);
+ //Logger.Log("Bundles updated");
+ }
+
+ private void BuildLess()
+ {
+ foreach (Project project in _dte.Solution.Projects)
+ {
+ LessProjectCompiler.CompileProject(project);
+ }
+ }
+
+ //private void BuildTypeScript()
+ //{
+ // foreach (Project project in _dte.Solution.Projects)
+ // {
+ // new TypeScriptMargin().CompileProjectFiles(project);
+ // }
+ //}
+
+ private void Minify()
+ {
+ _dte.StatusBar.Text = "Web Essentials: Minifying files...";
+ var files = GetFiles();
+
+ foreach (string path in files)
+ {
+ string extension = Path.GetExtension(path);
+ string minPath = MinifyFileMenu.GetMinFileName(path, extension);
+
+ if (!path.EndsWith(".min" + extension) && File.Exists(minPath) && _dte.Solution.FindProjectItem(path) != null)
+ {
+ if (extension.Equals(".js", StringComparison.OrdinalIgnoreCase))
+ {
+ JavaScriptSaveListener.Minify(path, minPath, false);
+ }
+ else
+ {
+ CssSaveListener.Minify(path, minPath);
+ }
+ }
+ }
+
+ _dte.StatusBar.Text = "Web Essentials: Files minified";
+ }
+
+ private IEnumerable GetFiles()
+ {
+ //Project project = ProjectHelpers.GetActiveProject();
+
+ foreach (Project project in _dte.Solution.Projects)
+ {
+ if (string.IsNullOrEmpty(project.FullName))
+ continue;
+
+ string dir = Path.GetDirectoryName(project.FullName);
+
+ List list = new List();
+ list.AddRange(Directory.GetFiles(dir, "*.css", SearchOption.AllDirectories));
+ list.AddRange(Directory.GetFiles(dir, "*.js", SearchOption.AllDirectories));
+
+ foreach (string file in list.Where(f => !f.Contains(".min.")))
+ {
+ string extension = Path.GetExtension(file);
+
+ if (extension == ".css")
+ {
+ if (!File.Exists(file.Replace(".css", ".less")) &&
+ !File.Exists(file.Replace(".css", ".scss")) &&
+ !File.Exists(file + ".bundle"))
+
+ yield return file;
+ }
+ if (extension == ".js")
+ {
+ if (!File.Exists(file.Replace(".js", ".coffee")) &&
+ !File.Exists(file.Replace(".js", ".ts")) &&
+ !File.Exists(file + ".bundle"))
+
+ yield return file;
+ }
+ }
+ }
+
+ yield break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/MenuItems/BundleFiles.cs b/EditorExtensions/MenuItems/BundleFiles.cs
new file mode 100644
index 000000000..0ce185c0b
--- /dev/null
+++ b/EditorExtensions/MenuItems/BundleFiles.cs
@@ -0,0 +1,400 @@
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.ComponentModel.Design;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Windows;
+using System.Windows.Threading;
+using System.Xml;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(IWpfTextViewCreationListener))]
+ [ContentType("CSS")]
+ [ContentType("JavaScript")]
+ [ContentType("XML")]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ internal class BundleFilesMenu : IWpfTextViewCreationListener
+ {
+ private static DTE2 _dte;
+ private OleMenuCommandService _mcs;
+ public const string _ext = ".bundle";
+
+ public BundleFilesMenu()
+ {
+ // Used by the IWpfTextViewCreationListener
+ _dte = EditorExtensionsPackage.DTE;
+ }
+
+ public BundleFilesMenu(DTE2 dte, OleMenuCommandService mcs)
+ {
+ _dte = dte;
+ _mcs = mcs;
+ }
+
+ public void TextViewCreated(IWpfTextView textView)
+ {
+ ITextDocument document;
+ textView.TextDataModel.DocumentBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document);
+
+ if (document != null)
+ {
+ document.FileActionOccurred += document_FileActionOccurred;
+ }
+ }
+
+ private void document_FileActionOccurred(object sender, TextDocumentFileActionEventArgs e)
+ {
+ if (e.FileActionType == FileActionTypes.ContentSavedToDisk)
+ {
+ string file = e.FilePath.EndsWith(_ext) ? null : e.FilePath;
+
+ System.Threading.Tasks.Task.Run(() =>
+ {
+ UpdateBundles(file, file == null);
+ });
+ }
+ }
+
+ public static void UpdateBundles(string changedFile, bool isBuild)
+ {
+ if (string.IsNullOrEmpty(changedFile))
+ {
+ foreach (Project project in EditorExtensionsPackage.DTE.Solution.Projects)
+ {
+ if (project.ProjectItems.Count > 1)
+ {
+ UpdateBundle(project.ProjectItems.Item(project.ProjectItems.Count).FileNames[1], isBuild);
+ }
+ }
+ }
+ else
+ {
+ UpdateBundle(changedFile, isBuild);
+ }
+ }
+
+ private static void UpdateBundle(string changedFile, bool isBuild)
+ {
+ string dir = ProjectHelpers.GetProjectFolder(changedFile);
+
+ if (string.IsNullOrEmpty(dir))
+ return;
+
+ //if (dir.Contains("."))
+ //{
+ // dir = Path.GetDirectoryName(dir);
+ //}
+
+ foreach (string file in Directory.GetFiles(dir, "*" + _ext, SearchOption.AllDirectories))
+ {
+ if (file.IndexOf("\\app_data\\", StringComparison.OrdinalIgnoreCase) > -1)
+ continue;
+
+ XmlDocument doc = GetXmlDocument(file);
+ bool enabled = false;
+
+ if (doc != null)
+ {
+ XmlNode bundleNode = doc.SelectSingleNode("//bundle");
+ if (bundleNode == null)
+ continue;
+
+ XmlNodeList nodes = doc.SelectNodes("//file");
+ foreach (XmlNode node in nodes)
+ {
+ string relative = node.InnerText;
+ string absolute = ProjectHelpers.ToAbsoluteFilePath(relative, dir).Replace("/", "\\").Replace("\\\\", "\\");
+
+ if (changedFile != null && absolute.Equals(changedFile.Replace("\\\\", "\\"), StringComparison.OrdinalIgnoreCase))
+ {
+ enabled = true;
+ break;
+ }
+ }
+
+ if (isBuild && bundleNode.Attributes["runOnBuild"] != null && bundleNode.Attributes["runOnBuild"].InnerText == "true")
+ {
+ enabled = true;
+ }
+
+ if (enabled)
+ {
+ WriteBundleFile(file, doc);
+ }
+ }
+ }
+ }
+
+ private static XmlDocument GetXmlDocument(string filePath)
+ {
+ try
+ {
+ XmlDocument doc = new XmlDocument();
+ doc.LoadXml(File.ReadAllText(filePath));
+ return doc;
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(ex);
+ return null;
+ }
+ }
+
+ public void SetupCommands()
+ {
+ CommandID commandCss = new CommandID(GuidList.guidBundleCmdSet, (int)PkgCmdIDList.BundleCss);
+ OleMenuCommand menuCommandCss = new OleMenuCommand((s, e) => CreateBundlefile(".css"), commandCss);
+ menuCommandCss.BeforeQueryStatus += (s, e) => { BeforeQueryStatus(s, ".css"); };
+ _mcs.AddCommand(menuCommandCss);
+
+ CommandID commandJs = new CommandID(GuidList.guidBundleCmdSet, (int)PkgCmdIDList.BundleJs);
+ OleMenuCommand menuCommandJs = new OleMenuCommand((s, e) => CreateBundlefile(".js"), commandJs);
+ menuCommandJs.BeforeQueryStatus += (s, e) => { BeforeQueryStatus(s, ".js"); };
+ _mcs.AddCommand(menuCommandJs);
+ }
+
+ private void BeforeQueryStatus(object sender, string extension)
+ {
+ OleMenuCommand menuCommand = sender as OleMenuCommand;
+ //_selectedItems = ProjectHelpers.GetSelectedItems().Where(p => Path.GetExtension(p.FileNames[1]) == extension);
+
+ menuCommand.Enabled = GetSelectedItems(extension).Count() > 1;
+ }
+
+ private IEnumerable GetSelectedItems(string extension)
+ {
+ return ProjectHelpers.GetSelectedItems().Where(p => Path.GetExtension(p.FileNames[1]) == extension);
+ }
+
+ //private IEnumerable GetSelectedFilePaths(string extension)
+ //{
+ // var raw = ProjectHelpers.GetSelectedItems().Where(p => Path.GetExtension(p.Properties.Item("FullPath").ToString()) == extension);
+
+ // foreach (string file in raw)
+ // {
+ // //if (!file.EndsWith(".min" + extension))
+ // //{
+ // yield return file;
+ // //}
+ // }
+ //}
+
+ private void CreateBundlefile(string extension)
+ {
+ //var selectedPaths = GetSelectedFilePaths(extension);
+ StringBuilder sb = new StringBuilder();
+ string firstFile = null;
+ var items = GetSelectedItems(extension);
+
+ foreach (ProjectItem item in items)
+ {
+ if (string.IsNullOrEmpty(firstFile))
+ firstFile = item.FileNames[1];
+
+ string content = File.ReadAllText(item.FileNames[1]);
+ sb.AppendLine(content);
+ }
+
+ if (firstFile != null)
+ {
+ string dir = Path.GetDirectoryName(firstFile);
+
+
+ dir = GetProjectRelativeFolder(items.ElementAt(0));
+
+ if (Directory.Exists(dir))
+ {
+ string bundleFile = Microsoft.VisualBasic.Interaction.InputBox("Specify the name of the bundle", "Web Essentials", "bundle1");
+
+ if (!bundleFile.EndsWith(_ext, StringComparison.OrdinalIgnoreCase))
+ bundleFile += extension + _ext;
+
+ string bundlePath = Path.Combine(dir, bundleFile);
+
+ if (File.Exists(bundlePath))
+ {
+ MessageBox.Show("The file already exist", "Web Essentials", MessageBoxButton.OK, MessageBoxImage.Warning);
+ }
+ else
+ {
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => WriteFile(bundlePath, items, extension, bundleFile.Replace(_ext, string.Empty))),
+ DispatcherPriority.ApplicationIdle, null);
+ }
+ }
+ }
+ }
+
+ private static string GetProjectRelativeFolder(ProjectItem item)
+ {
+ object parent = item.Collection.Parent;
+ ProjectItem folder = parent as ProjectItem;
+ Project project = parent as Project;
+
+ if (folder != null)
+ {
+ return folder.FileNames[1];
+ }
+ else if (project != null)
+ {
+ return project.FullName;
+ }
+
+ return null;
+ }
+
+ private void WriteFile(string filePath, IEnumerable files, string extension, string output)
+ {
+ string projectRoot = ProjectHelpers.GetProjectFolder(files.ElementAt(0).FileNames[1]);
+ StringBuilder sb = new StringBuilder();
+ XmlWriterSettings settings = new XmlWriterSettings();
+ settings.Indent = true;
+
+ using (XmlWriter writer = XmlWriter.Create(sb, settings))
+ {
+ writer.WriteStartElement("bundle");
+ writer.WriteAttributeString("minify", "true");
+ writer.WriteAttributeString("runOnBuild", "true");
+ writer.WriteAttributeString("output", output);
+ writer.WriteComment("The order of the elements determines the order of them when bundled.");
+
+ foreach (ProjectItem item in files)
+ {
+ string relative = item.IsLink() ? item.FileNames[1] : "/" + FileHelpers.RelativePath(projectRoot, item.FileNames[1]);
+ writer.WriteElementString("file", relative);
+ }
+
+ writer.WriteEndElement();
+ }
+
+ sb.Replace(Encoding.Unicode.WebName, Encoding.UTF8.WebName);
+
+ ProjectHelpers.CheckOutFileFromSourceControl(filePath);
+ File.WriteAllText(filePath, sb.ToString());
+ ProjectHelpers.AddFileToActiveProject(filePath, "None");
+
+ _dte.ItemOperations.OpenFile(filePath);
+
+ XmlDocument doc = GetXmlDocument(filePath);
+
+ if (doc != null)
+ {
+ Dispatcher.CurrentDispatcher.BeginInvoke(
+ new Action(() => WriteBundleFile(filePath, doc)), DispatcherPriority.ApplicationIdle, null);
+ }
+ }
+
+ private static void WriteBundleFile(string filePath, XmlDocument doc)
+ {
+ XmlNode bundleNode = doc.SelectSingleNode("//bundle");
+
+ if (bundleNode == null)
+ return;
+
+ XmlNode outputAttr = bundleNode.Attributes["output"];
+
+ if (outputAttr != null && (outputAttr.InnerText.Contains("/") || outputAttr.InnerText.Contains("\\")))
+ {
+ MessageBox.Show("The 'output' attribute is for file names only - not paths", "Web Essentials");
+ return;
+ }
+
+ Dictionary files = new Dictionary();
+ string extension = Path.GetExtension(filePath.Replace(_ext, string.Empty));
+ XmlNodeList nodes = doc.SelectNodes("//file");
+
+ foreach (XmlNode node in nodes)
+ {
+ string absolute = ProjectHelpers.ToAbsoluteFilePath(node.InnerText, ProjectHelpers.GetProjectFolder(filePath)).Replace("\\\\", "\\");
+
+ if (node.InnerText.Contains(":\\") || node.InnerText.StartsWith("\\\\"))
+ {
+ absolute = node.InnerText;
+ }
+
+ if (File.Exists(absolute))
+ {
+ if (!files.ContainsKey(absolute))
+ files.Add(absolute, node.InnerText);
+ }
+ else
+ {
+ string error = string.Format("Bundle error: The file '{0}' doesn't exist", node.InnerText);
+ _dte.ItemOperations.OpenFile(filePath);
+ MessageBox.Show(error, "Web Essentials");
+ return;
+ }
+ }
+
+ string bundlePath = outputAttr != null ? Path.Combine(Path.GetDirectoryName(filePath), outputAttr.InnerText) : filePath.Replace(_ext, string.Empty);
+ StringBuilder sb = new StringBuilder();
+
+ foreach (string file in files.Keys)
+ {
+ //if (extension.Equals(".css", StringComparison.OrdinalIgnoreCase))
+ //{
+ // sb.AppendLine("/*#source " + files[file] + " */");
+ //}
+ if (extension.Equals(".js", StringComparison.OrdinalIgnoreCase) && WESettings.GetBoolean(WESettings.Keys.GenerateJavaScriptSourceMaps))
+ {
+ sb.AppendLine("///#source 1 1 " + files[file]);
+ }
+
+ sb.AppendLine(File.ReadAllText(file));
+ }
+
+ if (!File.Exists(bundlePath) || File.ReadAllText(bundlePath) != sb.ToString())
+ {
+ ProjectHelpers.CheckOutFileFromSourceControl(bundlePath);
+ using (StreamWriter writer = new StreamWriter(bundlePath, false, new UTF8Encoding(true)))
+ {
+ writer.Write(sb.ToString());
+ Logger.Log("Updating bundle: " + Path.GetFileName(bundlePath));
+ }
+ MarginBase.AddFileToProject(filePath, bundlePath);
+
+ if (bundleNode.Attributes["minify"] != null || bundleNode.Attributes["minify"].InnerText == "true")
+ {
+ WriteMinFile(filePath, bundlePath, sb.ToString(), extension);
+ }
+ }
+ }
+
+ private static void WriteMinFile(string filePath, string bundlePath, string content, string extension)
+ {
+ string minPath = bundlePath.Replace(Path.GetExtension(bundlePath), ".min" + Path.GetExtension(bundlePath));
+
+ if (extension.Equals(".js", StringComparison.OrdinalIgnoreCase))
+ {
+ JavaScriptSaveListener.Minify(bundlePath, minPath, true);
+ MarginBase.AddFileToProject(filePath, minPath);
+
+ if (WESettings.GetBoolean(WESettings.Keys.GenerateJavaScriptSourceMaps))
+ {
+ MarginBase.AddFileToProject(filePath, minPath + ".map");
+ }
+ }
+ else if (extension.Equals(".css", StringComparison.OrdinalIgnoreCase))
+ {
+ string minContent = MinifyFileMenu.MinifyString(extension, content);
+
+ ProjectHelpers.CheckOutFileFromSourceControl(minPath);
+
+ using (StreamWriter writer = new StreamWriter(minPath, false, new UTF8Encoding(true)))
+ {
+ writer.Write(minContent);
+ }
+ MarginBase.AddFileToProject(filePath, minPath);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/MenuItems/Diff.cs b/EditorExtensions/MenuItems/Diff.cs
new file mode 100644
index 000000000..038baa05f
--- /dev/null
+++ b/EditorExtensions/MenuItems/Diff.cs
@@ -0,0 +1,66 @@
+using System.ComponentModel.Design;
+using System.Linq;
+using System.Text;
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Shell;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class DiffMenu
+ {
+ private DTE2 _dte;
+ private OleMenuCommandService _mcs;
+
+ public DiffMenu(DTE2 dte, OleMenuCommandService mcs)
+ {
+ _dte = dte;
+ _mcs = mcs;
+ }
+
+ public void SetupCommands()
+ {
+ CommandID commandId = new CommandID(GuidList.guidDiffCmdSet, (int)PkgCmdIDList.cmdDiff);
+ OleMenuCommand menuCommand = new OleMenuCommand((s, e) => Sort(), commandId);
+ menuCommand.BeforeQueryStatus += menuCommand_BeforeQueryStatus;
+ _mcs.AddCommand(menuCommand);
+ }
+
+ //private List list = new List()
+ //{
+ // ".txt", ".cs", ".aspx", ".ascx", ".asmx", ".master", ".cshtml", ".vbhtml", ".js", ".coffee", ".css", ".less", ".sass", ".scss", ".xml"
+ //};
+
+ private List files;
+
+ void menuCommand_BeforeQueryStatus(object sender, System.EventArgs e)
+ {
+ OleMenuCommand menuCommand = sender as OleMenuCommand;
+ files = new List(ProjectHelpers.GetSelectedItemPaths());
+
+ //if (files.Count == 2)
+ //{
+ // if (list.Contains(Path.GetExtension(files[0]).ToLowerInvariant()))
+ // {
+ // if (list.Contains(Path.GetExtension(files[1]).ToLowerInvariant()))
+ // {
+ // menuCommand.Enabled = true;
+ // return;
+ // }
+ // }
+ //}
+
+ menuCommand.Enabled = files.Count == 2;
+ }
+
+ private void Sort()
+ {
+ if (files.Count == 2)
+ EditorExtensionsPackage.DTE.ExecuteCommand("Tools.DiffFiles \"" + files[0] + "\" \"" + files[1] + "\"");
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/MenuItems/Encoding.cs b/EditorExtensions/MenuItems/Encoding.cs
new file mode 100644
index 000000000..c3e58763e
--- /dev/null
+++ b/EditorExtensions/MenuItems/Encoding.cs
@@ -0,0 +1,62 @@
+//using System;
+//using System.ComponentModel.Design;
+//using System.Web;
+//using EnvDTE;
+//using EnvDTE80;
+//using Microsoft.VisualStudio.Shell;
+
+//namespace MadsKristensen.EditorExtensions
+//{
+// internal class EncodingMenu
+// {
+// private DTE2 _dte;
+// private OleMenuCommandService _mcs;
+// private delegate string Replacement(string original);
+
+// public EncodingMenu(DTE2 dte, OleMenuCommandService mcs)
+// {
+// _dte = dte;
+// _mcs = mcs;
+// }
+
+// public void SetupCommands()
+// {
+// SetupCommand(PkgCmdIDList.htmlEncode, HttpUtility.HtmlEncode);
+// SetupCommand(PkgCmdIDList.attrEncode, HttpUtility.HtmlAttributeEncode);
+// SetupCommand(PkgCmdIDList.htmlDecode, HttpUtility.HtmlDecode);
+// SetupCommand(PkgCmdIDList.urlEncode, HttpUtility.UrlEncode);
+// SetupCommand(PkgCmdIDList.urlPathEncode, HttpUtility.UrlPathEncode);
+// SetupCommand(PkgCmdIDList.urlDecode, HttpUtility.UrlDecode);
+// SetupCommand(PkgCmdIDList.jsEncode, HttpUtility.JavaScriptStringEncode);
+// }
+
+// private void SetupCommand(uint command, Replacement callback)
+// {
+// CommandID commandId = new CommandID(GuidList.guidEditorExtensionsCmdSet, (int)command);
+// OleMenuCommand menuCommand = new OleMenuCommand((s, e) => Replace(callback), commandId);
+
+// menuCommand.BeforeQueryStatus += (s, e) =>
+// {
+// string selection = GetTextDocument().Selection.Text;
+// menuCommand.Enabled = selection.Length > 0 && callback(selection) != selection;
+// };
+
+// _mcs.AddCommand(menuCommand);
+// }
+
+// private TextDocument GetTextDocument()
+// {
+// return _dte.ActiveDocument.Object("TextDocument") as TextDocument;
+// }
+
+// private void Replace(Replacement callback)
+// {
+// TextDocument document = GetTextDocument();
+// string replacement = callback(document.Selection.Text);
+
+// _dte.UndoContext.Open(callback.Method.Name);
+// document.Selection.Insert(replacement, 0);
+// _dte.UndoContext.Close();
+// }
+// }
+//}
\ No newline at end of file
diff --git a/EditorExtensions/MenuItems/JsHint.cs b/EditorExtensions/MenuItems/JsHint.cs
new file mode 100644
index 000000000..9c9b6ec7a
--- /dev/null
+++ b/EditorExtensions/MenuItems/JsHint.cs
@@ -0,0 +1,52 @@
+using EnvDTE80;
+using Microsoft.VisualStudio.Shell;
+using System.Collections.Generic;
+using System.ComponentModel.Design;
+using System.IO;
+using System.Linq;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class JsHintMenu
+ {
+ private DTE2 _dte;
+ private OleMenuCommandService _mcs;
+
+ public JsHintMenu(DTE2 dte, OleMenuCommandService mcs)
+ {
+ _dte = dte;
+ _mcs = mcs;
+ }
+
+ public void SetupCommands()
+ {
+ CommandID commandId = new CommandID(GuidList.guidDiffCmdSet, (int)PkgCmdIDList.cmdJsHint);
+ OleMenuCommand menuCommand = new OleMenuCommand((s, e) => RunJsHint(), commandId);
+ menuCommand.BeforeQueryStatus += menuCommand_BeforeQueryStatus;
+ _mcs.AddCommand(menuCommand);
+ }
+
+ private List files;
+
+ void menuCommand_BeforeQueryStatus(object sender, System.EventArgs e)
+ {
+ OleMenuCommand menuCommand = sender as OleMenuCommand;
+
+ var raw = MinifyFileMenu.GetSelectedFilePaths(_dte);
+ files = raw.Where(f => !JsHintRunner.ShouldIgnore(f)).ToList();
+
+ menuCommand.Enabled = files.Count > 0;
+ }
+
+ private void RunJsHint()
+ {
+ JsHintRunner.Reset();
+
+ foreach (string file in files)
+ {
+ JsHintRunner runner = new JsHintRunner(file);
+ runner.RunCompiler();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/MenuItems/MarkdownStylesheet.cs b/EditorExtensions/MenuItems/MarkdownStylesheet.cs
new file mode 100644
index 000000000..def2b71da
--- /dev/null
+++ b/EditorExtensions/MenuItems/MarkdownStylesheet.cs
@@ -0,0 +1,45 @@
+using System.ComponentModel.Design;
+using System.Linq;
+using System.Text;
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Shell;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class MarkdownStylesheetMenu
+ {
+ private DTE2 _dte;
+ private OleMenuCommandService _mcs;
+
+ public MarkdownStylesheetMenu(DTE2 dte, OleMenuCommandService mcs)
+ {
+ _dte = dte;
+ _mcs = mcs;
+ }
+
+ public void SetupCommands()
+ {
+ CommandID commandId = new CommandID(GuidList.guidDiffCmdSet, (int)PkgCmdIDList.cmdMarkdownStylesheet);
+ OleMenuCommand menuCommand = new OleMenuCommand((s, e) => AddStylesheet(), commandId);
+ menuCommand.BeforeQueryStatus += menuCommand_BeforeQueryStatus;
+ _mcs.AddCommand(menuCommand);
+ }
+
+ void menuCommand_BeforeQueryStatus(object sender, System.EventArgs e)
+ {
+ OleMenuCommand menuCommand = sender as OleMenuCommand;
+
+ menuCommand.Enabled = string.IsNullOrEmpty(MarkdownMargin.GetStylesheet());
+ }
+
+ private void AddStylesheet()
+ {
+ MarkdownMargin.CreateStylesheet();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/MenuItems/MinifyFile.cs b/EditorExtensions/MenuItems/MinifyFile.cs
new file mode 100644
index 000000000..036ee59fc
--- /dev/null
+++ b/EditorExtensions/MenuItems/MinifyFile.cs
@@ -0,0 +1,224 @@
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.Ajax.Utilities;
+using Microsoft.VisualStudio.Shell;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Design;
+using System.IO;
+using System.Linq;
+using System.Windows;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class MinifyFileMenu
+ {
+ private DTE2 _dte;
+ private OleMenuCommandService _mcs;
+
+ public MinifyFileMenu(DTE2 dte, OleMenuCommandService mcs)
+ {
+ _dte = dte;
+ _mcs = mcs;
+ }
+
+ public void SetupCommands()
+ {
+ CommandID commandCss = new CommandID(GuidList.guidMinifyCmdSet, (int)PkgCmdIDList.MinifyCss);
+ OleMenuCommand menuCommandCss = new OleMenuCommand((s, e) => MinifyFile(".css"), commandCss);
+ menuCommandCss.BeforeQueryStatus += (s, e) => { BeforeQueryStatus(s, ".css"); };
+ _mcs.AddCommand(menuCommandCss);
+
+ CommandID commandJs = new CommandID(GuidList.guidMinifyCmdSet, (int)PkgCmdIDList.MinifyJs);
+ OleMenuCommand menuCommandJs = new OleMenuCommand((s, e) => MinifyFile(".js"), commandJs);
+ menuCommandJs.BeforeQueryStatus += (s, e) => { BeforeQueryStatus(s, ".js"); };
+ _mcs.AddCommand(menuCommandJs);
+
+ //CommandID commandSelection = new CommandID(GuidList.guidMinifyCmdSet, (int)PkgCmdIDList.MinifySelection);
+ //OleMenuCommand menuCommandSelection = new OleMenuCommand((s, e) => MinifySelection(), commandSelection);
+ //menuCommandSelection.BeforeQueryStatus += menuCommandSelection_BeforeQueryStatus;
+ //_mcs.AddCommand(menuCommandSelection);
+ }
+
+ private readonly string[] _supported = new[] { "CSS", "JAVASCRIPT" };
+
+ //void menuCommandSelection_BeforeQueryStatus(object sender, EventArgs e)
+ //{
+ // OleMenuCommand menu = sender as OleMenuCommand;
+ // var view = ProjectHelpers.GetCurentTextView();
+
+ // if (view != null && view.Selection.SelectedSpans.Count > 0)
+ // {
+ // menu.Enabled = view.Selection.SelectedSpans[0].Length > 0;
+ // }
+ // else
+ // {
+ // menu.Enabled = false;
+ // }
+ //}
+
+ void BeforeQueryStatus(object sender, string extension)
+ {
+ OleMenuCommand menuCommand = sender as OleMenuCommand;
+ var selectedPaths = GetSelectedFilePaths(_dte).Where(p => Path.GetExtension(p) == extension);
+ bool enabled = false;
+
+ foreach (string path in selectedPaths)
+ {
+ string minFile = GetMinFileName(path, extension);
+
+ if (!path.EndsWith(".min" + extension) && !File.Exists(minFile))
+ {
+ enabled = true;
+ break;
+ }
+ }
+
+ menuCommand.Enabled = enabled;
+ }
+
+ //private void MinifySelection()
+ //{
+ // var view = ProjectHelpers.GetCurentTextView();
+
+ // if (view != null)
+ // {
+ // _dte.UndoContext.Open("Minify");
+
+ // string content = view.Selection.SelectedSpans[0].GetText();
+ // string extension = Path.GetExtension(_dte.ActiveDocument.FullName).ToLowerInvariant();
+ // string result = MinifyString(extension, content);
+
+ // view.TextBuffer.Replace(view.Selection.SelectedSpans[0].Span, result);
+
+ // _dte.UndoContext.Close();
+ // }
+ //}
+
+ private void MinifyFile(string extension)
+ {
+ var selectedPaths = GetSelectedFilePaths(_dte);
+
+ foreach (string path in selectedPaths.Where(p => p.EndsWith(extension, StringComparison.OrdinalIgnoreCase)))
+ {
+ string minPath = GetMinFileName(path, extension);
+
+ if (!path.EndsWith(".min" + extension) && !File.Exists(minPath) && _dte.Solution.FindProjectItem(path) != null)
+ {
+ if (extension.Equals(".js", StringComparison.OrdinalIgnoreCase))
+ {
+ JavaScriptSaveListener.Minify(path, minPath, false);
+ }
+ else
+ {
+ CssSaveListener.Minify(path, minPath);
+ }
+
+ MarginBase.AddFileToProject(path, minPath);
+ }
+ }
+
+ EnableSync(extension);
+ }
+
+ private void EnableSync(string extension)
+ {
+ string message = string.Format("Do you also want to enable automatic minification when the source file changes?", extension);
+
+ if (extension.Equals(".css", StringComparison.OrdinalIgnoreCase) && !WESettings.GetBoolean(WESettings.Keys.EnableCssMinification))
+ {
+ var result = MessageBox.Show(message, "Web Essentials", MessageBoxButton.YesNo, MessageBoxImage.Question);
+ if (result == MessageBoxResult.Yes)
+ {
+ Settings.SetValue(WESettings.Keys.EnableCssMinification, true);
+ Settings.Save();
+ }
+ }
+ else if (extension.Equals(".js", StringComparison.OrdinalIgnoreCase) && !WESettings.GetBoolean(WESettings.Keys.EnableJsMinification))
+ {
+ var result = MessageBox.Show(message, "Web Essentials", MessageBoxButton.YesNo, MessageBoxImage.Question);
+ if (result == MessageBoxResult.Yes)
+ {
+ Settings.SetValue(WESettings.Keys.EnableJsMinification, true);
+ Settings.Save();
+ }
+ }
+ }
+
+ public static string GetMinFileName(string path, string extension)
+ {
+ return path.Insert(path.Length - extension.Length, ".min");
+ }
+
+ public static string MinifyString(string extension, string content)
+ {
+ if (extension == ".css")
+ {
+ Minifier minifier = new Minifier();
+ CssSettings settings = new CssSettings();
+ settings.CommentMode = CssComment.None;
+
+ if (WESettings.GetBoolean(WESettings.Keys.KeepImportantComments))
+ {
+ settings.CommentMode = CssComment.Important;
+ }
+
+ return minifier.MinifyStyleSheet(content, settings);
+ }
+ else if (extension == ".js")
+ {
+ Minifier minifier = new Minifier();
+ CodeSettings settings = new CodeSettings()
+ {
+ EvalTreatment = EvalTreatment.MakeImmediateSafe,
+ PreserveImportantComments = WESettings.GetBoolean(WESettings.Keys.KeepImportantComments)
+ };
+
+ return minifier.MinifyJavaScript(content, settings);
+ }
+
+ return null;
+ }
+
+ public static IEnumerable GetSelectedFilePaths(DTE2 dte)
+ {
+ var selectedPaths = GetSelectedItemPaths(dte);
+ List list = new List();
+
+ foreach (string path in selectedPaths)
+ {
+ string extension = Path.GetExtension(path);
+
+ if (!string.IsNullOrEmpty(extension))
+ {
+ // file
+ list.Add(path);
+ }
+ else
+ {
+ // Folder
+ if (Directory.Exists(path))
+ {
+ list.AddRange(Directory.GetFiles(path));
+ }
+ }
+ }
+
+ return list;
+ }
+
+ private static IEnumerable GetSelectedItemPaths(DTE2 dte)
+ {
+ var items = (Array)dte.ToolWindows.SolutionExplorer.SelectedItems;
+ foreach (UIHierarchyItem selItem in items)
+ {
+ var item = selItem.Object as ProjectItem;
+ if (item != null)
+ {
+ yield return item.Properties.Item("FullPath").Value.ToString();
+ }
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/MenuItems/ProjectSettings.cs b/EditorExtensions/MenuItems/ProjectSettings.cs
new file mode 100644
index 000000000..03fb1f5c7
--- /dev/null
+++ b/EditorExtensions/MenuItems/ProjectSettings.cs
@@ -0,0 +1,95 @@
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.VisualStudio.Shell;
+using System.ComponentModel.Design;
+using System.IO;
+using System.Windows.Forms;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class ProjectSettingsMenu
+ {
+ private DTE2 _dte;
+ private OleMenuCommandService _mcs;
+
+ public ProjectSettingsMenu(DTE2 dte, OleMenuCommandService mcs)
+ {
+ _dte = dte;
+ _mcs = mcs;
+ }
+
+ public void SetupCommands()
+ {
+ CommandID commandSol = new CommandID(GuidList.guidDiffCmdSet, (int)PkgCmdIDList.cmdSolutionSettings);
+ OleMenuCommand menuCommandSol = new OleMenuCommand((s, e) => ApplySolutionSettings(), commandSol);
+ menuCommandSol.BeforeQueryStatus += SolutionBeforeQueryStatus;
+ _mcs.AddCommand(menuCommandSol);
+
+ ProjectItemsEvents projectEvents = ((Events2)_dte.Events).ProjectItemsEvents;
+ projectEvents.ItemRemoved += ItemRemoved;
+ projectEvents.ItemRenamed += ItemRenamed;
+
+ SolutionEvents solutionEvents = ((Events2)_dte.Events).SolutionEvents;
+ solutionEvents.ProjectRemoved += ProjectRemoved;
+ }
+
+ private void SolutionBeforeQueryStatus(object sender, System.EventArgs e)
+ {
+ OleMenuCommand menuCommand = sender as OleMenuCommand;
+ bool settingsExist = Settings.SolutionSettingsExist;
+
+ menuCommand.Enabled = !settingsExist;
+ }
+
+ private void ApplySolutionSettings()
+ {
+ Settings.CreateSolutionSettings();
+ }
+
+ private void ItemRenamed(ProjectItem ProjectItem, string OldName)
+ {
+ if (OldName.EndsWith(Settings._fileName) || ProjectItem.Name == Settings._fileName)
+ Settings.UpdateCache();
+ }
+
+ private void ItemRemoved(ProjectItem ProjectItem)
+ {
+ if (ProjectItem.Name == Settings._fileName &&
+ ProjectItem.ContainingProject != null &&
+ ProjectItem.ContainingProject.Name == Settings._solutionFolder)
+ {
+ DeleteSolutionSettings();
+ }
+ }
+
+ private void ProjectRemoved(Project project)
+ {
+ if (project.Name == Settings._solutionFolder)
+ {
+ DeleteSolutionSettings();
+ }
+ }
+
+ private static void DeleteSolutionSettings()
+ {
+ string file = Settings.GetSolutionFilePath();
+
+ if (File.Exists(file))
+ {
+ string text = "The Web Essentials setting file still exist in the solution folder.\r\n\r\nDo you want to delete it?";
+ DialogResult result = MessageBox.Show(text, "Web Essentials", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
+
+ if (result == DialogResult.Yes)
+ {
+ File.Delete(file);
+ Settings.UpdateCache();
+ Settings.UpdateStatusBar("applied");
+ }
+ else
+ {
+ Settings.UpdateStatusBar("still applies. The settings file still exist in the solution folder.");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/MenuItems/SolutionColors.cs b/EditorExtensions/MenuItems/SolutionColors.cs
new file mode 100644
index 000000000..769bbbc19
--- /dev/null
+++ b/EditorExtensions/MenuItems/SolutionColors.cs
@@ -0,0 +1,39 @@
+using EnvDTE80;
+using Microsoft.VisualStudio.Shell;
+using System.ComponentModel.Design;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class SolutionColorsMenu
+ {
+ private DTE2 _dte;
+ private OleMenuCommandService _mcs;
+
+ public SolutionColorsMenu(DTE2 dte, OleMenuCommandService mcs)
+ {
+ _dte = dte;
+ _mcs = mcs;
+ }
+
+ public void SetupCommands()
+ {
+ CommandID commandSol = new CommandID(GuidList.guidDiffCmdSet, (int)PkgCmdIDList.cmdSolutionColors);
+ OleMenuCommand menuCommandSol = new OleMenuCommand((s, e) => ApplySolutionSettings(), commandSol);
+ menuCommandSol.BeforeQueryStatus += SolutionBeforeQueryStatus;
+ _mcs.AddCommand(menuCommandSol);
+ }
+
+ private void SolutionBeforeQueryStatus(object sender, System.EventArgs e)
+ {
+ OleMenuCommand menuCommand = sender as OleMenuCommand;
+ bool settingsExist = XmlColorPaletteProvider.SolutionColorsExist;
+
+ menuCommand.Enabled = !settingsExist;
+ }
+
+ private void ApplySolutionSettings()
+ {
+ XmlColorPaletteProvider.CreateSolutionColors();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/MenuItems/Transform.cs b/EditorExtensions/MenuItems/Transform.cs
new file mode 100644
index 000000000..3212ce1f9
--- /dev/null
+++ b/EditorExtensions/MenuItems/Transform.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Security.Cryptography;
+using System.Linq;
+using System.ComponentModel.Design;
+using System.Web;
+using EnvDTE;
+using EnvDTE80;
+using Microsoft.VisualStudio.Shell;
+using System.Globalization;
+using System.Text;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class TransformMenu
+ {
+ private DTE2 _dte;
+ private OleMenuCommandService _mcs;
+ private delegate string Replacement(string original);
+
+ public TransformMenu(DTE2 dte, OleMenuCommandService mcs)
+ {
+ _dte = dte;
+ _mcs = mcs;
+ }
+
+ public void SetupCommands()
+ {
+ SetupCommand(PkgCmdIDList.upperCaseTransform, new Replacement(x => x.ToUpperInvariant()));
+ SetupCommand(PkgCmdIDList.lowerCaseTransform, new Replacement(x => x.ToLowerInvariant()));
+ SetupCommand(PkgCmdIDList.titleCaseTransform, new Replacement(x => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(x)));
+ SetupCommand(PkgCmdIDList.reverseTransform, new Replacement(x => new string(x.Reverse().ToArray())));
+ SetupCommand(PkgCmdIDList.normalizeTransform, new Replacement(x => RemoveDiacritics(x)));
+ SetupCommand(PkgCmdIDList.md5Transform, new Replacement(x => Hash(x, new MD5CryptoServiceProvider())));
+ SetupCommand(PkgCmdIDList.sha1Transform, new Replacement(x => Hash(x, new SHA1CryptoServiceProvider())));
+ SetupCommand(PkgCmdIDList.sha256Transform, new Replacement(x => Hash(x, new SHA256CryptoServiceProvider())));
+ SetupCommand(PkgCmdIDList.sha384Transform, new Replacement(x => Hash(x, new SHA384CryptoServiceProvider())));
+ SetupCommand(PkgCmdIDList.sha512Transform, new Replacement(x => Hash(x, new SHA512CryptoServiceProvider())));
+ }
+
+ public static string RemoveDiacritics(string s)
+ {
+ string stFormD = s.Normalize(NormalizationForm.FormD);
+ StringBuilder sb = new StringBuilder();
+
+ for (int ich = 0; ich < stFormD.Length; ich++)
+ {
+ UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(stFormD[ich]);
+ if (uc != UnicodeCategory.NonSpacingMark)
+ {
+ sb.Append(stFormD[ich]);
+ }
+ }
+
+ return (sb.ToString().Normalize(NormalizationForm.FormC));
+ }
+
+ private static string Hash(string original, HashAlgorithm algorithm)
+ {
+ byte[] hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(original));
+ StringBuilder sb = new StringBuilder();
+
+ foreach (byte b in hash)
+ {
+ sb.Append(b.ToString("x2").ToLowerInvariant());
+ }
+
+ return sb.ToString();
+ }
+
+ private void SetupCommand(uint command, Replacement callback)
+ {
+ CommandID commandId = new CommandID(GuidList.guidEditorExtensionsCmdSet, (int)command);
+ OleMenuCommand menuCommand = new OleMenuCommand((s, e) => Replace(callback), commandId);
+
+ menuCommand.BeforeQueryStatus += (s, e) =>
+ {
+ string selection = GetTextDocument().Selection.Text;
+ menuCommand.Enabled = selection.Length > 0 && callback(selection) != selection;
+ };
+
+ _mcs.AddCommand(menuCommand);
+ }
+
+ private TextDocument GetTextDocument()
+ {
+ return _dte.ActiveDocument.Object("TextDocument") as TextDocument;
+ }
+
+ private void Replace(Replacement callback)
+ {
+ TextDocument document = GetTextDocument();
+ string replacement = callback(document.Selection.Text);
+
+ _dte.UndoContext.Open(callback.Method.Name);
+ document.Selection.Insert(replacement, 0);
+ _dte.UndoContext.Close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/NavigateTo/GoToLineProviderFactory.cs b/EditorExtensions/NavigateTo/GoToLineProviderFactory.cs
new file mode 100644
index 000000000..006d903dd
--- /dev/null
+++ b/EditorExtensions/NavigateTo/GoToLineProviderFactory.cs
@@ -0,0 +1,189 @@
+using Microsoft.CSS.Core;
+using Microsoft.VisualStudio.Language.NavigateTo.Interfaces;
+using Microsoft.VisualStudio.PlatformUI;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Operations;
+using Microsoft.VisualStudio.Text.Outlining;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Threading;
+
+namespace MadsKristensen.EditorExtensions
+{
+ [Export(typeof(INavigateToItemProviderFactory))]
+ internal sealed class GoToLineProviderFactory : INavigateToItemProviderFactory, INavigateToItemDisplayFactory
+ {
+ [Import]
+ internal IEditorOperationsFactoryService EditorOperationsFactoryService = null;
+
+ [Import]
+ internal IOutliningManagerService OutliningManagerService = null;
+
+ public bool TryCreateNavigateToItemProvider(IServiceProvider serviceProvider, out INavigateToItemProvider provider)
+ {
+ provider = new GoToLineProvider(this);
+ return true;
+ }
+
+ public INavigateToItemDisplay CreateItemDisplay(NavigateToItem item)
+ {
+ return item.Tag as INavigateToItemDisplay;
+ }
+ }
+
+ internal sealed class GoToLineProvider : DisposableObject, INavigateToItemProvider
+ {
+ private readonly GoToLineProviderFactory _owner;
+
+ public GoToLineProvider(GoToLineProviderFactory owner)
+ {
+ _owner = owner;
+ }
+
+ public void StartSearch(INavigateToCallback callback, string searchValue)
+ {
+ CssParser parser = new CssParser();
+ var state = new Tuple(parser, searchValue, callback);
+
+ System.Threading.ThreadPool.QueueUserWorkItem(DoWork, state);
+ }
+
+ public void DoWork(object state)
+ {
+ var tuple = (Tuple)state;
+ var parser = tuple.Item1;
+ var searchValue = tuple.Item2;
+ var callback = tuple.Item3;
+
+ try
+ {
+ IEnumerable files = GetFiles();
+
+ Parallel.For(0, files.Count(), i =>
+ {
+ string file = files.ElementAt(i);
+
+ IEnumerable items = GetItems(file, parser, searchValue);
+
+ foreach (ParseItem sel in items)
+ {
+ callback.AddItem(new NavigateToItem(searchValue, NavigateToItemKind.Field, null, searchValue, new GoToLineTag(sel, file), MatchKind.Exact, _owner));
+ }
+
+ callback.ReportProgress(i, files.Count());
+ });
+ }
+ catch { }
+ finally
+ {
+ callback.Done();
+ }
+ }
+
+ public void StopSearch()
+ {
+ }
+
+ private IEnumerable GetItems(string file, CssParser parser, string searchValue)
+ {
+ StyleSheet ss = parser.Parse(File.ReadAllText(file), true);
+
+ var visitorClass = new CssItemCollector(true);
+ ss.Accept(visitorClass);
+
+ var classes = from c in visitorClass.Items
+ where c.Text.Contains(searchValue)
+ select c;
+
+ var visitorIDs = new CssItemCollector(true);
+ ss.Accept(visitorIDs);
+
+ var ids = from c in visitorIDs.Items
+ where c.Text.Contains(searchValue)
+ select c;
+
+ List list = new List();
+ list.AddRange(classes);
+ list.AddRange(ids);
+
+ return list;
+ }
+
+ private IEnumerable GetFiles()
+ {
+ string[] files = Directory.GetFiles(ProjectHelpers.GetRootFolder(), "*.css", SearchOption.AllDirectories);
+
+ foreach (string file in files)
+ {
+ if (!file.Contains(".min.") && !file.Contains(".bundle."))
+ yield return file;
+ }
+ }
+ }
+
+ internal class GoToLineTag : INavigateToItemDisplay
+ {
+ private ParseItem _selector;
+ private string _file;
+
+ public GoToLineTag(ParseItem selector, string file)
+ {
+ _selector = selector;
+ _file = file;
+ }
+
+ public string AdditionalInformation
+ {
+ get
+ {
+ return "CSS selector - " + Path.GetFileName(_file);
+ }
+ }
+
+ public string Description
+ {
+ get
+ {
+ return _selector.Text;
+ }
+ }
+
+ public System.Collections.ObjectModel.ReadOnlyCollection DescriptionItems
+ {
+ get { return null; }
+ }
+
+ public System.Drawing.Icon Glyph
+ {
+ get { return null; }
+ }
+
+ public string Name
+ {
+ get { return _selector.FindType().Text; }
+ }
+
+ public void NavigateTo()
+ {
+ EditorExtensionsPackage.DTE.ItemOperations.OpenFile(_file);
+
+ Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
+ {
+ var view = ProjectHelpers.GetCurentTextView();
+ var textBuffer = ProjectHelpers.GetCurentTextBuffer();
+ var span = new SnapshotSpan(textBuffer.CurrentSnapshot, _selector.Start, _selector.Length);
+ var point = new SnapshotPoint(textBuffer.CurrentSnapshot, _selector.Start + _selector.Length);
+
+ view.ViewScroller.EnsureSpanVisible(span);
+ view.Caret.MoveTo(point);
+ view.Selection.Select(span, false);
+
+
+ }), DispatcherPriority.ApplicationIdle, null);
+ }
+ }
+}
diff --git a/EditorExtensions/Options/CoffeeScript.cs b/EditorExtensions/Options/CoffeeScript.cs
new file mode 100644
index 000000000..1974c3575
--- /dev/null
+++ b/EditorExtensions/Options/CoffeeScript.cs
@@ -0,0 +1,72 @@
+using Microsoft.VisualStudio.Shell;
+using System.ComponentModel;
+
+namespace MadsKristensen.EditorExtensions
+{
+ class CoffeeScriptOptions : DialogPage
+ {
+ public CoffeeScriptOptions()
+ {
+ Settings.Updated += delegate { LoadSettingsFromStorage(); };
+ }
+
+ public override void SaveSettingsToStorage()
+ {
+ Settings.SetValue(WESettings.Keys.GenerateJsFileFromCoffeeScript, GenerateJsFileFromCoffeeScript);
+ Settings.SetValue(WESettings.Keys.ShowCoffeeScriptPreviewWindow, ShowCoffeeScriptPreviewWindow);
+ Settings.SetValue(WESettings.Keys.WrapCoffeeScriptClosure, WrapCoffeeScriptClosure);
+ Settings.SetValue(WESettings.Keys.CoffeeScriptMinify, CoffeeScriptMinify);
+ Settings.SetValue(WESettings.Keys.EnableIcedCoffeeScript, EnableIcedCoffeeScript);
+ Settings.SetValue(WESettings.Keys.CoffeeScriptCompileToFolder, CoffeeScriptCompileToFolder);
+ Settings.SetValue(WESettings.Keys.CoffeeScriptCompileOnBuild, CoffeeScriptCompileOnBuild);
+
+ Settings.Save();
+ }
+
+ public override void LoadSettingsFromStorage()
+ {
+ GenerateJsFileFromCoffeeScript = WESettings.GetBoolean(WESettings.Keys.GenerateJsFileFromCoffeeScript);
+ ShowCoffeeScriptPreviewWindow = WESettings.GetBoolean(WESettings.Keys.ShowCoffeeScriptPreviewWindow);
+ WrapCoffeeScriptClosure = WESettings.GetBoolean(WESettings.Keys.WrapCoffeeScriptClosure);
+ CoffeeScriptMinify = WESettings.GetBoolean(WESettings.Keys.CoffeeScriptMinify);
+ EnableIcedCoffeeScript = WESettings.GetBoolean(WESettings.Keys.EnableIcedCoffeeScript);
+ CoffeeScriptCompileToFolder = WESettings.GetBoolean(WESettings.Keys.CoffeeScriptCompileToFolder);
+ CoffeeScriptCompileOnBuild = WESettings.GetBoolean(WESettings.Keys.CoffeeScriptCompileOnBuild);
+ }
+
+ [LocDisplayName("Generate JavaScript file on save")]
+ [Description("Generate JavaScript file when CoffeeScript file is saved")]
+ [Category("CoffeeScript")]
+ public bool GenerateJsFileFromCoffeeScript { get; set; }
+
+ [LocDisplayName("Show preview window")]
+ [Description("Show the preview window when editing a CoffeeScript file.")]
+ [Category("CoffeeScript")]
+ public bool ShowCoffeeScriptPreviewWindow { get; set; }
+
+ [LocDisplayName("Wrap generated JavaScript")]
+ [Description("Wrap the generated JavaScript in an anonymous function.")]
+ [Category("CoffeeScript")]
+ public bool WrapCoffeeScriptClosure { get; set; }
+
+ [LocDisplayName("Enable Iced CoffeeScript")]
+ [Description("Switches to use the Iced CoffeeScript compiler.")]
+ [Category("CoffeeScript")]
+ public bool EnableIcedCoffeeScript { get; set; }
+
+ [LocDisplayName("Minify generated JavaScript")]
+ [Description("Creates a minified version of the compiled JavaScript file (file.min.js)")]
+ [Category("CoffeeScript")]
+ public bool CoffeeScriptMinify { get; set; }
+
+ [LocDisplayName("Compile to 'js' folder")]
+ [Description("Compiles all CoffeeScript files into a folder called 'js' in the same directory as the .coffee file")]
+ [Category("CoffeeScript")]
+ public bool CoffeeScriptCompileToFolder { get; set; }
+
+ [LocDisplayName("Compile on build")]
+ [Description("Compiles all CoffeeScript files in the project that has a corresponding .js file.")]
+ [Category("CoffeeScript")]
+ public bool CoffeeScriptCompileOnBuild { get; set; }
+ }
+}
diff --git a/EditorExtensions/Options/Css.cs b/EditorExtensions/Options/Css.cs
new file mode 100644
index 000000000..eaa25b74e
--- /dev/null
+++ b/EditorExtensions/Options/Css.cs
@@ -0,0 +1,135 @@
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Schemas;
+using Microsoft.VisualStudio.Shell;
+using System.ComponentModel;
+
+namespace MadsKristensen.EditorExtensions
+{
+ class CssOptions : DialogPage
+ {
+ public CssOptions()
+ {
+ Settings.Updated += delegate { LoadSettingsFromStorage(); };
+ }
+
+ public override void SaveSettingsToStorage()
+ {
+ Settings.SetValue(WESettings.Keys.EnableCssSelectorHighligting, EnableCssSelectorHighligting);
+ Settings.SetValue(WESettings.Keys.AutoCloseCurlyBraces, AutoCloseCurlyBraces);
+ Settings.SetValue(WESettings.Keys.EnableCssMinification, EnableCssMinification);
+ Settings.SetValue(WESettings.Keys.ValidateStarSelector, ValidateStarSelector);
+ Settings.SetValue(WESettings.Keys.ValidateOverQualifiedSelector, ValidateOverQualifiedSelector);
+ Settings.SetValue(WESettings.Keys.CssErrorLocation, (int)CssErrorLocation);
+ Settings.SetValue(WESettings.Keys.OnlyW3cAllowed, OnlyW3cAllowed);
+ Settings.SetValue(WESettings.Keys.SyncVendorValues, SyncVendorValues);
+ Settings.SetValue(WESettings.Keys.ShowInitialInherit, ShowInitialInherit);
+ Settings.SetValue(WESettings.Keys.ShowUnsupported, ShowUnsupported);
+ Settings.SetValue(WESettings.Keys.ShowBrowserTooltip, ShowBrowserTooltip);
+ Settings.SetValue(WESettings.Keys.ValidateZeroUnit, ValidateZeroUnit);
+ Settings.SetValue(WESettings.Keys.ValidateVendorSpecifics, ValidateVendorSpecifics);
+ Settings.SetValue(WESettings.Keys.EnableSpeedTyping, EnableSpeedTyping);
+
+ OnChanged();
+ Settings.Save();
+ }
+
+ public override void LoadSettingsFromStorage()
+ {
+ EnableCssSelectorHighligting = WESettings.GetBoolean(WESettings.Keys.EnableCssSelectorHighligting);
+ AutoCloseCurlyBraces = WESettings.GetBoolean(WESettings.Keys.AutoCloseCurlyBraces);
+ EnableCssMinification = WESettings.GetBoolean(WESettings.Keys.EnableCssMinification);
+ ValidateStarSelector = WESettings.GetBoolean(WESettings.Keys.ValidateStarSelector);
+ ValidateOverQualifiedSelector = WESettings.GetBoolean(WESettings.Keys.ValidateOverQualifiedSelector);
+ CssErrorLocation = (WESettings.Keys.ErrorLocation)WESettings.GetInt(WESettings.Keys.CssErrorLocation);
+ OnlyW3cAllowed = WESettings.GetBoolean(WESettings.Keys.OnlyW3cAllowed);
+ SyncVendorValues = WESettings.GetBoolean(WESettings.Keys.SyncVendorValues);
+ ShowInitialInherit = WESettings.GetBoolean(WESettings.Keys.ShowInitialInherit);
+ ShowUnsupported = WESettings.GetBoolean(WESettings.Keys.ShowUnsupported);
+ ValidateEmbedImages = WESettings.GetBoolean(WESettings.Keys.ValidateEmbedImages);
+ ShowBrowserTooltip = WESettings.GetBoolean(WESettings.Keys.ShowBrowserTooltip);
+ ValidateZeroUnit = WESettings.GetBoolean(WESettings.Keys.ValidateZeroUnit);
+ ValidateVendorSpecifics = WESettings.GetBoolean(WESettings.Keys.ValidateVendorSpecifics);
+ EnableSpeedTyping = WESettings.GetBoolean(WESettings.Keys.EnableSpeedTyping);
+ }
+
+ protected void OnChanged()
+ {
+ CssSchemaManager.SchemaManager.ReloadSchemas();
+ }
+
+ [LocDisplayName("Enable selector highlighting")]
+ [Description("Highlight matching simple selectors when cursor position changes")]
+ [Category("Misc")]
+ public bool EnableCssSelectorHighligting { get; set; }
+
+ [LocDisplayName("Auto-close curly braces")]
+ [Description("when a open curly brace is typed, the closing curly is inserted automatically and type-through is enabled.")]
+ [Category("Misc")]
+ public bool AutoCloseCurlyBraces { get; set; }
+
+ [LocDisplayName("Minify CSS files on save")]
+ [Description("When a .css file (foo.css) is saved and a minified version (foo.min.css) exist, the minified file will be updated. Right-click any .css file to generate .min.css file")]
+ [Category("Misc")]
+ public bool EnableCssMinification { get; set; }
+
+ [LocDisplayName("Enable Speed Typing")]
+ [Description("Speed Typing makes it easier to write CSS by eliminating the need for typing curlies, colons and semi-colons.")]
+ [Category("Misc")]
+ public bool EnableSpeedTyping { get; set; }
+
+ [LocDisplayName("Disallow universal selector")]
+ [Description("Disallow the universal, also known as the star selector")]
+ [Category("Performance")]
+ public bool ValidateStarSelector { get; set; }
+
+ [LocDisplayName("Disallow over qualified ID selector")]
+ [Description("Disallow the use of over qualifed ID selectors.")]
+ [Category("Performance")]
+ public bool ValidateOverQualifiedSelector { get; set; }
+
+ [LocDisplayName("Small images should be inlined")]
+ [Description("Small images should be base64 encoded and embedded directly into the stylesheet as dataURIs.")]
+ [Category("Performance")]
+ public bool ValidateEmbedImages { get; set; }
+
+ [LocDisplayName("Validation location")]
+ [Description("Controls where errors are located. To use the 'Errors' output window, select 'Warnings' and change the Visual Studio CSS settings to use 'Errors'")]
+ [Category("Validation")]
+ public WESettings.Keys.ErrorLocation CssErrorLocation { get; set; }
+
+ [LocDisplayName("Only allow W3C values")]
+ [Description("Ensures that the stylesheet only uses properties, @-directives, pseudos and values defined by the W3C.")]
+ [Category("Validation")]
+ public bool OnlyW3cAllowed { get; set; }
+
+ [LocDisplayName("Validate vendor specifics")]
+ [Description("Validates vendor specific properties, psuedos and @-directives.")]
+ [Category("Validation")]
+ public bool ValidateVendorSpecifics { get; set; }
+
+ [LocDisplayName("Sync vendor specific values")]
+ [Description("Syncronizes vendor specific property values when the standard property is being modified")]
+ [Category("Intellisense")]
+ public bool SyncVendorValues { get; set; }
+
+ [LocDisplayName("Show initial/inherit")]
+ [Description("Shows or hides the global property values 'initial' and 'inherit'. They are still valid to use.")]
+ [Category("Intellisense")]
+ public bool ShowInitialInherit { get; set; }
+
+ [LocDisplayName("Show unsupported")]
+ [Description("Shows the property names, values and pseudos that aren't supported by any browser yet.")]
+ [Category("Intellisense")]
+ public bool ShowUnsupported { get; set; }
+
+ [LocDisplayName("Show browser support")]
+ [Description("Shows the browser support when the mouse is hovering over any CSS property.")]
+ [Category("Intellisense")]
+ public bool ShowBrowserTooltip { get; set; }
+
+ [LocDisplayName("Disallow units for 0 values")]
+ [Description("The value of 0 works without specifying units in all situations where numbers with units or percentages are allowed")]
+ [Category("Performance")]
+ public bool ValidateZeroUnit { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/EditorExtensions/Options/General.cs b/EditorExtensions/Options/General.cs
new file mode 100644
index 000000000..738588d4d
--- /dev/null
+++ b/EditorExtensions/Options/General.cs
@@ -0,0 +1,45 @@
+using Microsoft.VisualStudio.Shell;
+using System.ComponentModel;
+
+namespace MadsKristensen.EditorExtensions
+{
+ class GeneralOptions : DialogPage
+ {
+ public GeneralOptions()
+ {
+ Settings.Updated += delegate { LoadSettingsFromStorage(); };
+ }
+
+ public override void SaveSettingsToStorage()
+ {
+ Settings.SetValue(WESettings.Keys.EnableMustache, EnableMustache);
+ Settings.SetValue(WESettings.Keys.EnableHtmlZenCoding, EnableHtmlZenCoding);
+ Settings.SetValue(WESettings.Keys.KeepImportantComments, KeepImportantComments);
+
+ Settings.Save();
+ }
+
+ public override void LoadSettingsFromStorage()
+ {
+ EnableMustache = WESettings.GetBoolean(WESettings.Keys.EnableMustache);
+ EnableHtmlZenCoding = WESettings.GetBoolean(WESettings.Keys.EnableHtmlZenCoding);
+ KeepImportantComments = WESettings.GetBoolean(WESettings.Keys.KeepImportantComments);
+ }
+
+ // MISC
+ [LocDisplayName("Enable Mustache/Handlebars")]
+ [Description("Enable colorization Mustache/Handlebars syntax in the HTML editor")]
+ [Category("Misc")]
+ public bool EnableMustache { get; set; }
+
+ [LocDisplayName("Enable HTML ZenCoding")]
+ [Description("Enables ZenCoding in the HTML editor")]
+ [Category("Misc")]
+ public bool EnableHtmlZenCoding { get; set; }
+
+ [LocDisplayName("Keep important comments")]
+ [Description("Don't strip important comments when minifying JS and CSS. Important comments follows this pattern: /*! text */")]
+ [Category("Minification")]
+ public bool KeepImportantComments { get; set; }
+ }
+}
diff --git a/EditorExtensions/Options/JavaScript.cs b/EditorExtensions/Options/JavaScript.cs
new file mode 100644
index 000000000..f2c61f47b
--- /dev/null
+++ b/EditorExtensions/Options/JavaScript.cs
@@ -0,0 +1,59 @@
+using Microsoft.VisualStudio.Shell;
+using System.ComponentModel;
+
+namespace MadsKristensen.EditorExtensions
+{
+ class JavaScriptOptions : DialogPage
+ {
+ public JavaScriptOptions()
+ {
+ Settings.Updated += delegate { LoadSettingsFromStorage(); };
+ }
+
+ public override void SaveSettingsToStorage()
+ {
+ Settings.SetValue(WESettings.Keys.EnableJavascriptRegions, EnableJavascriptRegions);
+ Settings.SetValue(WESettings.Keys.EnableJsMinification, EnableJsMinification);
+ Settings.SetValue(WESettings.Keys.GenerateJavaScriptSourceMaps, GenerateJavaScriptSourceMaps);
+ Settings.SetValue(WESettings.Keys.JavaScriptAutoCloseBraces, JavaScriptAutoCloseBraces);
+ Settings.SetValue(WESettings.Keys.JavaScriptOutlining, JavaScriptOutlining);
+
+ Settings.Save();
+ }
+
+ public override void LoadSettingsFromStorage()
+ {
+ EnableJavascriptRegions = WESettings.GetBoolean(WESettings.Keys.EnableJavascriptRegions);
+ EnableJsMinification = WESettings.GetBoolean(WESettings.Keys.EnableJsMinification);
+ GenerateJavaScriptSourceMaps = WESettings.GetBoolean(WESettings.Keys.GenerateJavaScriptSourceMaps);
+ JavaScriptAutoCloseBraces = WESettings.GetBoolean(WESettings.Keys.JavaScriptAutoCloseBraces);
+ JavaScriptOutlining = WESettings.GetBoolean(WESettings.Keys.JavaScriptOutlining);
+ }
+
+
+ [LocDisplayName("Enable JavaScript regions")]
+ [Description("Enable regions using this syntax: '//#region Name' followed by '//#endregion'")]
+ [Category("JavaScript")]
+ public bool EnableJavascriptRegions { get; set; }
+
+ [LocDisplayName("Minify JavaScript files on save")]
+ [Description("When a .js file (foo.js) is saved and a minified version (foo.min.js) exist, the minified file will be updated. Right-click any .js file to generate .min.js file")]
+ [Category("JavaScript")]
+ public bool EnableJsMinification { get; set; }
+
+ [LocDisplayName("Generate source maps (.map)")]
+ [Description("When minification is enabled, a source map file (*.min.js.map) is generated.")]
+ [Category("JavaScript")]
+ public bool GenerateJavaScriptSourceMaps { get; set; }
+
+ [LocDisplayName("Auto-close braces")]
+ [Description("Automatically inserts closing braces as provisional text. Braces are: ], ) and }")]
+ [Category("JavaScript")]
+ public bool JavaScriptAutoCloseBraces { get; set; }
+
+ [LocDisplayName("Enable outlining/folding")]
+ [Description("Enables outlining for any non-function structures. Enabling can collide with other extensions.")]
+ [Category("JavaScript")]
+ public bool JavaScriptOutlining { get; set; }
+ }
+}
diff --git a/EditorExtensions/Options/JsHint.cs b/EditorExtensions/Options/JsHint.cs
new file mode 100644
index 000000000..edad031f2
--- /dev/null
+++ b/EditorExtensions/Options/JsHint.cs
@@ -0,0 +1,466 @@
+using Microsoft.VisualStudio.Shell;
+using System;
+using System.ComponentModel;
+
+namespace MadsKristensen.EditorExtensions
+{
+ class JsHintOptions : DialogPage
+ {
+ public JsHintOptions()
+ {
+ Settings.Updated += delegate { LoadSettingsFromStorage(); };
+ }
+
+ public override void SaveSettingsToStorage()
+ {
+ Settings.SetValue(WESettings.Keys.JsHint_maxerr, JsHint_maxerr);
+ Settings.SetValue(WESettings.Keys.RunJsHintOnBuild, RunJsHintOnBuild);
+ Settings.SetValue(WESettings.Keys.EnableJsHint, EnableJsHint);
+ Settings.SetValue(WESettings.Keys.JsHintErrorLocation, (int)ErrorLocation);
+
+ Settings.SetValue(WESettings.Keys.JsHint_bitwise, JsHint_bitwise);
+ Settings.SetValue(WESettings.Keys.JsHint_camelcase, JsHint_camelcase);
+ Settings.SetValue(WESettings.Keys.JsHint_curly, JsHint_curly);
+ Settings.SetValue(WESettings.Keys.JsHint_eqeqeq, JsHint_eqeqeq);
+ Settings.SetValue(WESettings.Keys.JsHint_forin, JsHint_forin);
+ Settings.SetValue(WESettings.Keys.JsHint_immed, JsHint_immed);
+ Settings.SetValue(WESettings.Keys.JsHint_indent, JsHint_indent);
+ Settings.SetValue(WESettings.Keys.JsHint_latedef, JsHint_latedef);
+ Settings.SetValue(WESettings.Keys.JsHint_newcap, JsHint_newcap);
+ Settings.SetValue(WESettings.Keys.JsHint_noarg, JsHint_noarg);
+ Settings.SetValue(WESettings.Keys.JsHint_noempty, JsHint_noempty);
+ Settings.SetValue(WESettings.Keys.JsHint_nonew, JsHint_nonew);
+ Settings.SetValue(WESettings.Keys.JsHint_plusplus, JsHint_plusplus);
+ Settings.SetValue(WESettings.Keys.JsHint_quotmark, JsHint_quotmark);
+ Settings.SetValue(WESettings.Keys.JsHint_regexp, JsHint_regexp);
+ Settings.SetValue(WESettings.Keys.JsHint_undef, JsHint_undef);
+ Settings.SetValue(WESettings.Keys.JsHint_unused, JsHint_unused);
+ Settings.SetValue(WESettings.Keys.JsHint_strict, JsHint_strict);
+ Settings.SetValue(WESettings.Keys.JsHint_trailing, JsHint_trailing);
+
+ Settings.SetValue(WESettings.Keys.JsHint_asi, JsHint_asi);
+ Settings.SetValue(WESettings.Keys.JsHint_boss, JsHint_boss);
+ Settings.SetValue(WESettings.Keys.JsHint_debug, JsHint_debug);
+ Settings.SetValue(WESettings.Keys.JsHint_eqnull, JsHint_eqnull);
+ Settings.SetValue(WESettings.Keys.JsHint_es5, JsHint_es5);
+ Settings.SetValue(WESettings.Keys.JsHint_esnext, JsHint_esnext);
+ Settings.SetValue(WESettings.Keys.JsHint_evil, JsHint_evil);
+ Settings.SetValue(WESettings.Keys.JsHint_expr, JsHint_expr);
+ Settings.SetValue(WESettings.Keys.JsHint_funcscope, JsHint_funcscope);
+ Settings.SetValue(WESettings.Keys.JsHint_globalstrict, JsHint_globalstrict);
+ Settings.SetValue(WESettings.Keys.JsHint_iterator, JsHint_iterator);
+ Settings.SetValue(WESettings.Keys.JsHint_lastsemic, JsHint_lastsemic);
+ Settings.SetValue(WESettings.Keys.JsHint_laxbreak, JsHint_laxbreak);
+ Settings.SetValue(WESettings.Keys.JsHint_laxcomma, JsHint_laxcomma);
+ Settings.SetValue(WESettings.Keys.JsHint_loopfunc, JsHint_loopfunc);
+ Settings.SetValue(WESettings.Keys.JsHint_multistr, JsHint_multistr);
+ Settings.SetValue(WESettings.Keys.JsHint_onecase, JsHint_onecase);
+ Settings.SetValue(WESettings.Keys.JsHint_proto, JsHint_proto);
+ Settings.SetValue(WESettings.Keys.JsHint_regexdash, JsHint_regexdash);
+ Settings.SetValue(WESettings.Keys.JsHint_scripturl, JsHint_scripturl);
+ Settings.SetValue(WESettings.Keys.JsHint_smarttabs, JsHint_smarttabs);
+ Settings.SetValue(WESettings.Keys.JsHint_shadow, JsHint_shadow);
+ Settings.SetValue(WESettings.Keys.JsHint_sub, JsHint_sub);
+ Settings.SetValue(WESettings.Keys.JsHint_supernew, JsHint_supernew);
+ Settings.SetValue(WESettings.Keys.JsHint_validthis, JsHint_validthis);
+
+ Settings.SetValue(WESettings.Keys.JsHint_browser, JsHint_browser);
+ Settings.SetValue(WESettings.Keys.JsHint_couch, JsHint_couch);
+ Settings.SetValue(WESettings.Keys.JsHint_devel, JsHint_devel);
+ Settings.SetValue(WESettings.Keys.JsHint_dojo, JsHint_dojo);
+ Settings.SetValue(WESettings.Keys.JsHint_mootools, JsHint_mootools);
+ Settings.SetValue(WESettings.Keys.JsHint_node, JsHint_node);
+ Settings.SetValue(WESettings.Keys.JsHint_nonstandard, JsHint_nonstandard);
+ Settings.SetValue(WESettings.Keys.JsHint_prototypejs, JsHint_prototypejs);
+ Settings.SetValue(WESettings.Keys.JsHint_rhino, JsHint_rhino);
+ Settings.SetValue(WESettings.Keys.JsHint_worker, JsHint_worker);
+ Settings.SetValue(WESettings.Keys.JsHint_wsh, JsHint_wsh);
+
+ OnChanged();
+ Settings.Save();
+ }
+
+ public override void LoadSettingsFromStorage()
+ {
+ EnableJsHint = WESettings.GetBoolean(WESettings.Keys.EnableJsHint);
+ RunJsHintOnBuild = WESettings.GetBoolean(WESettings.Keys.RunJsHintOnBuild);
+ ErrorLocation = (WESettings.Keys.FullErrorLocation)WESettings.GetInt(WESettings.Keys.JsHintErrorLocation);
+ JsHint_maxerr = WESettings.GetInt(WESettings.Keys.JsHint_maxerr);
+
+ JsHint_bitwise = WESettings.GetBoolean(WESettings.Keys.JsHint_bitwise);
+ JsHint_camelcase = WESettings.GetBoolean(WESettings.Keys.JsHint_camelcase);
+ JsHint_curly = WESettings.GetBoolean(WESettings.Keys.JsHint_curly);
+ JsHint_eqeqeq = WESettings.GetBoolean(WESettings.Keys.JsHint_eqeqeq);
+ JsHint_forin = WESettings.GetBoolean(WESettings.Keys.JsHint_forin);
+ JsHint_immed = WESettings.GetBoolean(WESettings.Keys.JsHint_immed);
+ JsHint_indent = WESettings.GetInt(WESettings.Keys.JsHint_indent);
+ JsHint_latedef = WESettings.GetBoolean(WESettings.Keys.JsHint_latedef);
+ JsHint_newcap = WESettings.GetBoolean(WESettings.Keys.JsHint_newcap);
+ JsHint_noarg = WESettings.GetBoolean(WESettings.Keys.JsHint_noarg);
+ JsHint_noempty = WESettings.GetBoolean(WESettings.Keys.JsHint_noempty);
+ JsHint_nonew = WESettings.GetBoolean(WESettings.Keys.JsHint_nonew);
+ JsHint_plusplus = WESettings.GetBoolean(WESettings.Keys.JsHint_plusplus);
+ JsHint_quotmark = WESettings.GetBoolean(WESettings.Keys.JsHint_quotmark);
+ JsHint_regexp = WESettings.GetBoolean(WESettings.Keys.JsHint_regexp);
+ JsHint_undef = WESettings.GetBoolean(WESettings.Keys.JsHint_undef);
+ JsHint_unused = WESettings.GetBoolean(WESettings.Keys.JsHint_unused);
+ JsHint_strict = WESettings.GetBoolean(WESettings.Keys.JsHint_strict);
+ JsHint_trailing = WESettings.GetBoolean(WESettings.Keys.JsHint_trailing);
+
+ JsHint_asi = WESettings.GetBoolean(WESettings.Keys.JsHint_asi);
+ JsHint_boss = WESettings.GetBoolean(WESettings.Keys.JsHint_boss);
+ JsHint_debug = WESettings.GetBoolean(WESettings.Keys.JsHint_debug);
+ JsHint_eqnull = WESettings.GetBoolean(WESettings.Keys.JsHint_eqnull);
+ JsHint_es5 = WESettings.GetBoolean(WESettings.Keys.JsHint_es5);
+ JsHint_esnext = WESettings.GetBoolean(WESettings.Keys.JsHint_esnext);
+ JsHint_evil = WESettings.GetBoolean(WESettings.Keys.JsHint_evil);
+ JsHint_expr = WESettings.GetBoolean(WESettings.Keys.JsHint_expr);
+ JsHint_funcscope = WESettings.GetBoolean(WESettings.Keys.JsHint_funcscope);
+ JsHint_globalstrict = WESettings.GetBoolean(WESettings.Keys.JsHint_globalstrict);
+ JsHint_iterator = WESettings.GetBoolean(WESettings.Keys.JsHint_iterator);
+ JsHint_lastsemic = WESettings.GetBoolean(WESettings.Keys.JsHint_lastsemic);
+ JsHint_laxbreak = WESettings.GetBoolean(WESettings.Keys.JsHint_laxbreak);
+ JsHint_laxcomma = WESettings.GetBoolean(WESettings.Keys.JsHint_laxcomma);
+ JsHint_loopfunc = WESettings.GetBoolean(WESettings.Keys.JsHint_loopfunc);
+ JsHint_multistr = WESettings.GetBoolean(WESettings.Keys.JsHint_multistr);
+ JsHint_onecase = WESettings.GetBoolean(WESettings.Keys.JsHint_onecase);
+ JsHint_proto = WESettings.GetBoolean(WESettings.Keys.JsHint_proto);
+ JsHint_regexdash = WESettings.GetBoolean(WESettings.Keys.JsHint_regexdash);
+ JsHint_scripturl = WESettings.GetBoolean(WESettings.Keys.JsHint_scripturl);
+ JsHint_smarttabs = WESettings.GetBoolean(WESettings.Keys.JsHint_smarttabs);
+ JsHint_shadow = WESettings.GetBoolean(WESettings.Keys.JsHint_shadow);
+ JsHint_sub = WESettings.GetBoolean(WESettings.Keys.JsHint_sub);
+ JsHint_supernew = WESettings.GetBoolean(WESettings.Keys.JsHint_supernew);
+ JsHint_validthis = WESettings.GetBoolean(WESettings.Keys.JsHint_validthis);
+
+ JsHint_browser = WESettings.GetBoolean(WESettings.Keys.JsHint_browser);
+ JsHint_couch = WESettings.GetBoolean(WESettings.Keys.JsHint_couch);
+ JsHint_devel = WESettings.GetBoolean(WESettings.Keys.JsHint_devel);
+ JsHint_dojo = WESettings.GetBoolean(WESettings.Keys.JsHint_dojo);
+ JsHint_jquery = WESettings.GetBoolean(WESettings.Keys.JsHint_jquery);
+ JsHint_mootools = WESettings.GetBoolean(WESettings.Keys.JsHint_mootools);
+ JsHint_node = WESettings.GetBoolean(WESettings.Keys.JsHint_node);
+ JsHint_nonstandard = WESettings.GetBoolean(WESettings.Keys.JsHint_nonstandard);
+ JsHint_prototypejs = WESettings.GetBoolean(WESettings.Keys.JsHint_prototypejs);
+ JsHint_rhino = WESettings.GetBoolean(WESettings.Keys.JsHint_rhino);
+ JsHint_worker = WESettings.GetBoolean(WESettings.Keys.JsHint_worker);
+ JsHint_wsh = WESettings.GetBoolean(WESettings.Keys.JsHint_wsh);
+ }
+
+ public static event EventHandler Changed;
+
+ protected void OnChanged()
+ {
+ if (Changed != null)
+ {
+ Changed(this, EventArgs.Empty);
+ }
+ }
+
+ [LocDisplayName("Maximum number of errors")]
+ [Description("This option suppresses warnings about mixed tabs and spaces when the latter are used for alignmnent only.")]
+ [Category("Common")]
+ public int JsHint_maxerr { get; set; }
+
+ [LocDisplayName("Enable JSHint")]
+ [Description("Runs JSHint in any open .js file when saved.")]
+ [Category("Common")]
+ public bool EnableJsHint { get; set; }
+
+ [LocDisplayName("Run on build")]
+ [Description("Runs JSHint on all .js files in the active project on build")]
+ [Category("Common")]
+ public bool RunJsHintOnBuild { get; set; }
+
+ [LocDisplayName("Error location")]
+ [Description("Determins where to output the JSHint errors")]
+ [Category("Common")]
+ public WESettings.Keys.FullErrorLocation ErrorLocation { get; set; }
+
+ // Enforcing Options
+
+ [LocDisplayName("Disallow bitwise operators")]
+ [Description("[bitwise] This option prohibits the use of bitwise operators such as ^ (XOR), | (OR) and others. Bitwise operators are very rare in JavaScript programs and very often & is simply a mistyped &&&&.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_bitwise { get; set; }
+
+ [LocDisplayName("Require camelcasing")]
+ [Description("[camelcase] This option allows you to force all variable names to use either camelCase style or UPPER_CASE with underscores.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_camelcase { get; set; }
+
+ [LocDisplayName("Enforce curly braces")]
+ [Description("[curly] This option requires you to always put curly braces around blocks in loops and conditionals. JavaScript allows you to omit curly braces when the block consists of only one statement.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_curly { get; set; }
+
+ [LocDisplayName("Disallow == and !=")]
+ [Description("[eqeqeq] This options prohibits the use of == and != in favor of === and !==.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_eqeqeq { get; set; }
+
+ [LocDisplayName("Filter for-in loops")]
+ [Description("[forin] This option requires all for in loops to filter object's items.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_forin { get; set; }
+
+ [LocDisplayName("Enforce invocation parentheses")]
+ [Description("[immed] This option prohibits the use of immediate function invocations without wrapping them in parentheses.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_immed { get; set; }
+
+ [LocDisplayName("Enforce indent size (-1 to disable)")]
+ [Description("[indent] This option enforces specific tab width for your code.")]
+ [Category("Enforcing Options")]
+ public int JsHint_indent { get; set; }
+
+ [LocDisplayName("Declare param before use")]
+ [Description("[latedef] This option prohibits the use of a variable before it was defined.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_latedef { get; set; }
+
+ [LocDisplayName("Capitalize constructor functions")]
+ [Description("[newcap] This option requires you to capitalize names of constructor functions.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_newcap { get; set; }
+
+ [LocDisplayName("Disallow arguments.caller/callee")]
+ [Description("[noarg] This option prohibits the use of arguments.caller and arguments.callee.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_noarg { get; set; }
+
+ [LocDisplayName("No empty code blocks")]
+ [Description("[noempty] This option warns when you have an empty block in your code.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_noempty { get; set; }
+
+ [LocDisplayName("Disallow constructor functions")]
+ [Description("[nonew] This option prohibits the use of constructor functions for side-effects.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_nonew { get; set; }
+
+ [LocDisplayName("Disallow ++ and -- operators")]
+ [Description("[plusplus] This option prohibits the use of unary increment and decrement operators.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_plusplus { get; set; }
+
+ [LocDisplayName("Use consistant quotation")]
+ [Description("[quotmark] This option enforces the consistency of quotation marks used throughout your code.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_quotmark { get; set; }
+
+ [LocDisplayName("Disallow . in regex")]
+ [Description("[regexp] This option prohibits the use of unsafe . in regular expressions.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_regexp { get; set; }
+
+ [LocDisplayName("Disallow use of undeclared var")]
+ [Description("[undef] This option prohibits the use of explicitly undeclared variables. This option is very useful for spotting leaking and mistyped variables.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_undef { get; set; }
+
+ [LocDisplayName("Disallow unused variables")]
+ [Description("[unused] This option warns when you define and never use your variables. It is very useful for general code cleanup, especially when used in addition to undef.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_unused { get; set; }
+
+ [LocDisplayName("Require 'strict mode'")]
+ [Description("[strict] This option requires all functions to run in EcmaScript 5's strict mode.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_strict { get; set; }
+
+ [LocDisplayName("Disallow trailing whitespace")]
+ [Description("[trailing] This option makes it an error to leave a trailing whitespace in your code. Trailing whitespaces can be source of nasty bugs with multi-line strings.")]
+ [Category("Enforcing Options")]
+ public bool JsHint_trailing { get; set; }
+
+ // Relaxing Options
+
+ [LocDisplayName("Allow missing semicolons")]
+ [Description("[asi] This option suppresses warnings about missing semicolons.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_asi { get; set; }
+
+ [LocDisplayName("Allow assignments")]
+ [Description("[boss] This option suppresses warnings about the use of assignments in cases where comparisons are expected.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_boss { get; set; }
+
+ [LocDisplayName("Allow debugger statements")]
+ [Description("[debug] This option suppresses warnings about the debugger statements in your code.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_debug { get; set; }
+
+ [LocDisplayName("Allow == null")]
+ [Description("[eqnull] This option suppresses warnings about == null comparisons.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_eqnull { get; set; }
+
+ [LocDisplayName("Use EcmaScript 5")]
+ [Description("[es5] This option tells JSHint that your code uses ECMAScript 5 specific features such as getters and setters.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_es5 { get; set; }
+
+ [LocDisplayName("Allow ES.next features")]
+ [Description("[esnext] This option tells JSHint that your code uses ES.next specific features such as const.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_esnext { get; set; }
+
+ [LocDisplayName("Allow 'eval'")]
+ [Description("[evil] This option suppresses warnings about the use of eval.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_evil { get; set; }
+
+ [LocDisplayName("Allow expressions")]
+ [Description("[expr] This option suppresses warnings about the use of expressions where normally you would expect to see assignments or function calls.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_expr { get; set; }
+
+ [LocDisplayName("Allow variable scoping mismatch")]
+ [Description("[funcscope] This option suppresses warnings about declaring variables inside of control structures while accessing them later from the outside.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_funcscope { get; set; }
+
+ [LocDisplayName("Allow global strict mode")]
+ [Description("[globalstrict] This option suppresses warnings about the use of global strict mode.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_globalstrict { get; set; }
+
+ [LocDisplayName("Allow '__iterator__'")]
+ [Description("[iterator] This option suppresses warnings about the __iterator__ property.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_iterator { get; set; }
+
+ [LocDisplayName("Allow omitting last semicolon")]
+ [Description("[lastsemic] This option suppresses warnings about missing semicolons, but only when the semicolon is omitted for the last statement in a one-line block.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_lastsemic { get; set; }
+
+ [LocDisplayName("Allow unsafe line breaks")]
+ [Description("[laxbreak] This option suppresses most of the warnings about possibly unsafe line breakings in your code. It doesn't suppress warnings about comma-first coding style.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_laxbreak { get; set; }
+
+ [LocDisplayName("Allow comma first")]
+ [Description("[laxcomma] This option suppresses warnings about comma-first coding style.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_laxcomma { get; set; }
+
+ [LocDisplayName("Allow functions inside loops")]
+ [Description("[loopfunc] This option suppresses warnings about functions inside of loops.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_loopfunc { get; set; }
+
+ [LocDisplayName("Allow multiline strings")]
+ [Description("[multistr] This option suppresses warnings about multi-line strings. Multi-line strings can be dangerous in JavaScript because all hell breaks loose if you accidentally put a whitespace in between the escape character (\\) and a new line.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_multistr { get; set; }
+
+ [LocDisplayName("Allow on-case switches")]
+ [Description("[onecase] This option suppresses warnings about switches with just one case.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_onecase { get; set; }
+
+ [LocDisplayName("Allow '__proto__'")]
+ [Description("[proto] This option suppresses warnings about the __proto__ property. This property is deprecated and not supported by all browsers.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_proto { get; set; }
+
+ [LocDisplayName("Allow unescaped '-' in regex")]
+ [Description("[regexdash] This option suppresses warnings about unescaped - in the end of regular expressions.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_regexdash { get; set; }
+
+ [LocDisplayName("Allow 'javascript:' URLs")]
+ [Description("[scripturl] This option suppresses warnings about the use of script-targeted URLs—such as javascript:")]
+ [Category("Relaxing Options")]
+ public bool JsHint_scripturl { get; set; }
+
+ [LocDisplayName("Allow mixed tabs/spaces")]
+ [Description("[smarttabs] This option suppresses warnings about mixed tabs and spaces when the latter are used for alignmnent only.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_smarttabs { get; set; }
+
+ [LocDisplayName("Allow variable shadowing")]
+ [Description("[shadow] This option suppresses warnings about variable shadowing i.e. declaring a variable that had been already declared somewhere in the outer scope.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_shadow { get; set; }
+
+ [LocDisplayName("Allow object['member']")]
+ [Description("[sub] This option suppresses warnings about using [] notation when it can be expressed in dot notation: person['name'] vs. person.name.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_sub { get; set; }
+
+ [LocDisplayName("Allow weird constructions")]
+ [Description("[supernew] This option suppresses warnings about 'weird' constructions like new function () { ... } and new Object;.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_supernew { get; set; }
+
+ [LocDisplayName("Allow strict mode violations")]
+ [Description("[validthis] This option suppresses warnings about possible strict violations when the code is running in strict mode and you use this in a non-constructor function.")]
+ [Category("Relaxing Options")]
+ public bool JsHint_validthis { get; set; }
+
+ // Environment
+
+ [LocDisplayName("Assume browser")]
+ [Description("[browser] This option defines globals exposed by modern browsers: all the way from good ol' document and navigator to the HTML5 FileReader and other new developments in the browser world.")]
+ [Category("Environment")]
+ public bool JsHint_browser { get; set; }
+
+ [LocDisplayName("Assume CouchDB")]
+ [Description("[couch] This option defines globals exposed by CouchDB. CouchDB is a document-oriented database that can be queried and indexed in a MapReduce fashion using JavaScript.")]
+ [Category("Environment")]
+ public bool JsHint_couch { get; set; }
+
+ [LocDisplayName("Allow console, alert etc.")]
+ [Description("[devel] This option defines globals that are usually used for logging poor-man's debugging: console, alert, etc.")]
+ [Category("Environment")]
+ public bool JsHint_devel { get; set; }
+
+ [LocDisplayName("Assume Dojo")]
+ [Description("[dojo] This option defines globals exposed by the Dojo Toolkit.")]
+ [Category("Environment")]
+ public bool JsHint_dojo { get; set; }
+
+ [LocDisplayName("Assume jQuery")]
+ [Description("[jquery] This option defines globals exposed by the jQuery JavaScript library.")]
+ [Category("Environment")]
+ public bool JsHint_jquery { get; set; }
+
+ [LocDisplayName("Assume MooTools")]
+ [Description("[mootools] This option defines globals exposed by the MooTools JavaScript framework.")]
+ [Category("Environment")]
+ public bool JsHint_mootools { get; set; }
+
+ [LocDisplayName("Assume NodeJS")]
+ [Description("[node] This option defines globals available when your code is running inside of the Node runtime environment.")]
+ [Category("Environment")]
+ public bool JsHint_node { get; set; }
+
+ [LocDisplayName("Allow non-standards")]
+ [Description("[nonstandard] This option defines non-standard but widely adopted globals such as escape and unescape.")]
+ [Category("Environment")]
+ public bool JsHint_nonstandard { get; set; }
+
+ [LocDisplayName("Assume Prototype.js")]
+ [Description("[prototypejs] This option defines globals exposed by the Prototype JavaScript framework.")]
+ [Category("Environment")]
+ public bool JsHint_prototypejs { get; set; }
+
+ [LocDisplayName("Assume Rhino")]
+ [Description("[rhino] This option defines globals available when your code is running inside of the Rhino runtime environment.")]
+ [Category("Environment")]
+ public bool JsHint_rhino { get; set; }
+
+ [LocDisplayName("Allow Web Workers")]
+ [Description("[worker] This option defines globals available when your code is running inside of a Web Worker.")]
+ [Category("Environment")]
+ public bool JsHint_worker { get; set; }
+
+ [LocDisplayName("Assume Windows Script Host")]
+ [Description("[wsh] This option defines globals available when your code is running as a script for the Windows Script Host.")]
+ [Category("Environment")]
+ public bool JsHint_wsh { get; set; }
+ }
+}
diff --git a/EditorExtensions/Options/Less.cs b/EditorExtensions/Options/Less.cs
new file mode 100644
index 000000000..e0537ed34
--- /dev/null
+++ b/EditorExtensions/Options/Less.cs
@@ -0,0 +1,58 @@
+using Microsoft.VisualStudio.Shell;
+using System.ComponentModel;
+
+namespace MadsKristensen.EditorExtensions
+{
+ class LessOptions : DialogPage
+ {
+ public LessOptions()
+ {
+ Settings.Updated += delegate { LoadSettingsFromStorage(); };
+ }
+
+ public override void SaveSettingsToStorage()
+ {
+ Settings.SetValue(WESettings.Keys.GenerateCssFileFromLess, GenerateCssFileFromLess);
+ Settings.SetValue(WESettings.Keys.ShowLessPreviewWindow, ShowLessPreviewWindow);
+ Settings.SetValue(WESettings.Keys.LessMinify, LessMinify);
+ Settings.SetValue(WESettings.Keys.LessCompileOnBuild, LessCompileOnBuild);
+ Settings.SetValue(WESettings.Keys.LessCompileToFolder, LessCompileToFolder);
+
+ Settings.Save();
+ }
+
+ public override void LoadSettingsFromStorage()
+ {
+ GenerateCssFileFromLess = WESettings.GetBoolean(WESettings.Keys.GenerateCssFileFromLess);
+ ShowLessPreviewWindow = WESettings.GetBoolean(WESettings.Keys.ShowLessPreviewWindow);
+ LessMinify = WESettings.GetBoolean(WESettings.Keys.LessMinify);
+ LessCompileOnBuild = WESettings.GetBoolean(WESettings.Keys.LessCompileOnBuild);
+ LessCompileToFolder = WESettings.GetBoolean(WESettings.Keys.LessCompileToFolder);
+ }
+
+ [LocDisplayName("Generate CSS file on save")]
+ [Description("Generate CSS file when LESS file is saved")]
+ [Category("LESS")]
+ public bool GenerateCssFileFromLess { get; set; }
+
+ [LocDisplayName("Generate min file on save")]
+ [Description("Creates a minified version of the compiled CSS file (file.min.css)")]
+ [Category("LESS")]
+ public bool LessMinify { get; set; }
+
+ [LocDisplayName("Show preview window")]
+ [Description("Show the preview window when editing a LESS file.")]
+ [Category("LESS")]
+ public bool ShowLessPreviewWindow { get; set; }
+
+ [LocDisplayName("Compile on build")]
+ [Description("Compiles all LESS files in the project that has a corresponding .css file.")]
+ [Category("LESS")]
+ public bool LessCompileOnBuild { get; set; }
+
+ [LocDisplayName("Compile to 'css' folder")]
+ [Description("Compiles all LESS files into a folder called 'css' in the same directory as the .less file")]
+ [Category("LESS")]
+ public bool LessCompileToFolder { get; set; }
+ }
+}
diff --git a/EditorExtensions/Options/ProjectSettingsStore.cs b/EditorExtensions/Options/ProjectSettingsStore.cs
new file mode 100644
index 000000000..07cfaad2a
--- /dev/null
+++ b/EditorExtensions/Options/ProjectSettingsStore.cs
@@ -0,0 +1,349 @@
+using EnvDTE;
+using EnvDTE80;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using Keys = MadsKristensen.EditorExtensions.WESettings.Keys;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class Settings
+ {
+ public const string _fileName = "WE-settings.xml";
+ public const string _solutionFolder = "Solution Items";
+
+ private static SortedDictionary _cache = DefaultSettings();
+ private static bool _inProgress;
+ private static object _syncFileRoot = new object();
+ private static object _syncCacheRoot = new object();
+
+ public Settings()
+ {
+ UpdateCache();
+ }
+
+ public static bool SolutionSettingsExist
+ {
+ get { return File.Exists(GetSolutionFilePath()); }
+ }
+
+ public static float Version { get; private set; }
+
+ public static object GetValue(string propertyName)
+ {
+ lock (_syncCacheRoot)
+ {
+ if (_cache.ContainsKey(propertyName))
+ return _cache[propertyName];
+ }
+
+ return null;
+ }
+
+ public static void SetValue(string propertyName, object value)
+ {
+ lock (_syncCacheRoot)
+ {
+ string v = value.ToString().ToLowerInvariant();
+ _cache[propertyName] = v;
+ }
+ }
+
+ public static void Save(string file = null)
+ {
+ //_dispatcher.BeginInvoke(new Action(() =>
+ //{
+ Task.Run(() =>
+ {
+ SaveToDisk(file);
+ UpdateStatusBar("updated");
+ });
+
+ //}), DispatcherPriority.ApplicationIdle, null);
+ }
+
+ internal static void CreateSolutionSettings()
+ {
+ string path = GetSolutionFilePath();
+
+ if (!File.Exists(path))
+ {
+ lock (_syncFileRoot)
+ {
+ File.WriteAllText(path, string.Empty);
+ }
+
+ Save(path);
+
+ Solution2 solution = EditorExtensionsPackage.DTE.Solution as Solution2;
+ Project project = solution.Projects
+ .OfType()
+ .FirstOrDefault(p => p.Name.Equals(_solutionFolder, StringComparison.OrdinalIgnoreCase));
+
+ if (project == null)
+ {
+ project = solution.AddSolutionFolder(_solutionFolder);
+ }
+
+ project.ProjectItems.AddFromFile(path);
+ //EditorExtensionsPackage.DTE.ItemOperations.OpenFile(path);
+ UpdateStatusBar("applied");
+ }
+ }
+
+ public static void UpdateCache()
+ {
+ try
+ {
+ string path = GetFilePath();
+
+ if (File.Exists(path))
+ {
+ XmlDocument doc = LoadXmlDocument(path);
+
+ if (doc != null)
+ {
+
+ XmlNode settingsNode = doc.SelectSingleNode("webessentials/settings");
+
+ if (settingsNode != null)
+ {
+ XmlAttribute versionAttr = settingsNode.Attributes["version"];
+ if (versionAttr != null)
+ {
+ float version;
+
+ if (float.TryParse(versionAttr.InnerText, out version))
+ {
+ Version = version;
+ }
+ }
+
+ lock (_syncCacheRoot)
+ {
+ _cache.Clear();
+
+ foreach (XmlNode node in settingsNode.ChildNodes)
+ {
+ _cache[node.Name] = node.InnerText;
+ }
+ }
+
+ OnUpdated();
+ }
+ }
+ }
+ }
+ catch
+ { }
+ }
+
+ private static void SaveToDisk(string file)
+ {
+ if (!_inProgress)
+ {
+ _inProgress = true;
+ string path = file ?? GetFilePath();
+
+ lock (_syncFileRoot)
+ {
+ string xml = GenerateXml();
+
+ ProjectHelpers.CheckOutFileFromSourceControl(path);
+ File.WriteAllText(path, xml);
+ }
+
+ _inProgress = false;
+ }
+ }
+
+ private static string GenerateXml()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ XmlWriterSettings settings = new XmlWriterSettings();
+ settings.Indent = true;
+
+ using (XmlWriter writer = XmlWriter.Create(sb, settings))
+ {
+ writer.WriteStartElement("webessentials");
+ writer.WriteAttributeString("version", "1.9");
+
+ writer.WriteStartElement("settings");
+
+ lock (_syncCacheRoot)
+ {
+ foreach (string property in _cache.Keys)
+ {
+ string value = _cache[property].ToString();
+ writer.WriteElementString(property, value);
+ }
+ }
+
+ writer.WriteEndElement();// settings
+ writer.WriteEndElement();// webessentials
+ }
+
+ sb.Replace(Encoding.Unicode.WebName, Encoding.UTF8.WebName);
+
+ return sb.ToString();
+ }
+
+ private static XmlDocument LoadXmlDocument(string path)
+ {
+ try
+ {
+ lock (_syncFileRoot)
+ {
+ XmlDocument doc = new XmlDocument();
+ doc.Load(path);
+ return doc;
+ }
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ private static string GetFilePath()
+ {
+ string path = GetSolutionFilePath();
+
+ if (!File.Exists(path))
+ {
+ path = GetUserFilePath();
+ }
+
+ return path;
+ }
+
+ public static string GetSolutionFilePath()
+ {
+ EnvDTE.Solution solution = EditorExtensionsPackage.DTE.Solution;
+
+ if (solution == null || string.IsNullOrEmpty(solution.FullName))
+ return null;
+
+ return Path.Combine(Path.GetDirectoryName(solution.FullName), _fileName);
+ }
+
+ private static string GetUserFilePath()
+ {
+ string user = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ string folder = Path.Combine(user, "Web Essentials");
+
+ if (!Directory.Exists(folder))
+ {
+ Directory.CreateDirectory(folder);
+ }
+
+ return Path.Combine(folder, _fileName);
+ }
+
+ private static SortedDictionary DefaultSettings()
+ {
+ var dic = new SortedDictionary();
+
+ // MISC
+ dic.Add(Keys.EnableMustache, true);
+ dic.Add(Keys.EnableJavascriptRegions, true);
+
+ // LESS
+ dic.Add(Keys.GenerateCssFileFromLess, true);
+ dic.Add(Keys.ShowLessPreviewWindow, true);
+ dic.Add(Keys.LessMinify, true);
+
+ // SCSS
+ dic.Add(Keys.GenerateCssFileFromScss, true);
+ dic.Add(Keys.ShowScssPreviewWindow, true);
+ dic.Add(Keys.ScssMinify, true);
+
+ // CoffeeScript
+ dic.Add(Keys.GenerateJsFileFromCoffeeScript, true);
+ dic.Add(Keys.ShowCoffeeScriptPreviewWindow, true);
+
+ // CSS
+ dic.Add(Keys.CssErrorLocation, (int)Keys.ErrorLocation.Messages);
+ dic.Add(Keys.SyncVendorValues, true);
+ dic.Add(Keys.EnableCssSelectorHighligting, true);
+ dic.Add(Keys.ShowUnsupported, true);
+ dic.Add(Keys.AutoCloseCurlyBraces, true);
+
+ //JSHint
+ dic.Add(Keys.EnableJsHint, true);
+ dic.Add(Keys.JsHintErrorLocation, (int)Keys.FullErrorLocation.Messages);
+ dic.Add(Keys.JsHint_bitwise, true);
+ dic.Add(Keys.JsHint_browser, true);
+ dic.Add(Keys.JsHint_devel, true);
+ dic.Add(Keys.JsHint_eqeqeq, true);
+ dic.Add(Keys.JsHint_es5, true);
+ dic.Add(Keys.JsHint_expr, true);
+ dic.Add(Keys.JsHint_debug, true);
+ dic.Add(Keys.JsHint_jquery, true);
+ dic.Add(Keys.JsHint_laxbreak, true);
+ dic.Add(Keys.JsHint_laxcomma, true);
+ dic.Add(Keys.JsHint_maxerr, 50);
+ dic.Add(Keys.JsHint_regexdash, true);
+ dic.Add(Keys.JsHint_smarttabs, true);
+
+ // MISC
+ dic.Add(Keys.ShowBrowserTooltip, true);
+ dic.Add(Keys.WrapCoffeeScriptClosure, true);
+
+ // TypeScript
+ //dic.Add(Keys.ShowTypeScriptPreviewWindow, true);
+ //dic.Add(Keys.GenerateJsFileFromTypeScript, true);
+ //dic.Add(Keys.TypeScriptAddGeneratedFilesToProject, true);
+
+ // Minification
+ dic.Add(Keys.EnableCssMinification, true);
+ dic.Add(Keys.EnableJsMinification, true);
+
+ // Minification
+ dic.Add(Keys.CoffeeScriptMinify, true);
+ //dic.Add(Keys.TypeScriptMinify, true);
+
+ dic.Add(Keys.GenerateJavaScriptSourceMaps, true);
+ dic.Add(Keys.EnableHtmlZenCoding, true);
+
+ dic.Add(Keys.JavaScriptAutoCloseBraces, true);
+ dic.Add(Keys.JavaScriptOutlining, true);
+
+ return dic;
+ }
+
+ public static event EventHandler Updated;
+
+ private static void OnUpdated()
+ {
+ if (Updated != null)
+ {
+ Updated(null, EventArgs.Empty);
+ }
+ }
+
+ public static void UpdateStatusBar(string action)
+ {
+ try
+ {
+ if (SolutionSettingsExist)
+ {
+ EditorExtensionsPackage.DTE.StatusBar.Text = "Web Essentials: Solution settings " + action;
+ }
+ else
+ {
+ EditorExtensionsPackage.DTE.StatusBar.Text = "Web Essentials: Global settings " + action;
+ }
+ }
+ catch
+ {
+ Logger.Log("Error updating status bar");
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/Options/ProjectSettingsTextViewListener.cs b/EditorExtensions/Options/ProjectSettingsTextViewListener.cs
new file mode 100644
index 000000000..8b5861154
--- /dev/null
+++ b/EditorExtensions/Options/ProjectSettingsTextViewListener.cs
@@ -0,0 +1,36 @@
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using System.ComponentModel.Composition;
+using System.Threading.Tasks;
+
+namespace MadsKristensen.EditorExtensions.Options
+{
+ [Export(typeof(IWpfTextViewCreationListener))]
+ [ContentType("XML")]
+ [TextViewRole(PredefinedTextViewRoles.Document)]
+ internal class ProjectSettingsTextViewListener : IWpfTextViewCreationListener
+ {
+ public void TextViewCreated(IWpfTextView textView)
+ {
+ ITextDocument document;
+ textView.TextDataModel.DocumentBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document);
+
+ if (document != null)
+ {
+ document.FileActionOccurred += document_FileActionOccurred;
+ }
+ }
+
+ private void document_FileActionOccurred(object sender, TextDocumentFileActionEventArgs e)
+ {
+ if (e.FileActionType == FileActionTypes.ContentSavedToDisk && e.FilePath.EndsWith(Settings._fileName))
+ {
+ Task.Run(() =>
+ {
+ Settings.UpdateCache();
+ });
+ }
+ }
+ }
+}
diff --git a/EditorExtensions/Options/Scss.cs b/EditorExtensions/Options/Scss.cs
new file mode 100644
index 000000000..c92b300c5
--- /dev/null
+++ b/EditorExtensions/Options/Scss.cs
@@ -0,0 +1,58 @@
+using Microsoft.VisualStudio.Shell;
+using System.ComponentModel;
+
+namespace MadsKristensen.EditorExtensions
+{
+ class ScssOptions : DialogPage
+ {
+ public ScssOptions()
+ {
+ Settings.Updated += delegate { LoadSettingsFromStorage(); };
+ }
+
+ public override void SaveSettingsToStorage()
+ {
+ Settings.SetValue(WESettings.Keys.GenerateCssFileFromScss, GenerateCssFileFromScss);
+ Settings.SetValue(WESettings.Keys.ShowScssPreviewWindow, ShowScssPreviewWindow);
+ Settings.SetValue(WESettings.Keys.ScssMinify, ScssMinify);
+ Settings.SetValue(WESettings.Keys.ScssCompileOnBuild, ScssCompileOnBuild);
+ Settings.SetValue(WESettings.Keys.ScssCompileToFolder, ScssCompileToFolder);
+
+ Settings.Save();
+ }
+
+ public override void LoadSettingsFromStorage()
+ {
+ GenerateCssFileFromScss = WESettings.GetBoolean(WESettings.Keys.GenerateCssFileFromScss);
+ ShowScssPreviewWindow = WESettings.GetBoolean(WESettings.Keys.ShowScssPreviewWindow);
+ ScssMinify = WESettings.GetBoolean(WESettings.Keys.ScssMinify);
+ ScssCompileOnBuild = WESettings.GetBoolean(WESettings.Keys.ScssCompileOnBuild);
+ ScssCompileToFolder = WESettings.GetBoolean(WESettings.Keys.ScssCompileToFolder);
+ }
+
+ [LocDisplayName("Generate CSS file on save")]
+ [Description("Generate CSS file when Scss file is saved")]
+ [Category("Scss")]
+ public bool GenerateCssFileFromScss { get; set; }
+
+ [LocDisplayName("Generate min file on save")]
+ [Description("Creates a minified version of the compiled CSS file (file.min.css)")]
+ [Category("Scss")]
+ public bool ScssMinify { get; set; }
+
+ [LocDisplayName("Show preview window")]
+ [Description("Show the preview window when editing a Scss file.")]
+ [Category("Scss")]
+ public bool ShowScssPreviewWindow { get; set; }
+
+ [LocDisplayName("Compile on build")]
+ [Description("Compiles all Scss files in the project that has a corresponding .css file.")]
+ [Category("Scss")]
+ public bool ScssCompileOnBuild { get; set; }
+
+ [LocDisplayName("Compile to 'css' folder")]
+ [Description("Compiles all SCSS files into a folder called 'css' in the same directory as the .scss file")]
+ [Category("Scss")]
+ public bool ScssCompileToFolder { get; set; }
+ }
+}
diff --git a/EditorExtensions/Options/TypeScript.cs b/EditorExtensions/Options/TypeScript.cs
new file mode 100644
index 000000000..b04e4206a
--- /dev/null
+++ b/EditorExtensions/Options/TypeScript.cs
@@ -0,0 +1,88 @@
+using Microsoft.VisualStudio.Shell;
+using System.ComponentModel;
+
+namespace MadsKristensen.EditorExtensions
+{
+ class TypeScriptOptions : DialogPage
+ {
+ public override void SaveSettingsToStorage()
+ {
+ Settings.SetValue(WESettings.Keys.GenerateJsFileFromTypeScript, GenerateJsFileFromTypeScript);
+ Settings.SetValue(WESettings.Keys.ShowTypeScriptPreviewWindow, ShowTypeScriptPreviewWindow);
+ Settings.SetValue(WESettings.Keys.CompileTypeScriptOnBuild, CompileTypeScriptOnBuild);
+ Settings.SetValue(WESettings.Keys.TypeScriptKeepComments, TypeScriptKeepComments);
+ Settings.SetValue(WESettings.Keys.TypeScriptUseAmdModule, TypeScriptUseAmdModule);
+ Settings.SetValue(WESettings.Keys.TypeScriptCompileES3, TypeScriptCompileES3);
+ Settings.SetValue(WESettings.Keys.TypeScriptProduceSourceMap, TypeScriptProduceSourceMap);
+ Settings.SetValue(WESettings.Keys.TypeScriptMinify, TypeScriptMinify);
+ Settings.SetValue(WESettings.Keys.TypeScriptAddGeneratedFilesToProject, TypeScriptAddGeneratedFilesToProject);
+ Settings.SetValue(WESettings.Keys.TypeScriptResaveWithUtf8BOM, TypeScriptResaveWithUtf8BOM);
+
+ Settings.Save();
+ }
+
+ public override void LoadSettingsFromStorage()
+ {
+ GenerateJsFileFromTypeScript = WESettings.GetBoolean(WESettings.Keys.GenerateJsFileFromTypeScript);
+ ShowTypeScriptPreviewWindow = WESettings.GetBoolean(WESettings.Keys.ShowTypeScriptPreviewWindow);
+ CompileTypeScriptOnBuild = WESettings.GetBoolean(WESettings.Keys.CompileTypeScriptOnBuild);
+ TypeScriptKeepComments = WESettings.GetBoolean(WESettings.Keys.TypeScriptKeepComments);
+ TypeScriptUseAmdModule = WESettings.GetBoolean(WESettings.Keys.TypeScriptUseAmdModule);
+ TypeScriptCompileES3 = WESettings.GetBoolean(WESettings.Keys.TypeScriptCompileES3);
+ TypeScriptProduceSourceMap = WESettings.GetBoolean(WESettings.Keys.TypeScriptProduceSourceMap);
+ TypeScriptMinify = WESettings.GetBoolean(WESettings.Keys.TypeScriptMinify);
+ TypeScriptAddGeneratedFilesToProject = WESettings.GetBoolean(WESettings.Keys.TypeScriptAddGeneratedFilesToProject);
+ TypeScriptResaveWithUtf8BOM = WESettings.GetBoolean(WESettings.Keys.TypeScriptResaveWithUtf8BOM);
+ }
+
+ [LocDisplayName("Compile TypeScript on save")]
+ [Description("Generate JavaScript file when TypeScript file is saved")]
+ [Category("TypeScript")]
+ public bool GenerateJsFileFromTypeScript { get; set; }
+
+ [LocDisplayName("Show preview window")]
+ [Description("Show the preview window when editing a TypeScript file.")]
+ [Category("TypeScript")]
+ public bool ShowTypeScriptPreviewWindow { get; set; }
+
+ [LocDisplayName("Add generated files to project")]
+ [Description("Includes the generated .js, .min.js and .map files to the current project, nested under the .ts file.")]
+ [Category("TypeScript")]
+ public bool TypeScriptAddGeneratedFilesToProject { get; set; }
+
+ [LocDisplayName("Compile all TypeScript files on build")]
+ [Description("Runs the compiler on all TypeScript files in your project on build")]
+ [Category("TypeScript")]
+ public bool CompileTypeScriptOnBuild { get; set; }
+
+ [LocDisplayName("Minify generated JavaScript")]
+ [Description("Creates a minified version of the compiled JavaScript file (file.min.js)")]
+ [Category("TypeScript")]
+ public bool TypeScriptMinify { get; set; }
+
+ [LocDisplayName("Re-save JS with UTF8 BOM")]
+ [Description("Re-saves the compiled output with a UTF-8 BOM")]
+ [Category("TypeScript")]
+ public bool TypeScriptResaveWithUtf8BOM { get; set; }
+
+ [LocDisplayName("Use the AMD module")]
+ [Description("Sets the '--module AMD' flag on the compiler")]
+ [Category("Compiler flags")]
+ public bool TypeScriptUseAmdModule { get; set; }
+
+ [LocDisplayName("Compile to EcmaScript 3")]
+ [Description("Sets the '--target ES3' flag on the compiler. Default is EcmaScript 5")]
+ [Category("Compiler flags")]
+ public bool TypeScriptCompileES3 { get; set; }
+
+ [LocDisplayName("Generate Source Map")]
+ [Description("Sets the '-sourcemap' flag on the compiler.")]
+ [Category("Compiler flags")]
+ public bool TypeScriptProduceSourceMap { get; set; }
+
+ [LocDisplayName("Keep comments")]
+ [Description("Keeps the comments in the generated JavaScript files by setting the '-c' flag on the compiler.")]
+ [Category("Compiler flags")]
+ public bool TypeScriptKeepComments { get; set; }
+ }
+}
diff --git a/EditorExtensions/Options/WebEssentialsSettings.cs b/EditorExtensions/Options/WebEssentialsSettings.cs
new file mode 100644
index 000000000..97380dc83
--- /dev/null
+++ b/EditorExtensions/Options/WebEssentialsSettings.cs
@@ -0,0 +1,185 @@
+
+namespace MadsKristensen.EditorExtensions
+{
+ static class WESettings
+ {
+ public class Keys
+ {
+ // General
+ public const string EnableHtmlZenCoding = "HtmlEnableZenCoding";
+ public const string EnableMustache = "HtmlEnableMustache";
+ public const string KeepImportantComments = "KeepImportantComments";
+
+ // LESS
+ public const string GenerateCssFileFromLess = "LessGenerateCssFile";
+ public const string ShowLessPreviewWindow = "LessShowPreviewWindow";
+ public const string LessMinify = "LessMinify";
+ public const string LessCompileOnBuild = "LessCompileOnBuild";
+ public const string LessCompileToFolder = "LessCompileToFolder";
+
+ // SCSS
+ public const string GenerateCssFileFromScss = "ScssGenerateCssFile";
+ public const string ShowScssPreviewWindow = "ScssShowPreviewWindow";
+ public const string ScssMinify = "ScssMinify";
+ public const string ScssCompileOnBuild = "ScssCompileOnBuild";
+ public const string ScssCompileToFolder = "ScssCompileToFolder";
+
+ // CoffeeScript
+ public const string GenerateJsFileFromCoffeeScript = "CoffeeScriptGenerateJsFile";
+ public const string ShowCoffeeScriptPreviewWindow = "CoffeeScriptShowPreviewWindow";
+ public const string CoffeeScriptMinify = "CoffeeScriptMinify";
+ public const string WrapCoffeeScriptClosure = "CoffeeScriptWrapClosure";
+ public const string EnableIcedCoffeeScript = "CoffeeScriptEnableIced";
+ public const string CoffeeScriptCompileToFolder = "CoffeeScriptCompileToFolder";
+ public const string CoffeeScriptCompileOnBuild = "CoffeeScriptCompileOnBuild";
+
+ // CSS
+ public const string ValidateStarSelector = "CssValidateStarSelector";
+ public const string ValidateOverQualifiedSelector = "CSSValidateOverQualifiedSelector";
+ public const string CssErrorLocation = "CssErrorLocation";
+ public const string EnableCssSelectorHighligting = "CssEnableSelectorHighligting";
+ public const string ValidateEmbedImages = "CssValidateEmbedImages";
+ public const string AutoCloseCurlyBraces = "CssAutoCloseCurlyBraces";
+ public const string ShowBrowserTooltip = "CssShowBrowserTooltip";
+ public const string SyncVendorValues = "CssSyncVendorValues";
+ public const string ShowInitialInherit = "CssShowInitialInherit";
+ public const string ShowUnsupported = "CssShowUnsupported";
+ public const string EnableCssMinification = "CssEnableMinification";
+ public const string ValidateZeroUnit = "CssValidateZeroUnit";
+ public const string OnlyW3cAllowed = "CssOnlyW3cAllowed";
+ public const string ValidateVendorSpecifics = "ValidateVendorSpecifics";
+ public const string EnableSpeedTyping = "EnableSpeedTyping";
+
+ // JavaScript
+ public const string EnableJavascriptRegions = "JavascriptEnableRegions";
+ public const string EnableJsMinification = "JavaScriptEnableMinification";
+ public const string JavaScriptAutoCloseBraces = "JavaScriptAutoCloseBraces";
+ public const string JavaScriptOutlining = "JavaScriptOutlining";
+ public const string GenerateJavaScriptSourceMaps = "JavaScriptGenerateSourceMaps";
+
+ // TypeScript
+ //public const string GenerateJsFileFromTypeScript = "TypeScriptGenerateJsFile";
+ //public const string ShowTypeScriptPreviewWindow = "TypeScriptShowPreviewWindow";
+ //public const string CompileTypeScriptOnBuild = "TypeScriptCompileOnBuild";
+ //public const string TypeScriptKeepComments = "TypeScriptKeepComments";
+ //public const string TypeScriptUseAmdModule = "TypeScriptUseAmdModule";
+ //public const string TypeScriptCompileES3 = "TypeScriptCompileES3";
+ //public const string TypeScriptProduceSourceMap = "TypeScriptProduceSourceMap";
+ //public const string TypeScriptMinify = "TypeScriptMinify";
+ //public const string TypeScriptAddGeneratedFilesToProject = "TypeScriptAddGeneratedFilesToProject";
+ //public const string TypeScriptResaveWithUtf8BOM = "TypeScriptResaveWithUtf8BOM";
+
+ // JSHint
+ public const string EnableJsHint = "JsHintEnable";
+ public const string RunJsHintOnBuild = "JsHintRunOnBuild";
+ public const string JsHintErrorLocation = "JsHintErrorLocation";
+ public const string JsHint_eqeqeq = "JsHint_eqeqeq";
+ public const string JsHint_bitwise = "JsHint_bitwise";
+ public const string JsHint_maxerr = "JsHint_maxerr"; // int
+ public const string JsHint_camelcase = "JsHint_camelcase";
+ public const string JsHint_curly = "JsHint_curly";
+ public const string JsHint_forin = "JsHint_forin";
+ public const string JsHint_immed = "JsHint_immed";
+ public const string JsHint_indent = "JsHint_indent"; // int
+ public const string JsHint_latedef = "JsHint_latedef";
+ public const string JsHint_newcap = "JsHint_newcap";
+ public const string JsHint_noarg = "JsHint_noarg";
+ public const string JsHint_noempty = "JsHint_noempty";
+ public const string JsHint_nonew = "JsHint_nonew";
+ public const string JsHint_plusplus = "JsHint_plusplus";
+ public const string JsHint_quotmark = "JsHint_quotmark";
+ public const string JsHint_regexp = "JsHint_regexp";
+ public const string JsHint_undef = "JsHint_undef";
+ public const string JsHint_unused = "JsHint_unused";
+ public const string JsHint_strict = "JsHint_strict";
+ public const string JsHint_trailing = "JsHint_trailing";
+
+ // Relaxing
+ public const string JsHint_asi = "JsHint_asi";
+ public const string JsHint_boss = "JsHint_boss";
+ public const string JsHint_debug = "JsHint_debug";
+ public const string JsHint_eqnull = "JsHint_eqnull";
+ public const string JsHint_es5 = "JsHint_es5";
+ public const string JsHint_esnext = "JsHint_esnext";
+ public const string JsHint_evil = "JsHint_evil";
+ public const string JsHint_expr = "JsHint_expr";
+ public const string JsHint_funcscope = "JsHint_funcscope";
+ public const string JsHint_globalstrict = "JsHint_globalstrict";
+ public const string JsHint_iterator = "JsHint_iterator";
+ public const string JsHint_lastsemic = "JsHint_lastsemic";
+ public const string JsHint_laxbreak = "JsHint_laxbreak";
+ public const string JsHint_laxcomma = "JsHint_laxcomma";
+ public const string JsHint_loopfunc = "JsHint_loopfunc";
+ public const string JsHint_multistr = "JsHint_multistr";
+ public const string JsHint_onecase = "JsHint_onecase";
+ public const string JsHint_proto = "JsHint_proto";
+ public const string JsHint_regexdash = "JsHint_regexdash";
+ public const string JsHint_scripturl = "JsHint_scripturl";
+ public const string JsHint_smarttabs = "JsHint_smarttabs";
+ public const string JsHint_shadow = "JsHint_shadow";
+ public const string JsHint_sub = "JsHint_sub";
+ public const string JsHint_supernew = "JsHint_supernew";
+ public const string JsHint_validthis = "JsHint_validthis";
+
+ // Environment
+ public const string JsHint_browser = "JsHint_browser";
+ public const string JsHint_devel = "JsHint_devel";
+ public const string JsHint_jquery = "JsHint_jquery";
+ public const string JsHint_couch = "JsHint_couch";
+ public const string JsHint_dojo = "JsHint_dojo";
+ public const string JsHint_mootools = "JsHint_mootools";
+ public const string JsHint_node = "JsHint_node";
+ public const string JsHint_nonstandard = "JsHint_nonstandard";
+ public const string JsHint_prototypejs = "JsHint_prototypejs";
+ public const string JsHint_rhino = "JsHint_rhino";
+ public const string JsHint_worker = "JsHint_worker";
+ public const string JsHint_wsh = "JsHint_wsh";
+
+ public enum ErrorLocation
+ {
+ Warnings = 0,
+ Messages = 1,
+ }
+
+ public enum FullErrorLocation
+ {
+ Errors = 0,
+ Warnings = 1,
+ Messages = 2,
+ }
+ }
+
+ private static Settings _projectStore;
+
+ static WESettings()
+ {
+ _projectStore = new Settings();
+ }
+
+ public static bool GetBoolean(string propertyName)
+ {
+ bool result;
+ object value = Settings.GetValue(propertyName);
+
+ if (value != null && bool.TryParse(value.ToString(), out result))
+ {
+ return result;
+ }
+
+ return false;
+ }
+
+ public static int GetInt(string propertyName)
+ {
+ int result;
+ object value = Settings.GetValue(propertyName);
+
+ if (value != null && int.TryParse(value.ToString(), out result))
+ {
+ return result;
+ }
+
+ return -1;
+ }
+ }
+}
diff --git a/EditorExtensions/PkgCmdID.cs b/EditorExtensions/PkgCmdID.cs
new file mode 100644
index 000000000..6347b3362
--- /dev/null
+++ b/EditorExtensions/PkgCmdID.cs
@@ -0,0 +1,58 @@
+
+namespace MadsKristensen.EditorExtensions
+{
+ static class PkgCmdIDList
+ {
+ public const uint myCommand = 0x100;
+ public const uint htmlEncode = 0x102;
+ public const uint htmlDecode = 0x103;
+ public const uint urlEncode = 0x106;
+ public const uint urlDecode = 0x107;
+ public const uint jsEncode = 0x108;
+ public const uint attrEncode = 0x109;
+ public const uint urlPathEncode = 0x110;
+ public const uint upperCaseTransform = 0x111;
+ public const uint lowerCaseTransform = 0x114;
+ public const uint titleCaseTransform = 0x115;
+ public const uint reverseTransform = 0x116;
+ public const uint normalizeTransform = 0x118;
+ public const uint md5Transform = 0x120;
+ public const uint sha1Transform = 0x121;
+ public const uint sha256Transform = 0x122;
+ public const uint sha384Transform = 0x123;
+ public const uint sha512Transform = 0x124;
+ public const uint sortCssProperties = 0x125;
+ public const uint addMissingVendor = 0x127;
+ public const uint addMissingStandard = 0x128;
+ public const uint cssRemoveDuplicates = 0x129;
+ public const uint cssHideUnsupported = 0x1033;
+ public const uint cssHideInheritInitial = 0x1035;
+ public const uint addNewFeature = 0x334;
+ public const uint SurroundWith = 0x334;
+ public const uint ExpandSelection = 0x335;
+ public const uint ContractSelection = 0x336;
+ public const uint cmdDiff = 0x1041;
+ public const uint cmdJsHint = 0x1042;
+ public const uint cmdProjectSettings = 0x1043;
+ public const uint cmdSolutionSettings = 0x1044;
+ public const uint cmdSolutionColors = 0x1045;
+ public const uint cmdMarkdownStylesheet = 0x1046;
+ public const uint CssIntellisenseSubMenu = 0x1031;
+ public const uint MinifyCss = 0x1051;
+ public const uint MinifyJs = 0x1052;
+ public const uint MinifySelection = 0x1053;
+ public const uint ExtractSelection = 0x1054;
+ public const uint SelectBrowsers = 0x1055;
+ public const uint ExtractVariable = 0x1056;
+ public const uint ExtractMixin = 0x1057;
+ public const uint BundleCss = 0x1071;
+ public const uint BundleJs = 0x1072;
+
+ // Build
+ public const uint cmdBuildBundles = 0x1083;
+ public const uint cmdBuildLess = 0x1084;
+ //public const uint cmdBuildTypeScript = 0x1085;
+ public const uint cmdBuildMinify = 0x1086;
+ public const uint cmdBuildCoffeeScript = 0x1087;
+ };
+}
diff --git a/EditorExtensions/Properties/AssemblyInfo.cs b/EditorExtensions/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..570631b1f
--- /dev/null
+++ b/EditorExtensions/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Reflection;
+using System.Resources;
+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("Web Essentials 2013")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Mads Kristensen")]
+[assembly: AssemblyProduct("Web Essentials 2013")]
+[assembly: AssemblyCopyright("")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: ComVisible(false)]
+[assembly: CLSCompliant(false)]
+[assembly: NeutralResourcesLanguage("en-US")]
+
+// 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 Revision and Build Numbers
+// by using the '*' as shown below:
+
+[assembly: AssemblyVersion("1.5.0.0")]
+[assembly: AssemblyFileVersion("1.5.0.0")]
+
+
+
diff --git a/EditorExtensions/QuickInfo/Declaration/DeclarationQuickInfo.cs b/EditorExtensions/QuickInfo/Declaration/DeclarationQuickInfo.cs
new file mode 100644
index 000000000..9453c4d44
--- /dev/null
+++ b/EditorExtensions/QuickInfo/Declaration/DeclarationQuickInfo.cs
@@ -0,0 +1,293 @@
+using Microsoft.CSS.Core;
+using Microsoft.CSS.Editor;
+using Microsoft.CSS.Editor.Intellisense;
+using Microsoft.CSS.Editor.Schemas;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace MadsKristensen.EditorExtensions
+{
+ internal class DeclarationQuickInfo : IQuickInfoSource
+ {
+ private DeclarationQuickInfoSourceProvider _provider;
+ private ITextBuffer _buffer;
+ private CssTree _tree;
+ private static readonly string[] browserAbbr = new[] { "C", "FF", "IE", "O", "S" };
+ private ICssSchemaInstance _rootSchema;
+
+ public DeclarationQuickInfo(DeclarationQuickInfoSourceProvider provider, ITextBuffer buffer)
+ {
+ _provider = provider;
+ _buffer = buffer;
+ _rootSchema = CssSchemaManager.SchemaManager.GetSchemaRootForBuffer(buffer);
+ }
+
+ public void AugmentQuickInfoSession(IQuickInfoSession session, IList