Skip to content

Commit

Permalink
Merge pull request #943 from contour-terminal/feature/rasterize-to-image
Browse files Browse the repository at this point in the history
prep-work for supporting different formats when copying selection to clipboard.
  • Loading branch information
christianparpart authored Dec 24, 2022
2 parents 935e5b8 + add07cd commit a0232e2
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 37 deletions.
42 changes: 41 additions & 1 deletion src/contour/Actions.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
*/
#pragma once

#include <crispy/assert.h>

#include <fmt/format.h>

#include <optional>
Expand All @@ -22,12 +24,28 @@
namespace contour::actions
{

// Defines the format to use when extracting a selection range from the terminal.
enum class CopyFormat
{
// Copies purely the text (with their whitespaces, and newlines, but no formatting).
Text,

// Copies the selection in HTML format.
HTML,

// Copies the selection in escaped VT sequence format.
VT,

// Copies the selection as PNG image.
PNG,
};

// clang-format off
struct CancelSelection{};
struct ChangeProfile{ std::string name; };
struct ClearHistoryAndReset{};
struct CopyPreviousMarkRange{};
struct CopySelection{};
struct CopySelection{ CopyFormat format = CopyFormat::Text; };
struct CreateDebugDump{};
struct DecreaseFontSize{};
struct DecreaseOpacity{};
Expand Down Expand Up @@ -255,6 +273,28 @@ struct formatter<contour::actions::Action>
return fmt::format_to(ctx.out(), "UNKNOWN ACTION");
}
};
template <>
struct formatter<contour::actions::CopyFormat>
{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx)
{
return ctx.begin();
}
template <typename FormatContext>
auto format(contour::actions::CopyFormat value, FormatContext& ctx)
{
switch (value)
{
case contour::actions::CopyFormat::Text: return fmt::format_to(ctx.out(), "Text");
case contour::actions::CopyFormat::HTML: return fmt::format_to(ctx.out(), "HTML");
case contour::actions::CopyFormat::PNG: return fmt::format_to(ctx.out(), "PNG");
case contour::actions::CopyFormat::VT: return fmt::format_to(ctx.out(), "VT");
}
crispy::unreachable();
}
};

} // namespace fmt
#undef HANDLE_ACTION
// ]}}
28 changes: 28 additions & 0 deletions src/contour/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
#include <utility>
#include <vector>

#include "contour/Actions.h"

#if defined(_WIN32)
#include <Windows.h>
#elif defined(__APPLE__)
Expand Down Expand Up @@ -876,6 +878,32 @@ namespace
return nullopt;
}

if (holds_alternative<actions::CopySelection>(action))
{
if (auto node = _parent["format"]; node && node.IsScalar())
{
_usedKeys.emplace(_prefix + ".format");
auto const formatString = toUpper(node.as<string>());
static auto constexpr mappings =
std::array<std::pair<std::string_view, actions::CopyFormat>, 4> { {
{ "TEXT", actions::CopyFormat::Text },
{ "HTML", actions::CopyFormat::HTML },
{ "PNG", actions::CopyFormat::PNG },
{ "VT", actions::CopyFormat::VT },
} };
if (auto const p = std::find_if(mappings.begin(),
mappings.end(),
[&](auto const& t) { return t.first == formatString; });
p != mappings.end())
{
return actions::CopySelection { p->second };
}
errorlog()("Invalid format '{}' in CopySelection action. Defaulting to 'text'.",
node.as<string>());
return actions::CopySelection { actions::CopyFormat::Text };
}
}

if (holds_alternative<actions::PasteClipboard>(action))
{
if (auto node = _parent["strip"]; node && node.IsScalar())
Expand Down
18 changes: 16 additions & 2 deletions src/contour/TerminalSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -703,9 +703,23 @@ bool TerminalSession::operator()(actions::CopyPreviousMarkRange)
return true;
}

bool TerminalSession::operator()(actions::CopySelection)
bool TerminalSession::operator()(actions::CopySelection copySelection)
{
copyToClipboard(terminal().extractSelectionText());
switch (copySelection.format)
{
case actions::CopyFormat::Text:
// Copy the selection in pure text, plus whitespaces and newline.
copyToClipboard(terminal().extractSelectionText());
break;
case actions::CopyFormat::HTML:
// TODO: This requires walking through each selected cell and construct HTML+CSS for it.
case actions::CopyFormat::VT:
// TODO: Construct VT escape sequences.
case actions::CopyFormat::PNG:
// TODO: Copy to clipboard as rendered PNG for the selected area.
errorlog()("CopySelection format {} is not yet supported.", copySelection.format);
return false;
}
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion src/text_shaper/fontconfig_locator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ namespace
return nullopt;
}

constexpr int fcWeight(font_weight _weight) noexcept
int fcWeight(font_weight _weight) noexcept
{
for (auto const& mapping: fontWeightMappings)
if (mapping.first == _weight)
Expand Down
11 changes: 7 additions & 4 deletions src/vtbackend/RenderBufferBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,16 @@ RenderBufferBuilder<Cell>::RenderBufferBuilder(Terminal const& _terminal,
bool theReverseVideo,
HighlightSearchMatches highlightSearchMatches,
InputMethodData inputMethodData,
optional<CellLocation> theCursorPosition):
optional<CellLocation> theCursorPosition,
bool includeSelection):
output { _output },
terminal { _terminal },
cursorPosition { theCursorPosition },
baseLine { base },
reverseVideo { theReverseVideo },
_highlightSearchMatches { highlightSearchMatches },
_inputMethodData { std::move(inputMethodData) }
_inputMethodData { std::move(inputMethodData) },
_includeSelection { includeSelection }
{
output.frameID = _terminal.lastFrameID();

Expand Down Expand Up @@ -256,7 +258,8 @@ RGBColorPair RenderBufferBuilder<Cell>::makeColorsForCell(CellLocation gridPosit
&& output.cursor->shape == CursorShape::Block;
// clang-format on

auto const selected = terminal.isSelected(CellLocation { gridPosition.line, gridPosition.column });
auto const selected =
_includeSelection && terminal.isSelected(CellLocation { gridPosition.line, gridPosition.column });
auto const highlighted = terminal.isHighlighted(CellLocation { gridPosition.line, gridPosition.column });
auto const blink = terminal.blinkState();
auto const rapidBlink = terminal.rapidBlinkState();
Expand Down Expand Up @@ -345,7 +348,7 @@ void RenderBufferBuilder<Cell>::renderTrivialLine(TrivialLineBuffer const& lineB
// We're not testing for cursor shape (which should be done in order to be 100% correct)
// because it's not really draining performance.
bool const canRenderViaSimpleLine =
!terminal.isSelected(lineOffset) && !gridLineContainsCursor(lineOffset);
(!terminal.isSelected(lineOffset) || !_includeSelection) && !gridLineContainsCursor(lineOffset);

if (canRenderViaSimpleLine)
{
Expand Down
4 changes: 3 additions & 1 deletion src/vtbackend/RenderBufferBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class RenderBufferBuilder
bool reverseVideo,
HighlightSearchMatches highlightSearchMatches,
InputMethodData inputMethodData,
std::optional<CellLocation> theCursorPosition);
std::optional<CellLocation> theCursorPosition,
bool includeSelection);

/// Renders a single grid cell.
/// This call is guaranteed to be invoked sequencially, from top line
Expand Down Expand Up @@ -119,6 +120,7 @@ class RenderBufferBuilder
bool reverseVideo;
HighlightSearchMatches _highlightSearchMatches;
InputMethodData _inputMethodData;
bool _includeSelection;
ColumnCount _inputMethodSkipColumns = ColumnCount(0);

int prevWidth = 0;
Expand Down
57 changes: 31 additions & 26 deletions src/vtbackend/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,21 @@ bool Terminal::ensureFreshRenderBuffer(bool _locked)
break;
renderBuffer_.state = RenderBufferState::RefreshBuffersAndTrySwap;
[[fallthrough]];
case RenderBufferState::RefreshBuffersAndTrySwap:
case RenderBufferState::RefreshBuffersAndTrySwap: {
auto& backBuffer = renderBuffer_.backBuffer();
auto const lastCursorPos = std::move(backBuffer.cursor);
if (!_locked)
refreshRenderBuffer(renderBuffer_.backBuffer());
fillRenderBuffer(renderBuffer_.backBuffer(), true);
else
refreshRenderBufferInternal(renderBuffer_.backBuffer());
fillRenderBufferInternal(renderBuffer_.backBuffer(), true);
auto const cursorChanged =
lastCursorPos.has_value() != backBuffer.cursor.has_value()
|| (backBuffer.cursor.has_value() && backBuffer.cursor->position != lastCursorPos->position);
if (cursorChanged)
eventListener_.cursorPositionChanged();
renderBuffer_.state = RenderBufferState::TrySwapBuffers;
[[fallthrough]];
}
case RenderBufferState::TrySwapBuffers: {
[[maybe_unused]] auto const success = renderBuffer_.swapBuffers(currentTime_);

Expand All @@ -300,12 +308,6 @@ bool Terminal::ensureFreshRenderBuffer(bool _locked)
return true;
}

void Terminal::refreshRenderBuffer(RenderBuffer& _output)
{
auto const _l = lock_guard { *this };
refreshRenderBufferInternal(_output);
}

PageSize Terminal::SelectionHelper::pageSize() const noexcept
{
return terminal->pageSize();
Expand Down Expand Up @@ -368,12 +370,17 @@ void Terminal::updateInputMethodPreeditString(std::string preeditString)
screenUpdated();
}

void Terminal::refreshRenderBufferInternal(RenderBuffer& _output)
void Terminal::fillRenderBuffer(RenderBuffer& output, bool includeSelection)
{
auto const _l = lock_guard { *this };
fillRenderBufferInternal(output, includeSelection);
}

void Terminal::fillRenderBufferInternal(RenderBuffer& output, bool includeSelection)
{
verifyState();

auto const lastCursorPos = std::move(_output.cursor);
_output.clear();
output.clear();

changes_.store(0);
screenDirty_ = false;
Expand All @@ -398,23 +405,25 @@ void Terminal::refreshRenderBufferInternal(RenderBuffer& _output)
if (isPrimaryScreen())
_lastRenderPassHints =
primaryScreen_.render(RenderBufferBuilder<PrimaryScreenCell> { *this,
_output,
output,
LineOffset(0),
mainDisplayReverseVideo,
HighlightSearchMatches::Yes,
inputMethodData_,
theCursorPosition },
theCursorPosition,
includeSelection },
viewport_.scrollOffset(),
highlightSearchMatches);
else
_lastRenderPassHints =
alternateScreen_.render(RenderBufferBuilder<AlternateScreenCell> { *this,
_output,
output,
LineOffset(0),
mainDisplayReverseVideo,
HighlightSearchMatches::Yes,
inputMethodData_,
theCursorPosition },
theCursorPosition,
includeSelection },
viewport_.scrollOffset(),
highlightSearchMatches);

Expand All @@ -427,32 +436,28 @@ void Terminal::refreshRenderBufferInternal(RenderBuffer& _output)
updateIndicatorStatusLine();
indicatorStatusScreen_.render(
RenderBufferBuilder<StatusDisplayCell> { *this,
_output,
output,
pageSize().lines.as<LineOffset>(),
!mainDisplayReverseVideo,
HighlightSearchMatches::No,
InputMethodData {},
nullopt },
nullopt,
includeSelection },
ScrollOffset(0));
break;
case StatusDisplayType::HostWritable:
hostWritableStatusLineScreen_.render(
RenderBufferBuilder<StatusDisplayCell> { *this,
_output,
output,
pageSize().lines.as<LineOffset>(),
!mainDisplayReverseVideo,
HighlightSearchMatches::No,
InputMethodData {},
nullopt },
nullopt,
includeSelection },
ScrollOffset(0));
break;
}

if (lastCursorPos.has_value() != _output.cursor.has_value()
|| (_output.cursor.has_value() && _output.cursor->position != lastCursorPos->position))
{
eventListener_.cursorPositionChanged();
}
}
// }}}

Expand Down
9 changes: 7 additions & 2 deletions src/vtbackend/Terminal.h
Original file line number Diff line number Diff line change
Expand Up @@ -658,10 +658,15 @@ class Terminal
Settings const& settings() const noexcept { return settings_; }
Settings& settings() noexcept { return settings_; }

// Renders current visual terminal state to the render buffer.
//
// @param output target render buffer to write the current visual state to.
// @param _includeSelection boolean to indicate whether or not to include colorize selection.
void fillRenderBuffer(RenderBuffer& output, bool includeSelection); // <- acquires the lock

private:
void mainLoop();
void refreshRenderBuffer(RenderBuffer& _output); // <- acquires the lock
void refreshRenderBufferInternal(RenderBuffer& _output);
void fillRenderBufferInternal(RenderBuffer& _output, bool includeSelection);
void updateIndicatorStatusLine();
void updateCursorVisibilityState() const;
bool updateCursorHoveringState();
Expand Down

0 comments on commit a0232e2

Please sign in to comment.