| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | TableOutput.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | Non-templated implementation for TableOutput: FormattedCell rendering and |
| 12 | the WrapText helper. The TableOutput<FieldCount> class template remains in |
| 13 | the header. |
| 14 | |
| 15 | --*/ |
| 16 | #include "precomp.h" |
| 17 | #include "TableOutput.h" |
| 18 | |
| 19 | using namespace wsl::windows::common::vt; |
| 20 | |
| 21 | namespace wsl::windows::wslc { |
| 22 | |
| 23 | FormattedCell::FormattedCell(std::wstring_view text, const Sequence& seq) : sequences({&seq, &Format::Default}) |
| 24 | { |
| 25 | WI_ASSERT(text.find(L"{}") == std::wstring_view::npos); |
| 26 | fmt.reserve(4 + text.size()); |
| 27 | fmt += L"{}"; |
| 28 | fmt += text; |
| 29 | fmt += L"{}"; |
| 30 | } |
| 31 | |
| 32 | size_t FormattedCell::VisibleWidth() const |
| 33 | { |
| 34 | if (sequences.empty()) |
| 35 | { |
| 36 | return fmt.size(); |
| 37 | } |
| 38 | |
| 39 | size_t width = 0; |
| 40 | for (size_t i = 0; i < fmt.size(); ++i) |
| 41 | { |
| 42 | if (i + 1 < fmt.size() && fmt[i] == L'{' && fmt[i + 1] == L'}') |
| 43 | { |
| 44 | ++i; // skip the pair |
| 45 | } |
| 46 | else |
| 47 | { |
| 48 | ++width; |
| 49 | } |
| 50 | } |
| 51 | return width; |
| 52 | } |
| 53 | |
| 54 | std::wstring FormattedCell::Render(bool vtEnabled, bool colorEnabled) const |
| 55 | { |
| 56 | if (sequences.empty()) |
| 57 | { |
| 58 | return fmt; // plain text, no placeholders |
| 59 | } |
| 60 | |
| 61 | std::wstring result; |
| 62 | result.reserve(fmt.size() + (vtEnabled ? sequences.size() * 8 : 0)); |
| 63 | size_t seqIdx = 0; |
| 64 | |
| 65 | for (size_t i = 0; i < fmt.size(); ++i) |
| 66 | { |
| 67 | if (i + 1 < fmt.size() && fmt[i] == L'{' && fmt[i + 1] == L'}') |
| 68 | { |
| 69 | if (vtEnabled && seqIdx < sequences.size() && (colorEnabled || !sequences[seqIdx]->IsColor())) |
| 70 | { |
| 71 | result.append(sequences[seqIdx]->Get()); |
| 72 | } |
| 73 | ++seqIdx; |
| 74 | ++i; // skip the pair |
| 75 | } |
| 76 | else |
| 77 | { |
| 78 | result += fmt[i]; |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | return result; |
| 83 | } |
| 84 | |
| 85 | std::wstring FormattedCell::RenderTruncated(size_t maxWidth, bool vtEnabled, bool colorEnabled) const |
| 86 | { |
| 87 | if (sequences.empty()) |
| 88 | { |
| 89 | // Plain text: simple truncation. |
| 90 | if (fmt.size() <= maxWidth) |
| 91 | { |
| 92 | return fmt; |
| 93 | } |
| 94 | return fmt.substr(0, maxWidth > 0 ? maxWidth - 1 : 0) + L"\u2026"; |
| 95 | } |
| 96 | |
| 97 | std::wstring result; |
| 98 | result.reserve(fmt.size()); |
| 99 | size_t seqIdx = 0; |
| 100 | size_t visibleChars = 0; |
| 101 | bool truncated = (maxWidth == 0); |
| 102 | const size_t truncateAt = maxWidth > 1 ? maxWidth - 1 : 0; |
| 103 | |
| 104 | for (size_t i = 0; i < fmt.size(); ++i) |
| 105 | { |
| 106 | if (i + 1 < fmt.size() && fmt[i] == L'{' && fmt[i + 1] == L'}') |
| 107 | { |
| 108 | // Always emit sequences (they're invisible); they handle resets after truncation. |
| 109 | if (vtEnabled && seqIdx < sequences.size() && (colorEnabled || !sequences[seqIdx]->IsColor())) |
| 110 | { |
| 111 | result.append(sequences[seqIdx]->Get()); |
| 112 | } |
| 113 | ++seqIdx; |
| 114 | ++i; |
| 115 | } |
| 116 | else if (!truncated) |
| 117 | { |
| 118 | if (visibleChars < truncateAt) |
| 119 | { |
| 120 | result += fmt[i]; |
| 121 | ++visibleChars; |
| 122 | } |
| 123 | else |
| 124 | { |
| 125 | result += L'\u2026'; |
| 126 | truncated = true; |
| 127 | } |
| 128 | } |
| 129 | // After truncation, skip remaining visible chars but continue to emit sequences. |
| 130 | } |
| 131 | |
| 132 | return result; |
| 133 | } |
| 134 | |
| 135 | namespace details { |
| 136 | |
| 137 | std::vector<std::wstring> WrapText(const std::wstring& text, size_t maxWidth) |
| 138 | { |
| 139 | if (maxWidth == 0 || text.length() <= maxWidth) |
| 140 | { |
| 141 | return {text}; |
| 142 | } |
| 143 | |
| 144 | std::vector<std::wstring> lines; |
| 145 | size_t pos = 0; |
| 146 | |
| 147 | while (pos < text.length()) |
| 148 | { |
| 149 | size_t chunkEnd = std::min(pos + maxWidth, text.length()); |
| 150 | |
| 151 | if (chunkEnd < text.length()) |
| 152 | { |
| 153 | size_t breakAt = text.rfind(L' ', chunkEnd); |
| 154 | if (breakAt != std::wstring::npos && breakAt > pos) |
| 155 | { |
| 156 | chunkEnd = breakAt; |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | lines.emplace_back(text.substr(pos, chunkEnd - pos)); |
| 161 | |
| 162 | pos = chunkEnd; |
| 163 | while (pos < text.length() && text[pos] == L' ') |
| 164 | { |
| 165 | ++pos; |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | return lines; |
| 170 | } |
| 171 | |
| 172 | } // namespace details |
| 173 | |
| 174 | } // namespace wsl::windows::wslc |