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
70 changes: 49 additions & 21 deletions SmartCmdArgs/SmartCmdArgs.Shared/Services/ItemAggregationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public ItemAggregationService(
this.treeViewModel = treeViewModel;
}

private TResult AggregateComamndLineItemsForProject<TResult>(IVsHierarchyWrapper project, Func<IEnumerable<CmdBase>, Func<CmdContainer, TResult>, CmdContainer, TResult> joinItems)
private TResult AggregateComamndLineItemsForProject<TResult>(IVsHierarchyWrapper project, Func<IEnumerable<CmdBase>, Func<CmdContainer, TResult>, CmdContainer, TResult> joinItems, bool includeUnchecked = false)
{
if (project == null)
return default;
Expand All @@ -54,8 +54,10 @@ private TResult AggregateComamndLineItemsForProject<TResult>(IVsHierarchyWrapper

TResult JoinContainer(CmdContainer con)
{
IEnumerable<CmdBase> items = con.Items
.Where(x => x.IsChecked != false);
IEnumerable<CmdBase> items = con.Items;

if (!includeUnchecked)
items = items.Where(x => x.IsChecked != false);

if (projConfig != null)
items = items.Where(x => { var conf = x.UsedProjectConfig; return conf == null || conf == projConfig; });
Expand All @@ -73,6 +75,9 @@ TResult JoinContainer(CmdContainer con)
}

public IEnumerable<CmdParameter> GetAllComamndLineParamsForProject(IVsHierarchyWrapper project)
=> GetAllComamndLineParamsForProject(project, includeUnchecked: false);

private IEnumerable<CmdParameter> GetAllComamndLineParamsForProject(IVsHierarchyWrapper project, bool includeUnchecked)
{
IEnumerable<CmdParameter> joinItems(IEnumerable<CmdBase> items, Func<CmdContainer, IEnumerable<CmdParameter>> joinContainer, CmdContainer parentContainer)
{
Expand All @@ -90,7 +95,8 @@ IEnumerable<CmdParameter> joinItems(IEnumerable<CmdBase> items, Func<CmdContaine
}
}

return AggregateComamndLineItemsForProject<IEnumerable<CmdParameter>>(project, joinItems);
return AggregateComamndLineItemsForProject<IEnumerable<CmdParameter>>(project, joinItems, includeUnchecked)
?? Enumerable.Empty<CmdParameter>();
}

public string CreateCommandLineArgsForProject(IVsHierarchyWrapper project)
Expand All @@ -113,12 +119,20 @@ public string CreateCommandLineArgsForProject(IVsHierarchyWrapper project)

public IDictionary<string, string> GetEnvVarsForProject(IVsHierarchyWrapper project)
{
// If the project has no environment variable items at all, this extension does not manage the
// environment for it. Returning null (instead of an empty dictionary) prevents the caller from
// overwriting the environment which is defined by the project itself.
var items = GetAllComamndLineParamsForProject(project, includeUnchecked: true)
.Where(x => x.ParamType == CmdParamType.EnvVar)
.ToList();

if (items.Count == 0)
return null;

var result = new Dictionary<string, string>();

foreach (var item in GetAllComamndLineParamsForProject(project))
foreach (var item in items.Where(x => x.IsChecked != false))
{
if (item.ParamType != CmdParamType.EnvVar) continue;

if (itemEvaluation.TryParseEnvVar(item.Value, out EnvVar envVar))
{
result[envVar.Name] = itemEvaluation.EvaluateMacros(envVar.Value, project);
Expand All @@ -128,32 +142,46 @@ public IDictionary<string, string> GetEnvVarsForProject(IVsHierarchyWrapper proj
return result;
}

public string GetWorkDirForProject(IVsHierarchyWrapper project)
/// <summary>
/// Returns the value of the last active parameter of the given type or
/// <c>null</c> if the project has no parameter of that type at all.
/// <para>
/// Returning <c>null</c> is important: it tells the caller that this extension does not manage
/// this setting for the project, so the project's own value must be left untouched. Returning an
/// empty string instead would overwrite (and thereby erase) settings like the debugger command or
/// the working directory that are defined by the project itself.
/// </para>
/// </summary>
private string GetSingleParamValueForProject(IVsHierarchyWrapper project, CmdParamType paramType)
{
// Unchecked items are included here on purpose. If the project has items of this type but all
// of them are unchecked, the user explicitly disabled them and we return an empty string to
// clear the setting. Only if there is no item of this type at all we return null.
var items = GetAllComamndLineParamsForProject(project, includeUnchecked: true)
.Where(x => x.ParamType == paramType)
.ToList();

if (items.Count == 0)
return null;

var result = "";

foreach (var item in GetAllComamndLineParamsForProject(project))
foreach (var item in items.Where(x => x.IsChecked != false))
{
if (item.ParamType != CmdParamType.WorkDir) continue;

result = itemEvaluation.EvaluateMacros(item.Value, project);
}

return result;
}

public string GetLaunchAppForProject(IVsHierarchyWrapper project)
public string GetWorkDirForProject(IVsHierarchyWrapper project)
{
var result = "";

foreach (var item in GetAllComamndLineParamsForProject(project))
{
if (item.ParamType != CmdParamType.LaunchApp) continue;

result = itemEvaluation.EvaluateMacros(item.Value, project);
}
return GetSingleParamValueForProject(project, CmdParamType.WorkDir);
}

return result;
public string GetLaunchAppForProject(IVsHierarchyWrapper project)
{
return GetSingleParamValueForProject(project, CmdParamType.LaunchApp);
}

public string CreateCommandLineArgsForProject(Guid guid)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,88 @@ public async Task GetWorkDirForProject_ShouldReturnWorkDir()
Assert.Equal("WorkDir", result);
}

[Fact]
public async Task GetWorkDirForProject_ShouldReturnNull_WhenProjectHasNoWorkDirItem()
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

// Arrange
var projectGuid = Guid.NewGuid();
var project = new Mock<IVsHierarchyWrapper>().Register(vsHelperServiceMock, projectGuid).Object;
var cmdArg = new CmdParameter(Guid.NewGuid(), CmdParamType.CmdArg, "arg1", isChecked: true);
var cmdProject = new CmdProject(projectGuid, Guid.Empty, "TestProject", new[] { cmdArg }, false, false, " ", "", "");

treeViewModel.Projects.Add(projectGuid, cmdProject);

// Act
var result = itemAggregationService.GetWorkDirForProject(project);

// Assert
// null means "not managed by this extension" so the project's own working directory is kept
Assert.Null(result);
}

[Fact]
public async Task GetWorkDirForProject_ShouldReturnEmpty_WhenWorkDirItemIsUnchecked()
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

// Arrange
var projectGuid = Guid.NewGuid();
var project = new Mock<IVsHierarchyWrapper>().Register(vsHelperServiceMock, projectGuid).Object;
var workDirArg = new CmdParameter(Guid.NewGuid(), CmdParamType.WorkDir, "WorkDir", isChecked: false);
var cmdProject = new CmdProject(projectGuid, Guid.Empty, "TestProject", new[] { workDirArg }, false, false, " ", "", "");

treeViewModel.Projects.Add(projectGuid, cmdProject);

// Act
var result = itemAggregationService.GetWorkDirForProject(project);

// Assert
// the item exists but was disabled by the user, so the setting is cleared
Assert.Equal("", result);
}

[Fact]
public async Task GetLaunchAppForProject_ShouldReturnNull_WhenProjectHasNoLaunchAppItem()
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

// Arrange
var projectGuid = Guid.NewGuid();
var project = new Mock<IVsHierarchyWrapper>().Register(vsHelperServiceMock, projectGuid).Object;
var cmdArg = new CmdParameter(Guid.NewGuid(), CmdParamType.CmdArg, "arg1", isChecked: true);
var cmdProject = new CmdProject(projectGuid, Guid.Empty, "TestProject", new[] { cmdArg }, false, false, " ", "", "");

treeViewModel.Projects.Add(projectGuid, cmdProject);

// Act
var result = itemAggregationService.GetLaunchAppForProject(project);

// Assert
Assert.Null(result);
}

[Fact]
public async Task GetEnvVarsForProject_ShouldReturnNull_WhenProjectHasNoEnvVarItem()
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

// Arrange
var projectGuid = Guid.NewGuid();
var project = new Mock<IVsHierarchyWrapper>().Register(vsHelperServiceMock, projectGuid).Object;
var cmdArg = new CmdParameter(Guid.NewGuid(), CmdParamType.CmdArg, "arg1", isChecked: true);
var cmdProject = new CmdProject(projectGuid, Guid.Empty, "TestProject", new[] { cmdArg }, false, false, " ", "", "");

treeViewModel.Projects.Add(projectGuid, cmdProject);

// Act
var result = itemAggregationService.GetEnvVarsForProject(project);

// Assert
Assert.Null(result);
}

[Fact]
public async Task CreateCommandLineArgsForProject_WithGuid_ShouldCreateCommandLineArgs()
{
Expand Down