mirror of
https://github.com/microsoft/terminal.git
synced 2026-09-25 08:24:54 +00:00
Merge branch 'master' into dev/miniksa/layout_2
This commit is contained in:
@@ -3,6 +3,10 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Microsoft's Windows Terminal Settings Profile Schema'",
|
||||
"definitions": {
|
||||
"KeyChordSegment": {
|
||||
"pattern": "^(?<modifier>(ctrl|alt|shift)\\+?((ctrl|alt|shift)(?<!\\2)\\+?)?((ctrl|alt|shift)(?<!\\2|\\4))?\\+?)?(?<key>[^+\\s]+?)?(?<=[^+\\s])$",
|
||||
"type": "string"
|
||||
},
|
||||
"Color": {
|
||||
"default": "#",
|
||||
"pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",
|
||||
@@ -244,12 +248,18 @@
|
||||
},
|
||||
"keys": {
|
||||
"description": "Defines the key combinations used to call the command.",
|
||||
"items": {
|
||||
"pattern": "^(?<modifier>(ctrl|alt|shift)\\+?((ctrl|alt|shift)(?<!\\2)\\+?)?((ctrl|alt|shift)(?<!\\2|\\4))?\\+?)?(?<key>[^+\\s]+?)?(?<=[^+\\s])$",
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/KeyChordSegment"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/KeyChordSegment"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -54,8 +54,8 @@ object under a root property `"globals"`.
|
||||
This is an array of key chords and shortcuts to invoke various commands.
|
||||
Each command can have more than one key binding.
|
||||
|
||||
NOTE: Key bindings is a subfield of the global settings and
|
||||
key bindings apply to all profiles in the same manner.
|
||||
> 👉 **Note**: Key bindings is a subfield of the global settings and
|
||||
> key bindings apply to all profiles in the same manner.
|
||||
|
||||
For example, here's a sample of the default keybindings:
|
||||
|
||||
@@ -69,9 +69,26 @@ For example, here's a sample of the default keybindings:
|
||||
// etc.
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You can also use a single key chord string as the value of `"keys"`.
|
||||
It will be treated as a chord of length one.
|
||||
This will allow you to simplify the above snippet as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"keybindings":
|
||||
[
|
||||
{ "command": "closePane", "keys": "ctrl+shift+w" },
|
||||
{ "command": "copy", "keys": "ctrl+shift+c" },
|
||||
{ "command": "newTab", "keys": "ctrl+shift+t" },
|
||||
// etc.
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Unbinding keys
|
||||
|
||||
If you ever come across a key binding that you're unhappy with, it's possible to
|
||||
|
||||
@@ -1314,6 +1314,101 @@ TextBuffer::DelimiterClass TextBuffer::_GetDelimiterClass(const std::wstring_vie
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines the line-by-line rectangles based on two COORDs
|
||||
// - expands the rectangles to support wide glyphs
|
||||
// - used for selection rects and UIA bounding rects
|
||||
// Arguments:
|
||||
// - start: a corner of the text region of interest (inclusive)
|
||||
// - end: the other corner of the text region of interest (inclusive)
|
||||
// - blockSelection: when enabled, only get the rectangular text region,
|
||||
// as opposed to the text extending to the left/right
|
||||
// buffer margins
|
||||
// Return Value:
|
||||
// - the delimiter class for the given char
|
||||
const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, bool blockSelection) const
|
||||
{
|
||||
std::vector<SMALL_RECT> textRects;
|
||||
|
||||
const auto bufferSize = GetSize();
|
||||
|
||||
// (0,0) is the top-left of the screen
|
||||
// the physically "higher" coordinate is closer to the top-left
|
||||
// the physically "lower" coordinate is closer to the bottom-right
|
||||
const auto [higherCoord, lowerCoord] = bufferSize.CompareInBounds(start, end) <= 0 ?
|
||||
std::make_tuple(start, end) :
|
||||
std::make_tuple(end, start);
|
||||
|
||||
const auto textRectSize = base::ClampedNumeric<short>(1) + lowerCoord.Y - higherCoord.Y;
|
||||
textRects.reserve(textRectSize);
|
||||
for (auto row = higherCoord.Y; row <= lowerCoord.Y; row++)
|
||||
{
|
||||
SMALL_RECT textRow;
|
||||
|
||||
textRow.Top = row;
|
||||
textRow.Bottom = row;
|
||||
|
||||
if (blockSelection || higherCoord.Y == lowerCoord.Y)
|
||||
{
|
||||
// set the left and right margin to the left-/right-most respectively
|
||||
textRow.Left = std::min(higherCoord.X, lowerCoord.X);
|
||||
textRow.Right = std::max(higherCoord.X, lowerCoord.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
textRow.Left = (row == higherCoord.Y) ? higherCoord.X : bufferSize.Left();
|
||||
textRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive();
|
||||
}
|
||||
|
||||
_ExpandTextRow(textRow);
|
||||
textRects.emplace_back(textRow);
|
||||
}
|
||||
|
||||
return textRects;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Expand the selection row according to include wide glyphs fully
|
||||
// - this is particularly useful for box selections (ALT + selection)
|
||||
// Arguments:
|
||||
// - selectionRow: the selection row to be expanded
|
||||
// Return Value:
|
||||
// - modifies selectionRow's Left and Right values to expand properly
|
||||
void TextBuffer::_ExpandTextRow(SMALL_RECT& textRow) const
|
||||
{
|
||||
const auto bufferSize = GetSize();
|
||||
|
||||
// expand left side of rect
|
||||
COORD targetPoint{ textRow.Left, textRow.Top };
|
||||
if (GetCellDataAt(targetPoint)->DbcsAttr().IsTrailing())
|
||||
{
|
||||
if (targetPoint.X == bufferSize.Left())
|
||||
{
|
||||
bufferSize.IncrementInBounds(targetPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferSize.DecrementInBounds(targetPoint);
|
||||
}
|
||||
textRow.Left = targetPoint.X;
|
||||
}
|
||||
|
||||
// expand right side of rect
|
||||
targetPoint = { textRow.Right, textRow.Bottom };
|
||||
if (GetCellDataAt(targetPoint)->DbcsAttr().IsLeading())
|
||||
{
|
||||
if (targetPoint.X == bufferSize.RightInclusive())
|
||||
{
|
||||
bufferSize.DecrementInBounds(targetPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferSize.IncrementInBounds(targetPoint);
|
||||
}
|
||||
textRow.Right = targetPoint.X;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves the text data from the selected region and presents it in a clipboard-ready format (given little post-processing).
|
||||
// Arguments:
|
||||
|
||||
@@ -135,6 +135,8 @@ public:
|
||||
bool MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const;
|
||||
bool MoveToPreviousWord(COORD& pos, const std::wstring_view wordDelimiters) const;
|
||||
|
||||
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection = false) const;
|
||||
|
||||
class TextAndColor
|
||||
{
|
||||
public:
|
||||
@@ -193,6 +195,8 @@ private:
|
||||
ROW& _GetFirstRow();
|
||||
ROW& _GetPrevRowNoWrap(const ROW& row);
|
||||
|
||||
void _ExpandTextRow(SMALL_RECT& selectionRow) const;
|
||||
|
||||
enum class DelimiterClass
|
||||
{
|
||||
ControlChar,
|
||||
|
||||
@@ -93,9 +93,7 @@
|
||||
roll up our subproject resources. We have to suppress that rule but keep part of its logic, because that rule is
|
||||
where the AppxPackagePayload items are created. -->
|
||||
<PropertyGroup>
|
||||
<!-- Only for MSBuild versions <= 16.4.0 -->
|
||||
<!-- TODO: Change this to hard less than once the 16.4.0 previews fix the bug. -->
|
||||
<WapProjBeforeGenerateAppxManifestDependsOn Condition="$(MSBuildVersion) <= '16.4.0'">
|
||||
<WapProjBeforeGenerateAppxManifestDependsOn>
|
||||
$([MSBuild]::Unescape('$(WapProjBeforeGenerateAppxManifestDependsOn.Replace('_RemoveAllNonWapUWPItems', '_OpenConsoleRemoveAllNonWapUWPItems'))'))
|
||||
</WapProjBeforeGenerateAppxManifestDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(TestArbitraryArgs);
|
||||
TEST_METHOD(TestSplitPaneArgs);
|
||||
|
||||
TEST_METHOD(TestStringOverload);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
@@ -458,4 +460,27 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindingsTests::TestStringOverload()
|
||||
{
|
||||
const std::string bindings0String{ R"([
|
||||
{ "command": "copy", "keys": "ctrl+c" }
|
||||
])" };
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
|
||||
VERIFY_IS_NOT_NULL(appKeyBindings);
|
||||
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
|
||||
appKeyBindings->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_TRUE(realArgs.TrimWhitespace());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ HRESULT _stdcall TerminalStartSelection(void* terminal, COORD cursorPosition, bo
|
||||
terminalPosition.Y /= fontSize.Y;
|
||||
|
||||
publicTerminal->_terminal->SetSelectionAnchor(terminalPosition);
|
||||
publicTerminal->_terminal->SetBoxSelection(altPressed);
|
||||
publicTerminal->_terminal->SetBlockSelection(altPressed);
|
||||
|
||||
publicTerminal->_renderer->TriggerSelection();
|
||||
|
||||
@@ -354,7 +354,7 @@ HRESULT _stdcall TerminalMoveSelection(void* terminal, COORD cursorPosition)
|
||||
terminalPosition.X /= fontSize.X;
|
||||
terminalPosition.Y /= fontSize.Y;
|
||||
|
||||
publicTerminal->_terminal->SetEndSelectionPosition(terminalPosition);
|
||||
publicTerminal->_terminal->SetSelectionEnd(terminalPosition);
|
||||
publicTerminal->_renderer->TriggerSelection();
|
||||
|
||||
return S_OK;
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
instead of APISet forwarders for easier Windows 7 compatibility. -->
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Uiautomationcore.lib;onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
</Project>
|
||||
|
||||
@@ -441,11 +441,13 @@ void winrt::TerminalApp::implementation::AppKeyBindings::LayerJson(const Json::V
|
||||
|
||||
if (keys)
|
||||
{
|
||||
if (!keys.isArray() || keys.size() != 1)
|
||||
const auto validString = keys.isString();
|
||||
const auto validArray = keys.isArray() && keys.size() == 1;
|
||||
if (!validString && !validArray)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const auto keyChordString = winrt::to_hstring(keys[0].asString());
|
||||
const auto keyChordString = keys.isString() ? winrt::to_hstring(keys.asString()) : winrt::to_hstring(keys[0].asString());
|
||||
// Invalid is our placeholder that the action was not parsed.
|
||||
ShortcutAction action = ShortcutAction::Invalid;
|
||||
|
||||
|
||||
@@ -183,6 +183,7 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
|
||||
terminalSettings.CursorShape(_cursorShape);
|
||||
|
||||
// Fill in the remaining properties from the profile
|
||||
terminalSettings.ProfileName(_name);
|
||||
terminalSettings.UseAcrylic(_useAcrylic);
|
||||
terminalSettings.TintOpacity(_acrylicTransparency);
|
||||
|
||||
|
||||
@@ -205,55 +205,55 @@
|
||||
],
|
||||
"keybindings":
|
||||
[
|
||||
{ "command": "closePane", "keys": [ "ctrl+shift+w" ] },
|
||||
{ "command": "closeWindow", "keys": [ "alt+f4" ] },
|
||||
{ "command": "copy", "keys": [ "ctrl+shift+c" ] },
|
||||
{ "command": "copy", "keys": [ "ctrl+insert" ] },
|
||||
{ "command": "decreaseFontSize", "keys": [ "ctrl+-" ] },
|
||||
{ "command": "duplicateTab", "keys": [ "ctrl+shift+d" ] },
|
||||
{ "command": "increaseFontSize", "keys": [ "ctrl+=" ] },
|
||||
{ "command": { "action": "moveFocus", "direction": "down" }, "keys": [ "alt+down" ] },
|
||||
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": [ "alt+left" ] },
|
||||
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": [ "alt+right" ] },
|
||||
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": [ "alt+up" ] },
|
||||
{ "command": "newTab", "keys": [ "ctrl+shift+t" ] },
|
||||
{ "command": { "action": "newTab", "index": 0 }, "keys": ["ctrl+shift+1"] },
|
||||
{ "command": { "action": "newTab", "index": 1 }, "keys": ["ctrl+shift+2"] },
|
||||
{ "command": { "action": "newTab", "index": 2 }, "keys": ["ctrl+shift+3"] },
|
||||
{ "command": { "action": "newTab", "index": 3 }, "keys": ["ctrl+shift+4"] },
|
||||
{ "command": { "action": "newTab", "index": 4 }, "keys": ["ctrl+shift+5"] },
|
||||
{ "command": { "action": "newTab", "index": 5 }, "keys": ["ctrl+shift+6"] },
|
||||
{ "command": { "action": "newTab", "index": 6 }, "keys": ["ctrl+shift+7"] },
|
||||
{ "command": { "action": "newTab", "index": 7 }, "keys": ["ctrl+shift+8"] },
|
||||
{ "command": { "action": "newTab", "index": 8 }, "keys": ["ctrl+shift+9"] },
|
||||
{ "command": "nextTab", "keys": [ "ctrl+tab" ] },
|
||||
{ "command": "openNewTabDropdown", "keys": [ "ctrl+shift+space" ] },
|
||||
{ "command": "openSettings", "keys": [ "ctrl+," ] },
|
||||
{ "command": "paste", "keys": [ "ctrl+shift+v" ] },
|
||||
{ "command": "paste", "keys": [ "shift+insert" ] },
|
||||
{ "command": "prevTab", "keys": [ "ctrl+shift+tab" ] },
|
||||
{ "command": "resetFontSize", "keys": ["ctrl+0"]},
|
||||
{ "command": { "action": "resizePane", "direction": "down" }, "keys": [ "alt+shift+down" ] },
|
||||
{ "command": { "action": "resizePane", "direction": "left" }, "keys": [ "alt+shift+left" ] },
|
||||
{ "command": { "action": "resizePane", "direction": "right" }, "keys": [ "alt+shift+right" ] },
|
||||
{ "command": { "action": "resizePane", "direction": "up" }, "keys": [ "alt+shift+up" ] },
|
||||
{ "command": "scrollDown", "keys": [ "ctrl+shift+down" ] },
|
||||
{ "command": "scrollDownPage", "keys": [ "ctrl+shift+pgdn" ] },
|
||||
{ "command": "scrollUp", "keys": [ "ctrl+shift+up" ] },
|
||||
{ "command": "scrollUpPage", "keys": [ "ctrl+shift+pgup" ] },
|
||||
{ "command": { "action": "splitPane", "split": "horizontal"}, "keys": [ "alt+shift+-" ] },
|
||||
{ "command": { "action": "splitPane", "split": "vertical"}, "keys": [ "alt+shift+plus" ] },
|
||||
{ "command": { "action": "switchToTab", "index": 0 }, "keys": ["ctrl+alt+1"] },
|
||||
{ "command": { "action": "switchToTab", "index": 1 }, "keys": ["ctrl+alt+2"] },
|
||||
{ "command": { "action": "switchToTab", "index": 2 }, "keys": ["ctrl+alt+3"] },
|
||||
{ "command": { "action": "switchToTab", "index": 3 }, "keys": ["ctrl+alt+4"] },
|
||||
{ "command": { "action": "switchToTab", "index": 4 }, "keys": ["ctrl+alt+5"] },
|
||||
{ "command": { "action": "switchToTab", "index": 5 }, "keys": ["ctrl+alt+6"] },
|
||||
{ "command": { "action": "switchToTab", "index": 6 }, "keys": ["ctrl+alt+7"] },
|
||||
{ "command": { "action": "switchToTab", "index": 7 }, "keys": ["ctrl+alt+8"] },
|
||||
{ "command": { "action": "switchToTab", "index": 8 }, "keys": ["ctrl+alt+9"] },
|
||||
{ "command": "find", "keys": [ "ctrl+shift+f" ] },
|
||||
{ "command": "toggleFullscreen", "keys": [ "alt+enter" ] },
|
||||
{ "command": "toggleFullscreen", "keys": [ "f11" ] }
|
||||
{ "command": "closePane", "keys": "ctrl+shift+w" },
|
||||
{ "command": "closeWindow", "keys": "alt+f4" },
|
||||
{ "command": "copy", "keys": "ctrl+shift+c" },
|
||||
{ "command": "copy", "keys": "ctrl+insert" },
|
||||
{ "command": "decreaseFontSize", "keys": "ctrl+-" },
|
||||
{ "command": "duplicateTab", "keys": "ctrl+shift+d" },
|
||||
{ "command": "increaseFontSize", "keys": "ctrl+=" },
|
||||
{ "command": { "action": "moveFocus", "direction": "down" }, "keys": "alt+down" },
|
||||
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+left" },
|
||||
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+right" },
|
||||
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+up" },
|
||||
{ "command": "newTab", "keys": "ctrl+shift+t" },
|
||||
{ "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+shift+1" },
|
||||
{ "command": { "action": "newTab", "index": 1 }, "keys": "ctrl+shift+2" },
|
||||
{ "command": { "action": "newTab", "index": 2 }, "keys": "ctrl+shift+3" },
|
||||
{ "command": { "action": "newTab", "index": 3 }, "keys": "ctrl+shift+4" },
|
||||
{ "command": { "action": "newTab", "index": 4 }, "keys": "ctrl+shift+5" },
|
||||
{ "command": { "action": "newTab", "index": 5 }, "keys": "ctrl+shift+6" },
|
||||
{ "command": { "action": "newTab", "index": 6 }, "keys": "ctrl+shift+7" },
|
||||
{ "command": { "action": "newTab", "index": 7 }, "keys": "ctrl+shift+8" },
|
||||
{ "command": { "action": "newTab", "index": 8 }, "keys": "ctrl+shift+9" },
|
||||
{ "command": "nextTab", "keys": "ctrl+tab" },
|
||||
{ "command": "openNewTabDropdown", "keys": "ctrl+shift+space" },
|
||||
{ "command": "openSettings", "keys": "ctrl+," },
|
||||
{ "command": "paste", "keys": "ctrl+shift+v" },
|
||||
{ "command": "paste", "keys": "shift+insert" },
|
||||
{ "command": "prevTab", "keys": "ctrl+shift+tab" },
|
||||
{ "command": "resetFontSize", "keys": "ctrl+0" },
|
||||
{ "command": { "action": "resizePane", "direction": "down" }, "keys": "alt+shift+down" },
|
||||
{ "command": { "action": "resizePane", "direction": "left" }, "keys": "alt+shift+left" },
|
||||
{ "command": { "action": "resizePane", "direction": "right" }, "keys": "alt+shift+right" },
|
||||
{ "command": { "action": "resizePane", "direction": "up" }, "keys": "alt+shift+up" },
|
||||
{ "command": "scrollDown", "keys": "ctrl+shift+down" },
|
||||
{ "command": "scrollDownPage", "keys": "ctrl+shift+pgdn" },
|
||||
{ "command": "scrollUp", "keys": "ctrl+shift+up" },
|
||||
{ "command": "scrollUpPage", "keys": "ctrl+shift+pgup" },
|
||||
{ "command": { "action": "splitPane", "split": "horizontal"}, "keys": "alt+shift+-" },
|
||||
{ "command": { "action": "splitPane", "split": "vertical"}, "keys": "alt+shift+plus" },
|
||||
{ "command": { "action": "switchToTab", "index": 0 }, "keys": "ctrl+alt+1" },
|
||||
{ "command": { "action": "switchToTab", "index": 1 }, "keys": "ctrl+alt+2" },
|
||||
{ "command": { "action": "switchToTab", "index": 2 }, "keys": "ctrl+alt+3" },
|
||||
{ "command": { "action": "switchToTab", "index": 3 }, "keys": "ctrl+alt+4" },
|
||||
{ "command": { "action": "switchToTab", "index": 4 }, "keys": "ctrl+alt+5" },
|
||||
{ "command": { "action": "switchToTab", "index": 5 }, "keys": "ctrl+alt+6" },
|
||||
{ "command": { "action": "switchToTab", "index": 6 }, "keys": "ctrl+alt+7" },
|
||||
{ "command": { "action": "switchToTab", "index": 7 }, "keys": "ctrl+alt+8" },
|
||||
{ "command": { "action": "switchToTab", "index": 8 }, "keys": "ctrl+alt+9" },
|
||||
{ "command": "find", "keys": "ctrl+shift+f" },
|
||||
{ "command": "toggleFullscreen", "keys": "alt+enter" },
|
||||
{ "command": "toggleFullscreen", "keys": "f11" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
@@ -26,36 +26,36 @@
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
@@ -117,27 +117,48 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="CaseSensitivityButtonLocalizedText.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<data name="SearchBox_CaseSensitivity.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Match Case</value>
|
||||
<comment>The tooltip text for CaseSensitivityButton</comment>
|
||||
<comment>The tooltip text for the case sensitivity button on the search box control.</comment>
|
||||
</data>
|
||||
<data name="CloseButtonLocalizedText.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<data name="SearchBox_Close.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Close</value>
|
||||
<comment>The tooltip text for CloseButton</comment>
|
||||
<comment>The tooltip text for the close button on the search box control.</comment>
|
||||
</data>
|
||||
<data name="GoBackwardButtonLocalizedText.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<data name="SearchBox_SearchBackwards.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Find Up</value>
|
||||
<comment>The tooltip text for GoBackward Button</comment>
|
||||
<comment>The tooltip text for the search backward button.</comment>
|
||||
</data>
|
||||
<data name="GoForwardButtonLocalizedText.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<data name="SearchBox_SearchForwards.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Find Down</value>
|
||||
<comment>The tooltip text for GoForward Button</comment>
|
||||
<comment>The tooltip text for the search forward button.</comment>
|
||||
</data>
|
||||
<data name="TextBoxLocalizedText.PlaceholderText" xml:space="preserve">
|
||||
<data name="SearchBox_TextBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Find...</value>
|
||||
<comment>The placeholder text in the search dialog TextBox</comment>
|
||||
<comment>The placeholder text in the search box control.</comment>
|
||||
</data>
|
||||
<data name="DragFileCaption" xml:space="preserve">
|
||||
<value>Copy path to file</value>
|
||||
<comment>The displayed caption for dragging a file onto a terminal.</comment>
|
||||
</data>
|
||||
</root>
|
||||
<data name="SearchBox_CaseSensitivity.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Case Sensitivity</value>
|
||||
<comment>The name of the case sensitivity button on the search box control for accessibility.</comment>
|
||||
</data>
|
||||
<data name="SearchBox_SearchForwards.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Search Forward</value>
|
||||
<comment>The name of the search forward button for accessibility.</comment>
|
||||
</data>
|
||||
<data name="SearchBox_SearchBackwards.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Search Backward</value>
|
||||
<comment>The name of the search backward button for accessibility.</comment>
|
||||
</data>
|
||||
<data name="SearchBox_TextBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Search Text</value>
|
||||
<comment>The name of the text box on the search box control for accessibility.</comment>
|
||||
</data>
|
||||
<data name="TerminalControl_ControlType" xml:space="preserve">
|
||||
<value>terminal</value>
|
||||
<comment>The type of control that the terminal ahderes to. Used to identify how a user can interact with this kind of control.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -153,29 +153,44 @@
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Style="{ThemeResource SearchBoxBackground}" Padding="5" CornerRadius="0,0,2,2">
|
||||
<TextBox x:Name="TextBox" x:Uid="TextBoxLocalizedText" AutomationProperties.Name="Search Text" CornerRadius="2" Width="160"
|
||||
PlaceholderForeground="{ThemeResource TextBoxPlaceholderTextThemeBrush}" FontSize="15" KeyDown = "TextBoxKeyDown"
|
||||
Margin="5" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<TextBox x:Name="TextBox"
|
||||
x:Uid="SearchBox_TextBox"
|
||||
CornerRadius="2"
|
||||
Width="160"
|
||||
PlaceholderForeground="{ThemeResource TextBoxPlaceholderTextThemeBrush}"
|
||||
FontSize="15"
|
||||
KeyDown="TextBoxKeyDown"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
</TextBox>
|
||||
|
||||
<ToggleButton x:Name="GoBackwardButton" x:Uid="GoBackwardButtonLocalizedText" AutomationProperties.Name="Set Search Backward"
|
||||
HorizontalAlignment="Right" Style="{StaticResource ToggleButtonStyle}"
|
||||
Click="GoBackwardClicked" IsChecked="True">
|
||||
<ToggleButton x:Name="GoBackwardButton"
|
||||
x:Uid="SearchBox_SearchBackwards"
|
||||
HorizontalAlignment="Right"
|
||||
Style="{StaticResource ToggleButtonStyle}"
|
||||
Click="GoBackwardClicked"
|
||||
IsChecked="True">
|
||||
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="" Style="{ThemeResource FontIconStyle}"/>
|
||||
</ToggleButton>
|
||||
<ToggleButton x:Name="GoForwardButton" x:Uid="GoForwardButtonLocalizedText"
|
||||
AutomationProperties.Name="Set Search Forward" Style="{StaticResource ToggleButtonStyle}"
|
||||
<ToggleButton x:Name="GoForwardButton"
|
||||
x:Uid="SearchBox_SearchForwards"
|
||||
Style="{StaticResource ToggleButtonStyle}"
|
||||
Click="GoForwardClicked">
|
||||
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="" Style="{ThemeResource FontIconStyle}"/>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton x:Name="CaseSensitivityButton" x:Uid="CaseSensitivityButtonLocalizedText"
|
||||
AutomationProperties.Name="CaseSensitivity" Style="{StaticResource ToggleButtonStyle}">
|
||||
<ToggleButton x:Name="CaseSensitivityButton"
|
||||
x:Uid="SearchBox_CaseSensitivity"
|
||||
Style="{StaticResource ToggleButtonStyle}">
|
||||
<PathIcon Data="M8.87305 10H7.60156L6.5625 7.25195H2.40625L1.42871 10H0.150391L3.91016 0.197266H5.09961L8.87305 10ZM6.18652 6.21973L4.64844 2.04297C4.59831 1.90625 4.54818 1.6875 4.49805 1.38672H4.4707C4.42513 1.66471 4.37272 1.88346 4.31348 2.04297L2.78906 6.21973H6.18652ZM15.1826 10H14.0615V8.90625H14.0342C13.5465 9.74479 12.8288 10.1641 11.8809 10.1641C11.1836 10.1641 10.6367 9.97949 10.2402 9.61035C9.84831 9.24121 9.65234 8.7513 9.65234 8.14062C9.65234 6.83268 10.4225 6.07161 11.9629 5.85742L14.0615 5.56348C14.0615 4.37402 13.5807 3.7793 12.6191 3.7793C11.776 3.7793 11.015 4.06641 10.3359 4.64062V3.49219C11.0241 3.05469 11.8171 2.83594 12.7148 2.83594C14.36 2.83594 15.1826 3.70638 15.1826 5.44727V10ZM14.0615 6.45898L12.373 6.69141C11.8535 6.76432 11.4616 6.89421 11.1973 7.08105C10.9329 7.26335 10.8008 7.58919 10.8008 8.05859C10.8008 8.40039 10.9215 8.68066 11.1631 8.89941C11.4092 9.11361 11.735 9.2207 12.1406 9.2207C12.6966 9.2207 13.1546 9.02702 13.5146 8.63965C13.8792 8.24772 14.0615 7.75326 14.0615 7.15625V6.45898Z"/>
|
||||
</ToggleButton>
|
||||
|
||||
<Button x:Name="CloseButton" x:Uid="CloseButtonLocalizedText" AutomationProperties.Name="Close"
|
||||
Padding="0" Click="CloseClick" Style="{ ThemeResource ButtonStyle}">
|
||||
<Button x:Name="CloseButton"
|
||||
x:Uid="SearchBox_Close"
|
||||
Padding="0"
|
||||
Click="CloseClick"
|
||||
Style="{ThemeResource ButtonStyle}">
|
||||
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="" FontSize="12"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
@@ -17,35 +17,10 @@ using namespace winrt::Windows::UI::Xaml;
|
||||
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
TSFInputControl::TSFInputControl() :
|
||||
_editContext{ nullptr }
|
||||
_editContext{ nullptr },
|
||||
_inComposition{ false }
|
||||
{
|
||||
_Create();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Creates XAML controls for displaying user input and hooks up CoreTextEditContext handlers
|
||||
// for handling text input from the Text Services Framework.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TSFInputControl::_Create()
|
||||
{
|
||||
// TextBlock for user input form TSF
|
||||
_textBlock = Controls::TextBlock();
|
||||
_textBlock.Visibility(Visibility::Collapsed);
|
||||
_textBlock.IsTextSelectionEnabled(false);
|
||||
_textBlock.TextDecorations(TextDecorations::Underline);
|
||||
|
||||
// Canvas for controlling exact position of the TextBlock
|
||||
_canvas = Windows::UI::Xaml::Controls::Canvas();
|
||||
_canvas.Visibility(Visibility::Collapsed);
|
||||
|
||||
// add the Textblock to the Canvas
|
||||
_canvas.Children().Append(_textBlock);
|
||||
|
||||
// set the content of this control to be the Canvas
|
||||
this->Content(_canvas);
|
||||
InitializeComponent();
|
||||
|
||||
// Create a CoreTextEditingContext for since we are acting like a custom edit control
|
||||
auto manager = Core::CoreTextServicesManager::GetForCurrentView();
|
||||
@@ -138,32 +113,31 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Get the cursor position in text buffer position
|
||||
auto cursorArgs = winrt::make_self<CursorPositionEventArgs>();
|
||||
_CurrentCursorPositionHandlers(*this, *cursorArgs);
|
||||
const COORD cursorPos = { gsl::narrow_cast<SHORT>(cursorArgs->CurrentPosition().X), gsl::narrow_cast<SHORT>(cursorArgs->CurrentPosition().Y) };
|
||||
const COORD cursorPos = { ::base::ClampedNumeric<short>(cursorArgs->CurrentPosition().X), ::base::ClampedNumeric<short>(cursorArgs->CurrentPosition().Y) };
|
||||
|
||||
// Get Font Info as we use this is the pixel size for characters in the display
|
||||
auto fontArgs = winrt::make_self<FontInfoEventArgs>();
|
||||
_CurrentFontInfoHandlers(*this, *fontArgs);
|
||||
|
||||
const float fontWidth = fontArgs->FontSize().Width;
|
||||
const float fontHeight = fontArgs->FontSize().Height;
|
||||
const auto fontWidth = fontArgs->FontSize().Width;
|
||||
const auto fontHeight = fontArgs->FontSize().Height;
|
||||
|
||||
// Convert text buffer cursor position to client coordinate position within the window
|
||||
COORD clientCursorPos;
|
||||
COORD screenCursorPos;
|
||||
THROW_IF_FAILED(ShortMult(cursorPos.X, gsl::narrow<SHORT>(fontWidth), &clientCursorPos.X));
|
||||
THROW_IF_FAILED(ShortMult(cursorPos.Y, gsl::narrow<SHORT>(fontHeight), &clientCursorPos.Y));
|
||||
clientCursorPos.X = ::base::ClampMul(cursorPos.X, ::base::ClampedNumeric<short>(fontWidth));
|
||||
clientCursorPos.Y = ::base::ClampMul(cursorPos.Y, ::base::ClampedNumeric<short>(fontHeight));
|
||||
|
||||
// Convert from client coordinate to screen coordinate by adding window position
|
||||
THROW_IF_FAILED(ShortAdd(clientCursorPos.X, gsl::narrow_cast<SHORT>(windowBounds.X), &screenCursorPos.X));
|
||||
THROW_IF_FAILED(ShortAdd(clientCursorPos.Y, gsl::narrow_cast<SHORT>(windowBounds.Y), &screenCursorPos.Y));
|
||||
COORD screenCursorPos;
|
||||
screenCursorPos.X = ::base::ClampAdd(clientCursorPos.X, ::base::ClampedNumeric<short>(windowBounds.X));
|
||||
screenCursorPos.Y = ::base::ClampAdd(clientCursorPos.Y, ::base::ClampedNumeric<short>(windowBounds.Y));
|
||||
|
||||
// get any offset (margin + tabs, etc..) of the control within the window
|
||||
const auto offsetPoint = this->TransformToVisual(nullptr).TransformPoint(winrt::Windows::Foundation::Point(0, 0));
|
||||
|
||||
// add the margin offsets if any
|
||||
const auto currentMargin = this->Margin();
|
||||
THROW_IF_FAILED(ShortAdd(screenCursorPos.X, gsl::narrow_cast<SHORT>(offsetPoint.X), &screenCursorPos.X));
|
||||
THROW_IF_FAILED(ShortAdd(screenCursorPos.Y, gsl::narrow_cast<SHORT>(offsetPoint.Y), &screenCursorPos.Y));
|
||||
screenCursorPos.X = ::base::ClampAdd(screenCursorPos.X, ::base::ClampedNumeric<short>(offsetPoint.X));
|
||||
screenCursorPos.Y = ::base::ClampAdd(screenCursorPos.Y, ::base::ClampedNumeric<short>(offsetPoint.Y));
|
||||
|
||||
// Get scale factor for view
|
||||
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
|
||||
@@ -177,18 +151,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
request.LayoutBounds().ControlBounds(ScaleRect(controlRect, scaleFactor));
|
||||
|
||||
// position textblock to cursor position
|
||||
_canvas.SetLeft(_textBlock, clientCursorPos.X);
|
||||
_canvas.SetTop(_textBlock, static_cast<double>(clientCursorPos.Y));
|
||||
|
||||
// width is cursor to end of canvas
|
||||
_textBlock.Width(200); // TODO GitHub #3640: Determine proper Width
|
||||
_textBlock.Height(fontHeight);
|
||||
Canvas().SetLeft(TextBlock(), clientCursorPos.X);
|
||||
Canvas().SetTop(TextBlock(), ::base::ClampedNumeric<double>(clientCursorPos.Y));
|
||||
|
||||
TextBlock().Height(fontHeight);
|
||||
// calculate FontSize in pixels from DIPs
|
||||
const double fontSizePx = (fontHeight * 72) / USER_DEFAULT_SCREEN_DPI;
|
||||
_textBlock.FontSize(fontSizePx);
|
||||
TextBlock().FontSize(fontSizePx);
|
||||
|
||||
_textBlock.FontFamily(Media::FontFamily(fontArgs->FontFace()));
|
||||
TextBlock().FontFamily(Media::FontFamily(fontArgs->FontFace()));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -201,8 +172,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TSFInputControl::_compositionStartedHandler(CoreTextEditContext sender, CoreTextCompositionStartedEventArgs const& /*args*/)
|
||||
{
|
||||
_canvas.Visibility(Visibility::Visible);
|
||||
_textBlock.Visibility(Visibility::Visible);
|
||||
_inComposition = true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -215,30 +185,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TSFInputControl::_compositionCompletedHandler(CoreTextEditContext sender, CoreTextCompositionCompletedEventArgs const& /*args*/)
|
||||
{
|
||||
_inComposition = false;
|
||||
|
||||
// only need to do work if the current buffer has text
|
||||
if (!_inputBuffer.empty())
|
||||
{
|
||||
// call event handler with data handled by parent
|
||||
_compositionCompletedHandlers(_inputBuffer);
|
||||
|
||||
// clear the buffer for next round
|
||||
const auto bufferLength = gsl::narrow_cast<int32_t>(_inputBuffer.length());
|
||||
_inputBuffer.clear();
|
||||
_textBlock.Text(L"");
|
||||
|
||||
// indicate text is now 0
|
||||
_editContext.NotifyTextChanged({ 0, bufferLength }, 0, { 0, 0 });
|
||||
|
||||
// hide the controls until composition starts again
|
||||
_canvas.Visibility(Visibility::Collapsed);
|
||||
_textBlock.Visibility(Visibility::Collapsed);
|
||||
_SendAndClearText();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Handler for FocusRemoved event by CoreEditContext responsible
|
||||
// for removing focus for the TSFInputControl control accordingly
|
||||
// when focus was forcibly removed from text input control. (TODO GitHub #3644)
|
||||
// when focus was forcibly removed from text input control.
|
||||
// NOTE: Documentation says application should handle this event
|
||||
// Arguments:
|
||||
// - sender: CoreTextEditContext sending the request. Not used in method.
|
||||
@@ -265,7 +224,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
try
|
||||
{
|
||||
const auto textRequested = _inputBuffer.substr(range.StartCaretPosition, static_cast<size_t>(range.EndCaretPosition) - static_cast<size_t>(range.StartCaretPosition));
|
||||
const auto textEnd = ::base::ClampMin<size_t>(range.EndCaretPosition, _inputBuffer.length());
|
||||
const auto length = ::base::ClampSub<size_t>(textEnd, range.StartCaretPosition);
|
||||
const auto textRequested = _inputBuffer.substr(range.StartCaretPosition, length);
|
||||
|
||||
args.Request().Text(textRequested);
|
||||
}
|
||||
@@ -315,12 +276,22 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
try
|
||||
{
|
||||
Canvas().Visibility(Visibility::Visible);
|
||||
|
||||
const auto length = ::base::ClampSub<size_t>(range.EndCaretPosition, range.StartCaretPosition);
|
||||
_inputBuffer = _inputBuffer.replace(
|
||||
range.StartCaretPosition,
|
||||
static_cast<size_t>(range.EndCaretPosition) - static_cast<size_t>(range.StartCaretPosition),
|
||||
length,
|
||||
text);
|
||||
|
||||
_textBlock.Text(_inputBuffer);
|
||||
TextBlock().Text(_inputBuffer);
|
||||
|
||||
// If we receive tabbed IME input like emoji, kaomojis, and symbols, send it to the terminal immediately.
|
||||
// They aren't composition, so we don't want to wait for the user to start and finish a composition to send the text.
|
||||
if (!_inComposition)
|
||||
{
|
||||
_SendAndClearText();
|
||||
}
|
||||
|
||||
// Notify the TSF that the update succeeded
|
||||
args.Result(CoreTextTextUpdatingResult::Succeeded);
|
||||
@@ -334,6 +305,34 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sends the currently held text in the input buffer to the parent and
|
||||
// clears the input buffer and text block for the next round of input.
|
||||
// Then hides the text block control until the next time text received.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TSFInputControl::_SendAndClearText()
|
||||
{
|
||||
// call event handler with data handled by parent
|
||||
_compositionCompletedHandlers(_inputBuffer);
|
||||
|
||||
// clear the buffer for next round
|
||||
const auto bufferLength = ::base::ClampedNumeric<int32_t>(_inputBuffer.length());
|
||||
_inputBuffer.clear();
|
||||
TextBlock().Text(L"");
|
||||
|
||||
// Leaving focus before NotifyTextChanged seems to guarantee that the next
|
||||
// composition will send us a CompositionStarted event.
|
||||
_editContext.NotifyFocusLeave();
|
||||
_editContext.NotifyTextChanged({ 0, bufferLength }, 0, { 0, 0 });
|
||||
_editContext.NotifyFocusEnter();
|
||||
|
||||
// hide the controls until text input starts again
|
||||
Canvas().Visibility(Visibility::Collapsed);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Handler for FormatUpdating event by CoreEditContext responsible
|
||||
// for handling different format updates for a particular range of text.
|
||||
|
||||
@@ -67,14 +67,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::CompositionStarted_revoker _compositionStartedRevoker;
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::CompositionCompleted_revoker _compositionCompletedRevoker;
|
||||
|
||||
Windows::UI::Xaml::Controls::Canvas _canvas;
|
||||
Windows::UI::Xaml::Controls::TextBlock _textBlock;
|
||||
|
||||
Windows::UI::Text::Core::CoreTextEditContext _editContext;
|
||||
|
||||
std::wstring _inputBuffer;
|
||||
|
||||
void _Create();
|
||||
bool _inComposition;
|
||||
void _SendAndClearText();
|
||||
};
|
||||
}
|
||||
namespace winrt::Microsoft::Terminal::TerminalControl::factory_implementation
|
||||
|
||||
16
src/cascadia/TerminalControl/TSFInputControl.xaml
Normal file
16
src/cascadia/TerminalControl/TSFInputControl.xaml
Normal file
@@ -0,0 +1,16 @@
|
||||
<UserControl
|
||||
x:Class="Microsoft.Terminal.TerminalControl.TSFInputControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="768"
|
||||
d:DesignWidth="1024">
|
||||
|
||||
<Canvas x:Name="Canvas" Visibility="Collapsed">
|
||||
<TextBlock x:Name="TextBlock"
|
||||
IsTextSelectionEnabled="false"
|
||||
TextDecorations="Underline" />
|
||||
</Canvas>
|
||||
</UserControl>
|
||||
@@ -18,6 +18,7 @@
|
||||
using namespace ::Microsoft::Console::Types;
|
||||
using namespace ::Microsoft::Terminal::Core;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Input;
|
||||
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::System;
|
||||
@@ -53,8 +54,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
TermControl::TermControl(Settings::IControlSettings settings, TerminalConnection::ITerminalConnection connection) :
|
||||
_connection{ connection },
|
||||
_initializedTerminal{ false },
|
||||
_root{ nullptr },
|
||||
_swapChainPanel{ nullptr },
|
||||
_settings{ settings },
|
||||
_closing{ false },
|
||||
_isTerminalInitiatedScroll{ false },
|
||||
@@ -69,56 +68,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_lastMouseClick{},
|
||||
_lastMouseClickPos{},
|
||||
_searchBox{ nullptr },
|
||||
_tsfInputControl{ nullptr },
|
||||
_unfocusedClickPos{ std::nullopt },
|
||||
_isClickDragSelection{ false }
|
||||
{
|
||||
_EnsureStaticInitialization();
|
||||
_Create();
|
||||
}
|
||||
|
||||
void TermControl::_Create()
|
||||
{
|
||||
Controls::Grid container;
|
||||
|
||||
Controls::ColumnDefinition contentColumn{};
|
||||
Controls::ColumnDefinition scrollbarColumn{};
|
||||
contentColumn.Width(GridLength{ 1.0, GridUnitType::Star });
|
||||
scrollbarColumn.Width(GridLength{ 1.0, GridUnitType::Auto });
|
||||
|
||||
container.ColumnDefinitions().Append(contentColumn);
|
||||
container.ColumnDefinitions().Append(scrollbarColumn);
|
||||
|
||||
_scrollBar = Controls::Primitives::ScrollBar{};
|
||||
_scrollBar.Orientation(Controls::Orientation::Vertical);
|
||||
_scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator);
|
||||
_scrollBar.HorizontalAlignment(HorizontalAlignment::Right);
|
||||
_scrollBar.VerticalAlignment(VerticalAlignment::Stretch);
|
||||
|
||||
// Initialize the scrollbar with some placeholder values.
|
||||
// The scrollbar will be updated with real values on _Initialize
|
||||
_scrollBar.Maximum(1);
|
||||
_scrollBar.ViewportSize(10);
|
||||
_scrollBar.IsTabStop(false);
|
||||
_scrollBar.SmallChange(1);
|
||||
_scrollBar.LargeChange(4);
|
||||
_scrollBar.Visibility(Visibility::Visible);
|
||||
|
||||
_tsfInputControl = TSFInputControl();
|
||||
_tsfInputControl.CompositionCompleted({ this, &TermControl::_CompositionCompleted });
|
||||
_tsfInputControl.CurrentCursorPosition({ this, &TermControl::_CurrentCursorPositionHandler });
|
||||
_tsfInputControl.CurrentFontInfo({ this, &TermControl::_FontInfoHandler });
|
||||
container.Children().Append(_tsfInputControl);
|
||||
|
||||
// Create the SwapChainPanel that will display our content
|
||||
Controls::SwapChainPanel swapChainPanel;
|
||||
|
||||
_sizeChangedRevoker = swapChainPanel.SizeChanged(winrt::auto_revoke, { this, &TermControl::_SwapChainSizeChanged });
|
||||
_compositionScaleChangedRevoker = swapChainPanel.CompositionScaleChanged(winrt::auto_revoke, { this, &TermControl::_SwapChainScaleChanged });
|
||||
InitializeComponent();
|
||||
|
||||
// Initialize the terminal only once the swapchainpanel is loaded - that
|
||||
// way, we'll be able to query the real pixel size it got on layout
|
||||
_layoutUpdatedRevoker = swapChainPanel.LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
|
||||
_layoutUpdatedRevoker = SwapChainPanel().LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
|
||||
// This event fires every time the layout changes, but it is always the last one to fire
|
||||
// in any layout change chain. That gives us great flexibility in finding the right point
|
||||
// at which to initialize our renderer (and our terminal).
|
||||
@@ -131,74 +89,29 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
});
|
||||
|
||||
container.Children().Append(swapChainPanel);
|
||||
container.Children().Append(_scrollBar);
|
||||
Controls::Grid::SetColumn(swapChainPanel, 0);
|
||||
Controls::Grid::SetColumn(_scrollBar, 1);
|
||||
|
||||
Controls::Grid root{};
|
||||
Controls::Image bgImageLayer{};
|
||||
root.Children().Append(bgImageLayer);
|
||||
root.Children().Append(container);
|
||||
|
||||
_root = root;
|
||||
_bgImageLayer = bgImageLayer;
|
||||
|
||||
_swapChainPanel = swapChainPanel;
|
||||
this->Content(_root);
|
||||
|
||||
_ApplyUISettings();
|
||||
|
||||
// These are important:
|
||||
// 1. When we get tapped, focus us
|
||||
_tappedRevoker = this->Tapped(winrt::auto_revoke, [this](auto&, auto& e) {
|
||||
Focus(FocusState::Pointer);
|
||||
e.Handled(true);
|
||||
});
|
||||
// 2. Make sure we can be focused (why this isn't `Focusable` I'll never know)
|
||||
this->IsTabStop(true);
|
||||
// 3. Actually not sure about this one. Maybe it isn't necessary either.
|
||||
this->AllowFocusOnInteraction(true);
|
||||
|
||||
// DON'T CALL _InitializeTerminal here - wait until the swap chain is loaded to do that.
|
||||
|
||||
// Subscribe to the connection's disconnected event and call our connection closed handlers.
|
||||
_connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) {
|
||||
_ConnectionStateChangedHandlers(*this, nullptr);
|
||||
});
|
||||
|
||||
_root.AllowDrop(true);
|
||||
_root.Drop({ get_weak(), &TermControl::_DragDropHandler });
|
||||
_root.DragOver({ get_weak(), &TermControl::_DragOverHandler });
|
||||
_ApplyUISettings();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Create the SearchBoxControl object, and attach it
|
||||
// to the Terminal Control root
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
// - Loads the search box from the xaml UI and focuses it.
|
||||
void TermControl::CreateSearchBoxControl()
|
||||
{
|
||||
if (!_searchBox)
|
||||
// Lazy load the search box control.
|
||||
if (auto loadedSearchBox{ FindName(L"SearchBox") })
|
||||
{
|
||||
_searchBox = winrt::make_self<SearchBoxControl>();
|
||||
_searchBox->HorizontalAlignment(HorizontalAlignment::Right);
|
||||
_searchBox->VerticalAlignment(VerticalAlignment::Top);
|
||||
// We need to make sure the searchbox does not overlap
|
||||
// with the scroll bar
|
||||
Thickness searchBoxPadding = { 0, 0, _scrollBar.ActualWidth(), 0 };
|
||||
_searchBox->Margin(searchBoxPadding);
|
||||
|
||||
_root.Children().Append(*_searchBox);
|
||||
|
||||
// Event handlers
|
||||
_searchBox->Search({ get_weak(), &TermControl::_Search });
|
||||
_searchBox->Closed({ get_weak(), &TermControl::_CloseSearchBoxControl });
|
||||
if (auto searchBox{ loadedSearchBox.try_as<::winrt::Microsoft::Terminal::TerminalControl::SearchBoxControl>() })
|
||||
{
|
||||
// get at its private implementation
|
||||
_searchBox.copy_from(winrt::get_self<implementation::SearchBoxControl>(searchBox));
|
||||
_searchBox->Visibility(Visibility::Visible);
|
||||
_searchBox->SetFocusOnTextbox();
|
||||
}
|
||||
}
|
||||
|
||||
_searchBox->SetFocusOnTextbox();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -229,7 +142,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
auto lock = _terminal->LockForWriting();
|
||||
if (search.FindNext())
|
||||
{
|
||||
_terminal->SetBoxSelection(false);
|
||||
_terminal->SetBlockSelection(false);
|
||||
search.Select();
|
||||
_renderer->TriggerSelection();
|
||||
}
|
||||
@@ -238,8 +151,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Method Description:
|
||||
// - The handler for the close button or pressing "Esc" when focusing on the
|
||||
// search dialog.
|
||||
// This removes the SearchBoxControl object from the XAML tree,
|
||||
// reset smart pointer and set focus back to Terminal
|
||||
// Arguments:
|
||||
// - IInspectable: not used
|
||||
// - RoutedEventArgs: not used
|
||||
@@ -247,11 +158,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TermControl::_CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& /*sender*/, RoutedEventArgs const& /*args*/)
|
||||
{
|
||||
unsigned int idx;
|
||||
_root.Children().IndexOf(*_searchBox, idx);
|
||||
_root.Children().RemoveAt(idx);
|
||||
|
||||
_searchBox = nullptr;
|
||||
_searchBox->Visibility(Visibility::Collapsed);
|
||||
|
||||
// Set focus back to terminal control
|
||||
this->Focus(FocusState::Programmatic);
|
||||
@@ -270,7 +177,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
// Dispatch a call to the UI thread to apply the new settings to the
|
||||
// terminal.
|
||||
co_await winrt::resume_foreground(_root.Dispatcher());
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
|
||||
// If 'weakThis' is locked, then we can safely work with 'this'
|
||||
if (auto control{ weakThis.get() })
|
||||
@@ -287,8 +194,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Refresh our font with the renderer
|
||||
_UpdateFont();
|
||||
|
||||
const auto width = _swapChainPanel.ActualWidth();
|
||||
const auto height = _swapChainPanel.ActualHeight();
|
||||
const auto width = SwapChainPanel().ActualWidth();
|
||||
const auto height = SwapChainPanel().ActualHeight();
|
||||
if (width != 0 && height != 0)
|
||||
{
|
||||
// If the font size changed, or the _swapchainPanel's size changed
|
||||
@@ -297,11 +204,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
auto lock = _terminal->LockForWriting();
|
||||
_DoResize(width, height);
|
||||
}
|
||||
|
||||
// set TSF Foreground
|
||||
Media::SolidColorBrush foregroundBrush{};
|
||||
foregroundBrush.Color(ColorRefToColor(_settings.DefaultForeground()));
|
||||
_tsfInputControl.Foreground(foregroundBrush);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,12 +228,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
// Apply padding as swapChainPanel's margin
|
||||
auto newMargin = _ParseThicknessFromPadding(_settings.Padding());
|
||||
auto existingMargin = _swapChainPanel.Margin();
|
||||
_swapChainPanel.Margin(newMargin);
|
||||
SwapChainPanel().Margin(newMargin);
|
||||
|
||||
// Initialize our font information.
|
||||
const auto fontFace = _settings.FontFace();
|
||||
const short fontHeight = gsl::narrow<short>(_settings.FontSize());
|
||||
const short fontHeight = gsl::narrow_cast<short>(_settings.FontSize());
|
||||
// The font width doesn't terribly matter, we'll only be using the
|
||||
// height to look it up
|
||||
// The other params here also largely don't matter.
|
||||
@@ -344,8 +245,24 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// set TSF Foreground
|
||||
Media::SolidColorBrush foregroundBrush{};
|
||||
foregroundBrush.Color(ColorRefToColor(_settings.DefaultForeground()));
|
||||
_tsfInputControl.Foreground(foregroundBrush);
|
||||
_tsfInputControl.Margin(newMargin);
|
||||
TSFInputControl().Foreground(foregroundBrush);
|
||||
TSFInputControl().Margin(newMargin);
|
||||
|
||||
// Apply settings for scrollbar
|
||||
if (_settings.ScrollState() == ScrollbarState::Hidden)
|
||||
{
|
||||
// In the scenario where the user has turned off the OS setting to automatically hide scollbars, the
|
||||
// Terminal scrollbar would still be visible; so, we need to set the control's visibility accordingly to
|
||||
// achieve the intended effect.
|
||||
ScrollBar().IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::None);
|
||||
ScrollBar().Visibility(Visibility::Collapsed);
|
||||
}
|
||||
else // (default or Visible)
|
||||
{
|
||||
// Default behavior
|
||||
ScrollBar().IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator);
|
||||
ScrollBar().Visibility(Visibility::Visible);
|
||||
}
|
||||
|
||||
// set number of rows to scroll at a time
|
||||
_rowsToScroll = _settings.RowsToScroll();
|
||||
@@ -371,7 +288,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
// See if we've already got an acrylic background brush
|
||||
// to avoid the flicker when setting up a new one
|
||||
auto acrylic = _root.Background().try_as<Media::AcrylicBrush>();
|
||||
auto acrylic = RootGrid().Background().try_as<Media::AcrylicBrush>();
|
||||
|
||||
// Instantiate a brush if there's not already one there
|
||||
if (acrylic == nullptr)
|
||||
@@ -396,15 +313,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
acrylic.TintOpacity(_settings.TintOpacity());
|
||||
|
||||
// Apply brush to control if it's not already there
|
||||
if (_root.Background() != acrylic)
|
||||
if (RootGrid().Background() != acrylic)
|
||||
{
|
||||
_root.Background(acrylic);
|
||||
RootGrid().Background(acrylic);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Media::SolidColorBrush solidColor{};
|
||||
_root.Background(solidColor);
|
||||
RootGrid().Background(solidColor);
|
||||
}
|
||||
|
||||
if (!_settings.BackgroundImage().empty())
|
||||
@@ -414,7 +331,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Check if the image brush is already pointing to the image
|
||||
// in the modified settings; if it isn't (or isn't there),
|
||||
// set a new image source for the brush
|
||||
auto imageSource = _bgImageLayer.Source().try_as<Media::Imaging::BitmapImage>();
|
||||
auto imageSource = BackgroundImage().Source().try_as<Media::Imaging::BitmapImage>();
|
||||
|
||||
if (imageSource == nullptr ||
|
||||
imageSource.UriSource() == nullptr ||
|
||||
@@ -425,18 +342,18 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// may well be both large and somewhere out on the
|
||||
// internet.
|
||||
Media::Imaging::BitmapImage image(imageUri);
|
||||
_bgImageLayer.Source(image);
|
||||
BackgroundImage().Source(image);
|
||||
}
|
||||
|
||||
// Apply stretch, opacity and alignment settings
|
||||
_bgImageLayer.Stretch(_settings.BackgroundImageStretchMode());
|
||||
_bgImageLayer.Opacity(_settings.BackgroundImageOpacity());
|
||||
_bgImageLayer.HorizontalAlignment(_settings.BackgroundImageHorizontalAlignment());
|
||||
_bgImageLayer.VerticalAlignment(_settings.BackgroundImageVerticalAlignment());
|
||||
BackgroundImage().Stretch(_settings.BackgroundImageStretchMode());
|
||||
BackgroundImage().Opacity(_settings.BackgroundImageOpacity());
|
||||
BackgroundImage().HorizontalAlignment(_settings.BackgroundImageHorizontalAlignment());
|
||||
BackgroundImage().VerticalAlignment(_settings.BackgroundImageVerticalAlignment());
|
||||
}
|
||||
else
|
||||
{
|
||||
_bgImageLayer.Source(nullptr);
|
||||
BackgroundImage().Source(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,7 +367,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
co_await winrt::resume_foreground(_root.Dispatcher());
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
@@ -464,12 +381,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
bgColor.B = B;
|
||||
bgColor.A = 255;
|
||||
|
||||
if (auto acrylic = _root.Background().try_as<Media::AcrylicBrush>())
|
||||
if (auto acrylic = RootGrid().Background().try_as<Media::AcrylicBrush>())
|
||||
{
|
||||
acrylic.FallbackColor(bgColor);
|
||||
acrylic.TintColor(bgColor);
|
||||
}
|
||||
else if (auto solidColor = _root.Background().try_as<Media::SolidColorBrush>())
|
||||
else if (auto solidColor = RootGrid().Background().try_as<Media::SolidColorBrush>())
|
||||
{
|
||||
solidColor.Color(bgColor);
|
||||
}
|
||||
@@ -522,9 +439,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return _actualFont;
|
||||
}
|
||||
|
||||
const Windows::UI::Xaml::Thickness TermControl::GetPadding() const
|
||||
const Windows::UI::Xaml::Thickness TermControl::GetPadding()
|
||||
{
|
||||
return _swapChainPanel.Margin();
|
||||
return SwapChainPanel().Margin();
|
||||
}
|
||||
|
||||
TerminalConnection::ConnectionState TermControl::ConnectionState() const
|
||||
@@ -542,13 +459,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
auto chain = _renderEngine->GetSwapChain();
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
co_await winrt::resume_foreground(_swapChainPanel.Dispatcher());
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
|
||||
// If 'weakThis' is locked, then we can safely work with 'this'
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
auto lock = _terminal->LockForWriting();
|
||||
auto nativePanel = _swapChainPanel.as<ISwapChainPanelNative>();
|
||||
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
|
||||
nativePanel->SetSwapChain(chain.Get());
|
||||
}
|
||||
}
|
||||
@@ -558,12 +475,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
auto chain = _renderEngine->GetSwapChain();
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
co_await winrt::resume_foreground(_swapChainPanel.Dispatcher());
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
_terminal->LockConsole();
|
||||
auto nativePanel = _swapChainPanel.as<ISwapChainPanelNative>();
|
||||
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
|
||||
nativePanel->SetSwapChain(chain.Get());
|
||||
_terminal->UnlockConsole();
|
||||
}
|
||||
@@ -576,8 +493,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto windowWidth = _swapChainPanel.ActualWidth(); // Width() and Height() are NaN?
|
||||
const auto windowHeight = _swapChainPanel.ActualHeight();
|
||||
const auto windowWidth = SwapChainPanel().ActualWidth(); // Width() and Height() are NaN?
|
||||
const auto windowHeight = SwapChainPanel().ActualHeight();
|
||||
|
||||
if (windowWidth == 0 || windowHeight == 0)
|
||||
{
|
||||
@@ -672,62 +589,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
auto bottom = _terminal->GetViewport().BottomExclusive();
|
||||
auto bufferHeight = bottom;
|
||||
|
||||
const auto originalMaximum = _scrollBar.Maximum();
|
||||
const auto originalMinimum = _scrollBar.Minimum();
|
||||
const auto originalValue = _scrollBar.Value();
|
||||
const auto originalViewportSize = _scrollBar.ViewportSize();
|
||||
|
||||
_scrollBar.Maximum(bufferHeight - bufferHeight);
|
||||
_scrollBar.Minimum(0);
|
||||
_scrollBar.Value(0);
|
||||
_scrollBar.ViewportSize(bufferHeight);
|
||||
_scrollBar.ValueChanged({ this, &TermControl::_ScrollbarChangeHandler });
|
||||
_scrollBar.PointerPressed({ this, &TermControl::_CapturePointer });
|
||||
_scrollBar.PointerReleased({ this, &TermControl::_ReleasePointerCapture });
|
||||
|
||||
// Apply settings for scrollbar
|
||||
if (_settings.ScrollState() == ScrollbarState::Visible)
|
||||
{
|
||||
_scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator);
|
||||
}
|
||||
else if (_settings.ScrollState() == ScrollbarState::Hidden)
|
||||
{
|
||||
_scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::None);
|
||||
|
||||
// In the scenario where the user has turned off the OS setting to automatically hide scollbars, the
|
||||
// Terminal scrollbar would still be visible; so, we need to set the control's visibility accordingly to
|
||||
// achieve the intended effect.
|
||||
_scrollBar.Visibility(Visibility::Collapsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default behavior
|
||||
_scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator);
|
||||
}
|
||||
|
||||
_root.PointerWheelChanged({ this, &TermControl::_MouseWheelHandler });
|
||||
|
||||
// These need to be hooked up to the SwapChainPanel because we don't want the scrollbar to respond to pointer events (GitHub #950)
|
||||
_swapChainPanel.PointerPressed({ this, &TermControl::_PointerPressedHandler });
|
||||
_swapChainPanel.PointerMoved({ this, &TermControl::_PointerMovedHandler });
|
||||
_swapChainPanel.PointerReleased({ this, &TermControl::_PointerReleasedHandler });
|
||||
ScrollBar().Maximum(bufferHeight - bufferHeight);
|
||||
ScrollBar().Minimum(0);
|
||||
ScrollBar().Value(0);
|
||||
ScrollBar().ViewportSize(bufferHeight);
|
||||
|
||||
localPointerToThread->EnablePainting();
|
||||
|
||||
// No matter what order these guys are in, The KeyDown's will fire
|
||||
// before the CharacterReceived, so we can't easily get characters
|
||||
// first, then fallback to getting keys from vkeys.
|
||||
// TODO: This apparently handles keys and characters correctly, though
|
||||
// I'd keep an eye on it, and test more.
|
||||
// I presume that the characters that aren't translated by terminalInput
|
||||
// just end up getting ignored, and the rest of the input comes
|
||||
// through CharacterReceived.
|
||||
// I don't believe there's a difference between KeyDown and
|
||||
// PreviewKeyDown for our purposes
|
||||
// These two handlers _must_ be on this, not _root.
|
||||
this->PreviewKeyDown({ this, &TermControl::_KeyDownHandler });
|
||||
this->CharacterReceived({ this, &TermControl::_CharacterHandler });
|
||||
|
||||
auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
|
||||
_terminal->SetTitleChangedCallback(pfnTitleChanged);
|
||||
|
||||
@@ -760,10 +628,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// import value from WinUser (convert from milli-seconds to micro-seconds)
|
||||
_multiClickTimer = GetDoubleClickTime() * 1000;
|
||||
|
||||
_gotFocusRevoker = this->GotFocus(winrt::auto_revoke, { this, &TermControl::_GotFocusHandler });
|
||||
_lostFocusRevoker = this->LostFocus(winrt::auto_revoke, { this, &TermControl::_LostFocusHandler });
|
||||
|
||||
// Focus the control here. If we do it up above (in _Create_), then the
|
||||
// Focus the control here. If we do it during control initialization, then
|
||||
// focus won't actually get passed to us. I believe this is because
|
||||
// we're not technically a part of the UI tree yet, so focusing us
|
||||
// becomes a no-op.
|
||||
@@ -892,6 +757,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return handled;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - handle a tap event by taking focus
|
||||
// Arguments:
|
||||
// - sender: the XAML element responding to the tap event
|
||||
// - args: event data
|
||||
void TermControl::_TappedHandler(const IInspectable& /*sender*/, const TappedRoutedEventArgs& e)
|
||||
{
|
||||
Focus(FocusState::Pointer);
|
||||
e.Handled(true);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - handle a mouse click event. Begin selection process.
|
||||
// Arguments:
|
||||
@@ -903,7 +779,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_CapturePointer(sender, args);
|
||||
|
||||
const auto ptr = args.Pointer();
|
||||
const auto point = args.GetCurrentPoint(_root);
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
|
||||
if (!_focused)
|
||||
{
|
||||
@@ -941,7 +817,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
const auto terminalPosition = _GetTerminalPosition(cursorPosition);
|
||||
|
||||
// handle ALT key
|
||||
_terminal->SetBoxSelection(altEnabled);
|
||||
_terminal->SetBlockSelection(altEnabled);
|
||||
|
||||
auto clickCount = _NumberOfClicks(cursorPosition, point.Timestamp());
|
||||
|
||||
@@ -952,17 +828,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
if (multiClickMapper == 3)
|
||||
{
|
||||
_terminal->TripleClickSelection(terminalPosition);
|
||||
_terminal->MultiClickSelection(terminalPosition, ::Terminal::SelectionExpansionMode::Line);
|
||||
}
|
||||
else if (multiClickMapper == 2)
|
||||
{
|
||||
_terminal->DoubleClickSelection(terminalPosition);
|
||||
_terminal->MultiClickSelection(terminalPosition, ::Terminal::SelectionExpansionMode::Word);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (shiftEnabled && _terminal->IsSelectionActive())
|
||||
{
|
||||
_terminal->SetEndSelectionPosition(terminalPosition);
|
||||
_terminal->SetSelectionEnd(terminalPosition, ::Terminal::SelectionExpansionMode::Cell);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1007,7 +883,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
Input::PointerRoutedEventArgs const& args)
|
||||
{
|
||||
const auto ptr = args.Pointer();
|
||||
const auto point = args.GetCurrentPoint(_root);
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
|
||||
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen)
|
||||
{
|
||||
@@ -1027,8 +903,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
const auto cursorPosition = point.Position();
|
||||
_SetEndSelectionPointAtCursor(cursorPosition);
|
||||
|
||||
const double cursorBelowBottomDist = cursorPosition.Y - _swapChainPanel.Margin().Top - _swapChainPanel.ActualHeight();
|
||||
const double cursorAboveTopDist = -1 * cursorPosition.Y + _swapChainPanel.Margin().Top;
|
||||
const double cursorBelowBottomDist = cursorPosition.Y - SwapChainPanel().Margin().Top - SwapChainPanel().ActualHeight();
|
||||
const double cursorAboveTopDist = -1 * cursorPosition.Y + SwapChainPanel().Margin().Top;
|
||||
|
||||
constexpr double MinAutoScrollDist = 2.0; // Arbitrary value
|
||||
double newAutoScrollVelocity = 0.0;
|
||||
@@ -1071,10 +947,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// panning down)
|
||||
const float numRows = -1.0f * (dy / fontHeight);
|
||||
|
||||
const auto currentOffset = ::base::ClampedNumeric<double>(_scrollBar.Value());
|
||||
const auto currentOffset = ::base::ClampedNumeric<double>(ScrollBar().Value());
|
||||
const auto newValue = numRows + currentOffset;
|
||||
|
||||
_scrollBar.Value(newValue);
|
||||
ScrollBar().Value(newValue);
|
||||
|
||||
// Use this point as our new scroll anchor.
|
||||
_touchAnchor = newTouchPoint;
|
||||
@@ -1135,7 +1011,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Input::PointerRoutedEventArgs const& args)
|
||||
{
|
||||
const auto point = args.GetCurrentPoint(_root);
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
const auto delta = point.Properties().MouseWheelDelta();
|
||||
|
||||
// Get the state of the Ctrl & Shift keys
|
||||
@@ -1173,7 +1049,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
try
|
||||
{
|
||||
auto acrylicBrush = _root.Background().as<Media::AcrylicBrush>();
|
||||
auto acrylicBrush = RootGrid().Background().as<Media::AcrylicBrush>();
|
||||
acrylicBrush.TintOpacity(acrylicBrush.TintOpacity() + effectiveDelta);
|
||||
}
|
||||
CATCH_LOG();
|
||||
@@ -1216,7 +1092,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - mouseDelta: the mouse wheel delta that triggered this event.
|
||||
void TermControl::_MouseScrollHandler(const double mouseDelta, Windows::UI::Input::PointerPoint const& pointerPoint)
|
||||
{
|
||||
const auto currentOffset = _scrollBar.Value();
|
||||
const auto currentOffset = ScrollBar().Value();
|
||||
|
||||
// negative = down, positive = up
|
||||
// However, for us, the signs are flipped.
|
||||
@@ -1229,7 +1105,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
// The scroll bar's ValueChanged handler will actually move the viewport
|
||||
// for us.
|
||||
_scrollBar.Value(newValue);
|
||||
ScrollBar().Value(newValue);
|
||||
|
||||
if (_terminal->IsSelectionActive() && pointerPoint.Properties().IsLeftButtonPressed())
|
||||
{
|
||||
@@ -1360,7 +1236,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
static constexpr double microSecPerSec = 1000000.0;
|
||||
const double deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(timeNow - _lastAutoScrollUpdateTime.value()).count() / microSecPerSec;
|
||||
_scrollBar.Value(_scrollBar.Value() + _autoScrollVelocity * deltaTime);
|
||||
ScrollBar().Value(ScrollBar().Value() + _autoScrollVelocity * deltaTime);
|
||||
|
||||
if (_autoScrollingPointerPoint.has_value())
|
||||
{
|
||||
@@ -1384,16 +1260,25 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_focused = true;
|
||||
|
||||
// If the searchbox is focused, we don't want TSFInputControl to think
|
||||
// it has focus so it doesn't intercept IME input. We also don't want the
|
||||
// terminal's cursor to start blinking. So, we'll just return quickly here.
|
||||
if (_searchBox && _searchBox->ContainsFocus())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_uiaEngine.get())
|
||||
{
|
||||
THROW_IF_FAILED(_uiaEngine->Enable());
|
||||
}
|
||||
|
||||
if (_tsfInputControl != nullptr)
|
||||
if (TSFInputControl() != nullptr)
|
||||
{
|
||||
_tsfInputControl.NotifyFocusEnter();
|
||||
TSFInputControl().NotifyFocusEnter();
|
||||
}
|
||||
|
||||
if (_cursorTimer.has_value())
|
||||
@@ -1423,9 +1308,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
THROW_IF_FAILED(_uiaEngine->Disable());
|
||||
}
|
||||
|
||||
if (_tsfInputControl != nullptr)
|
||||
if (TSFInputControl() != nullptr)
|
||||
{
|
||||
_tsfInputControl.NotifyFocusLeave();
|
||||
TSFInputControl().NotifyFocusLeave();
|
||||
}
|
||||
|
||||
if (_cursorTimer.has_value())
|
||||
@@ -1489,7 +1374,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
auto lock = _terminal->LockForWriting();
|
||||
|
||||
const int newDpi = static_cast<int>(static_cast<double>(USER_DEFAULT_SCREEN_DPI) * _swapChainPanel.CompositionScaleX());
|
||||
const int newDpi = static_cast<int>(static_cast<double>(USER_DEFAULT_SCREEN_DPI) * SwapChainPanel().CompositionScaleX());
|
||||
|
||||
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
|
||||
// actually fail. We need a way to gracefully fallback.
|
||||
@@ -1508,7 +1393,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
try
|
||||
{
|
||||
// Make sure we have a non-zero font size
|
||||
const auto newSize = std::max(gsl::narrow<short>(fontSize), static_cast<short>(1));
|
||||
const auto newSize = std::max<short>(gsl::narrow_cast<short>(fontSize), 1);
|
||||
const auto fontFace = _settings.FontFace();
|
||||
_actualFont = { fontFace, 0, 10, { 0, newSize }, CP_UTF8, false };
|
||||
_desiredFont = { _actualFont };
|
||||
@@ -1520,7 +1405,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// problems (like what happens when you change the font size while the
|
||||
// window is maximized?)
|
||||
auto lock = _terminal->LockForWriting();
|
||||
_DoResize(_swapChainPanel.ActualWidth(), _swapChainPanel.ActualHeight());
|
||||
_DoResize(SwapChainPanel().ActualWidth(), SwapChainPanel().ActualHeight());
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
@@ -1589,7 +1474,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
terminalPosition.X = std::clamp<short>(terminalPosition.X, 0, lastVisibleCol);
|
||||
|
||||
// save location (for rendering) + render
|
||||
_terminal->SetEndSelectionPosition(terminalPosition);
|
||||
_terminal->SetSelectionEnd(terminalPosition);
|
||||
_renderer->TriggerSelection();
|
||||
}
|
||||
|
||||
@@ -1697,7 +1582,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
co_await winrt::resume_foreground(_scrollBar.Dispatcher());
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
|
||||
// Even if we weren't closed/closing few lines above, we might be
|
||||
// while waiting for this block of code to be dispatched.
|
||||
@@ -1707,7 +1592,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
if (!_closing.load())
|
||||
{
|
||||
// Update our scrollbar
|
||||
_ScrollbarUpdater(_scrollBar, viewTop, viewHeight, bufferSize);
|
||||
_ScrollbarUpdater(ScrollBar(), viewTop, viewHeight, bufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1721,6 +1606,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return hstr;
|
||||
}
|
||||
|
||||
hstring TermControl::GetProfileName() const
|
||||
{
|
||||
return _settings.ProfileName();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Given a copy-able selection, get the selected text from the buffer and send it to the
|
||||
// Windows Clipboard (CascadiaWin32:main.cpp).
|
||||
@@ -1793,7 +1683,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_connection.TerminalOutput(_connectionOutputEventToken);
|
||||
_connectionStateChangedRevoker.revoke();
|
||||
|
||||
_tsfInputControl.Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
|
||||
TSFInputControl().Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
|
||||
|
||||
if (auto localConnection{ std::exchange(_connection, nullptr) })
|
||||
{
|
||||
@@ -1825,7 +1715,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - viewTop: the viewTop to scroll to
|
||||
void TermControl::ScrollViewport(int viewTop)
|
||||
{
|
||||
_scrollBar.Value(viewTop);
|
||||
ScrollBar().Value(viewTop);
|
||||
}
|
||||
|
||||
int TermControl::GetScrollOffset()
|
||||
@@ -1860,7 +1750,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
// Initialize our font information.
|
||||
const auto fontFace = settings.FontFace();
|
||||
const short fontHeight = gsl::narrow<short>(settings.FontSize());
|
||||
const short fontHeight = gsl::narrow_cast<short>(settings.FontSize());
|
||||
// The font width doesn't terribly matter, we'll only be using the
|
||||
// height to look it up
|
||||
// The other params here also largely don't matter.
|
||||
@@ -1894,8 +1784,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// the Terminal). At runtime, this is fine, as we'll transform
|
||||
// everything by our scaling, so it'll work out. However, right now we
|
||||
// need to get the exact pixel count.
|
||||
const float fFontWidth = gsl::narrow<float>(fontSize.X * scale);
|
||||
const float fFontHeight = gsl::narrow<float>(fontSize.Y * scale);
|
||||
const float fFontWidth = gsl::narrow_cast<float>(fontSize.X * scale);
|
||||
const float fFontHeight = gsl::narrow_cast<float>(fontSize.Y * scale);
|
||||
|
||||
// UWP XAML scrollbars aren't guaranteed to be the same size as the
|
||||
// ComCtl scrollbars, but it's certainly close enough.
|
||||
@@ -1940,7 +1830,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Return Value:
|
||||
// - The minimum size that this terminal control can be resized to and still
|
||||
// have a visible character.
|
||||
winrt::Windows::Foundation::Size TermControl::MinimumSize() const
|
||||
winrt::Windows::Foundation::Size TermControl::MinimumSize()
|
||||
{
|
||||
const auto fontSize = _actualFont.GetSize();
|
||||
double width = fontSize.X;
|
||||
@@ -1948,11 +1838,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Reserve additional space if scrollbar is intended to be visible
|
||||
if (_settings.ScrollState() == ScrollbarState::Visible)
|
||||
{
|
||||
width += _scrollBar.ActualWidth();
|
||||
width += ScrollBar().ActualWidth();
|
||||
}
|
||||
|
||||
// Account for the size of any padding
|
||||
const auto padding = _swapChainPanel.Margin();
|
||||
const auto padding = SwapChainPanel().Margin();
|
||||
width += padding.Left + padding.Right;
|
||||
height += padding.Top + padding.Bottom;
|
||||
|
||||
@@ -1967,19 +1857,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - dimension: a dimension (width or height) to be snapped
|
||||
// Return Value:
|
||||
// - A dimension that would be aligned to the character grid.
|
||||
float TermControl::SnapDimensionToGrid(const bool widthOrHeight, const float dimension) const
|
||||
float TermControl::SnapDimensionToGrid(const bool widthOrHeight, const float dimension)
|
||||
{
|
||||
const auto fontSize = _actualFont.GetSize();
|
||||
const auto fontDimension = widthOrHeight ? fontSize.X : fontSize.Y;
|
||||
|
||||
const auto padding = _swapChainPanel.Margin();
|
||||
const auto padding = SwapChainPanel().Margin();
|
||||
auto nonTerminalArea = gsl::narrow_cast<float>(widthOrHeight ?
|
||||
padding.Left + padding.Right :
|
||||
padding.Top + padding.Bottom);
|
||||
|
||||
if (widthOrHeight && _settings.ScrollState() == ScrollbarState::Visible)
|
||||
{
|
||||
nonTerminalArea += gsl::narrow_cast<float>(_scrollBar.ActualWidth());
|
||||
nonTerminalArea += gsl::narrow_cast<float>(ScrollBar().ActualWidth());
|
||||
}
|
||||
|
||||
const auto gridSize = dimension - nonTerminalArea;
|
||||
@@ -2104,8 +1994,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
// Exclude padding from cursor position calculation
|
||||
COORD terminalPosition = {
|
||||
static_cast<SHORT>(cursorPosition.X - _swapChainPanel.Margin().Left),
|
||||
static_cast<SHORT>(cursorPosition.Y - _swapChainPanel.Margin().Top)
|
||||
static_cast<SHORT>(cursorPosition.X - SwapChainPanel().Margin().Left),
|
||||
static_cast<SHORT>(cursorPosition.Y - SwapChainPanel().Margin().Top)
|
||||
};
|
||||
|
||||
const auto fontSize = _actualFont.GetSize();
|
||||
@@ -2141,7 +2031,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_CurrentCursorPositionHandler(const IInspectable& /*sender*/, const CursorPositionEventArgs& eventArgs)
|
||||
{
|
||||
const COORD cursorPos = _terminal->GetCursorPosition();
|
||||
Windows::Foundation::Point p = { gsl::narrow<float>(cursorPos.X), gsl::narrow<float>(cursorPos.Y) };
|
||||
Windows::Foundation::Point p = { gsl::narrow_cast<float>(cursorPos.X), gsl::narrow_cast<float>(cursorPos.Y) };
|
||||
eventArgs.CurrentPosition(p);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,13 +61,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
winrt::fire_and_forget UpdateSettings(Settings::IControlSettings newSettings);
|
||||
|
||||
hstring Title();
|
||||
hstring GetProfileName() const;
|
||||
|
||||
bool CopySelectionToClipboard(bool trimTrailingWhitespace);
|
||||
void PasteTextFromClipboard();
|
||||
void Close();
|
||||
Windows::Foundation::Size CharacterDimensions() const;
|
||||
Windows::Foundation::Size MinimumSize() const;
|
||||
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension) const;
|
||||
Windows::Foundation::Size MinimumSize();
|
||||
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension);
|
||||
|
||||
void ScrollViewport(int viewTop);
|
||||
int GetScrollOffset();
|
||||
@@ -85,7 +86,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
|
||||
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
|
||||
const FontInfo GetActualFont() const;
|
||||
const Windows::UI::Xaml::Thickness GetPadding() const;
|
||||
const Windows::UI::Xaml::Thickness GetPadding();
|
||||
|
||||
TerminalConnection::ConnectionState ConnectionState() const;
|
||||
|
||||
@@ -105,20 +106,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
friend struct TermControlT<TermControl>; // friend our parent so it can bind private event handlers
|
||||
TerminalConnection::ITerminalConnection _connection;
|
||||
bool _initializedTerminal;
|
||||
|
||||
Windows::UI::Xaml::Controls::Grid _root;
|
||||
Windows::UI::Xaml::Controls::Image _bgImageLayer;
|
||||
Windows::UI::Xaml::Controls::SwapChainPanel _swapChainPanel;
|
||||
Windows::UI::Xaml::Controls::Primitives::ScrollBar _scrollBar;
|
||||
|
||||
winrt::com_ptr<SearchBoxControl> _searchBox;
|
||||
|
||||
TSFInputControl _tsfInputControl;
|
||||
|
||||
event_token _connectionOutputEventToken;
|
||||
TermControl::Tapped_revoker _tappedRevoker;
|
||||
TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker;
|
||||
|
||||
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
|
||||
@@ -165,21 +159,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
std::optional<winrt::Windows::Foundation::Point> _unfocusedClickPos;
|
||||
bool _isClickDragSelection;
|
||||
|
||||
// Event revokers -- we need to deregister ourselves before we die,
|
||||
// lest we get callbacks afterwards.
|
||||
winrt::Windows::UI::Xaml::Controls::Control::SizeChanged_revoker _sizeChangedRevoker;
|
||||
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::CompositionScaleChanged_revoker _compositionScaleChangedRevoker;
|
||||
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
|
||||
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
|
||||
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
|
||||
|
||||
void _Create();
|
||||
void _ApplyUISettings();
|
||||
void _InitializeBackgroundBrush();
|
||||
winrt::fire_and_forget _BackgroundColorChanged(const uint32_t color);
|
||||
bool _InitializeTerminal();
|
||||
void _UpdateFont(const bool initialUpdate = false);
|
||||
void _SetFontSize(int fontSize);
|
||||
void _TappedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs const& e);
|
||||
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
void _CharacterHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::CharacterReceivedRoutedEventArgs const& e);
|
||||
void _PointerPressedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
|
||||
81
src/cascadia/TerminalControl/TermControl.xaml
Normal file
81
src/cascadia/TerminalControl/TermControl.xaml
Normal file
@@ -0,0 +1,81 @@
|
||||
<UserControl
|
||||
x:Class="Microsoft.Terminal.TerminalControl.TermControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.Terminal.TerminalControl"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
d:DesignHeight="768"
|
||||
d:DesignWidth="1024"
|
||||
TabNavigation="Cycle"
|
||||
IsTabStop="True"
|
||||
AllowFocusOnInteraction="True"
|
||||
AllowDrop="True"
|
||||
Drop="_DragDropHandler"
|
||||
DragOver="_DragOverHandler"
|
||||
Tapped="_TappedHandler"
|
||||
PointerWheelChanged="_MouseWheelHandler"
|
||||
PreviewKeyDown="_KeyDownHandler"
|
||||
CharacterReceived="_CharacterHandler"
|
||||
GotFocus="_GotFocusHandler"
|
||||
LostFocus="_LostFocusHandler">
|
||||
<!--
|
||||
TODO GH#4031: We've investigated whether we should be using KeyDown or PreviewKeyDown
|
||||
but not necessarily come to a consensus. It's possible that moving input to the SwapChainPanel
|
||||
will solve a bunch of the search input issues we found last time we switched.
|
||||
-->
|
||||
|
||||
<Grid x:Name="RootGrid">
|
||||
<Image x:Name="BackgroundImage" />
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Column="0">
|
||||
<SwapChainPanel x:Name="SwapChainPanel"
|
||||
SizeChanged="_SwapChainSizeChanged"
|
||||
CompositionScaleChanged="_SwapChainScaleChanged"
|
||||
PointerPressed="_PointerPressedHandler"
|
||||
PointerMoved="_PointerMovedHandler"
|
||||
PointerReleased="_PointerReleasedHandler" />
|
||||
|
||||
<!-- Putting this in a grid w/ the SwapChainPanel
|
||||
ensures that it's always aligned w/ the scrollbar -->
|
||||
<local:SearchBoxControl x:Name="SearchBox"
|
||||
x:Load="False"
|
||||
Visibility="Collapsed"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Search="_Search"
|
||||
Closed="_CloseSearchBoxControl" />
|
||||
</Grid>
|
||||
|
||||
<ScrollBar Grid.Column="1"
|
||||
x:Name="ScrollBar"
|
||||
Orientation="Vertical"
|
||||
IndicatorMode="MouseIndicator"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Stretch"
|
||||
Maximum="1"
|
||||
ViewportSize="10"
|
||||
IsTabStop="False"
|
||||
SmallChange="1"
|
||||
LargeChange="4"
|
||||
ValueChanged="_ScrollbarChangeHandler"
|
||||
PointerPressed="_CapturePointer"
|
||||
PointerReleased="_ReleasePointerCapture" />
|
||||
</Grid>
|
||||
|
||||
<local:TSFInputControl x:Name="TSFInputControl"
|
||||
CompositionCompleted="_CompositionCompleted"
|
||||
CurrentCursorPosition="_CurrentCursorPositionHandler"
|
||||
CurrentFontInfo="_FontInfoHandler" />
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include <UIAutomationCore.h>
|
||||
#include <LibraryResources.h>
|
||||
#include "TermControlAutomationPeer.h"
|
||||
#include "TermControl.h"
|
||||
#include "TermControlAutomationPeer.g.cpp"
|
||||
@@ -77,7 +78,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
});
|
||||
}
|
||||
|
||||
winrt::hstring TermControlAutomationPeer::GetClassNameCore() const
|
||||
hstring TermControlAutomationPeer::GetClassNameCore() const
|
||||
{
|
||||
return L"TermControl";
|
||||
}
|
||||
@@ -87,13 +88,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return AutomationControlType::Text;
|
||||
}
|
||||
|
||||
winrt::hstring TermControlAutomationPeer::GetLocalizedControlTypeCore() const
|
||||
hstring TermControlAutomationPeer::GetLocalizedControlTypeCore() const
|
||||
{
|
||||
// TODO GitHub #2142: Localize string
|
||||
return L"TerminalControl";
|
||||
return RS_(L"TerminalControl_ControlType");
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IInspectable TermControlAutomationPeer::GetPatternCore(PatternInterface patternInterface) const
|
||||
Windows::Foundation::IInspectable TermControlAutomationPeer::GetPatternCore(PatternInterface patternInterface) const
|
||||
{
|
||||
switch (patternInterface)
|
||||
{
|
||||
@@ -105,15 +105,41 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
}
|
||||
|
||||
AutomationOrientation TermControlAutomationPeer::GetOrientationCore() const
|
||||
{
|
||||
return AutomationOrientation::Vertical;
|
||||
}
|
||||
|
||||
hstring TermControlAutomationPeer::GetNameCore() const
|
||||
{
|
||||
// fallback to title if profile name is empty
|
||||
auto profileName = _termControl->GetProfileName();
|
||||
if (profileName.empty())
|
||||
{
|
||||
return _termControl->Title();
|
||||
}
|
||||
return profileName;
|
||||
}
|
||||
|
||||
hstring TermControlAutomationPeer::GetHelpTextCore() const
|
||||
{
|
||||
return _termControl->Title();
|
||||
}
|
||||
|
||||
AutomationLiveSetting TermControlAutomationPeer::GetLiveSettingCore() const
|
||||
{
|
||||
return AutomationLiveSetting::Polite;
|
||||
}
|
||||
|
||||
#pragma region ITextProvider
|
||||
winrt::com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetSelection()
|
||||
com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetSelection()
|
||||
{
|
||||
SAFEARRAY* pReturnVal;
|
||||
THROW_IF_FAILED(_uiaProvider->GetSelection(&pReturnVal));
|
||||
return WrapArrayOfTextRangeProviders(pReturnVal);
|
||||
}
|
||||
|
||||
winrt::com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetVisibleRanges()
|
||||
com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetVisibleRanges()
|
||||
{
|
||||
SAFEARRAY* pReturnVal;
|
||||
THROW_IF_FAILED(_uiaProvider->GetVisibleRanges(&pReturnVal));
|
||||
@@ -152,7 +178,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return xutr.as<XamlAutomation::ITextRangeProvider>();
|
||||
}
|
||||
|
||||
Windows::UI::Xaml::Automation::SupportedTextSelection TermControlAutomationPeer::SupportedTextSelection()
|
||||
XamlAutomation::SupportedTextSelection TermControlAutomationPeer::SupportedTextSelection()
|
||||
{
|
||||
UIA::SupportedTextSelection returnVal;
|
||||
THROW_IF_FAILED(_uiaProvider->get_SupportedTextSelection(&returnVal));
|
||||
@@ -171,10 +197,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
auto rect = GetBoundingRectangle();
|
||||
return {
|
||||
gsl::narrow<LONG>(rect.X),
|
||||
gsl::narrow<LONG>(rect.Y),
|
||||
gsl::narrow<LONG>(rect.X + rect.Width),
|
||||
gsl::narrow<LONG>(rect.Y + rect.Height)
|
||||
gsl::narrow_cast<LONG>(rect.X),
|
||||
gsl::narrow_cast<LONG>(rect.Y),
|
||||
gsl::narrow_cast<LONG>(rect.X + rect.Width),
|
||||
gsl::narrow_cast<LONG>(rect.Y + rect.Height)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -214,7 +240,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - SAFEARRAY of UIA::UiaTextRange (ITextRangeProviders)
|
||||
// Return Value:
|
||||
// - com_array of Xaml Wrapped UiaTextRange (ITextRangeProviders)
|
||||
winrt::com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges)
|
||||
com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges)
|
||||
{
|
||||
// transfer ownership of UiaTextRanges to this new vector
|
||||
auto providers = SafeArrayToOwningVector<::Microsoft::Terminal::TermControlUiaTextRange>(textRanges);
|
||||
@@ -225,11 +251,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
auto parentProvider = this->ProviderFromPeer(*this);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
auto xutr = winrt::make_self<XamlUiaTextRange>(providers[i].detach(), parentProvider);
|
||||
auto xutr = make_self<XamlUiaTextRange>(providers[i].detach(), parentProvider);
|
||||
vec.emplace_back(xutr.as<XamlAutomation::ITextRangeProvider>());
|
||||
}
|
||||
|
||||
winrt::com_array<XamlAutomation::ITextRangeProvider> result{ vec };
|
||||
com_array<XamlAutomation::ITextRangeProvider> result{ vec };
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -39,12 +39,18 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
::Microsoft::Console::Types::IControlAccessibilityInfo
|
||||
{
|
||||
public:
|
||||
TermControlAutomationPeer(winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl* owner);
|
||||
TermControlAutomationPeer(Microsoft::Terminal::TerminalControl::implementation::TermControl* owner);
|
||||
|
||||
winrt::hstring GetClassNameCore() const;
|
||||
winrt::Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const;
|
||||
winrt::hstring GetLocalizedControlTypeCore() const;
|
||||
winrt::Windows::Foundation::IInspectable GetPatternCore(winrt::Windows::UI::Xaml::Automation::Peers::PatternInterface patternInterface) const;
|
||||
#pragma region FrameworkElementAutomationPeer
|
||||
hstring GetClassNameCore() const;
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const;
|
||||
hstring GetLocalizedControlTypeCore() const;
|
||||
Windows::Foundation::IInspectable GetPatternCore(Windows::UI::Xaml::Automation::Peers::PatternInterface patternInterface) const;
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationOrientation GetOrientationCore() const;
|
||||
hstring GetNameCore() const;
|
||||
hstring GetHelpTextCore() const;
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationLiveSetting GetLiveSettingCore() const;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IUiaEventDispatcher
|
||||
void SignalSelectionChanged() override;
|
||||
@@ -55,8 +61,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
#pragma region ITextProvider Pattern
|
||||
Windows::UI::Xaml::Automation::Provider::ITextRangeProvider RangeFromPoint(Windows::Foundation::Point screenLocation);
|
||||
Windows::UI::Xaml::Automation::Provider::ITextRangeProvider RangeFromChild(Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple childElement);
|
||||
winrt::com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> GetVisibleRanges();
|
||||
winrt::com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> GetSelection();
|
||||
com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> GetVisibleRanges();
|
||||
com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> GetSelection();
|
||||
Windows::UI::Xaml::Automation::SupportedTextSelection SupportedTextSelection();
|
||||
Windows::UI::Xaml::Automation::Provider::ITextRangeProvider DocumentRange();
|
||||
#pragma endregion
|
||||
|
||||
@@ -37,13 +37,13 @@
|
||||
<DependentUpon>SearchBoxControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TermControl.h">
|
||||
<DependentUpon>TermControl.idl</DependentUpon>
|
||||
<DependentUpon>TermControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TermControlAutomationPeer.h">
|
||||
<DependentUpon>TermControlAutomationPeer.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TSFInputControl.h">
|
||||
<DependentUpon>TSFInputControl.idl</DependentUpon>
|
||||
<DependentUpon>TSFInputControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="XamlUiaTextRange.h" />
|
||||
</ItemGroup>
|
||||
@@ -56,10 +56,10 @@
|
||||
<DependentUpon>SearchBoxControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TermControl.cpp">
|
||||
<DependentUpon>TermControl.idl</DependentUpon>
|
||||
<DependentUpon>TermControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TSFInputControl.cpp">
|
||||
<DependentUpon>TSFInputControl.idl</DependentUpon>
|
||||
<DependentUpon>TSFInputControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
<ClCompile Include="TermControlAutomationPeer.cpp">
|
||||
@@ -71,9 +71,13 @@
|
||||
<Midl Include="SearchBoxControl.idl">
|
||||
<DependentUpon>SearchBoxControl.xaml</DependentUpon>
|
||||
</Midl>
|
||||
<Midl Include="TermControl.idl" />
|
||||
<Midl Include="TermControl.idl">
|
||||
<DependentUpon>TermControl.xaml</DependentUpon>
|
||||
</Midl>
|
||||
<Midl Include="TermControlAutomationPeer.idl" />
|
||||
<Midl Include="TSFInputControl.idl" />
|
||||
<Midl Include="TSFInputControl.idl">
|
||||
<DependentUpon>TSFInputControl.xaml</DependentUpon>
|
||||
</Midl>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
@@ -109,6 +113,12 @@
|
||||
<Page Include="SearchBoxControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="TermControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="TSFInputControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<ClCompile Include="TermControlUiaProvider.cpp" />
|
||||
<ClCompile Include="UiaTextRange.cpp" />
|
||||
<ClCompile Include="SearchBoxControl.cpp" />
|
||||
<ClCompile Include="init.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
@@ -26,12 +27,12 @@
|
||||
<ClInclude Include="XamlUiaTextRange.h" />
|
||||
<ClInclude Include="TermControlUiaProvider.hpp" />
|
||||
<ClInclude Include="UiaTextRange.hpp" />
|
||||
<ClCompile Include="SearchBoxControl.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="TermControl.idl" />
|
||||
<Midl Include="TermControlAutomationPeer.idl" />
|
||||
<Midl Include="SearchBoxControl.idl" />
|
||||
<Midl Include="TSFInputControl.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="TerminalControl.def" />
|
||||
@@ -43,4 +44,9 @@
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Resources/Resources.language-en.resw">
|
||||
<Filter>Resources</Filter>
|
||||
</PRIResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -44,12 +44,10 @@ Terminal::Terminal() :
|
||||
_pfnWriteInput{ nullptr },
|
||||
_scrollOffset{ 0 },
|
||||
_snapOnInput{ true },
|
||||
_boxSelection{ false },
|
||||
_selectionActive{ false },
|
||||
_blockSelection{ false },
|
||||
_selection{ std::nullopt },
|
||||
_allowSingleCharSelection{ true },
|
||||
_copyOnSelect{ false },
|
||||
_selectionAnchor{ 0, 0 },
|
||||
_endSelectionPosition{ 0, 0 }
|
||||
_copyOnSelect{ false }
|
||||
{
|
||||
auto dispatch = std::make_unique<TerminalDispatch>(*this);
|
||||
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
||||
@@ -454,6 +452,22 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView)
|
||||
// With well behaving shells during normal operation this safeguard should normally not be encountered.
|
||||
proposedCursorPosition.X = 0;
|
||||
proposedCursorPosition.Y++;
|
||||
|
||||
// Try the character again.
|
||||
i--;
|
||||
|
||||
// Mark the line we're currently on as wrapped
|
||||
|
||||
// TODO: GH#780 - This should really be a _deferred_ newline. If
|
||||
// the next character to come in is a newline or a cursor
|
||||
// movement or anything, then we should _not_ wrap this line
|
||||
// here.
|
||||
//
|
||||
// This is more WriteCharsLegacy2ElectricBoogaloo work. I'm
|
||||
// leaving it like this for now - it'll break for lines that
|
||||
// _exactly_ wrap, but we can't re-wrap lines now anyways, so it
|
||||
// doesn't matter.
|
||||
_buffer->GetRowByOffset(cursorPosBefore.Y).GetCharRow().SetWrapForced(true);
|
||||
}
|
||||
|
||||
_AdjustCursorPosition(proposedCursorPosition);
|
||||
|
||||
@@ -142,7 +142,7 @@ public:
|
||||
void ClearSelection() override;
|
||||
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
|
||||
const COORD GetSelectionAnchor() const noexcept override;
|
||||
const COORD GetEndSelectionPosition() const noexcept override;
|
||||
const COORD GetSelectionEnd() const noexcept override;
|
||||
const std::wstring GetConsoleTitle() const noexcept override;
|
||||
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute) override;
|
||||
#pragma endregion
|
||||
@@ -157,12 +157,17 @@ public:
|
||||
|
||||
#pragma region TextSelection
|
||||
// These methods are defined in TerminalSelection.cpp
|
||||
enum class SelectionExpansionMode
|
||||
{
|
||||
Cell,
|
||||
Word,
|
||||
Line
|
||||
};
|
||||
const bool IsCopyOnSelectActive() const noexcept;
|
||||
void DoubleClickSelection(const COORD position);
|
||||
void TripleClickSelection(const COORD position);
|
||||
void MultiClickSelection(const COORD viewportPos, SelectionExpansionMode expansionMode);
|
||||
void SetSelectionAnchor(const COORD position);
|
||||
void SetEndSelectionPosition(const COORD position);
|
||||
void SetBoxSelection(const bool isEnabled) noexcept;
|
||||
void SetSelectionEnd(const COORD position, std::optional<SelectionExpansionMode> newExpansionMode = std::nullopt);
|
||||
void SetBlockSelection(const bool isEnabled) noexcept;
|
||||
|
||||
const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace) const;
|
||||
#pragma endregion
|
||||
@@ -187,19 +192,20 @@ private:
|
||||
bool _suppressApplicationTitle;
|
||||
|
||||
#pragma region Text Selection
|
||||
enum class SelectionExpansionMode
|
||||
// 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
|
||||
{
|
||||
Cell,
|
||||
Word,
|
||||
Line
|
||||
COORD start;
|
||||
COORD end;
|
||||
COORD pivot;
|
||||
};
|
||||
COORD _selectionAnchor;
|
||||
COORD _endSelectionPosition;
|
||||
bool _boxSelection;
|
||||
bool _selectionActive;
|
||||
std::optional<SelectionAnchors> _selection;
|
||||
bool _blockSelection;
|
||||
bool _allowSingleCharSelection;
|
||||
bool _copyOnSelect;
|
||||
SHORT _selectionVerticalOffset;
|
||||
std::wstring _wordDelimiters;
|
||||
SelectionExpansionMode _multiClickSelectionMode;
|
||||
#pragma endregion
|
||||
@@ -248,15 +254,10 @@ private:
|
||||
#pragma region TextSelection
|
||||
// These methods are defined in TerminalSelection.cpp
|
||||
std::vector<SMALL_RECT> _GetSelectionRects() const noexcept;
|
||||
SHORT _ExpandWideGlyphSelectionLeft(const SHORT xPos, const SHORT yPos) const;
|
||||
SHORT _ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPos) const;
|
||||
COORD _ExpandDoubleClickSelectionLeft(const COORD position) const;
|
||||
COORD _ExpandDoubleClickSelectionRight(const COORD position) const;
|
||||
std::pair<COORD, COORD> _PivotSelection(const COORD targetPos) const;
|
||||
std::pair<COORD, COORD> _ExpandSelectionAnchors(std::pair<COORD, COORD> anchors) const;
|
||||
COORD _ConvertToBufferCell(const COORD viewportPos) const;
|
||||
const bool _IsSingleCellSelection() const noexcept;
|
||||
std::tuple<COORD, COORD> _PreprocessSelectionCoords() const;
|
||||
SMALL_RECT _GetSelectionRow(const SHORT row, const COORD higherCoord, const COORD lowerCoord) const;
|
||||
void _ExpandSelectionRow(SMALL_RECT& selectionRow) const;
|
||||
#pragma endregion
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
|
||||
@@ -7,6 +7,38 @@
|
||||
|
||||
using namespace Microsoft::Terminal::Core;
|
||||
|
||||
/* Selection Pivot Description:
|
||||
* The pivot helps properly update the selection when a user moves a selection over itself
|
||||
* See SelectionTest::DoubleClickDrag_Left for an example of the functionality mentioned here
|
||||
* As an example, consider the following scenario...
|
||||
* 1. Perform a word selection (double-click) on a word
|
||||
*
|
||||
* |-position where we double-clicked
|
||||
* _|_
|
||||
* |word|
|
||||
* |--|
|
||||
* start & pivot-| |-end
|
||||
*
|
||||
* 2. Drag your mouse down a line
|
||||
*
|
||||
*
|
||||
* start & pivot-|__________
|
||||
* __|word_______|
|
||||
* |______|
|
||||
* |
|
||||
* |-end & mouse position
|
||||
*
|
||||
* 3. Drag your mouse up two lines
|
||||
*
|
||||
* |-start & mouse position
|
||||
* |________
|
||||
* ____| ______|
|
||||
* |___w|ord
|
||||
* |-end & pivot
|
||||
*
|
||||
* The pivot never moves until a new selection is created. It ensures that that cell will always be selected.
|
||||
*/
|
||||
|
||||
// Method Description:
|
||||
// - Helper to determine the selected region of the buffer. Used for rendering.
|
||||
// Return Value:
|
||||
@@ -22,91 +54,12 @@ std::vector<SMALL_RECT> Terminal::_GetSelectionRects() const noexcept
|
||||
|
||||
try
|
||||
{
|
||||
// NOTE: (0,0) is the top-left of the screen
|
||||
// the physically "higher" coordinate is closer to the top-left
|
||||
// the physically "lower" coordinate is closer to the bottom-right
|
||||
const auto [higherCoord, lowerCoord] = _PreprocessSelectionCoords();
|
||||
|
||||
SHORT selectionRectSize;
|
||||
THROW_IF_FAILED(ShortSub(lowerCoord.Y, higherCoord.Y, &selectionRectSize));
|
||||
THROW_IF_FAILED(ShortAdd(selectionRectSize, 1, &selectionRectSize));
|
||||
|
||||
std::vector<SMALL_RECT> selectionArea;
|
||||
selectionArea.reserve(selectionRectSize);
|
||||
for (auto row = higherCoord.Y; row <= lowerCoord.Y; row++)
|
||||
{
|
||||
SMALL_RECT selectionRow = _GetSelectionRow(row, higherCoord, lowerCoord);
|
||||
_ExpandSelectionRow(selectionRow);
|
||||
selectionArea.emplace_back(selectionRow);
|
||||
}
|
||||
result.swap(selectionArea);
|
||||
return _buffer->GetTextRects(_selection->start, _selection->end, _blockSelection);
|
||||
}
|
||||
CATCH_LOG();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - convert selection anchors to proper coordinates for rendering
|
||||
// NOTE: (0,0) is top-left so vertical comparison is inverted
|
||||
// Arguments:
|
||||
// - None
|
||||
// Return Value:
|
||||
// - tuple.first: the physically "higher" coordinate (closer to the top-left)
|
||||
// - tuple.second: the physically "lower" coordinate (closer to the bottom-right)
|
||||
std::tuple<COORD, COORD> Terminal::_PreprocessSelectionCoords() const
|
||||
{
|
||||
// create these new anchors for comparison and rendering
|
||||
COORD selectionAnchorWithOffset{ _selectionAnchor };
|
||||
COORD endSelectionPositionWithOffset{ _endSelectionPosition };
|
||||
|
||||
// Add anchor offset here to update properly on new buffer output
|
||||
THROW_IF_FAILED(ShortAdd(selectionAnchorWithOffset.Y, _selectionVerticalOffset, &selectionAnchorWithOffset.Y));
|
||||
THROW_IF_FAILED(ShortAdd(endSelectionPositionWithOffset.Y, _selectionVerticalOffset, &endSelectionPositionWithOffset.Y));
|
||||
|
||||
// clamp anchors to be within buffer bounds
|
||||
const auto bufferSize = _buffer->GetSize();
|
||||
bufferSize.Clamp(selectionAnchorWithOffset);
|
||||
bufferSize.Clamp(endSelectionPositionWithOffset);
|
||||
|
||||
// NOTE: (0,0) is top-left so vertical comparison is inverted
|
||||
// CompareInBounds returns whether A is to the left of (rv<0), equal to (rv==0), or to the right of (rv>0) B.
|
||||
// Here, we want the "left"most coordinate to be the one "higher" on the screen. The other gets the dubious honor of
|
||||
// being the "lower."
|
||||
return bufferSize.CompareInBounds(selectionAnchorWithOffset, endSelectionPositionWithOffset) <= 0 ?
|
||||
std::make_tuple(selectionAnchorWithOffset, endSelectionPositionWithOffset) :
|
||||
std::make_tuple(endSelectionPositionWithOffset, selectionAnchorWithOffset);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - constructs the selection row at the given row
|
||||
// NOTE: (0,0) is top-left so vertical comparison is inverted
|
||||
// Arguments:
|
||||
// - row: the buffer y-value under observation
|
||||
// - higherCoord: the physically "higher" coordinate (closer to the top-left)
|
||||
// - lowerCoord: the physically "lower" coordinate (closer to the bottom-right)
|
||||
// Return Value:
|
||||
// - the selection row needed for rendering
|
||||
SMALL_RECT Terminal::_GetSelectionRow(const SHORT row, const COORD higherCoord, const COORD lowerCoord) const
|
||||
{
|
||||
SMALL_RECT selectionRow;
|
||||
|
||||
selectionRow.Top = row;
|
||||
selectionRow.Bottom = row;
|
||||
|
||||
if (_boxSelection || higherCoord.Y == lowerCoord.Y)
|
||||
{
|
||||
selectionRow.Left = std::min(higherCoord.X, lowerCoord.X);
|
||||
selectionRow.Right = std::max(higherCoord.X, lowerCoord.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectionRow.Left = (row == higherCoord.Y) ? higherCoord.X : _buffer->GetSize().Left();
|
||||
selectionRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : _buffer->GetSize().RightInclusive();
|
||||
}
|
||||
|
||||
return selectionRow;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the current anchor position relative to the whole text buffer
|
||||
// Arguments:
|
||||
@@ -115,9 +68,7 @@ SMALL_RECT Terminal::_GetSelectionRow(const SHORT row, const COORD higherCoord,
|
||||
// - None
|
||||
const COORD Terminal::GetSelectionAnchor() const noexcept
|
||||
{
|
||||
COORD selectionAnchorPos{ _selectionAnchor };
|
||||
selectionAnchorPos.Y = base::ClampAdd(selectionAnchorPos.Y, _selectionVerticalOffset);
|
||||
return selectionAnchorPos;
|
||||
return _selection->start;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -126,91 +77,9 @@ const COORD Terminal::GetSelectionAnchor() const noexcept
|
||||
// - None
|
||||
// Return Value:
|
||||
// - None
|
||||
const COORD Terminal::GetEndSelectionPosition() const noexcept
|
||||
const COORD Terminal::GetSelectionEnd() const noexcept
|
||||
{
|
||||
COORD endSelectionPos{ _endSelectionPosition };
|
||||
endSelectionPos.Y = base::ClampAdd(endSelectionPos.Y, _selectionVerticalOffset);
|
||||
return endSelectionPos;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Expand the selection row according to selection mode and wide glyphs
|
||||
// - this is particularly useful for box selections (ALT + selection)
|
||||
// Arguments:
|
||||
// - selectionRow: the selection row to be expanded
|
||||
// Return Value:
|
||||
// - modifies selectionRow's Left and Right values to expand properly
|
||||
void Terminal::_ExpandSelectionRow(SMALL_RECT& selectionRow) const
|
||||
{
|
||||
const auto row = selectionRow.Top;
|
||||
|
||||
// expand selection for Double/Triple Click
|
||||
if (_multiClickSelectionMode == SelectionExpansionMode::Word)
|
||||
{
|
||||
selectionRow.Left = _ExpandDoubleClickSelectionLeft({ selectionRow.Left, row }).X;
|
||||
selectionRow.Right = _ExpandDoubleClickSelectionRight({ selectionRow.Right, row }).X;
|
||||
}
|
||||
else if (_multiClickSelectionMode == SelectionExpansionMode::Line)
|
||||
{
|
||||
selectionRow.Left = _buffer->GetSize().Left();
|
||||
selectionRow.Right = _buffer->GetSize().RightInclusive();
|
||||
}
|
||||
|
||||
// expand selection for Wide Glyphs
|
||||
selectionRow.Left = _ExpandWideGlyphSelectionLeft(selectionRow.Left, row);
|
||||
selectionRow.Right = _ExpandWideGlyphSelectionRight(selectionRow.Right, row);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Expands the selection left-wards to cover a wide glyph, if necessary
|
||||
// Arguments:
|
||||
// - position: the (x,y) coordinate on the visible viewport
|
||||
// Return Value:
|
||||
// - updated x position to encapsulate the wide glyph
|
||||
SHORT Terminal::_ExpandWideGlyphSelectionLeft(const SHORT xPos, const SHORT yPos) const
|
||||
{
|
||||
// don't change the value if at/outside the boundary
|
||||
const auto bufferSize = _buffer->GetSize();
|
||||
if (xPos <= bufferSize.Left() || xPos > bufferSize.RightInclusive())
|
||||
{
|
||||
return xPos;
|
||||
}
|
||||
|
||||
COORD position{ xPos, yPos };
|
||||
const auto attr = _buffer->GetCellDataAt(position)->DbcsAttr();
|
||||
if (attr.IsTrailing())
|
||||
{
|
||||
// move off by highlighting the lead half too.
|
||||
// alters position.X
|
||||
bufferSize.DecrementInBounds(position);
|
||||
}
|
||||
return position.X;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Expands the selection right-wards to cover a wide glyph, if necessary
|
||||
// Arguments:
|
||||
// - position: the (x,y) coordinate on the visible viewport
|
||||
// Return Value:
|
||||
// - updated x position to encapsulate the wide glyph
|
||||
SHORT Terminal::_ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPos) const
|
||||
{
|
||||
// don't change the value if at/outside the boundary
|
||||
const auto bufferSize = _buffer->GetSize();
|
||||
if (xPos < bufferSize.Left() || xPos >= bufferSize.RightInclusive())
|
||||
{
|
||||
return xPos;
|
||||
}
|
||||
|
||||
COORD position{ xPos, yPos };
|
||||
const auto attr = _buffer->GetCellDataAt(position)->DbcsAttr();
|
||||
if (attr.IsLeading())
|
||||
{
|
||||
// move off by highlighting the trailing half too.
|
||||
// alters position.X
|
||||
bufferSize.IncrementInBounds(position);
|
||||
}
|
||||
return position.X;
|
||||
return _selection->end;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -219,7 +88,7 @@ SHORT Terminal::_ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPo
|
||||
// - bool representing if selection is only a single cell. Used for copyOnSelect
|
||||
const bool Terminal::_IsSingleCellSelection() const noexcept
|
||||
{
|
||||
return (_selectionAnchor == _endSelectionPosition);
|
||||
return (_selection->start == _selection->end);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -234,7 +103,7 @@ const bool Terminal::IsSelectionActive() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return _selectionActive;
|
||||
return _selection.has_value();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -247,79 +116,60 @@ const bool Terminal::IsCopyOnSelectActive() const noexcept
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Select the sequence between delimiters defined in Settings
|
||||
// - Perform a multi-click selection at viewportPos expanding according to the expansionMode
|
||||
// Arguments:
|
||||
// - position: the (x,y) coordinate on the visible viewport
|
||||
void Terminal::DoubleClickSelection(const COORD position)
|
||||
// - viewportPos: the (x,y) coordinate on the visible viewport
|
||||
// - expansionMode: the SelectionExpansionMode to dictate the boundaries of the selection anchors
|
||||
void Terminal::MultiClickSelection(const COORD viewportPos, SelectionExpansionMode expansionMode)
|
||||
{
|
||||
#pragma warning(suppress : 26496) // cpp core checks wants this const but .Clamp() can write it.
|
||||
COORD positionWithOffsets = _ConvertToBufferCell(position);
|
||||
// set the selection pivot to expand the selection using SetSelectionEnd()
|
||||
_selection = SelectionAnchors{};
|
||||
_selection->pivot = _ConvertToBufferCell(viewportPos);
|
||||
|
||||
// scan leftwards until delimiter is found and
|
||||
// set selection anchor to one right of that spot
|
||||
_selectionAnchor = _ExpandDoubleClickSelectionLeft(positionWithOffsets);
|
||||
THROW_IF_FAILED(ShortSub(_selectionAnchor.Y, gsl::narrow<SHORT>(ViewStartIndex()), &_selectionAnchor.Y));
|
||||
_selectionVerticalOffset = gsl::narrow<SHORT>(ViewStartIndex());
|
||||
_multiClickSelectionMode = expansionMode;
|
||||
SetSelectionEnd(viewportPos);
|
||||
|
||||
// scan rightwards until delimiter is found and
|
||||
// set endSelectionPosition to one left of that spot
|
||||
_endSelectionPosition = _ExpandDoubleClickSelectionRight(positionWithOffsets);
|
||||
THROW_IF_FAILED(ShortSub(_endSelectionPosition.Y, gsl::narrow<SHORT>(ViewStartIndex()), &_endSelectionPosition.Y));
|
||||
|
||||
_selectionActive = true;
|
||||
_multiClickSelectionMode = SelectionExpansionMode::Word;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Select the entire row of the position clicked
|
||||
// Arguments:
|
||||
// - position: the (x,y) coordinate on the visible viewport
|
||||
void Terminal::TripleClickSelection(const COORD position)
|
||||
{
|
||||
SetSelectionAnchor({ 0, position.Y });
|
||||
SetEndSelectionPosition({ _buffer->GetSize().RightInclusive(), position.Y });
|
||||
|
||||
_multiClickSelectionMode = SelectionExpansionMode::Line;
|
||||
// we need to set the _selectionPivot again
|
||||
// for future shift+clicks
|
||||
_selection->pivot = _selection->start;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Record the position of the beginning of a selection
|
||||
// Arguments:
|
||||
// - position: the (x,y) coordinate on the visible viewport
|
||||
void Terminal::SetSelectionAnchor(const COORD position)
|
||||
void Terminal::SetSelectionAnchor(const COORD viewportPos)
|
||||
{
|
||||
_selectionAnchor = position;
|
||||
_selection = SelectionAnchors{};
|
||||
_selection->pivot = _ConvertToBufferCell(viewportPos);
|
||||
|
||||
// include _scrollOffset here to ensure this maps to the right spot of the original viewport
|
||||
THROW_IF_FAILED(ShortSub(_selectionAnchor.Y, gsl::narrow<SHORT>(_scrollOffset), &_selectionAnchor.Y));
|
||||
|
||||
// copy value of ViewStartIndex to support scrolling
|
||||
// and update on new buffer output (used in _GetSelectionRects())
|
||||
_selectionVerticalOffset = gsl::narrow<SHORT>(ViewStartIndex());
|
||||
|
||||
_selectionActive = true;
|
||||
_allowSingleCharSelection = (_copyOnSelect) ? false : true;
|
||||
|
||||
SetEndSelectionPosition(position);
|
||||
|
||||
_multiClickSelectionMode = SelectionExpansionMode::Cell;
|
||||
SetSelectionEnd(viewportPos);
|
||||
|
||||
_selection->start = _selection->pivot;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Record the position of the end of a selection
|
||||
// - Update selection anchors when dragging to a position
|
||||
// - based on the selection expansion mode
|
||||
// Arguments:
|
||||
// - position: the (x,y) coordinate on the visible viewport
|
||||
void Terminal::SetEndSelectionPosition(const COORD position)
|
||||
// - viewportPos: the (x,y) coordinate on the visible viewport
|
||||
// - newExpansionMode: overwrites the _multiClickSelectionMode for this function call. Used for ShiftClick
|
||||
void Terminal::SetSelectionEnd(const COORD viewportPos, std::optional<SelectionExpansionMode> newExpansionMode)
|
||||
{
|
||||
_endSelectionPosition = position;
|
||||
const auto textBufferPos = _ConvertToBufferCell(viewportPos);
|
||||
|
||||
// include _scrollOffset here to ensure this maps to the right spot of the original viewport
|
||||
THROW_IF_FAILED(ShortSub(_endSelectionPosition.Y, gsl::narrow<SHORT>(_scrollOffset), &_endSelectionPosition.Y));
|
||||
// if this is a shiftClick action, we need to overwrite the _multiClickSelectionMode value (even if it's the same)
|
||||
// Otherwise, we may accidentally expand during other selection-based actions
|
||||
_multiClickSelectionMode = newExpansionMode.has_value() ? *newExpansionMode : _multiClickSelectionMode;
|
||||
|
||||
// copy value of ViewStartIndex to support scrolling
|
||||
// and update on new buffer output (used in _GetSelectionRects())
|
||||
_selectionVerticalOffset = gsl::narrow<SHORT>(ViewStartIndex());
|
||||
const auto anchors = _PivotSelection(textBufferPos);
|
||||
std::tie(_selection->start, _selection->end) = _ExpandSelectionAnchors(anchors);
|
||||
|
||||
// moving the endpoint of what used to be a single cell selection
|
||||
// allows the user to drag back and select just one cell
|
||||
if (_copyOnSelect && !_IsSingleCellSelection())
|
||||
{
|
||||
_allowSingleCharSelection = true;
|
||||
@@ -327,12 +177,65 @@ void Terminal::SetEndSelectionPosition(const COORD position)
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - enable/disable box selection (ALT + selection)
|
||||
// - returns a new pair of selection anchors for selecting around the pivot
|
||||
// - This ensures start < end when compared
|
||||
// Arguments:
|
||||
// - isEnabled: new value for _boxSelection
|
||||
void Terminal::SetBoxSelection(const bool isEnabled) noexcept
|
||||
// - targetPos: the (x,y) coordinate we are moving to on the text buffer
|
||||
// Return Value:
|
||||
// - the new start/end for a selection
|
||||
std::pair<COORD, COORD> Terminal::_PivotSelection(const COORD targetPos) const
|
||||
{
|
||||
_boxSelection = isEnabled;
|
||||
if (_buffer->GetSize().CompareInBounds(targetPos, _selection->pivot) <= 0)
|
||||
{
|
||||
// target is before pivot
|
||||
// treat target as start
|
||||
return std::make_pair(targetPos, _selection->pivot);
|
||||
}
|
||||
else
|
||||
{
|
||||
// target is after pivot
|
||||
// treat pivot as start
|
||||
return std::make_pair(_selection->pivot, targetPos);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update the selection anchors to expand according to the expansion mode
|
||||
// Arguments:
|
||||
// - anchors: a pair of selection anchors representing a desired selection
|
||||
// Return Value:
|
||||
// - the new start/end for a selection
|
||||
std::pair<COORD, COORD> Terminal::_ExpandSelectionAnchors(std::pair<COORD, COORD> anchors) const
|
||||
{
|
||||
COORD start = anchors.first;
|
||||
COORD end = anchors.second;
|
||||
|
||||
const auto bufferSize = _buffer->GetSize();
|
||||
switch (_multiClickSelectionMode)
|
||||
{
|
||||
case SelectionExpansionMode::Line:
|
||||
start = { bufferSize.Left(), start.Y };
|
||||
end = { bufferSize.RightInclusive(), end.Y };
|
||||
break;
|
||||
case SelectionExpansionMode::Word:
|
||||
start = _buffer->GetWordStart(start, _wordDelimiters);
|
||||
end = _buffer->GetWordEnd(end, _wordDelimiters);
|
||||
break;
|
||||
case SelectionExpansionMode::Cell:
|
||||
default:
|
||||
// no expansion is necessary
|
||||
break;
|
||||
}
|
||||
return std::make_pair(start, end);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - enable/disable block selection (ALT + selection)
|
||||
// Arguments:
|
||||
// - isEnabled: new value for _blockSelection
|
||||
void Terminal::SetBlockSelection(const bool isEnabled) noexcept
|
||||
{
|
||||
_blockSelection = isEnabled;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -340,11 +243,8 @@ void Terminal::SetBoxSelection(const bool isEnabled) noexcept
|
||||
#pragma warning(disable : 26440) // changing this to noexcept would require a change to ConHost's selection model
|
||||
void Terminal::ClearSelection()
|
||||
{
|
||||
_selectionActive = false;
|
||||
_allowSingleCharSelection = false;
|
||||
_selectionAnchor = { 0, 0 };
|
||||
_endSelectionPosition = { 0, 0 };
|
||||
_selectionVerticalOffset = 0;
|
||||
_selection = std::nullopt;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -359,47 +259,13 @@ const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool tri
|
||||
std::function<COLORREF(TextAttribute&)> GetForegroundColor = std::bind(&Terminal::GetForegroundColor, this, std::placeholders::_1);
|
||||
std::function<COLORREF(TextAttribute&)> GetBackgroundColor = std::bind(&Terminal::GetBackgroundColor, this, std::placeholders::_1);
|
||||
|
||||
return _buffer->GetTextForClipboard(!_boxSelection,
|
||||
return _buffer->GetTextForClipboard(!_blockSelection,
|
||||
trimTrailingWhitespace,
|
||||
_GetSelectionRects(),
|
||||
GetForegroundColor,
|
||||
GetBackgroundColor);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - expand the double click selection to the left
|
||||
// - stopped by delimiter if started on delimiter
|
||||
// Arguments:
|
||||
// - position: buffer coordinate for selection
|
||||
// Return Value:
|
||||
// - updated copy of "position" to new expanded location (with vertical offset)
|
||||
COORD Terminal::_ExpandDoubleClickSelectionLeft(const COORD position) const
|
||||
{
|
||||
// force position to be within bounds
|
||||
#pragma warning(suppress : 26496) // cpp core checks wants this const but .Clamp() can write it.
|
||||
COORD positionWithOffsets = position;
|
||||
_buffer->GetSize().Clamp(positionWithOffsets);
|
||||
|
||||
return _buffer->GetWordStart(positionWithOffsets, _wordDelimiters);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - expand the double click selection to the right
|
||||
// - stopped by delimiter if started on delimiter
|
||||
// Arguments:
|
||||
// - position: buffer coordinate for selection
|
||||
// Return Value:
|
||||
// - updated copy of "position" to new expanded location (with vertical offset)
|
||||
COORD Terminal::_ExpandDoubleClickSelectionRight(const COORD position) const
|
||||
{
|
||||
// force position to be within bounds
|
||||
#pragma warning(suppress : 26496) // cpp core checks wants this const but .Clamp() can write it.
|
||||
COORD positionWithOffsets = position;
|
||||
_buffer->GetSize().Clamp(positionWithOffsets);
|
||||
|
||||
return _buffer->GetWordEnd(positionWithOffsets, _wordDelimiters);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - convert viewport position to the corresponding location on the buffer
|
||||
// Arguments:
|
||||
@@ -408,13 +274,10 @@ COORD Terminal::_ExpandDoubleClickSelectionRight(const COORD position) const
|
||||
// - the corresponding location on the buffer
|
||||
COORD Terminal::_ConvertToBufferCell(const COORD viewportPos) const
|
||||
{
|
||||
// Force position to be valid
|
||||
COORD positionWithOffsets = viewportPos;
|
||||
_buffer->GetSize().Clamp(positionWithOffsets);
|
||||
|
||||
THROW_IF_FAILED(ShortSub(viewportPos.Y, gsl::narrow<SHORT>(_scrollOffset), &positionWithOffsets.Y));
|
||||
THROW_IF_FAILED(ShortAdd(positionWithOffsets.Y, gsl::narrow<SHORT>(ViewStartIndex()), &positionWithOffsets.Y));
|
||||
return positionWithOffsets;
|
||||
const auto yPos = base::ClampedNumeric<short>(_VisibleStartIndex()) + viewportPos.Y;
|
||||
COORD bufferPos = { viewportPos.X, yPos };
|
||||
_buffer->GetSize().Clamp(bufferPos);
|
||||
return bufferPos;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -173,7 +173,7 @@ void Terminal::SelectNewRegion(const COORD coordStart, const COORD coordEnd)
|
||||
realCoordEnd.Y -= gsl::narrow<short>(_VisibleStartIndex());
|
||||
|
||||
SetSelectionAnchor(realCoordStart);
|
||||
SetEndSelectionPosition(realCoordEnd);
|
||||
SetSelectionEnd(realCoordEnd, SelectionExpansionMode::Cell);
|
||||
}
|
||||
|
||||
const std::wstring Terminal::GetConsoleTitle() const noexcept
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace Microsoft.Terminal.Settings
|
||||
// for specifically the control.
|
||||
interface IControlSettings requires Microsoft.Terminal.Settings.ICoreSettings
|
||||
{
|
||||
String ProfileName;
|
||||
|
||||
Boolean UseAcrylic;
|
||||
Double TintOpacity;
|
||||
ScrollbarState ScrollState;
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
_cursorHeight{ DEFAULT_CURSOR_HEIGHT },
|
||||
_wordDelimiters{ DEFAULT_WORD_DELIMITERS },
|
||||
_copyOnSelect{ false },
|
||||
_profileName{},
|
||||
_useAcrylic{ false },
|
||||
_tintOpacity{ 0.5 },
|
||||
_padding{ DEFAULT_PADDING },
|
||||
@@ -196,6 +197,16 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
_copyOnSelect = value;
|
||||
}
|
||||
|
||||
void TerminalSettings::ProfileName(hstring const& value)
|
||||
{
|
||||
_profileName = value;
|
||||
}
|
||||
|
||||
hstring TerminalSettings::ProfileName()
|
||||
{
|
||||
return _profileName;
|
||||
}
|
||||
|
||||
bool TerminalSettings::UseAcrylic() noexcept
|
||||
{
|
||||
return _useAcrylic;
|
||||
|
||||
@@ -55,6 +55,8 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
void CopyOnSelect(bool value) noexcept;
|
||||
// ------------------------ End of Core Settings -----------------------
|
||||
|
||||
hstring ProfileName();
|
||||
void ProfileName(hstring const& value);
|
||||
bool UseAcrylic() noexcept;
|
||||
void UseAcrylic(bool value) noexcept;
|
||||
double TintOpacity() noexcept;
|
||||
@@ -120,6 +122,7 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
uint32_t _cursorHeight;
|
||||
hstring _wordDelimiters;
|
||||
|
||||
hstring _profileName;
|
||||
bool _useAcrylic;
|
||||
double _tintOpacity;
|
||||
hstring _fontFace;
|
||||
|
||||
@@ -44,7 +44,7 @@ using namespace Microsoft::Terminal::Core;
|
||||
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class TerminalBufferTests;
|
||||
class ConptyRoundtripTests;
|
||||
};
|
||||
using namespace TerminalCoreUnitTests;
|
||||
|
||||
@@ -152,8 +152,14 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
|
||||
TEST_METHOD(SimpleWriteOutputTest);
|
||||
TEST_METHOD(WriteTwoLinesUsesNewline);
|
||||
TEST_METHOD(WriteAFewSimpleLines);
|
||||
|
||||
TEST_METHOD(PassthroughClearScrollback);
|
||||
|
||||
TEST_METHOD(TestWrappingALongString);
|
||||
TEST_METHOD(TestAdvancedWrapping);
|
||||
TEST_METHOD(TestExactWrappingWithoutSpaces);
|
||||
TEST_METHOD(TestExactWrappingWithSpaces);
|
||||
|
||||
TEST_METHOD(MoveCursorAtEOL);
|
||||
|
||||
private:
|
||||
@@ -348,6 +354,262 @@ void ConptyRoundtripTests::WriteAFewSimpleLines()
|
||||
verifyData(termTb);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::TestWrappingALongString()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
_checkConptyOutput = false;
|
||||
|
||||
const auto initialTermView = term->GetViewport();
|
||||
|
||||
const auto charsToWrite = gsl::narrow_cast<short>(TestUtils::Test100CharsString.size());
|
||||
VERIFY_ARE_EQUAL(100, charsToWrite);
|
||||
|
||||
VERIFY_ARE_EQUAL(0, initialTermView.Top());
|
||||
VERIFY_ARE_EQUAL(32, initialTermView.BottomExclusive());
|
||||
|
||||
hostSm.ProcessString(TestUtils::Test100CharsString);
|
||||
|
||||
const auto secondView = term->GetViewport();
|
||||
|
||||
VERIFY_ARE_EQUAL(0, secondView.Top());
|
||||
VERIFY_ARE_EQUAL(32, secondView.BottomExclusive());
|
||||
|
||||
auto verifyBuffer = [&](const TextBuffer& tb) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor wrapped to the second line
|
||||
VERIFY_ARE_EQUAL(charsToWrite % initialTermView.Width(), cursor.GetPosition().X);
|
||||
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
|
||||
|
||||
// Verify that we marked the 0th row as _wrapped_
|
||||
const auto& row0 = tb.GetRowByOffset(0);
|
||||
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());
|
||||
|
||||
const auto& row1 = tb.GetRowByOffset(1);
|
||||
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
|
||||
|
||||
TestUtils::VerifyExpectedString(tb, TestUtils::Test100CharsString, { 0, 0 });
|
||||
};
|
||||
|
||||
verifyBuffer(hostTb);
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
verifyBuffer(termTb);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::TestAdvancedWrapping()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
const auto initialTermView = term->GetViewport();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
const auto charsToWrite = gsl::narrow_cast<short>(TestUtils::Test100CharsString.size());
|
||||
VERIFY_ARE_EQUAL(100, charsToWrite);
|
||||
|
||||
hostSm.ProcessString(TestUtils::Test100CharsString);
|
||||
hostSm.ProcessString(L"\n");
|
||||
hostSm.ProcessString(L" ");
|
||||
hostSm.ProcessString(L"1234567890");
|
||||
|
||||
auto verifyBuffer = [&](const TextBuffer& tb) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor wrapped to the second line
|
||||
VERIFY_ARE_EQUAL(2, cursor.GetPosition().Y);
|
||||
VERIFY_ARE_EQUAL(20, cursor.GetPosition().X);
|
||||
|
||||
// Verify that we marked the 0th row as _wrapped_
|
||||
const auto& row0 = tb.GetRowByOffset(0);
|
||||
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());
|
||||
|
||||
const auto& row1 = tb.GetRowByOffset(1);
|
||||
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
|
||||
|
||||
TestUtils::VerifyExpectedString(tb, TestUtils::Test100CharsString, { 0, 0 });
|
||||
TestUtils::VerifyExpectedString(tb, L" 1234567890", { 0, 2 });
|
||||
};
|
||||
|
||||
verifyBuffer(hostTb);
|
||||
|
||||
// First write the first 80 characters from the string
|
||||
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
|
||||
// Without line breaking, write the remaining 20 chars
|
||||
expectedOutput.push_back(R"(qrstuvwxyz{|}~!"#$%&)");
|
||||
// Clear the rest of row 1
|
||||
expectedOutput.push_back("\x1b[K");
|
||||
// This is the hard line break
|
||||
expectedOutput.push_back("\r\n");
|
||||
// Now write row 2 of the buffer
|
||||
expectedOutput.push_back(" 1234567890");
|
||||
// and clear everything after the text, because the buffer is empty.
|
||||
expectedOutput.push_back("\x1b[K");
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
verifyBuffer(termTb);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::TestExactWrappingWithoutSpaces()
|
||||
{
|
||||
// This test (and TestExactWrappingWitSpaces) reveals a bug in the old
|
||||
// implementation.
|
||||
//
|
||||
// If a line _exactly_ wraps to the next line, we can't tell if the line
|
||||
// should really wrap, or manually break. The client app is writing a line
|
||||
// that's exactly the width of the buffer that manually linebreaked at the
|
||||
// end of the line, followed by another line.
|
||||
//
|
||||
// With the old PaintBufferLine interface, there's no way to know if this
|
||||
// case is because the line wrapped or not. Hence, the addition of the
|
||||
// `lineWrapped` parameter
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
const auto initialTermView = term->GetViewport();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
const auto charsToWrite = initialTermView.Width();
|
||||
VERIFY_ARE_EQUAL(80, charsToWrite);
|
||||
|
||||
for (auto i = 0; i < charsToWrite; i++)
|
||||
{
|
||||
// This is a handy way of just printing the printable characters that
|
||||
// _aren't_ the space character.
|
||||
const wchar_t wch = static_cast<wchar_t>(33 + (i % 94));
|
||||
hostSm.ProcessCharacter(wch);
|
||||
}
|
||||
|
||||
hostSm.ProcessString(L"\n");
|
||||
hostSm.ProcessString(L"1234567890");
|
||||
|
||||
auto verifyBuffer = [&](const TextBuffer& tb, const bool isTerminal) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor wrapped to the second line
|
||||
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
|
||||
VERIFY_ARE_EQUAL(10, cursor.GetPosition().X);
|
||||
|
||||
// TODO: GH#780 - In the Terminal, neither line should be wrapped.
|
||||
// Unfortunately, until WriteCharsLegacy2ElectricBoogaloo is complete,
|
||||
// the Terminal will still treat the first line as wrapped. When #780 is
|
||||
// implemented, these tests will fail, and should again expect the first
|
||||
// line to not be wrapped.
|
||||
|
||||
// Verify that we marked the 0th row as _not wrapped_
|
||||
const auto& row0 = tb.GetRowByOffset(0);
|
||||
VERIFY_ARE_EQUAL(isTerminal, row0.GetCharRow().WasWrapForced());
|
||||
|
||||
const auto& row1 = tb.GetRowByOffset(1);
|
||||
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
|
||||
|
||||
TestUtils::VerifyExpectedString(tb, LR"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)", { 0, 0 });
|
||||
TestUtils::VerifyExpectedString(tb, L"1234567890", { 0, 1 });
|
||||
};
|
||||
|
||||
verifyBuffer(hostTb, false);
|
||||
|
||||
// First write the first 80 characters from the string
|
||||
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
|
||||
|
||||
// This is the hard line break
|
||||
expectedOutput.push_back("\r\n");
|
||||
// Now write row 2 of the buffer
|
||||
expectedOutput.push_back("1234567890");
|
||||
// and clear everything after the text, because the buffer is empty.
|
||||
expectedOutput.push_back("\x1b[K");
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
verifyBuffer(termTb, true);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::TestExactWrappingWithSpaces()
|
||||
{
|
||||
// This test is also explained by the comment at the top of TestExactWrappingWithoutSpaces
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
const auto initialTermView = term->GetViewport();
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
const auto charsToWrite = initialTermView.Width();
|
||||
VERIFY_ARE_EQUAL(80, charsToWrite);
|
||||
|
||||
for (auto i = 0; i < charsToWrite; i++)
|
||||
{
|
||||
// This is a handy way of just printing the printable characters that
|
||||
// _aren't_ the space character.
|
||||
const wchar_t wch = static_cast<wchar_t>(33 + (i % 94));
|
||||
hostSm.ProcessCharacter(wch);
|
||||
}
|
||||
|
||||
hostSm.ProcessString(L"\n");
|
||||
hostSm.ProcessString(L" ");
|
||||
hostSm.ProcessString(L"1234567890");
|
||||
|
||||
auto verifyBuffer = [&](const TextBuffer& tb, const bool isTerminal) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor wrapped to the second line
|
||||
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
|
||||
VERIFY_ARE_EQUAL(20, cursor.GetPosition().X);
|
||||
|
||||
// TODO: GH#780 - In the Terminal, neither line should be wrapped.
|
||||
// Unfortunately, until WriteCharsLegacy2ElectricBoogaloo is complete,
|
||||
// the Terminal will still treat the first line as wrapped. When #780 is
|
||||
// implemented, these tests will fail, and should again expect the first
|
||||
// line to not be wrapped.
|
||||
|
||||
// Verify that we marked the 0th row as _not wrapped_
|
||||
const auto& row0 = tb.GetRowByOffset(0);
|
||||
VERIFY_ARE_EQUAL(isTerminal, row0.GetCharRow().WasWrapForced());
|
||||
|
||||
const auto& row1 = tb.GetRowByOffset(1);
|
||||
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
|
||||
|
||||
TestUtils::VerifyExpectedString(tb, LR"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)", { 0, 0 });
|
||||
TestUtils::VerifyExpectedString(tb, L" 1234567890", { 0, 1 });
|
||||
};
|
||||
|
||||
verifyBuffer(hostTb, false);
|
||||
|
||||
// First write the first 80 characters from the string
|
||||
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
|
||||
|
||||
// This is the hard line break
|
||||
expectedOutput.push_back("\r\n");
|
||||
// Now write row 2 of the buffer
|
||||
expectedOutput.push_back(" 1234567890");
|
||||
// and clear everything after the text, because the buffer is empty.
|
||||
expectedOutput.push_back("\x1b[K");
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
verifyBuffer(termTb, true);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::MoveCursorAtEOL()
|
||||
{
|
||||
// This is a test for GH#1245
|
||||
@@ -360,7 +622,6 @@ void ConptyRoundtripTests::MoveCursorAtEOL()
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
|
||||
_flushFirstFrame();
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionAnchor({ 5, rowValue });
|
||||
|
||||
// Simulate move to (x,y) = (15,20)
|
||||
term.SetEndSelectionPosition({ 15, 20 });
|
||||
term.SetSelectionEnd({ 15, 20 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
@@ -110,19 +110,19 @@ namespace TerminalCoreUnitTests
|
||||
{
|
||||
const COORD maxCoord = { SHRT_MAX, SHRT_MAX };
|
||||
|
||||
// Test SetSelectionAnchor(COORD) and SetEndSelectionPosition(COORD)
|
||||
// Test SetSelectionAnchor(COORD) and SetSelectionEnd(COORD)
|
||||
// Behavior: clamp coord to viewport.
|
||||
auto ValidateSingleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
|
||||
Terminal term;
|
||||
DummyRenderTarget emptyRT;
|
||||
term.Create({ 10, 10 }, scrollback, emptyRT);
|
||||
|
||||
// NOTE: SetEndSelectionPosition(COORD) is called within SetSelectionAnchor(COORD)
|
||||
// NOTE: SetSelectionEnd(COORD) is called within SetSelectionAnchor(COORD)
|
||||
term.SetSelectionAnchor(maxCoord);
|
||||
ValidateSingleRowSelection(term, expected);
|
||||
};
|
||||
|
||||
// Test DoubleClickSelection(COORD)
|
||||
// Test a Double Click Selection
|
||||
// Behavior: clamp coord to viewport.
|
||||
// Then, do double click selection.
|
||||
auto ValidateDoubleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
|
||||
@@ -130,11 +130,11 @@ namespace TerminalCoreUnitTests
|
||||
DummyRenderTarget emptyRT;
|
||||
term.Create({ 10, 10 }, scrollback, emptyRT);
|
||||
|
||||
term.DoubleClickSelection(maxCoord);
|
||||
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansionMode::Word);
|
||||
ValidateSingleRowSelection(term, expected);
|
||||
};
|
||||
|
||||
// Test TripleClickSelection(COORD)
|
||||
// Test a Triple Click Selection
|
||||
// Behavior: clamp coord to viewport.
|
||||
// Then, do triple click selection.
|
||||
auto ValidateTripleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
|
||||
@@ -142,7 +142,7 @@ namespace TerminalCoreUnitTests
|
||||
DummyRenderTarget emptyRT;
|
||||
term.Create({ 10, 10 }, scrollback, emptyRT);
|
||||
|
||||
term.TripleClickSelection(maxCoord);
|
||||
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansionMode::Line);
|
||||
ValidateSingleRowSelection(term, expected);
|
||||
};
|
||||
|
||||
@@ -226,17 +226,17 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Case 1: Move out of right boundary
|
||||
Log::Comment(L"Out of bounds: X-value too large");
|
||||
term.SetEndSelectionPosition({ 20, 5 });
|
||||
term.SetSelectionEnd({ 20, 5 });
|
||||
ValidateSingleRowSelection(term, SMALL_RECT({ 5, 5, rightBoundary, 5 }));
|
||||
|
||||
// Case 2: Move out of left boundary
|
||||
Log::Comment(L"Out of bounds: X-value negative");
|
||||
term.SetEndSelectionPosition({ -20, 5 });
|
||||
term.SetSelectionEnd({ -20, 5 });
|
||||
ValidateSingleRowSelection(term, { leftBoundary, 5, 5, 5 });
|
||||
|
||||
// Case 3: Move out of top boundary
|
||||
Log::Comment(L"Out of bounds: Y-value negative");
|
||||
term.SetEndSelectionPosition({ 5, -20 });
|
||||
term.SetSelectionEnd({ 5, -20 });
|
||||
{
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
|
||||
@@ -267,7 +267,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Case 4: Move out of bottom boundary
|
||||
Log::Comment(L"Out of bounds: Y-value too large");
|
||||
term.SetEndSelectionPosition({ 5, 20 });
|
||||
term.SetSelectionEnd({ 5, 20 });
|
||||
{
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
|
||||
@@ -310,10 +310,10 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate ALT + click at (x,y) = (5,10)
|
||||
term.SetSelectionAnchor({ 5, rowValue });
|
||||
term.SetBoxSelection(true);
|
||||
term.SetBlockSelection(true);
|
||||
|
||||
// Simulate move to (x,y) = (15,20)
|
||||
term.SetEndSelectionPosition({ 15, 20 });
|
||||
term.SetSelectionEnd({ 15, 20 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
@@ -349,7 +349,7 @@ namespace TerminalCoreUnitTests
|
||||
term.SetSelectionAnchor({ 5, rowValue });
|
||||
|
||||
// Simulate move to (x,y) = (15,20)
|
||||
term.SetEndSelectionPosition({ 15, 20 });
|
||||
term.SetSelectionEnd({ 15, 20 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
@@ -449,10 +449,10 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate ALT + click at (x,y) = (5,8)
|
||||
term.SetSelectionAnchor({ 5, 8 });
|
||||
term.SetBoxSelection(true);
|
||||
term.SetBlockSelection(true);
|
||||
|
||||
// Simulate move to (x,y) = (7,12)
|
||||
term.SetEndSelectionPosition({ 7, 12 });
|
||||
term.SetSelectionEnd({ 7, 12 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
@@ -501,7 +501,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate double click at (x,y) = (5,10)
|
||||
auto clickPos = COORD{ 5, 10 };
|
||||
term.DoubleClickSelection(clickPos);
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Word);
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, (4 + gsl::narrow<SHORT>(text.size()) - 1), 10 }));
|
||||
@@ -519,7 +519,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate click at (x,y) = (5,10)
|
||||
auto clickPos = COORD{ 5, 10 };
|
||||
term.DoubleClickSelection(clickPos);
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Word);
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
@@ -546,7 +546,7 @@ namespace TerminalCoreUnitTests
|
||||
// Simulate click at (x,y) = (15,10)
|
||||
// this is over the '>' char
|
||||
auto clickPos = COORD{ 15, 10 };
|
||||
term.DoubleClickSelection(clickPos);
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Word);
|
||||
|
||||
// ---Validate selection area---
|
||||
// "Terminal" is in class 2
|
||||
@@ -572,14 +572,14 @@ namespace TerminalCoreUnitTests
|
||||
term.Write(text);
|
||||
|
||||
// Simulate double click at (x,y) = (5,10)
|
||||
term.DoubleClickSelection({ 5, 10 });
|
||||
term.MultiClickSelection({ 5, 10 }, Terminal::SelectionExpansionMode::Word);
|
||||
|
||||
// Simulate move to (x,y) = (21,10)
|
||||
//
|
||||
// buffer: doubleClickMe dragThroughHere
|
||||
// ^ ^
|
||||
// start finish
|
||||
term.SetEndSelectionPosition({ 21, 10 });
|
||||
term.SetSelectionEnd({ 21, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
|
||||
@@ -601,14 +601,14 @@ namespace TerminalCoreUnitTests
|
||||
term.Write(text);
|
||||
|
||||
// Simulate double click at (x,y) = (21,10)
|
||||
term.DoubleClickSelection({ 21, 10 });
|
||||
term.MultiClickSelection({ 21, 10 }, Terminal::SelectionExpansionMode::Word);
|
||||
|
||||
// Simulate move to (x,y) = (5,10)
|
||||
//
|
||||
// buffer: doubleClickMe dragThroughHere
|
||||
// ^ ^
|
||||
// finish start
|
||||
term.SetEndSelectionPosition({ 5, 10 });
|
||||
term.SetSelectionEnd({ 5, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
|
||||
@@ -622,7 +622,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate click at (x,y) = (5,10)
|
||||
auto clickPos = COORD{ 5, 10 };
|
||||
term.TripleClickSelection(clickPos);
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Line);
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, SMALL_RECT({ 0, 10, 99, 10 }));
|
||||
@@ -636,10 +636,10 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate click at (x,y) = (5,10)
|
||||
auto clickPos = COORD{ 5, 10 };
|
||||
term.TripleClickSelection(clickPos);
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Line);
|
||||
|
||||
// Simulate move to (x,y) = (7,10)
|
||||
term.SetEndSelectionPosition({ 7, 10 });
|
||||
term.SetSelectionEnd({ 7, 10 });
|
||||
|
||||
// Validate selection area
|
||||
ValidateSingleRowSelection(term, SMALL_RECT({ 0, 10, 99, 10 }));
|
||||
@@ -653,10 +653,10 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate click at (x,y) = (5,10)
|
||||
auto clickPos = COORD{ 5, 10 };
|
||||
term.TripleClickSelection(clickPos);
|
||||
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Line);
|
||||
|
||||
// Simulate move to (x,y) = (5,11)
|
||||
term.SetEndSelectionPosition({ 5, 11 });
|
||||
term.SetSelectionEnd({ 5, 11 });
|
||||
|
||||
// Simulate renderer calling TriggerSelection and acquiring selection area
|
||||
auto selectionRects = term.GetSelectionRects();
|
||||
@@ -689,7 +689,7 @@ namespace TerminalCoreUnitTests
|
||||
|
||||
// Simulate move to (x,y) = (5,10)
|
||||
// (So, no movement)
|
||||
term.SetEndSelectionPosition({ 5, 10 });
|
||||
term.SetSelectionEnd({ 5, 10 });
|
||||
|
||||
// Case 1: single cell selection not allowed
|
||||
{
|
||||
@@ -705,12 +705,12 @@ namespace TerminalCoreUnitTests
|
||||
}
|
||||
|
||||
// Case 2: move off of single cell
|
||||
term.SetEndSelectionPosition({ 6, 10 });
|
||||
term.SetSelectionEnd({ 6, 10 });
|
||||
ValidateSingleRowSelection(term, { 5, 10, 6, 10 });
|
||||
VERIFY_IS_TRUE(term.IsSelectionActive());
|
||||
|
||||
// Case 3: move back onto single cell (now allowed)
|
||||
term.SetEndSelectionPosition({ 5, 10 });
|
||||
term.SetSelectionEnd({ 5, 10 });
|
||||
ValidateSingleRowSelection(term, { 5, 10, 5, 10 });
|
||||
|
||||
// single cell selection should now be allowed
|
||||
|
||||
@@ -29,6 +29,9 @@ class TerminalCoreUnitTests::TerminalBufferTests final
|
||||
|
||||
TEST_METHOD(TestSimpleBufferWriting);
|
||||
|
||||
TEST_METHOD(TestWrappingCharByChar);
|
||||
TEST_METHOD(TestWrappingALongString);
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
// STEP 1: Set up the Terminal
|
||||
@@ -66,3 +69,76 @@ void TerminalBufferTests::TestSimpleBufferWriting()
|
||||
|
||||
TestUtils::VerifyExpectedString(termTb, L"Hello World", { 0, 0 });
|
||||
}
|
||||
|
||||
void TerminalBufferTests::TestWrappingCharByChar()
|
||||
{
|
||||
auto& termTb = *term->_buffer;
|
||||
auto& termSm = *term->_stateMachine;
|
||||
const auto initialView = term->GetViewport();
|
||||
auto& cursor = termTb.GetCursor();
|
||||
|
||||
const auto charsToWrite = gsl::narrow_cast<short>(TestUtils::Test100CharsString.size());
|
||||
|
||||
VERIFY_ARE_EQUAL(0, initialView.Top());
|
||||
VERIFY_ARE_EQUAL(32, initialView.BottomExclusive());
|
||||
|
||||
for (auto i = 0; i < charsToWrite; i++)
|
||||
{
|
||||
// This is a handy way of just printing the printable characters that
|
||||
// _aren't_ the space character.
|
||||
const wchar_t wch = static_cast<wchar_t>(33 + (i % 94));
|
||||
termSm.ProcessCharacter(wch);
|
||||
}
|
||||
|
||||
const auto secondView = term->GetViewport();
|
||||
|
||||
VERIFY_ARE_EQUAL(0, secondView.Top());
|
||||
VERIFY_ARE_EQUAL(32, secondView.BottomExclusive());
|
||||
|
||||
// Verify the cursor wrapped to the second line
|
||||
VERIFY_ARE_EQUAL(charsToWrite % initialView.Width(), cursor.GetPosition().X);
|
||||
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
|
||||
|
||||
// Verify that we marked the 0th row as _wrapped_
|
||||
const auto& row0 = termTb.GetRowByOffset(0);
|
||||
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());
|
||||
|
||||
const auto& row1 = termTb.GetRowByOffset(1);
|
||||
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
|
||||
|
||||
TestUtils::VerifyExpectedString(termTb, TestUtils::Test100CharsString, { 0, 0 });
|
||||
}
|
||||
|
||||
void TerminalBufferTests::TestWrappingALongString()
|
||||
{
|
||||
auto& termTb = *term->_buffer;
|
||||
auto& termSm = *term->_stateMachine;
|
||||
const auto initialView = term->GetViewport();
|
||||
auto& cursor = termTb.GetCursor();
|
||||
|
||||
const auto charsToWrite = gsl::narrow_cast<short>(TestUtils::Test100CharsString.size());
|
||||
VERIFY_ARE_EQUAL(100, charsToWrite);
|
||||
|
||||
VERIFY_ARE_EQUAL(0, initialView.Top());
|
||||
VERIFY_ARE_EQUAL(32, initialView.BottomExclusive());
|
||||
|
||||
termSm.ProcessString(TestUtils::Test100CharsString);
|
||||
|
||||
const auto secondView = term->GetViewport();
|
||||
|
||||
VERIFY_ARE_EQUAL(0, secondView.Top());
|
||||
VERIFY_ARE_EQUAL(32, secondView.BottomExclusive());
|
||||
|
||||
// Verify the cursor wrapped to the second line
|
||||
VERIFY_ARE_EQUAL(charsToWrite % initialView.Width(), cursor.GetPosition().X);
|
||||
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
|
||||
|
||||
// Verify that we marked the 0th row as _wrapped_
|
||||
const auto& row0 = termTb.GetRowByOffset(0);
|
||||
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());
|
||||
|
||||
const auto& row1 = termTb.GetRowByOffset(1);
|
||||
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
|
||||
|
||||
TestUtils::VerifyExpectedString(termTb, TestUtils::Test100CharsString, { 0, 0 });
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ namespace TerminalCoreUnitTests
|
||||
class TerminalCoreUnitTests::TestUtils
|
||||
{
|
||||
public:
|
||||
static constexpr std::wstring_view Test100CharsString{
|
||||
LR"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&)"
|
||||
};
|
||||
|
||||
// Function Description:
|
||||
// - Helper function to validate that a number of characters in a row are all
|
||||
// the same. Validates that the next end-start characters are all equal to the
|
||||
|
||||
@@ -28,10 +28,10 @@ inline winrt::Windows::UI::Color ColorRefToColor(const COLORREF& colorref)
|
||||
// - Rect scaled by scale
|
||||
inline winrt::Windows::Foundation::Rect ScaleRect(winrt::Windows::Foundation::Rect rect, double scale)
|
||||
{
|
||||
const float scaleLocal = gsl::narrow_cast<float>(scale);
|
||||
rect.X *= scaleLocal;
|
||||
rect.Y *= scaleLocal;
|
||||
rect.Width *= scaleLocal;
|
||||
rect.Height *= scaleLocal;
|
||||
const auto scaleLocal = base::ClampedNumeric<float>(scale);
|
||||
rect.X = base::ClampMul(rect.X, scaleLocal);
|
||||
rect.Y = base::ClampMul(rect.Y, scaleLocal);
|
||||
rect.Width = base::ClampMul(rect.Width, scaleLocal);
|
||||
rect.Height = base::ClampMul(rect.Height, scaleLocal);
|
||||
return rect;
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ const COORD RenderData::GetSelectionAnchor() const noexcept
|
||||
// - none
|
||||
// Return Value:
|
||||
// - current selection anchor
|
||||
const COORD RenderData::GetEndSelectionPosition() const noexcept
|
||||
const COORD RenderData::GetSelectionEnd() const noexcept
|
||||
{
|
||||
// The selection area in ConHost is encoded as two things...
|
||||
// - SelectionAnchor: the initial position where the selection was started
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
void ClearSelection() override;
|
||||
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
|
||||
const COORD GetSelectionAnchor() const noexcept;
|
||||
const COORD GetEndSelectionPosition() const noexcept;
|
||||
const COORD GetSelectionEnd() const noexcept;
|
||||
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute attr);
|
||||
#pragma endregion
|
||||
};
|
||||
|
||||
@@ -34,111 +34,6 @@ Selection& Selection::Instance()
|
||||
return *_instance;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Determines 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 issues
|
||||
std::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 rectangle
|
||||
for (short i = selectionRect.Top; i <= selectionRect.Bottom; i++)
|
||||
{
|
||||
// create a rectangle representing the highlight on one row
|
||||
SMALL_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 edge
|
||||
if (lineSelection)
|
||||
{
|
||||
// if not the first row, pad the left selection to the buffer edge
|
||||
if (i != selectionRect.Top)
|
||||
{
|
||||
highlightRow.Left = 0;
|
||||
}
|
||||
|
||||
// if not the last row, pad the right selection to the buffer edge
|
||||
if (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 rectangle
|
||||
highlightRow.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 rectangle
|
||||
highlightRow.Right = selectionRect.Left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compensate for double width characters by calling double-width measuring/limiting function
|
||||
const 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:
|
||||
// - Determines the line-by-line selection rectangles based on global selection state.
|
||||
// Arguments:
|
||||
@@ -154,65 +49,17 @@ std::vector<SMALL_RECT> Selection::GetSelectionRects() const
|
||||
return std::vector<SMALL_RECT>();
|
||||
}
|
||||
|
||||
return s_GetSelectionRects(_srSelectionRect, _coordSelectionAnchor, IsLineSelection());
|
||||
}
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
const auto& screenInfo = gci.GetActiveOutputBuffer();
|
||||
|
||||
// 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 region
|
||||
SMALL_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--;
|
||||
}
|
||||
}
|
||||
// _coordSelectionAnchor is at one of the corners of _srSelectionRects
|
||||
// endSelectionAnchor is at the exact opposite corner
|
||||
COORD endSelectionAnchor;
|
||||
endSelectionAnchor.X = (_coordSelectionAnchor.X == _srSelectionRect.Left) ? _srSelectionRect.Right : _srSelectionRect.Left;
|
||||
endSelectionAnchor.Y = (_coordSelectionAnchor.Y == _srSelectionRect.Top) ? _srSelectionRect.Bottom : _srSelectionRect.Top;
|
||||
|
||||
// Check end position of strings
|
||||
if (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;
|
||||
const auto blockSelection = !IsLineSelection();
|
||||
return screenInfo.GetTextBuffer().GetTextRects(_coordSelectionAnchor, endSelectionAnchor, blockSelection);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -564,18 +411,13 @@ void Selection::ColorSelection(const SMALL_RECT& srRect, const TextAttribute att
|
||||
// - 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);
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
const auto& screenInfo = gci.GetActiveOutputBuffer();
|
||||
|
||||
const auto rectangles = screenInfo.GetTextBuffer().GetTextRects(coordSelectionStart, coordSelectionEnd);
|
||||
for (const auto& rect : rectangles)
|
||||
{
|
||||
ColorSelection(rect, attr);
|
||||
|
||||
@@ -72,15 +72,6 @@ private:
|
||||
|
||||
void _PaintSelection() const;
|
||||
|
||||
static SMALL_RECT s_BisectSelection(const short sStringLength,
|
||||
const COORD coordTargetPoint,
|
||||
const SCREEN_INFORMATION& screenInfo,
|
||||
const SMALL_RECT rect);
|
||||
|
||||
static std::vector<SMALL_RECT> s_GetSelectionRects(const SMALL_RECT& selectionRect,
|
||||
const COORD selectionAnchor,
|
||||
const bool lineSelection);
|
||||
|
||||
void _CancelMarkSelection();
|
||||
void _CancelMouseSelection();
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@ class SelectionTests
|
||||
|
||||
// selection rectangle starts from the target and goes for the length requested
|
||||
srSelection.Left = coordTargetPoint.X;
|
||||
srSelection.Right = coordTargetPoint.X + sStringLength - 1;
|
||||
srSelection.Right = coordTargetPoint.X + sStringLength;
|
||||
|
||||
// save original for comparison
|
||||
srOriginal.Top = srSelection.Top;
|
||||
@@ -331,7 +331,12 @@ class SelectionTests
|
||||
srOriginal.Left = srSelection.Left;
|
||||
srOriginal.Right = srSelection.Right;
|
||||
|
||||
srSelection = Selection::s_BisectSelection(sStringLength, coordTargetPoint, screenInfo, srSelection);
|
||||
COORD startPos{ sTargetX, sTargetY };
|
||||
COORD endPos{ base::ClampAdd(sTargetX, sLength), sTargetY };
|
||||
const auto selectionRects = screenInfo.GetTextBuffer().GetTextRects(startPos, endPos);
|
||||
|
||||
VERIFY_ARE_EQUAL(static_cast<size_t>(1), selectionRects.size());
|
||||
srSelection = selectionRects.at(0);
|
||||
|
||||
VERIFY_ARE_EQUAL(srOriginal.Top, srSelection.Top);
|
||||
VERIFY_ARE_EQUAL(srOriginal.Bottom, srSelection.Bottom);
|
||||
@@ -378,10 +383,10 @@ class SelectionTests
|
||||
|
||||
// start from position 10 before end of row (80 length row)
|
||||
// row is 2
|
||||
// selection is 10 characters long
|
||||
// selection is 9 characters long
|
||||
// the left edge shouldn't move
|
||||
// the right edge should move one to the left (-1) to not select the leading byte
|
||||
TestBisectSelectionDelta(70, 2, 10, 0, -1);
|
||||
TestBisectSelectionDelta(70, 2, 9, 0, -1);
|
||||
|
||||
// 2b. End position is leading half and is elsewhere in the row
|
||||
|
||||
@@ -389,16 +394,16 @@ class SelectionTests
|
||||
// row is 2
|
||||
// selection is 10 characters long
|
||||
// the left edge shouldn't move
|
||||
// the right edge should move one to the right (+1) to add the trailing byte to the selection
|
||||
TestBisectSelectionDelta(58, 2, 10, 0, 1);
|
||||
// the right edge should not move, because it is already on the trailing byte
|
||||
TestBisectSelectionDelta(58, 2, 10, 0, 0);
|
||||
|
||||
// 2c. End position is leading half and is at end of buffer
|
||||
// start from position 10 before end of row (80 length row)
|
||||
// row is 300 (or 299 for the index)
|
||||
// selection is 10 characters long
|
||||
// selection is 9 characters long
|
||||
// the left edge shouldn't move
|
||||
// the right edge shouldn't move
|
||||
TestBisectSelectionDelta(70, 299, 10, 0, 0);
|
||||
// the right edge should move one to the left (-1) to not select the leading byte
|
||||
TestBisectSelectionDelta(70, 299, 9, 0, -1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -148,6 +148,8 @@ class TextBufferTests
|
||||
|
||||
void WriteLinesToBuffer(const std::vector<std::wstring>& text, TextBuffer& buffer);
|
||||
TEST_METHOD(GetWordBoundaries);
|
||||
|
||||
TEST_METHOD(GetTextRects);
|
||||
};
|
||||
|
||||
void TextBufferTests::TestBufferCreate()
|
||||
@@ -2136,3 +2138,68 @@ void TextBufferTests::GetWordBoundaries()
|
||||
VERIFY_ARE_EQUAL(expected, result);
|
||||
}
|
||||
}
|
||||
|
||||
void TextBufferTests::GetTextRects()
|
||||
{
|
||||
// GetTextRects() is used to...
|
||||
// - Represent selection rects
|
||||
// - Represent UiaTextRanges for accessibility
|
||||
|
||||
// This is the burrito emoji: 🌯
|
||||
// It's encoded in UTF-16, as needed by the buffer.
|
||||
const auto burrito = std::wstring(L"\xD83C\xDF2F");
|
||||
|
||||
COORD bufferSize{ 20, 50 };
|
||||
UINT cursorSize = 12;
|
||||
TextAttribute attr{ 0x7f };
|
||||
auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
|
||||
|
||||
// Setup: Write lines of text to the buffer
|
||||
const std::vector<std::wstring> text = { L"0123456789",
|
||||
L" " + burrito + L"3456" + burrito,
|
||||
L" " + burrito + L"45" + burrito,
|
||||
burrito + L"234567" + burrito,
|
||||
L"0123456789" };
|
||||
WriteLinesToBuffer(text, *_buffer);
|
||||
// - - - Text Buffer Contents - - -
|
||||
// |0123456789
|
||||
// | 🌯3456🌯
|
||||
// | 🌯45🌯
|
||||
// |🌯234567🌯
|
||||
// |0123456789
|
||||
// - - - - - - - - - - - - - - - -
|
||||
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:blockSelection", L"{false, true}")
|
||||
END_TEST_METHOD_PROPERTIES();
|
||||
|
||||
bool blockSelection;
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"blockSelection", blockSelection), L"Get 'blockSelection' variant");
|
||||
|
||||
std::vector<SMALL_RECT> expected{};
|
||||
if (blockSelection)
|
||||
{
|
||||
expected.push_back({ 1, 0, 7, 0 });
|
||||
expected.push_back({ 1, 1, 8, 1 }); // expand right
|
||||
expected.push_back({ 1, 2, 7, 2 });
|
||||
expected.push_back({ 0, 3, 7, 3 }); // expand left
|
||||
expected.push_back({ 1, 4, 7, 4 });
|
||||
}
|
||||
else
|
||||
{
|
||||
expected.push_back({ 1, 0, 19, 0 });
|
||||
expected.push_back({ 0, 1, 19, 1 });
|
||||
expected.push_back({ 0, 2, 19, 2 });
|
||||
expected.push_back({ 0, 3, 19, 3 });
|
||||
expected.push_back({ 0, 4, 7, 4 });
|
||||
}
|
||||
|
||||
COORD start{ 1, 0 };
|
||||
COORD end{ 7, 4 };
|
||||
const auto result = _buffer->GetTextRects(start, end, blockSelection);
|
||||
VERIFY_ARE_EQUAL(expected.size(), result.size());
|
||||
for (size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(expected.at(i), result.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,7 +604,7 @@ void VtRendererTest::Xterm256TestCursor()
|
||||
clusters.emplace_back(std::wstring_view{ &line[i], 1 }, static_cast<size_t>(rgWidths[i]));
|
||||
}
|
||||
|
||||
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false));
|
||||
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false, false));
|
||||
|
||||
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
|
||||
VERIFY_SUCCEEDED(engine->_MoveCursor({ 10, 1 }));
|
||||
@@ -1020,7 +1020,7 @@ void VtRendererTest::XtermTestCursor()
|
||||
clusters.emplace_back(std::wstring_view{ &line[i], 1 }, static_cast<size_t>(rgWidths[i]));
|
||||
}
|
||||
|
||||
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false));
|
||||
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false, false));
|
||||
|
||||
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
|
||||
VERIFY_SUCCEEDED(engine->_MoveCursor({ 10, 1 }));
|
||||
@@ -1250,7 +1250,7 @@ void VtRendererTest::WinTelnetTestCursor()
|
||||
clusters.emplace_back(std::wstring_view{ &line[i], 1 }, static_cast<size_t>(rgWidths[i]));
|
||||
}
|
||||
|
||||
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false));
|
||||
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false, false));
|
||||
|
||||
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
|
||||
VERIFY_SUCCEEDED(engine->_MoveCursor({ 10, 1 }));
|
||||
@@ -1315,8 +1315,8 @@ void VtRendererTest::TestWrapping()
|
||||
clusters2.emplace_back(std::wstring_view{ &line2[i], 1 }, static_cast<size_t>(rgWidths[i]));
|
||||
}
|
||||
|
||||
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters1.data(), clusters1.size() }, { 0, 0 }, false));
|
||||
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters2.data(), clusters2.size() }, { 0, 1 }, false));
|
||||
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters1.data(), clusters1.size() }, { 0, 0 }, false, false));
|
||||
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters2.data(), clusters2.size() }, { 0, 1 }, false, false));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,8 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid
|
||||
|
||||
[[nodiscard]] HRESULT BgfxEngine::PaintBufferLine(const std::basic_string_view<Cluster> clusters,
|
||||
const COORD coord,
|
||||
const bool /*trimLeft*/) noexcept
|
||||
const bool /*trimLeft*/,
|
||||
const bool /*lineWrapped*/) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -51,7 +51,8 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT PaintBackground() noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferLine(const std::basic_string_view<Cluster> clusters,
|
||||
const COORD coord,
|
||||
const bool trimLeft) noexcept override;
|
||||
const bool trimLeft,
|
||||
const bool lineWrapped) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferGridLines(GridLines const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override;
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ HRESULT ScreenInfoUiaProvider::GetSelectionRange(_In_ IRawElementProviderSimple*
|
||||
const auto start = _pData->GetSelectionAnchor();
|
||||
|
||||
// we need to make end exclusive
|
||||
auto end = _pData->GetEndSelectionPosition();
|
||||
auto end = _pData->GetSelectionEnd();
|
||||
_pData->GetTextBuffer().GetSize().IncrementInBounds(end, true);
|
||||
|
||||
// TODO GH #4509: Box Selection is misrepresented here as a line selection.
|
||||
|
||||
@@ -597,15 +597,23 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
|
||||
// Retrieve the cell information iterator limited to just this line we want to redraw.
|
||||
auto it = buffer.GetCellDataAt(bufferLine.Origin(), bufferLine);
|
||||
|
||||
// Calculate if two things are true:
|
||||
// 1. this row wrapped
|
||||
// 2. We're painting the last col of the row.
|
||||
// In that case, set lineWrapped=true for the _PaintBufferOutputHelper call.
|
||||
const auto lineWrapped = (buffer.GetRowByOffset(bufferLine.Origin().Y).GetCharRow().WasWrapForced()) &&
|
||||
(bufferLine.RightExclusive() == buffer.GetSize().Width());
|
||||
|
||||
// Ask the helper to paint through this specific line.
|
||||
_PaintBufferOutputHelper(pEngine, it, screenLine.Origin());
|
||||
_PaintBufferOutputHelper(pEngine, it, screenLine.Origin(), lineWrapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
||||
TextBufferCellIterator it,
|
||||
const COORD target)
|
||||
const COORD target,
|
||||
const bool lineWrapped)
|
||||
{
|
||||
// If we have valid data, let's figure out how to draw it.
|
||||
if (it)
|
||||
@@ -694,7 +702,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
||||
} while (it);
|
||||
|
||||
// Do the painting.
|
||||
THROW_IF_FAILED(pEngine->PaintBufferLine({ clusters.data(), clusters.size() }, screenPoint, trimLeft));
|
||||
THROW_IF_FAILED(pEngine->PaintBufferLine({ clusters.data(), clusters.size() }, screenPoint, trimLeft, lineWrapped));
|
||||
|
||||
// If we're allowed to do grid drawing, draw that now too (since it will be coupled with the color data)
|
||||
if (_pData->IsGridLineDrawingAllowed())
|
||||
@@ -843,7 +851,7 @@ void Renderer::_PaintOverlay(IRenderEngine& engine,
|
||||
|
||||
auto it = overlay.buffer.GetCellLineDataAt(source);
|
||||
|
||||
_PaintBufferOutputHelper(&engine, it, target);
|
||||
_PaintBufferOutputHelper(&engine, it, target, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,8 @@ namespace Microsoft::Console::Render
|
||||
|
||||
void _PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
||||
TextBufferCellIterator it,
|
||||
const COORD target);
|
||||
const COORD target,
|
||||
const bool lineWrapped);
|
||||
|
||||
static IRenderEngine::GridLines s_GetGridlines(const TextAttribute& textAttribute) noexcept;
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ RenderThread::RenderThread() :
|
||||
_hEvent(nullptr),
|
||||
_hPaintCompletedEvent(nullptr),
|
||||
_fKeepRunning(true),
|
||||
_hPaintEnabledEvent(nullptr)
|
||||
_hPaintEnabledEvent(nullptr),
|
||||
_fNextFrameRequested(false),
|
||||
_fWaiting(false)
|
||||
{
|
||||
_fNextFrameRequested.clear();
|
||||
_fPainting.clear();
|
||||
}
|
||||
|
||||
RenderThread::~RenderThread()
|
||||
@@ -161,20 +161,45 @@ DWORD WINAPI RenderThread::_ThreadProc()
|
||||
{
|
||||
WaitForSingleObject(_hPaintEnabledEvent, INFINITE);
|
||||
|
||||
// Skip waiting if next frame is requested.
|
||||
if (_fNextFrameRequested.test_and_set(std::memory_order_relaxed))
|
||||
if (!_fNextFrameRequested.exchange(false))
|
||||
{
|
||||
_fNextFrameRequested.clear(std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
{
|
||||
WaitForSingleObject(_hEvent, INFINITE);
|
||||
// <--
|
||||
// If `NotifyPaint` is called at this point, then it will not
|
||||
// set the event because `_fWaiting` is not `true` yet so we have
|
||||
// to check again below.
|
||||
|
||||
_fWaiting.store(true);
|
||||
|
||||
// check again now (see comment above)
|
||||
if (!_fNextFrameRequested.exchange(false))
|
||||
{
|
||||
// Wait until a next frame is requested.
|
||||
WaitForSingleObject(_hEvent, INFINITE);
|
||||
}
|
||||
|
||||
// <--
|
||||
// If `NotifyPaint` is called at this point, then it _will_ set
|
||||
// the event because `_fWaiting` is `true`, but we're not waiting
|
||||
// anymore!
|
||||
// This can probably happen quite often: imagine a scenario where
|
||||
// we are waiting, and the terminal calls `NotifyPaint` twice
|
||||
// very quickly.
|
||||
// In that case, both calls might end up calling `SetEvent`. The
|
||||
// first one will resume this thread and the second one will
|
||||
// `SetEvent` the event. So the next time we wait, the event will
|
||||
// already be set and we won't actually wait.
|
||||
// Because it can happen often, and because rendering is an
|
||||
// expensive operation, we should reset the event to not render
|
||||
// again if nothing changed.
|
||||
|
||||
_fWaiting.store(false);
|
||||
|
||||
// see comment above
|
||||
ResetEvent(_hEvent);
|
||||
}
|
||||
|
||||
ResetEvent(_hPaintCompletedEvent);
|
||||
|
||||
_fPainting.test_and_set(std::memory_order_acquire);
|
||||
|
||||
LOG_IF_FAILED(_pRenderer->PaintFrame());
|
||||
|
||||
SetEvent(_hPaintCompletedEvent);
|
||||
@@ -184,8 +209,6 @@ DWORD WINAPI RenderThread::_ThreadProc()
|
||||
{
|
||||
Sleep(s_FrameLimitMilliseconds);
|
||||
}
|
||||
|
||||
_fPainting.clear(std::memory_order_release);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
@@ -193,15 +216,14 @@ DWORD WINAPI RenderThread::_ThreadProc()
|
||||
|
||||
void RenderThread::NotifyPaint()
|
||||
{
|
||||
// If we are currently painting a frame, set _fNextFrameRequested flag
|
||||
// to indicate we want to paint next frame immediately.
|
||||
if (_fPainting.test_and_set(std::memory_order_acquire))
|
||||
if (_fWaiting.load())
|
||||
{
|
||||
_fNextFrameRequested.test_and_set(std::memory_order_relaxed);
|
||||
return;
|
||||
SetEvent(_hEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
_fNextFrameRequested.store(true);
|
||||
}
|
||||
|
||||
SetEvent(_hEvent);
|
||||
}
|
||||
|
||||
void RenderThread::EnablePainting()
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Microsoft::Console::Render
|
||||
IRenderer* _pRenderer; // Non-ownership pointer
|
||||
|
||||
bool _fKeepRunning;
|
||||
std::atomic_flag _fNextFrameRequested;
|
||||
std::atomic_flag _fPainting;
|
||||
std::atomic<bool> _fNextFrameRequested;
|
||||
std::atomic<bool> _fWaiting;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1220,7 +1220,8 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
// - S_OK or relevant DirectX error
|
||||
[[nodiscard]] HRESULT DxEngine::PaintBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
COORD const coord,
|
||||
const bool /*trimLeft*/) noexcept
|
||||
const bool /*trimLeft*/,
|
||||
const bool /*lineWrapped*/) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -76,7 +76,8 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT PaintBackground() noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
COORD const coord,
|
||||
bool const fTrimLeft) noexcept override;
|
||||
bool const fTrimLeft,
|
||||
const bool lineWrapped) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT PaintBufferGridLines(GridLines const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override;
|
||||
|
||||
@@ -44,7 +44,8 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT PaintBackground() noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
const COORD coord,
|
||||
const bool trimLeft) noexcept override;
|
||||
const bool trimLeft,
|
||||
const bool lineWrapped) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferGridLines(const GridLines lines,
|
||||
const COLORREF color,
|
||||
const size_t cchLine,
|
||||
|
||||
@@ -286,7 +286,8 @@ using namespace Microsoft::Console::Render;
|
||||
//#define MAX_POLY_LINES 80
|
||||
[[nodiscard]] HRESULT GdiEngine::PaintBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
const COORD coord,
|
||||
const bool trimLeft) noexcept
|
||||
const bool trimLeft,
|
||||
const bool /*lineWrapped*/) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -93,7 +93,8 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] virtual HRESULT PaintBackground() noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
const COORD coord,
|
||||
const bool fTrimLeft) noexcept = 0;
|
||||
const bool fTrimLeft,
|
||||
const bool lineWrapped) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PaintBufferGridLines(const GridLines lines,
|
||||
const COLORREF color,
|
||||
const size_t cchLine,
|
||||
|
||||
@@ -276,7 +276,8 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
|
||||
// - S_FALSE
|
||||
[[nodiscard]] HRESULT UiaEngine::PaintBufferLine(std::basic_string_view<Cluster> const /*clusters*/,
|
||||
COORD const /*coord*/,
|
||||
const bool /*trimLeft*/) noexcept
|
||||
const bool /*trimLeft*/,
|
||||
const bool /*lineWrapped*/) noexcept
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,8 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT PaintBackground() noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
COORD const coord,
|
||||
bool const fTrimLeft) noexcept override;
|
||||
bool const fTrimLeft,
|
||||
const bool lineWrapped) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferGridLines(GridLines const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override;
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
_ColorTable(ColorTable),
|
||||
_cColorTable(cColorTable),
|
||||
_fUseAsciiOnly(fUseAsciiOnly),
|
||||
_previousLineWrapped(false),
|
||||
_usingUnderLine(false),
|
||||
_needToDisableCursor(false),
|
||||
_lastCursorIsVisible(false),
|
||||
@@ -235,6 +234,8 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
_trace.TraceMoveCursor(_lastText, coord);
|
||||
|
||||
if (coord.X != _lastText.X || coord.Y != _lastText.Y)
|
||||
{
|
||||
if (coord.X == 0 && coord.Y == 0)
|
||||
@@ -248,8 +249,15 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
|
||||
// If the previous line wrapped, then the cursor is already at this
|
||||
// position, we just don't know it yet. Don't emit anything.
|
||||
if (_previousLineWrapped)
|
||||
bool previousLineWrapped = false;
|
||||
if (_wrappedRow.has_value())
|
||||
{
|
||||
previousLineWrapped = coord.Y == _wrappedRow.value() + 1;
|
||||
}
|
||||
|
||||
if (previousLineWrapped)
|
||||
{
|
||||
_trace.TraceWrapped();
|
||||
hr = S_OK;
|
||||
}
|
||||
else
|
||||
@@ -312,7 +320,11 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
_newBottomLine = false;
|
||||
}
|
||||
_deferredCursorPos = INVALID_COORDS;
|
||||
|
||||
_wrappedRow = std::nullopt;
|
||||
|
||||
_delayedEolWrap = false;
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -430,15 +442,19 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
// - trimLeft - This specifies whether to trim one character width off the left
|
||||
// side of the output. Used for drawing the right-half only of a
|
||||
// double-wide character.
|
||||
// - lineWrapped: true if this run we're painting is the end of a line that
|
||||
// wrapped. If we're not painting the last column of a wrapped line, then this
|
||||
// will be false.
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT XtermEngine::PaintBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
const COORD coord,
|
||||
const bool /*trimLeft*/) noexcept
|
||||
const bool /*trimLeft*/,
|
||||
const bool lineWrapped) noexcept
|
||||
{
|
||||
return _fUseAsciiOnly ?
|
||||
VtEngine::_PaintAsciiBufferLine(clusters, coord) :
|
||||
VtEngine::_PaintUtf8BufferLine(clusters, coord);
|
||||
VtEngine::_PaintUtf8BufferLine(clusters, coord, lineWrapped);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -48,7 +48,8 @@ namespace Microsoft::Console::Render
|
||||
const bool isSettingDefaultBrushes) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
const COORD coord,
|
||||
const bool trimLeft) noexcept override;
|
||||
const bool trimLeft,
|
||||
const bool lineWrapped) noexcept override;
|
||||
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;
|
||||
@@ -59,7 +60,6 @@ namespace Microsoft::Console::Render
|
||||
const COLORREF* const _ColorTable;
|
||||
const WORD _cColorTable;
|
||||
const bool _fUseAsciiOnly;
|
||||
bool _previousLineWrapped;
|
||||
bool _usingUnderLine;
|
||||
bool _needToDisableCursor;
|
||||
bool _lastCursorIsVisible;
|
||||
|
||||
@@ -115,11 +115,15 @@ using namespace Microsoft::Console::Types;
|
||||
// - trimLeft - This specifies whether to trim one character width off the left
|
||||
// side of the output. Used for drawing the right-half only of a
|
||||
// double-wide character.
|
||||
// - lineWrapped: true if this run we're painting is the end of a line that
|
||||
// wrapped. If we're not painting the last column of a wrapped line, then this
|
||||
// will be false.
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT VtEngine::PaintBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
const COORD coord,
|
||||
const bool /*trimLeft*/) noexcept
|
||||
const bool /*trimLeft*/,
|
||||
const bool /*lineWrapped*/) noexcept
|
||||
{
|
||||
return VtEngine::_PaintAsciiBufferLine(clusters, coord);
|
||||
}
|
||||
@@ -149,6 +153,8 @@ using namespace Microsoft::Console::Types;
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT VtEngine::PaintCursor(const IRenderEngine::CursorOptions& options) noexcept
|
||||
{
|
||||
_trace.TracePaintCursor(options.coordCursor);
|
||||
|
||||
// MSFT:15933349 - Send the terminal the updated cursor information, if it's changed.
|
||||
LOG_IF_FAILED(_MoveCursor(options.coordCursor));
|
||||
|
||||
@@ -369,15 +375,14 @@ using namespace Microsoft::Console::Types;
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from writing pipe.
|
||||
[[nodiscard]] HRESULT VtEngine::_PaintUtf8BufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
const COORD coord) noexcept
|
||||
const COORD coord,
|
||||
const bool lineWrapped) noexcept
|
||||
{
|
||||
if (coord.Y < _virtualTop)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
RETURN_IF_FAILED(_MoveCursor(coord));
|
||||
|
||||
std::wstring unclusteredString;
|
||||
unclusteredString.reserve(clusters.size());
|
||||
short totalWidth = 0;
|
||||
@@ -445,10 +450,37 @@ using namespace Microsoft::Console::Types;
|
||||
(totalWidth - numSpaces) :
|
||||
totalWidth;
|
||||
|
||||
if (cchActual == 0)
|
||||
{
|
||||
// If the previous row wrapped, but this line is empty, then we actually
|
||||
// do want to move the cursor down. Otherwise, we'll possibly end up
|
||||
// accidentally erasing the last character from the previous line, as
|
||||
// the cursor is still waiting on that character for the next character
|
||||
// to follow it.
|
||||
_wrappedRow = std::nullopt;
|
||||
}
|
||||
|
||||
// Move the cursor to the start of this run.
|
||||
RETURN_IF_FAILED(_MoveCursor(coord));
|
||||
|
||||
// Write the actual text string
|
||||
std::wstring wstr = std::wstring(unclusteredString.data(), cchActual);
|
||||
RETURN_IF_FAILED(VtEngine::_WriteTerminalUtf8(wstr));
|
||||
|
||||
// If we've written text to the last column of the viewport, then mark
|
||||
// that we've wrapped this line. The next time we attempt to move the
|
||||
// cursor, if we're trying to move it to the start of the next line,
|
||||
// we'll remember that this line was wrapped, and not manually break the
|
||||
// line.
|
||||
// Don't do this if the last character we're writing is a space - The last
|
||||
// char will always be a space, but if we see that, we shouldn't wrap.
|
||||
const short lastWrittenChar = base::ClampAdd(_lastText.X, base::ClampSub(totalWidth, numSpaces));
|
||||
if (lineWrapped &&
|
||||
lastWrittenChar > _lastViewport.RightInclusive())
|
||||
{
|
||||
_wrappedRow = coord.Y;
|
||||
}
|
||||
|
||||
// Update our internal tracker of the cursor's position.
|
||||
// See MSFT:20266233 (which is also GH#357)
|
||||
// If the cursor is at the rightmost column of the terminal, and we write a
|
||||
|
||||
@@ -225,3 +225,48 @@ void RenderTracing::TraceLastText(const COORD lastTextPos) const
|
||||
UNREFERENCED_PARAMETER(lastTextPos);
|
||||
#endif UNIT_TESTING
|
||||
}
|
||||
void RenderTracing::TraceMoveCursor(const COORD lastTextPos, const COORD cursor) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
const auto lastTextStr = _CoordToString(lastTextPos);
|
||||
const auto lastText = lastTextStr.c_str();
|
||||
|
||||
const auto cursorStr = _CoordToString(cursor);
|
||||
const auto cursorPos = cursorStr.c_str();
|
||||
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceMoveCursor",
|
||||
TraceLoggingString(lastText),
|
||||
TraceLoggingString(cursorPos),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(lastTextPos);
|
||||
UNREFERENCED_PARAMETER(cursor);
|
||||
#endif UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceWrapped() const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
const auto* const msg = "Wrapped instead of \\r\\n";
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceWrapped",
|
||||
TraceLoggingString(msg),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
#else
|
||||
#endif UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TracePaintCursor(const COORD coordCursor) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
const auto cursorPosString = _CoordToString(coordCursor);
|
||||
const auto cursorPos = cursorPosString.c_str();
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TracePaintCursor",
|
||||
TraceLoggingString(cursorPos),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(coordCursor);
|
||||
#endif UNIT_TESTING
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void TraceString(const std::string_view& str) const;
|
||||
void TraceInvalidate(const Microsoft::Console::Types::Viewport view) const;
|
||||
void TraceLastText(const COORD lastText) const;
|
||||
void TraceMoveCursor(const COORD lastText, const COORD cursor) const;
|
||||
void TraceWrapped() const;
|
||||
void TracePaintCursor(const COORD coordCursor) const;
|
||||
void TraceInvalidateAll(const Microsoft::Console::Types::Viewport view) const;
|
||||
void TraceTriggerCircling(const bool newFrame) const;
|
||||
void TraceStartPaint(const bool quickReturn,
|
||||
|
||||
@@ -65,7 +65,8 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT PaintBackground() noexcept override;
|
||||
[[nodiscard]] virtual HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
const COORD coord,
|
||||
const bool trimLeft) noexcept override;
|
||||
const bool trimLeft,
|
||||
const bool lineWrapped) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferGridLines(const GridLines lines,
|
||||
const COLORREF color,
|
||||
const size_t cchLine,
|
||||
@@ -144,6 +145,8 @@ namespace Microsoft::Console::Render
|
||||
Microsoft::Console::VirtualTerminal::RenderTracing _trace;
|
||||
bool _inResizeRequest{ false };
|
||||
|
||||
std::optional<short> _wrappedRow{ std::nullopt };
|
||||
|
||||
bool _delayedEolWrap{ false };
|
||||
|
||||
[[nodiscard]] HRESULT _Write(std::string_view const str) noexcept;
|
||||
@@ -214,7 +217,8 @@ namespace Microsoft::Console::Render
|
||||
bool _WillWriteSingleChar() const;
|
||||
|
||||
[[nodiscard]] HRESULT _PaintUtf8BufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
const COORD coord) noexcept;
|
||||
const COORD coord,
|
||||
const bool lineWrapped) noexcept;
|
||||
|
||||
[[nodiscard]] HRESULT _PaintAsciiBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
const COORD coord) noexcept;
|
||||
|
||||
@@ -260,7 +260,8 @@ bool WddmConEngine::IsInitialized()
|
||||
|
||||
[[nodiscard]] HRESULT WddmConEngine::PaintBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
const COORD coord,
|
||||
const bool /*trimLeft*/) noexcept
|
||||
const bool /*trimLeft*/,
|
||||
const bool /*lineWrapped*/) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -43,7 +43,8 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT PaintBackground() noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
|
||||
const COORD coord,
|
||||
const bool trimLeft) noexcept override;
|
||||
const bool trimLeft,
|
||||
const bool lineWrapped) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferGridLines(GridLines const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override;
|
||||
[[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
class AdaptDefaults
|
||||
{
|
||||
public:
|
||||
virtual ~AdaptDefaults() = default;
|
||||
virtual void Print(const wchar_t wch) = 0;
|
||||
// These characters need to be mutable so that they can be processed by the TerminalInput translater.
|
||||
virtual void PrintString(const std::wstring_view string) = 0;
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
class ConGetSet
|
||||
{
|
||||
public:
|
||||
virtual ~ConGetSet() = default;
|
||||
virtual bool GetConsoleCursorInfo(CONSOLE_CURSOR_INFO& cursorInfo) const = 0;
|
||||
virtual bool GetConsoleScreenBufferInfoEx(CONSOLE_SCREEN_BUFFER_INFOEX& screenBufferInfo) const = 0;
|
||||
virtual bool SetConsoleScreenBufferInfoEx(const CONSOLE_SCREEN_BUFFER_INFOEX& screenBufferInfo) = 0;
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Microsoft::Console::Types
|
||||
virtual void ClearSelection() = 0;
|
||||
virtual void SelectNewRegion(const COORD coordStart, const COORD coordEnd) = 0;
|
||||
virtual const COORD GetSelectionAnchor() const noexcept = 0;
|
||||
virtual const COORD GetEndSelectionPosition() const noexcept = 0;
|
||||
virtual const COORD GetSelectionEnd() const noexcept = 0;
|
||||
virtual void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute attr) = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ HRESULT TermControlUiaProvider::GetSelectionRange(_In_ IRawElementProviderSimple
|
||||
const auto start = _pData->GetSelectionAnchor();
|
||||
|
||||
// we need to make end exclusive
|
||||
auto end = _pData->GetEndSelectionPosition();
|
||||
auto end = _pData->GetSelectionEnd();
|
||||
_pData->GetTextBuffer().GetSize().IncrementInBounds(end, true);
|
||||
|
||||
// TODO GH #4509: Box Selection is misrepresented here as a line selection.
|
||||
|
||||
Reference in New Issue
Block a user