1
1
using System . Collections ;
2
+ using System . Text ;
2
3
using System . Management . Automation ;
4
+ using System . Management . Automation . Host ;
3
5
using Microsoft . PowerShell . Commands ;
4
6
using AIShell . Abstraction ;
5
7
@@ -59,7 +61,7 @@ protected override void EndProcessing()
59
61
targetObject : null ) ;
60
62
ThrowTerminatingError ( error ) ;
61
63
}
62
- else if ( UseClipboardForCommandOutput ( lastExitCode ) )
64
+ else
63
65
{
64
66
// '$? == False' but no 'ErrorRecord' can be found that is associated with the last command line,
65
67
// and '$LASTEXITCODE' is non-zero, which indicates the last failed command is a native command.
@@ -68,19 +70,27 @@ Running the command line `{commandLine}` in PowerShell v{channel.PSVersion} fail
68
70
Please try to explain the failure and suggest the right fix.
69
71
Output of the command line can be found in the context information below.
70
72
""" ;
71
- IncludeOutputFromClipboard = true ;
72
- }
73
- else
74
- {
75
- ThrowTerminatingError ( new (
76
- new NotSupportedException ( $ "The output content is needed for suggestions on native executable failures.") ,
77
- errorId : "OutputNeededForNativeCommand" ,
78
- ErrorCategory . InvalidData ,
79
- targetObject : null
80
- ) ) ;
73
+
74
+ context = ScrapeScreenForNativeCommandOutput ( commandLine ) ;
75
+ if ( context is null )
76
+ {
77
+ if ( UseClipboardForCommandOutput ( ) )
78
+ {
79
+ IncludeOutputFromClipboard = true ;
80
+ }
81
+ else
82
+ {
83
+ ThrowTerminatingError ( new (
84
+ new NotSupportedException ( $ "The output content is needed for suggestions on native executable failures.") ,
85
+ errorId : "OutputNeededForNativeCommand" ,
86
+ ErrorCategory . InvalidData ,
87
+ targetObject : null
88
+ ) ) ;
89
+ }
90
+ }
81
91
}
82
92
83
- if ( IncludeOutputFromClipboard )
93
+ if ( context is null && IncludeOutputFromClipboard )
84
94
{
85
95
pwsh . Commands . Clear ( ) ;
86
96
var r = pwsh
@@ -94,7 +104,7 @@ Output of the command line can be found in the context information below.
94
104
channel . PostQuery ( new PostQueryMessage ( query , context , Agent ) ) ;
95
105
}
96
106
97
- private bool UseClipboardForCommandOutput ( int lastExitCode )
107
+ private bool UseClipboardForCommandOutput ( )
98
108
{
99
109
if ( IncludeOutputFromClipboard )
100
110
{
@@ -127,4 +137,93 @@ private bool TryGetLastError(HistoryInfo lastHistory, out ErrorRecord lastError)
127
137
128
138
return true ;
129
139
}
140
+
141
+ private string ScrapeScreenForNativeCommandOutput ( string lastCommandLine )
142
+ {
143
+ if ( ! OperatingSystem . IsWindows ( ) )
144
+ {
145
+ return null ;
146
+ }
147
+
148
+ try
149
+ {
150
+ PSHostRawUserInterface rawUI = Host . UI . RawUI ;
151
+ Coordinates start = new ( 0 , 0 ) , end = rawUI . CursorPosition ;
152
+
153
+ string currentCommandLine = MyInvocation . Line ;
154
+ end . X = rawUI . BufferSize . Width - 1 ;
155
+
156
+ BufferCell [ , ] content = rawUI . GetBufferContents ( new Rectangle ( start , end ) ) ;
157
+ StringBuilder line = new ( ) , buffer = new ( ) ;
158
+
159
+ bool collect = false ;
160
+ int rows = content . GetLength ( 0 ) ;
161
+ int columns = content . GetLength ( 1 ) ;
162
+
163
+ for ( int row = 0 ; row < rows ; row ++ )
164
+ {
165
+ line . Clear ( ) ;
166
+ for ( int column = 0 ; column < columns ; column ++ )
167
+ {
168
+ line . Append ( content [ row , column ] . Character ) ;
169
+ }
170
+
171
+ string lineStr = line . ToString ( ) . TrimEnd ( ) ;
172
+ if ( ! collect && IsStartOfCommand ( lineStr , columns , lastCommandLine ) )
173
+ {
174
+ collect = true ;
175
+ buffer . Append ( lineStr ) ;
176
+ continue ;
177
+ }
178
+
179
+ if ( collect )
180
+ {
181
+ // The current command line is just `Resolve-Error` or `fixit`, which should be on the same line
182
+ // and thus there is no need to check for the span-to-the-next-line case.
183
+ if ( lineStr . EndsWith ( currentCommandLine , StringComparison . Ordinal ) )
184
+ {
185
+ break ;
186
+ }
187
+
188
+ buffer . Append ( '\n ' ) . Append ( lineStr ) ;
189
+ }
190
+ }
191
+
192
+ return buffer . Length is 0 ? null : buffer . ToString ( ) ;
193
+ }
194
+ catch
195
+ {
196
+ return null ;
197
+ }
198
+
199
+ static bool IsStartOfCommand ( string lineStr , int columns , string commandLine )
200
+ {
201
+ if ( lineStr . EndsWith ( commandLine , StringComparison . Ordinal ) )
202
+ {
203
+ return true ;
204
+ }
205
+
206
+ // Handle the case where the command line is too long and spans to the next line on screen,
207
+ // like az, gcloud, and aws CLI commands which are usually long with many parameters.
208
+ if ( columns - lineStr . Length > 3 || commandLine . Length < 20 )
209
+ {
210
+ // The line on screen unlikely spanned to the next line in this case.
211
+ return false ;
212
+ }
213
+
214
+ // We check if the prefix of the command line is the suffix of the current line on screen.
215
+ ReadOnlySpan < char > lineStrSpan = lineStr . AsSpan ( ) ;
216
+ ReadOnlySpan < char > cmdLineSpan = commandLine . AsSpan ( ) ;
217
+
218
+ // We assume the first 20 chars of the command line should be in the current line on screen.
219
+ // This assumption is not perfect but practically good enough.
220
+ int index = lineStrSpan . IndexOf ( cmdLineSpan [ ..20 ] , StringComparison . Ordinal ) ;
221
+ if ( index >= 0 && cmdLineSpan . StartsWith ( lineStrSpan [ index ..] , StringComparison . Ordinal ) )
222
+ {
223
+ return true ;
224
+ }
225
+
226
+ return false ;
227
+ }
228
+ }
130
229
}
0 commit comments