331 lines
11 KiB
Dart
331 lines
11 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:meta/meta.dart';
|
|
import 'package:process/process.dart';
|
|
|
|
import '../base/file_system.dart';
|
|
import '../base/io.dart';
|
|
import '../base/platform.dart';
|
|
import '../base/utils.dart';
|
|
import '../base/version.dart';
|
|
import '../convert.dart';
|
|
import '../doctor_validator.dart';
|
|
|
|
const String extensionIdentifier = 'Dart-Code.flutter';
|
|
const String extensionMarketplaceUrl =
|
|
'https://marketplace.visualstudio.com/items?itemName=$extensionIdentifier';
|
|
|
|
class VsCode {
|
|
VsCode._(this.directory, this.extensionDirectory, { Version? version, this.edition, required FileSystem fileSystem})
|
|
: version = version ?? Version.unknown {
|
|
|
|
if (!fileSystem.isDirectorySync(directory)) {
|
|
_validationMessages.add(ValidationMessage.error('VS Code not found at $directory'));
|
|
return;
|
|
} else {
|
|
_validationMessages.add(ValidationMessage('VS Code at $directory'));
|
|
}
|
|
|
|
// If the extensions directory doesn't exist at all, the listSync()
|
|
// below will fail, so just bail out early.
|
|
const ValidationMessage notInstalledMessage = ValidationMessage(
|
|
'Flutter extension can be installed from:',
|
|
contextUrl: extensionMarketplaceUrl,
|
|
);
|
|
if (!fileSystem.isDirectorySync(extensionDirectory)) {
|
|
_validationMessages.add(notInstalledMessage);
|
|
return;
|
|
}
|
|
|
|
// Check for presence of extension.
|
|
final String extensionIdentifierLower = extensionIdentifier.toLowerCase();
|
|
final Iterable<FileSystemEntity> extensionDirs = fileSystem
|
|
.directory(extensionDirectory)
|
|
.listSync()
|
|
.whereType<Directory>()
|
|
.where((Directory d) => d.basename.toLowerCase().startsWith(extensionIdentifierLower));
|
|
|
|
if (extensionDirs.isNotEmpty) {
|
|
final FileSystemEntity extensionDir = extensionDirs.first;
|
|
|
|
_extensionVersion = Version.parse(
|
|
extensionDir.basename.substring('$extensionIdentifier-'.length));
|
|
_validationMessages.add(ValidationMessage('Flutter extension version $_extensionVersion'));
|
|
} else {
|
|
_validationMessages.add(notInstalledMessage);
|
|
}
|
|
}
|
|
|
|
factory VsCode.fromDirectory(
|
|
String installPath,
|
|
String extensionDirectory, {
|
|
String? edition,
|
|
required FileSystem fileSystem,
|
|
}) {
|
|
final String packageJsonPath =
|
|
fileSystem.path.join(installPath, 'resources', 'app', 'package.json');
|
|
final String? versionString = _getVersionFromPackageJson(packageJsonPath, fileSystem);
|
|
Version? version;
|
|
if (versionString != null) {
|
|
version = Version.parse(versionString);
|
|
}
|
|
return VsCode._(installPath, extensionDirectory, version: version, edition: edition, fileSystem: fileSystem);
|
|
}
|
|
|
|
final String directory;
|
|
final String extensionDirectory;
|
|
final Version version;
|
|
final String? edition;
|
|
|
|
Version? _extensionVersion;
|
|
final List<ValidationMessage> _validationMessages = <ValidationMessage>[];
|
|
|
|
String get productName => 'VS Code${edition != null ? ', $edition' : ''}';
|
|
|
|
Iterable<ValidationMessage> get validationMessages => _validationMessages;
|
|
|
|
static List<VsCode> allInstalled(
|
|
FileSystem fileSystem,
|
|
Platform platform,
|
|
ProcessManager processManager,
|
|
) {
|
|
if (platform.isMacOS) {
|
|
return _installedMacOS(fileSystem, platform, processManager);
|
|
}
|
|
if (platform.isWindows) {
|
|
return _installedWindows(fileSystem, platform);
|
|
}
|
|
if (platform.isLinux) {
|
|
return _installedLinux(fileSystem, platform);
|
|
}
|
|
// VS Code isn't supported on the other platforms.
|
|
return <VsCode>[];
|
|
}
|
|
|
|
// macOS:
|
|
// /Applications/Visual Studio Code.app/Contents/
|
|
// /Applications/Visual Studio Code - Insiders.app/Contents/
|
|
// $HOME/Applications/Visual Studio Code.app/Contents/
|
|
// $HOME/Applications/Visual Studio Code - Insiders.app/Contents/
|
|
// macOS Extensions:
|
|
// $HOME/.vscode/extensions
|
|
// $HOME/.vscode-insiders/extensions
|
|
static List<VsCode> _installedMacOS(FileSystem fileSystem, Platform platform, ProcessManager processManager) {
|
|
final String? homeDirPath = FileSystemUtils(fileSystem: fileSystem, platform: platform).homeDirPath;
|
|
|
|
String vsCodeSpotlightResult = '';
|
|
String vsCodeInsiderSpotlightResult = '';
|
|
// Query Spotlight for unexpected installation locations.
|
|
try {
|
|
final ProcessResult vsCodeSpotlightQueryResult = processManager.runSync(<String>[
|
|
'mdfind',
|
|
'kMDItemCFBundleIdentifier="com.microsoft.VSCode"',
|
|
]);
|
|
vsCodeSpotlightResult = vsCodeSpotlightQueryResult.stdout as String;
|
|
final ProcessResult vsCodeInsidersSpotlightQueryResult = processManager.runSync(<String>[
|
|
'mdfind',
|
|
'kMDItemCFBundleIdentifier="com.microsoft.VSCodeInsiders"',
|
|
]);
|
|
vsCodeInsiderSpotlightResult = vsCodeInsidersSpotlightQueryResult.stdout as String;
|
|
} on ProcessException {
|
|
// The Spotlight query is a nice-to-have, continue checking known installation locations.
|
|
}
|
|
|
|
// De-duplicated set.
|
|
return _findInstalled(<VsCodeInstallLocation>{
|
|
VsCodeInstallLocation(
|
|
fileSystem.path.join('/Applications', 'Visual Studio Code.app', 'Contents'),
|
|
'.vscode',
|
|
),
|
|
if (homeDirPath != null)
|
|
VsCodeInstallLocation(
|
|
fileSystem.path.join(
|
|
homeDirPath,
|
|
'Applications',
|
|
'Visual Studio Code.app',
|
|
'Contents',
|
|
),
|
|
'.vscode',
|
|
),
|
|
VsCodeInstallLocation(
|
|
fileSystem.path.join('/Applications', 'Visual Studio Code - Insiders.app', 'Contents'),
|
|
'.vscode-insiders',
|
|
),
|
|
if (homeDirPath != null)
|
|
VsCodeInstallLocation(
|
|
fileSystem.path.join(
|
|
homeDirPath,
|
|
'Applications',
|
|
'Visual Studio Code - Insiders.app',
|
|
'Contents',
|
|
),
|
|
'.vscode-insiders',
|
|
),
|
|
for (final String vsCodePath in LineSplitter.split(vsCodeSpotlightResult))
|
|
VsCodeInstallLocation(
|
|
fileSystem.path.join(vsCodePath, 'Contents'),
|
|
'.vscode',
|
|
),
|
|
for (final String vsCodeInsidersPath in LineSplitter.split(vsCodeInsiderSpotlightResult))
|
|
VsCodeInstallLocation(
|
|
fileSystem.path.join(vsCodeInsidersPath, 'Contents'),
|
|
'.vscode-insiders',
|
|
),
|
|
}, fileSystem, platform);
|
|
}
|
|
|
|
// Windows:
|
|
// $programfiles(x86)\Microsoft VS Code
|
|
// $programfiles(x86)\Microsoft VS Code Insiders
|
|
// User install:
|
|
// $localappdata\Programs\Microsoft VS Code
|
|
// $localappdata\Programs\Microsoft VS Code Insiders
|
|
// TODO(dantup): Confirm these are correct for 64bit
|
|
// $programfiles\Microsoft VS Code
|
|
// $programfiles\Microsoft VS Code Insiders
|
|
// Windows Extensions:
|
|
// $HOME/.vscode/extensions
|
|
// $HOME/.vscode-insiders/extensions
|
|
static List<VsCode> _installedWindows(
|
|
FileSystem fileSystem,
|
|
Platform platform,
|
|
) {
|
|
final String? progFiles86 = platform.environment['programfiles(x86)'];
|
|
final String? progFiles = platform.environment['programfiles'];
|
|
final String? localAppData = platform.environment['localappdata'];
|
|
|
|
final List<VsCodeInstallLocation> searchLocations = <VsCodeInstallLocation>[
|
|
if (localAppData != null)
|
|
VsCodeInstallLocation(
|
|
fileSystem.path.join(localAppData, r'Programs\Microsoft VS Code'),
|
|
'.vscode',
|
|
),
|
|
if (progFiles86 != null)
|
|
...<VsCodeInstallLocation>[
|
|
VsCodeInstallLocation(
|
|
fileSystem.path.join(progFiles86, 'Microsoft VS Code'),
|
|
'.vscode',
|
|
edition: '32-bit edition',
|
|
),
|
|
VsCodeInstallLocation(
|
|
fileSystem.path.join(progFiles86, 'Microsoft VS Code Insiders'),
|
|
'.vscode-insiders',
|
|
edition: '32-bit edition',
|
|
),
|
|
],
|
|
if (progFiles != null)
|
|
...<VsCodeInstallLocation>[
|
|
VsCodeInstallLocation(
|
|
fileSystem.path.join(progFiles, 'Microsoft VS Code'),
|
|
'.vscode',
|
|
edition: '64-bit edition',
|
|
),
|
|
VsCodeInstallLocation(
|
|
fileSystem.path.join(progFiles, 'Microsoft VS Code Insiders'),
|
|
'.vscode-insiders',
|
|
edition: '64-bit edition',
|
|
),
|
|
],
|
|
if (localAppData != null)
|
|
VsCodeInstallLocation(
|
|
fileSystem.path.join(localAppData, r'Programs\Microsoft VS Code Insiders'),
|
|
'.vscode-insiders',
|
|
),
|
|
];
|
|
return _findInstalled(searchLocations, fileSystem, platform);
|
|
}
|
|
|
|
// Linux:
|
|
// /usr/share/code/bin/code
|
|
// /snap/code/current
|
|
// /usr/share/code-insiders/bin/code-insiders
|
|
// Linux Extensions:
|
|
// $HOME/.vscode/extensions
|
|
// $HOME/.vscode-insiders/extensions
|
|
static List<VsCode> _installedLinux(FileSystem fileSystem, Platform platform) {
|
|
return _findInstalled(<VsCodeInstallLocation>[
|
|
const VsCodeInstallLocation('/usr/share/code', '.vscode'),
|
|
const VsCodeInstallLocation('/snap/code/current', '.vscode'),
|
|
const VsCodeInstallLocation(
|
|
'/usr/share/code-insiders',
|
|
'.vscode-insiders',
|
|
),
|
|
], fileSystem, platform);
|
|
}
|
|
|
|
static List<VsCode> _findInstalled(
|
|
Iterable<VsCodeInstallLocation> allLocations,
|
|
FileSystem fileSystem,
|
|
Platform platform,
|
|
) {
|
|
final List<VsCode> results = <VsCode>[];
|
|
|
|
for (final VsCodeInstallLocation searchLocation in allLocations) {
|
|
final String? homeDirPath = FileSystemUtils(fileSystem: fileSystem, platform: platform).homeDirPath;
|
|
if (homeDirPath != null && fileSystem.isDirectorySync(searchLocation.installPath)) {
|
|
final String extensionDirectory = fileSystem.path.join(
|
|
homeDirPath,
|
|
searchLocation.extensionsFolder,
|
|
'extensions',
|
|
);
|
|
results.add(VsCode.fromDirectory(
|
|
searchLocation.installPath,
|
|
extensionDirectory,
|
|
edition: searchLocation.edition,
|
|
fileSystem: fileSystem,
|
|
));
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
@override
|
|
String toString() =>
|
|
'VS Code ($version)${_extensionVersion != Version.unknown ? ', Flutter ($_extensionVersion)' : ''}';
|
|
|
|
static String? _getVersionFromPackageJson(String packageJsonPath, FileSystem fileSystem) {
|
|
if (!fileSystem.isFileSync(packageJsonPath)) {
|
|
return null;
|
|
}
|
|
final String jsonString = fileSystem.file(packageJsonPath).readAsStringSync();
|
|
try {
|
|
final Map<String, dynamic>? jsonObject = castStringKeyedMap(json.decode(jsonString));
|
|
if (jsonObject?.containsKey('version') == true) {
|
|
return jsonObject!['version'] as String;
|
|
}
|
|
} on FormatException {
|
|
return null;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@immutable
|
|
@visibleForTesting
|
|
class VsCodeInstallLocation {
|
|
const VsCodeInstallLocation(
|
|
this.installPath,
|
|
this.extensionsFolder, {
|
|
this.edition,
|
|
});
|
|
|
|
final String installPath;
|
|
final String extensionsFolder;
|
|
final String? edition;
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
return other is VsCodeInstallLocation &&
|
|
other.installPath == installPath &&
|
|
other.extensionsFolder == extensionsFolder &&
|
|
other.edition == edition;
|
|
}
|
|
|
|
@override
|
|
// Lowest bit is for isInsiders boolean.
|
|
int get hashCode => Object.hash(installPath, extensionsFolder, edition);
|
|
}
|