Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@
<Compile Include="Dialogs\RenameProjectDialog.xaml.cs">
<DependentUpon>RenameProjectDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Dialogs\RenameProgressDialog.xaml.cs">
<DependentUpon>RenameProgressDialog.xaml</DependentUpon>
</Compile>
<Compile Include="ProjectRenamifierPackage.cs" />
<Compile Include="Services\ProjectFileService.cs" />
<Compile Include="Services\ProjectReferenceService.cs" />
Expand All @@ -90,6 +93,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Dialogs\RenameProgressDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<None Include="Resources\extension.manifest.json" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.ComponentModel.Design;
using System.IO;
using System.Windows.Interop;
using System.Windows.Threading;
using CodingWithCalvin.ProjectRenamifier.Dialogs;
using CodingWithCalvin.ProjectRenamifier.Services;
using EnvDTE;
Expand Down Expand Up @@ -80,41 +81,116 @@ private void RenameProject(Project project, DTE2 dte)
var newName = dialog.NewProjectName;
var projectFilePath = project.FullName;

// Collect projects that reference this project before removal
var referencingProjects = ProjectReferenceService.FindProjectsReferencingTarget(dte.Solution, projectFilePath);
// Show progress dialog
var progressDialog = new RenameProgressDialog(currentName);
var progressHelper = new WindowInteropHelper(progressDialog)
{
Owner = dte.MainWindow.HWnd
};
progressDialog.Show();

var stepIndex = 0;

// Step 1: Collect projects that reference this project before removal
ExecuteStep(progressDialog, stepIndex++, () =>
{
return ProjectReferenceService.FindProjectsReferencingTarget(dte.Solution, projectFilePath);
}, out var referencingProjects);
var oldProjectFilePath = projectFilePath;

// Capture the parent solution folder before removal
var parentSolutionFolder = SolutionFolderService.GetParentSolutionFolder(project);
// Step 2: Capture the parent solution folder before removal
ExecuteStep(progressDialog, stepIndex++, () =>
{
return SolutionFolderService.GetParentSolutionFolder(project);
}, out var parentSolutionFolder);

// Remove project from solution before file operations
dte.Solution.Remove(project);
// Step 3: Remove project from solution before file operations
ExecuteStep(progressDialog, stepIndex++, () =>
{
dte.Solution.Remove(project);
});

// Step 4: Update RootNamespace and AssemblyName in .csproj
ExecuteStep(progressDialog, stepIndex++, () =>
{
ProjectFileService.UpdateProjectFile(projectFilePath, currentName, newName);
});

// Update RootNamespace and AssemblyName in .csproj
ProjectFileService.UpdateProjectFile(projectFilePath, currentName, newName);
// Step 5: Update namespace declarations in source files
ExecuteStep(progressDialog, stepIndex++, () =>
{
SourceFileService.UpdateNamespacesInProject(projectFilePath, currentName, newName);
});

// Update namespace declarations in source files
SourceFileService.UpdateNamespacesInProject(projectFilePath, currentName, newName);
// Step 6: Rename the project file on disk
ExecuteStep(progressDialog, stepIndex++, () =>
{
return ProjectFileService.RenameProjectFile(projectFilePath, newName);
}, out projectFilePath);

// Rename the project file on disk
projectFilePath = ProjectFileService.RenameProjectFile(projectFilePath, newName);
// Step 7: Rename parent directory if it matches the old project name
ExecuteStep(progressDialog, stepIndex++, () =>
{
return ProjectFileService.RenameParentDirectoryIfMatches(projectFilePath, currentName, newName);
}, out projectFilePath);

// Rename parent directory if it matches the old project name
projectFilePath = ProjectFileService.RenameParentDirectoryIfMatches(projectFilePath, currentName, newName);
// Step 8: Update references in projects that referenced this project
ExecuteStep(progressDialog, stepIndex++, () =>
{
ProjectReferenceService.UpdateProjectReferences(referencingProjects, oldProjectFilePath, projectFilePath);
});

// Update references in projects that referenced this project
ProjectReferenceService.UpdateProjectReferences(referencingProjects, oldProjectFilePath, projectFilePath);
// Step 9: Re-add project to solution, preserving solution folder location
ExecuteStep(progressDialog, stepIndex++, () =>
{
SolutionFolderService.AddProjectToSolution(dte.Solution, projectFilePath, parentSolutionFolder);
});

// Re-add project to solution, preserving solution folder location
SolutionFolderService.AddProjectToSolution(dte.Solution, projectFilePath, parentSolutionFolder);
// Step 10: Update using statements across the entire solution
ExecuteStep(progressDialog, stepIndex++, () =>
{
SourceFileService.UpdateUsingStatementsInSolution(dte.Solution, currentName, newName);
});

// Update using statements across the entire solution
SourceFileService.UpdateUsingStatementsInSolution(dte.Solution, currentName, newName);
// Mark as complete and close after a brief delay
progressDialog.Complete();
DoEvents();
System.Threading.Thread.Sleep(500);
progressDialog.Close();

// TODO: Implement remaining rename operations
// See open issues for requirements:
// - #12: Progress indication
// - #13: Error handling and rollback
}

private void ExecuteStep(RenameProgressDialog dialog, int stepIndex, Action action)
{
dialog.StartStep(stepIndex);
DoEvents();
action();
dialog.CompleteStep(stepIndex);
DoEvents();
}

private void ExecuteStep<T>(RenameProgressDialog dialog, int stepIndex, Func<T> func, out T result)
{
dialog.StartStep(stepIndex);
DoEvents();
result = func();
dialog.CompleteStep(stepIndex);
DoEvents();
}

private void DoEvents()
{
var frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(obj =>
{
((DispatcherFrame)obj).Continue = false;
return null;
}), frame);
Dispatcher.PushFrame(frame);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<Window x:Class="CodingWithCalvin.ProjectRenamifier.Dialogs.RenameProgressDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Renaming Project"
Width="400"
Height="340"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
ShowInTaskbar="False">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<TextBlock Grid.Row="0"
x:Name="HeaderText"
Text="Renaming project..."
FontWeight="SemiBold"
FontSize="14"
Margin="0,0,0,16"/>

<ItemsControl Grid.Row="1"
x:Name="StepsList"
Margin="0,0,0,16">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding StatusIcon}"
FontFamily="Segoe UI Symbol"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Text="{Binding Description}"
Foreground="{Binding TextColor}"
VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

<ProgressBar Grid.Row="2"
x:Name="ProgressBar"
Height="8"
IsIndeterminate="False"
Minimum="0"
Maximum="100"/>
</Grid>
</Window>
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;

namespace CodingWithCalvin.ProjectRenamifier.Dialogs
{
public partial class RenameProgressDialog : Window
{
public ObservableCollection<ProgressStep> Steps { get; }

public RenameProgressDialog(string projectName)
{
InitializeComponent();

HeaderText.Text = $"Renaming '{projectName}'...";

Steps = new ObservableCollection<ProgressStep>
{
new ProgressStep("Analyzing project references"),
new ProgressStep("Capturing solution structure"),
new ProgressStep("Removing project from solution"),
new ProgressStep("Updating project file"),
new ProgressStep("Updating namespace declarations"),
new ProgressStep("Renaming project file"),
new ProgressStep("Renaming project directory"),
new ProgressStep("Updating project references"),
new ProgressStep("Re-adding project to solution"),
new ProgressStep("Updating using statements")
};

StepsList.ItemsSource = Steps;
}

public void StartStep(int stepIndex)
{
if (stepIndex >= 0 && stepIndex < Steps.Count)
{
Steps[stepIndex].Status = StepStatus.InProgress;
UpdateProgress(stepIndex);
}
}

public void CompleteStep(int stepIndex)
{
if (stepIndex >= 0 && stepIndex < Steps.Count)
{
Steps[stepIndex].Status = StepStatus.Completed;
UpdateProgress(stepIndex + 1);
}
}

public void FailStep(int stepIndex, string error)
{
if (stepIndex >= 0 && stepIndex < Steps.Count)
{
Steps[stepIndex].Status = StepStatus.Failed;
Steps[stepIndex].Description += $" - {error}";
}
}

public void Complete()
{
HeaderText.Text = "Rename completed successfully!";
ProgressBar.Value = 100;
}

private void UpdateProgress(int completedSteps)
{
ProgressBar.Value = (completedSteps * 100.0) / Steps.Count;
}
}

public enum StepStatus
{
Pending,
InProgress,
Completed,
Failed
}

public class ProgressStep : INotifyPropertyChanged
{
private string _description;
private StepStatus _status;

public ProgressStep(string description)
{
_description = description;
_status = StepStatus.Pending;
}

public string Description
{
get => _description;
set
{
_description = value;
OnPropertyChanged(nameof(Description));
}
}

public StepStatus Status
{
get => _status;
set
{
_status = value;
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(StatusIcon));
OnPropertyChanged(nameof(TextColor));
}
}

public string StatusIcon => _status switch
{
StepStatus.Pending => "\u2022", // Bullet
StepStatus.InProgress => "\u25B6", // Play arrow
StepStatus.Completed => "\u2714", // Check mark
StepStatus.Failed => "\u2716", // X mark
_ => "\u2022"
};

public Brush TextColor => _status switch
{
StepStatus.Pending => Brushes.Gray,
StepStatus.InProgress => Brushes.Black,
StepStatus.Completed => Brushes.Green,
StepStatus.Failed => Brushes.Red,
_ => Brushes.Gray
};

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}