Skip to content

Commit f8dbd0d

Browse files
tomaszgolebiowskiTomasz Gołębiowski
andauthored
Add history to Edit Code window (#380)
This PR adds support for historical entries in the Edit Code window. Entries are stored in the `./.vs/Cody.EditCode.History.json` file. A maximum of 40 recent prompts are retained. You can navigate between prompts using the `Alt+Up/Down` keyboard shortcuts. ## Test plan 1. Open the solution in VS 2. From the editor's pop-up menu, select Cody -> Edit Code 3. Enter the prompt and confirm it 4. Reopen the Edit Code window 5. Use the `Alt+Up` keyboard shortcut to replay the last used prompt <!-- REQUIRED; info at https://docs-legacy.sourcegraph.com/dev/background-information/testing_principles --> --------- Co-authored-by: Tomasz Gołębiowski <tgolebiowski@virtuslab.com>
1 parent 6569aea commit f8dbd0d

File tree

8 files changed

+207
-26
lines changed

8 files changed

+207
-26
lines changed

src/Cody.Core/Cody.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
<Compile Include="Infrastructure\ISecretStorageService.cs" />
131131
<Compile Include="Infrastructure\IProgressService.cs" />
132132
<Compile Include="Infrastructure\IToastNotificationService.cs" />
133+
<Compile Include="Infrastructure\IVsFolderStoreService.cs" />
133134
<Compile Include="Logging\ISentryLog.cs" />
134135
<Compile Include="Logging\ITestLogger.cs" />
135136
<Compile Include="Logging\SentryLog.cs" />
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Cody.Core.Infrastructure
8+
{
9+
public interface IVsFolderStoreService
10+
{
11+
bool SaveData(string fileName, object data);
12+
T LoadData<T>(string fileName) where T : class;
13+
}
14+
}

src/Cody.UI/ViewModels/EditCodeViewModel.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ namespace Cody.UI.ViewModels
1111
{
1212
public class EditCodeViewModel : NotifyPropertyChangedBase
1313
{
14-
public EditCodeViewModel(IEnumerable<Model> models, string selectedModelId, string instruction)
14+
private List<string> instructionsHistory;
15+
private int currentHistoryItem;
16+
17+
public EditCodeViewModel(IEnumerable<Model> models, string selectedModelId, string instruction, List<string> instructionsHistory)
1518
{
1619
var collection = new ObservableCollection<Model>(models);
1720
var modelsSource = new CollectionViewSource();
@@ -21,6 +24,9 @@ public EditCodeViewModel(IEnumerable<Model> models, string selectedModelId, stri
2124
Models = modelsSource;
2225

2326
SelectedModel = collection.FirstOrDefault(x => x.Id == selectedModelId);
27+
28+
this.instructionsHistory = instructionsHistory;
29+
currentHistoryItem = instructionsHistory.Count;
2430
Instruction = instruction;
2531
}
2632

@@ -41,10 +47,40 @@ public string Instruction
4147
{
4248
SetProperty(ref instruction, value);
4349
OnNotifyPropertyChanged(nameof(EditButtonIsEnabled));
50+
currentHistoryItem = instructionsHistory.Count;
4451
}
4552
}
4653

4754
public bool EditButtonIsEnabled => !string.IsNullOrWhiteSpace(instruction);
55+
56+
private DelegateCommand historyUpCommand;
57+
public DelegateCommand HistoryUpCommand => historyUpCommand = historyUpCommand ?? new DelegateCommand(OnHistoryUp);
58+
59+
private DelegateCommand historyDownCommand;
60+
public DelegateCommand HistoryDownCommand => historyDownCommand = historyDownCommand ?? new DelegateCommand(OnHistoryDown);
61+
62+
private void OnHistoryUp()
63+
{
64+
if (currentHistoryItem - 1 >= 0)
65+
{
66+
currentHistoryItem--;
67+
SetProperty(ref instruction, instructionsHistory[currentHistoryItem], nameof(Instruction));
68+
OnNotifyPropertyChanged(nameof(EditButtonIsEnabled));
69+
}
70+
}
71+
72+
private void OnHistoryDown()
73+
{
74+
if (currentHistoryItem + 1 <= instructionsHistory.Count)
75+
{
76+
currentHistoryItem++;
77+
var inst = string.Empty;
78+
if (currentHistoryItem != instructionsHistory.Count) inst = instructionsHistory[currentHistoryItem];
79+
80+
SetProperty(ref instruction, inst, nameof(Instruction));
81+
OnNotifyPropertyChanged(nameof(EditButtonIsEnabled));
82+
}
83+
}
4884
}
4985

5086
public class Model

src/Cody.UI/Views/EditCodeView.xaml

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<platformui:DialogWindow
22
xmlns:platformui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"
3-
xmlns:theme="clr-namespace:Community.VisualStudio.Toolkit"
3+
xmlns:theme="clr-namespace:Community.VisualStudio.Toolkit"
44
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
55
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
66
x:Class="Cody.UI.Views.EditCodeView"
@@ -14,6 +14,10 @@
1414
ShowInTaskbar="False"
1515
theme:Themes.UseVsTheme="True"
1616
FocusManager.FocusedElement="{Binding ElementName=InputTextBox}">
17+
<Window.InputBindings>
18+
<KeyBinding Command="{Binding HistoryUpCommand}" Gesture="ALT+UP" />
19+
<KeyBinding Command="{Binding HistoryDownCommand}" Gesture="ALT+DOWN" />
20+
</Window.InputBindings>
1721
<Grid>
1822
<Grid.RowDefinitions>
1923
<RowDefinition Height="*"/>
@@ -35,7 +39,7 @@
3539
/>
3640
<TextBox Grid.Row="1"
3741
x:Name="InputTextBox"
38-
Text="{Binding Instruction, UpdateSourceTrigger=PropertyChanged}"
42+
Text="{Binding Instruction, UpdateSourceTrigger=PropertyChanged}"
3943
TextWrapping="Wrap"
4044
VerticalScrollBarVisibility="Auto"
4145
Margin="0,0,0,12"/>
@@ -59,25 +63,37 @@
5963
</ComboBox>
6064
</Grid>
6165
<!-- Button Area -->
62-
<StackPanel Grid.Row="1"
63-
Orientation="Horizontal"
64-
HorizontalAlignment="Right"
65-
Margin="12">
66-
<Button x:Name="EditButton"
67-
IsDefault="True"
68-
Content="Edit Code"
69-
Width="75"
70-
Height="23"
71-
Margin="0,0,6,0"
72-
IsEnabled="{Binding EditButtonIsEnabled}"
73-
Click="OnEditCodeClick"
74-
/>
75-
<Button x:Name="CancelButton"
76-
Content="Cancel"
77-
IsCancel="True"
78-
Width="75"
79-
Height="23"
80-
/>
81-
</StackPanel>
66+
<Grid Grid.Row="1" Margin="12">
67+
<Grid.ColumnDefinitions>
68+
<ColumnDefinition Width="*"/>
69+
<ColumnDefinition Width="Auto"/>
70+
<ColumnDefinition Width="Auto"/>
71+
</Grid.ColumnDefinitions>
72+
73+
<TextBlock Grid.Column="0"
74+
Text="Alt+🡡🡣 for history"
75+
Foreground="Gray"
76+
VerticalAlignment="Center">
77+
</TextBlock>
78+
79+
<Button Grid.Column="1"
80+
x:Name="EditButton"
81+
IsDefault="True"
82+
Content="Edit Code"
83+
Width="75"
84+
Height="23"
85+
Margin="0,0,6,0"
86+
IsEnabled="{Binding EditButtonIsEnabled}"
87+
Click="OnEditCodeClick"
88+
/>
89+
90+
<Button Grid.Column="2"
91+
x:Name="CancelButton"
92+
Content="Cancel"
93+
IsCancel="True"
94+
Width="75"
95+
Height="23"
96+
/>
97+
</Grid>
8298
</Grid>
8399
</platformui:DialogWindow>

src/Cody.VisualStudio/Cody.VisualStudio.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
<Compile Include="Services\StatusbarService.cs" />
8181
<Compile Include="Services\ToastNotificationService.cs" />
8282
<Compile Include="Services\UserSettingsProvider.cs" />
83+
<Compile Include="Services\VsFolderStoreService.cs" />
8384
<Compile Include="Services\VsVersionService.cs" />
8485
<Compile Include="Utilities\FilePathHelper.cs" />
8586
<Compile Include="Utilities\UIHelper.cs" />

src/Cody.VisualStudio/CodyPackage.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public sealed partial class CodyPackage : AsyncPackage
7171
public IFileDialogService FileDialogService;
7272
public IEditCodeService EditCodeService;
7373
public IToastNotificationService ToastNotificationService;
74+
public IVsFolderStoreService VsFolderStoreService;
7475

7576
private IInfobarNotifications InfobarNotifications;
7677

@@ -193,10 +194,11 @@ private void InitializeServices(LoggerFactory loggerFactory)
193194

194195
var runningDocumentTable = this.GetService<SVsRunningDocumentTable, IVsRunningDocumentTable>();
195196

196-
197197
VsUIShell = this.GetService<SVsUIShell, IVsUIShell>();
198198
FileDialogService = new FileDialogService(SolutionService, Logger);
199-
EditCodeService = new EditCodeService();
199+
VsFolderStoreService = new VsFolderStoreService(vsSolution, Logger);
200+
EditCodeService = new EditCodeService(VsFolderStoreService);
201+
200202

201203
ProgressNotificationHandlers = new ProgressNotificationHandlers(ProgressService);
202204
TextDocumentNotificationHandlers = new TextDocumentNotificationHandlers(DocumentService, FileDialogService, StatusbarService, Logger);

src/Cody.VisualStudio/Services/EditCodeService.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,41 @@ namespace Cody.VisualStudio.Services
1212
{
1313
public class EditCodeService : IEditCodeService
1414
{
15+
private readonly IVsFolderStoreService vsFolderStoreService;
16+
private List<string> instructionsHistory;
17+
18+
private const string InstructionsHistoryFile = "Cody.EditCode.History.json";
19+
private const int MaxHistoryItems = 40;
20+
21+
public EditCodeService(IVsFolderStoreService vsFolderStoreService)
22+
{
23+
this.vsFolderStoreService = vsFolderStoreService;
24+
}
25+
1526
public EditCodeResult ShowEditCodeDialog(IEnumerable<EditModel> models, string defaultModelId, string instruction)
1627
{
28+
if (instructionsHistory == null)
29+
{
30+
var historyFromFile = vsFolderStoreService.LoadData<List<string>>(InstructionsHistoryFile);
31+
instructionsHistory = historyFromFile ?? new List<string>();
32+
}
33+
1734
var result = ThreadHelper.JoinableTaskFactory.Run(async delegate
1835
{
1936
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
2037

2138
var vm = new EditCodeViewModel(models.Select(x => new Model { Id = x.Id, Name = x.Name, Provider = x.Provider }),
22-
defaultModelId, instruction);
39+
defaultModelId, instruction, instructionsHistory);
2340
var window = new EditCodeView();
2441

2542
window.DataContext = vm;
2643
if (window.ShowModal() == true)
2744
{
45+
if (!string.IsNullOrWhiteSpace(vm.Instruction) && instructionsHistory.LastOrDefault() != vm.Instruction)
46+
{
47+
SaveInstructionInHistory(vm.Instruction);
48+
}
49+
2850
return new EditCodeResult { Instruction = vm.Instruction, ModelId = vm.SelectedModel.Id };
2951
}
3052

@@ -33,6 +55,15 @@ public EditCodeResult ShowEditCodeDialog(IEnumerable<EditModel> models, string d
3355

3456
return result;
3557
}
58+
59+
private void SaveInstructionInHistory(string instruction)
60+
{
61+
instructionsHistory.Add(instruction);
62+
var itemsToRemove = instructionsHistory.Count - MaxHistoryItems;
63+
if (itemsToRemove > 0) instructionsHistory.RemoveRange(0, itemsToRemove);
64+
65+
vsFolderStoreService.SaveData(InstructionsHistoryFile, instructionsHistory);
66+
}
3667
}
3768

3869

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using Cody.Core.Infrastructure;
2+
using Cody.Core.Logging;
3+
using Microsoft.VisualStudio.Shell.Interop;
4+
using Newtonsoft.Json;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
12+
namespace Cody.VisualStudio.Services
13+
{
14+
public class VsFolderStoreService : IVsFolderStoreService
15+
{
16+
private readonly IVsSolution vsSolution;
17+
private readonly ILog logger;
18+
19+
public VsFolderStoreService(IVsSolution vsSolution, ILog logger)
20+
{
21+
this.vsSolution = vsSolution;
22+
this.logger = logger;
23+
}
24+
25+
private string GetVsFolder()
26+
{
27+
vsSolution.GetProperty((int)__VSPROPID.VSPROPID_SolutionDirectory, out object value);
28+
29+
if (value == null) return null;
30+
31+
return Path.Combine((string)value, ".vs");
32+
}
33+
34+
public bool SaveData(string fileName, object data)
35+
{
36+
try
37+
{
38+
var path = GetVsFolder();
39+
if (path == null) return false;
40+
var json = JsonConvert.SerializeObject(data);
41+
var filePath = Path.Combine(path, fileName);
42+
File.WriteAllText(filePath, json);
43+
44+
return true;
45+
}
46+
catch (Exception ex)
47+
{
48+
logger.Error("Can't save data in .vs folder", ex);
49+
}
50+
51+
return false;
52+
53+
}
54+
55+
public T LoadData<T>(string fileName) where T : class
56+
{
57+
try
58+
{
59+
var path = GetVsFolder();
60+
if (path == null) return null;
61+
var filePath = Path.Combine(path, fileName);
62+
if (File.Exists(filePath))
63+
{
64+
var json = File.ReadAllText(filePath);
65+
var obj = JsonConvert.DeserializeObject<T>(json);
66+
67+
return obj;
68+
}
69+
70+
logger.Info($"File '{filePath}' does not exist");
71+
}
72+
catch (Exception ex)
73+
{
74+
logger.Error("Can't load data from .vs folder", ex);
75+
}
76+
77+
return null;
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)