Show 32/64bit in VS Code headings on Windows (#15018)

* Show 32/64bit in VS Code headings on Windows

Fixes #14949.
diff --git a/packages/flutter_tools/lib/src/vscode/vscode.dart b/packages/flutter_tools/lib/src/vscode/vscode.dart
index b8b8395..9671696 100644
--- a/packages/flutter_tools/lib/src/vscode/vscode.dart
+++ b/packages/flutter_tools/lib/src/vscode/vscode.dart
@@ -15,7 +15,7 @@
 class VsCode {
   static const String extensionIdentifier = 'Dart-Code.dart-code';
 
-  VsCode._(this.directory, this.extensionDirectory, { Version version })
+  VsCode._(this.directory, this.extensionDirectory, { Version version, this.edition })
       : this.version = version ?? Version.unknown {
 
     if (!fs.isDirectorySync(directory)) {
@@ -50,22 +50,25 @@
   final String directory;
   final String extensionDirectory;
   final Version version;
+  final String edition;
 
   bool _isValid = false;
   Version _extensionVersion;
   final List<String> _validationMessages = <String>[];
 
-  factory VsCode.fromDirectory(String installPath, String extensionDirectory) {
+  factory VsCode.fromDirectory(String installPath, String extensionDirectory,
+      { String edition }) {
     final String packageJsonPath =
         fs.path.join(installPath, 'resources', 'app', 'package.json');
     final String versionString = _getVersionFromPackageJson(packageJsonPath);
     Version version;
     if (versionString != null)
       version = new Version.parse(versionString);
-    return new VsCode._(installPath, extensionDirectory, version: version);
+    return new VsCode._(installPath, extensionDirectory, version: version, edition: edition);
   }
 
   bool get isValid => _isValid;
+  String get productName => 'VS Code' + (edition != null ? ', $edition' : '');
 
   Iterable<String> get validationMessages => _validationMessages;
 
@@ -90,21 +93,26 @@
   //   $HOME/.vscode/extensions
   //   $HOME/.vscode-insiders/extensions
   static List<VsCode> _installedMacOS() {
-    final Map<String, String> stable = <String, String>{
-      fs.path.join('/Applications', 'Visual Studio Code.app', 'Contents'):
-          '.vscode',
-      fs.path.join(homeDirPath, 'Applications', 'Visual Studio Code.app',
-          'Contents'): '.vscode'
-    };
-    final Map<String, String> insiders = <String, String>{
-      fs.path.join(
-              '/Applications', 'Visual Studio Code - Insiders.app', 'Contents'):
-          '.vscode-insiders',
-      fs.path.join(homeDirPath, 'Applications',
-          'Visual Studio Code - Insiders.app', 'Contents'): '.vscode-insiders'
-    };
-
-    return _findInstalled(stable, insiders);
+    return _findInstalled(<_VsCodeInstallLocation>[
+      new _VsCodeInstallLocation(
+        fs.path.join('/Applications', 'Visual Studio Code.app', 'Contents'),
+        '.vscode',
+      ),
+      new _VsCodeInstallLocation(
+        fs.path.join(homeDirPath, 'Applications', 'Visual Studio Code.app', 'Contents'),
+        '.vscode',
+      ),
+      new _VsCodeInstallLocation(
+        fs.path.join('/Applications', 'Visual Studio Code - Insiders.app', 'Contents'),
+        '.vscode-insiders',
+        isInsiders: true,
+      ),
+      new _VsCodeInstallLocation(
+        fs.path.join(homeDirPath, 'Applications', 'Visual Studio Code - Insiders.app', 'Contents'),
+        '.vscode-insiders',
+        isInsiders: true,
+      )
+    ]);
   }
 
   // Windows:
@@ -120,17 +128,16 @@
     final String progFiles86 = platform.environment['programfiles(x86)'];
     final String progFiles = platform.environment['programfiles'];
 
-    final Map<String, String> stable = <String, String>{
-      fs.path.join(progFiles86, 'Microsoft VS Code'): '.vscode',
-      fs.path.join(progFiles, 'Microsoft VS Code'): '.vscode'
-    };
-    final Map<String, String> insiders = <String, String>{
-      fs.path.join(progFiles86, 'Microsoft VS Code Insiders'):
-          '.vscode-insiders',
-      fs.path.join(progFiles, 'Microsoft VS Code Insiders'): '.vscode-insiders'
-    };
-
-    return _findInstalled(stable, insiders);
+    return _findInstalled(<_VsCodeInstallLocation>[
+      new _VsCodeInstallLocation(fs.path.join(progFiles86, 'Microsoft VS Code'), '.vscode',
+          edition: '32-bit edition'),
+      new _VsCodeInstallLocation(fs.path.join(progFiles, 'Microsoft VS Code'), '.vscode',
+          edition: '64-bit edition'),
+      new _VsCodeInstallLocation(fs.path.join(progFiles86 , 'Microsoft VS Code Insiders'), '.vscode-insiders',
+          edition: '32-bit edition', isInsiders: true),
+      new _VsCodeInstallLocation(fs.path.join(progFiles, 'Microsoft VS Code Insiders'), '.vscode-insiders',
+          edition: '64-bit edition', isInsiders: true),
+    ]);
   }
 
   // Linux:
@@ -140,26 +147,26 @@
   //   $HOME/.vscode/extensions
   //   $HOME/.vscode-insiders/extensions
   static List<VsCode> _installedLinux() {
-    return _findInstalled(
-      <String, String>{'/usr/share/code': '.vscode'},
-      <String, String>{'/usr/share/code-insiders': '.vscode-insiders'}
-    );
+    return _findInstalled(<_VsCodeInstallLocation>[
+      const _VsCodeInstallLocation('/usr/share/code', '.vscode'),
+      const _VsCodeInstallLocation('/usr/share/code-insiders', '.vscode-insiders', isInsiders: true),
+    ]);
   }
 
   static List<VsCode> _findInstalled(
-      Map<String, String> stable, Map<String, String> insiders) {
-    final Map<String, String> allPaths = <String, String>{};
-    allPaths.addAll(stable);
-    if (_includeInsiders)
-      allPaths.addAll(insiders);
+      List<_VsCodeInstallLocation> allLocations) {
+    final Iterable<_VsCodeInstallLocation> searchLocations = 
+      _includeInsiders
+        ? allLocations
+        : allLocations.where((_VsCodeInstallLocation p) => p.isInsiders != true);
 
     final List<VsCode> results = <VsCode>[];
 
-    for (String directory in allPaths.keys) {
-      if (fs.directory(directory).existsSync()) {
+    for (_VsCodeInstallLocation searchLocation in searchLocations) {
+      if (fs.directory(searchLocation.installPath).existsSync()) {
         final String extensionDirectory =
-            fs.path.join(homeDirPath, allPaths[directory], 'extensions');
-        results.add(new VsCode.fromDirectory(directory, extensionDirectory));
+            fs.path.join(homeDirPath, searchLocation.extensionsFolder, 'extensions');
+        results.add(new VsCode.fromDirectory(searchLocation.installPath, extensionDirectory, edition: searchLocation.edition));
       }
     }
 
@@ -178,3 +185,12 @@
     return json['version'];
   }
 }
+
+class _VsCodeInstallLocation {
+  final String installPath;
+  final String extensionsFolder;
+  final String edition;
+  final bool isInsiders;
+  const _VsCodeInstallLocation(this.installPath, this.extensionsFolder, { this.edition, bool isInsiders })
+    : this.isInsiders = isInsiders ?? false;
+}
diff --git a/packages/flutter_tools/lib/src/vscode/vscode_validator.dart b/packages/flutter_tools/lib/src/vscode/vscode_validator.dart
index d27dcc2..23340c7 100644
--- a/packages/flutter_tools/lib/src/vscode/vscode_validator.dart
+++ b/packages/flutter_tools/lib/src/vscode/vscode_validator.dart
@@ -13,7 +13,7 @@
     'https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code';
   final VsCode _vsCode;
 
-  VsCodeValidator(this._vsCode) : super('VS Code');
+  VsCodeValidator(this._vsCode) : super(_vsCode.productName);
 
   static Iterable<DoctorValidator> get installedValidators {
     return VsCode
diff --git a/packages/flutter_tools/test/commands/doctor_test.dart b/packages/flutter_tools/test/commands/doctor_test.dart
index ea2a3c9..d611694 100644
--- a/packages/flutter_tools/test/commands/doctor_test.dart
+++ b/packages/flutter_tools/test/commands/doctor_test.dart
@@ -50,6 +50,22 @@
       expect(message.message, 'Dart Code extension version 4.5.6');
     });
 
+    testUsingContext('vs code validator when 64bit installed', () async {
+      expect(VsCodeValidatorTestTargets.installedWithExtension64bit.title, 'VS Code, 64-bit edition');
+      final ValidationResult result = await VsCodeValidatorTestTargets.installedWithExtension64bit.validate();
+      expect(result.type, ValidationType.installed);
+      expect(result.statusInfo, 'version 1.2.3');
+      expect(result.messages, hasLength(2));
+
+      ValidationMessage message = result.messages
+          .firstWhere((ValidationMessage m) => m.message.startsWith('VS Code '));
+      expect(message.message, 'VS Code at ${VsCodeValidatorTestTargets.validInstall}');
+
+      message = result.messages
+          .firstWhere((ValidationMessage m) => m.message.startsWith('Dart Code '));
+      expect(message.message, 'Dart Code extension version 4.5.6');
+    });
+
     testUsingContext('vs code validator when extension missing', () async {
       final ValidationResult result = await VsCodeValidatorTestTargets.installedWithoutExtension.validate();
       expect(result.type, ValidationType.partial);
@@ -279,12 +295,15 @@
   static final String validInstall = fs.path.join('test', 'data', 'vscode', 'application');
   static final String validExtensions = fs.path.join('test', 'data', 'vscode', 'extensions');
   static final String missingExtensions = fs.path.join('test', 'data', 'vscode', 'notExtensions');
-  VsCodeValidatorTestTargets._(String installDirectory, String extensionDirectory) 
-    : super(new VsCode.fromDirectory(installDirectory, extensionDirectory));
+  VsCodeValidatorTestTargets._(String installDirectory, String extensionDirectory, {String edition}) 
+    : super(new VsCode.fromDirectory(installDirectory, extensionDirectory, edition: edition));
 
   static VsCodeValidatorTestTargets get installedWithExtension =>
     new VsCodeValidatorTestTargets._(validInstall, validExtensions);
 
+    static VsCodeValidatorTestTargets get installedWithExtension64bit =>
+    new VsCodeValidatorTestTargets._(validInstall, validExtensions, edition: '64-bit edition');
+
   static VsCodeValidatorTestTargets get installedWithoutExtension =>
     new VsCodeValidatorTestTargets._(validInstall, missingExtensions);
 }