Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 78 additions & 6 deletions VSFormatOnSaveShared/AllowDenyDocumentFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,100 @@
// See https://github.com/Elders/VSE-FormatDocumentOnSave.

using System;
using System.IO;
using System.Linq;

namespace Tinyfish.FormatOnSave
{
public class AllowDenyPathFilter
{
private string[] _whitelistedPaths { get; set; }
private string[] _blacklistedPaths { get; set; }
public AllowDenyPathFilter(string[] whitelistPaths, string[] blacklistPaths)
{
_whitelistedPaths = whitelistPaths.Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
_blacklistedPaths = blacklistPaths.Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
}

// allowed if a path is whitelisted or not blacklisted
public bool IsAllowed(string path)
{
if (string.IsNullOrEmpty(path))
return true;

bool whiteListed = _whitelistedPaths.Any(x => IsUnderFolder(x, path));
if (whiteListed)
{
return true;
}
else
{
return !_blacklistedPaths.Any(x => IsUnderFolder(x, path));
}
}

private bool CheckIfParentDir(DirectoryInfo childDir, DirectoryInfo parentDir)
{
if (!parentDir.Exists)
return false;

bool isParent = false;
while (childDir.Parent != null)
{
if (childDir.Parent.FullName == parentDir.FullName)
{
isParent = true;
break;
}
else
{
childDir = childDir.Parent;
}
}
return isParent;
}

private bool IsUnderFolder(string parent, string child)
{
try
{
DirectoryInfo parentDir = new DirectoryInfo(parent.TrimEnd(Path.DirectorySeparatorChar));
DirectoryInfo childDir = new DirectoryInfo(child.TrimEnd(Path.DirectorySeparatorChar));

if (parentDir.FullName == childDir.FullName)
{
return true;
}

return CheckIfParentDir(childDir, parentDir);
}
catch
{
return false;
}
}
}

public class AllowDenyDocumentFilter
{
private readonly Func<string, bool> _isAllowed = fileName => true;
private readonly Func<string, string, AllowDenyPathFilter, bool> _isAllowed = (fileName, path, pathFilter) => pathFilter.IsAllowed(path);
private AllowDenyPathFilter _pathFilter;

public AllowDenyDocumentFilter(string[] allowedExtensions, string[] deniedExtensions)
public AllowDenyDocumentFilter(string[] allowedExtensions, string[] deniedExtensions, AllowDenyPathFilter pathfilter)
{
allowedExtensions = allowedExtensions?.Where(x => !x.Equals(".*") && !string.IsNullOrEmpty(x)).ToArray();
deniedExtensions = deniedExtensions?.Where(x => !x.Equals(".*") && !string.IsNullOrEmpty(x)).ToArray();
_pathFilter = pathfilter;

if (allowedExtensions != null && allowedExtensions.Any())
_isAllowed = fileName => allowedExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase));
_isAllowed = (fileName, path, _pathFilter) => allowedExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) && _pathFilter.IsAllowed(path);
else if (deniedExtensions != null && deniedExtensions.Any())
_isAllowed = fileName => !deniedExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase));
_isAllowed = (fileName, path, _pathFilter) => !deniedExtensions.Any(ext => fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) && _pathFilter.IsAllowed(path);
}

public bool IsAllowed(string fileName)
public bool IsAllowed(string fileName, string path)
{
return _isAllowed(fileName);
return _isAllowed(fileName, path, _pathFilter);
}
}
}
18 changes: 9 additions & 9 deletions VSFormatOnSaveShared/FormatOnSavePackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,32 +197,32 @@ public bool Format(Document document)
vsTextView.SetCaretPos(oldCaretLine, 0);

// Do TabToSpace before FormatDocument, since VS format may break the tab formatting.
if (OptionsPage.EnableTabToSpace && OptionsPage.AllowDenyTabToSpaceFilter.IsAllowed(document.Name))
if (OptionsPage.EnableTabToSpace && OptionsPage.AllowDenyTabToSpaceFilter.IsAllowed(document.Name, document.Path))
TabToSpace(wpfTextView, document.TabSize);

if (OptionsPage.EnableRemoveAndSort && OptionsPage.AllowDenyRemoveAndSortFilter.IsAllowed(document.Name)
if (OptionsPage.EnableRemoveAndSort && OptionsPage.AllowDenyRemoveAndSortFilter.IsAllowed(document.Name, document.Path)
&& ext == ".cs")
if (!OptionsPage.EnableSmartRemoveAndSort || !HasIfCompilerDirective(wpfTextView))
RemoveAndSort();

if (OptionsPage.EnableFormatDocument && OptionsPage.AllowDenyFormatDocumentFilter.IsAllowed(document.Name))
if (OptionsPage.EnableFormatDocument && OptionsPage.AllowDenyFormatDocumentFilter.IsAllowed(document.Name, document.Path))
FormatDocument(ext);

// Do TabToSpace again after FormatDocument, since VS2017 may stick to tab. Should remove this after VS2017 fix the bug.
// At 2021.10 the bug has gone. But VS seems to stick to space now, new bug?
// At 2023.01 when formatting in project/solution, with a .editorconfig set to space,
// FormatDocument use tab instead. It seems FormatDocument ignore .editorconfig, and only apply VS's settings.
if (OptionsPage.EnableTabToSpace && OptionsPage.AllowDenyTabToSpaceFilter.IsAllowed(document.Name)
if (OptionsPage.EnableTabToSpace && OptionsPage.AllowDenyTabToSpaceFilter.IsAllowed(document.Name, document.Path)
&& document.Language == "C/C++")
TabToSpace(wpfTextView, document.TabSize);

if (OptionsPage.EnableUnifyLineBreak && OptionsPage.AllowDenyUnifyLineBreakFilter.IsAllowed(document.Name))
UnifyLineBreak(wpfTextView, OptionsPage.ForceCRLFFilter.IsAllowed(document.Name));
if (OptionsPage.EnableUnifyLineBreak && OptionsPage.AllowDenyUnifyLineBreakFilter.IsAllowed(document.Name, document.Path))
UnifyLineBreak(wpfTextView, OptionsPage.ForceCRLFFilter.IsAllowed(document.Name, document.Path));

if (OptionsPage.EnableUnifyEndOfFile && OptionsPage.AllowDenyUnifyEndOfFileFilter.IsAllowed(document.Name))
if (OptionsPage.EnableUnifyEndOfFile && OptionsPage.AllowDenyUnifyEndOfFileFilter.IsAllowed(document.Name, document.Path))
UnifyEndOfFile(wpfTextView);

if (OptionsPage.EnableForceUtf8WithBom && OptionsPage.AllowDenyForceUtf8WithBomFilter.IsAllowed(document.Name))
if (OptionsPage.EnableForceUtf8WithBom && OptionsPage.AllowDenyForceUtf8WithBomFilter.IsAllowed(document.Name, document.Path))
ForceUtf8WithBom(wpfTextView);

// Caret stay in new line but keep old column.
Expand Down Expand Up @@ -374,7 +374,7 @@ private void FormatDocument(string ext)
// In VS2022, .razor and .cshtml file will delayed Edit.FormatDocument command, which modify document after saving.
// I try to capture the modification and save again.
if (MajorVersion >= 17
&& !OptionsPage.ImmediateFormatDocumentFilter.IsAllowed(ext))
&& !OptionsPage.ImmediateFormatDocumentFilter.IsAllowed(ext, null))
{
UpdateCaptureEvents();

Expand Down
30 changes: 22 additions & 8 deletions VSFormatOnSaveShared/OptionsPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,33 +158,47 @@ public enum LineBreakStyle

public AllowDenyDocumentFilter AllowDenyForceUtf8WithBomFilter;

[Category("Format document")]
[DisplayName("Format Folder Blacklist")]
[Description("Files in these folders and their children are denied, unless explicitly Whitelisted. Comma separated")]
public string BlacklistFolders { get; set; } = "";

[Category("Format document")]
[DisplayName("Format Folder Whitelist")]
[Description("Files in these folders and their children are allowed, even if their parent folders are blacklisted. Comma separated")]
public string WhitelistFolders { get; set; } = "";

public AllowDenyPathFilter AllowDenyUserPaths;

public event EventHandler OnSettingsUpdated;

public void UpdateSettings()
{
AllowDenyUserPaths = new AllowDenyPathFilter(WhitelistFolders.Split(','), BlacklistFolders.Split(','));

AllowDenyRemoveAndSortFilter = new AllowDenyDocumentFilter(
AllowRemoveAndSortExtensions.Split(' '), DenyRemoveAndSortExtensions.Split(' '));
AllowRemoveAndSortExtensions.Split(' '), DenyRemoveAndSortExtensions.Split(' '), AllowDenyUserPaths);

AllowDenyFormatDocumentFilter = new AllowDenyDocumentFilter(
AllowFormatDocumentExtentions.Split(' '), DenyFormatDocumentExtentions.Split(' '));
AllowFormatDocumentExtentions.Split(' '), DenyFormatDocumentExtentions.Split(' '), AllowDenyUserPaths);

ImmediateFormatDocumentFilter = new AllowDenyDocumentFilter(null, DelayedFormatDocumentExtentions.Split(' '));
ImmediateFormatDocumentFilter = new AllowDenyDocumentFilter(null, DelayedFormatDocumentExtentions.Split(' '), AllowDenyUserPaths);

AllowDenyUnifyLineBreakFilter = new AllowDenyDocumentFilter(
AllowUnifyLineBreakExtensions.Split(' '), DenyUnifyLineBreakExtensions.Split(' '));
AllowUnifyLineBreakExtensions.Split(' '), DenyUnifyLineBreakExtensions.Split(' '), AllowDenyUserPaths);

if (string.IsNullOrWhiteSpace(ForceCRLFExtensions))
ForceCRLFExtensions = ".aspx";
ForceCRLFFilter = new AllowDenyDocumentFilter(ForceCRLFExtensions.Split(' '), null);
ForceCRLFFilter = new AllowDenyDocumentFilter(ForceCRLFExtensions.Split(' '), null, AllowDenyUserPaths);

AllowDenyUnifyEndOfFileFilter = new AllowDenyDocumentFilter(
AllowUnifyEndOfFileExtensions.Split(' '), DenyUnifyEndOfFileExtensions.Split(' '));
AllowUnifyEndOfFileExtensions.Split(' '), DenyUnifyEndOfFileExtensions.Split(' '), AllowDenyUserPaths);

AllowDenyTabToSpaceFilter = new AllowDenyDocumentFilter(
AllowTabToSpaceExtensions.Split(' '), DenyTabToSpaceExtensions.Split(' '));
AllowTabToSpaceExtensions.Split(' '), DenyTabToSpaceExtensions.Split(' '), AllowDenyUserPaths);

AllowDenyForceUtf8WithBomFilter = new AllowDenyDocumentFilter(
AllowForceUtf8WithBomExtentions.Split(' '), DenyForceUtf8WithBomExtentions.Split(' '));
AllowForceUtf8WithBomExtentions.Split(' '), DenyForceUtf8WithBomExtentions.Split(' '), AllowDenyUserPaths);

OnSettingsUpdated?.Invoke(this, null);
}
Expand Down
12 changes: 6 additions & 6 deletions VSFormatOnSaveShared/SolutionExplorerContextMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,12 @@ private void FormatItem(object item)
private void FormatProjectItem(ProjectItem item)
{
ThreadHelper.ThrowIfNotOnUIThread();
if (!((_package.OptionsPage.EnableRemoveAndSort && _package.OptionsPage.AllowDenyRemoveAndSortFilter.IsAllowed(item.Name))
|| (_package.OptionsPage.EnableFormatDocument && _package.OptionsPage.AllowDenyFormatDocumentFilter.IsAllowed(item.Name))
|| (_package.OptionsPage.EnableUnifyLineBreak && _package.OptionsPage.AllowDenyUnifyLineBreakFilter.IsAllowed(item.Name))
|| (_package.OptionsPage.EnableUnifyEndOfFile && _package.OptionsPage.AllowDenyUnifyEndOfFileFilter.IsAllowed(item.Name))
|| (_package.OptionsPage.EnableTabToSpace && _package.OptionsPage.AllowDenyTabToSpaceFilter.IsAllowed(item.Name))
|| (_package.OptionsPage.EnableForceUtf8WithBom && _package.OptionsPage.AllowDenyForceUtf8WithBomFilter.IsAllowed(item.Name)))
if (!((_package.OptionsPage.EnableRemoveAndSort && _package.OptionsPage.AllowDenyRemoveAndSortFilter.IsAllowed(item.Name, item.Document.Path))
|| (_package.OptionsPage.EnableFormatDocument && _package.OptionsPage.AllowDenyFormatDocumentFilter.IsAllowed(item.Name, item.Document.Path))
|| (_package.OptionsPage.EnableUnifyLineBreak && _package.OptionsPage.AllowDenyUnifyLineBreakFilter.IsAllowed(item.Name, item.Document.Path))
|| (_package.OptionsPage.EnableUnifyEndOfFile && _package.OptionsPage.AllowDenyUnifyEndOfFileFilter.IsAllowed(item.Name, item.Document.Path))
|| (_package.OptionsPage.EnableTabToSpace && _package.OptionsPage.AllowDenyTabToSpaceFilter.IsAllowed(item.Name, item.Document.Path))
|| (_package.OptionsPage.EnableForceUtf8WithBom && _package.OptionsPage.AllowDenyForceUtf8WithBomFilter.IsAllowed(item.Name, item.Document.Path)))
)
return;

Expand Down