uninstallPlugin method

Future<PluginOperationResult> uninstallPlugin(
  1. String plugin, [
  2. PluginScope scope = PluginScope.user,
  3. bool deleteDataDirFlag = true
])

Uninstall a plugin.

Implementation

Future<PluginOperationResult> uninstallPlugin(
  String plugin, [
  PluginScope scope = PluginScope.user,
  bool deleteDataDirFlag = true,
]) async {
  assertInstallableScope(scope);

  final allPlugins = await loadAllPlugins();
  final foundPlugin = findPluginByIdentifier(plugin, allPlugins);

  final settingSource = scopeToSettingSource(scope);
  final settings = getSettingsEnabledPlugins(settingSource);

  String pluginId;
  String pluginName;

  if (foundPlugin != null) {
    pluginId =
        settings?.keys.firstWhere(
          (k) =>
              k == plugin ||
              k == foundPlugin.name ||
              k.startsWith('${foundPlugin.name}@'),
          orElse: () => plugin.contains('@') ? plugin : foundPlugin.name,
        ) ??
        (plugin.contains('@') ? plugin : foundPlugin.name);
    pluginName = foundPlugin.name;
  } else {
    return PluginOperationResult(
      success: false,
      message: 'Plugin "$plugin" not found in installed plugins',
    );
  }

  // Check scope installation.
  final projectPath = getProjectPathForScope(scope);
  final installedData = loadInstalledPluginsV2();
  final installations = installedData.plugins[pluginId];
  final scopeInstallation = installations
      ?.cast<PluginInstallationRecord?>()
      .firstWhere(
        (i) => i!.scope == scope && i.projectPath == projectPath,
        orElse: () => null,
      );

  if (scopeInstallation == null) {
    final actual = getPluginInstallationFromV2(pluginId);
    if (actual.scope != scope &&
        installations != null &&
        installations.isNotEmpty) {
      if (actual.scope == PluginScope.project) {
        return PluginOperationResult(
          success: false,
          message:
              'Plugin "$plugin" is enabled at project scope. '
              'To disable just for you: neomage plugin disable $plugin --scope local',
        );
      }
      return PluginOperationResult(
        success: false,
        message:
            'Plugin "$plugin" is installed in ${actual.scope.name} scope, '
            'not ${scope.name}. Use --scope ${actual.scope.name} to uninstall.',
      );
    }
    return PluginOperationResult(
      success: false,
      message:
          'Plugin "$plugin" is not installed in ${scope.name} scope. '
          'Use --scope to specify the correct scope.',
    );
  }

  // Remove from settings.
  final newEnabled = Map<String, bool>.from(settings ?? {});
  newEnabled.remove(pluginId);
  updateSettings(settingSource, {'enabledPlugins': newEnabled});
  clearAllCaches();

  // Remove from V2 data.
  removePluginInstallation(pluginId, scope, projectPath);

  // Check if this was the last installation scope.
  final updatedData = loadInstalledPluginsV2();
  final remaining = updatedData.plugins[pluginId];
  final isLastScope = remaining == null || remaining.isEmpty;

  if (isLastScope && scopeInstallation.installPath != null) {
    await markVersionOrphaned(scopeInstallation.installPath!);
  }
  if (isLastScope) {
    deletePluginOptions(pluginId);
    if (deleteDataDirFlag) {
      await deletePluginDataDir(pluginId);
    }
  }

  final reverseDeps = findReverseDependents(pluginId, allPlugins);
  final depWarn = reverseDeps.isNotEmpty
      ? '. Warning: the following plugins depend on this: ${reverseDeps.join(', ')}'
      : '';

  return PluginOperationResult(
    success: true,
    message:
        'Successfully uninstalled plugin: $pluginName (scope: ${scope.name})$depWarn',
    pluginId: pluginId,
    pluginName: pluginName,
    scope: scope,
    reverseDependents: reverseDeps.isNotEmpty ? reverseDeps : null,
  );
}