-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRenameStepCommand.cs
More file actions
189 lines (159 loc) · 7.85 KB
/
Copy pathRenameStepCommand.cs
File metadata and controls
189 lines (159 loc) · 7.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.Commands;
using Microsoft.VisualStudio.Extensibility.Editor;
using Microsoft.VisualStudio.Shell;
using Newtonsoft.Json.Linq;
using Reqnroll.IdeSupport.Common.Diagnostics;
using Reqnroll.IdeSupport.VisualStudio.Extension.Navigation;
namespace Reqnroll.IdeSupport.VisualStudio.Extension.RenameStep;
[VisualStudioContribution]
internal sealed class RenameStepCommand : Command
{
private readonly RenameStepState _state;
private readonly TraceSource _traceSource;
private readonly IDeveroomLogger _fileLogger = new SynchronousFileLogger();
private static readonly Guid GuidSHLMainMenu = new("{D309F791-903F-11D0-9EFC-00A0C911004F}");
private const int IDG_VS_CODEWIN_NAVIGATETOLOCATION = 0x02B1;
public RenameStepCommand(RenameStepState state, TraceSource traceSource)
{
_state = state;
_traceSource = traceSource;
}
public override CommandConfiguration CommandConfiguration => new("Rename Step")
{
Icon = new CommandIconConfiguration(ImageMoniker.Custom("ReqnrollIcon"), IconSettings.IconAndText),
VisibleWhen = ActivationConstraint.Or(
ActivationConstraint.EditorContentType("CSharp"),
ActivationConstraint.EditorContentType("reqnroll-gherkin")),
Placements =
[
CommandPlacement.VsctParent(GuidSHLMainMenu, id: IDG_VS_CODEWIN_NAVIGATETOLOCATION, priority: 0x0100),
],
};
public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
{
try
{
_fileLogger.LogInfo("RenameStepCommand: invoked.");
var service = _state.Service;
if (service is null)
{
_fileLogger.LogWarning("RenameStepCommand: LSP server not yet initialized.");
VsUtils.ShowStatusBarMessage("Reqnroll: LSP server not yet initialized.");
return;
}
var textView = await context.GetActiveTextViewAsync(cancellationToken).ConfigureAwait(false);
if (textView is null)
{
_fileLogger.LogWarning("RenameStepCommand: No active text view in client context.");
return;
}
var fileUri = textView.Uri.ToString();
var caretPos = textView.Selection.ActivePosition;
var line = caretPos.GetContainingLine();
var lineNum = line.LineNumber;
var charNum = caretPos.Offset - line.Text.Start;
_fileLogger.LogInfo($"RenameStepCommand: active view uri='{fileUri}', caret line={lineNum} char={charNum}.");
// Step 1: Get rename targets from the server
var targets = await service.GetRenameTargetsAsync(fileUri, lineNum, charNum, cancellationToken)
.ConfigureAwait(false);
if (targets is null || targets.Targets.Count == 0)
{
if (targets?.IsAmbiguous == true)
{
_fileLogger.LogInfo("RenameStepCommand: step at cursor position is ambiguously bound.");
VsUtils.ShowStatusBarMessage("Reqnroll: Rename is not supported for an ambiguously bound step.");
return;
}
_fileLogger.LogInfo("RenameStepCommand: no renameable targets at cursor position.");
VsUtils.ShowStatusBarMessage("Reqnroll: No step definition found to rename at this position.");
return;
}
// Step 2: Select target (picker if multiple)
int selectedAttributeIndex;
string currentLabel;
string currentExpression;
if (targets.Targets.Count == 1)
{
var item = targets.Targets[0];
selectedAttributeIndex = item.AttributeIndex;
currentLabel = item.Label;
currentExpression = item.Expression;
_fileLogger.LogInfo(
$"RenameStepCommand: single target, attributeIndex={selectedAttributeIndex}, label='{currentLabel}'.");
}
else
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var pickerTargets = targets.Targets
.Select(t => new NavigationTarget(t.Label, textView.Uri.LocalPath, 0, 0))
.ToList();
var dialog = new NavigationPickerDialog("Choose step definition to rename", pickerTargets);
if (dialog.ShowModal() != true || dialog.SelectedIndex < 0)
{
_fileLogger.LogInfo("RenameStepCommand: picker dismissed.");
return;
}
var chosen = targets.Targets[dialog.SelectedIndex];
selectedAttributeIndex = chosen.AttributeIndex;
currentLabel = chosen.Label;
currentExpression = chosen.Expression;
_fileLogger.LogInfo(
$"RenameStepCommand: user selected target index={dialog.SelectedIndex}, attributeIndex={selectedAttributeIndex}.");
}
// Step 3: Tell the server which attribute was selected
await service.SelectRenameTargetAsync(fileUri, version: 0, selectedAttributeIndex, cancellationToken)
.ConfigureAwait(false);
// Step 4: Prompt user for new step text
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var currentStepText = !string.IsNullOrEmpty(currentExpression)
? currentExpression
: ExtractStepTextFromLabel(currentLabel);
var newStepText = Microsoft.VisualBasic.Interaction.InputBox(
"Enter the new step text:", "Rename Step", currentStepText);
if (string.IsNullOrEmpty(newStepText))
{
_fileLogger.LogInfo("RenameStepCommand: user cancelled rename dialog.");
return;
}
_fileLogger.LogInfo($"RenameStepCommand: user entered new text '{newStepText}'.");
// Step 5: Send textDocument/rename via the service
var result = await service.SendRenameRequestAsync(
fileUri, lineNum, charNum, newStepText, cancellationToken)
.ConfigureAwait(false);
if (result is null)
{
_traceSource.TraceInformation("RenameStepCommand: server returned null from rename.");
VsUtils.ShowStatusBarMessage("Reqnroll: Rename failed.");
return;
}
_fileLogger.LogInfo($"RenameStepCommand: rename result = {result}");
// Step 6: Apply the WorkspaceEdit (buffer-first for open documents)
if (result is not null)
{
var applier = new WorkspaceEditApplier(service, _fileLogger, _traceSource);
await applier.ApplyAsync(result, cancellationToken).ConfigureAwait(false);
}
_fileLogger.LogInfo("RenameStepCommand: rename completed successfully.");
VsUtils.ShowStatusBarMessage("Reqnroll: Step renamed successfully.");
}
catch (Exception ex)
{
_fileLogger.LogWarning($"RenameStepCommand: failed: {ex}");
_traceSource.TraceEvent(TraceEventType.Error, 0, "RenameStepCommand: failed: {0}", ex);
}
}
private static string ExtractStepTextFromLabel(string label)
{
var space = label.IndexOf(' ');
var prefix = space >= 0 ? label.Substring(0, space + 1) : "";
return label.Length > prefix.Length ? label.Substring(prefix.Length) : label;
}
}