Skip to content

Commit

Permalink
osc 8 support for conhost and terminal
Browse files Browse the repository at this point in the history
  • Loading branch information
PankajBhojwani committed Aug 11, 2020
1 parent b759bdb commit 16720a0
Show file tree
Hide file tree
Showing 30 changed files with 347 additions and 8 deletions.
29 changes: 29 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:
// - Retrives the hyperlink ID of the text
// Return value:
// - The hyperlink ID
SHORT 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(SHORT id) noexcept
{
_hyperlinkId = id;
}

bool TextAttribute::IsLeadingByte() const noexcept
{
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_LEADING_BYTE);
Expand Down
17 changes: 13 additions & 4 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 @@ -110,8 +113,11 @@ class TextAttribute final

ExtendedAttributes GetExtendedAttributes() const noexcept;

bool IsHyperlink() const noexcept;

TextColor GetForeground() const noexcept;
TextColor GetBackground() const noexcept;
SHORT GetHyperlinkId() const noexcept;
void SetForeground(const TextColor foreground) noexcept;
void SetBackground(const TextColor background) noexcept;
void SetForeground(const COLORREF rgbForeground) noexcept;
Expand All @@ -121,6 +127,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(SHORT id) noexcept;

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

SHORT _hyperlinkId;

#ifdef UNIT_TESTING
friend class TextBufferTests;
friend class TextAttributeTests;
Expand All @@ -180,7 +189,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 Down
34 changes: 33 additions & 1 deletion 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{},
_curHyperlinkId{ 1 }
{
// initialize ROWs
for (size_t i = 0; i < static_cast<size_t>(screenBufferSize.Y); ++i)
Expand Down Expand Up @@ -2207,3 +2208,34 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,

return hr;
}

// Method Description:
// - Adds a hyperlink to our hyperlink table
// Arguments:
// - The hyperlink URI
void TextBuffer::AddHyperlinkToMap(std::wstring uri) noexcept
{
_hyperlinkMap.insert(std::pair<SHORT, std::wstring>(_curHyperlinkId, uri));
_curHyperlinkId++;
}

// Method Description:
// - Retrieves the next hyperlink ID to use - this is needed for setting
// the hyperlink ID in text attributes
// Return Value:
// - the SHORT hyperlink ID
SHORT TextBuffer::GetCurHyperlinkId() const noexcept
{
return _curHyperlinkId;
}

// Method Description:
// - Retrieves the URI associated with a particular hyperlink ID
// Arguments:
// - The hyperlink ID
// Return Value:
// - The URI
std::wstring TextBuffer::GetHyperlinkUriFromId(SHORT id) const noexcept
{
return _hyperlinkMap.at(id);
}
7 changes: 7 additions & 0 deletions src/buffer/out/textBuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ class TextBuffer final

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

void AddHyperlinkToMap(std::wstring uri) noexcept;
SHORT GetCurHyperlinkId() const noexcept;
std::wstring GetHyperlinkUriFromId(SHORT id) const noexcept;

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

SHORT _curHyperlinkId;
std::map<SHORT, std::wstring> _hyperlinkMap;

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

Microsoft::Console::Render::IRenderTarget& _renderTarget;
Expand Down
11 changes: 11 additions & 0 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// macro directly with a VirtualKeyModifiers
const auto altEnabled = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Menu));
const auto shiftEnabled = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Shift));
const auto ctrlEnabled = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Control));

if (_CanSendVTMouseInput())
{
Expand Down Expand Up @@ -1110,6 +1111,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_terminal->SetSelectionEnd(terminalPosition, mode);
_selectionNeedsToBeCopied = true;
}
if (ctrlEnabled && mode == ::Terminal::SelectionExpansionMode::Cell)
{
// Control+Click: if the text selected is a hyperlink, open the link
auto attr = _terminal->GetTextBuffer().GetCellDataAt(terminalPosition)->TextAttr();
if (attr.IsHyperlink())
{
auto uri = _terminal->GetTextBuffer().GetHyperlinkUriFromId(attr.GetHyperlinkId());
ShellExecute(nullptr, L"open", uri.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
}
}
else if (mode == ::Terminal::SelectionExpansionMode::Cell)
{
// Single Click: reset the selection and begin a new one
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalControl/TerminalControl.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@

<ItemDefinitionGroup>
<Link>
<AdditionalDependencies>dwrite.lib;dxgi.lib;d2d1.lib;d3d11.lib;shcore.lib;winmm.lib;pathcch.lib;propsys.lib;uiautomationcore.lib;Shlwapi.lib;ntdll.lib;user32.lib;kernel32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>dwrite.lib;dxgi.lib;d2d1.lib;d3d11.lib;shcore.lib;winmm.lib;pathcch.lib;propsys.lib;uiautomationcore.lib;Shlwapi.lib;ntdll.lib;user32.lib;kernel32.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<ClCompile>
<AdditionalIncludeDirectories>$(OpenConsoleDir)src\cascadia\inc;$(OpenConsoleDir)src\types\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalControl/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@
TRACELOGGING_DECLARE_PROVIDER(g_hTerminalControlProvider);
#include <telemetry/ProjectTelemetry.h>

#include <shellapi.h>

#include "til.h"
2 changes: 2 additions & 0 deletions src/cascadia/TerminalCore/ITerminalApi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ namespace Microsoft::Terminal::Core

virtual bool CopyToClipboard(std::wstring_view content) noexcept = 0;

virtual bool AddHyperlink(std::wstring uri) noexcept = 0;

protected:
ITerminalApi() = default;
};
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ class Microsoft::Terminal::Core::Terminal final :
bool IsVtInputEnabled() const noexcept override;

bool CopyToClipboard(std::wstring_view content) noexcept override;

bool AddHyperlink(std::wstring uri) noexcept override;
#pragma endregion

#pragma region ITerminalInput
Expand Down Expand Up @@ -152,6 +154,7 @@ class Microsoft::Terminal::Core::Terminal final :
bool IsScreenReversed() const noexcept override;
const std::vector<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
const bool IsGridLineDrawingAllowed() noexcept override;
const std::wstring GetHyperlinkUri(SHORT id) const noexcept override;
#pragma endregion

#pragma region IUiaData
Expand Down
31 changes: 31 additions & 0 deletions src/cascadia/TerminalCore/TerminalApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -560,3 +560,34 @@ try
return true;
}
CATCH_LOG_RETURN_FALSE()

// Method Description:
// - Updates the buffer's current text attributes depending on whether we are
// starting/ending a hyperlink
// Arguments:
// - The hyperlink URI
// Return Value:
// - true
bool Terminal::AddHyperlink(std::wstring uri) noexcept
{
auto attr = _buffer->GetCurrentAttributes();
if (uri.empty())
{
// URI is empty, this means we are ending a hyperlink
attr.SetBold(false); // for now we manually set the bold/underline text attributes;
attr.SetUnderlined(false); // at some point we should just change text rendering directly
attr.SetHyperlinkId(0); // based on whether the hyperlink id is non-zero
_buffer->SetCurrentAttributes(attr);
}
else
{
// URI is non-empty, this means we are starting a hyperlink
attr.SetBold(true);
attr.SetUnderlined(true);
attr.SetHyperlinkId(_buffer->GetCurHyperlinkId());
_buffer->SetCurrentAttributes(attr);
_buffer->AddHyperlinkToMap(uri);
}
return true;
;
}
11 changes: 11 additions & 0 deletions src/cascadia/TerminalCore/TerminalDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,17 @@ bool TerminalDispatch::ResetPrivateModes(const gsl::span<const DispatchTypes::Pr
return _SetResetPrivateModes(params, false);
}

// Method Description:
// - Start or end a hyperlink
// Arguments:
// - The hyperlink URI
// Return Value:
// - true
bool TerminalDispatch::AddHyperlink(const std::wstring uri)
{
return _terminalApi.AddHyperlink(uri);
}

// Routine Description:
// - Generalized handler for the setting/resetting of DECSET/DECRST parameters.
// All params in the rgParams will attempt to be executed, even if one
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalCore/TerminalDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatc
bool SetPrivateModes(const gsl::span<const ::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> /*params*/) noexcept override; // DECSET
bool ResetPrivateModes(const gsl::span<const ::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> /*params*/) noexcept override; // DECRST

bool AddHyperlink(const std::wstring uri) override;

private:
::Microsoft::Terminal::Core::ITerminalApi& _terminalApi;

Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalCore/terminalrenderdata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ const bool Terminal::IsGridLineDrawingAllowed() noexcept
return true;
}

const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkUri(SHORT id) const noexcept
{
return _buffer->GetHyperlinkUriFromId(id);
}

std::vector<Microsoft::Console::Types::Viewport> Terminal::GetSelectionRects() noexcept
try
{
Expand Down
30 changes: 30 additions & 0 deletions src/host/outputStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -770,3 +770,33 @@ bool ConhostInternalGetSet::PrivateIsVtInputEnabled() const
{
return _io.GetActiveInputBuffer()->IsInVirtualTerminalInputMode();
}

// Method Description:
// - Updates the buffer's current text attributes depending on whether we are
// starting/ending a hyperlink
// Arguments:
// - The hyperlink URI
// Return Value:
// - true
bool ConhostInternalGetSet::PrivateAddHyperlink(const std::wstring uri) const
{
auto attr = _io.GetActiveOutputBuffer().GetAttributes();
if (uri.empty())
{
// URI is empty, this means we are ending a hyperlink
attr.SetBold(false); // for now we manually set the bold/underline text attributes;
attr.SetUnderlined(false); // at some point we should just change text rendering directly
attr.SetHyperlinkId(0); // based on whether the hyperlink id is non-zero
_io.GetActiveOutputBuffer().SetAttributes(attr);
}
else
{
// URI is non-empty, this means we are starting a hyperlink
attr.SetBold(true);
attr.SetUnderlined(true);
attr.SetHyperlinkId(_io.GetActiveOutputBuffer().GetCurHyperlinkId());
_io.GetActiveOutputBuffer().SetAttributes(attr);
_io.GetActiveOutputBuffer().AddHyperlinkToMap(uri);
}
return true;
}
2 changes: 2 additions & 0 deletions src/host/outputStream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal::

bool PrivateIsVtInputEnabled() const override;

bool PrivateAddHyperlink(const std::wstring uri) const override;

private:
Microsoft::Console::IIoProvider& _io;
};
12 changes: 12 additions & 0 deletions src/host/renderData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,18 @@ const std::wstring RenderData::GetConsoleTitle() const noexcept
return gci.GetTitleAndPrefix();
}

// Method Description:
// - Get the hyperlink URI associated with a hyperlink ID
// Arguments:
// - The hyperlink ID
// Return Value:
// - The URI
const std::wstring RenderData::GetHyperlinkUri(SHORT id) const noexcept
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return gci.GetActiveOutputBuffer().GetHyperlinkUriFromId(id);
}

// Routine Description:
// - Converts a text attribute into the RGB values that should be presented, applying
// relevant table translation information and preferences.
Expand Down
2 changes: 2 additions & 0 deletions src/host/renderData.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ class RenderData final :
const bool IsGridLineDrawingAllowed() noexcept override;

const std::wstring GetConsoleTitle() const noexcept override;

const std::wstring GetHyperlinkUri(SHORT id) const noexcept override;
#pragma endregion

#pragma region IUiaData
Expand Down
Loading

1 comment on commit 16720a0

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New misspellings found, please review:

  • Retrives
To accept these changes, run the following commands
perl -e '
my @expect_files=qw('".github/actions/spell-check/expect/alphabet.txt
.github/actions/spell-check/expect/expect.txt
.github/actions/spell-check/expect/web.txt"');
@ARGV=@expect_files;
my @stale=qw('"atg BKMK colo consoleaccessibility Dst emptybox FFF Filledbox fullcolor hyperlink IDefault minmax notypeopt NRCS phsl popclip remoting rerendered ROWSTOSCROLL SCS shobjidl stdarg stddef stdlib terminalnuget Tofill unfocuses xab xb xbc xca xce xdd xffff "');
my $re=join "|", @stale;
my $suffix=".".time();
my $previous="";
sub maybe_unlink { unlink($_[0]) if $_[0]; }
while (<>) {
  if ($ARGV ne $old_argv) { maybe_unlink($previous); $previous="$ARGV$suffix"; rename($ARGV, $previous); open(ARGV_OUT, ">$ARGV"); select(ARGV_OUT); $old_argv = $ARGV; }
  next if /^($re)(?:$| .*)/; print;
}; maybe_unlink($previous);'
perl -e '
my $new_expect_file=".github/actions/spell-check/expect/16720a0beca4aa177e0aee2671dab706fbdba988.txt";
open FILE, q{<}, $new_expect_file; chomp(my @words = <FILE>); close FILE;
my @add=qw('"dst EMPTYBOX nrcs Remoting Retrives Scs Shobjidl "');
my %items; @items{@words} = @words x (1); @items{@add} = @add x (1);
@words = sort {lc($a) cmp lc($b)} keys %items;
open FILE, q{>}, $new_expect_file; for my $word (@words) { print FILE "$word\n" if $word =~ /\w/; };
close FILE;'
git add .github/actions/spell-check/expect || echo '... you want to ensure .github/actions/spell-check/expect/16720a0beca4aa177e0aee2671dab706fbdba988.txt is added to your repository...'
✏️ Contributor please read this

By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.

⚠️ The command is written for posix shells. You can copy the contents of each perl command excluding the outer ' marks and dropping any '"/"' quotation mark pairs into a file and then run perl file.pl from the root of the repository to run the code. Alternatively, you can manually insert the items...

If the listed items are:

  • ... misspelled, then please correct them instead of using the command.
  • ... names, please add them to .github/actions/spell-check/dictionary/names.txt.
  • ... APIs, you can add them to a file in .github/actions/spell-check/dictionary/.
  • ... just things you're using, please add them to an appropriate file in .github/actions/spell-check/expect/.
  • ... tokens you only need in one place and shouldn't generally be used, you can add an item in an appropriate file in .github/actions/spell-check/patterns/.

See the README.md in each directory for more information.

🔬 You can test your commits without appending to a PR by creating a new branch with that extra change and pushing it to your fork. The :check-spelling action will run in response to your push -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. 😉

⚠️ Reviewers

At present, the action that triggered this message will not show its ❌ in this PR unless the branch is within this repository.
Thus, you should make sure that this comment has been addressed before encouraging the merge bot to merge this PR.

Please sign in to comment.