Skip to content

Commit fe2d2c0

Browse files
authored
Merge pull request #5051 from mansellan/5041
VB6 Code Explorer - add 'Exclude from project' and 'Delete' to context menu
2 parents f6d8b21 + dd89779 commit fe2d2c0

File tree

11 files changed

+323
-35
lines changed

11 files changed

+323
-35
lines changed

Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerViewModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ private void ExecuteRemoveCommand(object param)
386386
public ImportCommand ImportCommand { get; set; }
387387
public ExportCommand ExportCommand { get; set; }
388388
public ExportAllCommand ExportAllCommand { get; set; }
389+
public DeleteCommand DeleteCommand { get; set; }
389390
public CommandBase RemoveCommand { get; }
390391
public PrintCommand PrintCommand { get; set; }
391392
public AddRemoveReferencesCommand AddRemoveReferencesCommand { get; set; }

Rubberduck.Core/UI/CodeExplorer/CodeExplorerControl.xaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,19 @@
476476
<Image Source="{StaticResource RemoveImage}" Style="{StaticResource ToolbarImageOpacity}" />
477477
</MenuItem.Icon>
478478
</MenuItem>
479+
<MenuItem Header="{Resx ResxName=Rubberduck.Resources.CodeExplorer.CodeExplorerUI, Key=CodeExplorer_Exclude}"
480+
Command="{Binding RemoveCommand}"
481+
CommandParameter="{Binding SelectedItem, Mode=OneWay}"
482+
Visibility="{Binding VB6Visibility}">
483+
</MenuItem>
484+
<MenuItem Header="{Resx ResxName=Rubberduck.Resources.CodeExplorer.CodeExplorerUI, Key=CodeExplorer_Delete}"
485+
Command="{Binding DeleteCommand}"
486+
CommandParameter="{Binding SelectedItem, Mode=OneWay}"
487+
Visibility="{Binding VB6Visibility}">
488+
<MenuItem.Icon>
489+
<Image Source="{StaticResource RemoveImage}" Style="{StaticResource ToolbarImageOpacity}" />
490+
</MenuItem.Icon>
491+
</MenuItem>
479492
<Separator />
480493
<MenuItem Header="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=Find}">
481494
<MenuItem.Icon>
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using Rubberduck.Interaction;
6+
using Rubberduck.Navigation.CodeExplorer;
7+
using Rubberduck.VBEditor.ComManagement;
8+
using Rubberduck.VBEditor.SafeComWrappers;
9+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
10+
11+
namespace Rubberduck.UI.CodeExplorer.Commands
12+
{
13+
public class DeleteCommand : CodeExplorerCommandBase
14+
{
15+
private readonly RemoveCommand _removeCommand;
16+
private readonly IProjectsProvider _projectsProvider;
17+
private readonly IMessageBox _messageBox;
18+
private readonly IVBE _vbe;
19+
20+
public DeleteCommand(RemoveCommand removeCommand, IProjectsProvider projectsProvider, IMessageBox messageBox, IVBE vbe)
21+
{
22+
_removeCommand = removeCommand;
23+
_projectsProvider = projectsProvider;
24+
_messageBox = messageBox;
25+
_vbe = vbe;
26+
27+
AddToCanExecuteEvaluation(SpecialEvaluateCanExecute);
28+
}
29+
30+
public override IEnumerable<Type> ApplicableNodeTypes { get; } = new List<Type> { typeof(CodeExplorerComponentViewModel) };
31+
32+
private bool SpecialEvaluateCanExecute(object parameter)
33+
{
34+
return _vbe.Kind == VBEKind.Standalone &&
35+
_removeCommand.CanExecute(parameter);
36+
}
37+
38+
protected override void OnExecute(object parameter)
39+
{
40+
if (!(parameter is CodeExplorerComponentViewModel node) || node.Declaration is null)
41+
{
42+
return;
43+
}
44+
45+
var qualifiedModuleName = node.Declaration.QualifiedModuleName;
46+
var component = _projectsProvider.Component(qualifiedModuleName);
47+
if (component is null)
48+
{
49+
return;
50+
}
51+
52+
// "{qualifiedModuleName.Name} will be permanently deleted. Continue?" (localized)
53+
var message = string.Format(Resources.CodeExplorer.CodeExplorerUI.ConfirmBeforeDelete_Prompt, qualifiedModuleName.Name);
54+
if (!_messageBox.ConfirmYesNo(message, Resources.CodeExplorer.CodeExplorerUI.ConfirmBeforeDelete_Caption))
55+
{
56+
return;
57+
}
58+
59+
// Have to build the file list *before* removing the component!
60+
var files = new List<string>();
61+
for (short i = 1; i <= component.FileCount; i++)
62+
{
63+
var fileName = component.GetFileName(i);
64+
if (fileName != null) // Unsaved components have a null filename for some reason
65+
{
66+
files.Add(component.GetFileName(i));
67+
}
68+
}
69+
70+
if (!_removeCommand.RemoveComponent(qualifiedModuleName, false))
71+
{
72+
return; // Remove was cancelled or failed
73+
}
74+
75+
var failedDeletions = new List<string>();
76+
foreach (var file in files)
77+
{
78+
try
79+
{
80+
File.Delete(file);
81+
}
82+
catch (Exception exception)
83+
{
84+
Logger.Warn(exception, "Failed to delete file");
85+
failedDeletions.Add(file);
86+
}
87+
}
88+
89+
// Let the user know if there are any component files left on disk
90+
if (failedDeletions.Any())
91+
{
92+
// "The following files could not be deleted: {fileDeletions}" (localized)
93+
message = string.Format(Resources.CodeExplorer.CodeExplorerUI.DeleteFailed_Message, string.Join(Environment.NewLine, failedDeletions));
94+
_messageBox.NotifyWarn(message, Resources.CodeExplorer.CodeExplorerUI.DeleteFailed_Caption);
95+
}
96+
97+
}
98+
}
99+
}

Rubberduck.Core/UI/CodeExplorer/Commands/ExportCommand.cs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,53 @@
88
using Rubberduck.VBEditor;
99
using Rubberduck.VBEditor.ComManagement;
1010
using Rubberduck.VBEditor.SafeComWrappers;
11+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
1112

1213
namespace Rubberduck.UI.CodeExplorer.Commands
1314
{
1415
public class ExportCommand : CommandBase
1516
{
16-
private static readonly Dictionary<ComponentType, string> ExportableFileExtensions = new Dictionary<ComponentType, string>
17+
private static readonly Dictionary<ComponentType, string> VBAExportableFileExtensions = new Dictionary<ComponentType, string>
1718
{
1819
{ ComponentType.StandardModule, ".bas" },
1920
{ ComponentType.ClassModule, ".cls" },
2021
{ ComponentType.Document, ".cls" },
21-
{ ComponentType.UserForm, ".frm" }
22+
{ ComponentType.UserForm, ".frm" }
23+
};
24+
25+
private static readonly Dictionary<ComponentType, string> VB6ExportableFileExtensions = new Dictionary<ComponentType, string>
26+
{
27+
{ ComponentType.StandardModule, ".bas" },
28+
{ ComponentType.ClassModule, ".cls" },
29+
{ ComponentType.VBForm, ".frm" },
30+
{ ComponentType.MDIForm, ".frm" },
31+
{ ComponentType.UserControl, ".ctl" },
32+
{ ComponentType.DocObject, ".dob" },
33+
{ ComponentType.ActiveXDesigner, ".dsr" },
34+
{ ComponentType.PropPage, ".pag" },
35+
{ ComponentType.ResFile, ".res" },
2236
};
2337

2438
private readonly IFileSystemBrowserFactory _dialogFactory;
39+
private readonly Dictionary<ComponentType, string> _exportableFileExtensions;
2540

26-
public ExportCommand(IFileSystemBrowserFactory dialogFactory, IMessageBox messageBox, IProjectsProvider projectsProvider)
41+
public ExportCommand(IFileSystemBrowserFactory dialogFactory, IMessageBox messageBox, IProjectsProvider projectsProvider, IVBE vbe)
2742
{
2843
_dialogFactory = dialogFactory;
2944
MessageBox = messageBox;
3045
ProjectsProvider = projectsProvider;
3146

47+
_exportableFileExtensions =
48+
vbe.Kind == VBEKind.Hosted
49+
? VBAExportableFileExtensions
50+
: VB6ExportableFileExtensions;
51+
3252
AddToCanExecuteEvaluation(SpecialEvaluateCanExecute);
3353
}
3454

3555
protected IMessageBox MessageBox { get; }
3656
protected IProjectsProvider ProjectsProvider { get; }
37-
57+
3858
private bool SpecialEvaluateCanExecute(object parameter)
3959
{
4060
if (!(parameter is CodeExplorerComponentViewModel node) ||
@@ -44,7 +64,8 @@ private bool SpecialEvaluateCanExecute(object parameter)
4464
}
4565

4666
var componentType = node.Declaration.QualifiedName.QualifiedModuleName.ComponentType;
47-
return ExportableFileExtensions.Select(s => s.Key).Contains(componentType);
67+
68+
return _exportableFileExtensions.ContainsKey(componentType);
4869
}
4970

5071
protected override void OnExecute(object parameter)
@@ -58,9 +79,9 @@ protected override void OnExecute(object parameter)
5879
PromptFileNameAndExport(node.Declaration.QualifiedName.QualifiedModuleName);
5980
}
6081

61-
protected bool PromptFileNameAndExport(QualifiedModuleName qualifiedModule)
82+
public bool PromptFileNameAndExport(QualifiedModuleName qualifiedModule)
6283
{
63-
if (!ExportableFileExtensions.TryGetValue(qualifiedModule.ComponentType, out var extension))
84+
if (!_exportableFileExtensions.TryGetValue(qualifiedModule.ComponentType, out var extension))
6485
{
6586
return false;
6687
}
@@ -83,6 +104,7 @@ protected bool PromptFileNameAndExport(QualifiedModuleName qualifiedModule)
83104
}
84105
catch (Exception ex)
85106
{
107+
Logger.Warn(ex, $"Failed to export component {qualifiedModule.Name}");
86108
MessageBox.NotifyWarn(ex.Message, string.Format(Resources.CodeExplorer.CodeExplorerUI.ExportError_Caption, qualifiedModule.ComponentName));
87109
}
88110
return true;
Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,107 @@
11
using System;
2+
using System.Collections.Generic;
23
using Rubberduck.Interaction;
34
using Rubberduck.Navigation.CodeExplorer;
45
using Rubberduck.Resources.CodeExplorer;
6+
using Rubberduck.VBEditor;
57
using Rubberduck.VBEditor.ComManagement;
68
using Rubberduck.VBEditor.SafeComWrappers;
9+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
710

811
namespace Rubberduck.UI.CodeExplorer.Commands
912
{
10-
public class RemoveCommand : ExportCommand
13+
public class RemoveCommand : CodeExplorerCommandBase
1114
{
12-
public RemoveCommand(IFileSystemBrowserFactory dialogFactory, IMessageBox messageBox, IProjectsProvider projectsProvider)
13-
: base(dialogFactory, messageBox, projectsProvider)
15+
private readonly ExportCommand _exportCommand;
16+
private readonly IProjectsRepository _projectsRepository;
17+
private readonly IMessageBox _messageBox;
18+
private readonly IVBE _vbe;
19+
20+
public RemoveCommand(ExportCommand exportCommand, IProjectsRepository projectsRepository, IMessageBox messageBox, IVBE vbe)
1421
{
22+
_exportCommand = exportCommand;
23+
_projectsRepository = projectsRepository;
24+
_messageBox = messageBox;
25+
_vbe = vbe;
1526

1627
AddToCanExecuteEvaluation(SpecialEvaluateCanExecute);
1728
}
1829

30+
public override IEnumerable<Type> ApplicableNodeTypes { get; } = new List<Type> { typeof(CodeExplorerComponentViewModel)};
31+
1932
private bool SpecialEvaluateCanExecute(object parameter)
2033
{
21-
return ((CodeExplorerComponentViewModel) parameter).Declaration.QualifiedName.QualifiedModuleName
22-
.ComponentType != ComponentType.Document;
34+
return _exportCommand.CanExecute(parameter) &&
35+
parameter is CodeExplorerComponentViewModel viewModel &&
36+
viewModel.Declaration != null &&
37+
viewModel.Declaration.QualifiedModuleName.ComponentType != ComponentType.Document;
2338
}
2439

2540
protected override void OnExecute(object parameter)
2641
{
2742
if (!(parameter is CodeExplorerComponentViewModel node) ||
2843
node.Declaration == null ||
29-
node.Declaration.QualifiedName.QualifiedModuleName.ComponentType == ComponentType.Document)
44+
node.Declaration.QualifiedModuleName.ComponentType == ComponentType.Document)
3045
{
3146
return;
3247
}
3348

34-
var qualifiedModuleName = node.Declaration.QualifiedName.QualifiedModuleName;
35-
var message = string.Format(CodeExplorerUI.ExportBeforeRemove_Prompt, node.Name);
49+
RemoveComponent(node.Declaration.QualifiedModuleName);
50+
}
3651

37-
switch (MessageBox.Confirm(message, CodeExplorerUI.ExportBeforeRemove_Caption, ConfirmationOutcome.Yes))
52+
public bool RemoveComponent(QualifiedModuleName qualifiedModuleName, bool promptToExport = true)
53+
{
54+
if (promptToExport && !TryExport(qualifiedModuleName))
3855
{
39-
case ConfirmationOutcome.Yes:
40-
if (!PromptFileNameAndExport(qualifiedModuleName))
41-
{
42-
return;
43-
}
44-
break;
45-
case ConfirmationOutcome.No:
46-
break;
47-
case ConfirmationOutcome.Cancel:
48-
return;
49-
}
50-
56+
return false;
57+
}
58+
5159
// No file export or file successfully exported--now remove it
52-
var components = ProjectsProvider.ComponentsCollection(qualifiedModuleName.ProjectId);
5360
try
5461
{
55-
components?.Remove(ProjectsProvider.Component(qualifiedModuleName));
62+
_projectsRepository.RemoveComponent(qualifiedModuleName);
5663
}
5764
catch (Exception ex)
5865
{
59-
MessageBox.NotifyWarn(ex.Message, string.Format(CodeExplorerUI.RemoveError_Caption, qualifiedModuleName.ComponentName));
66+
_messageBox.NotifyWarn(ex.Message, string.Format(CodeExplorerUI.RemoveError_Caption, qualifiedModuleName.ComponentName));
67+
return false;
68+
}
69+
70+
return true;
71+
}
72+
73+
private bool TryExport(QualifiedModuleName qualifiedModuleName)
74+
{
75+
var component = _projectsRepository.Component(qualifiedModuleName);
76+
if (component is null)
77+
{
78+
return false; // Edge-case, component already gone.
79+
}
80+
81+
if (_vbe.Kind == VBEKind.Standalone && component.IsSaved)
82+
{
83+
return true; // File already up-to-date
84+
}
85+
86+
// "Do you want to export '{qualifiedModuleName.Name}' before removing?" (localized)
87+
var message = string.Format(CodeExplorerUI.ExportBeforeRemove_Prompt, qualifiedModuleName.Name);
88+
89+
switch (_messageBox.Confirm(message, CodeExplorerUI.ExportBeforeRemove_Caption, ConfirmationOutcome.Yes))
90+
{
91+
case ConfirmationOutcome.No:
92+
// User elected to remove without export, return success.
93+
return true;
94+
95+
case ConfirmationOutcome.Yes:
96+
if (_exportCommand.PromptFileNameAndExport(qualifiedModuleName))
97+
{
98+
// Export complete
99+
return true;
100+
}
101+
break;
60102
}
103+
104+
return false; // Save dialog cancelled or export failed (failures will have already been displayed and logged by this point)
61105
}
62106
}
63107
}

0 commit comments

Comments
 (0)