terminal/src/cascadia/TerminalCore/Terminal.hpp
James Holderness 7b7dea009c
Consolidate the interfaces for setting VT input modes (#11384)
Instead of having a separate method for setting each mouse and keyboard
mode, this PR consolidates them all into a single method which takes a
mode parameter, and stores the modes in a `til::enumset` rather than
having a separate `bool` for each mode.

This enables us to get rid of a lot of boilerplate code, and makes the
code easier to extend when we want to introduce additional modes in the
future. It'll also makes it easier to read back the state of the various
modes when implementing the `DECRQM` query.

Most of the complication is in the `TerminalInput` class, which had to
be adjusted to work with an `enumset` in place of all the `bool` fields.
For the rest, it was largely a matter of replacing calls to all the old
mode setting methods with the new `SetInputMode` method, and deleting a
bunch of unused code.

One thing worth mentioning is that the `AdaptDispatch` implementation
used to have a `_ShouldPassThroughInputModeChange` method that was
called after every mode change. This code has now been moved up into the
`SetInputMode` implementation in `ConhostInternalGetSet` so it's just
handled in one place. Keeping this out of the dispatch class will also
be beneficial for sharing the implementation with `TerminalDispatch`.

## Validation

The updated interface necessitated some adjustments to the tests in
`AdapterTest` and `MouseInputTest`, but the essential structure of the
tests remains unchanged, and everything still passes.

I've also tested the keyboard and mouse modes in Vttest and confirmed
they still work at least as well as they did before (both conhost and
Windows Terminal), and I tested the alternate scroll mode manually
(conhost only).

Simplifying the `ConGetSet` and `ITerminalApi` is also part of the plan
to de-duplicate the `AdaptDispatch` and `TerminalDispatch`
implementation (#3849).
2021-10-26 21:12:22 +00:00

408 lines
18 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <conattrs.hpp>
#include "../../inc/DefaultSettings.h"
#include "../../buffer/out/textBuffer.hpp"
#include "../../types/inc/sgrStack.hpp"
#include "../../renderer/inc/BlinkingState.hpp"
#include "../../terminal/parser/StateMachine.hpp"
#include "../../terminal/input/terminalInput.hpp"
#include "../../types/inc/Viewport.hpp"
#include "../../types/inc/GlyphWidth.hpp"
#include "../../types/IUiaData.h"
#include "../../cascadia/terminalcore/ITerminalApi.hpp"
#include "../../cascadia/terminalcore/ITerminalInput.hpp"
#include <til/ticket_lock.h>
static constexpr std::wstring_view linkPattern{ LR"(\b(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|$!:,.;]*[A-Za-z0-9+&@#/%=~_|$])" };
static constexpr size_t TaskbarMinProgress{ 10 };
// You have to forward decl the ICoreSettings here, instead of including the header.
// If you include the header, there will be compilation errors with other
// headers that include Terminal.hpp
namespace winrt::Microsoft::Terminal::Core
{
struct ICoreSettings;
struct ICoreAppearance;
}
namespace Microsoft::Terminal::Core
{
class Terminal;
}
// fwdecl unittest classes
#ifdef UNIT_TESTING
namespace TerminalCoreUnitTests
{
class TerminalBufferTests;
class TerminalApiTest;
class ConptyRoundtripTests;
class ScrollTest;
};
#endif
class Microsoft::Terminal::Core::Terminal final :
public Microsoft::Terminal::Core::ITerminalApi,
public Microsoft::Terminal::Core::ITerminalInput,
public Microsoft::Console::Render::IRenderData,
public Microsoft::Console::Types::IUiaData
{
public:
Terminal();
~Terminal(){};
Terminal(const Terminal&) = default;
Terminal(Terminal&&) = default;
Terminal& operator=(const Terminal&) = default;
Terminal& operator=(Terminal&&) = default;
void Create(COORD viewportSize,
SHORT scrollbackLines,
Microsoft::Console::Render::IRenderTarget& renderTarget);
void CreateFromSettings(winrt::Microsoft::Terminal::Core::ICoreSettings settings,
Microsoft::Console::Render::IRenderTarget& renderTarget);
void UpdateSettings(winrt::Microsoft::Terminal::Core::ICoreSettings settings);
void UpdateAppearance(const winrt::Microsoft::Terminal::Core::ICoreAppearance& appearance);
void SetFontInfo(const FontInfo& fontInfo);
// Write goes through the parser
void Write(std::wstring_view stringView);
// WritePastedText goes directly to the connection
void WritePastedText(std::wstring_view stringView);
[[nodiscard]] std::unique_lock<til::ticket_lock> LockForReading();
[[nodiscard]] std::unique_lock<til::ticket_lock> LockForWriting();
short GetBufferHeight() const noexcept;
int ViewStartIndex() const noexcept;
int ViewEndIndex() const noexcept;
#pragma region ITerminalApi
// These methods are defined in TerminalApi.cpp
bool PrintString(std::wstring_view stringView) noexcept override;
bool ExecuteChar(wchar_t wch) noexcept override;
TextAttribute GetTextAttributes() const noexcept override;
void SetTextAttributes(const TextAttribute& attrs) noexcept override;
Microsoft::Console::Types::Viewport GetBufferSize() noexcept override;
bool SetCursorPosition(short x, short y) noexcept override;
COORD GetCursorPosition() noexcept override;
bool SetCursorVisibility(const bool visible) noexcept override;
bool EnableCursorBlinking(const bool enable) noexcept override;
bool CursorLineFeed(const bool withReturn) noexcept override;
bool DeleteCharacter(const size_t count) noexcept override;
bool InsertCharacter(const size_t count) noexcept override;
bool EraseCharacters(const size_t numChars) noexcept override;
bool EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) noexcept override;
bool EraseInDisplay(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) noexcept override;
bool WarningBell() noexcept override;
bool SetWindowTitle(std::wstring_view title) noexcept override;
bool SetColorTableEntry(const size_t tableIndex, const COLORREF color) noexcept override;
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) noexcept override;
bool SetCursorColor(const COLORREF color) noexcept override;
bool SetDefaultForeground(const COLORREF color) noexcept override;
bool SetDefaultBackground(const COLORREF color) noexcept override;
bool SetInputMode(const ::Microsoft::Console::VirtualTerminal::TerminalInput::Mode mode, const bool enabled) noexcept override;
bool SetScreenMode(const bool reverseMode) noexcept override;
bool EnableXtermBracketedPasteMode(const bool enabled) noexcept override;
bool IsXtermBracketedPasteModeEnabled() const noexcept override;
bool IsVtInputEnabled() const noexcept override;
bool CopyToClipboard(std::wstring_view content) noexcept override;
bool AddHyperlink(std::wstring_view uri, std::wstring_view params) noexcept override;
bool EndHyperlink() noexcept override;
bool SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) noexcept override;
bool SetWorkingDirectory(std::wstring_view uri) noexcept override;
std::wstring_view GetWorkingDirectory() noexcept override;
bool PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::VTParameters options) noexcept override;
bool PopGraphicsRendition() noexcept override;
#pragma endregion
#pragma region ITerminalInput
// These methods are defined in Terminal.cpp
bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states, const bool keyDown) override;
bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) override;
bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) override;
[[nodiscard]] HRESULT UserResize(const COORD viewportSize) noexcept override;
void UserScrollViewport(const int viewTop) override;
int GetScrollOffset() noexcept override;
void TrySnapOnInput() override;
bool IsTrackingMouseInput() const noexcept;
std::wstring GetHyperlinkAtPosition(const COORD position);
uint16_t GetHyperlinkIdAtPosition(const COORD position);
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> GetHyperlinkIntervalFromPosition(const COORD position);
#pragma endregion
#pragma region IBaseData(base to IRenderData and IUiaData)
Microsoft::Console::Types::Viewport GetViewport() noexcept override;
COORD GetTextBufferEndPosition() const noexcept override;
const TextBuffer& GetTextBuffer() noexcept override;
const FontInfo& GetFontInfo() noexcept override;
std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& attr) const noexcept override;
void LockConsole() noexcept override;
void UnlockConsole() noexcept override;
#pragma endregion
#pragma region IRenderData
// These methods are defined in TerminalRenderData.cpp
const TextAttribute GetDefaultBrushColors() noexcept override;
COORD GetCursorPosition() const noexcept override;
bool IsCursorVisible() const noexcept override;
bool IsCursorOn() const noexcept override;
ULONG GetCursorHeight() const noexcept override;
ULONG GetCursorPixelWidth() const noexcept override;
CursorType GetCursorStyle() const noexcept override;
COLORREF GetCursorColor() const noexcept override;
bool IsCursorDoubleWidth() const override;
bool IsScreenReversed() const noexcept override;
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
const bool IsGridLineDrawingAllowed() noexcept override;
const std::wstring GetHyperlinkUri(uint16_t id) const noexcept override;
const std::wstring GetHyperlinkCustomId(uint16_t id) const noexcept override;
const std::vector<size_t> GetPatternId(const COORD location) const noexcept override;
#pragma endregion
#pragma region IUiaData
std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept override;
const bool IsSelectionActive() const noexcept override;
const bool IsBlockSelection() const noexcept override;
void ClearSelection() override;
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
const COORD GetSelectionAnchor() const noexcept override;
const COORD GetSelectionEnd() const noexcept override;
const std::wstring_view GetConsoleTitle() const noexcept override;
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute) override;
const bool IsUiaDataInitialized() const noexcept override;
#pragma endregion
void SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept;
void SetWarningBellCallback(std::function<void()> pfn) noexcept;
void SetTitleChangedCallback(std::function<void(std::wstring_view)> pfn) noexcept;
void SetTabColorChangedCallback(std::function<void(const std::optional<til::color>)> pfn) noexcept;
void SetCopyToClipboardCallback(std::function<void(std::wstring_view)> pfn) noexcept;
void SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept;
void SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept;
void SetBackgroundCallback(std::function<void(const til::color)> pfn) noexcept;
void TaskbarProgressChangedCallback(std::function<void()> pfn) noexcept;
void SetCursorOn(const bool isOn);
bool IsCursorBlinkingAllowed() const noexcept;
void UpdatePatternsUnderLock() noexcept;
void ClearPatternTree() noexcept;
const std::optional<til::color> GetTabColor() const noexcept;
til::color GetDefaultBackground() const noexcept;
Microsoft::Console::Render::BlinkingState& GetBlinkingState() const noexcept;
const size_t GetTaskbarState() const noexcept;
const size_t GetTaskbarProgress() const noexcept;
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
enum class SelectionDirection
{
Left,
Right,
Up,
Down
};
enum class SelectionExpansion
{
Char,
Word,
Line, // Mouse selection only!
Viewport,
Buffer
};
void MultiClickSelection(const COORD viewportPos, SelectionExpansion expansionMode);
void SetSelectionAnchor(const COORD position);
void SetSelectionEnd(const COORD position, std::optional<SelectionExpansion> newExpansionMode = std::nullopt);
void SetBlockSelection(const bool isEnabled) noexcept;
void UpdateSelection(SelectionDirection direction, SelectionExpansion mode);
using UpdateSelectionParams = std::optional<std::pair<SelectionDirection, SelectionExpansion>>;
static UpdateSelectionParams ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey);
const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace);
#pragma endregion
private:
std::function<void(std::wstring&)> _pfnWriteInput;
std::function<void()> _pfnWarningBell;
std::function<void(std::wstring_view)> _pfnTitleChanged;
std::function<void(std::wstring_view)> _pfnCopyToClipboard;
// I've specifically put this instance here as it requires
// alignas(std::hardware_destructive_interference_size)
// for best performance.
//
// But we can abuse the fact that the surrounding members rarely change and are huge
// (std::function is like 64 bytes) to create some natural padding without wasting space.
til::ticket_lock _readWriteLock;
#ifndef NDEBUG
DWORD _lastLocker;
#endif
std::function<void(const int, const int, const int)> _pfnScrollPositionChanged;
std::function<void(const til::color)> _pfnBackgroundColorChanged;
std::function<void()> _pfnCursorPositionChanged;
std::function<void(const std::optional<til::color>)> _pfnTabColorChanged;
std::function<void()> _pfnTaskbarProgressChanged;
std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;
std::unique_ptr<::Microsoft::Console::VirtualTerminal::TerminalInput> _terminalInput;
std::optional<std::wstring> _title;
std::wstring _startingTitle;
std::optional<til::color> _tabColor;
std::optional<til::color> _startingTabColor;
// This is still stored as a COLORREF because it interacts with some code in ConTypes
std::array<COLORREF, XTERM_COLOR_TABLE_SIZE> _colorTable;
til::color _defaultFg;
til::color _defaultBg;
CursorType _defaultCursorShape;
bool _screenReversed;
mutable Microsoft::Console::Render::BlinkingState _blinkingState;
bool _snapOnInput;
bool _altGrAliasing;
bool _suppressApplicationTitle;
bool _bracketedPasteMode;
bool _trimBlockSelection;
bool _intenseIsBright;
bool _adjustIndistinguishableColors;
size_t _taskbarState;
size_t _taskbarProgress;
size_t _hyperlinkPatternId;
std::wstring _workingDirectory;
// This default fake font value is only used to check if the font is a raster font.
// Otherwise, the font is changed to a real value with the renderer via TriggerFontChange.
FontInfo _fontInfo{ DEFAULT_FONT_FACE, TMPF_TRUETYPE, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false };
#pragma region Text Selection
// a selection is represented as a range between two COORDs (start and end)
// the pivot is the COORD that remains selected when you extend a selection in any direction
// this is particularly useful when a word selection is extended over its starting point
// see TerminalSelection.cpp for more information
struct SelectionAnchors
{
COORD start;
COORD end;
COORD pivot;
};
std::optional<SelectionAnchors> _selection;
bool _blockSelection;
std::wstring _wordDelimiters;
SelectionExpansion _multiClickSelectionMode;
#pragma endregion
// TODO: These members are not shared by an alt-buffer. They should be
// encapsulated, such that a Terminal can have both a main and alt buffer.
std::unique_ptr<TextBuffer> _buffer;
Microsoft::Console::Types::Viewport _mutableViewport;
SHORT _scrollbackLines;
// _scrollOffset is the number of lines above the viewport that are currently visible
// If _scrollOffset is 0, then the visible region of the buffer is the viewport.
int _scrollOffset;
// TODO this might not be the value we want to store.
// We might want to store the height in the scrollback that's currently visible.
// Think on this some more.
// For example: While looking at the scrollback, we probably want the visible region to "stick"
// to the region they scrolled to. If that were the case, then every time we move _mutableViewport,
// we'd also need to update _offset.
// However, if we just stored it as a _visibleTop, then that point would remain fixed -
// Though if _visibleTop == _mutableViewport.Top, then we'd need to make sure to update
// _visibleTop as well.
// Additionally, maybe some people want to scroll into the history, then have that scroll out from
// underneath them, while others would prefer to anchor it in place.
// Either way, we should make this behavior controlled by a setting.
interval_tree::IntervalTree<til::point, size_t> _patternIntervalTree;
void _InvalidatePatternTree(interval_tree::IntervalTree<til::point, size_t>& tree);
void _InvalidateFromCoords(const COORD start, const COORD end);
// Since virtual keys are non-zero, you assume that this field is empty/invalid if it is.
struct KeyEventCodes
{
WORD VirtualKey;
WORD ScanCode;
};
std::optional<KeyEventCodes> _lastKeyEventCodes;
static WORD _ScanCodeFromVirtualKey(const WORD vkey) noexcept;
static WORD _VirtualKeyFromScanCode(const WORD scanCode) noexcept;
static WORD _VirtualKeyFromCharacter(const wchar_t ch) noexcept;
static wchar_t _CharacterFromKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) noexcept;
void _StoreKeyEvent(const WORD vkey, const WORD scanCode);
WORD _TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept;
int _VisibleStartIndex() const noexcept;
int _VisibleEndIndex() const noexcept;
Microsoft::Console::Types::Viewport _GetMutableViewport() const noexcept;
Microsoft::Console::Types::Viewport _GetVisibleViewport() const noexcept;
void _InitializeColorTable();
void _WriteBuffer(const std::wstring_view& stringView);
void _AdjustCursorPosition(const COORD proposedPosition);
void _NotifyScrollEvent() noexcept;
void _NotifyTerminalCursorPositionChanged() noexcept;
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
std::vector<SMALL_RECT> _GetSelectionRects() const noexcept;
std::pair<COORD, COORD> _PivotSelection(const COORD targetPos, bool& targetStart) const;
std::pair<COORD, COORD> _ExpandSelectionAnchors(std::pair<COORD, COORD> anchors) const;
COORD _ConvertToBufferCell(const COORD viewportPos) const;
void _MoveByChar(SelectionDirection direction, COORD& pos);
void _MoveByWord(SelectionDirection direction, COORD& pos);
void _MoveByViewport(SelectionDirection direction, COORD& pos);
void _MoveByBuffer(SelectionDirection direction, COORD& pos);
#pragma endregion
Microsoft::Console::VirtualTerminal::SgrStack _sgrStack;
void _MakeAdjustedColorArray();
std::array<std::array<COLORREF, 18>, 18> _adjustedForegroundColors;
#ifdef UNIT_TESTING
friend class TerminalCoreUnitTests::TerminalBufferTests;
friend class TerminalCoreUnitTests::TerminalApiTest;
friend class TerminalCoreUnitTests::ConptyRoundtripTests;
friend class TerminalCoreUnitTests::ScrollTest;
#endif
};