// Copyright (c) Microsoft Corporation.// Licensed under the MIT license.#include "precomp.h"#include "_output.h"#include "stream.h"#include "scrolling.hpp"#include "../interactivity/inc/ServiceLocator.hpp"std::unique_ptr<Selection> Selection::_instance;Selection::Selection() :_fSelectionVisible(false),_ulSavedCursorSize(0),_fSavedCursorVisible(false),_savedCursorColor(INVALID_COLOR),_savedCursorType(CursorType::Legacy),_dwSelectionFlags(0),_fLineSelection(true),_fUseAlternateSelection(false),_allowMouseDragSelection{ true }{ZeroMemory((void*)&_srSelectionRect, sizeof(_srSelectionRect));ZeroMemory((void*)&_coordSelectionAnchor, sizeof(_coordSelectionAnchor));ZeroMemory((void*)&_coordSavedCursorPosition, sizeof(_coordSavedCursorPosition));}Selection& Selection::Instance(){if (!_instance){_instance.reset(new Selection());}return *_instance;}// Routine Description:// - Detemines the line-by-line selection rectangles based on global selection state.// Arguments:// - selectionRect - The selection rectangle outlining the region to be selected// - selectionAnchor - The corner of the selection rectangle that selection started from// - lineSelection - True to process in line mode. False to process in block mode.// Return Value:// - Returns a vector where each SMALL_RECT is one Row worth of the area to be selected.// - Returns empty vector if no rows are selected.// - Throws exceptions for out of memory issuesstd::vector<SMALL_RECT> Selection::s_GetSelectionRects(const SMALL_RECT& selectionRect,const COORD selectionAnchor,const bool lineSelection){std::vector<SMALL_RECT> selectionAreas;const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();const auto& screenInfo = gci.GetActiveOutputBuffer();// if the anchor (start of select) was in the top right or bottom left of the box,// we need to remove rectangular overlap in the middle.// e.g.// For selections with the anchor in the top left (A) or bottom right (B),// it is valid to maintain the inner rectangle (+) as part of the selection// A+++++++================// ==============++++++++B// + and = are valid highlights in this scenario.// For selections with the anchor in in the top right (A) or bottom left (B),// we must remove a portion of the first/last line that lies within the rectangle (+)// +++++++A=================// ==============B+++++++// Only = is valid for highlight in this scenario.// This is only needed for line selection. Box selection doesn't need to account for this.bool removeRectPortion = false;if (lineSelection){const auto selectionStart = selectionAnchor;// only if top and bottom aren't the same line... we need the whole rectangle if we're on the same line.// e.g. A++++++++++++++B// All the + are valid select points.if (selectionRect.Top != selectionRect.Bottom){if ((selectionStart.X == selectionRect.Right && selectionStart.Y == selectionRect.Top) ||(selectionStart.X == selectionRect.Left && selectionStart.Y == selectionRect.Bottom)){removeRectPortion = true;}}}// for each row within the selection rectanglefor (short i = selectionRect.Top; i <= selectionRect.Bottom; i++){// create a rectangle representing the highlight on one rowSMALL_RECT highlightRow;highlightRow.Top = i;highlightRow.Bottom = i;highlightRow.Left = selectionRect.Left;highlightRow.Right = selectionRect.Right;// compensate for line selection by extending one or both ends of the rectangle to the edgeif (lineSelection){// if not the first row, pad the left selection to the buffer edgeif (i != selectionRect.Top){highlightRow.Left = 0;}// if not the last row, pad the right selection to the buffer edgeif (i != selectionRect.Bottom){highlightRow.Right = screenInfo.GetBufferSize().RightInclusive();}// if we've determined we're in a scenario where we must remove the inner rectangle from the lines...if (removeRectPortion){if (i == selectionRect.Top){// from the top row, move the left edge of the highlight line to the right edge of the rectanglehighlightRow.Left = selectionRect.Right;}else if (i == selectionRect.Bottom){// from the bottom row, move the right edge of the highlight line to the left edge of the rectanglehighlightRow.Right = selectionRect.Left;}}}// compensate for double width characters by calling double-width measuring/limiting functionconst COORD targetPoint{ highlightRow.Left, highlightRow.Top };const SHORT stringLength = highlightRow.Right - highlightRow.Left + 1;highlightRow = s_BisectSelection(stringLength, targetPoint, screenInfo, highlightRow);selectionAreas.emplace_back(highlightRow);}return selectionAreas;}// Routine Description:// - Detemines the line-by-line selection rectangles based on global selection state.// Arguments:// - <none> - Uses internal state to know what area is selected already.// Return Value:// - Returns a vector where each SMALL_RECT is one Row worth of the area to be selected.// - Returns empty vector if no rows are selected.// - Throws exceptions for out of memory issuesstd::vector<SMALL_RECT> Selection::GetSelectionRects() const{if (!_fSelectionVisible){return std::vector<SMALL_RECT>();}return s_GetSelectionRects(_srSelectionRect, _coordSelectionAnchor, IsLineSelection());}// Routine Description:// - This routine checks to ensure that clipboard selection isn't trying to cut a double byte character in half.// It will adjust the SmallRect rectangle size to ensure this.// Arguments:// - sStringLength - The length of the string we're attempting to clip.// - coordTargetPoint - The row/column position within the text buffer that we're about to try to clip.// - screenInfo - Screen information structure containing relevant text and dimension information.// - rect - The region of the text that we want to clip, and then adjusted to the region that should be// clipped without splicing double-width characters.// Return Value:// - the clipped regionSMALL_RECT Selection::s_BisectSelection(const short sStringLength,const COORD coordTargetPoint,const SCREEN_INFORMATION& screenInfo,const SMALL_RECT rect){SMALL_RECT outRect = rect;try{auto iter = screenInfo.GetCellDataAt(coordTargetPoint);if (iter->DbcsAttr().IsTrailing()){if (coordTargetPoint.X == 0){outRect.Left++;}else{outRect.Left--;}}// Check end position of stringsif (coordTargetPoint.X + sStringLength < screenInfo.GetBufferSize().Width()){iter += sStringLength;if (iter->DbcsAttr().IsTrailing()){outRect.Right++;}}else{if (coordTargetPoint.Y + 1 < screenInfo.GetBufferSize().Height()){const auto nextLineIter = screenInfo.GetCellDataAt({ 0, coordTargetPoint.Y + 1 });if (nextLineIter->DbcsAttr().IsTrailing()){outRect.Right--;}}}}CATCH_LOG();return outRect;}// Routine Description:// - Shows the selection area in the window if one is available and not already showing.// Arguments:// <none>// Return Value:// <none>void Selection::ShowSelection(){_SetSelectionVisibility(true);}// Routine Description:// - Hides the selection area in the window if one is available and already showing.// Arguments:// <none>// Return Value:// <none>void Selection::HideSelection(){_SetSelectionVisibility(false);}// Routine Description:// - Changes the visibility of the selection area on the screen.// - Used to turn the selection area on or off.// Arguments:// - fMakeVisible - If TRUE, we're turning the selection ON.// - If FALSE, we're turning the selection OFF.// Return Value:// <none>void Selection::_SetSelectionVisibility(const bool fMakeVisible){if (IsInSelectingState() && IsAreaSelected()){if (fMakeVisible == _fSelectionVisible){return;}_fSelectionVisible = fMakeVisible;_PaintSelection();}LOG_IF_FAILED(ServiceLocator::LocateConsoleWindow()->SignalUia(UIA_Text_TextSelectionChangedEventId));}// Routine Description:// - Inverts the selected region on the current screen buffer.// - Reads the selected area, selection mode, and active screen buffer// from the global properties and dispatches a GDI invert on the selected text area.// Arguments:// - <none>// Return Value:// - <none>void Selection::_PaintSelection() const{if (ServiceLocator::LocateGlobals().pRender != nullptr){ServiceLocator::LocateGlobals().pRender->TriggerSelection();}}// Routine Description:// - Starts the selection with the given initial position// Arguments:// - coordBufferPos - Position in which user started a selection// Return Value:// - <none>void Selection::InitializeMouseSelection(const COORD coordBufferPos){Scrolling::s_ClearScroll();// set flags_SetSelectingState(true);_dwSelectionFlags = CONSOLE_MOUSE_SELECTION | CONSOLE_SELECTION_NOT_EMPTY;// store anchor and rectangle of selection_coordSelectionAnchor = coordBufferPos;// since we've started with just a point, the rectangle is 1x1 on the point given_srSelectionRect.Left = coordBufferPos.X;_srSelectionRect.Right = coordBufferPos.X;_srSelectionRect.Top = coordBufferPos.Y;_srSelectionRect.Bottom = coordBufferPos.Y;// Check for ALT-Mouse Down "use alternate selection"// If in box mode, use line mode. If in line mode, use box mode.CheckAndSetAlternateSelection();// set window title to mouse selection modeIConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow();if (pWindow != nullptr){pWindow->UpdateWindowText();LOG_IF_FAILED(pWindow->SignalUia(UIA_Text_TextSelectionChangedEventId));}// Fire off an event to let accessibility apps know the selection has changed.ServiceLocator::LocateAccessibilityNotifier()->NotifyConsoleCaretEvent(IAccessibilityNotifier::ConsoleCaretEventFlags::CaretSelection, PACKCOORD(coordBufferPos));}// Routine Description:// - Modifies both ends of the current selection.// - Intended for use with functions that help auto-complete a selection area (e.g. double clicking)// Arguments:// - coordSelectionStart - Replaces the selection anchor, a.k.a. where the selection started from originally.// - coordSelectionEnd - The linear final position or opposite corner of the anchor to represent the complete selection area.// Return Value:// - <none>void Selection::AdjustSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd){// modify the anchor and then just use extend to adjust the other portion of the selection rectangle_coordSelectionAnchor = coordSelectionStart;ExtendSelection(coordSelectionEnd);_allowMouseDragSelection = false;}// Routine Description:// - Extends the selection out to the given position from the initial anchor point.// This means that a coordinate farther away will make the rectangle larger and a closer one will shrink it.// Arguments:// - coordBufferPos - Position to extend/contract the current selection up to.// Return Value:// - <none>void Selection::ExtendSelection(_In_ COORD coordBufferPos){CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer();_allowMouseDragSelection = true;// ensure position is within buffer bounds. Not less than 0 and not greater than the screen buffer size.try{screenInfo.GetTerminalBufferSize().Clamp(coordBufferPos);}CATCH_LOG_RETURN();if (!IsAreaSelected()){// we should only be extending a selection that has no area yet if we're coming from mark mode.// if not, just return.if (IsMouseInitiatedSelection()){return;}// scroll if necessary to make cursor visible.screenInfo.MakeCursorVisible(coordBufferPos, false);_dwSelectionFlags |= CONSOLE_SELECTION_NOT_EMPTY;_srSelectionRect.Left = _srSelectionRect.Right = _coordSelectionAnchor.X;_srSelectionRect.Top = _srSelectionRect.Bottom = _coordSelectionAnchor.Y;ShowSelection();}else{// scroll if necessary to make cursor visible.screenInfo.MakeCursorVisible(coordBufferPos, false);}// remember previous selection rectSMALL_RECT srNewSelection = _srSelectionRect;// update selection rect// this adjusts the rectangle dimensions based on which way the move was requested// in respect to the original selection position (the anchor)if (coordBufferPos.X <= _coordSelectionAnchor.X){srNewSelection.Left = coordBufferPos.X;srNewSelection.Right = _coordSelectionAnchor.X;}else if (coordBufferPos.X > _coordSelectionAnchor.X){srNewSelection.Right = coordBufferPos.X;srNewSelection.Left = _coordSelectionAnchor.X;}if (coordBufferPos.Y <= _coordSelectionAnchor.Y){srNewSelection.Top = coordBufferPos.Y;srNewSelection.Bottom = _coordSelectionAnchor.Y;}else if (coordBufferPos.Y > _coordSelectionAnchor.Y){srNewSelection.Bottom = coordBufferPos.Y;srNewSelection.Top = _coordSelectionAnchor.Y;}// call special update method to modify the displayed selection in-place// NOTE: Using HideSelection, editing the rectangle, then ShowSelection will cause flicker.//_PaintUpdateSelection(&srNewSelection);_srSelectionRect = srNewSelection;_PaintSelection();// Fire off an event to let accessibility apps know the selection has changed.ServiceLocator::LocateAccessibilityNotifier()->NotifyConsoleCaretEvent(IAccessibilityNotifier::ConsoleCaretEventFlags::CaretSelection, PACKCOORD(coordBufferPos));LOG_IF_FAILED(ServiceLocator::LocateConsoleWindow()->SignalUia(UIA_Text_TextSelectionChangedEventId));}// Routine Description:// - Cancels any mouse selection state to return to normal mode.// Arguments:// - <none> (Uses global state)// Return Value:// - <none>void Selection::_CancelMouseSelection(){CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();SCREEN_INFORMATION& ScreenInfo = gci.GetActiveOutputBuffer();// invert old select rect. if we're selecting by mouse, we// always have a selection rect.HideSelection();// turn off selection flag_SetSelectingState(false);IConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow();if (pWindow != nullptr){pWindow->UpdateWindowText();}// Mark the cursor position as changed so we'll fire off a win event.ScreenInfo.GetTextBuffer().GetCursor().SetHasMoved(true);}// Routine Description:// - Cancels any mark mode selection state to return to normal mode.// Arguments:// - <none>// Return Value:// - <none>void Selection::_CancelMarkSelection(){CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();SCREEN_INFORMATION& ScreenInfo = gci.GetActiveOutputBuffer();// Hide existing selection, if we have one.if (IsAreaSelected()){HideSelection();}// Turn off selection flag._SetSelectingState(false);IConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow();if (pWindow != nullptr){pWindow->UpdateWindowText();}// restore text cursor_RestoreDataToCursor(ScreenInfo.GetTextBuffer().GetCursor());}// Routine Description:// - If a selection exists, clears it and restores the state.// Will also unblock a blocked write if one exists.// Arguments:// - <none> (Uses global state)// Return Value:// - <none>void Selection::ClearSelection(){ClearSelection(false);}// Routine Description:// - If a selection exists, clears it and restores the state.// - Will only unblock a write if not starting a new selection.// Arguments:// - fStartingNewSelection - If we're going to start another selection right away, we'll keep the write blocked.// Return Value:// - <none>void Selection::ClearSelection(const bool fStartingNewSelection){if (IsInSelectingState()){if (IsMouseInitiatedSelection()){_CancelMouseSelection();}else{_CancelMarkSelection();}LOG_IF_FAILED(ServiceLocator::LocateConsoleWindow()->SignalUia(UIA_Text_TextSelectionChangedEventId));_dwSelectionFlags = 0;// If we were using alternate selection, cancel it here before starting a new area._fUseAlternateSelection = false;// Only unblock if we're not immediately starting a new selection. Otherwise stay blocked.if (!fStartingNewSelection){UnblockWriteConsole(CONSOLE_SELECTING);}}}// Routine Description:// - Colors all text in the given rectangle with the color attribute provided.// - This does not validate whether there is a valid selection right now or not.// It is assumed to already be in a proper selecting state and the given rectangle should be highlighted with the given color unconditionally.// Arguments:// - psrRect - Rectangular area to fill with color// - attr - The color attributes to applyvoid Selection::ColorSelection(const SMALL_RECT& srRect, const TextAttribute attr){CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();// Read selection rectangle, assumed already clipped to buffer.SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer();COORD coordTargetSize;coordTargetSize.X = CalcWindowSizeX(srRect);coordTargetSize.Y = CalcWindowSizeY(srRect);COORD coordTarget;coordTarget.X = srRect.Left;coordTarget.Y = srRect.Top;// Now color the selection a line at a time.for (; (coordTarget.Y < srRect.Top + coordTargetSize.Y); ++coordTarget.Y){const size_t cchWrite = gsl::narrow<size_t>(coordTargetSize.X);try{screenInfo.Write(OutputCellIterator(attr, cchWrite), coordTarget);}CATCH_LOG();}}// Routine Description:// - Given two points in the buffer space, color the selection between the two with the given attribute.// - This will create an internal selection rectangle covering the two points, assume a line selection,// and use the first point as the anchor for the selection (as if the mouse click started at that point)// Arguments:// - coordSelectionStart - Anchor point (start of selection) for the region to be colored// - coordSelectionEnd - Other point referencing the rectangle inscribing the selection area// - attr - Color to apply to region.void Selection::ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute attr){// Make a rectangle for the region as if it were selected by a mouse.// We will use the first one as the "anchor" to represent where the mouse went down.SMALL_RECT srSelection;srSelection.Top = std::min(coordSelectionStart.Y, coordSelectionEnd.Y);srSelection.Bottom = std::max(coordSelectionStart.Y, coordSelectionEnd.Y);srSelection.Left = std::min(coordSelectionStart.X, coordSelectionEnd.X);srSelection.Right = std::max(coordSelectionStart.X, coordSelectionEnd.X);// Extract row-by-row selection rectangles for the selection area.try{const auto rectangles = s_GetSelectionRects(srSelection, coordSelectionStart, true);for (const auto& rect : rectangles){ColorSelection(rect, attr);}}CATCH_LOG();}// Routine Description:// - Enters mark mode selection. Prepares the cursor to move around to select a region and sets up state variables.// Arguments:// - <none>// Return Value:// - <none>void Selection::InitializeMarkSelection(){CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();// clear any existing selection.ClearSelection(true);Scrolling::s_ClearScroll();// set flags_SetSelectingState(true);_dwSelectionFlags = 0;// save old cursor position and make console cursor into selection cursor.SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer();const auto& cursor = screenInfo.GetTextBuffer().GetCursor();_SaveCursorData(cursor);screenInfo.SetCursorInformation(100, TRUE);const COORD coordPosition = cursor.GetPosition();LOG_IF_FAILED(screenInfo.SetCursorPosition(coordPosition, true));// set the cursor position as the anchor position// it will get updated as the cursor moves for mark mode,// but it serves to prepare us for the inevitable start of the selection with Shift+Arrow Key_coordSelectionAnchor = coordPosition;// set frame title textIConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow();if (pWindow != nullptr){pWindow->UpdateWindowText();LOG_IF_FAILED(pWindow->SignalUia(UIA_Text_TextSelectionChangedEventId));}}// Routine Description:// - Resets the current selection and selects a new region from the start to end coordinates// Arguments:// - coordStart - Position to start selection area from// - coordEnd - Position to select up to// Return Value:// - <none>void Selection::SelectNewRegion(const COORD coordStart, const COORD coordEnd){// clear existing selection if applicableClearSelection();// initialize selectionInitializeMouseSelection(coordStart);ShowSelection();// extend selectionExtendSelection(coordEnd);}// Routine Description:// - Creates a new selection region of "all" available text.// The meaning of "all" can vary. If we have input text, then "all" is just the input text.// If we have no input text, "all" is the entire valid screen buffer (output text and the prompt)// Arguments:// - <none> (Uses global state)// Return Value:// - <none>void Selection::SelectAll(){CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();// save the old window positionSCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer();COORD coordWindowOrigin = screenInfo.GetViewport().Origin();// Get existing selection rectangle parametersconst bool fOldSelectionExisted = IsAreaSelected();const SMALL_RECT srOldSelection = _srSelectionRect;const COORD coordOldAnchor = _coordSelectionAnchor;// Attempt to get the boundaries of the current input line.COORD coordInputStart;COORD coordInputEnd;const bool fHasInputArea = s_GetInputLineBoundaries(&coordInputStart, &coordInputEnd);// These variables will be used to specify the new selection area when we're doneCOORD coordNewSelStart;COORD coordNewSelEnd;// Now evaluate conditions and attempt to assign a new selection area.if (!fHasInputArea){// If there's no input area, just select the entire valid text region.GetValidAreaBoundaries(&coordNewSelStart, &coordNewSelEnd);}else{if (!fOldSelectionExisted){// Temporary workaround until MSFT: 614579 is completed.const auto bufferSize = screenInfo.GetBufferSize();COORD coordOneAfterEnd = coordInputEnd;bufferSize.IncrementInBounds(coordOneAfterEnd);if (s_IsWithinBoundaries(screenInfo.GetTextBuffer().GetCursor().GetPosition(), coordInputStart, coordInputEnd)){// If there was no previous selection and the cursor is within the input line, select the input line onlycoordNewSelStart = coordInputStart;coordNewSelEnd = coordInputEnd;}else if (s_IsWithinBoundaries(screenInfo.GetTextBuffer().GetCursor().GetPosition(), coordOneAfterEnd, coordOneAfterEnd)){// Temporary workaround until MSFT: 614579 is completed.// Select only the input line if the cursor is one after the final position of the input line.coordNewSelStart = coordInputStart;coordNewSelEnd = coordInputEnd;}else{// otherwise if the cursor is elsewhere, select everythingGetValidAreaBoundaries(&coordNewSelStart, &coordNewSelEnd);}}else{// This is the complex case. We had an existing selection and we have an input area.// To figure this out, we need the anchor (the point where the selection starts) and its opposite cornerCOORD coordOldAnchorOpposite = Utils::s_GetOppositeCorner(srOldSelection, coordOldAnchor);// Check if both anchor and opposite corner fall within the input lineconst bool fIsOldSelWithinInput =s_IsWithinBoundaries(coordOldAnchor, coordInputStart, coordInputEnd) &&s_IsWithinBoundaries(coordOldAnchorOpposite, coordInputStart, coordInputEnd);// Check if both anchor and opposite corner are exactly the bounds of the input lineconst bool fAllInputSelected =((Utils::s_CompareCoords(coordInputStart, coordOldAnchor) == 0 && Utils::s_CompareCoords(coordInputEnd, coordOldAnchorOpposite) == 0) ||(Utils::s_CompareCoords(coordInputStart, coordOldAnchorOpposite) == 0 && Utils::s_CompareCoords(coordInputEnd, coordOldAnchor) == 0));if (fIsOldSelWithinInput && !fAllInputSelected){// If it's within the input area and the whole input is not selected, then select just the inputcoordNewSelStart = coordInputStart;coordNewSelEnd = coordInputEnd;}else{// Otherwise just select the whole valid areaGetValidAreaBoundaries(&coordNewSelStart, &coordNewSelEnd);}}}// If we're in box selection, adjust end coordinate to end of line and start coordinate to start of line// or it won't be selecting all the text.if (!IsLineSelection()){coordNewSelStart.X = 0;coordNewSelEnd.X = screenInfo.GetBufferSize().RightInclusive();}SelectNewRegion(coordNewSelStart, coordNewSelEnd);// restore the old window positionLOG_IF_FAILED(screenInfo.SetViewportOrigin(true, coordWindowOrigin, true));}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。