Skip to content

Commit ac05e74

Browse files
authored
Merge pull request #5258 from MDoerner/SCStyleImportCommands
SC style import commands
2 parents d59c3e2 + 112bda5 commit ac05e74

File tree

18 files changed

+1353
-64
lines changed

18 files changed

+1353
-64
lines changed

Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerViewModel.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Rubberduck.UI.Command;
1414
using Rubberduck.VBEditor.SafeComWrappers;
1515
using System.Windows;
16+
using System.Windows.Forms;
1617
using System.Windows.Input;
1718
using Rubberduck.Parsing.UIContext;
1819
using Rubberduck.Templates;
@@ -229,10 +230,11 @@ private void HandleStateChanged(object sender, ParserStateEventArgs e)
229230
{
230231
Unparsed = false;
231232

232-
if (e.State == ParserState.Ready)
233+
if (e.State == ParserState.Ready && e.OldState != ParserState.Busy)
233234
{
234235
// Finished up resolving references, so we can now update the reference nodes.
235236
//We have to wait for the task to guarantee that no new parse starts invalidating all cached components.
237+
//CAUTION: This must not be executed from the UI thread!!!
236238
_uiDispatcher.StartTask(() =>
237239
{
238240
var referenceFolders = Projects.SelectMany(node =>
@@ -385,6 +387,8 @@ private void ExecuteRemoveCommand(object param)
385387
public CopyResultsCommand CopyResultsCommand { get; set; }
386388
public CommandBase ExpandAllSubnodesCommand { get; }
387389
public ImportCommand ImportCommand { get; set; }
390+
public UpdateFromFilesCommand UpdateFromFilesCommand { get; set; }
391+
public ReplaceProjectContentsFromFilesCommand ReplaceProjectContentsFromFilesCommand { get; set; }
388392
public ExportCommand ExportCommand { get; set; }
389393
public ExportAllCommand ExportAllCommand { get; set; }
390394
public DeleteCommand DeleteCommand { get; set; }

Rubberduck.Core/UI/CodeExplorer/CodeExplorerControl.xaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,17 @@
454454
Command="{Binding OpenProjectPropertiesCommand}"
455455
CommandParameter="{Binding SelectedItem, Mode=OneWay}" />
456456
<Separator />
457+
<MenuItem Header="{Resx ResxName=Rubberduck.Resources.CodeExplorer.CodeExplorerUI, Key=SyncProject}">
458+
<MenuItem.Icon>
459+
<Image Source="{StaticResource SyncImage}" />
460+
</MenuItem.Icon>
461+
<MenuItem Header="{Resx ResxName=Rubberduck.Resources.CodeExplorer.CodeExplorerUI, Key=UpdateFromFiles}"
462+
Command="{Binding UpdateFromFilesCommand}"
463+
CommandParameter="{Binding SelectedItem, Mode=OneWay}" />
464+
<MenuItem Header="{Resx ResxName=Rubberduck.Resources.CodeExplorer.CodeExplorerUI, Key=ReplaceFromFiles}"
465+
Command="{Binding ReplaceProjectContentsFromFilesCommand}"
466+
CommandParameter="{Binding SelectedItem, Mode=OneWay}" />
467+
</MenuItem>
457468
<MenuItem Header="{Resx ResxName=Rubberduck.Resources.CodeExplorer.CodeExplorerUI, Key=CodeExplorer_AddModule}"
458469
ItemsSource="{StaticResource AddModuleCommands}">
459470
<MenuItem.Icon>
Lines changed: 116 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.IO;
34
using System.Linq;
45
using System.Windows.Forms;
6+
using Rubberduck.Interaction;
57
using Rubberduck.Navigation.CodeExplorer;
8+
using Rubberduck.Parsing.VBA;
69
using Rubberduck.Resources;
710
using Rubberduck.VBEditor.Events;
11+
using Rubberduck.VBEditor.Extensions;
812
using Rubberduck.VBEditor.SafeComWrappers;
913
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
1014

@@ -24,26 +28,33 @@ public class ImportCommand : CodeExplorerCommandBase
2428
private readonly IFileSystemBrowserFactory _dialogFactory;
2529
private readonly IList<string> _importableExtensions;
2630
private readonly string _filterExtensions;
31+
private readonly IParseManager _parseManager;
32+
33+
protected readonly IMessageBox MessageBox;
2734

2835
public ImportCommand(
29-
IVBE vbe,
30-
IFileSystemBrowserFactory dialogFactory,
31-
IVbeEvents vbeEvents)
36+
IVBE vbe,
37+
IFileSystemBrowserFactory dialogFactory,
38+
IVbeEvents vbeEvents,
39+
IParseManager parseManager,
40+
IMessageBox messageBox)
3241
: base(vbeEvents)
3342
{
3443
_vbe = vbe;
3544
_dialogFactory = dialogFactory;
45+
_parseManager = parseManager;
46+
47+
MessageBox = messageBox;
3648

3749
AddToCanExecuteEvaluation(SpecialEvaluateCanExecute);
3850

39-
_importableExtensions =
40-
vbe.Kind == VBEKind.Hosted
41-
? new List<string> {"bas", "cls", "frm", "doccls"} // VBA
42-
: new List<string> {"bas", "cls", "frm", "ctl", "pag", "dob"}; // VB6
51+
ComponentTypeForExtension = ComponentTypeExtensions.ComponentTypeForExtension(_vbe.Kind);
4352

44-
_filterExtensions = string.Join("; ", _importableExtensions.Select(ext => $"*.{ext}"));
53+
_importableExtensions = ComponentTypeForExtension.Keys.ToList();
54+
_filterExtensions = string.Join("; ", _importableExtensions.Select(ext => $"*{ext}"));
4555

4656
AddToCanExecuteEvaluation(SpecialEvaluateCanExecute);
57+
AddToOnExecuteEvaluation(SpecialEvaluateCanExecute);
4758
}
4859

4960
public sealed override IEnumerable<Type> ApplicableNodeTypes => ApplicableNodes;
@@ -61,37 +72,42 @@ private bool ThereIsAValidActiveProject()
6172
}
6273
}
6374

64-
protected override void OnExecute(object parameter)
75+
private (IVBProject project, bool needsDisposal) TargetProject(object parameter)
6576
{
66-
if (!CanExecute(parameter))
77+
var targetProject = TargetProjectFromParameter(parameter);
78+
if (targetProject != null)
6779
{
68-
return;
80+
return (targetProject, false);
6981
}
7082

71-
var usingFreshProjectWrapper = false;
72-
var project = (parameter as CodeExplorerItemViewModel)?.Declaration?.Project;
83+
targetProject = TargetProjectFromVbe();
84+
85+
return (targetProject, targetProject != null);
86+
}
87+
88+
private static IVBProject TargetProjectFromParameter(object parameter)
89+
{
90+
return (parameter as CodeExplorerItemViewModel)?.Declaration?.Project;
91+
}
7392

74-
if (project == null)
93+
private IVBProject TargetProjectFromVbe()
94+
{
95+
if (_vbe.ProjectsCount == 1)
7596
{
76-
if (_vbe.ProjectsCount == 1)
77-
{
78-
usingFreshProjectWrapper = true;
79-
using (var projects = _vbe.VBProjects)
80-
{
81-
project = projects[1];
82-
}
83-
}
84-
else if (ThereIsAValidActiveProject())
97+
using (var projects = _vbe.VBProjects)
8598
{
86-
usingFreshProjectWrapper = true;
87-
project = _vbe.ActiveVBProject;
88-
}
89-
else
90-
{
91-
return;
99+
return projects[1];
92100
}
93101
}
94102

103+
var activeProject = _vbe.ActiveVBProject;
104+
return activeProject != null && !activeProject.IsWrappingNullReference
105+
? activeProject
106+
: null;
107+
}
108+
109+
protected virtual ICollection<string> FilesToImport(object parameter)
110+
{
95111
using (var dialog = _dialogFactory.CreateOpenFileDialog())
96112
{
97113
dialog.AddExtension = true;
@@ -100,43 +116,94 @@ protected override void OnExecute(object parameter)
100116
dialog.CheckPathExists = true;
101117
dialog.Multiselect = true;
102118
dialog.ShowHelp = false;
103-
dialog.Title = RubberduckUI.ImportCommand_OpenDialog_Title;
104-
dialog.Filter =
119+
dialog.Title = DialogsTitle;
120+
dialog.Filter =
105121
$"{RubberduckUI.ImportCommand_OpenDialog_Filter_VBFiles} ({_filterExtensions})|{_filterExtensions}|" +
106122
$"{RubberduckUI.ImportCommand_OpenDialog_Filter_AllFiles}, (*.*)|*.*";
107123

108-
if (project == null || dialog.ShowDialog() != DialogResult.OK)
124+
if (dialog.ShowDialog() != DialogResult.OK)
109125
{
110-
if (usingFreshProjectWrapper)
111-
{
112-
project?.Dispose();
113-
}
114-
return;
126+
return new List<string>();
115127
}
116128

117-
var fileExists = dialog.FileNames.Select(s => s.Split('.').Last());
118-
if (fileExists.Any(fileExt => !_importableExtensions.Contains(fileExt)))
129+
var fileNames = dialog.FileNames;
130+
var fileExtensions = fileNames.Select(Path.GetExtension);
131+
if (fileExtensions.Any(fileExt => !_importableExtensions.Contains(fileExt)))
119132
{
120-
if (usingFreshProjectWrapper)
121-
{
122-
project.Dispose();
123-
}
124-
return;
133+
NotifyUserAboutAbortDueToUnsupportedFileExtensions(fileNames);
134+
return new List<string>();
125135
}
126136

127-
foreach (var filename in dialog.FileNames)
137+
return fileNames;
138+
}
139+
}
140+
141+
protected virtual string DialogsTitle => RubberduckUI.ImportCommand_OpenDialog_Title;
142+
143+
private void NotifyUserAboutAbortDueToUnsupportedFileExtensions(IEnumerable<string> fileNames)
144+
{
145+
var firstUnsupportedFile = fileNames.First(filename => !_importableExtensions.Contains(Path.GetExtension(filename)));
146+
var unsupportedFileName = Path.GetFileName(firstUnsupportedFile);
147+
var message = string.Format(RubberduckUI.ImportCommand_UnsupportedFileExtensions, unsupportedFileName);
148+
MessageBox.NotifyWarn(message, DialogsTitle);
149+
}
150+
151+
private void ImportFilesWithSuspension(ICollection<string> filesToImport, IVBProject targetProject)
152+
{
153+
var suspensionResult = _parseManager.OnSuspendParser(this, new[] {ParserState.Ready}, () => ImportFiles(filesToImport, targetProject));
154+
if (suspensionResult != SuspensionResult.Completed)
155+
{
156+
Logger.Warn("File import failed due to suspension failure.");
157+
}
158+
}
159+
160+
protected virtual void ImportFiles(ICollection<string> filesToImport, IVBProject targetProject)
161+
{
162+
using (var components = targetProject.VBComponents)
163+
{
164+
foreach (var filename in filesToImport)
128165
{
129-
using (var components = project.VBComponents)
166+
var fileExtension = Path.GetExtension(filename);
167+
if (fileExtension != null
168+
&& ComponentTypeForExtension.TryGetValue(fileExtension, out var componentType)
169+
&& componentType == ComponentType.Document)
130170
{
131-
components.Import(filename);
171+
//We have to dispose the return value.
172+
using (components.ImportSourceFile(filename)) { }
173+
}
174+
else
175+
{
176+
//We have to dispose the return value.
177+
using (components.Import(filename)) { }
132178
}
133179
}
134180
}
181+
}
135182

136-
if (usingFreshProjectWrapper)
183+
protected override void OnExecute(object parameter)
184+
{
185+
var (targetProject, targetProjectNeedsDisposal) = TargetProject(parameter);
186+
187+
if (targetProject == null)
137188
{
138-
project.Dispose();
189+
return;
190+
}
191+
192+
var filesToImport = FilesToImport(parameter);
193+
194+
if (!filesToImport.Any())
195+
{
196+
return;
197+
}
198+
199+
ImportFilesWithSuspension(filesToImport, targetProject);
200+
201+
if (targetProjectNeedsDisposal)
202+
{
203+
targetProject.Dispose();
139204
}
140205
}
206+
207+
protected IDictionary<string, ComponentType> ComponentTypeForExtension { get; }
141208
}
142209
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Rubberduck.Interaction;
4+
using Rubberduck.Parsing.VBA;
5+
using Rubberduck.Resources;
6+
using Rubberduck.VBEditor.Events;
7+
using Rubberduck.VBEditor.SafeComWrappers;
8+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
9+
10+
namespace Rubberduck.UI.CodeExplorer.Commands
11+
{
12+
public class ReplaceProjectContentsFromFilesCommand : ImportCommand
13+
{
14+
public ReplaceProjectContentsFromFilesCommand(
15+
IVBE vbe,
16+
IFileSystemBrowserFactory dialogFactory,
17+
IVbeEvents vbeEvents,
18+
IParseManager parseManager,
19+
IMessageBox messageBox)
20+
:base(vbe, dialogFactory, vbeEvents, parseManager, messageBox)
21+
{ }
22+
23+
protected override string DialogsTitle => RubberduckUI.ReplaceProjectContentsFromFilesCommand_DialogCaption;
24+
25+
protected override void ImportFiles(ICollection<string> filesToImport, IVBProject targetProject)
26+
{
27+
if (!UserConfirmsToReplaceProjectContents(targetProject))
28+
{
29+
return;
30+
}
31+
32+
RemoveReimportableComponents(targetProject);
33+
base.ImportFiles(filesToImport, targetProject);
34+
}
35+
36+
private bool UserConfirmsToReplaceProjectContents(IVBProject project)
37+
{
38+
var projectName = project.Name;
39+
var message = string.Format(RubberduckUI.ReplaceProjectContentsFromFilesCommand_DialogCaption, projectName);
40+
return MessageBox.ConfirmYesNo(message, DialogsTitle, false);
41+
}
42+
43+
private void RemoveReimportableComponents(IVBProject project)
44+
{
45+
var reimportableComponentTypes = ComponentTypeForExtension.Values
46+
.Where(componentType => componentType != ComponentType.Document);
47+
using(var components = project.VBComponents)
48+
{
49+
foreach(var component in components)
50+
{
51+
using (component)
52+
{
53+
if (reimportableComponentTypes.Contains(component.Type))
54+
{
55+
components.Remove(component);
56+
}
57+
}
58+
}
59+
}
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)