Skip to content

Commit

Permalink
Added multilanguage capabilities; generic refactoring and menu revisi…
Browse files Browse the repository at this point in the history
…on (#7)

* Added multilanguage support (WIP)
* Minor refactoring
* Updated graphics and versions
* Added pipeline badge on README; removed CQ step
  • Loading branch information
EmilianoMusso authored Jan 3, 2025
1 parent 159d413 commit 038240b
Show file tree
Hide file tree
Showing 23 changed files with 340 additions and 169 deletions.
48 changes: 48 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
on: [push]

jobs:
build:
runs-on: windows-latest
permissions:
actions: read
contents: read
security-events: write

steps:
- uses: actions/checkout@v4
name: Checkout code
with:
fetch-depth: 0

- name: Install GitVersion
uses: gittools/actions/gitversion/[email protected]
with:
versionSpec: '6.0.x'

- name: Determine version
uses: gittools/actions/gitversion/[email protected]

- run: |
echo "Full SemVer (env.fullSemVer) : ${{ env.fullSemVer }}"
name: Display GitVersion variables
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v2

- name: Setup NuGet
uses: nuget/setup-nuget@v2

- name: Restore NuGet packages
run: nuget restore EntwineLLM.sln

- name: Build EntwineLLM
run: msbuild EntwineLLM.sln /p:Configuration=Release /p:Version=${{ env.fullSemVer }}

- name: Publish artifacts
uses: actions/upload-artifact@v4
with:
name: entwinellm-${{ env.fullSemVer }}
retention-days: 5
path: |
**\bin\Release
**\obj\Release
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### v1.9.0 Generic refactoring
---
- Main menu moved to Extensions menu; command are now grouped
- Added multiple programming languages support
- Minor refactorings

### v1.8.0 Added specific models for each operation
---
- User can now configure a specific LLM to serve each operation
Expand Down
9 changes: 9 additions & 0 deletions EntwineLLM.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{86A9F96A-1105-4008-B97A-DB92ED1513EA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{F7210049-33DC-4458-A58D-D743BE1862FF}"
ProjectSection(SolutionItems) = preProject
.github\workflows\build.yml = .github\workflows\build.yml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -43,6 +50,8 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A1CE0860-7C00-473B-A75F-693E9A8F0CE3} = {5E4C7AB4-BFE4-45E5-BD0F-3050A7811B10}
{86A9F96A-1105-4008-B97A-DB92ED1513EA} = {CD17B50E-3006-4A0F-A203-FC6437B066AF}
{F7210049-33DC-4458-A58D-D743BE1862FF} = {86A9F96A-1105-4008-B97A-DB92ED1513EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D7998D2C-AA4F-4551-91F0-9F8F27BD89B0}
Expand Down
29 changes: 16 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
## EntwineLLM
### LLM coding assistant extension for Visual Studio
[![.github/workflows/build.yml](https://github.com/EmilianoMusso/EntwineLLM/actions/workflows/build.yml/badge.svg)](https://github.com/EmilianoMusso/EntwineLLM/actions/workflows/build.yml)
<div style="text-align: center;">
<img src="./src/EntwineLLM/Resources/entwine-template-title.png" width="400"/>
</div>

<img src="./src/EntwineLLM/Resources/entwine-logo.jpg" width="80" height="80" style="float:left;margin-right:10px"/>
EntwineLLM is a free Visual Studio extension designed to leverage LLM capabilities to assist developers in writing code, without relying on third-party APIs. Instead, it uses an open LLM implementation installed locally on the user's PC, such as Ollama

#### Prerequisites
Expand All @@ -13,18 +14,22 @@ To use the EntwineLLM extension, you need to have a local or Docker-hosted open
* [Ollama models](https://ollama.com/search)

#### Setup
After installing the EntwineLLM extension, its configuration options will be available in the Visual Studio Options menu. These options allow users to specify the base URL of the locally installed LLM, select the LLM model to use for each specific extension command (install them first), and configure the HTTP request timeout settings for communication with the LLM. These settings provide flexibility in customizing the behavior of the extension to match the users environment and preferences.
After installing the EntwineLLM extension, its configuration options will be available in the Visual Studio Options menu. These options allow users to specify the base URL of the locally installed LLM, select the LLM model to use for each specific extension command (install them first), and configure the HTTP request timeout settings for communication with the LLM. These settings provide flexibility in customizing the behavior of the extension to match the user's environment and preferences.

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

#### Using the extension
After installing the extension, the following commands will be available in the Tools menu:
After installing the extension, the following commands will be available in the `EntwineLLM` menu, placed in `Extensions` menu:

* `Ask Entwine`: Select a block of code or text related to the function you wish to create, and 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.
* `Generate Unit Tests with Entwine`: Select a block of code or function, and this command will generate unit tests covering all paths for the selection. The extension queries the LLM and displays the results in a separate window. If accepted, the generated tests can be applied directly to the codebase.
* `Follow-up`: After generating code using EntwineLLM, users can utilize the Follow-up field to submit additional prompts that build upon the original code generation request. This feature allows for iterative refinement of the generated code, enabling users to clarify requirements, request modifications, or explore alternative implementations seamlessly within the same workflow. The follow-up prompt is sent to the LLM, which produces updated results tailored to the additional input provided. This functionality enhances the extension's flexibility and precision, ensuring that developers can achieve exactly the results they need.
* `Document code with EntwineLlm`: Generate a markdown file with exhaustive documentation about selected code. The file can be saved as Markdown or exported as HTML.
* `Code review with EntwineLlm`: The LLM will act as an experienced developer, and will spot issues, performance problems, and give a full review on selected code, with hints and comments about the strategies that can be applied to fix it.
> Note: EntwineLLM Menu has been moved to Extensions menu since v1.9.0. Previous versions will show the following commands under Tools menu
![image](./src/EntwineLLM/Resources/vs-entwine-menu.png)

* <b>Refactor code</b>: Select a block of code or text related to the function you wish to create, and 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.
* <b>Generate unit tests</b>: Select a block of code or function, and this command will generate unit tests covering all paths for the selection. The extension queries the LLM and displays the results in a separate window. If accepted, the generated tests can be applied directly to the codebase.
* <b>Follow-up</b>: After generating code using EntwineLLM, users can utilize the Follow-up field to submit additional prompts that build upon the original code generation request. This feature allows for iterative refinement of the generated code, enabling users to clarify requirements, request modifications, or explore alternative implementations seamlessly within the same workflow. The follow-up prompt is sent to the LLM, which produces updated results tailored to the additional input provided. This functionality enhances the extension's flexibility and precision, ensuring that developers can achieve exactly the results they need.
* <b>Document code</b>: Generate a markdown file with exhaustive documentation about selected code. The file can be saved as Markdown or exported as HTML.
* <b>Code review</b>: The LLM will act as an experienced developer, and will spot issues, performance problems, and give a full review on selected code, with hints and comments about the strategies that can be applied to fix it.

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

Expand All @@ -33,9 +38,7 @@ All the available functions can overwrite the originally selected code with the
![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. 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)
The prompts for this extension are designed to work with multiple programming languages, such as C#, Python, Java. Prompts have a strict set of rules: requests unrelated to coding are rejected. 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 format, following strict style guidelines (Allman-style braces, vertical slicing) with no comments or additional context.

#### 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.
11 changes: 9 additions & 2 deletions src/EntwineLLM/Commands/BaseCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public string GetCurrentMethodCode()
{
ThreadHelper.ThrowIfNotOnUIThread();

if (!(Package.GetGlobalService(typeof(EnvDTE.DTE)) is EnvDTE.DTE dte))
if (Package.GetGlobalService(typeof(EnvDTE.DTE)) is not EnvDTE.DTE dte)
{
return string.Empty;
}
Expand All @@ -36,7 +36,7 @@ public string GetCurrentMethodCode()
return string.Empty;
}

if (!(activeDocument.Selection is EnvDTE.TextSelection textSelection))
if (activeDocument.Selection is not EnvDTE.TextSelection textSelection)
{
return string.Empty;
}
Expand All @@ -56,6 +56,13 @@ public async Task PerformRefactoringSuggestionAsync(CodeType codeType, string ma
SuggestedCodeEditor.Text
: GetCurrentMethodCode();

if (string.IsNullOrEmpty(methodCode))
{
progressBarHelper.StopDialog();
WindowHelper.MsgBox("It is necessary to select the source code to be processed from the editor");
return;
}

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

Expand Down
1 change: 0 additions & 1 deletion src/EntwineLLM/EntwineLLM.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@
<Content Include="Resources\vs-entwine-menu.png" />
<Content Include="Resources\vs-entwine-options.png" />
<Content Include="Resources\vs-entwine-suggestion.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
</ItemGroup>
Expand Down
73 changes: 51 additions & 22 deletions src/EntwineLLM/EntwineLLMPackage.vsct
Original file line number Diff line number Diff line change
@@ -1,61 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">

<Extern href="stdidcmd.h" />
<Extern href="vsshlids.h" />

<Commands package="guidEntwineLlmPackage">

<Groups>
<Group guid="guidEntwineLlmPackageCmdSet1" id="MainMenuGroup" />
</Groups>

<Menus>
<Menu guid="guidEntwineLlmPackageCmdSet1" id="MainEntwineMenu" type="Menu">
<Strings>
<ButtonText>EntwineLLM</ButtonText>
</Strings>
</Menu>
</Menus>

<Buttons>
<Button guid="guidEntwineLlmPackageCmdSet1" id="cmdidRequestRefactor" priority="0x0100" type="Button">
<Parent guid="guidEntwineLlmPackageCmdSet1" id="MyMenuGroup" />
<Button guid="guidEntwineLlmPackageCmdSet1" id="cmdidRequestRefactor" type="Button">
<Strings>
<ButtonText>Ask EntwineLlm</ButtonText>
<ButtonText>Refactor code</ButtonText>
</Strings>
</Button>
<Button guid="guidEntwineLlmPackageCmdSet1" id="cmdGenerateUnitTests" priority="0x0100" type="Button">
<Parent guid="guidEntwineLlmPackageCmdSet1" id="MyMenuGroup" />
<Button guid="guidEntwineLlmPackageCmdSet1" id="cmdGenerateUnitTests" type="Button">
<Strings>
<ButtonText>Generate Unit Tests with EntwineLlm</ButtonText>
<ButtonText>Generate unit tests</ButtonText>
</Strings>
</Button>
<Button guid="guidEntwineLlmPackageCmdSet1" id="cmdDocumentCode" priority="0x0100" type="Button">
<Parent guid="guidEntwineLlmPackageCmdSet1" id="MyMenuGroup" />
<Button guid="guidEntwineLlmPackageCmdSet1" id="cmdDocumentCode" type="Button">
<Strings>
<ButtonText>Document code with EntwineLlm</ButtonText>
<ButtonText>Document code</ButtonText>
</Strings>
</Button>
<Button guid="guidEntwineLlmPackageCmdSet1" id="cmdCodeReview" priority="0x0100" type="Button">
<Parent guid="guidEntwineLlmPackageCmdSet1" id="MyMenuGroup" />
<Button guid="guidEntwineLlmPackageCmdSet1" id="cmdCodeReview" type="Button">
<Strings>
<ButtonText>Code review with EntwineLlm</ButtonText>
<ButtonText>Code review</ButtonText>
</Strings>
</Button>
</Buttons>

<Groups>
<Group guid="guidEntwineLlmPackageCmdSet1" id="MyMenuGroup" priority="0x0600">
<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS" />
</Group>
</Groups>
</Commands>

<CommandPlacements>
<CommandPlacement guid="guidEntwineLlmPackageCmdSet1" id="MainEntwineMenu" priority="900">
<Parent guid="guidSHLMainMenu" id="IDG_VS_MM_TOOLSADDINS" />
</CommandPlacement>

<CommandPlacement guid="guidEntwineLlmPackageCmdSet1" id="MainMenuGroup" priority="0x0100">
<Parent guid="guidEntwineLlmPackageCmdSet1" id="MainEntwineMenu" />
</CommandPlacement>

<CommandPlacement guid="guidEntwineLlmPackageCmdSet1" id="cmdidRequestRefactor" priority="0x0101">
<Parent guid="guidEntwineLlmPackageCmdSet1" id="MainMenuGroup" />
</CommandPlacement>
<CommandPlacement guid="guidEntwineLlmPackageCmdSet1" id="cmdGenerateUnitTests" priority="0x0102">
<Parent guid="guidEntwineLlmPackageCmdSet1" id="MainMenuGroup" />
</CommandPlacement>
<CommandPlacement guid="guidEntwineLlmPackageCmdSet1" id="cmdDocumentCode" priority="0x0103">
<Parent guid="guidEntwineLlmPackageCmdSet1" id="MainMenuGroup" />
</CommandPlacement>
<CommandPlacement guid="guidEntwineLlmPackageCmdSet1" id="cmdCodeReview" priority="0x0104">
<Parent guid="guidEntwineLlmPackageCmdSet1" id="MainMenuGroup" />
</CommandPlacement>
</CommandPlacements>

<KeyBindings>
<KeyBinding guid="guidEntwineLlmPackageCmdSet1" id="cmdidRequestRefactor" editor="guidTextEditor" key1="VK_F1" mod1="SHIFT" />
<KeyBinding guid="guidEntwineLlmPackageCmdSet1" id="cmdGenerateUnitTests" editor="guidTextEditor" key1="VK_F2" mod1="SHIFT" />
<KeyBinding guid="guidEntwineLlmPackageCmdSet1" id="cmdDocumentCode" editor="guidTextEditor" key1="VK_F3" mod1="SHIFT" />
<KeyBinding guid="guidEntwineLlmPackageCmdSet1" id="cmdCodeReview" editor="guidTextEditor" key1="VK_F4" mod1="SHIFT" />
</KeyBindings>

<Symbols>
<GuidSymbol name="guidTextEditor" value="{8B382828-6202-11D1-8870-0000F87579D2}" />
<GuidSymbol name="guidSHLMainMenu" value="{D309F791-903F-11D0-9EFC-00A0C911004F}" />
<GuidSymbol name="guidEntwineLlmPackage" value="{3c995b0e-1f37-4cef-9ac7-9771b3fb6162}" />
<GuidSymbol name="guidTextEditor" value="{8B382828-6202-11D1-8870-0000F87579D2}" />

<GuidSymbol value="{714b6862-aad7-434e-8415-dd928555ba0e}" name="guidEntwineLlmPackageCmdSet1">
<IDSymbol value="4128" name="MyMenuGroup" />
<IDSymbol value="0x1020" name="MainEntwineMenu" />
<IDSymbol value="0x1021" name="MainMenuGroup" />
<IDSymbol value="250" name="cmdidRequestRefactor" />
<IDSymbol value="251" name="cmdGenerateUnitTests" />
<IDSymbol value="252" name="cmdDocumentCode" />
<IDSymbol value="253" name="cmdCodeReview" />
</GuidSymbol>

</Symbols>

</CommandTable>
11 changes: 7 additions & 4 deletions src/EntwineLLM/Helpers/RefactoringHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,12 @@ private async Task<CodeSuggestionResponse> GetCodeSuggestionsAsync(string method
response.EnsureSuccessStatusCode();

var responseContent = await response.Content.ReadAsStringAsync();
var code = JObject.Parse(responseContent)["message"]["content"].ToString();
var code = JObject
.Parse(responseContent)["message"]["content"]
.ToString()
.Replace("\r\n", Environment.NewLine);

const string pattern = "```csharp(.*?)```";
const string pattern = @"```(?:([a-zA-Z0-9+#]*)\n)?(.*?)```";
var matches = Regex.Matches(code, pattern, RegexOptions.Singleline);

if (MustReturnFullResponse(matches, codeType))
Expand All @@ -81,7 +84,7 @@ private async Task<CodeSuggestionResponse> GetCodeSuggestionsAsync(string method
var extractedCode = new StringBuilder();
foreach (Match match in matches)
{
extractedCode.AppendLine(match.Groups[1].Value.Trim());
extractedCode.AppendLine(match.Groups[2].Value.Trim());
}

return CodeSuggestionResponse.Success(codeType, extractedCode.ToString());
Expand All @@ -92,7 +95,7 @@ private async Task<CodeSuggestionResponse> GetCodeSuggestionsAsync(string method
}
}

private bool MustReturnFullResponse(MatchCollection matches, CodeType codeType)
private static bool MustReturnFullResponse(MatchCollection matches, CodeType codeType)
{
return matches.Count == 0 || codeType == CodeType.Documentation || codeType == CodeType.Review;
}
Expand Down
Loading

0 comments on commit 038240b

Please sign in to comment.