Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSC 8 support for conhost and terminal #7251

Merged
merged 35 commits into from
Sep 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
16720a0
osc 8 support for conhost and terminal
PankajBhojwani Aug 11, 2020
51bd477
Correctly gets buffer position on click now
PankajBhojwani Aug 11, 2020
d268322
small fixes
PankajBhojwani Aug 12, 2020
3090b1c
more small fixes
PankajBhojwani Aug 12, 2020
b81014e
Merge branch 'master' of https://github.com/microsoft/terminal into d…
PankajBhojwani Aug 12, 2020
015fb8f
tryna pass pipeline checks
PankajBhojwani Aug 12, 2020
4a74f83
send event upon hyperlink click, allow custom ids, remove obsolete re…
PankajBhojwani Aug 15, 2020
049ee61
text buffer tests
PankajBhojwani Aug 17, 2020
c18f32a
spell
PankajBhojwani Aug 17, 2020
bd74eff
output engine test
PankajBhojwani Aug 17, 2020
0327750
more tests
PankajBhojwani Aug 17, 2020
405066e
resolve conflict
PankajBhojwani Aug 17, 2020
5995275
fixes
PankajBhojwani Aug 18, 2020
409b358
some requested changes
PankajBhojwani Aug 18, 2020
dfc8771
more changes
PankajBhojwani Aug 18, 2020
c28d2a6
conflict resolution
PankajBhojwani Aug 19, 2020
8a105d7
bug fix
PankajBhojwani Aug 19, 2020
7e96737
small changes
PankajBhojwani Aug 21, 2020
e19fbe5
fixed hyperlink not working after resize
PankajBhojwani Aug 21, 2020
bc70439
fixes
PankajBhojwani Aug 21, 2020
9d65b32
noexcept
PankajBhojwani Aug 21, 2020
aefb2e0
more noexcept
PankajBhojwani Aug 21, 2020
d0de3d5
except
PankajBhojwani Aug 21, 2020
eb3cda5
optimize prune hyperlinks
PankajBhojwani Aug 21, 2020
63723e8
hyperlinks is not a recognized word??
PankajBhojwani Aug 21, 2020
01928d3
check ctrl first then shift
PankajBhojwani Aug 24, 2020
121f51c
fuzzer
PankajBhojwani Aug 24, 2020
52e5743
fix
PankajBhojwani Aug 24, 2020
d1b0e83
terminator, blank space fixes
PankajBhojwani Aug 25, 2020
82cc197
ctrl+shift when no uri updates selection
PankajBhojwani Aug 26, 2020
4ecbfc0
addressing comments
PankajBhojwani Aug 27, 2020
b75f189
SGR 0 fixes
PankajBhojwani Aug 27, 2020
5dcd706
id resolution
PankajBhojwani Aug 27, 2020
a099fc7
added roundtrip test
PankajBhojwani Aug 28, 2020
5620c01
spell
PankajBhojwani Aug 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spell-check/dictionary/apis.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ syscall
tmp
tx
userenv
wcstoui
XDocument
XElement
1 change: 1 addition & 0 deletions .github/actions/spell-check/dictionary/dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -186988,6 +186988,7 @@ hyperleucocytotic
hyperleukocytosis
hyperlexis
hyperlink
hyperlinks
hyperlinking
hyperlipaemia
hyperlipaemic
Expand Down
17 changes: 17 additions & 0 deletions src/buffer/out/AttrRow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,23 @@ size_t ATTR_ROW::FindAttrIndex(const size_t index, size_t* const pApplies) const
return runPos - _list.cbegin();
}

// Routine Description:
// - Finds the hyperlink IDs present in this row and returns them
// Return value:
// - An unordered set containing the hyperlink IDs present in this row
std::unordered_set<uint16_t> ATTR_ROW::GetHyperlinks()
{
std::unordered_set<uint16_t> ids;
for (const auto& run : _list)
{
if (run.GetAttributes().IsHyperlink())
{
ids.emplace(run.GetAttributes().GetHyperlinkId());
}
}
return ids;
}

// Routine Description:
// - Sets the attributes (colors) of all character positions from the given position through the end of the row.
// Arguments:
Expand Down
2 changes: 2 additions & 0 deletions src/buffer/out/AttrRow.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class ATTR_ROW final
size_t FindAttrIndex(const size_t index,
size_t* const pApplies) const;

std::unordered_set<uint16_t> GetHyperlinks();

bool SetAttrToEnd(const UINT iStart, const TextAttribute attr);
void ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith) noexcept;

Expand Down
41 changes: 39 additions & 2 deletions src/buffer/out/TextAttribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const gsl::span<
return { fg, bg };
}

// Method description:
// - Tells us whether the text is a hyperlink or not
// Return value:
// - True if it is a hyperlink, false otherwise
bool TextAttribute::IsHyperlink() const noexcept
{
// All non-hyperlink text have a default hyperlinkId of 0 while
// all hyperlink text have a non-zero hyperlinkId
return _hyperlinkId != 0;
}

TextColor TextAttribute::GetForeground() const noexcept
{
return _foreground;
Expand All @@ -122,6 +133,15 @@ TextColor TextAttribute::GetBackground() const noexcept
return _background;
}

// Method description:
// - Retrieves the hyperlink ID of the text
// Return value:
// - The hyperlink ID
uint16_t TextAttribute::GetHyperlinkId() const noexcept
{
return _hyperlinkId;
}

void TextAttribute::SetForeground(const TextColor foreground) noexcept
{
_foreground = foreground;
Expand Down Expand Up @@ -174,6 +194,15 @@ void TextAttribute::SetColor(const COLORREF rgbColor, const bool fIsForeground)
}
}

// Method description:
// - Sets the hyperlink ID of the text
// Arguments:
// - id - the id we wish to set
void TextAttribute::SetHyperlinkId(uint16_t id) noexcept
{
_hyperlinkId = id;
}

bool TextAttribute::IsLeadingByte() const noexcept
{
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_LEADING_BYTE);
Expand Down Expand Up @@ -336,6 +365,14 @@ void TextAttribute::SetDefaultBackground() noexcept
_background = TextColor();
}

// Method description:
// - Resets only the meta and extended attributes
void TextAttribute::SetDefaultMetaAttrs() noexcept
{
_extendedAttrs = ExtendedAttributes::Normal;
_wAttrLegacy = 0;
}

// Method Description:
// - Returns true if this attribute indicates its background is the "default"
// background. Its _rgbBackground will contain the actual value of the
Expand All @@ -356,6 +393,6 @@ bool TextAttribute::BackgroundIsDefault() const noexcept
// requires for most erasing and filling operations.
void TextAttribute::SetStandardErase() noexcept
{
_extendedAttrs = ExtendedAttributes::Normal;
_wAttrLegacy = 0;
SetDefaultMetaAttrs();
_hyperlinkId = 0;
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
}
24 changes: 18 additions & 6 deletions src/buffer/out/TextAttribute.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,17 @@ class TextAttribute final
_wAttrLegacy{ 0 },
_foreground{},
_background{},
_extendedAttrs{ ExtendedAttributes::Normal }
_extendedAttrs{ ExtendedAttributes::Normal },
_hyperlinkId{ 0 }
{
}

explicit constexpr TextAttribute(const WORD wLegacyAttr) noexcept :
_wAttrLegacy{ gsl::narrow_cast<WORD>(wLegacyAttr & META_ATTRS) },
_foreground{ s_LegacyIndexOrDefault(wLegacyAttr & FG_ATTRS, s_legacyDefaultForeground) },
_background{ s_LegacyIndexOrDefault((wLegacyAttr & BG_ATTRS) >> 4, s_legacyDefaultBackground) },
_extendedAttrs{ ExtendedAttributes::Normal }
_extendedAttrs{ ExtendedAttributes::Normal },
_hyperlinkId{ 0 }
{
// If we're given lead/trailing byte information with the legacy color, strip it.
WI_ClearAllFlags(_wAttrLegacy, COMMON_LVB_SBCSDBCS);
Expand All @@ -55,7 +57,8 @@ class TextAttribute final
_wAttrLegacy{ 0 },
_foreground{ rgbForeground },
_background{ rgbBackground },
_extendedAttrs{ ExtendedAttributes::Normal }
_extendedAttrs{ ExtendedAttributes::Normal },
_hyperlinkId{ 0 }
{
}

Expand Down Expand Up @@ -112,8 +115,11 @@ class TextAttribute final

ExtendedAttributes GetExtendedAttributes() const noexcept;

bool IsHyperlink() const noexcept;

TextColor GetForeground() const noexcept;
TextColor GetBackground() const noexcept;
uint16_t GetHyperlinkId() const noexcept;
void SetForeground(const TextColor foreground) noexcept;
void SetBackground(const TextColor background) noexcept;
void SetForeground(const COLORREF rgbForeground) noexcept;
Expand All @@ -123,9 +129,11 @@ class TextAttribute final
void SetIndexedForeground256(const BYTE fgIndex) noexcept;
void SetIndexedBackground256(const BYTE bgIndex) noexcept;
void SetColor(const COLORREF rgbColor, const bool fIsForeground) noexcept;
void SetHyperlinkId(uint16_t id) noexcept;

void SetDefaultForeground() noexcept;
void SetDefaultBackground() noexcept;
void SetDefaultMetaAttrs() noexcept;

bool BackgroundIsDefault() const noexcept;

Expand All @@ -147,7 +155,8 @@ class TextAttribute final
(_wAttrLegacy & META_ATTRS) == (other._wAttrLegacy & META_ATTRS) &&
((checkForeground && _foreground == other._foreground) ||
(!checkForeground && _background == other._background)) &&
_extendedAttrs == other._extendedAttrs;
_extendedAttrs == other._extendedAttrs &&
IsHyperlink() == other.IsHyperlink();
}

constexpr bool IsAnyGridLineEnabled() const noexcept
Expand All @@ -169,6 +178,8 @@ class TextAttribute final
TextColor _background;
ExtendedAttributes _extendedAttrs;

uint16_t _hyperlinkId;

#ifdef UNIT_TESTING
friend class TextBufferTests;
friend class TextAttributeTests;
Expand All @@ -182,7 +193,7 @@ class TextAttribute final
// 4 for _foreground
// 4 for _background
// 1 for _extendedAttrs
static_assert(sizeof(TextAttribute) <= 11 * sizeof(BYTE), "We should only need 11B for an entire TextColor. Any more than that is just waste");
static_assert(sizeof(TextAttribute) <= 13 * sizeof(BYTE), "We should only need 13B for an entire TextAttribute. We may need to increment this in the future as we add additional attributes");

enum class TextAttributeBehavior
{
Expand All @@ -196,7 +207,8 @@ constexpr bool operator==(const TextAttribute& a, const TextAttribute& b) noexce
return a._wAttrLegacy == b._wAttrLegacy &&
a._foreground == b._foreground &&
a._background == b._background &&
a._extendedAttrs == b._extendedAttrs;
a._extendedAttrs == b._extendedAttrs &&
a._hyperlinkId == b._hyperlinkId;
}

constexpr bool operator!=(const TextAttribute& a, const TextAttribute& b) noexcept
Expand Down
150 changes: 148 additions & 2 deletions src/buffer/out/textBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ TextBuffer::TextBuffer(const COORD screenBufferSize,
_storage{},
_unicodeStorage{},
_renderTarget{ renderTarget },
_size{}
_size{},
_currentHyperlinkId{ 1 }
{
// initialize ROWs
for (size_t i = 0; i < static_cast<size_t>(screenBufferSize.Y); ++i)
Expand Down Expand Up @@ -551,7 +552,10 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode)
// to the logical position 0 in the window (cursor coordinates and all other coordinates).
_renderTarget.TriggerCircling();

// First, clean out the old "first row" as it will become the "last row" of the buffer after the circle is performed.
// Prune hyperlinks to delete obsolete references
_PruneHyperlinks();

// Second, clean out the old "first row" as it will become the "last row" of the buffer after the circle is performed.
auto fillAttributes = _currentAttributes;
if (inVtMode)
{
Expand Down Expand Up @@ -1185,6 +1189,46 @@ const COORD TextBuffer::_GetWordEndForSelection(const COORD target, const std::w
return result;
}

void TextBuffer::_PruneHyperlinks()
{
// Check the old first row for hyperlink references
// If there are any, search the entire buffer for the same reference
// If the buffer does not contain the same reference, we can remove that hyperlink from our map
// This way, obsolete hyperlink references are cleared from our hyperlink map instead of hanging around
// Get all the hyperlink references in the row we're erasing
auto firstRowRefs = _storage.at(_firstRow).GetAttrRow().GetHyperlinks();
if (!firstRowRefs.empty())
{
const auto total = TotalRowCount();
// Loop through all the rows in the buffer except the first row -
// we have found all hyperlink references in the first row and put them in refs,
// now we need to search the rest of the buffer (i.e. all the rows except the first)
// to see if those references are anywhere else
for (size_t i = 1; i != total; ++i)
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
{
const auto nextRowRefs = GetRowByOffset(i).GetAttrRow().GetHyperlinks();
for (auto id : nextRowRefs)
{
if (firstRowRefs.find(id) != firstRowRefs.end())
{
firstRowRefs.erase(id);
}
}
if (firstRowRefs.empty())
{
// No more hyperlink references left to search for, terminate early
break;
}
}
}

// Now delete obsolete references from our map
for (auto hyperlinkReference : firstRowRefs)
{
RemoveHyperlinkFromMap(hyperlinkReference);
}
}

// Method Description:
// - Update pos to be the position of the first character of the next word. This is used for accessibility
// Arguments:
Expand Down Expand Up @@ -2142,6 +2186,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
// Finish copying remaining parameters from the old text buffer to the new one
newBuffer.CopyProperties(oldBuffer);
newBuffer.CopyHyperlinkMaps(oldBuffer);

// If we found where to put the cursor while placing characters into the buffer,
// just put the cursor there. Otherwise we have to advance manually.
Expand Down Expand Up @@ -2207,3 +2252,104 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,

return hr;
}

// Method Description:
// - Adds or updates a hyperlink in our hyperlink table
// Arguments:
// - The hyperlink URI, the hyperlink id (could be new or old)
void TextBuffer::AddHyperlinkToMap(std::wstring_view uri, uint16_t id)
{
_hyperlinkMap[id] = uri;
}

// Method Description:
// - Retrieves the URI associated with a particular hyperlink ID
// Arguments:
// - The hyperlink ID
// Return Value:
// - The URI
std::wstring TextBuffer::GetHyperlinkUriFromId(uint16_t id) const
{
return _hyperlinkMap.at(id);
}

// Method description:
// - Provides the hyperlink ID to be assigned as a text attribute, based on the optional custom id provided
// Arguments:
// - The user-defined id
// Return value:
// - The internal hyperlink ID
uint16_t TextBuffer::GetHyperlinkId(std::wstring_view params)
{
uint16_t id = 0;
if (params.empty())
{
// no custom id specified, return our internal count
id = _currentHyperlinkId;
++_currentHyperlinkId;
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
// assign _currentHyperlinkId if the custom id does not already exist
const auto result = _hyperlinkCustomIdMap.emplace(params, _currentHyperlinkId);
if (result.second)
{
// the custom id did not already exist
++_currentHyperlinkId;
}
id = (*(result.first)).second;
}
// _currentHyperlinkId could overflow, make sure its not 0
if (_currentHyperlinkId == 0)
{
++_currentHyperlinkId;
}
return id;
}

// Method Description:
// - Removes a hyperlink from the hyperlink map and the associated
// user defined id from the custom id map (if there is one)
// Arguments:
// - The ID of the hyperlink to be removed
void TextBuffer::RemoveHyperlinkFromMap(uint16_t id)
{
_hyperlinkMap.erase(id);
for (const auto& customIdPair : _hyperlinkCustomIdMap)
{
if (customIdPair.second == id)
{
_hyperlinkCustomIdMap.erase(customIdPair.first);
break;
}
}
}

// Method Description:
// - Obtains the custom ID, if there was one, associated with the
// uint16_t id of a hyperlink
// Arguments:
// - The uint16_t id of the hyperlink
// Return Value:
// - The custom ID if there was one, empty string otherwise
std::wstring TextBuffer::GetCustomIdFromId(uint16_t id) const
{
for (auto customIdPair : _hyperlinkCustomIdMap)
{
if (customIdPair.second == id)
{
return customIdPair.first;
}
}
return {};
}

// Method Description:
// - Copies the hyperlink/customID maps of the old buffer into this one
// Arguments:
// - The other buffer
void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other)
{
_hyperlinkMap = other._hyperlinkMap;
_hyperlinkCustomIdMap = other._hyperlinkCustomIdMap;
}
Loading