Skip to content

Commit

Permalink
Feature: Chain compile for LESS and SASS (madskristensen#189).
Browse files Browse the repository at this point in the history
* Menu item to add file in ignore list.
* Needs testing.
  • Loading branch information
am11 committed Jan 29, 2014
1 parent 54a3a5f commit ed4a3ab
Show file tree
Hide file tree
Showing 19 changed files with 193 additions and 23 deletions.
2 changes: 0 additions & 2 deletions EditorExtensions/Classifications/CSS/ImportantClassifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@ private void UpdateCache(ParseItem item)

foreach (TokenItem token in visitor.Items.Where(d => d.Important != null).Select(d => d.Important))
{
string text = token.Text;

if (!_cache.Contains(token))
_cache.Add(token);
}
Expand Down
1 change: 1 addition & 0 deletions EditorExtensions/CommandConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ enum CommandId
BundleJs = 0x1072,
BundleHtml = 0x1074,
ReferenceJs = 0x333,
ChainCompile = 0x337,

// Build
BuildBundles = 0x1083,
Expand Down
11 changes: 11 additions & 0 deletions EditorExtensions/Compilers/EditorCompilerInvoker.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Linq;
using EnvDTE;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text;
Expand Down Expand Up @@ -29,6 +30,7 @@ public EditorCompilerInvoker(ITextDocument doc, CompilerRunnerBase compilerRunne

///<summary>Releases all resources used by the EditorCompilerInvoker.</summary>
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }

///<summary>Releases the unmanaged resources used by the EditorCompilerInvoker and optionally releases the managed resources.</summary>
///<param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
Expand All @@ -38,6 +40,7 @@ protected virtual void Dispose(bool disposing)
Document.FileActionOccurred -= Document_FileActionOccurred;
}
}

private void Document_FileActionOccurred(object sender, TextDocumentFileActionEventArgs e)
{
if (e.FileActionType == FileActionTypes.ContentSavedToDisk || e.FileActionType == FileActionTypes.DocumentRenamed)
Expand All @@ -46,6 +49,7 @@ private void Document_FileActionOccurred(object sender, TextDocumentFileActionEv

///<summary>Occurs when the file has been compiled (on both success and failure).</summary>
public event EventHandler<CompilerResultEventArgs> CompilationReady;

///<summary>Raises the CompilationReady event.</summary>
///<param name="e">A CompilerResultEventArgs object that provides the event data.</param>
protected virtual void OnCompilationReady(CompilerResultEventArgs e)
Expand All @@ -72,6 +76,7 @@ public void RequestCompilationResult(bool cached)
IsSuccess = true,
Result = File.ReadAllText(targetPath)
}));

return;
}
}
Expand All @@ -81,6 +86,12 @@ async Task InitiateCompilationAsync(string sourcePath, bool save)
{
var result = await CompilerRunner.CompileAsync(sourcePath, save);
OnCompilationReady(new CompilerResultEventArgs(result));

if (WESettings.Instance.General.ChainCompilation && save && (Mef.GetChainCompilationContentTypes().Contains(CompilerRunner.SourceContentType)))
foreach (var source in ProjectHelpers.GetDependents(sourcePath))
{
result = await CompilerRunner.CompileToDefaultOutputAsync(source);
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions EditorExtensions/EditorExtensions.vsct
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,15 @@
<ButtonText>_references.js Intellisense file</ButtonText>
</Strings>
</Button>

<Button guid="guidEditorExtensionsCmdSet" id="cmdChainCompile" priority="0x2201" type="Button">
<Parent guid="guidSolutionExplorerCmdSet" id="MySubGroup"/>
<Icon guid="guidImages" id="bmpLogo"/>
<CommandFlag>DynamicVisibility</CommandFlag>
<Strings>
<ButtonText>Don't auto-compile when saving dependencies</ButtonText>
</Strings>
</Button>
</Buttons>

<Bitmaps>
Expand Down Expand Up @@ -797,6 +806,7 @@
<IDSymbol name="cmdidSha384Transform" value="0x0123" />
<IDSymbol name="cmdidSha512Transform" value="0x0124" />
<IDSymbol name="ReferenceJs" value="0x333" />
<IDSymbol name="cmdChainCompile" value="0x337" />
</GuidSymbol>

<GuidSymbol name="guidEditorLinesCmdSet" value="{e396b698-e00e-444b-9f5f-3dcb1ef74e67}">
Expand Down
2 changes: 2 additions & 0 deletions EditorExtensions/EditorExtensionsPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ protected override void Initialize()
ReferenceJsMenu referenceJsMenu = new ReferenceJsMenu(mcs);
CompressImageMenu compressImageMenu = new CompressImageMenu(DTE, mcs);
SpriteImageMenu spriteImageMenu = new SpriteImageMenu(DTE, mcs);
ChainCompilationMenu chainCompilationMenu = new ChainCompilationMenu(DTE, mcs);

HandleMenuVisibility(mcs);
referenceJsMenu.SetupCommands();
Expand All @@ -110,6 +111,7 @@ protected override void Initialize()
transform.SetupCommands();
compressImageMenu.SetupCommands();
spriteImageMenu.SetupCommands();
chainCompilationMenu.SetupCommands();
}

IconRegistration.RegisterIcons();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using MS.Internal.WindowsBase;

namespace MadsKristensen.EditorExtensions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using MS.Internal.WindowsBase;
using System.Windows.Threading;

namespace MadsKristensen.EditorExtensions
Expand Down
18 changes: 18 additions & 0 deletions EditorExtensions/Helpers/Mef.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.Html.Editor;
using Microsoft.VisualStudio.Utilities;
using Microsoft.Web.Editor;
using Microsoft.Web.Editor.Composition;
Expand Down Expand Up @@ -29,6 +30,23 @@ public static IEnumerable<string> GetSupportedExtensions<T>()
.SelectMany(fers.Value.GetExtensionsForContentType)
.Select(e => "." + e);
}

public static IEnumerable<IContentType> GetChainCompilationContentTypes()
{
return new[] {
ContentTypeManager.GetContentType("LESS"),
ContentTypeManager.GetContentType("SASS")
};
}

public static ISet<string> GetChainedCompileExtensions()
{
var fers = WebEditor.ExportProvider.GetExport<IFileExtensionRegistryService>();
return new HashSet<string>(GetChainCompilationContentTypes()
.SelectMany(fers.Value.GetExtensionsForContentType)
.Select(e => "*." + e));
}

public static T GetImport<T>(IContentType contentType) where T : class
{
return new ContentTypeImportComposer<T>(CompositionService).GetImport(contentType);
Expand Down
85 changes: 85 additions & 0 deletions EditorExtensions/Helpers/ProjectHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using EnvDTE;
using EnvDTE80;
using Microsoft.CSS.Core;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Editor;
Expand Down Expand Up @@ -479,5 +481,88 @@ public static ProjectItem AddFileToProject(string parentFileName, string fileNam

return null;
}

public static HashSet<string> ChainIgnoreList = new HashSet<string>();
static ConcurrentDictionary<string, IEnumerable<string>> DependencyDictionary = BuildDependencies();

// <summary>
// Call it once on project load,
// to get all dependencies for each
// LESS and SASS file.
// </summary>
// <returns>
// A Dictionary object with all LESS and SASS
// filenames under the Solution as keys and the
// list of their corresponding dependents as values.
// </returns>
public static ConcurrentDictionary<string, IEnumerable<string>> BuildDependencies()
{
var dependencies = new ConcurrentDictionary<string, IEnumerable<string>>();
var files = Mef.GetChainedCompileExtensions().SelectMany(e =>
Directory.EnumerateFiles(GetSolutionFolderPath(), e, SearchOption.AllDirectories));

foreach (var file in files)
{
var dependants = TraverseDependents(file).Where(d => !d.Equals(file));

if (!dependants.Any())
continue;

dependencies.GetOrAdd(file, dependants);
}

return dependencies;
}

// <summary>
// Depth-first-search to collect all
// dependents of a document.
// </summary>
// <param name="fileName">Full path to LESS or SASS file.</param>
// <returns>List of file paths depending on this document.</returns>
private static IEnumerable<string> TraverseDependents(string fileName)
{
var visited = new HashSet<string>();
var stack = new Stack<string>();

stack.Push(fileName);

while (stack.Count != 0)
{
var current = stack.Pop();

if (!visited.Add(current))
continue;

yield return current;

var distinctNeighbors = new CssItemAggregator<string> { (ImportDirective i) => i.FileName == null ? i.Url.UrlString.Text : i.FileName.Text }
.Crawl(new CssParser().Parse(File.ReadAllText(current), false))
.Select(s => Path.GetFullPath(Path.Combine(Path.GetDirectoryName(current), s.Trim("\"".ToCharArray()))))
.Where(n => !ChainIgnoreList.Contains(n) && !visited.Contains(n));

foreach (var neighbor in distinctNeighbors)
stack.Push(neighbor);
}
}

// <summary>
// When file is saved with Chain Compilation setting
// enabled, call this to update the global dictionary
// and to get dependents for recompilation.
// </summary>
// <param name="fileName">Full path to LESS or SASS file.</param>
// <returns>List of file paths of the dependent documents.</returns>
public static IEnumerable<string> GetDependents(string fileName)
{
var dependants = TraverseDependents(fileName).Where(d => !d.Equals(fileName));

if (!dependants.Any())
return new string[] { };

DependencyDictionary[fileName] = dependants;

return DependencyDictionary.Where(s => s.Value.Contains(fileName)).Select(s => s.Key);
}
}
}
4 changes: 2 additions & 2 deletions EditorExtensions/MenuItems/BuildMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using EnvDTE80;
using MadsKristensen.EditorExtensions.Compilers;
using MadsKristensen.EditorExtensions.Optimization.Minification;
using Microsoft.Html.Editor;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Utilities;
using Microsoft.Web.Editor;
Expand Down Expand Up @@ -81,13 +82,12 @@ private void Minify()
.SelectMany(p => Directory.EnumerateFiles(p, "*", SearchOption.AllDirectories))
.Where(f => extensions.Contains(Path.GetExtension(f)));

var extensionService = WebEditor.ExportProvider.GetExport<IFileExtensionRegistryService>();
var minifyService = WebEditor.ExportProvider.GetExport<MinificationSaveListener>();

// Perform expensive blocking work in parallel
Parallel.ForEach(files, file =>
minifyService.Value.ReMinify(
extensionService.Value.GetContentTypeForExtension(Path.GetExtension(file).TrimStart('.')),
ContentTypeManager.GetContentType(Path.GetExtension(file).TrimStart('.')),
file,
false
)
Expand Down
4 changes: 2 additions & 2 deletions EditorExtensions/MenuItems/BundleFiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -370,11 +370,11 @@ private static void WriteBundleFile(string bundleFilePath, XmlDocument doc)

if (bundleNode.Attributes["minify"] != null && bundleNode.Attributes["minify"].InnerText == "true")
{
WriteMinFile(bundleSourcePath, sb.ToString(), extension, bundleChanged);
WriteMinFile(bundleSourcePath, extension, bundleChanged);
}
}

private static void WriteMinFile(string bundleSourcePath, string content, string extension, bool bundleChanged)
private static void WriteMinFile(string bundleSourcePath, string extension, bool bundleChanged)
{
string minPath = Path.ChangeExtension(bundleSourcePath, ".min" + Path.GetExtension(bundleSourcePath));

Expand Down
53 changes: 53 additions & 0 deletions EditorExtensions/MenuItems/ChainCompilation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.IO;
using System.Linq;
using EnvDTE80;
using Microsoft.VisualStudio.Shell;

namespace MadsKristensen.EditorExtensions
{
internal class ChainCompilationMenu
{
public OleMenuCommand Command { get; private set; }

private DTE2 _dte;
private OleMenuCommandService _mcs;
private readonly IEnumerable<string> _sourceExtensions = Mef.GetChainedCompileExtensions();
private IEnumerable<string> selectedFiles;

public ChainCompilationMenu(DTE2 dte, OleMenuCommandService mcs)
{
_dte = dte;
_mcs = mcs;
}

public void SetupCommands()
{
PreCommand(CommandId.ChainCompile);
_mcs.AddCommand(Command);
}

public void PreCommand(CommandId id)
{
Mef.SatisfyImportsOnce(this);

Command = new OleMenuCommand((s, e) => Execute(), new CommandID(CommandGuids.guidEditorExtensionsCmdSet, (int)id));
Command.BeforeQueryStatus += (s, e) => CheckVisible();
}

private void CheckVisible()
{
selectedFiles = ProjectHelpers.GetSelectedFilePaths()
.Where(p => _sourceExtensions.Contains(Path.GetExtension(p)));
Command.Enabled = selectedFiles.Any();
}
private void Execute()
{
foreach (var file in selectedFiles)
{
ProjectHelpers.ChainIgnoreList.Add(file);
}
}
}
}
1 change: 0 additions & 1 deletion EditorExtensions/MenuItems/MinifyFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ internal class MinifyFileMenu
{
private DTE2 _dte;
private OleMenuCommandService _mcs;
private static List<string> _htmlExt = new List<string>() { ".html", ".htm", ".aspx", ".ascx", ".master", ".cshtml", ".vbhtml" };

public MinifyFileMenu(DTE2 dte, OleMenuCommandService mcs)
{
Expand Down
6 changes: 6 additions & 0 deletions EditorExtensions/Settings/WESettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public sealed class GeneralSettings : SettingsBase<GeneralSettings>, IMarginSett
[DefaultValue(false)]
public bool AllMessagesToOutputWindow { get; set; }

[Category("CSS")]
[DisplayName("Chain Compilation")]
[Description("Compile the dependents chain when a LESS or SASS file is saved.")]
[DefaultValue(false)]
public bool ChainCompilation { get; set; }

[Category("SVG Files")]
[DisplayName("Show preview pane")]
[Description("Show a preview pane when editing SVG files.")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public ItemCheckResult CheckItem(ParseItem item, ICssCheckerContext context)
int lineNo = FindLineNumber(dupe);

string errorMessage = string.Format(CultureInfo.InvariantCulture, Resources.BestPracticeDuplicateSelectors, lineNo);
SelectorErrorTag tag = new SelectorErrorTag(rule.Selectors, errorMessage);
SelectorErrorTag tag = new SelectorErrorTag(rule.Selectors);
context.AddError(tag);
}

Expand Down
Loading

0 comments on commit ed4a3ab

Please sign in to comment.