Skip to content
Merged
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
6 changes: 5 additions & 1 deletion VisualStudio.Tests/CommandFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@ public void when_creating_command_with_help_argument_then_throws_show_usage()
[InlineData("where", typeof(WhereCommand))]
[InlineData("modify", typeof(ModifyCommand))]
[InlineData("update", typeof(UpdateCommand))]

[InlineData("config", typeof(ConfigCommand))]
[InlineData("log", typeof(LogCommand))]
[InlineData("kill", typeof(KillCommand))]
public void when_creating_builtin_command_then_then_command_is_created(string commandName, Type expectedCommandType)
{
var commandFactory = new CommandFactory();

Assert.True(commandFactory.IsCommandRegistered(commandName));

var command = commandFactory.CreateCommand(commandName, Enumerable.Empty<string>());

Assert.NotNull(command);
Expand Down
23 changes: 23 additions & 0 deletions VisualStudio/AllOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using Mono.Options;

namespace VisualStudio
{
public class AllOption : OptionSet
{
public AllOption(string verb)
{
Add("all", $"{verb} all instances.", e => All = e != null);
}

protected override bool Parse(string argument, OptionContext c)
{
if ("all".Equals(argument, StringComparison.OrdinalIgnoreCase))
argument = "--all";

return base.Parse(argument, c);
}

public bool All { get; private set; }
}
}
45 changes: 45 additions & 0 deletions VisualStudio/Chooser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using vswhere;
using System.Diagnostics;

namespace VisualStudio
{
class Chooser
{
readonly string title;

public Chooser(string verb = "run") => title = $"Multiple instances found. Select the one to {verb}:";

public T Choose<T>(IEnumerable<T> instances, TextWriter output)
{
var instancesAsList = instances.ToList();

if (instancesAsList.Count > 1)
{
output.WriteLine(title);
for (int i = 0; i < instancesAsList.Count; i++)
output.WriteLine($"{i + 1}: {GetItemDescription(instancesAsList[i])}");

if (int.TryParse(Console.ReadLine(), out var index) && index > 0 && index <= instancesAsList.Count)
return instancesAsList[index - 1];

return default;
}

return instances.FirstOrDefault();
}

string GetItemDescription(object value)
{
if (value is VisualStudioInstance visualStudioInstance)
return $"{ visualStudioInstance.DisplayName} - Version { visualStudioInstance.Catalog.ProductDisplayVersion}";
else if (value is Process process)
return $"{process.MainWindowTitle} ({process.Id})";

return value.ToString();
}
}
}
1 change: 1 addition & 0 deletions VisualStudio/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public CommandFactory()
RegisterCommand<ModifyCommandDescriptor>("modify", x => new ModifyCommand(x, whereService, installerService));
RegisterCommand<ConfigCommandDescriptor>("config", x => new ConfigCommand(x, whereService));
RegisterCommand<LogCommandDescriptor>("log", x => new LogCommand(x, whereService));
RegisterCommand<KillCommandDescriptor>("kill", x => new KillCommand(x, whereService));
}

public IEnumerable<string> RegisteredCommands => factories.Keys;
Expand Down
2 changes: 1 addition & 1 deletion VisualStudio/ConfigCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public ConfigCommand(ConfigCommandDescriptor descriptor, WhereService whereServi
public override async Task ExecuteAsync(TextWriter output)
{
var instances = await whereService.GetAllInstancesAsync(Descriptor.Sku, Descriptor.Channel);
var instance = new VisualStudioInstanceChooser().Choose(instances, output);
var instance = new Chooser().Choose(instances, output);

if (instance != null)
{
Expand Down
46 changes: 46 additions & 0 deletions VisualStudio/KillCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System;
using System.Linq;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using vswhere;

namespace VisualStudio
{
class KillCommand : Command<KillCommandDescriptor>
{
readonly WhereService whereService;

public KillCommand(KillCommandDescriptor descriptor, WhereService whereService) : base(descriptor) =>
this.whereService = whereService;

public override async Task ExecuteAsync(TextWriter output)
{
var devenvProcesses = Process.GetProcessesByName("devenv").ToList();
var targetProcesses =
(from instance in await whereService.GetAllInstancesAsync(Descriptor.Sku, Descriptor.Channel)
from devenvProcess in devenvProcesses
where Match(devenvProcess, instance)
select devenvProcess).Distinct().ToList();

if (!Descriptor.KillAll)
{
var process = new Chooser("kill").Choose(targetProcesses, output);

targetProcesses.Clear();
if (process != null)
targetProcesses.Add(process);
}

foreach (var process in targetProcesses)
{
output.WriteLine($"Killing {process.MainWindowTitle} ({process.Id})...");
process.Kill();
}
}

bool Match(Process devenvProcess, VisualStudioInstance instance) =>
devenvProcess.MainModule.FileName.StartsWith(instance.InstallationPath, StringComparison.OrdinalIgnoreCase) &&
(!Descriptor.IsExperimental || devenvProcess.GetCommandLine().Contains("/rootSuffix Exp", StringComparison.OrdinalIgnoreCase));
}
}
21 changes: 21 additions & 0 deletions VisualStudio/KillCommandDescriptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using Mono.Options;

namespace VisualStudio
{
class KillCommandDescriptor : CommandDescriptor
{
readonly VisualStudioOptions options = new VisualStudioOptions(channelVerb: "Kill", showNickname: false, showExp: true);
readonly AllOption allOption = new AllOption("Kill");

public KillCommandDescriptor() => OptionSet = new CompositeOptionSet(options, allOption);

public Channel? Channel => options.Channel;

public Sku? Sku => options.Sku;

public bool IsExperimental => options.IsExperimental;

public bool KillAll => allOption.All;
}
}
4 changes: 2 additions & 2 deletions VisualStudio/LogCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ public LogCommand(LogCommandDescriptor descriptor, WhereService whereService) :
public override async Task ExecuteAsync(TextWriter output)
{
var instances = await whereService.GetAllInstancesAsync(Descriptor.Sku, Descriptor.Channel);
var instance = new VisualStudioInstanceChooser().Choose(instances, output);
var instance = new Chooser().Choose(instances, output);

if (instance != null)
{
var instanceDir = instance.InstallationVersion.Major + ".0_" + instance.InstanceId;
if (Descriptor.Experimental)
if (Descriptor.IsExperimental)
instanceDir += "Exp";

var path = Path.Combine(
Expand Down
10 changes: 3 additions & 7 deletions VisualStudio/LogCommandDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,14 @@ namespace VisualStudio
{
class LogCommandDescriptor : CommandDescriptor
{
readonly VisualStudioOptions options = new VisualStudioOptions(channelVerb: "Open", showNickname: false);
bool exp;
readonly VisualStudioOptions options = new VisualStudioOptions(channelVerb: "Open", showNickname: false, showExp: true);

public LogCommandDescriptor() => OptionSet = new CompositeOptionSet(options, new OptionSet
{
{ "exp", "Use experimental instance instead of regular.", e => exp = e != null },
});
public LogCommandDescriptor() => OptionSet = new CompositeOptionSet(options);

public Channel? Channel => options.Channel;

public Sku? Sku => options.Sku;

public bool Experimental => exp;
public bool IsExperimental => options.IsExperimental;
}
}
2 changes: 1 addition & 1 deletion VisualStudio/ModifyCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public override async Task ExecuteAsync(TextWriter output)
{
var instances = await whereService.GetAllInstancesAsync(Descriptor.Sku, Descriptor.Channel);

var instance = new VisualStudioInstanceChooser().Choose(instances, output);
var instance = new Chooser().Choose(instances, output);

if (instance != null)
{
Expand Down
19 changes: 18 additions & 1 deletion VisualStudio/ProcessExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Diagnostics;
using System;
using System.Linq;
using System.Diagnostics;
using System.IO;
using System.Management;

namespace VisualStudio
{
Expand All @@ -19,5 +22,19 @@ public static void Log(this ProcessStartInfo info, TextWriter output)
}

static string Quote(string value) => value.Contains(' ') ? "\"" + value + "\"" : value;

public static string GetCommandLine(this Process process)
{
try
{
using (var searcher = new ManagementObjectSearcher("SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + process.Id))
using (var objects = searcher.Get())
return objects.OfType<ManagementBaseObject>().FirstOrDefault()?["CommandLine"]?.ToString();
}
catch
{
return string.Empty;
}
}
}
}
4 changes: 2 additions & 2 deletions VisualStudio/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"profiles": {
"VisualStudio": {
"commandName": "Project",
"commandLineArgs": "config -pre -exp"
"commandLineArgs": "kill all exp"
}
}
}
}
2 changes: 1 addition & 1 deletion VisualStudio/UpdateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public override async Task ExecuteAsync(TextWriter output)
{
var instances = await whereService.GetAllInstancesAsync(Descriptor.Sku, Descriptor.Channel);

var instance = new VisualStudioInstanceChooser().Choose(instances, output);
var instance = new Chooser().Choose(instances, output);

if (instance != null)
{
Expand Down
1 change: 1 addition & 0 deletions VisualStudio/VisualStudio.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<PackageReference Include="vswhere" Version="2.8.4" PrivateAssets="all" />
<PackageReference Include="Mono.Options" Version="6.6.0.161" />
<PackageReference Include="MSBuilder.ThisAssembly.Metadata" Version="0.1.4" PrivateAssets="all" />
<PackageReference Include="System.Management" Version="5.0.0-preview.3.20214.6" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand Down
32 changes: 0 additions & 32 deletions VisualStudio/VisualStudioInstanceChooser.cs

This file was deleted.

18 changes: 17 additions & 1 deletion VisualStudio/VisualStudioOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ public class VisualStudioOptions : OptionSet
{
string[] channelShortcuts = new[] { "pre", "preview", "int", "internal", "master" };
string[] skuShortcuts = new[] { "e", "ent", "enterprise", "p", "pro", "professional", "c", "com", "community" };
string[] experimentalShortcuts = new[] { "exp", "experimental" };

public VisualStudioOptions(string channelVerb = "Install", bool showChannel = true, bool showSku = true, bool showNickname = true)
public VisualStudioOptions(
string channelVerb = "Install",
bool showChannel = true,
bool showSku = true,
bool showNickname = true,
bool showExp = false)
{
if (showChannel)
{
Expand All @@ -34,6 +40,11 @@ public VisualStudioOptions(string channelVerb = "Install", bool showChannel = tr
// Nickname
Add("nick|nickname:", "Optional nickname to use", n => Nickname = n);
}

if (showExp)
{
Add("exp|experimental", $"{channelVerb} experimental instance instead of regular.", e => IsExperimental = e != null);
}
}

Sku ParseSku(string sku)
Expand All @@ -56,6 +67,9 @@ protected override bool Parse(string argument, OptionContext c)
if (skuShortcuts.Contains(argument.ToLowerInvariant()))
argument = "--sku=" + argument;

if (experimentalShortcuts.Contains(argument.ToLowerInvariant()))
argument = "--" + argument;

return base.Parse(argument, c);
}

Expand All @@ -64,5 +78,7 @@ protected override bool Parse(string argument, OptionContext c)
public Sku? Sku { get; private set; }

public string Nickname { get; private set; }

public bool IsExperimental { get; private set; }
}
}