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 14 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 @@ -48,3 +48,4 @@ syscall
tmp
tx
userenv
wcstoui
30 changes: 30 additions & 0 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
USHORT 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(USHORT id) noexcept
{
_hyperlinkId = id;
}

bool TextAttribute::IsLeadingByte() const noexcept
{
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_LEADING_BYTE);
Expand Down Expand Up @@ -358,4 +387,5 @@ void TextAttribute::SetStandardErase() noexcept
{
_extendedAttrs = ExtendedAttributes::Normal;
_wAttrLegacy = 0;
_hyperlinkId = 0;
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
}
20 changes: 15 additions & 5 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;
USHORT GetHyperlinkId() const noexcept;
void SetForeground(const TextColor foreground) noexcept;
void SetBackground(const TextColor background) noexcept;
void SetForeground(const COLORREF rgbForeground) noexcept;
Expand All @@ -123,6 +129,7 @@ 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(USHORT id) noexcept;

void SetDefaultForeground() noexcept;
void SetDefaultBackground() noexcept;
Expand Down Expand Up @@ -169,6 +176,8 @@ class TextAttribute final
TextColor _background;
ExtendedAttributes _extendedAttrs;

USHORT _hyperlinkId;
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved

#ifdef UNIT_TESTING
friend class TextBufferTests;
friend class TextAttributeTests;
Expand All @@ -182,7 +191,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 +205,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
120 changes: 118 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,51 @@ const COORD TextBuffer::_GetWordEndForSelection(const COORD target, const std::w
return result;
}

void TextBuffer::_PruneHyperlinks()
{
// First, 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
std::unordered_set<USHORT> refs;
const auto firstAttrRow = _storage.at(_firstRow).GetAttrRow();
// Get all the hyperlink references in the row we're erasing
for (auto it = firstAttrRow.begin(); it != firstAttrRow.end(); ++it)
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
{
if (it->IsHyperlink())
{
refs.emplace(it->GetHyperlinkId());
}
}
if (!refs.empty())
{
const auto total = TotalRowCount();
// Loop through all the rows in the buffer
for (size_t i = 1; i != total; ++i)
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
{
const auto attrRow = GetRowByOffset(i).GetAttrRow();
for (auto it = attrRow.begin(); it != attrRow.end(); ++it)
{
if (it->IsHyperlink() && (refs.find(it->GetHyperlinkId()) != refs.end()))
{
refs.erase(it->GetHyperlinkId());
}
}
if (refs.empty())
{
// No more hyperlink references left to search for, terminate early
break;
}
}
}

// Now delete obsolete references from our map
for (auto it = refs.begin(); it != refs.end(); ++it)
{
RemoveHyperlinkFromMap(*it);
}
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
}

// 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 @@ -2207,3 +2256,70 @@ 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, USHORT 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(USHORT 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
USHORT TextBuffer::GetHyperlinkId(std::wstring_view params)
{
USHORT 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 = _customIdMap.emplace(params, _currentHyperlinkId);
if (result.second)
{
// the custom id did not already exist
++_currentHyperlinkId;
}
id = (*(result.first)).second;
}
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(USHORT id)
{
_hyperlinkMap.erase(id);
for (auto it = _customIdMap.begin(); it != _customIdMap.end(); ++it)
{
if (it->second == id)
{
_customIdMap.erase(it->first);
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
break;
}
}
}
11 changes: 11 additions & 0 deletions src/buffer/out/textBuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ class TextBuffer final

const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection = false) const;

void AddHyperlinkToMap(std::wstring_view uri, USHORT id);
std::wstring GetHyperlinkUriFromId(USHORT id) const;
USHORT GetHyperlinkId(std::wstring_view params);
void RemoveHyperlinkFromMap(USHORT id);

class TextAndColor
{
public:
Expand Down Expand Up @@ -188,6 +193,10 @@ class TextBuffer final
// storage location for glyphs that can't fit into the buffer normally
UnicodeStorage _unicodeStorage;

std::unordered_map<USHORT, std::wstring> _hyperlinkMap;
std::unordered_map<std::wstring, USHORT> _customIdMap;
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
USHORT _currentHyperlinkId;

void _RefreshRowIDs(std::optional<SHORT> newRowWidth);

Microsoft::Console::Render::IRenderTarget& _renderTarget;
Expand Down Expand Up @@ -216,6 +225,8 @@ class TextBuffer final
const COORD _GetWordEndForAccessibility(const COORD target, const std::wstring_view wordDelimiters) const;
const COORD _GetWordEndForSelection(const COORD target, const std::wstring_view wordDelimiters) const;

void _PruneHyperlinks();

#ifdef UNIT_TESTING
friend class TextBufferTests;
friend class UiaTextRangeTests;
Expand Down
8 changes: 8 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,8 @@ namespace winrt::TerminalApp::implementation
// Add an event handler when the terminal wants to paste data from the Clipboard.
term.PasteFromClipboard({ this, &TerminalPage::_PasteFromClipboardHandler });

term.OpenHyperlink({ this, &TerminalPage::_OpenHyperlinkHandler });

// Bind Tab events to the TermControl and the Tab's Pane
hostingTab.Initialize(term);

Expand Down Expand Up @@ -1786,6 +1788,12 @@ namespace winrt::TerminalApp::implementation
CATCH_LOG();
}

void TerminalPage::_OpenHyperlinkHandler(const IInspectable /*sender*/, const Microsoft::Terminal::TerminalControl::OpenHyperlinkEventArgs eventArgs)
{
ShellExecute(nullptr, L"open", eventArgs.Uri().c_str(), nullptr, nullptr, SW_SHOWNORMAL);
return;
}

// Method Description:
// - Copy text from the focused terminal to the Windows Clipboard
// Arguments:
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ namespace winrt::TerminalApp::implementation
winrt::fire_and_forget _CopyToClipboardHandler(const IInspectable sender, const winrt::Microsoft::Terminal::TerminalControl::CopyToClipboardEventArgs copiedData);
winrt::fire_and_forget _PasteFromClipboardHandler(const IInspectable sender,
const Microsoft::Terminal::TerminalControl::PasteFromClipboardEventArgs eventArgs);

void _OpenHyperlinkHandler(const IInspectable sender, const Microsoft::Terminal::TerminalControl::OpenHyperlinkEventArgs eventArgs);
bool _CopyText(const bool singleLine, const Windows::Foundation::IReference<Microsoft::Terminal::TerminalControl::CopyFormat>& formats);

void _PasteText();

fire_and_forget _LaunchSettings(const winrt::TerminalApp::SettingsTarget target);
Expand Down
Loading