Skip to content

Commit 81f71aa

Browse files
committed
Merge branch 'next' into GermanTranslations
2 parents 82835b4 + 38646b6 commit 81f71aa

File tree

10 files changed

+164
-43
lines changed

10 files changed

+164
-43
lines changed

Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerViewModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ private void ExecuteRemoveCommand(object param)
397397
public AddRemoveReferencesCommand AddRemoveReferencesCommand { get; set; }
398398
public ICommand ClearSearchCommand { get; }
399399
public CommandBase SyncCodePaneCommand { get; }
400+
public CodeExplorerExtractInterfaceCommand CodeExplorerExtractInterfaceCommand { get; set; }
400401

401402
public ICodeExplorerNode FindVisibleNodeForDeclaration(Declaration declaration)
402403
{

Rubberduck.Core/UI/CodeExplorer/CodeExplorerControl.xaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<BitmapImage x:Key="AddRemoveReferencesImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/node-select-all.png" />
4444
<BitmapImage x:Key="SyncImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Custom/PNG/SyncArrows.png" />
4545
<BitmapImage x:Key="FontSizeImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/edit-size.png" />
46+
<BitmapImage x:Key="ExtractInterfaceImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Custom/PNG/ExtractInterface.png" />
4647

4748
<converters:BooleanToNullableDoubleConverter x:Key="BoolToDouble" />
4849
<converters:BoolToHiddenVisibilityConverter x:Key="BoolToHiddenVisibility" />
@@ -438,6 +439,13 @@
438439
<MenuItem Header="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=Rename}"
439440
Command="{Binding RenameCommand}"
440441
CommandParameter="{Binding SelectedItem, Mode=OneWay}" />
442+
<MenuItem Header="{Resx ResxName=Rubberduck.Resources.CodeExplorer.CodeExplorerUI, Key=CodeExplorer_ExtractInterfaceText}"
443+
Command="{Binding CodeExplorerExtractInterfaceCommand}"
444+
CommandParameter="{Binding SelectedItem, Mode=OneWay}">
445+
<MenuItem.Icon>
446+
<Image Source="{StaticResource ExtractInterfaceImage}" />
447+
</MenuItem.Icon>
448+
</MenuItem>
441449
<Separator />
442450
<MenuItem Header="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=References_Caption}"
443451
Command="{Binding AddRemoveReferencesCommand}"
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using Rubberduck.Navigation.CodeExplorer;
2+
using Rubberduck.Parsing.VBA;
3+
using Rubberduck.Refactorings.Exceptions;
4+
using Rubberduck.Refactorings.ExtractInterface;
5+
using Rubberduck.UI.Command.Refactorings.Notifiers;
6+
using Rubberduck.VBEditor.Events;
7+
using System;
8+
using System.Collections.Generic;
9+
10+
namespace Rubberduck.UI.CodeExplorer.Commands
11+
{
12+
public class CodeExplorerExtractInterfaceCommand : CodeExplorerCommandBase
13+
{
14+
private static readonly Type[] ApplicableNodes =
15+
{
16+
typeof(CodeExplorerComponentViewModel)
17+
};
18+
19+
private readonly RubberduckParserState _state;
20+
private readonly ExtractInterfaceRefactoring _refactoring;
21+
private readonly ExtractInterfaceFailedNotifier _failureNotifier;
22+
23+
public CodeExplorerExtractInterfaceCommand(
24+
ExtractInterfaceRefactoring refactoring,
25+
RubberduckParserState state,
26+
ExtractInterfaceFailedNotifier failureNotifier,
27+
IVbeEvents vbeEvents)
28+
: base(vbeEvents)
29+
{
30+
_state = state;
31+
_refactoring = refactoring;
32+
_failureNotifier = failureNotifier;
33+
AddToCanExecuteEvaluation(SpecialEvaluateCanExecute);
34+
AddToOnExecuteEvaluation(FurtherCanExecuteEvaluation);
35+
}
36+
37+
public sealed override IEnumerable<Type> ApplicableNodeTypes => ApplicableNodes;
38+
39+
private bool SpecialEvaluateCanExecute(object parameter)
40+
{
41+
return _state.Status == ParserState.Ready
42+
&& parameter is CodeExplorerComponentViewModel node
43+
&& _refactoring.CanExecute(_state, node.QualifiedSelection.Value.QualifiedName);
44+
}
45+
46+
private bool FurtherCanExecuteEvaluation(object parameter)
47+
{
48+
return _state.Status == ParserState.Ready
49+
&& parameter is CodeExplorerItemViewModel node
50+
&& node.Declaration != null;
51+
}
52+
53+
protected override void OnExecute(object parameter)
54+
{
55+
try
56+
{
57+
_refactoring.Refactor(((CodeExplorerItemViewModel)parameter).Declaration);
58+
}
59+
catch (RefactoringAbortedException)
60+
{ }
61+
catch (RefactoringException exception)
62+
{
63+
_failureNotifier.Notify(exception);
64+
}
65+
}
66+
}
67+
}

Rubberduck.Core/UI/Command/Refactorings/RefactorCodePaneCommandBase.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ public abstract class RefactorCodePaneCommandBase : RefactorCommandBase
1010
{
1111
protected readonly ISelectionProvider SelectionProvider;
1212

13-
protected RefactorCodePaneCommandBase(IRefactoring refactoring, IRefactoringFailureNotifier failureNotifier, ISelectionProvider selectionProvider, IParserStatusProvider parserStatusProvider)
13+
protected RefactorCodePaneCommandBase(
14+
IRefactoring refactoring,
15+
IRefactoringFailureNotifier failureNotifier,
16+
ISelectionProvider selectionProvider,
17+
IParserStatusProvider parserStatusProvider)
1418
: base (refactoring, failureNotifier, parserStatusProvider)
1519
{
1620
SelectionProvider = selectionProvider;
Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
using System.Collections.Generic;
2-
using System.Linq;
31
using System.Runtime.InteropServices;
4-
using Antlr4.Runtime;
5-
using Rubberduck.Parsing;
6-
using Rubberduck.Parsing.Grammar;
7-
using Rubberduck.Parsing.Symbols;
82
using Rubberduck.Parsing.VBA;
93
using Rubberduck.Refactorings.ExtractInterface;
104
using Rubberduck.UI.Command.Refactorings.Notifiers;
@@ -16,7 +10,8 @@ namespace Rubberduck.UI.Command.Refactorings
1610
public class RefactorExtractInterfaceCommand : RefactorCodePaneCommandBase
1711
{
1812
private readonly RubberduckParserState _state;
19-
13+
private readonly ExtractInterfaceRefactoring _extractInterfaceRefactoring;
14+
2015
public RefactorExtractInterfaceCommand(
2116
ExtractInterfaceRefactoring refactoring,
2217
ExtractInterfaceFailedNotifier extractInterfaceFailedNotifier,
@@ -25,6 +20,7 @@ public RefactorExtractInterfaceCommand(
2520
:base(refactoring, extractInterfaceFailedNotifier, selectionProvider, state)
2621
{
2722
_state = state;
23+
_extractInterfaceRefactoring = refactoring;
2824

2925
AddToCanExecuteEvaluation(SpecializedEvaluateCanExecute);
3026
}
@@ -36,41 +32,7 @@ private bool SpecializedEvaluateCanExecute(object parameter)
3632
{
3733
return false;
3834
}
39-
40-
var interfaceClass = _state.AllUserDeclarations.SingleOrDefault(item =>
41-
item.QualifiedName.QualifiedModuleName.Equals(activeSelection.Value.QualifiedName)
42-
&& ModuleTypes.Contains(item.DeclarationType));
43-
44-
if (interfaceClass == null)
45-
{
46-
return false;
47-
}
48-
49-
// interface class must have members to be implementable
50-
var hasMembers = _state.AllUserDeclarations.Any(item =>
51-
item.DeclarationType.HasFlag(DeclarationType.Member)
52-
&& item.ParentDeclaration != null
53-
&& item.ParentDeclaration.Equals(interfaceClass));
54-
55-
if (!hasMembers)
56-
{
57-
return false;
58-
}
59-
60-
var parseTree = _state.GetParseTree(interfaceClass.QualifiedName.QualifiedModuleName);
61-
var context = ((ParserRuleContext)parseTree).GetDescendents<VBAParser.ImplementsStmtContext>();
62-
63-
// true if active code pane is for a class/document/form module
64-
return !context.Any()
65-
&& !_state.IsNewOrModified(interfaceClass.QualifiedModuleName)
66-
&& !_state.IsNewOrModified(activeSelection.Value.QualifiedName);
35+
return _extractInterfaceRefactoring.CanExecute(_state, activeSelection.Value.QualifiedName);
6736
}
68-
69-
private static readonly IReadOnlyList<DeclarationType> ModuleTypes = new[]
70-
{
71-
DeclarationType.ClassModule,
72-
DeclarationType.UserForm,
73-
DeclarationType.Document,
74-
};
7537
}
7638
}

Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceRefactoring.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Linq;
33
using NLog;
4+
using Rubberduck.Parsing;
45
using Rubberduck.Parsing.Grammar;
56
using Rubberduck.Parsing.Rewriter;
67
using Rubberduck.Parsing.Symbols;
@@ -140,5 +141,37 @@ private string GetInterfaceModuleBody(ExtractInterfaceModel model)
140141
DeclarationType.Document,
141142
DeclarationType.UserForm
142143
};
144+
145+
//TODO: Redesign how refactoring commands are wired up to make this a responsibility of the command again.
146+
public bool CanExecute(RubberduckParserState state, QualifiedModuleName qualifiedName)
147+
{
148+
var interfaceClass = state.AllUserDeclarations.SingleOrDefault(item =>
149+
item.QualifiedName.QualifiedModuleName.Equals(qualifiedName)
150+
&& ModuleTypes.Contains(item.DeclarationType));
151+
152+
if (interfaceClass == null)
153+
{
154+
return false;
155+
}
156+
157+
// interface class must have members to be implementable
158+
var hasMembers = state.AllUserDeclarations.Any(item =>
159+
item.DeclarationType.HasFlag(DeclarationType.Member)
160+
&& item.ParentDeclaration != null
161+
&& item.ParentDeclaration.Equals(interfaceClass));
162+
163+
if (!hasMembers)
164+
{
165+
return false;
166+
}
167+
168+
var parseTree = state.GetParseTree(interfaceClass.QualifiedName.QualifiedModuleName);
169+
var context = ((Antlr4.Runtime.ParserRuleContext)parseTree).GetDescendents<VBAParser.ImplementsStmtContext>();
170+
171+
// true if active code pane is for a class/document/form module
172+
return !context.Any()
173+
&& !state.IsNewOrModified(interfaceClass.QualifiedModuleName)
174+
&& !state.IsNewOrModified(qualifiedName);
175+
}
143176
}
144177
}

Rubberduck.Resources/CodeExplorer/CodeExplorerUI.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rubberduck.Resources/CodeExplorer/CodeExplorerUI.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,9 @@ Continue?</value>
440440

441441
{0}</value>
442442
</data>
443+
<data name="CodeExplorer_ExtractInterfaceText" xml:space="preserve">
444+
<value>Extract Interface</value>
445+
</data>
443446
<data name="SyncProject" xml:space="preserve">
444447
<value>Sync Project</value>
445448
</data>

RubberduckTests/CodeExplorer/CodeExplorerViewModelTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,31 @@ public bool AddUserDocument_CanExecuteBasedOnProjectType(ProjectType projectType
246246
}
247247
}
248248

249+
[Category("Code Explorer")]
250+
[Test]
251+
[TestCase(ComponentType.ActiveXDesigner, ExpectedResult = true)]
252+
[TestCase(ComponentType.ClassModule, ExpectedResult = true)]
253+
[TestCase(ComponentType.ComComponent, ExpectedResult = true)]
254+
[TestCase(ComponentType.DocObject, ExpectedResult = true)]
255+
[TestCase(ComponentType.Document, ExpectedResult = true)]
256+
[TestCase(ComponentType.MDIForm, ExpectedResult = true)]
257+
[TestCase(ComponentType.PropPage, ExpectedResult = true)]
258+
[TestCase(ComponentType.RelatedDocument, ExpectedResult = false, Ignore = "Project doesn't contain selectable modules")]
259+
[TestCase(ComponentType.ResFile, ExpectedResult = false, Ignore = "Project doesn't contain selectable modules")]
260+
[TestCase(ComponentType.StandardModule, ExpectedResult = false)]
261+
[TestCase(ComponentType.Undefined, ExpectedResult = true)]
262+
[TestCase(ComponentType.UserControl, ExpectedResult = true)]
263+
[TestCase(ComponentType.UserForm, ExpectedResult = true)]
264+
[TestCase(ComponentType.VBForm, ExpectedResult = true)]
265+
public bool RefactorExtractInterface_CanExecuteBasedOnComponentType(ComponentType componentType)
266+
{
267+
using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, componentType, @"Public Sub Foo(): MsgBox """":End Sub ")
268+
.ImplementExtractIntercaceCommand().SelectFirstModule())
269+
{
270+
return explorer.ViewModel.CodeExplorerExtractInterfaceCommand.CanExecute(explorer.ViewModel.SelectedItem);
271+
}
272+
}
273+
249274
[Category("Code Explorer")]
250275
[Test]
251276
public void AddTestModule()

RubberduckTests/CodeExplorer/MockedCodeExplorer.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,15 @@ public MockedCodeExplorer ImplementIndenterCommand()
460460
return this;
461461
}
462462

463+
public MockedCodeExplorer ImplementExtractIntercaceCommand()
464+
{
465+
ViewModel.CodeExplorerExtractInterfaceCommand = new CodeExplorerExtractInterfaceCommand(
466+
new Rubberduck.Refactorings.ExtractInterface.ExtractInterfaceRefactoring(
467+
State, State, null, null, null),
468+
State, null, VbeEvents.Object);
469+
return this;
470+
}
471+
463472
public MockedCodeExplorer ConfigureSaveDialog(string path, DialogResult result)
464473
{
465474
SaveDialog.Setup(o => o.FileName).Returns(path);

0 commit comments

Comments
 (0)