Next release of Rubberduck (should be 1.4) introduces a reorder parameters refactoring, which lets user reorder the parameters of a module (or class) member, and automatically adjust all usages.
Before:
After:
This is the IDialogView
interface implementation:
interface IReorderParametersView : IDialogView
{
Declaration Target { get; set; }
List<Parameter> Parameters { get; set; }
void InitializeParameterGrid();
}
And here is the Parameter
class:
public class Parameter
{
public string FullDeclaration { get; private set; }
public int Index { get; private set; }
public bool IsOptional { get; private set; }
public bool IsParamArray { get; private set; }
public Parameter(string fullDeclaration, int index)
{
FullDeclaration = fullDeclaration;
Index = index;
IsOptional = FullDeclaration.Contains("Optional");
IsParamArray = FullDeclaration.Contains("ParamArray");
}
}
This is the dialog code-behind:
public partial class ReorderParametersDialog : Form, IReorderParametersView
{
public List<Parameter> Parameters { get; set; }
private Parameter _selectedItem;
private Rectangle _dragBoxFromMouseDown;
Point _startPoint;
private int _newRowIndex;
public ReorderParametersDialog()
{
Parameters = new List<Parameter>();
InitializeComponent();
InitializeCaptions();
MethodParametersGrid.SelectionChanged += MethodParametersGrid_SelectionChanged;
MethodParametersGrid.MouseMove += MethodParametersGrid_MouseMove;
MethodParametersGrid.MouseDown += MethodParametersGrid_MouseDown;
MethodParametersGrid.DragOver += MethodParametersGrid_DragOver;
MethodParametersGrid.DragDrop += MethodParametersGrid_DragDrop;
}
private void InitializeCaptions()
{
OkButton.Text = RubberduckUI.OkButtonText;
CancelButton.Text = RubberduckUI.CancelButtonText;
Text = RubberduckUI.ReorderParamsDialog_Caption;
TitleLabel.Text = RubberduckUI.ReorderParamsDialog_TitleText;
InstructionsLabel.Text = RubberduckUI.ReorderParamsDialog_InstructionsLabelText;
MoveUpButton.Text = RubberduckUI.ReorderParamsDialog_MoveUpButtonText;
MoveDownButton.Text = RubberduckUI.ReorderParamsDialog_MoveDownButtonText;
}
private void MethodParametersGrid_SelectionChanged(object sender, EventArgs e)
{
SelectionChanged();
}
private void MethodParametersGrid_MouseMove(object sender, MouseEventArgs e)
{
if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
{
if (_dragBoxFromMouseDown != Rectangle.Empty && !_dragBoxFromMouseDown.Contains(e.X, e.Y))
{
var dropEffect = MethodParametersGrid.DoDragDrop(
MethodParametersGrid.Rows[_newRowIndex],
DragDropEffects.Move);
}
}
}
private void MethodParametersGrid_MouseDown(object sender, MouseEventArgs e)
{
_newRowIndex = MethodParametersGrid.HitTest(e.X, e.Y).RowIndex;
if (_newRowIndex == -1)
{
_dragBoxFromMouseDown = Rectangle.Empty;
return;
}
_startPoint = new Point(e.X, e.Y);
var dragSize = SystemInformation.DragSize;
_dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2), e.Y - (dragSize.Height / 2)), dragSize);
}
private void MethodParametersGrid_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void MethodParametersGrid_DragDrop(object sender, DragEventArgs e)
{
var clientPoint = MethodParametersGrid.PointToClient(new Point(e.X, e.Y));
if (e.Effect == DragDropEffects.Move && _newRowIndex != -1)
{
var rowIndexOfItemUnderMouse = MethodParametersGrid.HitTest(clientPoint.X, clientPoint.Y).RowIndex;
if (rowIndexOfItemUnderMouse < 0)
{
if (clientPoint.Y < _startPoint.Y)
{
rowIndexOfItemUnderMouse = 0;
}
else
{
rowIndexOfItemUnderMouse = Parameters.Count - 1;
}
}
var tmp = Parameters.ElementAt(_newRowIndex);
Parameters.RemoveAt(_newRowIndex);
Parameters.Insert(rowIndexOfItemUnderMouse, tmp);
ReselectParameter();
}
}
public void InitializeParameterGrid()
{
MethodParametersGrid.AutoGenerateColumns = false;
MethodParametersGrid.Columns.Clear();
MethodParametersGrid.DataSource = Parameters;
MethodParametersGrid.AlternatingRowsDefaultCellStyle.BackColor = Color.Lavender;
MethodParametersGrid.MultiSelect = false;
MethodParametersGrid.AllowUserToResizeRows = false;
MethodParametersGrid.AllowDrop = true;
MethodParametersGrid.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
var column = new DataGridViewTextBoxColumn
{
Name = "Parameter",
DataPropertyName = "FullDeclaration",
HeaderText = "Parameter",
ReadOnly = true,
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
};
MethodParametersGrid.Columns.Add(column);
_selectedItem = Parameters[0];
}
private void OkButtonClick(object sender, EventArgs e)
{
OnOkButtonClicked();
}
public event EventHandler CancelButtonClicked;
public void OnCancelButtonClicked()
{
Hide();
}
public event EventHandler OkButtonClicked;
public void OnOkButtonClicked()
{
var handler = OkButtonClicked;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
private Declaration _target;
public Declaration Target
{
get { return _target; }
set { _target = value; }
}
private void MoveUpButtonClicked(object sender, EventArgs e)
{
if (MethodParametersGrid.SelectedRows.Count == 0)
{
return;
}
var selectedIndex = MethodParametersGrid.SelectedRows[0].Index;
SwapParameters(selectedIndex, selectedIndex - 1);
ReselectParameter();
}
private void MoveDownButtonClicked(object sender, EventArgs e)
{
if (MethodParametersGrid.SelectedRows.Count == 0)
{
return;
}
var selectedIndex = MethodParametersGrid.SelectedRows[0].Index;
SwapParameters(selectedIndex, selectedIndex + 1);
ReselectParameter();
}
private void SwapParameters(int index1, int index2)
{
var tmp = Parameters[index1];
Parameters[index1] = Parameters[index2];
Parameters[index2] = tmp;
}
private void ReselectParameter()
{
MethodParametersGrid.Refresh();
MethodParametersGrid.Rows
.Cast<DataGridViewRow>()
.Single(row => row.DataBoundItem == _selectedItem).Selected = true;
SelectionChanged();
}
private void SelectionChanged()
{
_selectedItem = MethodParametersGrid.SelectedRows.Count == 0
? null
: (Parameter)MethodParametersGrid.SelectedRows[0].DataBoundItem;
MoveUpButton.Enabled = _selectedItem != null
&& MethodParametersGrid.SelectedRows[0].Index != 0;
MoveDownButton.Enabled = _selectedItem != null
&& MethodParametersGrid.SelectedRows[0].Index != Parameters.Count - 1;
}
}
And here is the presenter:
class ReorderParametersPresenter
{
private readonly IReorderParametersView _view;
private readonly Declarations _declarations;
private readonly QualifiedSelection _selection;
public ReorderParametersPresenter(IReorderParametersView view, VBProjectParseResult parseResult, QualifiedSelection selection)
{
_view = view;
_declarations = parseResult.Declarations;
_selection = selection;
_view.OkButtonClicked += OnOkButtonClicked;
}
public void Show()
{
AcquireTarget(_selection);
if (_view.Target != null)
{
LoadParameters();
if (_view.Parameters.Count < 2)
{
var message = string.Format(RubberduckUI.ReorderPresenter_LessThanTwoVariablesError, _view.Target.IdentifierName);
MessageBox.Show(message, RubberduckUI.ReorderParamsDialog_TitleText, MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
_view.InitializeParameterGrid();
_view.ShowDialog();
}
}
private void LoadParameters()
{
var procedure = (dynamic)_view.Target.Context;
var argList = (VBAParser.ArgListContext)procedure.argList();
var args = argList.arg();
var index = 0;
foreach (var arg in args)
{
_view.Parameters.Add(new Parameter(arg.GetText(), index++));
}
}
private void OnOkButtonClicked(object sender, EventArgs e)
{
if (!_view.Parameters.Where((t, i) => t.Index != i).Any())
{
return;
}
var indexOfFirstOptionalParam = _view.Parameters.FindIndex(param => param.IsOptional);
if (indexOfFirstOptionalParam >= 0)
{
for (var index = indexOfFirstOptionalParam + 1; index < _view.Parameters.Count; index++)
{
if (!_view.Parameters.ElementAt(index).IsOptional)
{
MessageBox.Show(RubberduckUI.ReorderPresenter_OptionalVariableError, RubberduckUI.ReorderParamsDialog_TitleText, MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
}
var indexOfParamArray = _view.Parameters.FindIndex(param => param.IsParamArray);
if (indexOfParamArray >= 0)
{
if (indexOfParamArray != _view.Parameters.Count - 1)
{
MessageBox.Show(RubberduckUI.ReorderPresenter_ParamArrayError, RubberduckUI.ReorderParamsDialog_TitleText, MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
AdjustSignatures();
AdjustReferences(_view.Target.References);
}
private void AdjustReferences(IEnumerable<IdentifierReference> references)
{
foreach (var reference in references.Where(item => item.Context != _view.Target.Context))
{
var proc = (dynamic)reference.Context.Parent;
var module = reference.QualifiedModuleName.Component.CodeModule;
// This is to prevent throws when this statement fails:
// (VBAParser.ArgsCallContext)proc.argsCall();
try
{
var check = (VBAParser.ArgsCallContext)proc.argsCall();
}
catch
{
continue;
}
var argList = (VBAParser.ArgsCallContext)proc.argsCall();
if (argList == null)
{
continue;
}
RewriteCall(reference, argList, module);
}
}
private void RewriteCall(IdentifierReference reference, VBAParser.ArgsCallContext argList, Microsoft.Vbe.Interop.CodeModule module)
{
var paramNames = argList.argCall().Select(arg => arg.GetText()).ToList();
var lineCount = argList.Stop.Line - argList.Start.Line + 1; // adjust for total line count
var variableIndex = 0;
for (var line = argList.Start.Line; line < argList.Start.Line + lineCount; line++)
{
var newContent = module.Lines[line, 1].Replace(" , ", "");
var currentStringIndex = line == argList.Start.Line ? reference.Declaration.IdentifierName.Length : 0;
for (var i = 0; i < paramNames.Count && variableIndex < _view.Parameters.Count; i++)
{
var variableStringIndex = newContent.IndexOf(paramNames.ElementAt(i), currentStringIndex);
if (variableStringIndex > -1)
{
if (_view.Parameters.ElementAt(variableIndex).Index >= paramNames.Count)
{
newContent = newContent.Insert(variableStringIndex, " , ");
i--;
variableIndex++;
continue;
}
var oldVariableString = paramNames.ElementAt(i);
var newVariableString = paramNames.ElementAt(_view.Parameters.ElementAt(variableIndex).Index);
var beginningSub = newContent.Substring(0, variableStringIndex);
var replaceSub = newContent.Substring(variableStringIndex).Replace(oldVariableString, newVariableString);
newContent = beginningSub + replaceSub;
variableIndex++;
currentStringIndex = beginningSub.Length + newVariableString.Length;
}
}
module.ReplaceLine(line, newContent);
}
}
private void AdjustSignatures()
{
var proc = (dynamic)_view.Target.Context;
var argList = (VBAParser.ArgListContext)proc.argList();
var module = _view.Target.QualifiedName.QualifiedModuleName.Component.CodeModule;
// if we are reordering a property getter, check if we need to reorder a letter/setter too
if (_view.Target.DeclarationType == DeclarationType.PropertyGet)
{
var setter = _declarations.Items.FirstOrDefault(item => item.ParentScope == _view.Target.ParentScope &&
item.IdentifierName == _view.Target.IdentifierName &&
item.DeclarationType == DeclarationType.PropertySet);
if (setter != null)
{
AdjustSignatures(setter);
}
var letter = _declarations.Items.FirstOrDefault(item => item.ParentScope == _view.Target.ParentScope &&
item.IdentifierName == _view.Target.IdentifierName &&
item.DeclarationType == DeclarationType.PropertyLet);
if (letter != null)
{
AdjustSignatures(letter);
}
}
RewriteSignature(argList, module);
foreach (var withEvents in _declarations.Items.Where(item => item.IsWithEvents && item.AsTypeName == _view.Target.ComponentName))
{
foreach (var reference in _declarations.FindEventProcedures(withEvents))
{
AdjustSignatures(reference);
}
}
var interfaceImplementations = _declarations.FindInterfaceImplementationMembers()
.Where(item => item.Project.Equals(_view.Target.Project) &&
item.IdentifierName == _view.Target.ComponentName + "_" + _view.Target.IdentifierName);
foreach (var interfaceImplentation in interfaceImplementations)
{
AdjustSignatures(interfaceImplentation);
AdjustReferences(interfaceImplentation.References);
}
}
private void AdjustSignatures(IdentifierReference reference)
{
var proc = (dynamic)reference.Context.Parent;
var module = reference.QualifiedModuleName.Component.CodeModule;
var argList = (VBAParser.ArgListContext)proc.argList();
RewriteSignature(argList, module);
}
private void AdjustSignatures(Declaration reference)
{
var proc = (dynamic)reference.Context.Parent;
var module = reference.QualifiedName.QualifiedModuleName.Component.CodeModule;
VBAParser.ArgListContext argList;
if (reference.DeclarationType == DeclarationType.PropertySet || reference.DeclarationType == DeclarationType.PropertyLet)
{
argList = (VBAParser.ArgListContext)proc.children[0].argList();
}
else
{
argList = (VBAParser.ArgListContext)proc.subStmt().argList();
}
RewriteSignature(argList, module);
}
private void RewriteSignature(VBAParser.ArgListContext argList, Microsoft.Vbe.Interop.CodeModule module)
{
var args = argList.arg();
var variableIndex = 0;
for (var lineNum = argList.Start.Line; lineNum < argList.Start.Line + argList.GetSelection().LineCount; lineNum++)
{
var newContent = module.Lines[lineNum, 1];
var currentStringIndex = 0;
for (var i = variableIndex; i < _view.Parameters.Count; i++)
{
var variableStringIndex = newContent.IndexOf(_view.Parameters.Find(item => item.Index == variableIndex).FullDeclaration, currentStringIndex);
if (variableStringIndex > -1)
{
var oldVariableString = _view.Parameters.Find(item => item.Index == variableIndex).FullDeclaration;
var newVariableString = _view.Parameters.ElementAt(i).FullDeclaration;
var beginningSub = newContent.Substring(0, variableStringIndex);
var replaceSub = newContent.Substring(variableStringIndex).Replace(oldVariableString, newVariableString);
newContent = beginningSub + replaceSub;
variableIndex++;
currentStringIndex = beginningSub.Length + newVariableString.Length;
}
}
module.ReplaceLine(lineNum, newContent);
}
}
private static readonly DeclarationType[] ValidDeclarationTypes =
{
DeclarationType.Event,
DeclarationType.Function,
DeclarationType.Procedure,
DeclarationType.PropertyGet,
DeclarationType.PropertyLet,
DeclarationType.PropertySet
};
private void AcquireTarget(QualifiedSelection selection)
{
var target = _declarations.Items
.Where(item => !item.IsBuiltIn)
.FirstOrDefault(item => IsSelectedDeclaration(selection, item)
|| IsSelectedReference(selection, item));
if (target == null || !ValidDeclarationTypes.Contains(target.DeclarationType))
{
FindTarget(ref target, selection);
}
if (target != null && target.DeclarationType == DeclarationType.PropertySet)
{
var getter = _declarations.Items.FirstOrDefault(item => item.ParentScope == target.ParentScope &&
item.IdentifierName == target.IdentifierName &&
item.DeclarationType == DeclarationType.PropertyGet);
if (getter != null)
{
target = getter;
}
}
PromptIfTargetImplementsInterface(ref target);
_view.Target = target;
}
private void FindTarget(ref Declaration target, QualifiedSelection selection)
{
var targets = _declarations.Items
.Where(item => !item.IsBuiltIn
&& item.ComponentName == selection.QualifiedName.ComponentName
&& ValidDeclarationTypes.Contains(item.DeclarationType));
foreach (var declaration in targets)
{
var startLine = declaration.Context.GetSelection().StartLine;
var startColumn = declaration.Context.GetSelection().StartColumn;
var endLine = declaration.Context.GetSelection().EndLine;
var endColumn = declaration.Context.GetSelection().EndColumn;
if (startLine <= selection.Selection.StartLine && endLine >= selection.Selection.EndLine)
{
if (startLine == selection.Selection.StartLine && startColumn > selection.Selection.StartColumn)
{
continue;
}
if (endLine == selection.Selection.EndLine && endColumn < selection.Selection.EndColumn)
{
continue;
}
target = declaration;
}
foreach (var reference in declaration.References)
{
var proc = (dynamic)reference.Context.Parent;
// This is to prevent throws when this statement fails:
// (VBAParser.ArgsCallContext)proc.argsCall();
try
{
var check = (VBAParser.ArgsCallContext)proc.argsCall();
}
catch
{
continue;
}
var argList = (VBAParser.ArgsCallContext)proc.argsCall();
if (argList == null)
{
continue;
}
startLine = argList.Start.Line;
startColumn = argList.Start.Column;
endLine = argList.Stop.Line;
endColumn = argList.Stop.Column + argList.Stop.Text.Length + 1;
if ((startLine <= selection.Selection.StartLine && endLine >= selection.Selection.EndLine) &&
(startLine == selection.Selection.StartLine && startColumn > selection.Selection.StartColumn ||
endLine == selection.Selection.EndLine && endColumn < selection.Selection.EndColumn))
{
continue;
}
target = reference.Declaration;
return;
}
}
}
private void PromptIfTargetImplementsInterface(ref Declaration target)
{
var declaration = target;
var interfaceImplementation = _declarations.FindInterfaceImplementationMembers().SingleOrDefault(m => m.Equals(declaration));
if (target == null || interfaceImplementation == null)
{
return;
}
var interfaceMember = _declarations.FindInterfaceMember(interfaceImplementation);
var message = string.Format(RubberduckUI.ReorderPresenter_TargetIsInterfaceMemberImplementation, target.IdentifierName, interfaceMember.ComponentName, interfaceMember.IdentifierName);
var confirm = MessageBox.Show(message, RubberduckUI.ReorderParamsDialog_TitleText, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation);
if (confirm == DialogResult.No)
{
target = null;
return;
}
target = interfaceMember;
}
private bool IsSelectedReference(QualifiedSelection selection, Declaration declaration)
{
return declaration.References.Any(r =>
r.QualifiedModuleName == selection.QualifiedName &&
r.Selection.ContainsFirstCharacter(selection.Selection));
}
private bool IsSelectedDeclaration(QualifiedSelection selection, Declaration declaration)
{
return declaration.QualifiedName.QualifiedModuleName == selection.QualifiedName
&& (declaration.Selection.ContainsFirstCharacter(selection.Selection));
}
}
My main concerns are how similar certain sections of the code are, any ideas to make this less repetitive are welcome.