private void ProcessMessageAtSessionInitialization(string line) { // Handle incoming messages at session startup. if (line.Contains("MOTD:")) { // Let's display the message-of-the-day from PowerShell Azure Shell. Console.WriteLine("\n" + line); // Also, seeing this message means we are now in pwsh. _codeExecutedTaskSource?.SetResult(null); } else if (line.Contains("VERBOSE: ")) { // Let's show the verbose message generated from the profile. Console.Write(line); } else if (line.Contains(CommandToSetPrompt)) { // pwsh will echo the command passed to it. // It's okay to show incoming messages after this very first command is echoed back. _sessionInitialized = true; string color = VTColorUtils.CombineColorSequences(ConsoleColor.Green, VTColorUtils.DefaultConsoleColor); Console.WriteLine($"\n{color}Welcome to Azure Cloud Shell!{VTColorUtils.ResetColor}"); Console.WriteLine($"{color}Submitted code will run in the Azure Cloud Shell, type 'exit' to quit.{VTColorUtils.ResetColor}"); } }
internal async Task ExitSession() { await SendCommand(_exitSessionCommand, waitForExecutionCompletion : false); _tokenRenewTimer.Stop(); string color = VTColorUtils.CombineColorSequences(ConsoleColor.Green, VTColorUtils.DefaultConsoleColor); Console.Write($"{color}Azure Cloud Shell session ended.{VTColorUtils.ResetColor}\n"); Console.Write($"{color}Submitted code will run in the local PowerShell sub kernel.{VTColorUtils.ResetColor}\n"); }
private void ForceRender() { var defaultColor = VTColorUtils.MapColorToEscapeSequence(_console.ForegroundColor, isBackground: false) + VTColorUtils.MapColorToEscapeSequence(_console.BackgroundColor, isBackground: true); // Geneate a sequence of logical lines with escape sequences for coloring. int logicalLineCount = GenerateRender(defaultColor); // Now write that out (and remember what we did so we can clear previous renders // and minimize writing more than necessary on the next render.) var renderLines = new RenderedLineData[logicalLineCount]; var renderData = new RenderData { lines = renderLines }; for (var i = 0; i < logicalLineCount; i++) { var line = _consoleBufferLines[i].ToString(); renderLines[i].line = line; renderLines[i].columns = LengthInBufferCells(line); } // And then do the real work of writing to the screen. // Rendering data is in reused ReallyRender(renderData, defaultColor); // Cleanup some excess buffers, saving a few because we know we'll use them. var bufferCount = _consoleBufferLines.Count; var excessBuffers = bufferCount - renderLines.Length; if (excessBuffers > 5) { _consoleBufferLines.RemoveRange(renderLines.Length, excessBuffers); } }
/// <summary> /// Generates an array of strings representing as much of the outstanding progress activities as possible within the given /// space. As more outstanding activities are collected, nodes are "compressed" (i.e. rendered in an increasing terse /// fashion) in order to display as many as possible. Ultimately, some nodes may be compressed to the point of /// invisibility. The oldest nodes are compressed first. /// </summary> /// <param name="maxWidth"> /// The maximum width (in BufferCells) that the rendering may consume. /// </param> /// <param name="maxHeight"> /// The maximum height (in BufferCells) that the rendering may consume. /// </param> /// <param name="ui"> /// The PSHostRawUserInterface used to gauge string widths in the rendering. /// </param> /// <returns> /// An array of strings containing the textual representation of the outstanding progress activities. /// </returns> internal List <string> Render(int maxWidth, int maxHeight, PSKernelHostUserInterface ui) { if (_topLevelNodes == null || _topLevelNodes.Count == 0) { // we have nothing to render. return(null); } int invisible = 0; PSHostRawUserInterface rawUI = ui.RawUI; if (TallyHeight(rawUI, maxHeight, maxWidth) > maxHeight) { // This will smash down nodes until the tree will fit into the alloted number of lines. If in the // process some nodes were made invisible, we will add a line to the display to say so. invisible = CompressToFit(rawUI, maxHeight, maxWidth); } var result = new List <string>(capacity: 5); string border = StringUtil.Padding(maxWidth); string vtSeqs = VTColorUtils.CombineColorSequences(ui.ProgressForegroundColor, ui.ProgressBackgroundColor); result.Add(string.IsNullOrEmpty(vtSeqs) ? border : vtSeqs + border); RenderHelper(result, _topLevelNodes, indentation: 0, maxWidth, rawUI); if (invisible == 1) { result.Add(" 1 activity not shown..."); } else if (invisible > 1) { result.Add(StringUtil.Format(" {0} activities not shown...", invisible)); } result.Add(string.IsNullOrEmpty(vtSeqs) ? border : border + VTColorUtils.ResetColor); return(result); }
private static string MakeCombinedColor(ConsoleColor fg, ConsoleColor bg) => VTColorUtils.AsEscapeSequence(fg) + VTColorUtils.AsEscapeSequence(bg, isBackground: true);
private void MenuCompleteImpl(Menu menu, CommandCompletion completions) { var menuStack = new Stack <Menu>(10); menuStack.Push(null); // Used to help detect excess backspaces RemoveEditsAfterUndo(); var undoPoint = _edits.Count; bool undo = false; int savedUserMark = _mark; _visualSelectionCommandCount++; // Get the unambiguous prefix, possibly removing the first quote var userCompletionText = GetUnambiguousPrefix(menu.MenuItems, out var ambiguous); if (userCompletionText.Length > 0) { var c = userCompletionText[0]; if (IsSingleQuote(c) || IsDoubleQuote(c)) { userCompletionText = userCompletionText.Substring(1); } } var userInitialCompletionLength = userCompletionText.Length; completions.CurrentMatchIndex = 0; menu.DrawMenu(null); bool processingKeys = true; int previousSelection = -1; while (processingKeys) { if (menu.CurrentSelection != previousSelection) { var currentMenuItem = menu.CurrentMenuItem; int curPos = FindUserCompletionTextPosition(currentMenuItem, userCompletionText); if (userCompletionText.Length == 0 && (IsSingleQuote(currentMenuItem.CompletionText[0]) || IsDoubleQuote(currentMenuItem.CompletionText[0]))) { curPos++; } // set mark to the end of UserCompletion but in real completion (because of .\ and so on) _mark = completions.ReplacementIndex + curPos + userCompletionText.Length; DoReplacementForCompletion(currentMenuItem, completions); ExchangePointAndMark(); // After replacement, the menu might be misplaced from the command line // getting shorter or longer. var endOfCommandLine = ConvertOffsetToPoint(_buffer.Length); var topAdjustment = (endOfCommandLine.Y + 1) - menu.Top; if (topAdjustment != 0) { menu.Top += topAdjustment; menu.DrawMenu(null); } if (topAdjustment > 0) { // Render did not clear the rest of the command line which flowed // into the menu, so we must do that here. _console.SaveCursor(); _console.SetCursorPosition(endOfCommandLine.X, endOfCommandLine.Y); _console.Write(Spaces(_console.BufferWidth - endOfCommandLine.X)); _console.RestoreCursor(); } if (previousSelection != -1) { if (menu.ToolTipLines > 0) { // Erase previous tooltip, taking into account if the menu moved up/down. WriteBlankLines(menu.Top + menu.Rows, -topAdjustment + menu.ToolTipLines); } menu.UpdateMenuSelection(previousSelection, /*select*/ false, /*showToolTips*/ false, VTColorUtils.AsEscapeSequence(Options.EmphasisColor)); } menu.UpdateMenuSelection(menu.CurrentSelection, /*select*/ true, Options.ShowToolTips, VTColorUtils.AsEscapeSequence(Options.EmphasisColor)); previousSelection = menu.CurrentSelection; } var nextKey = ReadKey(); if (nextKey.EqualsNormalized(Keys.RightArrow)) { menu.MoveRight(); } else if (nextKey.EqualsNormalized(Keys.LeftArrow)) { menu.MoveLeft(); } else if (nextKey.EqualsNormalized(Keys.DownArrow)) { menu.MoveDown(); } else if (nextKey.EqualsNormalized(Keys.UpArrow)) { menu.MoveUp(); } else if (nextKey.EqualsNormalized(Keys.PageDown)) { menu.MovePageDown(); } else if (nextKey.EqualsNormalized(Keys.PageUp)) { menu.MovePageUp(); } else if (nextKey.EqualsNormalized(Keys.Tab)) { // Search for possible unambiguous common prefix. string unAmbiguousText = GetUnambiguousPrefix(menu.MenuItems, out ambiguous); int userComplPos = unAmbiguousText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); // ... If found - advance IncrementalCompletion ... if (unAmbiguousText.Length > 0 && userComplPos >= 0 && unAmbiguousText.Length > (userComplPos + userCompletionText.Length)) { userCompletionText = unAmbiguousText.Substring(userComplPos); _current = completions.ReplacementIndex + FindUserCompletionTextPosition(menu.MenuItems[menu.CurrentSelection], userCompletionText) + userCompletionText.Length; Render(); Ding(); } // ... if no - usual Tab behaviour else { menu.MoveN(1); } } else if (nextKey.EqualsNormalized(Keys.ShiftTab)) { menu.MoveN(-1); } else if (nextKey.EqualsNormalized(Keys.CtrlG) || nextKey.EqualsNormalized(Keys.Escape)) { undo = true; processingKeys = false; _visualSelectionCommandCount = 0; _mark = savedUserMark; } else if (nextKey.EqualsNormalized(Keys.Backspace)) { // TODO: Shift + Backspace does not fail here? if (menuStack.Count > 1) { var newMenu = menuStack.Pop(); newMenu.DrawMenu(menu); previousSelection = -1; menu = newMenu; userCompletionText = userCompletionText.Substring(0, userCompletionText.Length - 1); } else if (menuStack.Count == 1) { Ding(); Debug.Assert(menuStack.Peek() == null, "sentinel value expected"); // Pop so the next backspace sends us to the else block and out of the loop. menuStack.Pop(); } else { processingKeys = false; undo = true; _visualSelectionCommandCount = 0; _mark = savedUserMark; PrependQueuedKeys(nextKey); } } else { bool prependNextKey = false; int cursorAdjustment = 0; bool truncateCurrentCompletion = false; bool keepSelection = false; var currentMenuItem = menu.CurrentMenuItem; if (IsDoneWithCompletions(currentMenuItem, nextKey)) { processingKeys = false; ExchangePointAndMark(); // cursor to the end of Completion if (!nextKey.EqualsNormalized(Keys.Enter)) { if (currentMenuItem.ResultType == CompletionResultType.ProviderContainer) { userCompletionText = GetUnquotedText( GetReplacementTextForDirectory(currentMenuItem.CompletionText, ref cursorAdjustment), consistentQuoting: false); } else { userCompletionText = GetUnquotedText(currentMenuItem, consistentQuoting: false); } // do not append the same char as last char in CompletionText (works for for '(', '\') prependNextKey = userCompletionText[userCompletionText.Length - 1] != nextKey.KeyChar; } } else if (nextKey.KeyChar > 0 && !char.IsControl(nextKey.KeyChar)) { userCompletionText += nextKey.KeyChar; // filter out matches and redraw menu var newMatches = FilterCompletions(completions, userCompletionText); if (newMatches.Count > 0) { var newMenu = CreateCompletionMenu(newMatches); newMenu.DrawMenu(menu); previousSelection = -1; // Remember the current menu for when we see Backspace. menu.ToolTipLines = 0; if (menuStack.Count == 0) { // The user hit backspace before there were any items on the stack // and we removed the sentinel - so put it back now. menuStack.Push(null); } menuStack.Push(menu); menu = newMenu; } else { processingKeys = false; prependNextKey = true; // we exit loop with current completion up to cursor truncateCurrentCompletion = true; if (userInitialCompletionLength == 0) { undo = true; } } } else // exit with any other Key chord { processingKeys = false; prependNextKey = true; // without this branch experience doesnt look naturally if (_dispatchTable.TryGetValue(nextKey, out var handler) && ( handler.Action == CopyOrCancelLine || handler.Action == Cut || handler.Action == DeleteChar || handler.Action == Paste ) ) { keepSelection = true; } } if (!processingKeys) // time to exit loop { if (truncateCurrentCompletion && !undo) { CompletionResult r = new CompletionResult(currentMenuItem .CompletionText.Substring(0, _current - completions.ReplacementIndex)); DoReplacementForCompletion(r, completions); } if (keepSelection) { _visualSelectionCommandCount = 1; } else { _visualSelectionCommandCount = 0; // if mark was set after cursor, it restored in uninspected position, because text before mark now longer // should we correct it ? I think not, beause any other text insertion does not correct it _mark = savedUserMark; } // without render all key chords that just move cursor leave selection visible, but it can be wrong if (!undo && !keepSelection) { Render(); } if (prependNextKey) { _current -= cursorAdjustment; PrependQueuedKeys(nextKey); } } } } menu.Clear(); var lastInsert = ((GroupedEdit)_edits[_edits.Count - 1])._groupedEditItems[1]; Debug.Assert(lastInsert is EditItemInsertString, "The only edits possible here are pairs of Delete/Insert"); var firstDelete = ((GroupedEdit)_edits[undoPoint])._groupedEditItems[0]; Debug.Assert(firstDelete is EditItemDelete, "The only edits possible here are pairs of Delete/Insert"); var groupEditCount = _edits.Count - undoPoint; _edits.RemoveRange(undoPoint, groupEditCount); _undoEditIndex = undoPoint; if (undo) { // Pretend it never happened. lastInsert.Undo(); firstDelete.Undo(); Render(); } else { // Leave one edit instead of possibly many to undo SaveEditItem(GroupedEdit.Create(new List <EditItem> { firstDelete, lastInsert })); } }