Skip to content

Commit 3078d19

Browse files
authored
feat(ui): add user input dialog for project rename (#14)
- Add WPF RenameProjectDialog with validation - Pre-populates with current project name - Validates: not empty, no invalid chars, different from current - Shows inline error messages - Integrate dialog into RenamifyProjectCommand - Convert solution to SLNX format - Add CLAUDE.md with project guidance and workflow rules - Add WPF references to csproj Closes #3
1 parent ca00e32 commit 3078d19

File tree

8 files changed

+272
-152
lines changed

8 files changed

+272
-152
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,6 @@ $RECYCLE.BIN/
279279

280280
# Windows shortcuts
281281
*.lnk
282+
283+
# Claude Code local settings
284+
.claude/settings.local.json

CLAUDE.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
This is a Visual Studio 2022 extension (VSIX) called "Project Renamifier" that allows users to rename projects completely from within Visual Studio, including the filename, parent folder, namespace, and references in the solution file and other projects.
8+
9+
**Status**: Pre-alpha / WIP - core functionality is stubbed out but not fully implemented.
10+
11+
## Build Commands
12+
13+
```bash
14+
# Build the solution (from repo root)
15+
msbuild src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.slnx
16+
17+
# Or open in Visual Studio and build (F5 to debug launches experimental VS instance)
18+
```
19+
20+
## Development Setup
21+
22+
- Requires Visual Studio 2022 with the VS SDK workload
23+
- Install [Extensibility Essentials 2022](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ExtensibilityEssentials2022) extension
24+
- Debug launches VS experimental instance (`/rootsuffix Exp`)
25+
26+
## Architecture
27+
28+
**Extension Entry Point**: `ProjectRenamifierPackage.cs` - AsyncPackage that initializes the command on VS startup.
29+
30+
**Command Handler**: `Commands/RenamifyProjectCommand.cs` - Handles the "Renamify Project" context menu command. Currently a skeleton awaiting implementation. See open GitHub issues for feature requirements.
31+
32+
**Command Definition**: `VSCommandTable.vsct` - Defines the command and places it in the Project context menu (`IDG_VS_CTXT_PROJECT_EXPLORE`).
33+
34+
## Key VS SDK Patterns Used
35+
36+
- Uses DTE/DTE2 automation model for solution/project manipulation
37+
- Uses VSLangProj for project reference management
38+
- Commands registered via VSCT files and OleMenuCommandService
39+
- **Always use WPF for user interface** (dialogs, windows, etc.)
40+
41+
## Coding Standards
42+
43+
Follow Microsoft's official guidelines:
44+
45+
- [C# Coding Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)
46+
- [.NET Design Guidelines](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/)
47+
- [Framework Design Guidelines](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/general-naming-conventions)
48+
49+
Key points:
50+
- Use PascalCase for public members, types, namespaces, and methods
51+
- Use camelCase for private fields (prefix with `_` e.g., `_fieldName`)
52+
- Use `var` when the type is obvious from the right side of the assignment
53+
- Use meaningful, descriptive names
54+
- Prefer `async`/`await` for asynchronous operations
55+
- Use `ThreadHelper.ThrowIfNotOnUIThread()` when accessing VS services that require the UI thread
56+
57+
---
58+
59+
## Workflow Rules
60+
61+
**These rules must be followed:**
62+
63+
1. **NEVER commit directly to main** - Always create a feature branch and submit a pull request. No exceptions.
64+
2. **Conventional commits** - Format: `type(scope): description`
65+
3. **GitHub Issues for TODOs** - Use `gh` CLI to manage issues; use conventional commit format for issue titles
66+
4. **Pull Request titles** - Use conventional commit format (same as commits)
67+
5. **Branch naming** - Use format: `type/scope/short-description` (e.g., `feat/rename/user-input-dialog`)
68+
6. **Working an issue** - Always create a new branch from an updated main branch
69+
7. **Check branch status before pushing** - Verify the remote tracking branch still exists; if a PR was merged/deleted, create a new branch from main
70+
8. **No AI attribution in commits** - Do NOT include "Generated with Claude Code", "Co-Authored-By: Claude", or similar AI attribution lines in commit messages
71+
72+
### GitHub Issues
73+
74+
```bash
75+
gh issue list # List open issues
76+
gh issue view <number> # View details
77+
gh issue create --title "feat(rename): add user input dialog" --body "..."
78+
gh issue close <number>
79+
```
80+
81+
### Conventional Commit Types
82+
83+
| Type | Description |
84+
|------|-------------|
85+
| `feat` | New feature |
86+
| `fix` | Bug fix |
87+
| `docs` | Documentation only |
88+
| `refactor` | Code change that neither fixes a bug nor adds a feature |
89+
| `test` | Adding or updating tests |
90+
| `chore` | Maintenance tasks |

src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.csproj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
<ItemGroup>
4848
<Compile Include="Properties\AssemblyInfo.cs" />
4949
<Compile Include="Commands\RenamifyProjectCommand.cs" />
50+
<Compile Include="Dialogs\RenameProjectDialog.xaml.cs">
51+
<DependentUpon>RenameProjectDialog.xaml</DependentUpon>
52+
</Compile>
5053
<Compile Include="ProjectRenamifierPackage.cs" />
5154
<Compile Include="source.extension.cs">
5255
<AutoGen>True</AutoGen>
@@ -59,6 +62,12 @@
5962
<DependentUpon>VSCommandTable.vsct</DependentUpon>
6063
</Compile>
6164
</ItemGroup>
65+
<ItemGroup>
66+
<Page Include="Dialogs\RenameProjectDialog.xaml">
67+
<SubType>Designer</SubType>
68+
<Generator>MSBuild:Compile</Generator>
69+
</Page>
70+
</ItemGroup>
6271
<ItemGroup>
6372
<None Include="Resources\extension.manifest.json" />
6473
<Content Include="Resources\LICENSE">
@@ -81,6 +90,10 @@
8190
<Reference Include="System" />
8291
<Reference Include="System.Design" />
8392
<Reference Include="System.ComponentModel.Composition" />
93+
<Reference Include="System.Xaml" />
94+
<Reference Include="PresentationCore" />
95+
<Reference Include="PresentationFramework" />
96+
<Reference Include="WindowsBase" />
8497
</ItemGroup>
8598
<ItemGroup>
8699
<PackageReference Include="Microsoft.VisualStudio.SDK">

src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.sln

Lines changed: 0 additions & 37 deletions
This file was deleted.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Solution>
2+
<Configurations>
3+
<BuildType Name="Debug" />
4+
<BuildType Name="Release" />
5+
<Platform Name="Any CPU" />
6+
<Platform Name="arm64" />
7+
<Platform Name="x86" />
8+
</Configurations>
9+
<Project Path="CodingWithCalvin.ProjectRenamifier.csproj" />
10+
</Solution>
Lines changed: 33 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
using EnvDTE;
2-
using EnvDTE80;
3-
using Microsoft.VisualStudio.Shell.Interop;
4-
using System.Collections.Generic;
51
using System.ComponentModel.Design;
62
using System.IO;
7-
using System.Windows.Forms;
8-
using VSLangProj;
3+
using System.Windows.Interop;
4+
using CodingWithCalvin.ProjectRenamifier.Dialogs;
5+
using EnvDTE;
6+
using EnvDTE80;
97

108
namespace CodingWithCalvin.ProjectRenamifier
119
{
@@ -36,140 +34,60 @@ private RenamifyProjectCommand(Package package)
3634
PackageGuids.CommandSetGuid,
3735
PackageIds.RenamifyProjectCommandId
3836
);
39-
var menuItem = new MenuCommand(RenamifyProject, menuCommandId);
37+
var menuItem = new MenuCommand(Execute, menuCommandId);
4038
commandService.AddCommand(menuItem);
4139
}
4240

43-
private void RenamifyProject(object sender, EventArgs e)
41+
private void Execute(object sender, EventArgs e)
4442
{
4543
ThreadHelper.ThrowIfNotOnUIThread();
44+
4645
if (!(ServiceProvider.GetService(typeof(DTE)) is DTE2 dte))
4746
{
48-
throw new ArgumentNullException(nameof(dte));
47+
return;
4948
}
5049

51-
foreach (
52-
UIHierarchyItem selectedItem in (Array)
53-
dte.ToolWindows.SolutionExplorer.SelectedItems
54-
)
50+
var selectedItems = (Array)dte.ToolWindows.SolutionExplorer.SelectedItems;
51+
foreach (UIHierarchyItem selectedItem in selectedItems)
5552
{
56-
switch (selectedItem.Object)
53+
if (selectedItem.Object is Project project)
5754
{
58-
case Project project:
59-
try
60-
{
61-
RenameProject(project, dte);
62-
}
63-
catch (Exception ex)
64-
{
65-
MessageBox.Show(
66-
$@"
67-
Unable to rename selected project
68-
{Environment.NewLine}
69-
{Environment.NewLine}
70-
Exception: {ex.Message}"
71-
);
72-
}
73-
74-
break;
55+
RenameProject(project, dte);
7556
}
7657
}
7758
}
7859

79-
void RenameProject(Project project, DTE2 dte)
60+
private void RenameProject(Project project, DTE2 dte)
8061
{
8162
ThreadHelper.ThrowIfNotOnUIThread();
8263

83-
var oldProjectFileName = Path.GetFileName(project.FullName);
84-
var projectFileNameNoExtension = Path.GetFileNameWithoutExtension(project.FullName);
85-
var projectExtension = Path.GetExtension(oldProjectFileName);
86-
87-
var newProjectFileName = $"{projectFileNameNoExtension}-NEW{projectExtension}";
88-
89-
var projectPath =
90-
Path.GetDirectoryName(project.FullName)
91-
?? throw new InvalidOperationException();
92-
var parentDirectoryName = new DirectoryInfo(projectPath).Parent.Name;
93-
var oldParentDirectory = new DirectoryInfo(projectPath).Parent.FullName;
94-
95-
var grandparentDirectory = new DirectoryInfo(parentDirectoryName).Parent.FullName;
96-
var newParentDirectory = Path.Combine(grandparentDirectory, $"{parentDirectoryName}-NEW");
97-
98-
// hold onto all projects that reference this project
99-
var referencingProjects = new List<Project>();
100-
foreach (Project p in dte.Solution.Projects)
101-
{
102-
if(p.Object is VSProject referencingVSProject)
103-
{
104-
foreach(Reference reference in referencingVSProject.References)
105-
{
106-
if(reference.SourceProject != null && reference.SourceProject.UniqueName == project.UniqueName)
107-
{
108-
referencingProjects.Add(p);
109-
}
110-
}
111-
}
112-
}
64+
var currentName = Path.GetFileNameWithoutExtension(project.FullName);
11365

114-
// hold onto all projects that this project references
115-
var referencedProjects = new List<Project>();
116-
117-
if (project.Object is VSProject referencedVSProject)
118-
{
119-
foreach (Reference reference in referencedVSProject.References)
120-
{
121-
if (reference.SourceProject != null)
122-
{
123-
referencedProjects.Add(reference.SourceProject);
124-
}
125-
}
126-
}
127-
128-
// unload project, then do the work
129-
dte.Solution.Remove(project);
66+
var dialog = new RenameProjectDialog(currentName);
13067

131-
//rename parent directory, if necessary
132-
if (parentDirectoryName.Equals(projectFileNameNoExtension))
68+
// Set the owner to the VS main window for proper modal behavior
69+
var hwnd = new IntPtr(dte.MainWindow.HWnd);
70+
var helper = new WindowInteropHelper(dialog)
13371
{
134-
Directory.Move(oldParentDirectory, newParentDirectory);
135-
}
136-
137-
// rename project
138-
File.Move(oldProjectFileName, newProjectFileName);
72+
Owner = hwnd
73+
};
13974

140-
// add new project back to solution
141-
dte.Solution.AddFromFile(newProjectFileName, true);
142-
143-
// need handle to new project now that its back
144-
Project newProjectReference = null;
145-
foreach (Project p in dte.Solution.Projects)
146-
{
147-
if(p.UniqueName == newProjectFileName)
148-
{
149-
newProjectReference = p;
150-
break;
151-
}
152-
}
153-
154-
// fix projects that reference the old project to reference the new one
155-
foreach (var referencingProject in referencingProjects)
75+
if (dialog.ShowDialog() != true)
15676
{
157-
if(referencingProject.Object is VSProject vsProject)
158-
{
159-
vsProject.References.AddProject(newProjectReference);
160-
}
77+
return;
16178
}
16279

163-
// fix this project's references
164-
foreach (var referencedProject in referencedProjects)
165-
{
166-
if(newProjectReference.Object is VSProject vsProject2)
167-
{
168-
vsProject2.References.AddProject(referencedProject);
169-
}
170-
}
171-
172-
// sync new namespace?
80+
var newName = dialog.NewProjectName;
81+
82+
// TODO: Implement the actual rename operation
83+
// See open issues for requirements:
84+
// - #6: Update RootNamespace in .csproj
85+
// - #7: Update AssemblyName in .csproj
86+
// - #8: Update namespace declarations in source files
87+
// - #9: Update using statements across solution
88+
// - #11: Solution folder support
89+
// - #12: Progress indication
90+
// - #13: Error handling and rollback
17391
}
17492
}
17593
}

0 commit comments

Comments
 (0)