Skip to content

Commit

Permalink
General refactor and unit tests generation feature (#1)
Browse files Browse the repository at this point in the history
* chore: First tests generation implementation (wip)
* chore: refactoring
* chore: refactored tests prompt, increased version
* chore: progress message revision
* Added license file and set manifest variables
* Added changelog; updated manifest
  • Loading branch information
EmilianoMusso authored Nov 26, 2024
1 parent 02d319f commit e5e3589
Show file tree
Hide file tree
Showing 24 changed files with 410 additions and 151 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### v1.5.0 Unit tests generation
---
- General refactor; implemented unit tests generation (fc8af9b)

### v1.0.0 First release
---
- First release; code writing and refactor functions enabled (02d319f)
2 changes: 2 additions & 0 deletions EntwineLLM.sln
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution items", "{CD17B50E-3006-4A0F-A203-FC6437B066AF}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
CHANGELOG.md = CHANGELOG.md
LICENSE.txt = LICENSE.txt
README.md = README.md
EndProjectSection
EndProject
Expand Down
21 changes: 21 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Emiliano Musso

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ After installing the EntwineLLM extension, its configuration options will be ava
![image](./src/EntwineLLM/Resources/vs-entwine-options.png)

#### Using the extension
After installing the extension, the `Ask Entwine` command will be available in the Tools menu. By selecting a block of code or text related to the function you wish to create, the extension will call the LLM APIs available at the URL configured in the options. The extension will then present a window displaying the suggested code. If the user chooses to apply the suggestion, the new code will overwrite the originally selected text or code, seamlessly integrating the AI's assistance into the developer's workflow.
After installing the extension, the `Ask Entwine` command will be available in the Tools menu. By selecting a block of code or text related to the function you wish to create, the extension will call the LLM APIs available at the URL configured in the options. The extension will then present a window displaying the suggested code. If the user chooses to apply the suggestion, the new code will overwrite the originally selected text or code, seamlessly integrating the AI's assistance into the developer's workflow. Instead, the `Generate Unit Tests with EntwineLlm` command can be used to generate unit tests for every path on a selected block of code or function. The same flow applies: the extension will query the LLM, showing results on a separated window.

![image](./src/EntwineLLM/Resources/vs-entwine-suggestion.png)

#### How it works
The prompt for this extension is designed to focus specifically on C# development within the Microsoft .NET ecosystem, including the full framework, .NET Core, and related technologies like LINQ, Entity Framework, and ASP.NET Core. It has a strict set of rules: requests unrelated to C# coding are rejected with a raw `return null;` statement. If the request involves refactoring code, it follows Clean Code principles, ensuring readability, maintainability, and performance, with no extra explanations or comments provided. For new code requests, the same principles apply, with the emphasis on modularity, testability, and high performance. All code is provided in raw C# format, following strict style guidelines (Allman-style braces, vertical slicing) with no comments or additional context

![image](./src/EntwineLLM/Resources/vs-entwine-menu.png)

#### Why Entwine?
The name Entwine and its logo, resembling the helix of DNA, symbolize the union of two forces: the natural intelligence and skills of the developer, and the artificial intelligence provided by the LLM. Just as DNA represents the intricate intertwining of biological elements that form life, Entwine reflects the harmonious connection between human creativity and AI-driven assistance. This synergy allows developers to harness the power of LLMs, enhancing their coding process while retaining their unique problem-solving abilities, creating a seamless collaboration between human and machine.
58 changes: 58 additions & 0 deletions src/EntwineLLM/Commands/BaseCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using EntwineLlm.Enums;
using EntwineLlm.Helpers;
using Microsoft.VisualStudio.Shell;
using System;
using System.Threading.Tasks;

namespace EntwineLlm.Commands
{
internal class BaseCommand
{
public AsyncPackage package;

public string ActiveDocumentPath;

public BaseCommand(AsyncPackage package)
{
this.package = package ?? throw new ArgumentNullException(nameof(package));
}

public string GetCurrentMethodCode()
{
ThreadHelper.ThrowIfNotOnUIThread();

if (!(Package.GetGlobalService(typeof(EnvDTE.DTE)) is EnvDTE.DTE dte))
{
return string.Empty;
}

var activeDocument = dte.ActiveDocument;
if (activeDocument == null)
{
return string.Empty;
}

if (!(activeDocument.Selection is EnvDTE.TextSelection textSelection))
{
return string.Empty;
}

ActiveDocumentPath = dte.ActiveDocument.FullName;
return textSelection.Text;
}

public async Task PerformRefactoringSuggestionAsync(RequestedCodeType codeType)
{
var message = "Waiting for LLM response (task requested: " + Enum.GetName(typeof(RequestedCodeType), codeType) + ") ...";

var progressBarHelper = new ProgressBarHelper(ServiceProvider.GlobalProvider);
progressBarHelper.StartIndeterminateDialog(message);

var methodCode = GetCurrentMethodCode();
var refactoringHelper = new RefactoringHelper(package);
await refactoringHelper.RequestCodeSuggestionsAsync(methodCode, ActiveDocumentPath, codeType);

progressBarHelper.StopDialog();
}
}
}
25 changes: 25 additions & 0 deletions src/EntwineLLM/Commands/GenerateTestsCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using EntwineLlm.Commands;
using EntwineLlm.Commands.Interfaces;
using Microsoft.VisualStudio.Shell;
using System;

namespace EntwineLlm
{
internal sealed class GenerateTestsCommand : BaseCommand, IBaseCommand
{
public int Id
{
get
{
return 251;
}
}

public GenerateTestsCommand(AsyncPackage package) : base(package) { }

public void Execute(object sender, EventArgs e)
{
_ = PerformRefactoringSuggestionAsync(Enums.RequestedCodeType.Test);
}
}
}
10 changes: 10 additions & 0 deletions src/EntwineLLM/Commands/Interfaces/IBaseCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace EntwineLlm.Commands.Interfaces
{
internal interface IBaseCommand
{
int Id { get; }
void Execute(object sender, EventArgs e);
}
}
91 changes: 10 additions & 81 deletions src/EntwineLLM/Commands/RequestRefactorCommand.cs
Original file line number Diff line number Diff line change
@@ -1,96 +1,25 @@
using EntwineLlm.Helpers;
using EntwineLlm.Commands;
using EntwineLlm.Commands.Interfaces;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System;
using System.ComponentModel.Design;
using System.Threading.Tasks;
using Task = System.Threading.Tasks.Task;

namespace EntwineLlm
{
internal sealed class RequestRefactorCommand
internal sealed class RequestRefactorCommand : BaseCommand, IBaseCommand
{
public const int CommandId = 256;

public static readonly Guid CommandSet = new Guid("714b6862-aad7-434e-8415-dd928555ba0e");

private readonly AsyncPackage package;

private static string _activeDocumentPath;

private RequestRefactorCommand(AsyncPackage package, OleMenuCommandService commandService)
{
this.package = package ?? throw new ArgumentNullException(nameof(package));
commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));

var menuCommandID = new CommandID(CommandSet, CommandId);
var menuItem = new MenuCommand(this.Execute, menuCommandID);
commandService.AddCommand(menuItem);
}

public static RequestRefactorCommand Instance { get; private set; }

public static async Task InitializeAsync(AsyncPackage package)
public int Id
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);

OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;
Instance = new RequestRefactorCommand(package, commandService);
}

private void Execute(object sender, EventArgs e)
{
_ = PerformRefactoringSuggestionAsync();
}

private async Task PerformRefactoringSuggestionAsync()
{
var progressBarHelper = new ProgressBarHelper(ServiceProvider.GlobalProvider);
progressBarHelper.StartIndeterminateDialog();

var methodCode = GetCurrentMethodCode();
var refactoringHelper = new RefactoringHelper(package);
await refactoringHelper.RequestAndShowRefactoringSuggestionAsync(methodCode, _activeDocumentPath);

progressBarHelper.StopDialog();
}

private static string GetCurrentMethodCode()
{
ThreadHelper.ThrowIfNotOnUIThread();

if (!(Package.GetGlobalService(typeof(EnvDTE.DTE)) is EnvDTE.DTE dte))
{
return string.Empty;
}

var activeDocument = dte.ActiveDocument;
if (activeDocument == null)
get
{
return string.Empty;
return 250;
}

if (!(activeDocument.Selection is EnvDTE.TextSelection textSelection))
{
return string.Empty;
}

_activeDocumentPath = dte.ActiveDocument.FullName;
return textSelection.Text;
}

public async Task<ToolWindowPane> ShowToolWindowAsync()
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
var window = await package.FindToolWindowAsync(typeof(RefactorSuggestionWindow), 0, true, package.DisposalToken);
if (window == null || window.Frame == null)
{
throw new NotSupportedException("Cannot create window");
}
public RequestRefactorCommand(AsyncPackage package) : base(package) { }

var windowFrame = (IVsWindowFrame)window.Frame;
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
return window;
public void Execute(object sender, EventArgs e)
{
_ = PerformRefactoringSuggestionAsync(Enums.RequestedCodeType.Refactor);
}
}
}
43 changes: 43 additions & 0 deletions src/EntwineLLM/CommandsMenu.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using EntwineLlm.Commands.Interfaces;
using Microsoft.VisualStudio.Shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Threading.Tasks;

namespace EntwineLlm
{
internal class CommandsMenu
{
private static readonly Guid CommandSet = new Guid("714b6862-aad7-434e-8415-dd928555ba0e");
private OleMenuCommandService CommandService;

public OleMenuCommandService Service
{
get
{
return CommandService;
}
}

public async Task InitializeAsync(AsyncPackage package)
{
CommandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;
}

public void AddCommand(IBaseCommand command)
{
var menuCommandID = new CommandID(CommandSet, command.Id);
var menuItem = new MenuCommand(command.Execute, menuCommandID);
CommandService.AddCommand(menuItem);
}

public void AddCommands(IEnumerable<IBaseCommand> commands)
{
foreach (var command in commands)
{
AddCommand(command);
}
}
}
}
27 changes: 22 additions & 5 deletions src/EntwineLLM/EntwineLLM.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,20 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="CommandsMenu.cs" />
<Compile Include="Commands\BaseCommand.cs" />
<Compile Include="Commands\GenerateTestsCommand.cs" />
<Compile Include="Commands\Interfaces\IBaseCommand.cs" />
<Compile Include="Commands\RequestRefactorCommand.cs" />
<Compile Include="EntwineLLMOptions.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Enums\RequestedCodeType.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Helpers\ProgressBarHelper.cs" />
<Compile Include="Helpers\PromptHelper.cs" />
<Compile Include="Helpers\RefactoringHelper.cs" />
<Compile Include="Helpers\WindowHelper.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
Expand All @@ -62,9 +71,6 @@
<Compile Include="Windows\AboutWindow.xaml.cs">
<DependentUpon>AboutWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\EntwineLLMOptions.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="EntwineLLMPackage.cs" />
<Compile Include="Windows\RefactorSuggestionWindow.cs" />
Expand Down Expand Up @@ -125,9 +131,20 @@
<Resource Include="Resources\entwine-logo.jpg" />
</ItemGroup>
<ItemGroup>
<Content Include="entwine-logo.ico" />
<Content Include="entwine-logo.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="Resources\vs-entwine-menu.png" />
<Content Include="Resources\vs-entwine-options.png" />
<Content Include="Resources\vs-entwine-suggestion.png" />
<Content Include="Resources\vs-entwine-suggestion.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
Expand Down
File renamed without changes.
Loading

0 comments on commit e5e3589

Please sign in to comment.