MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Collections.Specialized;7using System.Management.Automation;8using System.Management.Automation.Internal;9using System.Management.Automation.Host;10 11using Dbg = System.Management.Automation.Diagnostics;12 13// interfaces for host interaction14 15namespace Microsoft.PowerShell.Commands.Internal.Format16{17 /// <summary>18 /// Tear off class.19 /// </summary>20 internal class DisplayCellsHost : DisplayCells21 {22 internal DisplayCellsHost(PSHostRawUserInterface rawUserInterface)23 {24 _rawUserInterface = rawUserInterface;25 }26 27 internal override int Length(string str, int offset)28 {29 if (string.IsNullOrEmpty(str))30 {31 return 0;32 }33 34 if (offset < 0 || offset >= str.Length)35 {36 throw PSTraceSource.NewArgumentException(nameof(offset));37 }38 39 try40 {41 var valueStrDec = new ValueStringDecorated(str);42 if (valueStrDec.IsDecorated)43 {44 str = valueStrDec.ToString(OutputRendering.PlainText);45 }46 47 int length = 0;48 for (; offset < str.Length; offset++)49 {50 length += _rawUserInterface.LengthInBufferCells(str[offset]);51 }52 53 return length;54 }55 catch56 {57 // thrown when external host rawui is not implemented, in which case58 // we will fallback to the default value.59 return base.Length(str, offset);60 }61 }62 63 internal override int Length(char character)64 {65 try66 {67 return _rawUserInterface.LengthInBufferCells(character);68 }69 catch70 {71 // thrown when external host rawui is not implemented, in which case72 // we will fallback to the default value.73 return base.Length(character);74 }75 }76 77 private readonly PSHostRawUserInterface _rawUserInterface;78 }79 80 /// <summary>81 /// Implementation of the LineOutput interface on top of Console and RawConsole.82 /// </summary>83 internal sealed class ConsoleLineOutput : LineOutput84 {85 #region tracer86 [TraceSource("ConsoleLineOutput", "ConsoleLineOutput")]87 internal static readonly PSTraceSource tracer = PSTraceSource.GetTracer("ConsoleLineOutput", "ConsoleLineOutput");88 #endregion tracer89 90 /// <summary>91 /// The default buffer cell calculation already works for the PowerShell console host and Visual studio code host.92 /// </summary>93 private static readonly HashSet<string> s_psHost = new(StringComparer.Ordinal) { "ConsoleHost", "Visual Studio Code Host" };94 95 #region LineOutput implementation96 /// <summary>97 /// The # of columns is just the width of the screen buffer (not the98 /// width of the window)99 /// </summary>100 /// <value></value>101 internal override int ColumnNumber102 {103 get104 {105 CheckStopProcessing();106 PSHostRawUserInterface raw = _console.RawUI;107 108 // IMPORTANT NOTE: we subtract one because109 // we want to make sure the console's last column110 // is never considered written. This causes the writing111 // logic to always call WriteLine(), making sure a CR112 // is inserted.113 114 try115 {116 return _forceNewLine ? raw.BufferSize.Width - 1 : raw.BufferSize.Width;117 }118 catch119 {120 // thrown when external host rawui is not implemented, in which case121 // we will fallback to the default value.122 }123 124 return _forceNewLine ? _fallbackRawConsoleColumnNumber - 1 : _fallbackRawConsoleColumnNumber;125 }126 }127 128 /// <summary>129 /// The # of rows is the # of rows visible in the window (and not the # of130 /// rows in the screen buffer)131 /// </summary>132 /// <value></value>133 internal override int RowNumber134 {135 get136 {137 CheckStopProcessing();138 PSHostRawUserInterface raw = _console.RawUI;139 140 try141 {142 return raw.WindowSize.Height;143 }144 catch145 {146 // thrown when external host rawui is not implemented, in which case147 // we will fallback to the default value.148 }149 150 return _fallbackRawConsoleRowNumber;151 }152 }153 154 /// <summary>155 /// Write a line to the output device.156 /// </summary>157 /// <param name="s">Line to write.</param>158 internal override void WriteLine(string s)159 {160 CheckStopProcessing();161 162 // delegate the action to the helper,163 // that will properly break the string into164 // screen lines165 _writeLineHelper.WriteLine(s, this.ColumnNumber);166 }167 168 internal override DisplayCells DisplayCells169 {170 get171 {172 CheckStopProcessing();173 if (_displayCellsHost != null)174 {175 return _displayCellsHost;176 }177 178 // fall back if we do not have a Msh host specific instance179 return _displayCellsDefault;180 }181 }182 #endregion183 184 /// <summary>185 /// Constructor for the ConsoleLineOutput.186 /// </summary>187 /// <param name="host">PSHostUserInterface to wrap.</param>188 /// <param name="paging">True if we require prompting for page breaks.</param>189 /// <param name="errorContext">Error context to throw exceptions.</param>190 internal ConsoleLineOutput(PSHost host, bool paging, TerminatingErrorContext errorContext)191 {192 if (host == null)193 {194 throw PSTraceSource.NewArgumentNullException(nameof(host));195 }196 197 if (errorContext == null)198 {199 throw PSTraceSource.NewArgumentNullException(nameof(errorContext));200 }201 202 _console = host.UI;203 _errorContext = errorContext;204 205 if (paging)206 {207 tracer.WriteLine("paging is needed");208 209 // If we need to do paging, instantiate a prompt handler that will take care of the screen interaction210 string promptString = StringUtil.Format(FormatAndOut_out_xxx.ConsoleLineOutput_PagingPrompt);211 _prompt = new PromptHandler(promptString, this);212 }213 214 if (!s_psHost.Contains(host.Name) && _console.RawUI is not null)215 {216 // set only if we have a valid raw interface217 tracer.WriteLine("there is a valid raw interface");218 _displayCellsHost = new DisplayCellsHost(_console.RawUI);219 }220 221 // instantiate the helper to do the line processing when ILineOutput.WriteXXX() is called222 WriteLineHelper.WriteCallback wl = new WriteLineHelper.WriteCallback(this.OnWriteLine);223 WriteLineHelper.WriteCallback w = new WriteLineHelper.WriteCallback(this.OnWrite);224 225 if (_forceNewLine)226 {227 _writeLineHelper = new WriteLineHelper(/*lineWrap*/false, wl, null, this.DisplayCells);228 }229 else230 {231 _writeLineHelper = new WriteLineHelper(/*lineWrap*/false, wl, w, this.DisplayCells);232 }233 }234 235 /// <summary>236 /// Callback to be called when ILineOutput.WriteLine() is called by WriteLineHelper.237 /// </summary>238 /// <param name="s">String to write.</param>239 private void OnWriteLine(string s)240 {241 // Do any default transcription.242 _console.TranscribeResult(s);243 244 switch (this.WriteStream)245 {246 case WriteStreamType.Error:247 _console.WriteErrorLine(s);248 break;249 250 case WriteStreamType.Warning:251 _console.WriteWarningLine(s);252 break;253 254 case WriteStreamType.Verbose:255 _console.WriteVerboseLine(s);256 break;257 258 case WriteStreamType.Debug:259 _console.WriteDebugLine(s);260 break;261 262 default:263 // If the host is in "transcribe only"264 // mode (due to an implicitly added call to Out-Default -Transcribe),265 // then don't call the actual host API.266 if (!_console.TranscribeOnly)267 {268 _console.WriteLine(s);269 }270 271 break;272 }273 274 LineWrittenEvent();275 }276 277 /// <summary>278 /// Callback to be called when ILineOutput.Write() is called by WriteLineHelper279 /// This is called when the WriteLineHelper needs to write a line whose length280 /// is the same as the width of the screen buffer.281 /// </summary>282 /// <param name="s">String to write.</param>283 private void OnWrite(string s)284 {285 switch (this.WriteStream)286 {287 case WriteStreamType.Error:288 _console.WriteErrorLine(s);289 break;290 291 case WriteStreamType.Warning:292 _console.WriteWarningLine(s);293 break;294 295 case WriteStreamType.Verbose:296 _console.WriteVerboseLine(s);297 break;298 299 case WriteStreamType.Debug:300 _console.WriteDebugLine(s);301 break;302 303 default:304 _console.Write(s);305 break;306 }307 308 LineWrittenEvent();309 }310 311 /// <summary>312 /// Called when a line was written to console.313 /// </summary>314 private void LineWrittenEvent()315 {316 // check to avoid reentrancy from the prompt handler317 // writing during the PromptUser() call318 if (_disableLineWrittenEvent)319 return;320 321 // if there is no prompting, we are done322 if (_prompt == null)323 return;324 325 // increment the count of lines written to the screen326 _linesWritten++;327 328 // check if we need to put out a prompt329 if (this.NeedToPrompt)330 {331 // put out the prompt332 _disableLineWrittenEvent = true;333 PromptHandler.PromptResponse response = _prompt.PromptUser(_console);334 _disableLineWrittenEvent = false;335 336 switch (response)337 {338 case PromptHandler.PromptResponse.NextPage:339 {340 // reset the counter, since we are starting a new page341 _linesWritten = 0;342 }343 344 break;345 case PromptHandler.PromptResponse.NextLine:346 {347 // roll back the counter by one, since we allow one more line348 _linesWritten--;349 }350 351 break;352 case PromptHandler.PromptResponse.Quit:353 // 1021203-2005/05/09-JonN354 // HaltCommandException will cause the command355 // to stop, but not be reported as an error.356 throw new HaltCommandException();357 }358 }359 }360 361 /// <summary>362 /// Check if we need to put out a prompt.363 /// </summary>364 /// <value>true if we need to prompt</value>365 private bool NeedToPrompt366 {367 get368 {369 // NOTE: we recompute all the time to take into account screen resizing370 int rawRowNumber = this.RowNumber;371 372 if (rawRowNumber <= 0)373 {374 // something is wrong, there is no real estate, we suppress prompting375 return false;376 }377 378 // the prompt will occupy some lines, so we need to subtract them form the total379 // screen line count380 int computedPromptLines = _prompt.ComputePromptLines(this.DisplayCells, this.ColumnNumber);381 int availableLines = this.RowNumber - computedPromptLines;382 383 if (availableLines <= 0)384 {385 tracer.WriteLine("No available Lines; suppress prompting");386 // something is wrong, there is no real estate, we suppress prompting387 return false;388 }389 390 return _linesWritten >= availableLines;391 }392 }393 394 #region Private Members395 /// <summary>396 /// Object to manage prompting.397 /// </summary>398 private sealed class PromptHandler399 {400 /// <summary>401 /// Prompt handler with the given prompt.402 /// </summary>403 /// <param name="s">Prompt string to be used.</param>404 /// <param name="cmdlet">The Cmdlet using this prompt handler.</param>405 internal PromptHandler(string s, ConsoleLineOutput cmdlet)406 {407 if (string.IsNullOrEmpty(s))408 throw PSTraceSource.NewArgumentNullException(nameof(s));409 410 _promptString = s;411 _callingCmdlet = cmdlet;412 }413 414 /// <summary>415 /// Determine how many rows the prompt should take.416 /// </summary>417 /// <param name="cols">Current number of columns on the screen.</param>418 /// <param name="displayCells">String manipulation helper.</param>419 /// <returns></returns>420 internal int ComputePromptLines(DisplayCells displayCells, int cols)421 {422 // split the prompt string into lines423 _actualPrompt = StringManipulationHelper.GenerateLines(displayCells, _promptString, cols, cols);424 return _actualPrompt.Count;425 }426 427 /// <summary>428 /// Options returned by the PromptUser() call.429 /// </summary>430 internal enum PromptResponse431 {432 NextPage,433 NextLine,434 Quit435 }436 437 /// <summary>438 /// Do the actual prompting.439 /// </summary>440 /// <param name="console">PSHostUserInterface instance to prompt to.</param>441 internal PromptResponse PromptUser(PSHostUserInterface console)442 {443 // NOTE: assume the values passed to ComputePromptLines are still valid444 445 // write out the prompt line(s). The last one will not have a new line446 // at the end because we leave the prompt at the end of the line447 for (int k = 0; k < _actualPrompt.Count; k++)448 {449 if (k < (_actualPrompt.Count - 1))450 console.WriteLine(_actualPrompt[k]); // intermediate line(s)451 else452 console.Write(_actualPrompt[k]); // last line453 }454 455 while (true)456 {457 _callingCmdlet.CheckStopProcessing();458 KeyInfo ki = console.RawUI.ReadKey(ReadKeyOptions.IncludeKeyUp | ReadKeyOptions.NoEcho);459 char key = ki.Character;460 if (key == 'q' || key == 'Q')461 {462 // need to move to the next line since we accepted input, add a newline463 console.WriteLine();464 return PromptResponse.Quit;465 }466 else if (key == ' ')467 {468 // need to move to the next line since we accepted input, add a newline469 console.WriteLine();470 return PromptResponse.NextPage;471 }472 else if (key == '\r')473 {474 // need to move to the next line since we accepted input, add a newline475 console.WriteLine();476 return PromptResponse.NextLine;477 }478 }479 }480 481 /// <summary>482 /// Cached string(s) valid during a sequence of ComputePromptLines()/PromptUser()483 /// </summary>484 private StringCollection _actualPrompt;485 486 /// <summary>487 /// Prompt string as passed at initialization.488 /// </summary>489 private readonly string _promptString;490 491 /// <summary>492 /// The cmdlet that uses this prompt helper.493 /// </summary>494 private readonly ConsoleLineOutput _callingCmdlet = null;495 }496 497 /// <summary>498 /// Flag to force new lines in CMD.EXE by limiting the499 /// usable width to N-1 (e.g. 80-1) and forcing a call500 /// to WriteLine()501 /// </summary>502 private readonly bool _forceNewLine = true;503 504 /// <summary>505 /// Use this if IRawConsole is null;506 /// </summary>507 private readonly int _fallbackRawConsoleColumnNumber = 80;508 509 /// <summary>510 /// Use this if IRawConsole is null;511 /// </summary>512 private readonly int _fallbackRawConsoleRowNumber = 40;513 514 private readonly WriteLineHelper _writeLineHelper;515 516 /// <summary>517 /// Handler to prompt the user for page breaks518 /// if this handler is not null, we have prompting.519 /// </summary>520 private readonly PromptHandler _prompt = null;521 522 /// <summary>523 /// Counter for the # of lines written when prompting is on.524 /// </summary>525 private long _linesWritten = 0;526 527 /// <summary>528 /// Flag to avoid reentrancy on prompting.529 /// </summary>530 private bool _disableLineWrittenEvent = false;531 532 /// <summary>533 /// Reference to the PSHostUserInterface interface we use.534 /// </summary>535 private readonly PSHostUserInterface _console = null;536 537 /// <summary>538 /// Msh host specific string manipulation helper.539 /// </summary>540 private readonly DisplayCells _displayCellsHost;541 542 /// <summary>543 /// Reference to error context to throw Msh exceptions.544 /// </summary>545 private readonly TerminatingErrorContext _errorContext = null;546 547 #endregion548 }549}550 