mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-08 15:21:01 +00:00
Compare commits
23 Commits
dev/miniks
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4f3089bec | ||
|
|
a8ced8f4b4 | ||
|
|
df5f9733ce | ||
|
|
6ab833920a | ||
|
|
801b29635c | ||
|
|
c035e69874 | ||
|
|
093e8acaf8 | ||
|
|
2f532bd410 | ||
|
|
8c6c7ece5d | ||
|
|
f8e969e44b | ||
|
|
e8d06d7a74 | ||
|
|
04d403ca97 | ||
|
|
eee5beeaf3 | ||
|
|
8f8cf5996e | ||
|
|
322d511034 | ||
|
|
54f443b17f | ||
|
|
24765d708a | ||
|
|
2402c59f3d | ||
|
|
5e655ef2d8 | ||
|
|
acf84f188f | ||
|
|
243867aadc | ||
|
|
b0f1fa4d64 | ||
|
|
0bd30c2a7a |
54
.github/ISSUE_TEMPLATE/Bug_Report.md
vendored
Normal file
54
.github/ISSUE_TEMPLATE/Bug_Report.md
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
name: "Bug report 🐛"
|
||||
about: Report errors or unexpected behavior
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
||||
|
||||
I ACKNOWLEDGE THE FOLLOWING BEFORE PROCEEDING:
|
||||
1. If I delete this entire template and go my own path, the core team may close my issue without further explanation or engagement.
|
||||
2. If I list multiple bugs/concerns in this one issue, the core team may close my issue without further explanation or engagement.
|
||||
3. If I write an issue that has many duplicates, the core team may close my issue without further explanation or engagement (and without necessarily spending time to find the exact duplicate ID number).
|
||||
4. If I leave the title incomplete when filing the issue, the core team may close my issue without further explanation or engagement.
|
||||
5. If I file something completely blank in the body, the core team may close my issue without further explanation or engagement.
|
||||
|
||||
All good? Then proceed!
|
||||
-->
|
||||
|
||||
<!--
|
||||
This bug tracker is monitored by Windows Terminal development team and other technical folks.
|
||||
|
||||
**Important: When reporting BSODs or security issues, DO NOT attach memory dumps, logs, or traces to Github issues**.
|
||||
Instead, send dumps/traces to secure@microsoft.com, referencing this GitHub issue.
|
||||
|
||||
If this is an application crash, please also provide a Feedback Hub submission link so we can find your diagnostic data on the backend. Use the category "Apps > Windows Terminal (Preview)" and choose "Share My Feedback" after submission to get the link.
|
||||
|
||||
Please use this form and describe your issue, concisely but precisely, with as much detail as possible.
|
||||
|
||||
-->
|
||||
|
||||
# Environment
|
||||
|
||||
```none
|
||||
Windows build number: [run `[Environment]::OSVersion` for powershell, or `ver` for cmd]
|
||||
Windows Terminal version (if applicable):
|
||||
|
||||
Any other software?
|
||||
```
|
||||
|
||||
# Steps to reproduce
|
||||
|
||||
<!-- A description of how to trigger this bug. -->
|
||||
|
||||
# Expected behavior
|
||||
|
||||
<!-- A description of what you're expecting, possibly containing screenshots or reference material. -->
|
||||
|
||||
# Actual behavior
|
||||
|
||||
<!-- What's actually happening? -->
|
||||
57
.github/ISSUE_TEMPLATE/Bug_Report.yml
vendored
57
.github/ISSUE_TEMPLATE/Bug_Report.yml
vendored
@@ -1,57 +0,0 @@
|
||||
name: "Bug report 🐛"
|
||||
description: Report errors or unexpected behavior
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
issue_body: true
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please make sure to [search for existing issues](https://github.com/microsoft/terminal/issues) before filing a new one!
|
||||
|
||||
If this is an application crash, please also provide a [Feedback Hub](https://aka.ms/terminal-feedback-hub) submission link so we can find your diagnostic data on the backend. Use the category "Apps > Windows Terminal" and choose "Share My Feedback" after submission to get the link.
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Windows Terminal version (or Windows build number)
|
||||
placeholder: "10.0.19042.0, 1.7.3651.0"
|
||||
description: |
|
||||
If you are reporting an issue in Windows Terminal, you can find the version in the about dialog.
|
||||
|
||||
If you are reporting an issue with the Windows Console, please run `ver` or `[Environment]::OSVersion`.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Other Software
|
||||
description: If you're reporting a bug about our interaction with other software, what software? What versions?
|
||||
placeholder: |
|
||||
vim 8.2 (inside WSL)
|
||||
OpenSSH_for_Windows_8.1p1
|
||||
My Cool Application v0.3 (include a code snippet if it would help!)
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
placeholder: Tell us the steps required to trigger your bug.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: If you want to include screenshots, paste them into the markdown editor below the form or follow up with a separate comment.
|
||||
placeholder: What were you expecting?
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
placeholder: What happened instead?
|
||||
validations:
|
||||
required: true
|
||||
15
.vscode/settings.json
vendored
15
.vscode/settings.json
vendored
@@ -21,7 +21,20 @@
|
||||
"xloctime": "cpp",
|
||||
"multi_span": "cpp",
|
||||
"pointers": "cpp",
|
||||
"vector": "cpp"
|
||||
"vector": "cpp",
|
||||
"bitset": "cpp",
|
||||
"deque": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"list": "cpp",
|
||||
"queue": "cpp",
|
||||
"random": "cpp",
|
||||
"regex": "cpp",
|
||||
"stack": "cpp",
|
||||
"xhash": "cpp",
|
||||
"xtree": "cpp",
|
||||
"xutility": "cpp",
|
||||
"span": "cpp",
|
||||
"string_span": "cpp"
|
||||
},
|
||||
"files.exclude": {
|
||||
"**/bin/**": true,
|
||||
|
||||
7
.vscode/tasks.json
vendored
7
.vscode/tasks.json
vendored
@@ -9,7 +9,7 @@
|
||||
"-Command",
|
||||
"Import-Module ${workspaceFolder}\\tools\\OpenConsole.psm1;",
|
||||
"Set-MsBuildDevEnvironment;",
|
||||
"$project = switch(\"${input:buildProjectChoice}\"){OpenConsole{\"Conhost\\Host_EXE\"} Terminal{\"Terminal\\CascadiaPackage\"}};",
|
||||
"$project = switch(\"${input:buildProjectChoice}\"){OpenConsole{\"Conhost\\Host_EXE\"} Terminal{\"Terminal\\CascadiaPackage\"} TermControl{\"Terminal\\TerminalControl\"}};",
|
||||
"$target = switch(\"${input:buildModeChoice}\"){Build{\"\"} Rebuild{\":Rebuild\"} Clean{\":Clean\"}};",
|
||||
"$target = $project + $target;",
|
||||
"msbuild",
|
||||
@@ -98,10 +98,11 @@
|
||||
"description": "OpenConsole or Terminal?",
|
||||
"options":[
|
||||
"OpenConsole",
|
||||
"Terminal"
|
||||
"Terminal",
|
||||
"TermControl"
|
||||
],
|
||||
"default": "Terminal"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,19 +231,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"SwitchToAdjacentTabArgs" : {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{
|
||||
"enum": [
|
||||
"mru",
|
||||
"inOrder",
|
||||
"disabled"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ShortcutAction": {
|
||||
"properties": {
|
||||
"action": {
|
||||
@@ -615,38 +602,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"PrevTabAction": {
|
||||
"description": "Arguments corresponding to a Previous Tab Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type":"string", "pattern": "prevTab" },
|
||||
"tabSwitcherMode": {
|
||||
"$ref": "#/definitions/SwitchToAdjacentTabArgs",
|
||||
"default": null,
|
||||
"description": "Move to the previous tab using \"tabSwitcherMode\". If no mode is provided, use the one globally defined one."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"NextTabAction": {
|
||||
"description": "Arguments corresponding to a Next Tab Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type":"string", "pattern": "nextTab" },
|
||||
"tabSwitcherMode": {
|
||||
"$ref": "#/definitions/SwitchToAdjacentTabArgs",
|
||||
"default": null,
|
||||
"description": "Move to the next tab using \"tabSwitcherMode\". If no mode is provided, use the one globally defined one."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Keybinding": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -673,8 +628,6 @@
|
||||
{ "$ref": "#/definitions/MoveTabAction" },
|
||||
{ "$ref": "#/definitions/FindMatchAction" },
|
||||
{ "$ref": "#/definitions/NewWindowAction" },
|
||||
{ "$ref": "#/definitions/NextTabAction" },
|
||||
{ "$ref": "#/definitions/PrevTabAction" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"type": "git",
|
||||
"git": {
|
||||
"repositoryUrl": "https://github.com/fmtlib/fmt",
|
||||
"commitHash": "7bdf0628b1276379886c7f6dda2cef2b3b374f0b"
|
||||
"commitHash": "f19b1a521ee8b606dedcadfda69fd10ddf882753"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,27 +72,43 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
static_assert(F::is_integer, "From must be integral");
|
||||
static_assert(T::is_integer, "To must be integral");
|
||||
|
||||
if (detail::const_check(F::is_signed && !T::is_signed)) {
|
||||
if (F::is_signed && !T::is_signed) {
|
||||
// From may be negative, not allowed!
|
||||
if (fmt::detail::is_negative(from)) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
|
||||
// From is positive. Can it always fit in To?
|
||||
if (F::digits > T::digits &&
|
||||
from > static_cast<From>(detail::max_value<To>())) {
|
||||
ec = 1;
|
||||
return {};
|
||||
if (F::digits <= T::digits) {
|
||||
// yes, From always fits in To.
|
||||
} else {
|
||||
// from may not fit in To, we have to do a dynamic check
|
||||
if (from > static_cast<From>((T::max)())) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!F::is_signed && T::is_signed && F::digits >= T::digits &&
|
||||
from > static_cast<From>(detail::max_value<To>())) {
|
||||
ec = 1;
|
||||
return {};
|
||||
if (!F::is_signed && T::is_signed) {
|
||||
// can from be held in To?
|
||||
if (F::digits < T::digits) {
|
||||
// yes, From always fits in To.
|
||||
} else {
|
||||
// from may not fit in To, we have to do a dynamic check
|
||||
if (from > static_cast<From>((T::max)())) {
|
||||
// outside range.
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
return static_cast<To>(from); // Lossless conversion.
|
||||
}
|
||||
|
||||
// reaching here means all is ok for lossless conversion.
|
||||
return static_cast<To>(from);
|
||||
|
||||
} // function
|
||||
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
||||
@@ -174,9 +190,11 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
// safe conversion to IntermediateRep
|
||||
IntermediateRep count =
|
||||
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
|
||||
if (ec) return {};
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
// multiply with Factor::num without overflow or underflow
|
||||
if (detail::const_check(Factor::num != 1)) {
|
||||
if (Factor::num != 1) {
|
||||
const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
|
||||
if (count > max1) {
|
||||
ec = 1;
|
||||
@@ -191,9 +209,17 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
count *= Factor::num;
|
||||
}
|
||||
|
||||
if (detail::const_check(Factor::den != 1)) count /= Factor::den;
|
||||
auto tocount = lossless_integral_conversion<typename To::rep>(count, ec);
|
||||
return ec ? To() : To(tocount);
|
||||
// this can't go wrong, right? den>0 is checked earlier.
|
||||
if (Factor::den != 1) {
|
||||
count /= Factor::den;
|
||||
}
|
||||
// convert to the to type, safely
|
||||
using ToRep = typename To::rep;
|
||||
const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
return To{tocount};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,11 +351,6 @@ inline std::tm localtime(std::time_t time) {
|
||||
return lt.tm_;
|
||||
}
|
||||
|
||||
inline std::tm localtime(
|
||||
std::chrono::time_point<std::chrono::system_clock> time_point) {
|
||||
return localtime(std::chrono::system_clock::to_time_t(time_point));
|
||||
}
|
||||
|
||||
// Thread-safe replacement for std::gmtime
|
||||
inline std::tm gmtime(std::time_t time) {
|
||||
struct dispatcher {
|
||||
@@ -366,11 +387,6 @@ inline std::tm gmtime(std::time_t time) {
|
||||
return gt.tm_;
|
||||
}
|
||||
|
||||
inline std::tm gmtime(
|
||||
std::chrono::time_point<std::chrono::system_clock> time_point) {
|
||||
return gmtime(std::chrono::system_clock::to_time_t(time_point));
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
inline size_t strftime(char* str, size_t count, const char* format,
|
||||
const std::tm* time) {
|
||||
@@ -383,17 +399,6 @@ inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <typename Char>
|
||||
struct formatter<std::chrono::time_point<std::chrono::system_clock>, Char>
|
||||
: formatter<std::tm, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(std::chrono::time_point<std::chrono::system_clock> val,
|
||||
FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
std::tm time = localtime(val);
|
||||
return formatter<std::tm, Char>::format(time, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char> struct formatter<std::tm, Char> {
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
|
||||
@@ -463,16 +463,16 @@ template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
|
||||
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
|
||||
const char* begin = data::reset_color;
|
||||
const char* end = begin + sizeof(data::reset_color) - 1;
|
||||
buffer.append(begin, end);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void vformat_to(buffer<Char>& buf, const text_style& ts,
|
||||
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
@@ -496,7 +496,7 @@ void vformat_to(buffer<Char>& buf, const text_style& ts,
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
detail::vformat_to(buf, ts, to_string_view(format), args);
|
||||
buf.push_back(Char(0));
|
||||
@@ -504,22 +504,20 @@ void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats a string and prints it to the specified file stream using ANSI
|
||||
escape sequences to specify text formatting.
|
||||
|
||||
**Example**::
|
||||
|
||||
Example:
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
void print(std::FILE* f, const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
vprint(f, ts, format_str,
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
detail::check_format_string<Args...>(format_str);
|
||||
using context = buffer_context<char_t<S>>;
|
||||
format_arg_store<context, Args...> as{args...};
|
||||
vprint(f, ts, format_str, basic_format_args<context>(as));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -560,42 +558,7 @@ template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
return vformat(ts, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
/**
|
||||
Formats a string with the given text_style and writes the output to ``out``.
|
||||
*/
|
||||
template <typename OutputIt, typename Char,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
|
||||
OutputIt vformat_to(
|
||||
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
|
||||
detail::vformat_to(buf, ts, format_str, args);
|
||||
return detail::get_iterator(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments with the given text_style, writes the result to the output
|
||||
iterator ``out`` and returns the iterator past the end of the output range.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::vector<char> out;
|
||||
fmt::format_to(std::back_inserter(out),
|
||||
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
|
||||
detail::is_string<S>::value>
|
||||
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
|
||||
Args&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
return vformat_to(out, ts, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
@@ -368,8 +368,7 @@ template <typename... Args> struct type_list {};
|
||||
|
||||
// Returns a reference to the argument at index N from [first, rest...].
|
||||
template <int N, typename T, typename... Args>
|
||||
constexpr const auto& get([[maybe_unused]] const T& first,
|
||||
[[maybe_unused]] const Args&... rest) {
|
||||
constexpr const auto& get(const T& first, const Args&... rest) {
|
||||
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
||||
if constexpr (N == 0)
|
||||
return first;
|
||||
@@ -407,19 +406,6 @@ constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
||||
return {{&s[pos], size}};
|
||||
}
|
||||
|
||||
template <typename Char> struct code_unit {
|
||||
Char value;
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&...) const {
|
||||
return write<Char>(out, value);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_compiled_format<code_unit<Char>> : std::true_type {};
|
||||
|
||||
// A replacement field that refers to argument N.
|
||||
template <typename Char, typename T, int N> struct field {
|
||||
using char_type = Char;
|
||||
@@ -444,9 +430,7 @@ template <typename Char, typename T, int N> struct spec_field {
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
// This ensures that the argument type is convertile to `const T&`.
|
||||
const T& arg = get<N>(args...);
|
||||
const auto& vargs =
|
||||
make_format_args<basic_format_context<OutputIt, Char>>(args...);
|
||||
basic_format_context<OutputIt, Char> ctx(out, vargs);
|
||||
basic_format_context<OutputIt, Char> ctx(out, {});
|
||||
return fmt.format(arg, ctx);
|
||||
}
|
||||
};
|
||||
@@ -505,17 +489,16 @@ constexpr auto parse_tail(T head, S format_str) {
|
||||
template <typename T, typename Char> struct parse_specs_result {
|
||||
formatter<T, Char> fmt;
|
||||
size_t end;
|
||||
int next_arg_id;
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||
size_t pos, int arg_id) {
|
||||
size_t pos) {
|
||||
str.remove_prefix(pos);
|
||||
auto ctx = basic_format_parse_context<Char>(str, {}, arg_id + 1);
|
||||
auto ctx = basic_format_parse_context<Char>(str);
|
||||
auto f = formatter<T, Char>();
|
||||
auto end = f.parse(ctx);
|
||||
return {f, pos + (end - str.data()) + 1, ctx.next_arg_id()};
|
||||
return {f, pos + (end - str.data()) + 1};
|
||||
}
|
||||
|
||||
// Compiles a non-empty format string and returns the compiled representation
|
||||
@@ -535,8 +518,8 @@ constexpr auto compile_format_string(S format_str) {
|
||||
format_str);
|
||||
} else if constexpr (str[POS + 1] == ':') {
|
||||
using type = get_type<ID, Args>;
|
||||
constexpr auto result = parse_specs<type>(str, POS + 2, ID);
|
||||
return parse_tail<Args, result.end, result.next_arg_id>(
|
||||
constexpr auto result = parse_specs<type>(str, POS + 2);
|
||||
return parse_tail<Args, result.end, ID + 1>(
|
||||
spec_field<char_type, type, ID>{result.fmt}, format_str);
|
||||
} else {
|
||||
return unknown_format();
|
||||
@@ -547,13 +530,8 @@ constexpr auto compile_format_string(S format_str) {
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else {
|
||||
constexpr auto end = parse_text(str, POS + 1);
|
||||
if constexpr (end - POS > 1) {
|
||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
||||
format_str);
|
||||
} else {
|
||||
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
|
||||
format_str);
|
||||
}
|
||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
||||
format_str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,7 +587,8 @@ template <typename CompiledFormat, typename... Args,
|
||||
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
cf.format(detail::buffer_appender<Char>(buffer), args...);
|
||||
detail::buffer<Char>& base = buffer;
|
||||
cf.format(std::back_inserter(base), args...);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
@@ -629,7 +608,8 @@ template <typename CompiledFormat, typename... Args,
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
using context = buffer_context<Char>;
|
||||
detail::cf::vformat_to<context>(detail::buffer_appender<Char>(buffer), cf,
|
||||
detail::buffer<Char>& base = buffer;
|
||||
detail::cf::vformat_to<context>(std::back_inserter(base), cf,
|
||||
make_format_args<context>(args...));
|
||||
return to_string(buffer);
|
||||
}
|
||||
@@ -638,13 +618,9 @@ template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
||||
Args&&... args) {
|
||||
#ifdef __cpp_if_constexpr
|
||||
if constexpr (std::is_same<typename S::char_type, char>::value) {
|
||||
constexpr basic_string_view<typename S::char_type> str = S();
|
||||
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
|
||||
return fmt::to_string(detail::first(args...));
|
||||
}
|
||||
#endif
|
||||
constexpr basic_string_view<typename S::char_type> str = S();
|
||||
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
|
||||
return fmt::to_string(detail::first(args...));
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
return format(compiled, std::forward<Args>(args)...);
|
||||
}
|
||||
@@ -667,30 +643,18 @@ OutputIt format_to(OutputIt out, const S&, const Args&... args) {
|
||||
return format_to(out, compiled, args...);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args>
|
||||
auto format_to_n(OutputIt out, size_t n, const CompiledFormat& cf,
|
||||
const Args&... args) ->
|
||||
typename std::enable_if<
|
||||
detail::is_output_iterator<OutputIt,
|
||||
typename CompiledFormat::char_type>::value &&
|
||||
std::is_base_of<detail::basic_compiled_format,
|
||||
CompiledFormat>::value,
|
||||
format_to_n_result<OutputIt>>::type {
|
||||
template <
|
||||
typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&& std::is_base_of<
|
||||
detail::basic_compiled_format, CompiledFormat>::value)>
|
||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
auto it =
|
||||
format_to(detail::truncating_iterator<OutputIt>(out, n), cf, args...);
|
||||
return {it.base(), it.count()};
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, const S&,
|
||||
const Args&... args) {
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), compiled,
|
||||
args...);
|
||||
return {it.base(), it.count()};
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args>
|
||||
size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
|
||||
return format_to(detail::counting_iterator(), cf, args...).count();
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include <vector>
|
||||
|
||||
// The fmt library version in the form major * 10000 + minor * 100 + patch.
|
||||
#define FMT_VERSION 70103
|
||||
#define FMT_VERSION 70001
|
||||
|
||||
#ifdef __clang__
|
||||
# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
|
||||
@@ -57,7 +57,6 @@
|
||||
# define FMT_MSC_VER 0
|
||||
# define FMT_SUPPRESS_MSC_WARNING(n)
|
||||
#endif
|
||||
|
||||
#ifdef __has_feature
|
||||
# define FMT_HAS_FEATURE(x) __has_feature(x)
|
||||
#else
|
||||
@@ -65,7 +64,7 @@
|
||||
#endif
|
||||
|
||||
#if defined(__has_include) && !defined(__INTELLISENSE__) && \
|
||||
(!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1600)
|
||||
!(FMT_ICC_VERSION && FMT_ICC_VERSION < 1600)
|
||||
# define FMT_HAS_INCLUDE(x) __has_include(x)
|
||||
#else
|
||||
# define FMT_HAS_INCLUDE(x) 0
|
||||
@@ -100,7 +99,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef FMT_OVERRIDE
|
||||
# if FMT_HAS_FEATURE(cxx_override_control) || \
|
||||
# if FMT_HAS_FEATURE(cxx_override) || \
|
||||
(FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
|
||||
# define FMT_OVERRIDE override
|
||||
# else
|
||||
@@ -153,7 +152,7 @@
|
||||
# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900
|
||||
# define FMT_DEPRECATED [[deprecated]]
|
||||
# else
|
||||
# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__)
|
||||
# if defined(__GNUC__) || defined(__clang__)
|
||||
# define FMT_DEPRECATED __attribute__((deprecated))
|
||||
# elif FMT_MSC_VER
|
||||
# define FMT_DEPRECATED __declspec(deprecated)
|
||||
@@ -178,17 +177,9 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef FMT_USE_INLINE_NAMESPACES
|
||||
# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \
|
||||
(FMT_MSC_VER >= 1900 && !_MANAGED)
|
||||
# define FMT_USE_INLINE_NAMESPACES 1
|
||||
# else
|
||||
# define FMT_USE_INLINE_NAMESPACES 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef FMT_BEGIN_NAMESPACE
|
||||
# if FMT_USE_INLINE_NAMESPACES
|
||||
# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \
|
||||
FMT_MSC_VER >= 1900
|
||||
# define FMT_INLINE_NAMESPACE inline namespace
|
||||
# define FMT_END_NAMESPACE \
|
||||
} \
|
||||
@@ -278,7 +269,8 @@ struct monostate {};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// A helper function to suppress "conditional expression is constant" warnings.
|
||||
// A helper function to suppress bogus "conditional expression is constant"
|
||||
// warnings.
|
||||
template <typename T> constexpr T const_check(T value) { return value; }
|
||||
|
||||
FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
|
||||
@@ -307,8 +299,7 @@ template <typename T> struct std_string_view {};
|
||||
|
||||
#ifdef FMT_USE_INT128
|
||||
// Do nothing.
|
||||
#elif defined(__SIZEOF_INT128__) && !FMT_NVCC && \
|
||||
!(FMT_CLANG_VERSION && FMT_MSC_VER)
|
||||
#elif defined(__SIZEOF_INT128__) && !FMT_NVCC
|
||||
# define FMT_USE_INT128 1
|
||||
using int128_t = __int128_t;
|
||||
using uint128_t = __uint128_t;
|
||||
@@ -515,18 +506,6 @@ template <typename S> struct char_t_impl<S, enable_if_t<is_string<S>::value>> {
|
||||
using type = typename result::value_type;
|
||||
};
|
||||
|
||||
// Reports a compile-time error if S is not a valid format string.
|
||||
template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)>
|
||||
FMT_INLINE void check_format_string(const S&) {
|
||||
#ifdef FMT_ENFORCE_COMPILE_STRING
|
||||
static_assert(is_compile_string<S>::value,
|
||||
"FMT_ENFORCE_COMPILE_STRING requires all format strings to use "
|
||||
"FMT_STRING.");
|
||||
#endif
|
||||
}
|
||||
template <typename..., typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
|
||||
void check_format_string(S);
|
||||
|
||||
struct error_handler {
|
||||
constexpr error_handler() = default;
|
||||
constexpr error_handler(const error_handler&) = default;
|
||||
@@ -566,9 +545,8 @@ class basic_format_parse_context : private ErrorHandler {
|
||||
using iterator = typename basic_string_view<Char>::iterator;
|
||||
|
||||
explicit constexpr basic_format_parse_context(
|
||||
basic_string_view<Char> format_str, ErrorHandler eh = {},
|
||||
int next_arg_id = 0)
|
||||
: ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {}
|
||||
basic_string_view<Char> format_str, ErrorHandler eh = {})
|
||||
: ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {}
|
||||
|
||||
/**
|
||||
Returns an iterator to the beginning of the format string range being
|
||||
@@ -638,24 +616,8 @@ template <typename T, typename Context>
|
||||
using has_formatter =
|
||||
std::is_constructible<typename Context::template formatter_type<T>>;
|
||||
|
||||
// Checks whether T is a container with contiguous storage.
|
||||
template <typename T> struct is_contiguous : std::false_type {};
|
||||
template <typename Char>
|
||||
struct is_contiguous<std::basic_string<Char>> : std::true_type {};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Extracts a reference to the container from back_insert_iterator.
|
||||
template <typename Container>
|
||||
inline Container& get_container(std::back_insert_iterator<Container> it) {
|
||||
using bi_iterator = std::back_insert_iterator<Container>;
|
||||
struct accessor : bi_iterator {
|
||||
accessor(bi_iterator iter) : bi_iterator(iter) {}
|
||||
using bi_iterator::container;
|
||||
};
|
||||
return *accessor(it).container;
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
A contiguous memory buffer with an optional growing ability. It is an internal
|
||||
@@ -678,8 +640,6 @@ template <typename T> class buffer {
|
||||
size_(sz),
|
||||
capacity_(cap) {}
|
||||
|
||||
~buffer() = default;
|
||||
|
||||
/** Sets the buffer data and capacity. */
|
||||
void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT {
|
||||
ptr_ = buf_data;
|
||||
@@ -695,6 +655,7 @@ template <typename T> class buffer {
|
||||
|
||||
buffer(const buffer&) = delete;
|
||||
void operator=(const buffer&) = delete;
|
||||
virtual ~buffer() = default;
|
||||
|
||||
T* begin() FMT_NOEXCEPT { return ptr_; }
|
||||
T* end() FMT_NOEXCEPT { return ptr_ + size_; }
|
||||
@@ -714,26 +675,24 @@ template <typename T> class buffer {
|
||||
/** Returns a pointer to the buffer data. */
|
||||
const T* data() const FMT_NOEXCEPT { return ptr_; }
|
||||
|
||||
/**
|
||||
Resizes the buffer. If T is a POD type new elements may not be initialized.
|
||||
*/
|
||||
void resize(size_t new_size) {
|
||||
reserve(new_size);
|
||||
size_ = new_size;
|
||||
}
|
||||
|
||||
/** Clears this buffer. */
|
||||
void clear() { size_ = 0; }
|
||||
|
||||
// Tries resizing the buffer to contain *count* elements. If T is a POD type
|
||||
// the new elements may not be initialized.
|
||||
void try_resize(size_t count) {
|
||||
try_reserve(count);
|
||||
size_ = count <= capacity_ ? count : capacity_;
|
||||
}
|
||||
|
||||
// Tries increasing the buffer capacity to *new_capacity*. It can increase the
|
||||
// capacity by a smaller amount than requested but guarantees there is space
|
||||
// for at least one additional element either by increasing the capacity or by
|
||||
// flushing the buffer if it is full.
|
||||
void try_reserve(size_t new_capacity) {
|
||||
/** Reserves space to store at least *capacity* elements. */
|
||||
void reserve(size_t new_capacity) {
|
||||
if (new_capacity > capacity_) grow(new_capacity);
|
||||
}
|
||||
|
||||
void push_back(const T& value) {
|
||||
try_reserve(size_ + 1);
|
||||
reserve(size_ + 1);
|
||||
ptr_[size_++] = value;
|
||||
}
|
||||
|
||||
@@ -746,150 +705,32 @@ template <typename T> class buffer {
|
||||
}
|
||||
};
|
||||
|
||||
struct buffer_traits {
|
||||
explicit buffer_traits(size_t) {}
|
||||
size_t count() const { return 0; }
|
||||
size_t limit(size_t size) { return size; }
|
||||
};
|
||||
|
||||
class fixed_buffer_traits {
|
||||
private:
|
||||
size_t count_ = 0;
|
||||
size_t limit_;
|
||||
|
||||
public:
|
||||
explicit fixed_buffer_traits(size_t limit) : limit_(limit) {}
|
||||
size_t count() const { return count_; }
|
||||
size_t limit(size_t size) {
|
||||
size_t n = limit_ > count_ ? limit_ - count_ : 0;
|
||||
count_ += size;
|
||||
return size < n ? size : n;
|
||||
}
|
||||
};
|
||||
|
||||
// A buffer that writes to an output iterator when flushed.
|
||||
template <typename OutputIt, typename T, typename Traits = buffer_traits>
|
||||
class iterator_buffer final : public Traits, public buffer<T> {
|
||||
private:
|
||||
OutputIt out_;
|
||||
enum { buffer_size = 256 };
|
||||
T data_[buffer_size];
|
||||
|
||||
protected:
|
||||
void grow(size_t) final FMT_OVERRIDE {
|
||||
if (this->size() == buffer_size) flush();
|
||||
}
|
||||
void flush();
|
||||
|
||||
public:
|
||||
explicit iterator_buffer(OutputIt out, size_t n = buffer_size)
|
||||
: Traits(n),
|
||||
buffer<T>(data_, 0, buffer_size),
|
||||
out_(out) {}
|
||||
~iterator_buffer() { flush(); }
|
||||
|
||||
OutputIt out() {
|
||||
flush();
|
||||
return out_;
|
||||
}
|
||||
size_t count() const { return Traits::count() + this->size(); }
|
||||
};
|
||||
|
||||
template <typename T> class iterator_buffer<T*, T> final : public buffer<T> {
|
||||
protected:
|
||||
void grow(size_t) final FMT_OVERRIDE {}
|
||||
|
||||
public:
|
||||
explicit iterator_buffer(T* out, size_t = 0) : buffer<T>(out, 0, ~size_t()) {}
|
||||
|
||||
T* out() { return &*this->end(); }
|
||||
};
|
||||
|
||||
// A buffer that writes to a container with the contiguous storage.
|
||||
// A container-backed buffer.
|
||||
template <typename Container>
|
||||
class iterator_buffer<std::back_insert_iterator<Container>,
|
||||
enable_if_t<is_contiguous<Container>::value,
|
||||
typename Container::value_type>>
|
||||
final : public buffer<typename Container::value_type> {
|
||||
class container_buffer : public buffer<typename Container::value_type> {
|
||||
private:
|
||||
Container& container_;
|
||||
|
||||
protected:
|
||||
void grow(size_t capacity) final FMT_OVERRIDE {
|
||||
void grow(size_t capacity) FMT_OVERRIDE {
|
||||
container_.resize(capacity);
|
||||
this->set(&container_[0], capacity);
|
||||
}
|
||||
|
||||
public:
|
||||
explicit iterator_buffer(Container& c)
|
||||
explicit container_buffer(Container& c)
|
||||
: buffer<typename Container::value_type>(c.size()), container_(c) {}
|
||||
explicit iterator_buffer(std::back_insert_iterator<Container> out, size_t = 0)
|
||||
: iterator_buffer(get_container(out)) {}
|
||||
std::back_insert_iterator<Container> out() {
|
||||
return std::back_inserter(container_);
|
||||
}
|
||||
};
|
||||
|
||||
// A buffer that counts the number of code units written discarding the output.
|
||||
template <typename T = char> class counting_buffer final : public buffer<T> {
|
||||
private:
|
||||
enum { buffer_size = 256 };
|
||||
T data_[buffer_size];
|
||||
size_t count_ = 0;
|
||||
|
||||
protected:
|
||||
void grow(size_t) final FMT_OVERRIDE {
|
||||
if (this->size() != buffer_size) return;
|
||||
count_ += this->size();
|
||||
this->clear();
|
||||
}
|
||||
|
||||
public:
|
||||
counting_buffer() : buffer<T>(data_, 0, buffer_size) {}
|
||||
|
||||
size_t count() { return count_ + this->size(); }
|
||||
};
|
||||
|
||||
// An output iterator that appends to the buffer.
|
||||
// It is used to reduce symbol sizes for the common case.
|
||||
template <typename T>
|
||||
class buffer_appender : public std::back_insert_iterator<buffer<T>> {
|
||||
using base = std::back_insert_iterator<buffer<T>>;
|
||||
|
||||
public:
|
||||
explicit buffer_appender(buffer<T>& buf) : base(buf) {}
|
||||
buffer_appender(base it) : base(it) {}
|
||||
|
||||
buffer_appender& operator++() {
|
||||
base::operator++();
|
||||
return *this;
|
||||
}
|
||||
|
||||
buffer_appender operator++(int) {
|
||||
buffer_appender tmp = *this;
|
||||
++*this;
|
||||
return tmp;
|
||||
}
|
||||
};
|
||||
|
||||
// Maps an output iterator into a buffer.
|
||||
template <typename T, typename OutputIt>
|
||||
iterator_buffer<OutputIt, T> get_buffer(OutputIt);
|
||||
template <typename T> buffer<T>& get_buffer(buffer_appender<T>);
|
||||
|
||||
template <typename OutputIt> OutputIt get_buffer_init(OutputIt out) {
|
||||
return out;
|
||||
}
|
||||
template <typename T> buffer<T>& get_buffer_init(buffer_appender<T> out) {
|
||||
return get_container(out);
|
||||
}
|
||||
|
||||
template <typename Buffer>
|
||||
auto get_iterator(Buffer& buf) -> decltype(buf.out()) {
|
||||
return buf.out();
|
||||
}
|
||||
template <typename T> buffer_appender<T> get_iterator(buffer<T>& buf) {
|
||||
return buffer_appender<T>(buf);
|
||||
// Extracts a reference to the container from back_insert_iterator.
|
||||
template <typename Container>
|
||||
inline Container& get_container(std::back_insert_iterator<Container> it) {
|
||||
using bi_iterator = std::back_insert_iterator<Container>;
|
||||
struct accessor : bi_iterator {
|
||||
accessor(bi_iterator iter) : bi_iterator(iter) {}
|
||||
using bi_iterator::container;
|
||||
};
|
||||
return *accessor(it).container;
|
||||
}
|
||||
|
||||
template <typename T, typename Char = char, typename Enable = void>
|
||||
@@ -918,8 +759,7 @@ template <typename Char> struct named_arg_info {
|
||||
template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS>
|
||||
struct arg_data {
|
||||
// args_[0].named_args points to named_args_ to avoid bloating format_args.
|
||||
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
|
||||
T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)];
|
||||
T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : 1)];
|
||||
named_arg_info<Char> named_args_[NUM_NAMED_ARGS];
|
||||
|
||||
template <typename... U>
|
||||
@@ -931,8 +771,7 @@ struct arg_data {
|
||||
|
||||
template <typename T, typename Char, size_t NUM_ARGS>
|
||||
struct arg_data<T, Char, NUM_ARGS, 0> {
|
||||
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
|
||||
T args_[NUM_ARGS != 0 ? NUM_ARGS : +1];
|
||||
T args_[NUM_ARGS != 0 ? NUM_ARGS : 1];
|
||||
|
||||
template <typename... U>
|
||||
FMT_INLINE arg_data(const U&... init) : args_{init...} {}
|
||||
@@ -1120,8 +959,6 @@ enum { long_short = sizeof(long) == sizeof(int) };
|
||||
using long_type = conditional_t<long_short, int, long long>;
|
||||
using ulong_type = conditional_t<long_short, unsigned, unsigned long long>;
|
||||
|
||||
struct unformattable {};
|
||||
|
||||
// Maps formatting arguments to core types.
|
||||
template <typename Context> struct arg_mapper {
|
||||
using char_type = typename Context::char_type;
|
||||
@@ -1230,7 +1067,15 @@ template <typename Context> struct arg_mapper {
|
||||
return map(val.value);
|
||||
}
|
||||
|
||||
unformattable map(...) { return {}; }
|
||||
int map(...) {
|
||||
constexpr bool formattable = sizeof(Context) == 0;
|
||||
static_assert(
|
||||
formattable,
|
||||
"Cannot format argument. To make type T formattable provide a "
|
||||
"formatter<T> specialization: "
|
||||
"https://fmt.dev/latest/api.html#formatting-user-defined-types");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// A type constant after applying arg_mapper<Context>.
|
||||
@@ -1354,25 +1199,15 @@ FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg(
|
||||
return vis(monostate());
|
||||
}
|
||||
|
||||
template <typename T> struct formattable : std::false_type {};
|
||||
// Checks whether T is a container with contiguous storage.
|
||||
template <typename T> struct is_contiguous : std::false_type {};
|
||||
template <typename Char>
|
||||
struct is_contiguous<std::basic_string<Char>> : std::true_type {};
|
||||
template <typename Char>
|
||||
struct is_contiguous<detail::buffer<Char>> : std::true_type {};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// A workaround for gcc 4.8 to make void_t work in a SFINAE context.
|
||||
template <typename... Ts> struct void_t_impl { using type = void; };
|
||||
template <typename... Ts>
|
||||
using void_t = typename detail::void_t_impl<Ts...>::type;
|
||||
|
||||
template <typename It, typename T, typename Enable = void>
|
||||
struct is_output_iterator : std::false_type {};
|
||||
|
||||
template <typename It, typename T>
|
||||
struct is_output_iterator<
|
||||
It, T,
|
||||
void_t<typename std::iterator_traits<It>::iterator_category,
|
||||
decltype(*std::declval<It>() = std::declval<T>())>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename OutputIt>
|
||||
struct is_back_insert_iterator : std::false_type {};
|
||||
template <typename Container>
|
||||
@@ -1384,9 +1219,6 @@ struct is_contiguous_back_insert_iterator : std::false_type {};
|
||||
template <typename Container>
|
||||
struct is_contiguous_back_insert_iterator<std::back_insert_iterator<Container>>
|
||||
: is_contiguous<Container> {};
|
||||
template <typename Char>
|
||||
struct is_contiguous_back_insert_iterator<buffer_appender<Char>>
|
||||
: std::true_type {};
|
||||
|
||||
// A type-erased reference to an std::locale to avoid heavy <locale> include.
|
||||
class locale_ref {
|
||||
@@ -1418,24 +1250,13 @@ FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T& value) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
template <typename T> int check(unformattable) {
|
||||
static_assert(
|
||||
formattable<T>(),
|
||||
"Cannot format an argument. To make type T formattable provide a "
|
||||
"formatter<T> specialization: https://fmt.dev/latest/api.html#udt");
|
||||
return 0;
|
||||
}
|
||||
template <typename T, typename U> inline const U& check(const U& val) {
|
||||
return val;
|
||||
}
|
||||
|
||||
// The type template parameter is there to avoid an ODR violation when using
|
||||
// a fallback formatter in one translation unit and an implicit conversion in
|
||||
// another (not recommended).
|
||||
template <bool IS_PACKED, typename Context, type, typename T,
|
||||
FMT_ENABLE_IF(IS_PACKED)>
|
||||
inline value<Context> make_arg(const T& val) {
|
||||
return check<T>(arg_mapper<Context>().map(val));
|
||||
return arg_mapper<Context>().map(val);
|
||||
}
|
||||
|
||||
template <bool IS_PACKED, typename Context, type, typename T,
|
||||
@@ -1535,13 +1356,13 @@ template <typename OutputIt, typename Char> class basic_format_context {
|
||||
|
||||
template <typename Char>
|
||||
using buffer_context =
|
||||
basic_format_context<detail::buffer_appender<Char>, Char>;
|
||||
basic_format_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
|
||||
using format_context = buffer_context<char>;
|
||||
using wformat_context = buffer_context<wchar_t>;
|
||||
|
||||
// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164.
|
||||
// Workaround a bug in gcc: https://stackoverflow.com/q/62767544/471164.
|
||||
#define FMT_BUFFER_CONTEXT(Char) \
|
||||
basic_format_context<detail::buffer_appender<Char>, Char>
|
||||
basic_format_context<std::back_insert_iterator<detail::buffer<Char>>, Char>
|
||||
|
||||
/**
|
||||
\rst
|
||||
@@ -1593,7 +1414,7 @@ class format_arg_store
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a `~fmt::format_arg_store` object that contains references to
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
arguments and can be implicitly converted to `~fmt::format_args`. `Context`
|
||||
can be omitted in which case it defaults to `~fmt::context`.
|
||||
See `~fmt::arg` for lifetime considerations.
|
||||
@@ -1605,27 +1426,6 @@ inline format_arg_store<Context, Args...> make_format_args(
|
||||
return {args...};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a `~fmt::format_arg_store` object that contains references
|
||||
to arguments and can be implicitly converted to `~fmt::format_args`.
|
||||
If ``format_str`` is a compile-time string then `make_args_checked` checks
|
||||
its validity at compile time.
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args, typename S, typename Char = char_t<S>>
|
||||
inline auto make_args_checked(const S& format_str,
|
||||
const remove_reference_t<Args>&... args)
|
||||
-> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
|
||||
static_assert(
|
||||
detail::count<(
|
||||
std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
|
||||
std::is_reference<Args>::value)...>() == 0,
|
||||
"passing views as lvalues is disallowed");
|
||||
detail::check_format_string<Args...>(format_str);
|
||||
return {args...};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns a named argument to be used in a formatting function. It should only
|
||||
@@ -1913,7 +1713,7 @@ template <typename Context> class basic_format_args {
|
||||
}
|
||||
|
||||
template <typename Char> int get_id(basic_string_view<Char> name) const {
|
||||
if (!has_named_args()) return -1;
|
||||
if (!has_named_args()) return {};
|
||||
const auto& named_args =
|
||||
(is_packed() ? values_[-1] : args_[-1].value_).named_args;
|
||||
for (size_t i = 0; i < named_args.size; ++i) {
|
||||
@@ -1929,14 +1729,7 @@ template <typename Context> class basic_format_args {
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef FMT_ARM_ABI_COMPATIBILITY
|
||||
/** An alias to ``basic_format_args<format_context>``. */
|
||||
// Separate types would result in shorter symbols but break ABI compatibility
|
||||
// between clang and gcc on ARM (#1919).
|
||||
using format_args = basic_format_args<format_context>;
|
||||
using wformat_args = basic_format_args<wformat_context>;
|
||||
#else
|
||||
// DEPRECATED! These are kept for ABI compatibility.
|
||||
/** An alias to ``basic_format_args<context>``. */
|
||||
// It is a separate type rather than an alias to make symbols readable.
|
||||
struct format_args : basic_format_args<format_context> {
|
||||
template <typename... Args>
|
||||
@@ -1945,10 +1738,32 @@ struct format_args : basic_format_args<format_context> {
|
||||
struct wformat_args : basic_format_args<wformat_context> {
|
||||
using basic_format_args::basic_format_args;
|
||||
};
|
||||
#endif
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Reports a compile-time error if S is not a valid format string.
|
||||
template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)>
|
||||
FMT_INLINE void check_format_string(const S&) {
|
||||
#ifdef FMT_ENFORCE_COMPILE_STRING
|
||||
static_assert(is_compile_string<S>::value,
|
||||
"FMT_ENFORCE_COMPILE_STRING requires all format strings to use "
|
||||
"FMT_STRING.");
|
||||
#endif
|
||||
}
|
||||
template <typename..., typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
|
||||
void check_format_string(S);
|
||||
|
||||
template <typename... Args, typename S, typename Char = char_t<S>>
|
||||
inline format_arg_store<buffer_context<Char>, remove_reference_t<Args>...>
|
||||
make_args_checked(const S& format_str,
|
||||
const remove_reference_t<Args>&... args) {
|
||||
static_assert(count<(std::is_base_of<view, remove_reference_t<Args>>::value &&
|
||||
std::is_reference<Args>::value)...>() == 0,
|
||||
"passing views as lvalues is disallowed");
|
||||
check_format_string<Args...>(format_str);
|
||||
return {args...};
|
||||
}
|
||||
|
||||
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
std::basic_string<Char> vformat(
|
||||
basic_string_view<Char> format_str,
|
||||
@@ -1957,10 +1772,9 @@ std::basic_string<Char> vformat(
|
||||
FMT_API std::string vformat(string_view format_str, format_args args);
|
||||
|
||||
template <typename Char>
|
||||
void vformat_to(
|
||||
typename FMT_BUFFER_CONTEXT(Char)::iterator vformat_to(
|
||||
buffer<Char>& buf, basic_string_view<Char> format_str,
|
||||
basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args,
|
||||
detail::locale_ref loc = {});
|
||||
basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args);
|
||||
|
||||
template <typename Char, typename Args,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
@@ -1975,80 +1789,26 @@ inline void vprint_mojibake(std::FILE*, string_view, format_args) {}
|
||||
/** Formats a string and writes the output to ``out``. */
|
||||
// GCC 8 and earlier cannot handle std::back_insert_iterator<Container> with
|
||||
// vformat_to<ArgFormatter>(...) overload, so SFINAE on iterator type instead.
|
||||
template <typename OutputIt, typename S, typename Char = char_t<S>,
|
||||
bool enable = detail::is_output_iterator<OutputIt, Char>::value>
|
||||
auto vformat_to(OutputIt out, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> typename std::enable_if<enable, OutputIt>::type {
|
||||
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
|
||||
detail::vformat_to(buf, to_string_view(format_str), args);
|
||||
return detail::get_iterator(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments, writes the result to the output iterator ``out`` and returns
|
||||
the iterator past the end of the output range.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::vector<char> out;
|
||||
fmt::format_to(std::back_inserter(out), "{}", 42);
|
||||
\endrst
|
||||
*/
|
||||
// We cannot use FMT_ENABLE_IF because of a bug in gcc 8.3.
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
|
||||
inline auto format_to(OutputIt out, const S& format_str, Args&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
return vformat_to(out, to_string_view(format_str), vargs);
|
||||
}
|
||||
|
||||
template <typename OutputIt> struct format_to_n_result {
|
||||
/** Iterator past the end of the output range. */
|
||||
OutputIt out;
|
||||
/** Total (not truncated) output size. */
|
||||
size_t size;
|
||||
};
|
||||
|
||||
template <typename OutputIt, typename Char, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
|
||||
inline format_to_n_result<OutputIt> vformat_to_n(
|
||||
OutputIt out, size_t n, basic_string_view<Char> format_str,
|
||||
template <
|
||||
typename OutputIt, typename S, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_contiguous_back_insert_iterator<OutputIt>::value)>
|
||||
OutputIt vformat_to(
|
||||
OutputIt out, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
|
||||
n);
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
return {buf.out(), buf.count()};
|
||||
auto& c = detail::get_container(out);
|
||||
detail::container_buffer<remove_reference_t<decltype(c)>> buf(c);
|
||||
detail::vformat_to(buf, to_string_view(format_str), args);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments, writes up to ``n`` characters of the result to the output
|
||||
iterator ``out`` and returns the total output size and the iterator past the
|
||||
end of the output range.
|
||||
\endrst
|
||||
*/
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
|
||||
inline auto format_to_n(OutputIt out, size_t n, const S& format_str,
|
||||
const Args&... args) ->
|
||||
typename std::enable_if<enable, format_to_n_result<OutputIt>>::type {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
return vformat_to_n(out, n, to_string_view(format_str), vargs);
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of characters in the output of
|
||||
``format(format_str, args...)``.
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline size_t formatted_size(string_view format_str, Args&&... args) {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
detail::counting_buffer<> buf;
|
||||
detail::vformat_to(buf, format_str, vargs);
|
||||
return buf.count();
|
||||
template <typename Container, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(
|
||||
is_contiguous<Container>::value&& detail::is_string<S>::value)>
|
||||
inline std::back_insert_iterator<Container> format_to(
|
||||
std::back_insert_iterator<Container> out, const S& format_str,
|
||||
Args&&... args) {
|
||||
return vformat_to(out, to_string_view(format_str),
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
@@ -2072,7 +1832,7 @@ FMT_INLINE std::basic_string<Char> vformat(
|
||||
// std::basic_string<char_t<S>> to reduce the symbol size.
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
FMT_INLINE std::basic_string<Char> format(const S& format_str, Args&&... args) {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
const auto& vargs = detail::make_args_checked<Args...>(format_str, args...);
|
||||
return detail::vformat(to_string_view(format_str), vargs);
|
||||
}
|
||||
|
||||
@@ -2092,7 +1852,7 @@ FMT_API void vprint(std::FILE*, string_view, format_args);
|
||||
*/
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline void print(std::FILE* f, const S& format_str, Args&&... args) {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
const auto& vargs = detail::make_args_checked<Args...>(format_str, args...);
|
||||
return detail::is_unicode<Char>()
|
||||
? vprint(f, to_string_view(format_str), vargs)
|
||||
: detail::vprint_mojibake(f, to_string_view(format_str), vargs);
|
||||
@@ -2111,7 +1871,7 @@ inline void print(std::FILE* f, const S& format_str, Args&&... args) {
|
||||
*/
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline void print(const S& format_str, Args&&... args) {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
const auto& vargs = detail::make_args_checked<Args...>(format_str, args...);
|
||||
return detail::is_unicode<Char>()
|
||||
? vprint(to_string_view(format_str), vargs)
|
||||
: detail::vprint_mojibake(stdout, to_string_view(format_str),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,12 +15,22 @@
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
template <typename Char>
|
||||
typename buffer_context<Char>::iterator vformat_to(
|
||||
const std::locale& loc, buffer<Char>& buf,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
using af = arg_formatter<typename buffer_context<Char>::iterator, Char>;
|
||||
return vformat_to<af>(std::back_inserter(buf), to_string_view(format_str),
|
||||
args, detail::locale_ref(loc));
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
std::basic_string<Char> vformat(
|
||||
const std::locale& loc, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::vformat_to(buffer, format_str, args, detail::locale_ref(loc));
|
||||
detail::vformat_to(loc, buffer, format_str, args);
|
||||
return fmt::to_string(buffer);
|
||||
}
|
||||
} // namespace detail
|
||||
@@ -35,28 +45,32 @@ inline std::basic_string<Char> vformat(
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const std::locale& loc,
|
||||
const S& format_str, Args&&... args) {
|
||||
return detail::vformat(loc, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
return detail::vformat(
|
||||
loc, to_string_view(format_str),
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
template <typename S, typename OutputIt, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
|
||||
typename Char = enable_if_t<
|
||||
detail::is_output_iterator<OutputIt>::value, char_t<S>>>
|
||||
inline OutputIt vformat_to(
|
||||
OutputIt out, const std::locale& loc, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
|
||||
vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
|
||||
return detail::get_iterator(buf);
|
||||
format_args_t<type_identity_t<OutputIt>, Char> args) {
|
||||
using af = detail::arg_formatter<OutputIt, Char>;
|
||||
return vformat_to<af>(out, to_string_view(format_str), args,
|
||||
detail::locale_ref(loc));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
|
||||
inline auto format_to(OutputIt out, const std::locale& loc,
|
||||
const S& format_str, Args&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
return vformat_to(out, loc, to_string_view(format_str), vargs);
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&&
|
||||
detail::is_string<S>::value)>
|
||||
inline OutputIt format_to(OutputIt out, const std::locale& loc,
|
||||
const S& format_str, Args&&... args) {
|
||||
detail::check_format_string<Args...>(format_str);
|
||||
using context = format_context_t<OutputIt, char_t<S>>;
|
||||
format_arg_store<context, Args...> as{args...};
|
||||
return vformat_to(out, loc, to_string_view(format_str),
|
||||
basic_format_args<context>(as));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
#if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
#endif
|
||||
#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
|
||||
defined(__linux__)) && \
|
||||
#if FMT_HAS_INCLUDE("fcntl.h") && \
|
||||
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h> // for O_RDONLY
|
||||
# define FMT_USE_FCNTL 1
|
||||
@@ -279,8 +278,7 @@ class file {
|
||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
|
||||
CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
|
||||
APPEND = FMT_POSIX(O_APPEND) // Open in append mode.
|
||||
CREATE = FMT_POSIX(O_CREAT) // Create if the file doesn't exist.
|
||||
};
|
||||
|
||||
// Constructs a file object which doesn't represent any file.
|
||||
@@ -345,69 +343,36 @@ class file {
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
|
||||
namespace detail {
|
||||
class direct_buffered_file;
|
||||
|
||||
struct buffer_size {
|
||||
size_t value = 0;
|
||||
buffer_size operator=(size_t val) const {
|
||||
auto bs = buffer_size();
|
||||
bs.value = val;
|
||||
return bs;
|
||||
}
|
||||
};
|
||||
template <typename S, typename... Args>
|
||||
void print(direct_buffered_file& f, const S& format_str,
|
||||
const Args&... args);
|
||||
|
||||
struct ostream_params {
|
||||
int oflag = file::WRONLY | file::CREATE;
|
||||
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
|
||||
|
||||
ostream_params() {}
|
||||
|
||||
template <typename... T>
|
||||
ostream_params(T... params, int oflag) : ostream_params(params...) {
|
||||
this->oflag = oflag;
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
ostream_params(T... params, detail::buffer_size bs)
|
||||
: ostream_params(params...) {
|
||||
this->buffer_size = bs.value;
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
static constexpr detail::buffer_size buffer_size;
|
||||
|
||||
// A fast output stream which is not thread-safe.
|
||||
class ostream final : private detail::buffer<char> {
|
||||
// A buffered file with a direct buffer access and no synchronization.
|
||||
class direct_buffered_file {
|
||||
private:
|
||||
file file_;
|
||||
|
||||
enum { buffer_size = 4096 };
|
||||
char buffer_[buffer_size];
|
||||
int pos_;
|
||||
|
||||
void flush() {
|
||||
if (size() == 0) return;
|
||||
file_.write(data(), size());
|
||||
clear();
|
||||
if (pos_ == 0) return;
|
||||
file_.write(buffer_, pos_);
|
||||
pos_ = 0;
|
||||
}
|
||||
|
||||
FMT_API void grow(size_t) override final;
|
||||
|
||||
ostream(cstring_view path, const detail::ostream_params& params)
|
||||
: file_(path, params.oflag) {
|
||||
set(new char[params.buffer_size], params.buffer_size);
|
||||
}
|
||||
int free_capacity() const { return buffer_size - pos_; }
|
||||
|
||||
public:
|
||||
ostream(ostream&& other)
|
||||
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
|
||||
file_(std::move(other.file_)) {
|
||||
other.set(nullptr, 0);
|
||||
}
|
||||
~ostream() {
|
||||
flush();
|
||||
delete[] data();
|
||||
}
|
||||
direct_buffered_file(cstring_view path, int oflag)
|
||||
: file_(path, oflag), pos_(0) {}
|
||||
|
||||
template <typename... T>
|
||||
friend ostream output_file(cstring_view path, T... params);
|
||||
~direct_buffered_file() {
|
||||
flush();
|
||||
}
|
||||
|
||||
void close() {
|
||||
flush();
|
||||
@@ -415,20 +380,25 @@ class ostream final : private detail::buffer<char> {
|
||||
}
|
||||
|
||||
template <typename S, typename... Args>
|
||||
void print(const S& format_str, const Args&... args) {
|
||||
format_to(detail::buffer_appender<char>(*this), format_str, args...);
|
||||
friend void print(direct_buffered_file& f, const S& format_str,
|
||||
const Args&... args) {
|
||||
// We could avoid double buffering.
|
||||
auto buf = fmt::memory_buffer();
|
||||
fmt::format_to(std::back_inserter(buf), format_str, args...);
|
||||
auto remaining_pos = 0;
|
||||
auto remaining_size = buf.size();
|
||||
while (remaining_size > detail::to_unsigned(f.free_capacity())) {
|
||||
auto size = f.free_capacity();
|
||||
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, size);
|
||||
f.pos_ += size;
|
||||
f.flush();
|
||||
remaining_pos += size;
|
||||
remaining_size -= size;
|
||||
}
|
||||
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, remaining_size);
|
||||
f.pos_ += static_cast<int>(remaining_size);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Opens a file for writing. Supported parameters passed in `params`:
|
||||
* ``<integer>``: Output flags (``file::WRONLY | file::CREATE`` by default)
|
||||
* ``buffer_size=<integer>``: Output buffer size
|
||||
*/
|
||||
template <typename... T>
|
||||
inline ostream output_file(cstring_view path, T... params) {
|
||||
return {path, detail::ostream_params(params...)};
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
|
||||
@@ -49,26 +49,16 @@ template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||
}
|
||||
};
|
||||
|
||||
struct converter {
|
||||
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
|
||||
};
|
||||
|
||||
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||
private:
|
||||
void_t<> operator<<(converter);
|
||||
};
|
||||
// Hide all operator<< from std::basic_ostream<Char>.
|
||||
void_t<> operator<<(null<>);
|
||||
void_t<> operator<<(const Char*);
|
||||
|
||||
// Hide insertion operators for built-in types.
|
||||
template <typename Char, typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
|
||||
template <typename Char, typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
|
||||
template <typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
|
||||
template <typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
|
||||
template <typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
|
||||
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
|
||||
!std::is_enum<T>::value)>
|
||||
void_t<> operator<<(T);
|
||||
};
|
||||
|
||||
// Checks if T has a user-defined operator<< (e.g. not a member of
|
||||
// std::ostream).
|
||||
@@ -113,7 +103,7 @@ void format_value(buffer<Char>& buf, const T& value,
|
||||
#endif
|
||||
output << value;
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
buf.try_resize(buf.size());
|
||||
buf.resize(buf.size());
|
||||
}
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
@@ -170,7 +160,7 @@ template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
||||
vprint(os, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ template <typename Char> class printf_width_handler {
|
||||
template <typename Char, typename Context>
|
||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
Context(buffer_appender<Char>(buf), format, args).format();
|
||||
Context(std::back_inserter(buf), format, args).format();
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
@@ -598,7 +598,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
|
||||
template <typename Char>
|
||||
using basic_printf_context_t =
|
||||
basic_printf_context<detail::buffer_appender<Char>, Char>;
|
||||
basic_printf_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
|
||||
|
||||
using printf_context = basic_printf_context_t<char>;
|
||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
||||
|
||||
@@ -157,9 +157,6 @@ template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
using value_type = remove_cvref_t<decltype(*std::declval<Range>().begin())>;
|
||||
|
||||
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string<
|
||||
typename std::decay<Arg>::type>::value)>
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
|
||||
@@ -185,6 +182,7 @@ FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
|
||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
|
||||
return add_space ? L" '{}'" : L"'{}'";
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T> struct is_tuple_like {
|
||||
@@ -248,18 +246,9 @@ template <typename T, typename Char> struct is_range {
|
||||
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
enable_if_t<fmt::is_range<T, Char>::value
|
||||
// Workaround a bug in MSVC 2017 and earlier.
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
|
||||
&&
|
||||
(has_formatter<detail::value_type<T>, format_context>::value ||
|
||||
detail::has_fallback_formatter<detail::value_type<T>,
|
||||
format_context>::value)
|
||||
#endif
|
||||
>> {
|
||||
template <typename RangeT, typename Char>
|
||||
struct formatter<RangeT, Char,
|
||||
enable_if_t<fmt::is_range<RangeT, Char>::value>> {
|
||||
formatting_range<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
@@ -268,7 +257,8 @@ struct formatter<
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
|
||||
typename FormatContext::iterator format(const RangeT& values,
|
||||
FormatContext& ctx) {
|
||||
auto out = detail::copy(formatting.prefix, ctx.out());
|
||||
size_t i = 0;
|
||||
auto it = values.begin();
|
||||
|
||||
@@ -23,36 +23,6 @@ int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||
return precision < 0 ? snprintf_ptr(buf, size, format, value)
|
||||
: snprintf_ptr(buf, size, format, precision, value);
|
||||
}
|
||||
|
||||
template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x)
|
||||
FMT_NOEXCEPT;
|
||||
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
|
||||
FMT_NOEXCEPT;
|
||||
|
||||
// DEPRECATED! This function exists for ABI compatibility.
|
||||
template <typename Char>
|
||||
typename basic_format_context<std::back_insert_iterator<buffer<Char>>,
|
||||
Char>::iterator
|
||||
vformat_to(buffer<Char>& buf, basic_string_view<Char> format_str,
|
||||
basic_format_args<basic_format_context<
|
||||
std::back_insert_iterator<buffer<type_identity_t<Char>>>,
|
||||
type_identity_t<Char>>>
|
||||
args) {
|
||||
using iterator = std::back_insert_iterator<buffer<char>>;
|
||||
using context = basic_format_context<
|
||||
std::back_insert_iterator<buffer<type_identity_t<Char>>>,
|
||||
type_identity_t<Char>>;
|
||||
auto out = iterator(buf);
|
||||
format_handler<iterator, Char, context> h(out, format_str, args, {});
|
||||
parse_format_string<false>(format_str, h);
|
||||
return out;
|
||||
}
|
||||
template basic_format_context<std::back_insert_iterator<buffer<char>>,
|
||||
char>::iterator
|
||||
vformat_to(buffer<char>&, string_view,
|
||||
basic_format_args<basic_format_context<
|
||||
std::back_insert_iterator<buffer<type_identity_t<char>>>,
|
||||
type_identity_t<char>>>);
|
||||
} // namespace detail
|
||||
|
||||
template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;
|
||||
@@ -74,9 +44,9 @@ template FMT_API char detail::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void detail::buffer<char>::append(const char*, const char*);
|
||||
|
||||
template FMT_API void detail::vformat_to(
|
||||
template FMT_API FMT_BUFFER_CONTEXT(char)::iterator detail::vformat_to(
|
||||
detail::buffer<char>&, string_view,
|
||||
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
|
||||
basic_format_args<FMT_BUFFER_CONTEXT(char)>);
|
||||
|
||||
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
|
||||
@@ -62,7 +62,7 @@ using RWResult = int;
|
||||
inline unsigned convert_rwcount(std::size_t count) {
|
||||
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
|
||||
}
|
||||
#elif FMT_USE_FCNTL
|
||||
#else
|
||||
// Return type of read and write functions.
|
||||
using RWResult = ssize_t;
|
||||
|
||||
@@ -124,8 +124,7 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
||||
if (result != 0) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
|
||||
format_to(buffer_appender<char>(out), "{}: {}", message,
|
||||
utf8_message);
|
||||
format_to(std::back_inserter(out), "{}: {}", message, utf8_message);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -289,12 +288,12 @@ void file::pipe(file& read_end, file& write_end) {
|
||||
}
|
||||
|
||||
buffered_file file::fdopen(const char* mode) {
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
# if defined(__MINGW32__) && defined(_POSIX_)
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
#if defined(__MINGW32__) && defined(_POSIX_)
|
||||
FILE* f = ::fdopen(fd_, mode);
|
||||
# else
|
||||
#else
|
||||
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
# endif
|
||||
#endif
|
||||
if (!f)
|
||||
FMT_THROW(
|
||||
system_error(errno, "cannot associate stream with file descriptor"));
|
||||
@@ -314,9 +313,5 @@ long getpagesize() {
|
||||
return size;
|
||||
# endif
|
||||
}
|
||||
|
||||
FMT_API void ostream::grow(size_t) {
|
||||
if (this->size() == this->capacity()) flush();
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
@@ -4,6 +4,36 @@
|
||||
#include "precomp.h"
|
||||
#include "AttrRow.hpp"
|
||||
|
||||
// Routine Description:
|
||||
// - constructor
|
||||
// Arguments:
|
||||
// - cchRowWidth - the length of the default text attribute
|
||||
// - attr - the default text attribute
|
||||
// Return Value:
|
||||
// - constructed object
|
||||
ATTR_ROW::ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
_list.emplace_back(TextAttributeRun(cchRowWidth, attr));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
FAIL_FAST_CAUGHT_EXCEPTION();
|
||||
}
|
||||
_cchRowWidth = cchRowWidth;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sets all properties of the ATTR_ROW to default values
|
||||
// Arguments:
|
||||
// - attr - The default text attributes to use on text in this row.
|
||||
void ATTR_ROW::Reset(const TextAttribute attr)
|
||||
{
|
||||
_list.clear();
|
||||
_list.emplace_back(TextAttributeRun(_cchRowWidth, attr));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Takes an existing row of attributes, and changes the length so that it fills the NewWidth.
|
||||
// If the new size is bigger, then the last attr is extended to fill the NewWidth.
|
||||
@@ -15,12 +45,46 @@
|
||||
// - <none>, throws exceptions on failures.
|
||||
void ATTR_ROW::Resize(const size_t newWidth)
|
||||
{
|
||||
mybase::resize(gsl::narrow<UINT>(newWidth));
|
||||
}
|
||||
THROW_HR_IF(E_INVALIDARG, 0 == newWidth);
|
||||
|
||||
void ATTR_ROW::Reset(const TextAttribute attr)
|
||||
{
|
||||
mybase::fill(attr);
|
||||
// Easy case. If the new row is longer, increase the length of the last run by how much new space there is.
|
||||
if (newWidth > _cchRowWidth)
|
||||
{
|
||||
// Get the attribute that covers the final column of old width.
|
||||
const auto runPos = FindAttrIndex(_cchRowWidth - 1, nullptr);
|
||||
auto& run = _list.at(runPos);
|
||||
|
||||
// Extend its length by the additional columns we're adding.
|
||||
run.SetLength(run.GetLength() + newWidth - _cchRowWidth);
|
||||
|
||||
// Store that the new total width we represent is the new width.
|
||||
_cchRowWidth = newWidth;
|
||||
}
|
||||
// harder case: new row is shorter.
|
||||
else
|
||||
{
|
||||
// Get the attribute that covers the final column of the new width
|
||||
size_t CountOfAttr = 0;
|
||||
const auto runPos = FindAttrIndex(newWidth - 1, &CountOfAttr);
|
||||
auto& run = _list.at(runPos);
|
||||
|
||||
// CountOfAttr was given to us as "how many columns left from this point forward are covered by the returned run"
|
||||
// So if the original run was B5 covering a 5 size OldWidth and we have a NewWidth of 3
|
||||
// then when we called FindAttrIndex, it returned the B5 as the pIndexedRun and a 2 for how many more segments it covers
|
||||
// after and including the 3rd column.
|
||||
// B5-2 = B3, which is what we desire to cover the new 3 size buffer.
|
||||
run.SetLength(run.GetLength() - CountOfAttr + 1);
|
||||
|
||||
// Store that the new total width we represent is the new width.
|
||||
_cchRowWidth = newWidth;
|
||||
|
||||
// Erase segments after the one we just updated.
|
||||
_list.erase(_list.cbegin() + runPos + 1, _list.cend());
|
||||
|
||||
// NOTE: Under some circumstances here, we have leftover run segments in memory or blank run segments
|
||||
// in memory. We're not going to waste time redimensioning the array in the heap. We're just noting that the useful
|
||||
// portions of it have changed.
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -33,7 +97,7 @@ void ATTR_ROW::Reset(const TextAttribute attr)
|
||||
// - will throw on error
|
||||
TextAttribute ATTR_ROW::GetAttrByColumn(const size_t column) const
|
||||
{
|
||||
return mybase::at(gsl::narrow<UINT>(column));
|
||||
return GetAttrByColumn(column, nullptr);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -48,10 +112,73 @@ TextAttribute ATTR_ROW::GetAttrByColumn(const size_t column) const
|
||||
TextAttribute ATTR_ROW::GetAttrByColumn(const size_t column,
|
||||
size_t* const pApplies) const
|
||||
{
|
||||
UINT applies = 0;
|
||||
const auto attr = mybase::at(gsl::narrow<UINT>(column), applies);
|
||||
*pApplies = applies;
|
||||
return attr;
|
||||
THROW_HR_IF(E_INVALIDARG, column >= _cchRowWidth);
|
||||
const auto runPos = FindAttrIndex(column, pApplies);
|
||||
return _list.at(runPos).GetAttributes();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - reports how many runs we have stored (to be used for some optimizations
|
||||
// Return Value:
|
||||
// - Count of runs. 1 means we have 1 color to represent the entire row.
|
||||
size_t ATTR_ROW::GetNumberOfRuns() const noexcept
|
||||
{
|
||||
return _list.size();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This routine finds the nth attribute in this ATTR_ROW.
|
||||
// Arguments:
|
||||
// - index - which attribute to find
|
||||
// - applies - on output, contains corrected length of indexed attr.
|
||||
// for example, if the attribute string was { 5, BLUE } and the requested
|
||||
// index was 3, CountOfAttr would be 2.
|
||||
// Return Value:
|
||||
// - const reference to attribute run object
|
||||
size_t ATTR_ROW::FindAttrIndex(const size_t index, size_t* const pApplies) const
|
||||
{
|
||||
FAIL_FAST_IF(!(index < _cchRowWidth)); // The requested index cannot be longer than the total length described by this set of Attrs.
|
||||
|
||||
size_t cTotalLength = 0;
|
||||
|
||||
FAIL_FAST_IF(!(_list.size() > 0)); // There should be a non-zero and positive number of items in the array.
|
||||
|
||||
// Scan through the internal array from position 0 adding up the lengths that each attribute applies to
|
||||
auto runPos = _list.cbegin();
|
||||
do
|
||||
{
|
||||
cTotalLength += runPos->GetLength();
|
||||
|
||||
if (cTotalLength > index)
|
||||
{
|
||||
// If we've just passed up the requested index with the length we added, break early
|
||||
break;
|
||||
}
|
||||
|
||||
runPos++;
|
||||
} while (runPos < _list.cend());
|
||||
|
||||
// we should have broken before falling out the while case.
|
||||
// if we didn't break, then this ATTR_ROW wasn't filled with enough attributes for the entire row of characters
|
||||
FAIL_FAST_IF(runPos >= _list.cend());
|
||||
|
||||
// The remaining iterator position is the position of the attribute that is applicable at the position requested (index)
|
||||
// Calculate its remaining applicability if requested
|
||||
|
||||
// The length on which the found attribute applies is the total length seen so far minus the index we were searching for.
|
||||
FAIL_FAST_IF(!(cTotalLength > index)); // The length of all attributes we counted up so far should be longer than the index requested or we'll underflow.
|
||||
|
||||
if (nullptr != pApplies)
|
||||
{
|
||||
const auto attrApplies = cTotalLength - index;
|
||||
FAIL_FAST_IF(!(attrApplies > 0)); // An attribute applies for >0 characters
|
||||
// MSFT: 17130145 - will restore this and add a better assert to catch the real issue.
|
||||
//FAIL_FAST_IF(!(attrApplies <= _cchRowWidth)); // An attribute applies for a maximum of the total length available to us
|
||||
|
||||
*pApplies = attrApplies;
|
||||
}
|
||||
|
||||
return runPos - _list.cbegin();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -61,11 +188,11 @@ TextAttribute ATTR_ROW::GetAttrByColumn(const size_t column,
|
||||
std::vector<uint16_t> ATTR_ROW::GetHyperlinks()
|
||||
{
|
||||
std::vector<uint16_t> ids;
|
||||
for (const auto& run : *this)
|
||||
for (const auto& run : _list)
|
||||
{
|
||||
if (run.IsHyperlink())
|
||||
if (run.GetAttributes().IsHyperlink())
|
||||
{
|
||||
ids.emplace_back(run.GetHyperlinkId());
|
||||
ids.emplace_back(run.GetAttributes().GetHyperlinkId());
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
@@ -80,8 +207,10 @@ std::vector<uint16_t> ATTR_ROW::GetHyperlinks()
|
||||
// - <none>
|
||||
bool ATTR_ROW::SetAttrToEnd(const UINT iStart, const TextAttribute attr)
|
||||
{
|
||||
mybase::fill(attr, iStart);
|
||||
return true;
|
||||
size_t const length = _cchRowWidth - iStart;
|
||||
|
||||
const TextAttributeRun run(length, attr);
|
||||
return SUCCEEDED(InsertAttrRuns({ &run, 1 }, iStart, _cchRowWidth - 1, _cchRowWidth));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -94,7 +223,13 @@ bool ATTR_ROW::SetAttrToEnd(const UINT iStart, const TextAttribute attr)
|
||||
// - <none>
|
||||
void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith) noexcept
|
||||
{
|
||||
mybase::replace(toBeReplacedAttr, replaceWith);
|
||||
for (auto& run : _list)
|
||||
{
|
||||
if (run.GetAttributes() == toBeReplacedAttr)
|
||||
{
|
||||
run.SetAttributes(replaceWith);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -113,12 +248,392 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// otherwise STATUS_SUCCESS if we were successful.
|
||||
[[nodiscard]] HRESULT ATTR_ROW::InsertAttrRuns(const gsl::span<const TextAttributeRun> newAttrs,
|
||||
const size_t iStart,
|
||||
const size_t /*iEnd*/,
|
||||
const size_t /*cBufferWidth*/)
|
||||
try
|
||||
const size_t iEnd,
|
||||
const size_t cBufferWidth)
|
||||
{
|
||||
mybase::replace(iStart, mybase::npos, newAttrs.begin(), newAttrs.end());
|
||||
// Definitions:
|
||||
// Existing Run = The run length encoded color array we're already storing in memory before this was called.
|
||||
// Insert Run = The run length encoded color array that someone is asking us to inject into our stored memory run.
|
||||
// New Run = The run length encoded color array that we have to allocate and rebuild to store internally
|
||||
// which will replace Existing Run at the end of this function.
|
||||
// Example:
|
||||
// cBufferWidth = 10.
|
||||
// Existing Run: R3 -> G5 -> B2
|
||||
// Insert Run: Y1 -> N1 at iStart = 5 and iEnd = 6
|
||||
// (rgInsertAttrs is a 2 length array with Y1->N1 in it and cInsertAttrs = 2)
|
||||
// Final Run: R3 -> G2 -> Y1 -> N1 -> G1 -> B2
|
||||
|
||||
// We'll need to know what the last valid column is for some calculations versus iEnd
|
||||
// because iEnd is specified to us as an inclusive index value.
|
||||
// Do the -1 math here now so we don't have to have -1s scattered all over this function.
|
||||
const size_t iLastBufferCol = cBufferWidth - 1;
|
||||
|
||||
// If the insertion size is 1, do some pre-processing to
|
||||
// see if we can get this done quickly.
|
||||
if (newAttrs.size() == 1)
|
||||
{
|
||||
// Get the new color attribute we're trying to apply
|
||||
const TextAttribute NewAttr = til::at(newAttrs, 0).GetAttributes();
|
||||
|
||||
// If the existing run was only 1 element...
|
||||
// ...and the new color is the same as the old, we don't have to do anything and can exit quick.
|
||||
if (_list.size() == 1 && _list.at(0).GetAttributes() == NewAttr)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
// .. otherwise if we internally have a list of 2 or more and we're about to insert a single color
|
||||
// it's possible that we just walk left-to-right through the row and find a quick exit.
|
||||
else if (iStart >= 0 && iStart == iEnd)
|
||||
{
|
||||
// First we try to find the run where the insertion happens, using lowerBound and upperBound to track
|
||||
// where we are currently at.
|
||||
const auto begin = _list.begin();
|
||||
size_t lowerBound = 0;
|
||||
size_t upperBound = 0;
|
||||
for (size_t i = 0; i < _list.size(); i++)
|
||||
{
|
||||
const auto curr = begin + i;
|
||||
upperBound += curr->GetLength();
|
||||
|
||||
if (iStart >= lowerBound && iStart < upperBound)
|
||||
{
|
||||
// The run that we try to insert into has the same color as the new one.
|
||||
// e.g.
|
||||
// AAAAABBBBBBBCCC
|
||||
// ^
|
||||
// AAAAABBBBBBBCCC
|
||||
//
|
||||
// 'B' is the new color and '^' represents where iStart is. We don't have to
|
||||
// do anything.
|
||||
if (curr->GetAttributes() == NewAttr)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// If the current run has length of exactly one, we can simply change the attribute
|
||||
// of the current run.
|
||||
// e.g.
|
||||
// AAAAABCCCCCCCCC
|
||||
// ^
|
||||
// AAAAADCCCCCCCCC
|
||||
//
|
||||
// Here 'D' is the new color.
|
||||
if (curr->GetLength() == 1)
|
||||
{
|
||||
curr->SetAttributes(NewAttr);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// If the insertion happens at current run's lower boundary...
|
||||
if (iStart == lowerBound && i > 0)
|
||||
{
|
||||
const auto prev = std::prev(curr, 1);
|
||||
// ... and the previous run has the same color as the new one, we can
|
||||
// just adjust the counts in the existing two elements in our internal list.
|
||||
// e.g.
|
||||
// AAAAABBBBBBBCCC
|
||||
// ^
|
||||
// AAAAAABBBBBBCCC
|
||||
//
|
||||
// Here 'A' is the new color.
|
||||
if (NewAttr == prev->GetAttributes())
|
||||
{
|
||||
prev->IncrementLength();
|
||||
curr->DecrementLength();
|
||||
|
||||
// If we just reduced the right half to zero, just erase it out of the list.
|
||||
if (curr->GetLength() == 0)
|
||||
{
|
||||
_list.erase(curr);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// If the insertion happens at current run's upper boundary...
|
||||
if (iStart == upperBound - 1 && i + 1 < _list.size())
|
||||
{
|
||||
// ...then let's try our luck with the next run if possible. This is basically the opposite
|
||||
// of what we did with the previous run.
|
||||
// e.g.
|
||||
// AAAAAABBBBBBCCC
|
||||
// ^
|
||||
// AAAAABBBBBBBCCC
|
||||
//
|
||||
// Here 'B' is the new color.
|
||||
const auto next = std::next(curr, 1);
|
||||
if (NewAttr == next->GetAttributes())
|
||||
{
|
||||
curr->DecrementLength();
|
||||
next->IncrementLength();
|
||||
|
||||
if (curr->GetLength() == 0)
|
||||
{
|
||||
_list.erase(curr);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Advance one run in the _list.
|
||||
lowerBound = upperBound;
|
||||
|
||||
// The lowerBound is larger than iStart, which means we fail to find an early exit at the run
|
||||
// where the insertion happens. We can just break out.
|
||||
if (lowerBound > iStart)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're about to cover the entire existing run with a new one, we can also make an optimization.
|
||||
if (iStart == 0 && iEnd == iLastBufferCol)
|
||||
{
|
||||
// Just dump what we're given over what we have and call it a day.
|
||||
_list.assign(newAttrs.begin(), newAttrs.end());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// In the worst case scenario, we will need a new run that is the length of
|
||||
// The existing run in memory + The new run in memory + 1.
|
||||
// This worst case occurs when we inject a new item in the middle of an existing run like so
|
||||
// Existing R3->B5->G2, Insertion Y2 starting at 5 (in the middle of the B5)
|
||||
// becomes R3->B2->Y2->B1->G2.
|
||||
// The original run was 3 long. The insertion run was 1 long. We need 1 more for the
|
||||
// fact that an existing piece of the run was split in half (to hold the latter half).
|
||||
const size_t cNewRun = _list.size() + newAttrs.size() + 1;
|
||||
decltype(_list) newRun;
|
||||
newRun.reserve(cNewRun);
|
||||
|
||||
// We will start analyzing from the beginning of our existing run.
|
||||
// Use some pointers to keep track of where we are in walking through our runs.
|
||||
|
||||
// Get the existing run that we'll be updating/manipulating.
|
||||
const auto existingRun = _list.begin();
|
||||
auto pExistingRunPos = existingRun;
|
||||
const auto pExistingRunEnd = _list.end();
|
||||
auto pInsertRunPos = newAttrs.begin();
|
||||
size_t cInsertRunRemaining = newAttrs.size();
|
||||
size_t iExistingRunCoverage = 0;
|
||||
|
||||
// Copy the existing run into the new buffer up to the "start index" where the new run will be injected.
|
||||
// If the new run starts at 0, we have nothing to copy from the beginning.
|
||||
if (iStart != 0)
|
||||
{
|
||||
// While we're less than the desired insertion position...
|
||||
while (iExistingRunCoverage < iStart)
|
||||
{
|
||||
// Add up how much length we can cover by copying an item from the existing run.
|
||||
iExistingRunCoverage += pExistingRunPos->GetLength();
|
||||
|
||||
// Copy it to the new run buffer and advance both pointers.
|
||||
newRun.push_back(*pExistingRunPos++);
|
||||
}
|
||||
|
||||
// When we get to this point, we've copied full segments from the original existing run
|
||||
// into our new run buffer. We will have 1 or more full segments of color attributes and
|
||||
// we MIGHT have to cut the last copied segment's length back depending on where the inserted
|
||||
// attributes will fall in the final/new run.
|
||||
// Some examples:
|
||||
// - Starting with the original string R3 -> G5 -> B2
|
||||
// - 1. If the insertion is Y5 at start index 3
|
||||
// We are trying to get a result/final/new run of R3 -> Y5 -> B2.
|
||||
// We just copied R3 to the new destination buffer and we cang skip down and start inserting the new attrs.
|
||||
// - 2. If the insertion is Y3 at start index 5
|
||||
// We are trying to get a result/final/new run of R3 -> G2 -> Y3 -> B2.
|
||||
// We just copied R3 -> G5 to the new destination buffer with the code above.
|
||||
// But the insertion is going to cut out some of the length of the G5.
|
||||
// We need to fix this up below so it says G2 instead to leave room for the Y3 to fit in
|
||||
// the new/final run.
|
||||
|
||||
// Fetch out the length so we can fix it up based on the below conditions.
|
||||
size_t length = newRun.back().GetLength();
|
||||
|
||||
// If we've covered more cells already than the start of the attributes to be inserted...
|
||||
if (iExistingRunCoverage > iStart)
|
||||
{
|
||||
// ..then subtract some of the length of the final cell we copied.
|
||||
// We want to take remove the difference in distance between the cells we've covered in the new
|
||||
// run and the insertion point.
|
||||
// (This turns G5 into G2 from Example 2 just above)
|
||||
length -= (iExistingRunCoverage - iStart);
|
||||
}
|
||||
|
||||
// Now we're still on that "last cell copied" into the new run.
|
||||
// If the color of that existing copied cell matches the color of the first segment
|
||||
// of the run we're about to insert, we can just increment the length to extend the coverage.
|
||||
if (newRun.back().GetAttributes() == pInsertRunPos->GetAttributes())
|
||||
{
|
||||
length += pInsertRunPos->GetLength();
|
||||
|
||||
// Since the color matched, we have already "used up" part of the insert run
|
||||
// and can skip it in our big "memcopy" step below that will copy the bulk of the insert run.
|
||||
cInsertRunRemaining--;
|
||||
pInsertRunPos++;
|
||||
}
|
||||
|
||||
// We're done manipulating the length. Store it back.
|
||||
newRun.back().SetLength(length);
|
||||
}
|
||||
|
||||
// Bulk copy the majority (or all, depending on circumstance) of the insert run into the final run buffer.
|
||||
std::copy_n(pInsertRunPos, cInsertRunRemaining, std::back_inserter(newRun));
|
||||
|
||||
// We're technically done with the insert run now and have 0 remaining, but won't bother updating its pointers
|
||||
// and counts any further because we won't use them.
|
||||
|
||||
// Now we need to move our pointer for the original existing run forward and update our counts
|
||||
// on how many cells we could have copied from the source before finishing off the new run.
|
||||
while (iExistingRunCoverage <= iEnd)
|
||||
{
|
||||
FAIL_FAST_IF(!(pExistingRunPos != pExistingRunEnd));
|
||||
iExistingRunCoverage += pExistingRunPos->GetLength();
|
||||
pExistingRunPos++;
|
||||
}
|
||||
|
||||
// If we still have original existing run cells remaining, copy them into the final new run.
|
||||
if (pExistingRunPos != pExistingRunEnd || iExistingRunCoverage != (iEnd + 1))
|
||||
{
|
||||
// We advanced the existing run pointer and its count to on or past the end of what the insertion run filled in.
|
||||
// If this ended up being past the end of what the insertion run covers, we have to account for the cells after
|
||||
// the insertion run but before the next piece of the original existing run.
|
||||
// The example in this case is if we had...
|
||||
// Existing Run = R3 -> G5 -> B2 -> X5
|
||||
// Insert Run = Y2 @ iStart = 7 and iEnd = 8
|
||||
// ... then at this point in time, our states would look like...
|
||||
// New Run so far = R3 -> G4 -> Y2
|
||||
// Existing Run Pointer is at X5
|
||||
// Existing run coverage count at 3 + 5 + 2 = 10.
|
||||
// However, in order to get the final desired New Run
|
||||
// (which is R3 -> G4 -> Y2 -> B1 -> X5)
|
||||
// we would need to grab a piece of that B2 we already skipped past.
|
||||
// iExistingRunCoverage = 10. iEnd = 8. iEnd+1 = 9. 10 > 9. So we skipped something.
|
||||
if (iExistingRunCoverage > (iEnd + 1))
|
||||
{
|
||||
// Back up the existing run pointer so we can grab the piece we skipped.
|
||||
pExistingRunPos--;
|
||||
|
||||
// If the color matches what's already in our run, just increment the count value.
|
||||
// This case is slightly off from the example above. This case is for if the B2 above was actually Y2.
|
||||
// That Y2 from the existing run is the same color as the Y2 we just filled a few columns left in the final run
|
||||
// so we can just adjust the final run's column count instead of adding another segment here.
|
||||
if (newRun.back().GetAttributes() == pExistingRunPos->GetAttributes())
|
||||
{
|
||||
size_t length = newRun.back().GetLength();
|
||||
length += (iExistingRunCoverage - (iEnd + 1));
|
||||
newRun.back().SetLength(length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the color didn't match, then we just need to copy the piece we skipped and adjust
|
||||
// its length for the discrepancy in columns not yet covered by the final/new run.
|
||||
|
||||
// Move forward to a blank spot in the new run
|
||||
newRun.emplace_back();
|
||||
|
||||
// Copy the existing run's color information to the new run
|
||||
newRun.back().SetAttributes(pExistingRunPos->GetAttributes());
|
||||
|
||||
// Adjust the length of that copied color to cover only the reduced number of columns needed
|
||||
// now that some have been replaced by the insert run.
|
||||
newRun.back().SetLength(iExistingRunCoverage - (iEnd + 1));
|
||||
}
|
||||
|
||||
// Now that we're done recovering a piece of the existing run we skipped, move the pointer forward again.
|
||||
pExistingRunPos++;
|
||||
}
|
||||
|
||||
// OK. In this case, we didn't skip anything. The end of the insert run fell right at a boundary
|
||||
// in columns that was in the original existing run.
|
||||
// However, the next piece of the original existing run might happen to have the same color attribute
|
||||
// as the final piece of what we just copied.
|
||||
// As an example...
|
||||
// Existing Run = R3 -> G5 -> B2.
|
||||
// Insert Run = B5 @ iStart = 3 and iEnd = 7
|
||||
// New Run so far = R3 -> B5
|
||||
// New Run desired when done = R3 -> B7
|
||||
// Existing run pointer is on B2.
|
||||
// We want to merge the 2 from the B2 into the B5 so we get B7.
|
||||
else if (newRun.back().GetAttributes() == pExistingRunPos->GetAttributes())
|
||||
{
|
||||
// Add the value from the existing run into the current new run position.
|
||||
size_t length = newRun.back().GetLength();
|
||||
length += pExistingRunPos->GetLength();
|
||||
newRun.back().SetLength(length);
|
||||
|
||||
// Advance the existing run position since we consumed its value and merged it in.
|
||||
pExistingRunPos++;
|
||||
}
|
||||
|
||||
// Now bulk copy any segments left in the original existing run
|
||||
if (pExistingRunPos < pExistingRunEnd)
|
||||
{
|
||||
std::copy_n(pExistingRunPos, (pExistingRunEnd - pExistingRunPos), std::back_inserter(newRun));
|
||||
}
|
||||
}
|
||||
|
||||
// OK, phew. We're done. Now we just need to free the existing run and store the new run in its place.
|
||||
_list.swap(newRun);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN()
|
||||
|
||||
// Routine Description:
|
||||
// - packs a vector of TextAttribute into a vector of TextAttributeRun
|
||||
// Arguments:
|
||||
// - attrs - text attributes to pack
|
||||
// Return Value:
|
||||
// - packed text attribute run
|
||||
std::vector<TextAttributeRun> ATTR_ROW::PackAttrs(const std::vector<TextAttribute>& attrs)
|
||||
{
|
||||
std::vector<TextAttributeRun> runs;
|
||||
if (attrs.empty())
|
||||
{
|
||||
return runs;
|
||||
}
|
||||
for (auto attr : attrs)
|
||||
{
|
||||
if (runs.empty() || runs.back().GetAttributes() != attr)
|
||||
{
|
||||
runs.emplace_back(TextAttributeRun(1, attr));
|
||||
}
|
||||
else
|
||||
{
|
||||
runs.back().SetLength(runs.back().GetLength() + 1);
|
||||
}
|
||||
}
|
||||
return runs;
|
||||
}
|
||||
|
||||
ATTR_ROW::const_iterator ATTR_ROW::begin() const noexcept
|
||||
{
|
||||
return AttrRowIterator(this);
|
||||
}
|
||||
|
||||
ATTR_ROW::const_iterator ATTR_ROW::end() const noexcept
|
||||
{
|
||||
return AttrRowIterator::CreateEndIterator(this);
|
||||
}
|
||||
|
||||
ATTR_ROW::const_iterator ATTR_ROW::cbegin() const noexcept
|
||||
{
|
||||
return AttrRowIterator(this);
|
||||
}
|
||||
|
||||
ATTR_ROW::const_iterator ATTR_ROW::cend() const noexcept
|
||||
{
|
||||
return AttrRowIterator::CreateEndIterator(this);
|
||||
}
|
||||
|
||||
bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept
|
||||
{
|
||||
return (a._list.size() == b._list.size() &&
|
||||
a._list.data() == b._list.data() &&
|
||||
a._cchRowWidth == b._cchRowWidth);
|
||||
}
|
||||
|
||||
@@ -20,27 +20,37 @@ Revision History:
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "til/rle.h"
|
||||
|
||||
#include "TextAttributeRun.hpp"
|
||||
#include "AttrRowIterator.hpp"
|
||||
|
||||
class ATTR_ROW final : public til::rle<TextAttribute, unsigned int>
|
||||
class ATTR_ROW final
|
||||
{
|
||||
public:
|
||||
using mybase = til::rle<TextAttribute, unsigned int>;
|
||||
using const_iterator = typename AttrRowIterator;
|
||||
|
||||
using const_iterator = mybase::const_iterator;
|
||||
using const_reverse_iterator = mybase::const_reverse_iterator;
|
||||
ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr)
|
||||
noexcept;
|
||||
|
||||
using mybase::mybase; // use base constructor
|
||||
~ATTR_ROW() = default;
|
||||
|
||||
ATTR_ROW(const ATTR_ROW&) = default;
|
||||
ATTR_ROW& operator=(const ATTR_ROW&) = default;
|
||||
ATTR_ROW(ATTR_ROW&&)
|
||||
noexcept = default;
|
||||
ATTR_ROW& operator=(ATTR_ROW&&) noexcept = default;
|
||||
|
||||
TextAttribute GetAttrByColumn(const size_t column) const;
|
||||
TextAttribute GetAttrByColumn(const size_t column,
|
||||
size_t* const pApplies) const;
|
||||
|
||||
size_t GetNumberOfRuns() const noexcept;
|
||||
|
||||
size_t FindAttrIndex(const size_t index,
|
||||
size_t* const pApplies) const;
|
||||
|
||||
std::vector<uint16_t> GetHyperlinks();
|
||||
|
||||
bool SetAttrToEnd(const unsigned int iStart, const TextAttribute attr);
|
||||
bool SetAttrToEnd(const UINT iStart, const TextAttribute attr);
|
||||
void ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith) noexcept;
|
||||
|
||||
void Resize(const size_t newWidth);
|
||||
@@ -50,19 +60,26 @@ public:
|
||||
const size_t iEnd,
|
||||
const size_t cBufferWidth);
|
||||
|
||||
using mybase::begin;
|
||||
using mybase::cbegin;
|
||||
using mybase::cend;
|
||||
using mybase::end;
|
||||
static std::vector<TextAttributeRun> PackAttrs(const std::vector<TextAttribute>& attrs);
|
||||
|
||||
using mybase::operator==;
|
||||
const_iterator begin() const noexcept;
|
||||
const_iterator end() const noexcept;
|
||||
|
||||
const_iterator cbegin() const noexcept;
|
||||
const_iterator cend() const noexcept;
|
||||
|
||||
friend bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept;
|
||||
friend class AttrRowIterator;
|
||||
friend class ROW;
|
||||
|
||||
private:
|
||||
void Reset(const TextAttribute attr);
|
||||
|
||||
boost::container::small_vector<TextAttributeRun, 1> _list;
|
||||
size_t _cchRowWidth;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class AttrRowTests;
|
||||
friend class CommonState;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -22,23 +22,29 @@ Revision History:
|
||||
|
||||
#include "TextAttribute.hpp"
|
||||
|
||||
class TextAttributeRun final : public std::pair<TextAttribute, unsigned int>
|
||||
class TextAttributeRun final
|
||||
{
|
||||
public:
|
||||
using mybase = std::pair<TextAttribute, unsigned int>;
|
||||
|
||||
using mybase::mybase;
|
||||
|
||||
TextAttributeRun(const size_t cchLength, const TextAttribute attr) :
|
||||
mybase(attr, gsl::narrow<unsigned int>(cchLength))
|
||||
TextAttributeRun() = default;
|
||||
TextAttributeRun(const size_t cchLength, const TextAttribute attr) noexcept :
|
||||
_cchLength(gsl::narrow<unsigned int>(cchLength))
|
||||
{
|
||||
SetAttributes(attr);
|
||||
}
|
||||
|
||||
size_t GetLength() const noexcept { return mybase::second; }
|
||||
void SetLength(const size_t cchLength) noexcept { mybase::second = gsl::narrow<unsigned int>(cchLength); }
|
||||
void IncrementLength() noexcept { mybase::second++; }
|
||||
void DecrementLength() noexcept { mybase::second--; }
|
||||
size_t GetLength() const noexcept { return _cchLength; }
|
||||
void SetLength(const size_t cchLength) noexcept { _cchLength = gsl::narrow<unsigned int>(cchLength); }
|
||||
void IncrementLength() noexcept { _cchLength++; }
|
||||
void DecrementLength() noexcept { _cchLength--; }
|
||||
|
||||
const TextAttribute& GetAttributes() const noexcept { return mybase::first; }
|
||||
void SetAttributes(const TextAttribute textAttribute) noexcept { mybase::first = textAttribute; }
|
||||
const TextAttribute& GetAttributes() const noexcept { return _attributes; }
|
||||
void SetAttributes(const TextAttribute textAttribute) noexcept { _attributes = textAttribute; }
|
||||
|
||||
private:
|
||||
unsigned int _cchLength{ 0 };
|
||||
TextAttribute _attributes{ 0 };
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class AttrRowTests;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\AttrRow.cpp" />
|
||||
<ClCompile Include="..\AttrRowIterator.cpp" />
|
||||
<ClCompile Include="..\cursor.cpp" />
|
||||
<ClCompile Include="..\OutputCell.cpp" />
|
||||
<ClCompile Include="..\OutputCellIterator.cpp" />
|
||||
@@ -33,6 +34,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\AttrRow.hpp" />
|
||||
<ClInclude Include="..\AttrRowIterator.hpp" />
|
||||
<ClInclude Include="..\cursor.h" />
|
||||
<ClInclude Include="..\DbcsAttribute.hpp" />
|
||||
<ClInclude Include="..\ICharRow.hpp" />
|
||||
|
||||
@@ -30,6 +30,7 @@ PRECOMPILED_INCLUDE = ..\precomp.h
|
||||
|
||||
SOURCES= \
|
||||
..\AttrRow.cpp \
|
||||
..\AttrRowIterator.cpp \
|
||||
..\cursor.cpp \
|
||||
..\OutputCell.cpp \
|
||||
..\OutputCellIterator.cpp \
|
||||
|
||||
@@ -15,8 +15,8 @@ Author(s):
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AttrRowIterator.hpp"
|
||||
#include "CharRow.hpp"
|
||||
#include "AttrRow.hpp"
|
||||
#include "OutputCellView.hpp"
|
||||
#include "../../types/inc/viewport.hpp"
|
||||
|
||||
@@ -55,7 +55,7 @@ protected:
|
||||
OutputCellView _view;
|
||||
|
||||
const ROW* _pRow;
|
||||
ATTR_ROW::const_iterator _attrIter;
|
||||
AttrRowIterator _attrIter;
|
||||
const TextBuffer& _buffer;
|
||||
const Microsoft::Console::Types::Viewport _bounds;
|
||||
bool _exceeded;
|
||||
|
||||
731
src/buffer/out/ut_textbuffer/AttrRowTests.cpp
Normal file
731
src/buffer/out/ut_textbuffer/AttrRowTests.cpp
Normal file
@@ -0,0 +1,731 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "WexTestClass.h"
|
||||
#include "../../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "../textBuffer.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
namespace WEX
|
||||
{
|
||||
namespace TestExecution
|
||||
{
|
||||
template<>
|
||||
class VerifyOutputTraits<TextAttributeRun>
|
||||
{
|
||||
public:
|
||||
static WEX::Common::NoThrowString ToString(const TextAttributeRun& tar)
|
||||
{
|
||||
return WEX::Common::NoThrowString().Format(
|
||||
L"Length:%d, attr:%s",
|
||||
tar.GetLength(),
|
||||
VerifyOutputTraits<TextAttribute>::ToString(tar.GetAttributes()).GetBuffer());
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class VerifyCompareTraits<TextAttributeRun, TextAttributeRun>
|
||||
{
|
||||
public:
|
||||
static bool AreEqual(const TextAttributeRun& expected, const TextAttributeRun& actual)
|
||||
{
|
||||
return expected.GetAttributes() == actual.GetAttributes() &&
|
||||
expected.GetLength() == actual.GetLength();
|
||||
}
|
||||
|
||||
static bool AreSame(const TextAttributeRun& expected, const TextAttributeRun& actual)
|
||||
{
|
||||
return &expected == &actual;
|
||||
}
|
||||
|
||||
static bool IsLessThan(const TextAttributeRun&, const TextAttributeRun&) = delete;
|
||||
|
||||
static bool IsGreaterThan(const TextAttributeRun&, const TextAttributeRun&) = delete;
|
||||
|
||||
static bool IsNull(const TextAttributeRun& object)
|
||||
{
|
||||
return object.GetAttributes().IsLegacy() && object.GetAttributes().GetLegacyAttributes() == 0 &&
|
||||
object.GetLength() == 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class AttrRowTests
|
||||
{
|
||||
ATTR_ROW* pSingle;
|
||||
ATTR_ROW* pChain;
|
||||
|
||||
short _sDefaultLength = 80;
|
||||
short _sDefaultChainLength = 6;
|
||||
|
||||
short sChainSegLength;
|
||||
short sChainLeftover;
|
||||
short sChainSegmentsNeeded;
|
||||
|
||||
WORD __wDefaultAttr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
|
||||
WORD __wDefaultChainAttr = BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY;
|
||||
TextAttribute _DefaultAttr = TextAttribute(__wDefaultAttr);
|
||||
TextAttribute _DefaultChainAttr = TextAttribute(__wDefaultChainAttr);
|
||||
|
||||
TEST_CLASS(AttrRowTests);
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
pSingle = new ATTR_ROW(_sDefaultLength, _DefaultAttr);
|
||||
|
||||
// Segment length is the expected length divided by the row length
|
||||
// E.g. row of 80, 4 segments, 20 segment length each
|
||||
sChainSegLength = _sDefaultLength / _sDefaultChainLength;
|
||||
|
||||
// Leftover is spaces that don't fit evenly
|
||||
// E.g. row of 81, 4 segments, 1 leftover length segment
|
||||
sChainLeftover = _sDefaultLength % _sDefaultChainLength;
|
||||
|
||||
// Start with the number of segments we expect
|
||||
sChainSegmentsNeeded = _sDefaultChainLength;
|
||||
|
||||
// If we had a remainder, add one more segment
|
||||
if (sChainLeftover)
|
||||
{
|
||||
sChainSegmentsNeeded++;
|
||||
}
|
||||
|
||||
// Create the chain
|
||||
pChain = new ATTR_ROW(_sDefaultLength, _DefaultAttr);
|
||||
pChain->_list.resize(sChainSegmentsNeeded);
|
||||
|
||||
// Attach all chain segments that are even multiples of the row length
|
||||
for (short iChain = 0; iChain < _sDefaultChainLength; iChain++)
|
||||
{
|
||||
TextAttributeRun* pRun = &pChain->_list[iChain];
|
||||
|
||||
pRun->SetAttributes(TextAttribute{ gsl::narrow_cast<WORD>(iChain) }); // Just use the chain position as the value
|
||||
pRun->SetLength(sChainSegLength);
|
||||
}
|
||||
|
||||
if (sChainLeftover > 0)
|
||||
{
|
||||
// If we had a leftover, then this chain is one longer than we expected (the default length)
|
||||
// So use it as the index (because indices start at 0)
|
||||
TextAttributeRun* pRun = &pChain->_list[_sDefaultChainLength];
|
||||
|
||||
pRun->SetAttributes(_DefaultChainAttr);
|
||||
pRun->SetLength(sChainLeftover);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(MethodCleanup)
|
||||
{
|
||||
delete pSingle;
|
||||
|
||||
delete pChain;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD(TestInitialize)
|
||||
{
|
||||
// Properties needed for test
|
||||
const WORD wAttr = FOREGROUND_RED | BACKGROUND_BLUE;
|
||||
TextAttribute attr = TextAttribute(wAttr);
|
||||
// Cases to test
|
||||
ATTR_ROW* pTestItems[]{ pSingle, pChain };
|
||||
|
||||
// Loop cases
|
||||
for (UINT iIndex = 0; iIndex < ARRAYSIZE(pTestItems); iIndex++)
|
||||
{
|
||||
ATTR_ROW* pUnderTest = pTestItems[iIndex];
|
||||
|
||||
pUnderTest->Reset(attr);
|
||||
|
||||
VERIFY_ARE_EQUAL(pUnderTest->_list.size(), 1u);
|
||||
VERIFY_ARE_EQUAL(pUnderTest->_list[0].GetAttributes(), attr);
|
||||
VERIFY_ARE_EQUAL(pUnderTest->_list[0].GetLength(), (unsigned int)_sDefaultLength);
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Packs an array of words representing attributes into the more compact storage form used by the row.
|
||||
// Arguments:
|
||||
// - rgAttrs - Array of words representing the attribute associated with each character position in the row.
|
||||
// - cRowLength - Length of preceding array.
|
||||
// - outAttrRun - reference to unique_ptr that will contain packed attr run on success.
|
||||
// Return Value:
|
||||
// - Success if success. Buffer too small if row length is incorrect.
|
||||
HRESULT PackAttrs(_In_reads_(cRowLength) const TextAttribute* const rgAttrs,
|
||||
const size_t cRowLength,
|
||||
_Inout_ std::unique_ptr<TextAttributeRun[]>& outAttrRun,
|
||||
_Out_ size_t* const cOutAttrRun)
|
||||
{
|
||||
RETURN_HR_IF(E_NOT_SUFFICIENT_BUFFER, cRowLength == 0);
|
||||
|
||||
// first count up the deltas in the array
|
||||
size_t cDeltas = 1;
|
||||
|
||||
const TextAttribute* pPrevAttr = &rgAttrs[0];
|
||||
|
||||
for (size_t i = 1; i < cRowLength; i++)
|
||||
{
|
||||
const TextAttribute* pCurAttr = &rgAttrs[i];
|
||||
|
||||
if (*pCurAttr != *pPrevAttr)
|
||||
{
|
||||
cDeltas++;
|
||||
}
|
||||
|
||||
pPrevAttr = pCurAttr;
|
||||
}
|
||||
|
||||
// This whole situation was too complicated with a one off holder for one row run
|
||||
// new method:
|
||||
// delete the old buffer
|
||||
// make a new buffer, one run + one run for each change
|
||||
// set the values for each run one run index at a time
|
||||
|
||||
std::unique_ptr<TextAttributeRun[]> attrRun = std::make_unique<TextAttributeRun[]>(cDeltas);
|
||||
RETURN_HR_IF_NULL(E_OUTOFMEMORY, attrRun);
|
||||
|
||||
TextAttributeRun* pCurrentRun = attrRun.get();
|
||||
pCurrentRun->SetAttributes(rgAttrs[0]);
|
||||
pCurrentRun->SetLength(1);
|
||||
for (size_t i = 1; i < cRowLength; i++)
|
||||
{
|
||||
if (pCurrentRun->GetAttributes() == rgAttrs[i])
|
||||
{
|
||||
pCurrentRun->SetLength(pCurrentRun->GetLength() + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
pCurrentRun++;
|
||||
pCurrentRun->SetAttributes(rgAttrs[i]);
|
||||
pCurrentRun->SetLength(1);
|
||||
}
|
||||
}
|
||||
attrRun.swap(outAttrRun);
|
||||
*cOutAttrRun = cDeltas;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
NoThrowString LogRunElement(_In_ TextAttributeRun& run)
|
||||
{
|
||||
return NoThrowString().Format(L"%wc%d", run.GetAttributes().GetLegacyAttributes(), run.GetLength());
|
||||
}
|
||||
|
||||
void LogChain(_In_ PCWSTR pwszPrefix,
|
||||
boost::container::small_vector_base<TextAttributeRun>& chain)
|
||||
{
|
||||
NoThrowString str(pwszPrefix);
|
||||
|
||||
if (chain.size() > 0)
|
||||
{
|
||||
str.Append(LogRunElement(chain[0]));
|
||||
|
||||
for (size_t i = 1; i < chain.size(); i++)
|
||||
{
|
||||
str.AppendFormat(L"->%s", (const wchar_t*)(LogRunElement(chain[i])));
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(str);
|
||||
}
|
||||
|
||||
void LogChain(_In_ PCWSTR pwszPrefix,
|
||||
std::vector<TextAttributeRun>& chain)
|
||||
{
|
||||
NoThrowString str(pwszPrefix);
|
||||
|
||||
if (chain.size() > 0)
|
||||
{
|
||||
str.Append(LogRunElement(chain[0]));
|
||||
|
||||
for (size_t i = 1; i < chain.size(); i++)
|
||||
{
|
||||
str.AppendFormat(L"->%s", (const wchar_t*)(LogRunElement(chain[i])));
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(str);
|
||||
}
|
||||
|
||||
void DoTestInsertAttrRuns(UINT& uiStartPos, WORD& ch1, UINT& uiChar1Length, WORD& ch2, UINT& uiChar2Length)
|
||||
{
|
||||
Log::Comment(String().Format(L"StartPos: %d, Char1: %wc, Char1Length: %d, Char2: %wc, Char2Length: %d",
|
||||
uiStartPos,
|
||||
ch1,
|
||||
uiChar1Length,
|
||||
ch2,
|
||||
uiChar2Length));
|
||||
|
||||
bool const fUseStr2 = (ch2 != L'0');
|
||||
|
||||
// Set up our "original row" that we are going to try to insert into.
|
||||
// This will represent a 10 column run of R3->B5->G2 that we will use for all tests.
|
||||
ATTR_ROW originalRow{ static_cast<UINT>(_sDefaultLength), _DefaultAttr };
|
||||
originalRow._list.resize(3);
|
||||
originalRow._cchRowWidth = 10;
|
||||
originalRow._list[0].SetAttributes(TextAttribute{ 'R' });
|
||||
originalRow._list[0].SetLength(3);
|
||||
originalRow._list[1].SetAttributes(TextAttribute{ 'B' });
|
||||
originalRow._list[1].SetLength(5);
|
||||
originalRow._list[2].SetAttributes(TextAttribute{ 'G' });
|
||||
originalRow._list[2].SetLength(2);
|
||||
LogChain(L"Original: ", originalRow._list);
|
||||
|
||||
// Set up our "insertion run"
|
||||
size_t cInsertRow = 1;
|
||||
if (fUseStr2)
|
||||
{
|
||||
cInsertRow++;
|
||||
}
|
||||
|
||||
std::vector<TextAttributeRun> insertRow;
|
||||
insertRow.resize(cInsertRow);
|
||||
insertRow[0].SetAttributes(TextAttribute{ ch1 });
|
||||
insertRow[0].SetLength(uiChar1Length);
|
||||
if (fUseStr2)
|
||||
{
|
||||
insertRow[1].SetAttributes(TextAttribute{ ch2 });
|
||||
insertRow[1].SetLength(uiChar2Length);
|
||||
}
|
||||
|
||||
LogChain(L"Insert: ", insertRow);
|
||||
Log::Comment(NoThrowString().Format(L"At Index: %d", uiStartPos));
|
||||
|
||||
UINT uiTotalLength = uiChar1Length;
|
||||
if (fUseStr2)
|
||||
{
|
||||
uiTotalLength += uiChar2Length;
|
||||
}
|
||||
|
||||
VERIFY_IS_TRUE((uiStartPos + uiTotalLength) >= 1); // assert we won't underflow.
|
||||
UINT const uiEndPos = uiStartPos + uiTotalLength - 1;
|
||||
|
||||
// Calculate our expected final/result run by unpacking original, laying our insertion on it at the index
|
||||
// then using our pack function to repack it.
|
||||
// This method is easy to understand and very reliable, but its performance is bad.
|
||||
// The InsertAttrRuns method we test against below is hard to understand but very high performance in production.
|
||||
|
||||
// - 1. Unpack
|
||||
std::vector<TextAttribute> unpackedOriginal = { originalRow.begin(), originalRow.end() };
|
||||
|
||||
// - 2. Overlay insertion
|
||||
UINT uiInsertedCount = 0;
|
||||
UINT uiInsertIndex = 0;
|
||||
|
||||
// --- Walk through the unpacked run from start to end....
|
||||
for (UINT uiUnpackedIndex = uiStartPos; uiUnpackedIndex <= uiEndPos; uiUnpackedIndex++)
|
||||
{
|
||||
// Pull the item from the insert run to analyze.
|
||||
TextAttributeRun run = insertRow[uiInsertIndex];
|
||||
|
||||
// Copy the attribute from the run into the unpacked array
|
||||
unpackedOriginal[uiUnpackedIndex] = run.GetAttributes();
|
||||
|
||||
// Increment how many times we've copied this particular portion of the run
|
||||
uiInsertedCount++;
|
||||
|
||||
// If we've now inserted enough of them to match the length, advance the insert index and reset the counter.
|
||||
if (uiInsertedCount >= run.GetLength())
|
||||
{
|
||||
uiInsertIndex++;
|
||||
uiInsertedCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// - 3. Pack.
|
||||
std::unique_ptr<TextAttributeRun[]> packedRun;
|
||||
size_t cPackedRun = 0;
|
||||
VERIFY_SUCCEEDED(PackAttrs(unpackedOriginal.data(), originalRow._cchRowWidth, packedRun, &cPackedRun));
|
||||
|
||||
// Now send parameters into InsertAttrRuns and get its opinion on the subject.
|
||||
VERIFY_SUCCEEDED(originalRow.InsertAttrRuns({ insertRow.data(), insertRow.size() }, uiStartPos, uiEndPos, (UINT)originalRow._cchRowWidth));
|
||||
|
||||
// Compare and ensure that the expected and actual match.
|
||||
VERIFY_ARE_EQUAL(cPackedRun, originalRow._list.size(), L"Ensure that number of array elements required for RLE are the same.");
|
||||
|
||||
std::vector<TextAttributeRun> packedRunExpected;
|
||||
std::copy_n(packedRun.get(), cPackedRun, std::back_inserter(packedRunExpected));
|
||||
|
||||
LogChain(L"Expected: ", packedRunExpected);
|
||||
LogChain(L"Actual: ", originalRow._list);
|
||||
|
||||
for (size_t testIndex = 0; testIndex < cPackedRun; testIndex++)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(packedRun[testIndex], originalRow._list[testIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestInsertAttrRunsSingle)
|
||||
{
|
||||
UINT const uiTestRunLength = 10;
|
||||
|
||||
UINT uiStartPos = 0;
|
||||
WORD ch1 = L'0';
|
||||
UINT uiChar1Length = 0;
|
||||
WORD ch2 = L'0';
|
||||
UINT uiChar2Length = 0;
|
||||
|
||||
Log::Comment(L"Test inserting a single item of a variable length into the run.");
|
||||
WORD rgch1Options[] = { L'X', L'R', L'G', L'B' };
|
||||
for (size_t iCh1Option = 0; iCh1Option < ARRAYSIZE(rgch1Options); iCh1Option++)
|
||||
{
|
||||
ch1 = rgch1Options[iCh1Option];
|
||||
for (UINT iCh1Length = 1; iCh1Length <= uiTestRunLength; iCh1Length++)
|
||||
{
|
||||
uiChar1Length = iCh1Length;
|
||||
|
||||
// We can't try to insert a run that's longer than would fit.
|
||||
// If the run is of length 10 and we're trying to insert a length of 10,
|
||||
// we can only insert at position 0.
|
||||
// For the run length of 10 and an insert length of 9, we can try positions 0 and 1.
|
||||
// And so on...
|
||||
UINT const uiMaxPos = uiTestRunLength - uiChar1Length;
|
||||
|
||||
for (UINT iStartPos = 0; iStartPos < uiMaxPos; iStartPos++)
|
||||
{
|
||||
uiStartPos = iStartPos;
|
||||
|
||||
DoTestInsertAttrRuns(uiStartPos, ch1, uiChar1Length, ch2, uiChar2Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestInsertAttrRunsMultiple)
|
||||
{
|
||||
UINT const uiTestRunLength = 10;
|
||||
|
||||
UINT uiStartPos = 0;
|
||||
WORD ch1 = L'0';
|
||||
UINT uiChar1Length = 0;
|
||||
WORD ch2 = L'0';
|
||||
UINT uiChar2Length = 0;
|
||||
|
||||
Log::Comment(L"Test inserting a multiple item run with each piece having variable length into the existing run.");
|
||||
WORD rgch1Options[] = { L'X', L'R', L'G', L'B' };
|
||||
for (size_t iCh1Option = 0; iCh1Option < ARRAYSIZE(rgch1Options); iCh1Option++)
|
||||
{
|
||||
ch1 = rgch1Options[iCh1Option];
|
||||
|
||||
UINT const uiMaxCh1Length = uiTestRunLength - 1; // leave at least 1 space for the second piece of the insert run.
|
||||
for (UINT iCh1Length = 1; iCh1Length <= uiMaxCh1Length; iCh1Length++)
|
||||
{
|
||||
uiChar1Length = iCh1Length;
|
||||
|
||||
WORD rgch2Options[] = { L'Y' };
|
||||
for (size_t iCh2Option = 0; iCh2Option < ARRAYSIZE(rgch2Options); iCh2Option++)
|
||||
{
|
||||
ch2 = rgch2Options[iCh2Option];
|
||||
|
||||
// When choosing the length of the second item, it can't be bigger than the remaining space in the run
|
||||
// when accounting for the length of the first piece chosen.
|
||||
// For example if the total run length is 10 and the first piece chosen was 8 long,
|
||||
// the second piece can only be 1 or 2 long.
|
||||
UINT const uiMaxCh2Length = uiTestRunLength - uiMaxCh1Length;
|
||||
for (UINT iCh2Length = 1; iCh2Length <= uiMaxCh2Length; iCh2Length++)
|
||||
{
|
||||
uiChar2Length = iCh2Length;
|
||||
|
||||
// We can't try to insert a run that's longer than would fit.
|
||||
// If the run is of length 10 and we're trying to insert a total length of 10,
|
||||
// we can only insert at position 0.
|
||||
// For the run length of 10 and an insert length of 9, we can try positions 0 and 1.
|
||||
// And so on...
|
||||
UINT const uiMaxPos = uiTestRunLength - (uiChar1Length + uiChar2Length);
|
||||
|
||||
for (UINT iStartPos = 0; iStartPos <= uiMaxPos; iStartPos++)
|
||||
{
|
||||
uiStartPos = iStartPos;
|
||||
|
||||
DoTestInsertAttrRuns(uiStartPos, ch1, uiChar1Length, ch2, uiChar2Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestUnpackAttrs)
|
||||
{
|
||||
Log::Comment(L"Checking unpack of a single color for the entire length");
|
||||
{
|
||||
const std::vector<TextAttribute> attrs{ pSingle->begin(), pSingle->end() };
|
||||
|
||||
for (auto& attr : attrs)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(attr, _DefaultAttr);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"Checking unpack of the multiple color chain");
|
||||
|
||||
const std::vector<TextAttribute> attrs{ pChain->begin(), pChain->end() };
|
||||
|
||||
short cChainRun = 0; // how long we've been looking at the current piece of the chain
|
||||
short iChainSegIndex = 0; // which piece of the chain we should be on right now
|
||||
|
||||
for (auto& attr : attrs)
|
||||
{
|
||||
// by default the chain was assembled above to have the chain segment index be the attribute
|
||||
TextAttribute MatchingAttr = TextAttribute(iChainSegIndex);
|
||||
|
||||
// However, if the index is greater than the expected chain length, a remainder piece was made with a default attribute
|
||||
if (iChainSegIndex >= _sDefaultChainLength)
|
||||
{
|
||||
MatchingAttr = _DefaultChainAttr;
|
||||
}
|
||||
|
||||
VERIFY_ARE_EQUAL(attr, MatchingAttr);
|
||||
|
||||
// Add to the chain run
|
||||
cChainRun++;
|
||||
|
||||
// If the chain run is greater than the length the segments were specified to be
|
||||
if (cChainRun >= sChainSegLength)
|
||||
{
|
||||
// reset to 0
|
||||
cChainRun = 0;
|
||||
|
||||
// move to the next chain segment down the line
|
||||
iChainSegIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestReverseIteratorWalkFromMiddle)
|
||||
{
|
||||
// GH #3409, walking backwards through color range runs out of bounds
|
||||
// We're going to create an attribute row with assorted colors and varying lengths
|
||||
// just like the row of text on the Ubuntu prompt line that triggered this bug being found.
|
||||
// Then we're going to walk backwards through the iterator like a selection-expand-to-left
|
||||
// operation and ensure we don't run off the bounds.
|
||||
|
||||
// walk the chain, from index, stepSize at a time
|
||||
// ensure we don't crash
|
||||
auto testWalk = [](ATTR_ROW* chain, size_t index, int stepSize) {
|
||||
// move to starting index
|
||||
auto iter = chain->cbegin();
|
||||
iter += index;
|
||||
|
||||
// Now walk backwards in a loop until 0.
|
||||
while (iter)
|
||||
{
|
||||
iter -= stepSize;
|
||||
}
|
||||
|
||||
Log::Comment(L"We made it through without crashing!");
|
||||
};
|
||||
|
||||
// take one step of size stepSize on the chain
|
||||
// index is where we start from
|
||||
// expectedAttribute is what we expect to read here
|
||||
auto verifyStep = [](ATTR_ROW* chain, size_t index, int stepSize, TextAttribute expectedAttribute) {
|
||||
// move to starting index
|
||||
auto iter = chain->cbegin();
|
||||
iter += index;
|
||||
|
||||
// Now step backwards
|
||||
iter -= stepSize;
|
||||
|
||||
VERIFY_ARE_EQUAL(expectedAttribute, *iter);
|
||||
};
|
||||
|
||||
Log::Comment(L"Reverse iterate through ubuntu prompt");
|
||||
{
|
||||
// Create attr row representing a buffer that's 121 wide.
|
||||
auto chain = std::make_unique<ATTR_ROW>(121, _DefaultAttr);
|
||||
|
||||
// The repro case had 4 chain segments.
|
||||
chain->_list.resize(4);
|
||||
|
||||
// The color 10 went for the first 18.
|
||||
chain->_list[0].SetAttributes(TextAttribute(0xA));
|
||||
chain->_list[0].SetLength(18);
|
||||
|
||||
// Default color for the next 1
|
||||
chain->_list[1].SetAttributes(TextAttribute());
|
||||
chain->_list[1].SetLength(1);
|
||||
|
||||
// Color 12 for the next 29
|
||||
chain->_list[2].SetAttributes(TextAttribute(0xC));
|
||||
chain->_list[2].SetLength(29);
|
||||
|
||||
// Then default color to end the run
|
||||
chain->_list[3].SetAttributes(TextAttribute());
|
||||
chain->_list[3].SetLength(73);
|
||||
|
||||
// The sum of the lengths should be 121.
|
||||
VERIFY_ARE_EQUAL(chain->_cchRowWidth, chain->_list[0]._cchLength + chain->_list[1]._cchLength + chain->_list[2]._cchLength + chain->_list[3]._cchLength);
|
||||
|
||||
auto index = chain->_list[0].GetLength();
|
||||
auto stepSize = 1;
|
||||
testWalk(chain.get(), index, stepSize);
|
||||
}
|
||||
|
||||
Log::Comment(L"Reverse iterate across a text run in the chain");
|
||||
{
|
||||
// Create attr row representing a buffer that's 3 wide.
|
||||
auto chain = std::make_unique<ATTR_ROW>(3, _DefaultAttr);
|
||||
|
||||
// The repro case had 3 chain segments.
|
||||
chain->_list.resize(3);
|
||||
|
||||
// The color 10 went for the first 1.
|
||||
chain->_list[0].SetAttributes(TextAttribute(0xA));
|
||||
chain->_list[0].SetLength(1);
|
||||
|
||||
// The color 11 for the next 1
|
||||
chain->_list[1].SetAttributes(TextAttribute(0xB));
|
||||
chain->_list[1].SetLength(1);
|
||||
|
||||
// Color 12 for the next 1
|
||||
chain->_list[2].SetAttributes(TextAttribute(0xC));
|
||||
chain->_list[2].SetLength(1);
|
||||
|
||||
// The sum of the lengths should be 3.
|
||||
VERIFY_ARE_EQUAL(chain->_cchRowWidth, chain->_list[0]._cchLength + chain->_list[1]._cchLength + chain->_list[2]._cchLength);
|
||||
|
||||
// on 'ABC', step from B to A
|
||||
auto index = 1;
|
||||
auto stepSize = 1;
|
||||
verifyStep(chain.get(), index, stepSize, TextAttribute(0xA));
|
||||
}
|
||||
|
||||
Log::Comment(L"Reverse iterate across two text runs in the chain");
|
||||
{
|
||||
// Create attr row representing a buffer that's 3 wide.
|
||||
auto chain = std::make_unique<ATTR_ROW>(3, _DefaultAttr);
|
||||
|
||||
// The repro case had 3 chain segments.
|
||||
chain->_list.resize(3);
|
||||
|
||||
// The color 10 went for the first 1.
|
||||
chain->_list[0].SetAttributes(TextAttribute(0xA));
|
||||
chain->_list[0].SetLength(1);
|
||||
|
||||
// The color 11 for the next 1
|
||||
chain->_list[1].SetAttributes(TextAttribute(0xB));
|
||||
chain->_list[1].SetLength(1);
|
||||
|
||||
// Color 12 for the next 1
|
||||
chain->_list[2].SetAttributes(TextAttribute(0xC));
|
||||
chain->_list[2].SetLength(1);
|
||||
|
||||
// The sum of the lengths should be 3.
|
||||
VERIFY_ARE_EQUAL(chain->_cchRowWidth, chain->_list[0]._cchLength + chain->_list[1]._cchLength + chain->_list[2]._cchLength);
|
||||
|
||||
// on 'ABC', step from C to A
|
||||
auto index = 2;
|
||||
auto stepSize = 2;
|
||||
verifyStep(chain.get(), index, stepSize, TextAttribute(0xA));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestSetAttrToEnd)
|
||||
{
|
||||
const WORD wTestAttr = FOREGROUND_BLUE | BACKGROUND_GREEN;
|
||||
TextAttribute TestAttr = TextAttribute(wTestAttr);
|
||||
|
||||
Log::Comment(L"FIRST: Set index to > 0 to test making/modifying chains");
|
||||
const short iTestIndex = 50;
|
||||
VERIFY_IS_TRUE(iTestIndex >= 0 && iTestIndex < _sDefaultLength);
|
||||
|
||||
Log::Comment(L"SetAttrToEnd for single color applied to whole string.");
|
||||
pSingle->SetAttrToEnd(iTestIndex, TestAttr);
|
||||
|
||||
// Was 1 (single), should now have 2 segments
|
||||
VERIFY_ARE_EQUAL(pSingle->_list.size(), 2u);
|
||||
|
||||
VERIFY_ARE_EQUAL(pSingle->_list[0].GetAttributes(), _DefaultAttr);
|
||||
VERIFY_ARE_EQUAL(pSingle->_list[0].GetLength(), (unsigned int)(_sDefaultLength - (_sDefaultLength - iTestIndex)));
|
||||
|
||||
VERIFY_ARE_EQUAL(pSingle->_list[1].GetAttributes(), TestAttr);
|
||||
VERIFY_ARE_EQUAL(pSingle->_list[1].GetLength(), (unsigned int)(_sDefaultLength - iTestIndex));
|
||||
|
||||
Log::Comment(L"SetAttrToEnd for existing chain of multiple colors.");
|
||||
pChain->SetAttrToEnd(iTestIndex, TestAttr);
|
||||
|
||||
// From 7 segments down to 5.
|
||||
VERIFY_ARE_EQUAL(pChain->_list.size(), 5u);
|
||||
|
||||
// Verify chain colors and lengths
|
||||
VERIFY_ARE_EQUAL(TextAttribute(0), pChain->_list[0].GetAttributes());
|
||||
VERIFY_ARE_EQUAL(pChain->_list[0].GetLength(), (unsigned int)13);
|
||||
|
||||
VERIFY_ARE_EQUAL(TextAttribute(1), pChain->_list[1].GetAttributes());
|
||||
VERIFY_ARE_EQUAL(pChain->_list[1].GetLength(), (unsigned int)13);
|
||||
|
||||
VERIFY_ARE_EQUAL(TextAttribute(2), pChain->_list[2].GetAttributes());
|
||||
VERIFY_ARE_EQUAL(pChain->_list[2].GetLength(), (unsigned int)13);
|
||||
|
||||
VERIFY_ARE_EQUAL(TextAttribute(3), pChain->_list[3].GetAttributes());
|
||||
VERIFY_ARE_EQUAL(pChain->_list[3].GetLength(), (unsigned int)11);
|
||||
|
||||
VERIFY_ARE_EQUAL(TestAttr, pChain->_list[4].GetAttributes());
|
||||
VERIFY_ARE_EQUAL(pChain->_list[4].GetLength(), (unsigned int)30);
|
||||
|
||||
Log::Comment(L"SECOND: Set index to 0 to test replacing anything with a single");
|
||||
|
||||
ATTR_ROW* pTestItems[]{ pSingle, pChain };
|
||||
|
||||
for (UINT iIndex = 0; iIndex < ARRAYSIZE(pTestItems); iIndex++)
|
||||
{
|
||||
ATTR_ROW* pUnderTest = pTestItems[iIndex];
|
||||
|
||||
pUnderTest->SetAttrToEnd(0, TestAttr);
|
||||
|
||||
// should be down to 1 attribute set from beginning to end of string
|
||||
VERIFY_ARE_EQUAL(pUnderTest->_list.size(), 1u);
|
||||
|
||||
// singular pair should contain the color
|
||||
VERIFY_ARE_EQUAL(pUnderTest->_list[0].GetAttributes(), TestAttr);
|
||||
|
||||
// and its length should be the length of the whole string
|
||||
VERIFY_ARE_EQUAL(pUnderTest->_list[0].GetLength(), (unsigned int)_sDefaultLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestTotalLength)
|
||||
{
|
||||
ATTR_ROW* pTestItems[]{ pSingle, pChain };
|
||||
|
||||
for (UINT iIndex = 0; iIndex < ARRAYSIZE(pTestItems); iIndex++)
|
||||
{
|
||||
ATTR_ROW* pUnderTest = pTestItems[iIndex];
|
||||
|
||||
const size_t Result = pUnderTest->_cchRowWidth;
|
||||
|
||||
VERIFY_ARE_EQUAL((short)Result, _sDefaultLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestResize)
|
||||
{
|
||||
pSingle->Resize(240);
|
||||
pChain->Resize(240);
|
||||
|
||||
pSingle->Resize(255);
|
||||
pChain->Resize(255);
|
||||
|
||||
pSingle->Resize(255);
|
||||
pChain->Resize(255);
|
||||
|
||||
pSingle->Resize(60);
|
||||
pChain->Resize(60);
|
||||
|
||||
pSingle->Resize(60);
|
||||
pChain->Resize(60);
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(pSingle->Resize(0), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; });
|
||||
VERIFY_THROWS_SPECIFIC(pChain->Resize(0), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; });
|
||||
}
|
||||
};
|
||||
@@ -6,10 +6,11 @@
|
||||
<RootNamespace>TextBufferUnitTests</RootNamespace>
|
||||
<ProjectName>TextBuffer.Unit.Tests</ProjectName>
|
||||
<TargetName>TextBuffer.Unit.Tests</TargetName>
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AttrRowTests.cpp" />
|
||||
<ClCompile Include="ReflowTests.cpp" />
|
||||
<ClCompile Include="TextColorTests.cpp" />
|
||||
<ClCompile Include="TextAttributeTests.cpp" />
|
||||
|
||||
@@ -14,6 +14,7 @@ DLLDEF =
|
||||
|
||||
SOURCES = \
|
||||
$(SOURCES) \
|
||||
AttrRowTests.cpp \
|
||||
ReflowTests.cpp \
|
||||
TextColorTests.cpp \
|
||||
TextAttributeTests.cpp \
|
||||
|
||||
@@ -782,7 +782,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Switch to the next MRU tab, which is the fourth tab");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
ActionEventArgs eventArgs{};
|
||||
page->_HandleNextTab(nullptr, eventArgs);
|
||||
});
|
||||
|
||||
Log::Comment(L"Sleep to let events propagate");
|
||||
@@ -803,7 +804,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Switch to the next MRU tab, which is the second tab");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
ActionEventArgs eventArgs{};
|
||||
page->_HandleNextTab(nullptr, eventArgs);
|
||||
});
|
||||
|
||||
Log::Comment(L"Sleep to let events propagate");
|
||||
@@ -827,7 +829,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Switch to the next in-order tab, which is the third tab");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
ActionEventArgs eventArgs{};
|
||||
page->_HandleNextTab(nullptr, eventArgs);
|
||||
});
|
||||
TestOnUIThread([&page]() {
|
||||
uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
|
||||
@@ -839,7 +842,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Switch to the next in-order tab, which is the fourth tab");
|
||||
TestOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
ActionEventArgs eventArgs{};
|
||||
page->_HandleNextTab(nullptr, eventArgs);
|
||||
});
|
||||
TestOnUIThread([&page]() {
|
||||
uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
|
||||
@@ -912,7 +916,7 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(L"Switch to the next MRU tab, which is the third tab");
|
||||
RunOnUIThread([&page]() {
|
||||
page->_SelectNextTab(true, nullptr);
|
||||
page->_SelectNextTab(true);
|
||||
// In the course of a single tick, the Command Palette will:
|
||||
// * open
|
||||
// * select the proper tab from the mru's list
|
||||
|
||||
@@ -56,7 +56,7 @@ HRESULT OpenTerminalHere::Invoke(IShellItemArray* psiItemArray,
|
||||
siEx.StartupInfo.cb = sizeof(STARTUPINFOEX);
|
||||
|
||||
// Append a "\." to the given path, so that this will work in "C:\"
|
||||
auto cmdline{ wil::str_printf<std::wstring>(LR"-("%s" -d "%s\.")-", GetWtExePath().c_str(), pszName.get()) };
|
||||
std::wstring cmdline = fmt::format(L"\"{}\" -d \"{}\\.\"", GetWtExePath(), pszName.get());
|
||||
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(
|
||||
nullptr,
|
||||
cmdline.data(),
|
||||
|
||||
@@ -89,23 +89,15 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalPage::_HandleNextTab(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
const auto& realArgs = args.ActionArgs().try_as<NextTabArgs>();
|
||||
if (realArgs)
|
||||
{
|
||||
_SelectNextTab(true, realArgs.SwitcherMode());
|
||||
args.Handled(true);
|
||||
}
|
||||
_SelectNextTab(true);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandlePrevTab(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
const auto& realArgs = args.ActionArgs().try_as<PrevTabArgs>();
|
||||
if (realArgs)
|
||||
{
|
||||
_SelectNextTab(false, realArgs.SwitcherMode());
|
||||
args.Handled(true);
|
||||
}
|
||||
_SelectNextTab(false);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleSendInput(const IInspectable& /*sender*/,
|
||||
|
||||
@@ -530,15 +530,6 @@ namespace winrt::TerminalApp::implementation
|
||||
// which will cause us to refresh the list of filterable commands.
|
||||
_searchBox().Text(L"");
|
||||
_searchBox().Focus(FocusState::Programmatic);
|
||||
|
||||
if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(_searchBox()) })
|
||||
{
|
||||
automationPeer.RaiseNotificationEvent(
|
||||
Automation::Peers::AutomationNotificationKind::ActionCompleted,
|
||||
Automation::Peers::AutomationNotificationProcessing::CurrentThenMostRecent,
|
||||
fmt::format(std::wstring_view{ RS_(L"CommandPalette_NestedCommandAnnouncement") }, ParentCommandName()),
|
||||
L"CommandPaletteNestingLevelChanged" /* unique name for this notification category */);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -777,19 +768,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::ActionMode)
|
||||
{
|
||||
const auto currentNeedleHasResults{ _filteredActions.Size() > 0 };
|
||||
_noMatchesText().Visibility(currentNeedleHasResults ? Visibility::Collapsed : Visibility::Visible);
|
||||
if (!currentNeedleHasResults)
|
||||
{
|
||||
if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(_searchBox()) })
|
||||
{
|
||||
automationPeer.RaiseNotificationEvent(
|
||||
Automation::Peers::AutomationNotificationKind::ActionCompleted,
|
||||
Automation::Peers::AutomationNotificationProcessing::ImportantMostRecent,
|
||||
NoMatchesText(), // NoMatchesText contains the right text for the current mode
|
||||
L"CommandPaletteResultAnnouncement" /* unique name for this notification */);
|
||||
}
|
||||
}
|
||||
_noMatchesText().Visibility(_filteredActions.Size() > 0 ? Visibility::Collapsed : Visibility::Visible);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -918,9 +897,6 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
_currentMode = mode;
|
||||
|
||||
const auto currentlyVisible{ Visibility() == Visibility::Visible };
|
||||
|
||||
auto modeAnnouncementResourceKey{ USES_RESOURCE(L"CommandPaletteModeAnnouncement_ActionMode") };
|
||||
ParsedCommandLineText(L"");
|
||||
_searchBox().Text(L"");
|
||||
_searchBox().Select(_searchBox().Text().size(), 0);
|
||||
@@ -936,7 +912,6 @@ namespace winrt::TerminalApp::implementation
|
||||
NoMatchesText(RS_(L"TabSwitcher_NoMatchesText"));
|
||||
ControlName(RS_(L"TabSwitcherControlName"));
|
||||
PrefixCharacter(L"");
|
||||
modeAnnouncementResourceKey = USES_RESOURCE(L"CommandPaletteModeAnnouncement_TabSearchSwitchMode");
|
||||
break;
|
||||
}
|
||||
case CommandPaletteMode::CommandlineMode:
|
||||
@@ -944,7 +919,6 @@ namespace winrt::TerminalApp::implementation
|
||||
NoMatchesText(L"");
|
||||
ControlName(RS_(L"CommandPaletteControlName"));
|
||||
PrefixCharacter(L"");
|
||||
modeAnnouncementResourceKey = USES_RESOURCE(L"CommandPaletteModeAnnouncement_CommandlineMode");
|
||||
break;
|
||||
case CommandPaletteMode::ActionMode:
|
||||
default:
|
||||
@@ -952,23 +926,9 @@ namespace winrt::TerminalApp::implementation
|
||||
NoMatchesText(RS_(L"CommandPalette_NoMatchesText/Text"));
|
||||
ControlName(RS_(L"CommandPaletteControlName"));
|
||||
PrefixCharacter(L">");
|
||||
// modeAnnouncementResourceKey is already set to _ActionMode
|
||||
// We did this above to deduce the type (and make it easier on ourselves later).
|
||||
break;
|
||||
}
|
||||
|
||||
if (currentlyVisible)
|
||||
{
|
||||
if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(_searchBox()) })
|
||||
{
|
||||
automationPeer.RaiseNotificationEvent(
|
||||
Automation::Peers::AutomationNotificationKind::ActionCompleted,
|
||||
Automation::Peers::AutomationNotificationProcessing::CurrentThenMostRecent,
|
||||
GetLibraryResourceString(modeAnnouncementResourceKey),
|
||||
L"CommandPaletteModeSwitch" /* unique ID for this notification */);
|
||||
}
|
||||
}
|
||||
|
||||
// The smooth remove/add animations that happen during
|
||||
// UpdateFilteredActions don't work very well when switching between
|
||||
// modes because of the sheer amount of remove/adds. So, let's just
|
||||
|
||||
@@ -418,10 +418,10 @@
|
||||
<value>Do you want to close all tabs?</value>
|
||||
</data>
|
||||
<data name="CloseReadOnlyDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
<value>Close anyway</value>
|
||||
</data>
|
||||
<data name="CloseReadOnlyDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>Close anyway</value>
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="CloseReadOnlyDialog.Title" xml:space="preserve">
|
||||
<value>Warning</value>
|
||||
@@ -459,22 +459,6 @@
|
||||
<data name="CommandPalette_NoMatchesText.Text" xml:space="preserve">
|
||||
<value>No matching commands</value>
|
||||
</data>
|
||||
<data name="CommandPaletteModeAnnouncement_ActionMode" xml:space="preserve">
|
||||
<value>Action search mode</value>
|
||||
<comment>This text will be read aloud using assistive technologies when the command palette switches into action (command search) mode.</comment>
|
||||
</data>
|
||||
<data name="CommandPaletteModeAnnouncement_TabSearchSwitchMode" xml:space="preserve">
|
||||
<value>Tab title mode</value>
|
||||
<comment>This text will be read aloud using assistive technologies when the command palette switches into a mode that filters tab names.</comment>
|
||||
</data>
|
||||
<data name="CommandPaletteModeAnnouncement_CommandlineMode" xml:space="preserve">
|
||||
<value>Command-line mode</value>
|
||||
<comment>This text will be read aloud using assistive technologies when the command palette switches into the raw commandline parsing mode.</comment>
|
||||
</data>
|
||||
<data name="CommandPalette_NestedCommandAnnouncement" xml:space="preserve">
|
||||
<value>More options for "{}"</value>
|
||||
<comment>This text will be read aloud using assistive technologies when the user selects a command that has additional options. The {} will be expanded to the name of the command containing more options.</comment>
|
||||
</data>
|
||||
<data name="CommandPalette_ParsedCommandLine" xml:space="preserve">
|
||||
<value>Executing command line will invoke the following commands:</value>
|
||||
<comment>Will be followed by a list of strings describing parsed commands</comment>
|
||||
|
||||
@@ -1339,8 +1339,8 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
ContentDialogResult warningResult = co_await _ShowCloseReadOnlyDialog();
|
||||
|
||||
// If the user didn't explicitly click on close tab - leave
|
||||
if (warningResult != ContentDialogResult::Primary)
|
||||
// The primary action is canceling the removal
|
||||
if (warningResult == ContentDialogResult::Primary)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
@@ -1527,10 +1527,10 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// Method Description:
|
||||
// - Sets focus to the tab to the right or left the currently selected tab.
|
||||
void TerminalPage::_SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode)
|
||||
void TerminalPage::_SelectNextTab(const bool bMoveRight)
|
||||
{
|
||||
const auto index{ _GetFocusedTabIndex().value_or(0) };
|
||||
const auto tabSwitchMode = customTabSwitcherMode ? customTabSwitcherMode.Value() : _settings.GlobalSettings().TabSwitcherMode();
|
||||
const auto tabSwitchMode = _settings.GlobalSettings().TabSwitcherMode();
|
||||
if (tabSwitchMode == TabSwitcherMode::Disabled)
|
||||
{
|
||||
uint32_t tabCount = _tabs.Size();
|
||||
@@ -1736,8 +1736,8 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
ContentDialogResult warningResult = co_await _ShowCloseReadOnlyDialog();
|
||||
|
||||
// If the user didn't explicitly click on close tab - leave
|
||||
if (warningResult != ContentDialogResult::Primary)
|
||||
// The primary action is canceling the action
|
||||
if (warningResult == ContentDialogResult::Primary)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
@@ -1813,7 +1813,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page
|
||||
realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ?
|
||||
terminalTab->GetActiveTerminalControl().GetViewHeight() :
|
||||
terminalTab->GetActiveTerminalControl().ViewHeight() :
|
||||
_systemRowsToScroll;
|
||||
}
|
||||
else
|
||||
@@ -1954,7 +1954,7 @@ namespace winrt::TerminalApp::implementation
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
const auto control = _GetActiveControl();
|
||||
const auto termHeight = control.GetViewHeight();
|
||||
const auto termHeight = control.ViewHeight();
|
||||
auto scrollDelta = _ComputeScrollDelta(scrollDirection, termHeight);
|
||||
terminalTab->Scroll(scrollDelta);
|
||||
}
|
||||
@@ -2290,29 +2290,37 @@ namespace winrt::TerminalApp::implementation
|
||||
return false;
|
||||
}
|
||||
|
||||
void TerminalPage::_ControlNoticeRaisedHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::NoticeEventArgs eventArgs)
|
||||
// Important! Don't take this eventArgs by reference, we need to extend the
|
||||
// lifetime of it to the other side of the co_await!
|
||||
winrt::fire_and_forget TerminalPage::_ControlNoticeRaisedHandler(const IInspectable /*sender*/,
|
||||
const Microsoft::Terminal::Control::NoticeEventArgs eventArgs)
|
||||
{
|
||||
winrt::hstring message = eventArgs.Message();
|
||||
|
||||
winrt::hstring title;
|
||||
|
||||
switch (eventArgs.Level())
|
||||
auto weakThis = get_weak();
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
if (auto page = weakThis.get())
|
||||
{
|
||||
case NoticeLevel::Debug:
|
||||
title = RS_(L"NoticeDebug"); //\xebe8
|
||||
break;
|
||||
case NoticeLevel::Info:
|
||||
title = RS_(L"NoticeInfo"); // \xe946
|
||||
break;
|
||||
case NoticeLevel::Warning:
|
||||
title = RS_(L"NoticeWarning"); //\xe7ba
|
||||
break;
|
||||
case NoticeLevel::Error:
|
||||
title = RS_(L"NoticeError"); //\xe783
|
||||
break;
|
||||
}
|
||||
winrt::hstring message = eventArgs.Message();
|
||||
|
||||
_ShowControlNoticeDialog(title, message);
|
||||
winrt::hstring title;
|
||||
|
||||
switch (eventArgs.Level())
|
||||
{
|
||||
case NoticeLevel::Debug:
|
||||
title = RS_(L"NoticeDebug"); //\xebe8
|
||||
break;
|
||||
case NoticeLevel::Info:
|
||||
title = RS_(L"NoticeInfo"); // \xe946
|
||||
break;
|
||||
case NoticeLevel::Warning:
|
||||
title = RS_(L"NoticeWarning"); //\xe7ba
|
||||
break;
|
||||
case NoticeLevel::Error:
|
||||
title = RS_(L"NoticeError"); //\xe783
|
||||
break;
|
||||
}
|
||||
|
||||
page->_ShowControlNoticeDialog(title, message);
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_ShowControlNoticeDialog(const winrt::hstring& title, const winrt::hstring& message)
|
||||
@@ -2350,8 +2358,9 @@ namespace winrt::TerminalApp::implementation
|
||||
// Arguments:
|
||||
// - sender (not used)
|
||||
// - eventArgs: the arguments specifying how to set the progress indicator
|
||||
void TerminalPage::_SetTaskbarProgressHandler(const IInspectable /*sender*/, const IInspectable /*eventArgs*/)
|
||||
winrt::fire_and_forget TerminalPage::_SetTaskbarProgressHandler(const IInspectable /*sender*/, const IInspectable /*eventArgs*/)
|
||||
{
|
||||
co_await resume_foreground(Dispatcher());
|
||||
_SetTaskbarProgressHandlers(*this, nullptr);
|
||||
}
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term, TerminalTab& hostingTab);
|
||||
|
||||
void _SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode);
|
||||
void _SelectNextTab(const bool bMoveRight);
|
||||
bool _SelectTab(const uint32_t tabIndex);
|
||||
void _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
|
||||
|
||||
@@ -221,11 +221,11 @@ namespace winrt::TerminalApp::implementation
|
||||
void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri);
|
||||
bool _CopyText(const bool singleLine, const Windows::Foundation::IReference<Microsoft::Terminal::Control::CopyFormat>& formats);
|
||||
|
||||
void _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs);
|
||||
winrt::fire_and_forget _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs);
|
||||
|
||||
void _PasteText();
|
||||
|
||||
void _ControlNoticeRaisedHandler(const IInspectable sender, const Microsoft::Terminal::Control::NoticeEventArgs eventArgs);
|
||||
winrt::fire_and_forget _ControlNoticeRaisedHandler(const IInspectable sender, const Microsoft::Terminal::Control::NoticeEventArgs eventArgs);
|
||||
void _ShowControlNoticeDialog(const winrt::hstring& title, const winrt::hstring& message);
|
||||
|
||||
fire_and_forget _LaunchSettings(const Microsoft::Terminal::Settings::Model::SettingsTarget target);
|
||||
|
||||
@@ -57,7 +57,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
x:Load="False"
|
||||
x:Name="CloseReadOnlyDialog"
|
||||
x:Uid="CloseReadOnlyDialog"
|
||||
DefaultButton="Close">
|
||||
DefaultButton="Primary">
|
||||
</ContentDialog>
|
||||
|
||||
<ContentDialog
|
||||
|
||||
@@ -167,7 +167,17 @@ namespace winrt::TerminalApp::implementation
|
||||
if (lastFocusedControl)
|
||||
{
|
||||
lastFocusedControl.Focus(_focusState);
|
||||
lastFocusedControl.TaskbarProgressChanged();
|
||||
// !!TODO!!: This is weird: The tab calls a method on the
|
||||
// control to cause the control to raise an event to the page to
|
||||
// the app logic
|
||||
//
|
||||
// TermControl \v
|
||||
// /^ TerminalPage \v
|
||||
// TerminalTab /^ AppLogic \v
|
||||
// AppHost
|
||||
// Seems like there's a better way...
|
||||
//
|
||||
// lastFocusedControl.TaskbarProgressChanged();
|
||||
}
|
||||
// When we gain focus, remove the bell indicator if it is active
|
||||
if (_tabStatus.BellIndicator())
|
||||
@@ -378,7 +388,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
co_await winrt::resume_foreground(control.Dispatcher());
|
||||
|
||||
const auto currentOffset = control.GetScrollOffset();
|
||||
const auto currentOffset = control.ScrollOffset();
|
||||
control.ScrollViewport(::base::ClampAdd(currentOffset, delta));
|
||||
}
|
||||
|
||||
@@ -788,7 +798,7 @@ namespace winrt::TerminalApp::implementation
|
||||
closeTabMenuItem.Click([weakThis](auto&&, auto&&) {
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_ClosedHandlers(nullptr, nullptr);
|
||||
tab->_rootPane->Close();
|
||||
}
|
||||
});
|
||||
closeTabMenuItem.Text(RS_(L"TabClose"));
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
#include "winrt/Windows.UI.Xaml.Markup.h"
|
||||
#include "winrt/Windows.UI.Xaml.Documents.h"
|
||||
#include "winrt/Windows.UI.Xaml.Automation.h"
|
||||
#include "winrt/Windows.UI.Xaml.Automation.Peers.h"
|
||||
#include "winrt/Windows.UI.ViewManagement.h"
|
||||
#include <winrt/Windows.ApplicationModel.h>
|
||||
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
|
||||
|
||||
1246
src/cascadia/TerminalControl/ControlCore.cpp
Normal file
1246
src/cascadia/TerminalControl/ControlCore.cpp
Normal file
File diff suppressed because it is too large
Load Diff
214
src/cascadia/TerminalControl/ControlCore.h
Normal file
214
src/cascadia/TerminalControl/ControlCore.h
Normal file
@@ -0,0 +1,214 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "EventArgs.h"
|
||||
#include "ControlCore.g.h"
|
||||
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
|
||||
#include "../../renderer/base/Renderer.hpp"
|
||||
#include "../../renderer/dx/DxRenderer.hpp"
|
||||
#include "../../renderer/uia/UiaRenderer.hpp"
|
||||
#include "../../cascadia/TerminalCore/Terminal.hpp"
|
||||
#include "../buffer/out/search.h"
|
||||
#include "cppwinrt_utils.h"
|
||||
#include "ThrottledFunc.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
struct ControlCore : ControlCoreT<ControlCore>
|
||||
{
|
||||
public:
|
||||
ControlCore(IControlSettings settings,
|
||||
TerminalConnection::ITerminalConnection connection);
|
||||
|
||||
bool InitializeTerminal(const double actualWidth,
|
||||
const double actualHeight,
|
||||
const double compositionScaleX,
|
||||
const double compositionScaleY);
|
||||
|
||||
void UpdateSettings(const IControlSettings& settings);
|
||||
void SizeChanged(const double width, const double height);
|
||||
void ScaleChanged(const double scaleX, const double scaleY);
|
||||
float RendererScale() const;
|
||||
HANDLE GetSwapChainHandle() const;
|
||||
|
||||
void AdjustFontSize(int fontSizeDelta);
|
||||
void ResetFontSize();
|
||||
FontInfo GetFont() const;
|
||||
|
||||
til::color BackgroundColor() const;
|
||||
void SetBackgroundOpacity(const float opacity);
|
||||
|
||||
void SendInput(const winrt::hstring& wstr);
|
||||
void PasteText(const winrt::hstring& hstr);
|
||||
bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
|
||||
|
||||
void ToggleShaderEffects();
|
||||
void ResumeRendering();
|
||||
|
||||
void UpdatePatternLocations();
|
||||
void UpdateHoveredCell(const std::optional<COORD>& terminalPosition);
|
||||
winrt::hstring GetHyperlink(const til::point position) const;
|
||||
winrt::hstring GetHoveredUriText() const;
|
||||
std::optional<COORD> GetHoveredCell() const;
|
||||
|
||||
void SetSelectionAnchor(winrt::Windows::Foundation::Point const& position);
|
||||
void SetEndSelectionPoint(winrt::Windows::Foundation::Point const& position);
|
||||
|
||||
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
|
||||
|
||||
winrt::fire_and_forget _AsyncCloseConnection();
|
||||
void Close();
|
||||
|
||||
#pragma region ICoreState
|
||||
// existing
|
||||
Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() noexcept;
|
||||
const size_t TaskbarState() const noexcept;
|
||||
const size_t TaskbarProgress() const noexcept;
|
||||
hstring Title();
|
||||
hstring WorkingDirectory() const;
|
||||
TerminalConnection::ConnectionState ConnectionState() const;
|
||||
// new
|
||||
int ScrollOffset();
|
||||
int ViewHeight() const;
|
||||
int BufferHeight() const;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region ITerminalInputButNotReally
|
||||
bool TrySendKeyEvent(const WORD vkey,
|
||||
const WORD scanCode,
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
const bool eitherWinPressed,
|
||||
const bool keyDown);
|
||||
bool SendCharEvent(const wchar_t ch,
|
||||
const WORD scanCode,
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
|
||||
bool SendMouseEvent(const COORD viewportPos,
|
||||
const unsigned int uiButton,
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates states,
|
||||
const short wheelDelta,
|
||||
const ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state);
|
||||
void UserScrollViewport(const int viewTop);
|
||||
#pragma endregion
|
||||
|
||||
void BlinkAttributeTick();
|
||||
void BlinkCursor();
|
||||
bool CursorOn() const;
|
||||
void CursorOn(const bool isCursorOn);
|
||||
|
||||
bool IsVtMouseModeEnabled() const;
|
||||
til::point CursorPosition() const;
|
||||
|
||||
bool HasSelection() const;
|
||||
std::vector<std::wstring> SelectedText(bool trimTrailingWhitespace) const;
|
||||
|
||||
void Search(const winrt::hstring& text,
|
||||
const bool goForward,
|
||||
const bool caseSensitive);
|
||||
|
||||
void LeftClickOnTerminal(const til::point terminalPosition,
|
||||
const int numberOfClicks,
|
||||
const bool altEnabled,
|
||||
const bool shiftEnabled,
|
||||
const bool isOnOriginalPosition,
|
||||
bool& selectionNeedsToBeCopied);
|
||||
|
||||
void AttachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine);
|
||||
|
||||
bool IsInReadOnlyMode() const;
|
||||
void ToggleReadOnlyMode();
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
// clang-format off
|
||||
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
|
||||
|
||||
TYPED_EVENT(CopyToClipboard, IInspectable, Control::CopyToClipboardEventArgs);
|
||||
TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs);
|
||||
TYPED_EVENT(WarningBell, IInspectable, IInspectable);
|
||||
TYPED_EVENT(TabColorChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(BackgroundColorChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(ScrollPositionChanged, IInspectable, Control::ScrollPositionChangedArgs);
|
||||
TYPED_EVENT(CursorPositionChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(TaskbarProgressChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(HoveredHyperlinkChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(RendererEnteredErrorState, IInspectable, IInspectable);
|
||||
TYPED_EVENT(SwapChainChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(RendererWarning, IInspectable, Control::RendererWarningArgs);
|
||||
TYPED_EVENT(RaiseNotice, IInspectable, Control::NoticeEventArgs);
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
bool _initializedTerminal{ false };
|
||||
|
||||
TerminalConnection::ITerminalConnection _connection{ nullptr };
|
||||
event_token _connectionOutputEventToken;
|
||||
TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker;
|
||||
|
||||
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal{ nullptr };
|
||||
|
||||
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer{ nullptr };
|
||||
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine{ nullptr };
|
||||
|
||||
IControlSettings _settings{ nullptr }; // ? Might be able to get away with only retrieving pieces
|
||||
|
||||
FontInfoDesired _desiredFont;
|
||||
FontInfo _actualFont;
|
||||
|
||||
// storage location for the leading surrogate of a utf-16 surrogate pair
|
||||
std::optional<wchar_t> _leadingSurrogate{ std::nullopt };
|
||||
|
||||
bool _isReadOnly{ false }; // Probably belongs in Interactivity
|
||||
|
||||
std::optional<COORD> _lastHoveredCell{ std::nullopt };
|
||||
// Track the last hyperlink ID we hovered over
|
||||
uint16_t _lastHoveredId{ 0 };
|
||||
|
||||
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _lastHoveredInterval{ std::nullopt };
|
||||
|
||||
double _panelWidth{ 0 };
|
||||
double _panelHeight{ 0 };
|
||||
double _compositionScaleX{ 0 };
|
||||
double _compositionScaleY{ 0 };
|
||||
til::color _backgroundColor; // This is _in_ Terminal already!
|
||||
|
||||
void _SetFontSize(int fontSize);
|
||||
void _UpdateFont(const bool initialUpdate = false);
|
||||
void _RefreshSizeUnderLock();
|
||||
void _DoResizeUnderLock(const double newWidth,
|
||||
const double newHeight);
|
||||
|
||||
void _SendInputToConnection(const winrt::hstring& wstr);
|
||||
void _SendInputToConnection(std::wstring_view wstr);
|
||||
|
||||
#pragma region TerminalCoreCallbacks
|
||||
void _TerminalCopyToClipboard(const std::wstring_view& wstr);
|
||||
void _TerminalWarningBell();
|
||||
void _TerminalTitleChanged(const std::wstring_view& wstr);
|
||||
void _TerminalTabColorChanged(const std::optional<til::color> color);
|
||||
void _TerminalBackgroundColorChanged(const COLORREF color);
|
||||
void _TerminalScrollPositionChanged(const int viewTop,
|
||||
const int viewHeight,
|
||||
const int bufferSize);
|
||||
void _TerminalCursorPositionChanged();
|
||||
void _TerminalTaskbarProgressChanged();
|
||||
#pragma endregion
|
||||
|
||||
#pragma region RendererCallbacks
|
||||
void _RendererWarning(const HRESULT hr);
|
||||
void RenderEngineSwapChainChanged();
|
||||
#pragma endregion
|
||||
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// These methods are new
|
||||
|
||||
void _raiseHoveredHyperlinkChanged();
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(ControlCore);
|
||||
}
|
||||
16
src/cascadia/TerminalControl/ControlCore.idl
Normal file
16
src/cascadia/TerminalControl/ControlCore.idl
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "ICoreState.idl";
|
||||
import "IControlSettings.idl";
|
||||
import "EventArgs.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Control
|
||||
{
|
||||
|
||||
[default_interface] runtimeclass ControlCore : ICoreState
|
||||
{
|
||||
ControlCore(Microsoft.Terminal.Control.IControlSettings settings,
|
||||
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
|
||||
};
|
||||
}
|
||||
23
src/cascadia/TerminalControl/ICoreState.idl
Normal file
23
src/cascadia/TerminalControl/ICoreState.idl
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Terminal.Control
|
||||
{
|
||||
// These are properties of the TerminalCore that should be queryable by the
|
||||
// rest of the app.
|
||||
interface ICoreState
|
||||
{
|
||||
String Title { get; };
|
||||
UInt64 TaskbarState { get; };
|
||||
UInt64 TaskbarProgress { get; };
|
||||
|
||||
String WorkingDirectory { get; };
|
||||
|
||||
Windows.Foundation.IReference<Windows.UI.Color> TabColor { get; };
|
||||
|
||||
Int32 ScrollOffset { get; };
|
||||
Int32 ViewHeight { get; };
|
||||
|
||||
Microsoft.Terminal.TerminalConnection.ConnectionState ConnectionState { get; };
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,8 @@
|
||||
#include "SearchBoxControl.h"
|
||||
#include "ThrottledFunc.h"
|
||||
|
||||
#include "ControlCore.h"
|
||||
|
||||
namespace Microsoft::Console::VirtualTerminal
|
||||
{
|
||||
struct MouseButtonState;
|
||||
@@ -39,20 +41,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension);
|
||||
|
||||
void ScrollViewport(int viewTop);
|
||||
int GetScrollOffset();
|
||||
int GetViewHeight() const;
|
||||
int ScrollOffset();
|
||||
int ViewHeight() const;
|
||||
|
||||
void AdjustFontSize(int fontSizeDelta);
|
||||
void ResetFontSize();
|
||||
til::point GetFontSize() const;
|
||||
|
||||
void SendInput(const winrt::hstring& input);
|
||||
void ToggleShaderEffects();
|
||||
|
||||
winrt::fire_and_forget RenderEngineSwapChainChanged();
|
||||
void _AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain);
|
||||
winrt::fire_and_forget _RendererEnteredErrorState();
|
||||
winrt::fire_and_forget RenderEngineSwapChainChanged(const IInspectable& sender, const IInspectable& args);
|
||||
void _AttachDxgiSwapChainToXaml(HANDLE swapChainHandle);
|
||||
winrt::fire_and_forget _RendererEnteredErrorState(const IInspectable& sender, const IInspectable& args);
|
||||
void _RenderRetryButton_Click(IInspectable const& button, IInspectable const& args);
|
||||
winrt::fire_and_forget _RendererWarning(const HRESULT hr);
|
||||
winrt::fire_and_forget _RendererWarning(const IInspectable& sender,
|
||||
const Control::RendererWarningArgs& args);
|
||||
|
||||
void CreateSearchBoxControl();
|
||||
|
||||
@@ -62,13 +66,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown);
|
||||
|
||||
void UpdatePatternLocations();
|
||||
|
||||
~TermControl();
|
||||
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
|
||||
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
|
||||
const FontInfo GetActualFont() const;
|
||||
const Windows::UI::Xaml::Thickness GetPadding();
|
||||
|
||||
TerminalConnection::ConnectionState ConnectionState() const;
|
||||
@@ -85,60 +86,50 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() noexcept;
|
||||
|
||||
winrt::fire_and_forget TaskbarProgressChanged();
|
||||
// winrt::fire_and_forget TaskbarProgressChanged();
|
||||
const size_t TaskbarState() const noexcept;
|
||||
const size_t TaskbarProgress() const noexcept;
|
||||
|
||||
bool ReadOnly() const noexcept;
|
||||
void ToggleReadOnly();
|
||||
|
||||
// clang-format off
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
DECLARE_EVENT(FontSizeChanged, _fontSizeChangedHandlers, Control::FontSizeChangedEventArgs);
|
||||
DECLARE_EVENT(ScrollPositionChanged, _scrollPositionChangedHandlers, Control::ScrollPositionChangedEventArgs);
|
||||
// clang-format off
|
||||
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
|
||||
|
||||
TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs);
|
||||
TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
|
||||
TYPED_EVENT(CopyToClipboard, IInspectable, Control::CopyToClipboardEventArgs);
|
||||
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
|
||||
TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable);
|
||||
TYPED_EVENT(RaiseNotice, IInspectable, Control::NoticeEventArgs);
|
||||
FORWARDED_TYPED_EVENT(CopyToClipboard, IInspectable, Control::CopyToClipboardEventArgs, _core, CopyToClipboard);
|
||||
FORWARDED_TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs, _core, TitleChanged);
|
||||
FORWARDED_TYPED_EVENT(WarningBell, IInspectable, IInspectable, _core, WarningBell);
|
||||
FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable, _core, TabColorChanged);
|
||||
FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable, _core, TaskbarProgressChanged);
|
||||
FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable, _core, ConnectionStateChanged);
|
||||
|
||||
TYPED_EVENT(WarningBell, IInspectable, IInspectable);
|
||||
TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(Initialized, Control::TermControl, Windows::UI::Xaml::RoutedEventArgs);
|
||||
TYPED_EVENT(TabColorChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(HidePointerCursor, IInspectable, IInspectable);
|
||||
TYPED_EVENT(RestorePointerCursor, IInspectable, IInspectable);
|
||||
TYPED_EVENT(ReadOnlyChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
|
||||
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
|
||||
TYPED_EVENT(RaiseNotice, IInspectable, Control::NoticeEventArgs);
|
||||
TYPED_EVENT(HidePointerCursor, IInspectable, IInspectable);
|
||||
TYPED_EVENT(RestorePointerCursor, IInspectable, IInspectable);
|
||||
TYPED_EVENT(ReadOnlyChanged, IInspectable, IInspectable);
|
||||
TYPED_EVENT(FocusFollowMouseRequested, IInspectable, IInspectable);
|
||||
TYPED_EVENT(Initialized, Control::TermControl, Windows::UI::Xaml::RoutedEventArgs);
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
friend struct TermControlT<TermControl>; // friend our parent so it can bind private event handlers
|
||||
TerminalConnection::ITerminalConnection _connection;
|
||||
|
||||
winrt::com_ptr<ControlCore> _core{ nullptr };
|
||||
|
||||
bool _initializedTerminal;
|
||||
|
||||
winrt::com_ptr<SearchBoxControl> _searchBox;
|
||||
|
||||
event_token _connectionOutputEventToken;
|
||||
TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker;
|
||||
|
||||
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
|
||||
|
||||
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer;
|
||||
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine;
|
||||
std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine;
|
||||
|
||||
IControlSettings _settings;
|
||||
bool _focused;
|
||||
std::atomic<bool> _closing;
|
||||
|
||||
FontInfoDesired _desiredFont;
|
||||
FontInfo _actualFont;
|
||||
|
||||
std::shared_ptr<ThrottledFunc<>> _tsfTryRedrawCanvas;
|
||||
|
||||
std::shared_ptr<ThrottledFunc<>> _updatePatternLocations;
|
||||
|
||||
struct ScrollBarUpdate
|
||||
@@ -151,7 +142,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
std::shared_ptr<ThrottledFunc<ScrollBarUpdate>> _updateScrollBar;
|
||||
bool _isInternalScrollBarUpdate;
|
||||
|
||||
unsigned int _rowsToScroll;
|
||||
unsigned int _rowsToScroll; // Definitely Control/Interactivity
|
||||
|
||||
// Auto scroll occurs when user, while selecting, drags cursor outside viewport. View is then scrolled to 'follow' the cursor.
|
||||
double _autoScrollVelocity;
|
||||
@@ -159,9 +150,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
Windows::UI::Xaml::DispatcherTimer _autoScrollTimer;
|
||||
std::optional<std::chrono::high_resolution_clock::time_point> _lastAutoScrollUpdateTime;
|
||||
|
||||
// storage location for the leading surrogate of a utf-16 surrogate pair
|
||||
std::optional<wchar_t> _leadingSurrogate;
|
||||
|
||||
std::optional<Windows::UI::Xaml::DispatcherTimer> _cursorTimer;
|
||||
std::optional<Windows::UI::Xaml::DispatcherTimer> _blinkTimer;
|
||||
|
||||
@@ -169,13 +157,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// viewport via touch input.
|
||||
std::optional<winrt::Windows::Foundation::Point> _touchAnchor;
|
||||
|
||||
// Track the last cell we hovered over (used in pointerMovedHandler)
|
||||
std::optional<COORD> _lastHoveredCell;
|
||||
// Track the last hyperlink ID we hovered over
|
||||
uint16_t _lastHoveredId;
|
||||
|
||||
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _lastHoveredInterval;
|
||||
|
||||
using Timestamp = uint64_t;
|
||||
|
||||
// imported from WinUser
|
||||
@@ -184,25 +165,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
unsigned int _multiClickCounter;
|
||||
Timestamp _lastMouseClickTimestamp;
|
||||
std::optional<winrt::Windows::Foundation::Point> _lastMouseClickPos;
|
||||
std::optional<winrt::Windows::Foundation::Point> _lastMouseClickPosNoSelection;
|
||||
std::optional<winrt::Windows::Foundation::Point> _singleClickTouchdownPos;
|
||||
std::optional<winrt::Windows::Foundation::Point> _lastMouseClickPosNoSelection;
|
||||
// This field tracks whether the selection has changed meaningfully
|
||||
// since it was last copied. It's generally used to prevent copyOnSelect
|
||||
// from firing when the pointer _just happens_ to be released over the
|
||||
// terminal.
|
||||
bool _selectionNeedsToBeCopied;
|
||||
bool _selectionNeedsToBeCopied; // ->Interactivity
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
|
||||
|
||||
bool _isReadOnly{ false };
|
||||
|
||||
void _ApplyUISettings(const IControlSettings&);
|
||||
void _UpdateSettingsOnUIThread();
|
||||
void _UpdateSystemParameterSettings() noexcept;
|
||||
void _InitializeBackgroundBrush();
|
||||
winrt::fire_and_forget _BackgroundColorChanged(const COLORREF color);
|
||||
void _BackgroundColorChangedHandler(const IInspectable& sender, const IInspectable& args);
|
||||
winrt::fire_and_forget _changeBackgroundColor(til::color bg);
|
||||
bool _InitializeTerminal();
|
||||
void _UpdateFont(const bool initialUpdate = false);
|
||||
void _SetFontSize(int fontSize);
|
||||
void _TappedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs const& e);
|
||||
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
@@ -223,19 +202,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void _CursorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
|
||||
void _BlinkTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
|
||||
void _SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition);
|
||||
void _SendInputToConnection(const winrt::hstring& wstr);
|
||||
void _SendInputToConnection(std::wstring_view wstr);
|
||||
void _SendPastedTextToConnection(const std::wstring& wstr);
|
||||
void _SwapChainSizeChanged(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e);
|
||||
void _SwapChainScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender, Windows::Foundation::IInspectable const& args);
|
||||
void _DoResizeUnderLock(const double newWidth, const double newHeight);
|
||||
void _RefreshSizeUnderLock();
|
||||
void _TerminalWarningBell();
|
||||
void _TerminalTitleChanged(const std::wstring_view& wstr);
|
||||
void _TerminalTabColorChanged(const std::optional<til::color> color);
|
||||
void _CopyToClipboard(const std::wstring_view& wstr);
|
||||
void _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize);
|
||||
void _TerminalCursorPositionChanged();
|
||||
void _ScrollPositionChanged(const IInspectable& sender, const Control::ScrollPositionChangedArgs& args);
|
||||
void _CursorPositionChanged(const IInspectable& sender, const IInspectable& args);
|
||||
|
||||
void _MouseScrollHandler(const double mouseDelta, const Windows::Foundation::Point point, const bool isLeftButtonPressed);
|
||||
void _MouseZoomHandler(const double delta);
|
||||
@@ -259,7 +231,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point);
|
||||
bool _CanSendVTMouseInput();
|
||||
|
||||
const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
|
||||
const til::point _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
|
||||
const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime);
|
||||
double _GetAutoScrollSpeed(double cursorDistanceFromBorder) const;
|
||||
|
||||
@@ -270,11 +242,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void _CompositionCompleted(winrt::hstring text);
|
||||
void _CurrentCursorPositionHandler(const IInspectable& sender, const CursorPositionEventArgs& eventArgs);
|
||||
void _FontInfoHandler(const IInspectable& sender, const FontInfoEventArgs& eventArgs);
|
||||
winrt::fire_and_forget _AsyncCloseConnection();
|
||||
|
||||
winrt::fire_and_forget _RaiseReadOnlyWarning();
|
||||
winrt::fire_and_forget _hoveredHyperlinkChanged(const IInspectable& sender, const IInspectable& args);
|
||||
|
||||
void _UpdateHoveredCell(const std::optional<COORD>& terminalPosition);
|
||||
void _coreFontSizeChanged(const int fontWidth,
|
||||
const int fontHeight,
|
||||
const bool isInitialChange);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,22 +5,27 @@ import "IMouseWheelListener.idl";
|
||||
import "IControlSettings.idl";
|
||||
import "IDirectKeyListener.idl";
|
||||
import "EventArgs.idl";
|
||||
import "ICoreState.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Control
|
||||
{
|
||||
|
||||
[default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl, IDirectKeyListener, IMouseWheelListener
|
||||
[default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl,
|
||||
IDirectKeyListener,
|
||||
IMouseWheelListener,
|
||||
ICoreState
|
||||
{
|
||||
TermControl(Microsoft.Terminal.Control.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
|
||||
TermControl(IControlSettings settings,
|
||||
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
|
||||
|
||||
static Windows.Foundation.Size GetProposedDimensions(Microsoft.Terminal.Control.IControlSettings settings, UInt32 dpi);
|
||||
static Windows.Foundation.Size GetProposedDimensions(IControlSettings settings, UInt32 dpi);
|
||||
|
||||
void UpdateSettings();
|
||||
|
||||
Microsoft.Terminal.Control.IControlSettings Settings { get; };
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
|
||||
event FontSizeChangedEventArgs FontSizeChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, CopyToClipboardEventArgs> CopyToClipboard;
|
||||
event Windows.Foundation.TypedEventHandler<Object, PasteFromClipboardEventArgs> PasteFromClipboard;
|
||||
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
|
||||
@@ -29,8 +34,6 @@ namespace Microsoft.Terminal.Control
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WarningBell;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> HidePointerCursor;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> RestorePointerCursor;
|
||||
|
||||
event ScrollPositionChangedEventArgs ScrollPositionChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ReadOnlyChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> FocusFollowMouseRequested;
|
||||
@@ -40,10 +43,6 @@ namespace Microsoft.Terminal.Control
|
||||
// We expose this and ConnectionState here so that it might eventually be data bound.
|
||||
event Windows.Foundation.TypedEventHandler<Object, IInspectable> ConnectionStateChanged;
|
||||
|
||||
Microsoft.Terminal.TerminalConnection.ConnectionState ConnectionState { get; };
|
||||
|
||||
String Title { get; };
|
||||
|
||||
Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
|
||||
void PasteTextFromClipboard();
|
||||
void Close();
|
||||
@@ -52,8 +51,6 @@ namespace Microsoft.Terminal.Control
|
||||
Single SnapDimensionToGrid(Boolean widthOrHeight, Single dimension);
|
||||
|
||||
void ScrollViewport(Int32 viewTop);
|
||||
Int32 GetScrollOffset();
|
||||
Int32 GetViewHeight();
|
||||
|
||||
void CreateSearchBoxControl();
|
||||
|
||||
@@ -65,13 +62,7 @@ namespace Microsoft.Terminal.Control
|
||||
void ToggleShaderEffects();
|
||||
void SendInput(String input);
|
||||
|
||||
void TaskbarProgressChanged();
|
||||
UInt64 TaskbarState { get; };
|
||||
UInt64 TaskbarProgress { get; };
|
||||
|
||||
String WorkingDirectory { get; };
|
||||
|
||||
Windows.Foundation.IReference<Windows.UI.Color> TabColor { get; };
|
||||
// void TaskbarProgressChanged();
|
||||
|
||||
Boolean ReadOnly { get; };
|
||||
void ToggleReadOnly();
|
||||
|
||||
@@ -199,7 +199,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
#pragma region IControlAccessibilityInfo
|
||||
COORD TermControlAutomationPeer::GetFontSize() const
|
||||
{
|
||||
return _termControl->GetActualFont().GetSize();
|
||||
return _termControl->GetFontSize();
|
||||
}
|
||||
|
||||
RECT TermControlAutomationPeer::GetBounds() const
|
||||
|
||||
@@ -28,12 +28,15 @@
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="EventArgs.h">
|
||||
<DependentUpon>EventArgs.idl</DependentUpon>
|
||||
<ClInclude Include="ControlCore.h">
|
||||
<DependentUpon>ControlCore.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="KeyChord.h">
|
||||
<DependentUpon>KeyChord.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="EventArgs.h">
|
||||
<DependentUpon>EventArgs.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SearchBoxControl.h">
|
||||
<DependentUpon>SearchBoxControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -54,6 +57,9 @@
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ControlCore.cpp">
|
||||
<DependentUpon>ControlCore.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="EventArgs.cpp">
|
||||
<DependentUpon>EventArgs.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
@@ -79,9 +85,11 @@
|
||||
</ItemGroup>
|
||||
<!-- ========================= idl Files ======================== -->
|
||||
<ItemGroup>
|
||||
<Midl Include="EventArgs.idl" />
|
||||
<Midl Include="KeyChord.idl" />
|
||||
<Midl Include="ControlCore.idl" />
|
||||
<Midl Include="ICoreState.idl" />
|
||||
<Midl Include="IDirectKeyListener.idl" />
|
||||
<Midl Include="KeyChord.idl" />
|
||||
<Midl Include="EventArgs.idl" />
|
||||
<Midl Include="IKeyBindings.idl" />
|
||||
<Midl Include="IControlSettings.idl" />
|
||||
<Midl Include="SearchBoxControl.idl">
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace Microsoft::Terminal::Core
|
||||
virtual bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) = 0;
|
||||
virtual bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0;
|
||||
|
||||
// void SendMouseEvent(uint row, uint col, KeyModifiers modifiers);
|
||||
[[nodiscard]] virtual HRESULT UserResize(const COORD size) noexcept = 0;
|
||||
virtual void UserScrollViewport(const int viewTop) = 0;
|
||||
virtual int GetScrollOffset() = 0;
|
||||
|
||||
@@ -1196,6 +1196,7 @@ void Terminal::UpdatePatterns() noexcept
|
||||
// visible region is changing
|
||||
void Terminal::ClearPatternTree() noexcept
|
||||
{
|
||||
auto lock = LockForWriting();
|
||||
auto oldTree = _patternIntervalTree;
|
||||
_patternIntervalTree = {};
|
||||
_InvalidatePatternTree(oldTree);
|
||||
|
||||
@@ -151,8 +151,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{ ShortcutAction::ToggleCommandPalette, ToggleCommandPaletteArgs::FromJson },
|
||||
{ ShortcutAction::FindMatch, FindMatchArgs::FromJson },
|
||||
{ ShortcutAction::NewWindow, NewWindowArgs::FromJson },
|
||||
{ ShortcutAction::PrevTab, PrevTabArgs::FromJson },
|
||||
{ ShortcutAction::NextTab, NextTabArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::Invalid, nullptr },
|
||||
};
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
#include "FindMatchArgs.g.cpp"
|
||||
#include "ToggleCommandPaletteArgs.g.cpp"
|
||||
#include "NewWindowArgs.g.cpp"
|
||||
#include "PrevTabArgs.g.cpp"
|
||||
#include "NextTabArgs.g.cpp"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
@@ -536,26 +534,4 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
fmt::format(L"{}, {}", RS_(L"NewWindowCommandKey"), newTerminalArgsStr)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring PrevTabArgs::GenerateName() const
|
||||
{
|
||||
if (!_SwitcherMode)
|
||||
{
|
||||
return RS_(L"PrevTabCommandKey");
|
||||
}
|
||||
|
||||
const auto mode = _SwitcherMode.Value() == TabSwitcherMode::MostRecentlyUsed ? L"most recently used" : L"in order";
|
||||
return winrt::hstring(fmt::format(L"{}, {}", RS_(L"PrevTabCommandKey"), mode));
|
||||
}
|
||||
|
||||
winrt::hstring NextTabArgs::GenerateName() const
|
||||
{
|
||||
if (!_SwitcherMode)
|
||||
{
|
||||
return RS_(L"NextTabCommandKey");
|
||||
}
|
||||
|
||||
const auto mode = _SwitcherMode.Value() == TabSwitcherMode::MostRecentlyUsed ? L"most recently used" : L"in order";
|
||||
return winrt::hstring(fmt::format(L"{}, {}", RS_(L"NextTabCommandKey"), mode));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
#include "ToggleCommandPaletteArgs.g.h"
|
||||
#include "FindMatchArgs.g.h"
|
||||
#include "NewWindowArgs.g.h"
|
||||
#include "PrevTabArgs.g.h"
|
||||
#include "NextTabArgs.g.h"
|
||||
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
#include "JsonUtils.h"
|
||||
@@ -926,71 +924,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
};
|
||||
|
||||
struct PrevTabArgs : public PrevTabArgsT<PrevTabArgs>
|
||||
{
|
||||
PrevTabArgs() = default;
|
||||
WINRT_PROPERTY(Windows::Foundation::IReference<TabSwitcherMode>, SwitcherMode, nullptr);
|
||||
static constexpr std::string_view SwitcherModeKey{ "tabSwitcherMode" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<PrevTabArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_SwitcherMode == _SwitcherMode;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<PrevTabArgs>();
|
||||
JsonUtils::GetValueForKey(json, SwitcherModeKey, args->_SwitcherMode);
|
||||
return { *args, {} };
|
||||
}
|
||||
IActionArgs Copy() const
|
||||
{
|
||||
auto copy{ winrt::make_self<PrevTabArgs>() };
|
||||
copy->_SwitcherMode = _SwitcherMode;
|
||||
return *copy;
|
||||
}
|
||||
};
|
||||
|
||||
struct NextTabArgs : public NextTabArgsT<NextTabArgs>
|
||||
{
|
||||
NextTabArgs() = default;
|
||||
WINRT_PROPERTY(Windows::Foundation::IReference<TabSwitcherMode>, SwitcherMode, nullptr);
|
||||
static constexpr std::string_view SwitcherModeKey{ "tabSwitcherMode" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<NextTabArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_SwitcherMode == _SwitcherMode;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<NextTabArgs>();
|
||||
JsonUtils::GetValueForKey(json, SwitcherModeKey, args->_SwitcherMode);
|
||||
return { *args, {} };
|
||||
}
|
||||
IActionArgs Copy() const
|
||||
{
|
||||
auto copy{ winrt::make_self<NextTabArgs>() };
|
||||
copy->_SwitcherMode = _SwitcherMode;
|
||||
return *copy;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
|
||||
@@ -77,13 +77,6 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
CommandLine
|
||||
};
|
||||
|
||||
enum TabSwitcherMode
|
||||
{
|
||||
MostRecentlyUsed,
|
||||
InOrder,
|
||||
Disabled,
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass NewTerminalArgs {
|
||||
NewTerminalArgs();
|
||||
NewTerminalArgs(Int32 profileIndex);
|
||||
@@ -233,14 +226,4 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
NewWindowArgs(NewTerminalArgs terminalArgs);
|
||||
NewTerminalArgs TerminalArgs { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass PrevTabArgs : IActionArgs
|
||||
{
|
||||
Windows.Foundation.IReference<TabSwitcherMode> SwitcherMode;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass NextTabArgs : IActionArgs
|
||||
{
|
||||
Windows.Foundation.IReference<TabSwitcherMode> SwitcherMode;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,6 +28,13 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
MaximizedFocusMode,
|
||||
};
|
||||
|
||||
enum TabSwitcherMode
|
||||
{
|
||||
MostRecentlyUsed,
|
||||
InOrder,
|
||||
Disabled,
|
||||
};
|
||||
|
||||
enum WindowingMode
|
||||
{
|
||||
UseNew,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>onecoreuap_apiset.lib;d3dcompiler.lib;dwmapi.lib;uxtheme.lib;shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>onecoreuap_apiset.lib;d3dcompiler.lib;dwmapi.lib;uxtheme.lib;shlwapi.lib;ntdll.lib;dcomp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
|
||||
@@ -106,4 +106,4 @@
|
||||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
<Import Project="$(SolutionDir)src\common.build.tests.props" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -111,9 +111,6 @@
|
||||
<ClCompile Include="ObjectTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ConptyOutputTests.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="UnicodeLiteral.hpp">
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <memory_resource>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <shared_mutex>
|
||||
#include <new>
|
||||
#include <optional>
|
||||
@@ -95,7 +94,6 @@
|
||||
|
||||
// {fmt}, a C++20-compatible formatting library
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/compile.h>
|
||||
|
||||
#define USE_INTERVAL_TREE_NAMESPACE
|
||||
#include <IntervalTree.h>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "til/point.h"
|
||||
#include "til/operators.h"
|
||||
#include "til/rectangle.h"
|
||||
#include "til/rle.h"
|
||||
#include "til/bitmap.h"
|
||||
#include "til/u8u16convert.h"
|
||||
#include "til/spsc.h"
|
||||
|
||||
1161
src/inc/til/rle.h
1161
src/inc/til/rle.h
File diff suppressed because it is too large
Load Diff
@@ -79,6 +79,7 @@ DxEngine::DxEngine() :
|
||||
_backgroundColor{ 0 },
|
||||
_selectionBackground{},
|
||||
_haveDeviceResources{ false },
|
||||
_swapChainHandle{ INVALID_HANDLE_VALUE },
|
||||
_swapChainDesc{ 0 },
|
||||
_swapChainFrameLatencyWaitableObject{ INVALID_HANDLE_VALUE },
|
||||
_recreateDeviceRequested{ false },
|
||||
@@ -618,6 +619,13 @@ try
|
||||
}
|
||||
case SwapChainMode::ForComposition:
|
||||
{
|
||||
if (!_swapChainHandle)
|
||||
{
|
||||
RETURN_IF_FAILED(DCompositionCreateSurfaceHandle(GENERIC_ALL, nullptr, &_swapChainHandle));
|
||||
}
|
||||
|
||||
RETURN_IF_FAILED(_dxgiFactory2.As(&_dxgiFactoryMedia));
|
||||
|
||||
// Use the given target size for compositions.
|
||||
_swapChainDesc.Width = _displaySizePixels.width<UINT>();
|
||||
_swapChainDesc.Height = _displaySizePixels.height<UINT>();
|
||||
@@ -627,10 +635,11 @@ try
|
||||
// It's 100% required to use scaling mode stretch for composition. There is no other choice.
|
||||
_swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
|
||||
|
||||
RETURN_IF_FAILED(_dxgiFactory2->CreateSwapChainForComposition(_d3dDevice.Get(),
|
||||
&_swapChainDesc,
|
||||
nullptr,
|
||||
&_dxgiSwapChain));
|
||||
RETURN_IF_FAILED(_dxgiFactoryMedia->CreateSwapChainForCompositionSurfaceHandle(_d3dDevice.Get(),
|
||||
_swapChainHandle.get(),
|
||||
&_swapChainDesc,
|
||||
nullptr,
|
||||
&_dxgiSwapChain));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -1003,14 +1012,14 @@ try
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
HANDLE DxEngine::GetSwapChainHandle()
|
||||
{
|
||||
if (_dxgiSwapChain.Get() == nullptr)
|
||||
if (!_swapChainHandle)
|
||||
{
|
||||
THROW_IF_FAILED(_CreateDeviceResources(true));
|
||||
}
|
||||
|
||||
return _dxgiSwapChain;
|
||||
return _swapChainHandle.get();
|
||||
}
|
||||
|
||||
void DxEngine::_InvalidateRectangle(const til::rectangle& rc)
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
void SetSoftwareRendering(bool enable) noexcept;
|
||||
|
||||
::Microsoft::WRL::ComPtr<IDXGISwapChain1> GetSwapChain();
|
||||
HANDLE GetSwapChainHandle();
|
||||
|
||||
// IRenderEngine Members
|
||||
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
@@ -128,6 +128,8 @@ namespace Microsoft::Console::Render
|
||||
void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept;
|
||||
void SetDefaultTextBackgroundOpacity(const float opacity) noexcept;
|
||||
|
||||
wil::unique_handle _swapChainHandle;
|
||||
|
||||
void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept;
|
||||
|
||||
protected:
|
||||
@@ -210,6 +212,7 @@ namespace Microsoft::Console::Render
|
||||
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> _d2dBrushBackground;
|
||||
|
||||
::Microsoft::WRL::ComPtr<IDXGIFactory2> _dxgiFactory2;
|
||||
::Microsoft::WRL::ComPtr<IDXGIFactoryMedia> _dxgiFactoryMedia;
|
||||
::Microsoft::WRL::ComPtr<IDXGIDevice> _dxgiDevice;
|
||||
::Microsoft::WRL::ComPtr<IDXGISurface> _dxgiSurface;
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include <typeinfo>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <dcomp.h>
|
||||
|
||||
#include <dxgi.h>
|
||||
#include <dxgi1_2.h>
|
||||
#include <dxgi1_3.h>
|
||||
|
||||
@@ -472,8 +472,9 @@ using namespace Microsoft::Console::Render;
|
||||
// send the auto-assigned ID, prefixed with the PID of this session
|
||||
// (we do this so different conpty sessions do not overwrite each other's hyperlinks)
|
||||
const auto sessionID = GetCurrentProcessId();
|
||||
const std::string fmt{ "\x1b]8;id={}-{};{}\x1b\\" };
|
||||
const std::string uri_str{ til::u16u8(uri) };
|
||||
auto s = fmt::format(FMT_COMPILE("\x1b]8;id={}-{};{}\x1b\\"), sessionID, numberId, uri_str);
|
||||
auto s = fmt::format(fmt, sessionID, numberId, uri_str);
|
||||
return _Write(s);
|
||||
}
|
||||
else
|
||||
@@ -481,9 +482,10 @@ using namespace Microsoft::Console::Render;
|
||||
// This is the case of user-defined IDs:
|
||||
// send the user-defined ID, prefixed with a "u"
|
||||
// (we do this so no application can accidentally override a user defined ID)
|
||||
const std::string fmt{ "\x1b]8;id=u-{};{}\x1b\\" };
|
||||
const std::string uri_str{ til::u16u8(uri) };
|
||||
const std::string customId_str{ til::u16u8(customId) };
|
||||
auto s = fmt::format(FMT_COMPILE("\x1b]8;id=u-{};{}\x1b\\"), customId_str, uri_str);
|
||||
auto s = fmt::format(fmt, customId_str, uri_str);
|
||||
return _Write(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,7 +784,7 @@ std::wstring TerminalInput::_GenerateWin32KeySequence(const KeyEvent& key)
|
||||
// Kd: the value of bKeyDown - either a '0' or '1'. If omitted, defaults to '0'.
|
||||
// Cs: the value of dwControlKeyState - any number. If omitted, defaults to '0'.
|
||||
// Rc: the value of wRepeatCount - any number. If omitted, defaults to '1'.
|
||||
return fmt::format(FMT_COMPILE(L"\x1b[{};{};{};{};{};{}_"),
|
||||
return fmt::format(L"\x1b[{};{};{};{};{};{}_",
|
||||
key.GetVirtualKeyCode(),
|
||||
key.GetVirtualScanCode(),
|
||||
static_cast<int>(key.GetCharData()),
|
||||
|
||||
@@ -1,744 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "til/rle.h"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
class RunLengthEncodingTests
|
||||
{
|
||||
TEST_CLASS(RunLengthEncodingTests);
|
||||
|
||||
// NOTE: In some cases, these tests are also about ensuring that the various scenarios
|
||||
// for template usage can compile correctly and will have minimal exercised functionality
|
||||
// at unit test runtime.
|
||||
|
||||
TEST_METHOD(ConstructEmpty)
|
||||
{
|
||||
til::rle<unsigned int> rle;
|
||||
VERIFY_ARE_EQUAL(0, rle.size());
|
||||
VERIFY_ARE_EQUAL(rle.cbegin(), rle.cend());
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstructDefaultLength)
|
||||
{
|
||||
til::rle<unsigned int> rle(86, 9);
|
||||
VERIFY_ARE_EQUAL(86, rle.size());
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstructVerySmall)
|
||||
{
|
||||
const til::rle<unsigned short, unsigned char> rle(12, 37);
|
||||
VERIFY_ARE_EQUAL(12, rle.size());
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstructWithMinimumLoadSize)
|
||||
{
|
||||
const til::rle<unsigned short, unsigned short> def;
|
||||
const til::rle<unsigned short, unsigned short, 3> bigger;
|
||||
VERIFY_IS_GREATER_THAN(sizeof(bigger), sizeof(def));
|
||||
}
|
||||
|
||||
TEST_METHOD(Size)
|
||||
{
|
||||
const til::rle<unsigned short> rle(19, 12);
|
||||
VERIFY_ARE_EQUAL(19, rle.size());
|
||||
}
|
||||
|
||||
TEST_METHOD(AtPos)
|
||||
{
|
||||
til::rle<int> rle(10, 10);
|
||||
rle.insert(3, 0, 4);
|
||||
rle.insert(7, 4, 2);
|
||||
rle.insert(11, 6, 3);
|
||||
rle.insert(4, 9, 1);
|
||||
|
||||
VERIFY_ARE_EQUAL(3, rle.at(0));
|
||||
VERIFY_ARE_EQUAL(3, rle.at(1));
|
||||
VERIFY_ARE_EQUAL(3, rle.at(2));
|
||||
VERIFY_ARE_EQUAL(3, rle.at(3));
|
||||
VERIFY_ARE_EQUAL(7, rle.at(4));
|
||||
VERIFY_ARE_EQUAL(7, rle.at(5));
|
||||
VERIFY_ARE_EQUAL(11, rle.at(6));
|
||||
VERIFY_ARE_EQUAL(11, rle.at(7));
|
||||
VERIFY_ARE_EQUAL(11, rle.at(8));
|
||||
VERIFY_ARE_EQUAL(4, rle.at(9));
|
||||
}
|
||||
|
||||
TEST_METHOD(AtPosApplies)
|
||||
{
|
||||
til::rle<int> rle(10, 10);
|
||||
rle.insert(3, 0, 4);
|
||||
rle.insert(7, 4, 2);
|
||||
rle.insert(11, 6, 3);
|
||||
rle.insert(4, 9, 1);
|
||||
|
||||
size_t appliesExpected = 4;
|
||||
size_t applies = 0;
|
||||
VERIFY_ARE_EQUAL(3, rle.at(0, applies));
|
||||
VERIFY_ARE_EQUAL(appliesExpected, applies);
|
||||
--appliesExpected;
|
||||
VERIFY_ARE_EQUAL(3, rle.at(1, applies));
|
||||
VERIFY_ARE_EQUAL(appliesExpected, applies);
|
||||
--appliesExpected;
|
||||
VERIFY_ARE_EQUAL(3, rle.at(2, applies));
|
||||
VERIFY_ARE_EQUAL(appliesExpected, applies);
|
||||
--appliesExpected;
|
||||
VERIFY_ARE_EQUAL(3, rle.at(3, applies));
|
||||
VERIFY_ARE_EQUAL(appliesExpected, applies);
|
||||
appliesExpected = 2;
|
||||
VERIFY_ARE_EQUAL(7, rle.at(4, applies));
|
||||
VERIFY_ARE_EQUAL(appliesExpected, applies);
|
||||
--appliesExpected;
|
||||
VERIFY_ARE_EQUAL(7, rle.at(5, applies));
|
||||
VERIFY_ARE_EQUAL(appliesExpected, applies);
|
||||
appliesExpected = 3;
|
||||
VERIFY_ARE_EQUAL(11, rle.at(6, applies));
|
||||
VERIFY_ARE_EQUAL(appliesExpected, applies);
|
||||
--appliesExpected;
|
||||
VERIFY_ARE_EQUAL(11, rle.at(7, applies));
|
||||
VERIFY_ARE_EQUAL(appliesExpected, applies);
|
||||
--appliesExpected;
|
||||
VERIFY_ARE_EQUAL(11, rle.at(8, applies));
|
||||
VERIFY_ARE_EQUAL(appliesExpected, applies);
|
||||
appliesExpected = 1;
|
||||
VERIFY_ARE_EQUAL(4, rle.at(9, applies));
|
||||
VERIFY_ARE_EQUAL(appliesExpected, applies);
|
||||
}
|
||||
|
||||
TEST_METHOD(Substr)
|
||||
{
|
||||
til::rle<int> rle(10, 10);
|
||||
rle.insert(3, 0, 4);
|
||||
rle.insert(7, 4, 2);
|
||||
rle.insert(11, 6, 3);
|
||||
rle.insert(4, 9, 1);
|
||||
|
||||
// 3 3 3 3 7 7 11 11 11 4
|
||||
|
||||
Log::Comment(L"1.) Nothing substring should match original.");
|
||||
{
|
||||
til::rle<int> expected(10, 10);
|
||||
expected = rle;
|
||||
// 3 3 3 3 7 7 11 11 11 4
|
||||
|
||||
const auto actual = rle.substr();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
Log::Comment(L"2.) Offset substring to implicit end.");
|
||||
{
|
||||
til::rle<int> expected(7, 10);
|
||||
expected.insert(3, 0, 1);
|
||||
expected.insert(7, 1, 2);
|
||||
expected.insert(11, 3, 3);
|
||||
expected.insert(4, 6, 1);
|
||||
|
||||
// 3 7 7 11 11 11 4
|
||||
|
||||
const auto actual = rle.substr(3);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
Log::Comment(L"3.) Substring cutting out middle bit.");
|
||||
{
|
||||
til::rle<int> expected(4, 4);
|
||||
expected.insert(7, 0, 1);
|
||||
expected.insert(11, 1, 3);
|
||||
|
||||
// 7 11 11 11
|
||||
|
||||
const auto actual = rle.substr(5, 4);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_METHOD(Replace)
|
||||
{
|
||||
til::rle<int> actual(20, 10);
|
||||
actual.insert(3, 0, 4);
|
||||
actual.insert(7, 4, 2);
|
||||
actual.insert(11, 6, 3);
|
||||
actual.insert(4, 9, 1);
|
||||
actual.insert(7, 10, 5);
|
||||
actual.insert(11, 15, 2);
|
||||
actual.insert(9, 17, 3);
|
||||
|
||||
actual.replace(7, 49);
|
||||
actual.replace(9, 81);
|
||||
actual.replace(3, 9);
|
||||
|
||||
til::rle<int> expected(20, 10);
|
||||
expected.insert(9, 0, 4);
|
||||
expected.insert(49, 4, 2);
|
||||
expected.insert(11, 6, 3);
|
||||
expected.insert(4, 9, 1);
|
||||
expected.insert(49, 10, 5);
|
||||
expected.insert(11, 15, 2);
|
||||
expected.insert(81, 17, 3);
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(ResizeShrink)
|
||||
{
|
||||
til::rle<int> actual(10, 10);
|
||||
actual.insert(3, 0, 4);
|
||||
actual.insert(7, 4, 2);
|
||||
actual.insert(11, 6, 3);
|
||||
actual.insert(4, 9, 1);
|
||||
|
||||
// 3 3 3 3 7 7 11 11 11 4
|
||||
// 3 for 4, 7 for 2, 11 for 3, 4 for 1.
|
||||
|
||||
til::rle<int> expected(7, 10);
|
||||
expected.insert(3, 0, 4);
|
||||
expected.insert(7, 4, 2);
|
||||
expected.insert(11, 6, 1);
|
||||
|
||||
// 3 3 3 3 7 7 11
|
||||
// 3 for 4, 7 for 2, 11 for 1
|
||||
|
||||
actual.resize(7);
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(ResizeGrow)
|
||||
{
|
||||
til::rle<int> actual(10, 10);
|
||||
actual.insert(3, 0, 4);
|
||||
actual.insert(7, 4, 2);
|
||||
actual.insert(11, 6, 3);
|
||||
actual.insert(4, 9, 1);
|
||||
|
||||
// 3 3 3 3 7 7 11 11 11 4
|
||||
// 3 for 4, 7 for 2, 11 for 3, 4 for 1.
|
||||
|
||||
til::rle<int> expected(13, 10);
|
||||
expected.insert(3, 0, 4);
|
||||
expected.insert(7, 4, 2);
|
||||
expected.insert(11, 6, 3);
|
||||
expected.insert(4, 9, 4);
|
||||
|
||||
// 3 3 3 3 7 7 11 11 11 4 4 4 4
|
||||
// 3 for 4, 7 for 2, 11 for 3, 4 for 4.
|
||||
|
||||
actual.resize(13);
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(FillAll)
|
||||
{
|
||||
til::rle<int> actual(10, 10);
|
||||
actual.insert(3, 0, 4);
|
||||
actual.insert(7, 4, 2);
|
||||
actual.insert(11, 6, 3);
|
||||
actual.insert(4, 9, 1);
|
||||
actual.fill(20);
|
||||
|
||||
til::rle<int> expected(10, 20);
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(FillFrom)
|
||||
{
|
||||
til::rle<int> actual(10, 10);
|
||||
actual.insert(3, 0, 4);
|
||||
actual.insert(7, 4, 2);
|
||||
actual.insert(11, 6, 3);
|
||||
actual.insert(4, 9, 1);
|
||||
actual.fill(20, 2);
|
||||
|
||||
til::rle<int> expected(10, 20);
|
||||
expected.insert(3, 0, 2);
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(Insert)
|
||||
{
|
||||
til::rle<int> actual(10, 10);
|
||||
actual.insert(4, 9); // insert single, implicit length
|
||||
|
||||
til::rle<int> expected(10, 4);
|
||||
expected.insert(10, 0, 9); // insert multiple, say length
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(Assign)
|
||||
{
|
||||
til::rle<int> actual(10, 10);
|
||||
|
||||
// Prep initial buffer.
|
||||
// 10 10 10 10 10 10 10 10 10 10
|
||||
|
||||
// Make something that can hold a span of pairs to assign in bulk.
|
||||
std::vector<std::pair<int, size_t>> items;
|
||||
items.push_back({ 400, 2 });
|
||||
items.push_back({ 20, 3 });
|
||||
|
||||
// 400 400 20 20 20
|
||||
|
||||
// If we assign this to the front, we expect
|
||||
// 400 400 20 20 20 10 10 10 10 10
|
||||
til::rle<int> expected(10, 10);
|
||||
expected.insert(400, 0, 2);
|
||||
expected.insert(20, 2, 3);
|
||||
|
||||
actual.assign(items.cbegin(), items.cend());
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Now try assigning it again part way in.
|
||||
// Our new expectation building on the last one if we assign at
|
||||
// index 3 would be
|
||||
// 400 400 20 400 400 20 20 20 10 10
|
||||
expected.insert(400, 3, 2);
|
||||
expected.insert(20, 5, 3);
|
||||
|
||||
actual.assign(items.cbegin(), items.cend(), 3);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(Equal)
|
||||
{
|
||||
til::rle<int> actual(10, 10);
|
||||
til::rle<int> expected(10, 10);
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(NotEqualValue)
|
||||
{
|
||||
til::rle<int> actual(10, 9);
|
||||
til::rle<int> expected(10, 10);
|
||||
|
||||
VERIFY_ARE_NOT_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(NotEqualLength)
|
||||
{
|
||||
til::rle<int> actual(5, 10);
|
||||
til::rle<int> expected(10, 10);
|
||||
|
||||
VERIFY_ARE_NOT_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(IteratorIncDecOnes)
|
||||
{
|
||||
til::rle<int> rle(10, 1);
|
||||
rle.insert(2, 0, 2);
|
||||
rle.insert(3, 2, 3);
|
||||
rle.insert(4, 5, 4);
|
||||
|
||||
// test array should be 2 2 3 3 3 4 4 4 4 1
|
||||
// or 2 for 2, 3 for 3, 4 for 4, 1 for 1.
|
||||
|
||||
Log::Comment(L"Increment by 1s.");
|
||||
|
||||
auto it = rle.begin();
|
||||
for (auto i = 0; i < 2; ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(2, *it);
|
||||
++it;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 3; ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(3, *it);
|
||||
++it;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 4; ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(4, *it);
|
||||
++it;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 1; ++i)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(1, *it);
|
||||
++it;
|
||||
}
|
||||
|
||||
VERIFY_ARE_EQUAL(rle.end(), it);
|
||||
|
||||
Log::Comment(L"Decrement by 1s.");
|
||||
|
||||
for (auto i = 0; i < 1; ++i)
|
||||
{
|
||||
--it;
|
||||
VERIFY_ARE_EQUAL(1, *it);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 4; ++i)
|
||||
{
|
||||
--it;
|
||||
VERIFY_ARE_EQUAL(4, *it);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 3; ++i)
|
||||
{
|
||||
--it;
|
||||
VERIFY_ARE_EQUAL(3, *it);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 2; ++i)
|
||||
{
|
||||
--it;
|
||||
VERIFY_ARE_EQUAL(2, *it);
|
||||
}
|
||||
|
||||
VERIFY_ARE_EQUAL(rle.begin(), it);
|
||||
}
|
||||
|
||||
struct TestStruct
|
||||
{
|
||||
int a;
|
||||
int b;
|
||||
|
||||
[[nodiscard]] bool operator==(const TestStruct& right) const noexcept
|
||||
{
|
||||
return a == right.a && b == right.b;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_METHOD(ConstIteratorReference)
|
||||
{
|
||||
const TestStruct expected{ 3, 2 };
|
||||
|
||||
til::rle<TestStruct> rle(5, expected);
|
||||
|
||||
const TestStruct actual = *rle.cbegin();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorPointer)
|
||||
{
|
||||
const TestStruct expected{ 3, 2 };
|
||||
|
||||
til::rle<TestStruct> rle(5, expected);
|
||||
|
||||
const auto it = rle.cbegin();
|
||||
|
||||
const TestStruct actual{ it->a, it->b };
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorIncPrefix)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
rle.insert(7, 1, 1);
|
||||
|
||||
// 2 7 2 2 2
|
||||
|
||||
auto it = rle.cbegin();
|
||||
++it;
|
||||
VERIFY_ARE_EQUAL(7, *it);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorIncPostfix)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
rle.insert(7, 1, 1);
|
||||
|
||||
// 2 7 2 2 2
|
||||
|
||||
auto it = rle.cbegin();
|
||||
auto prevIt = it++;
|
||||
VERIFY_ARE_EQUAL(7, *it);
|
||||
VERIFY_ARE_EQUAL(2, *prevIt);
|
||||
VERIFY_ARE_NOT_EQUAL(it, prevIt);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorDecPrefix)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
rle.insert(7, 4, 1);
|
||||
|
||||
// 2 2 2 2 7
|
||||
|
||||
auto it = rle.cend();
|
||||
--it;
|
||||
VERIFY_ARE_EQUAL(7, *it);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorDecPostfix)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
rle.insert(7, 4, 1);
|
||||
|
||||
// 2 2 2 2 7
|
||||
|
||||
auto it = rle.cend();
|
||||
auto prevIt = it--;
|
||||
VERIFY_ARE_EQUAL(7, *it);
|
||||
VERIFY_ARE_EQUAL(rle.cend(), prevIt);
|
||||
VERIFY_ARE_NOT_EQUAL(it, prevIt);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorPlusEquals)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
rle.insert(7, 2, 1);
|
||||
|
||||
// 2 2 7 2 2
|
||||
|
||||
auto it = rle.cbegin();
|
||||
it += 2;
|
||||
VERIFY_ARE_EQUAL(7, *it);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorPlusOffset)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
rle.insert(7, 2, 1);
|
||||
|
||||
// 2 2 7 2 2
|
||||
|
||||
auto it = rle.cbegin();
|
||||
auto itAfter = it + 2;
|
||||
VERIFY_ARE_EQUAL(7, *itAfter);
|
||||
VERIFY_ARE_NOT_EQUAL(it, itAfter);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorMinusEquals)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
rle.insert(7, 3, 1);
|
||||
|
||||
// 2 2 2 7 2
|
||||
|
||||
auto it = rle.cend();
|
||||
it -= 2;
|
||||
VERIFY_ARE_EQUAL(7, *it);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorMinusOffset)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
rle.insert(7, 3, 1);
|
||||
auto it = rle.cend();
|
||||
auto itAfter = it - 2;
|
||||
VERIFY_ARE_EQUAL(7, *itAfter);
|
||||
VERIFY_ARE_NOT_EQUAL(it, itAfter);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorDifference)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
|
||||
const ptrdiff_t expected = 5;
|
||||
VERIFY_ARE_EQUAL(expected, rle.cend() - rle.cbegin());
|
||||
VERIFY_ARE_EQUAL(-expected, rle.cbegin() - rle.cend());
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorArrayOffset)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
rle.insert(7, 2, 1);
|
||||
const auto it = rle.cbegin();
|
||||
VERIFY_ARE_EQUAL(2, it[0]);
|
||||
VERIFY_ARE_EQUAL(2, it[1]);
|
||||
VERIFY_ARE_EQUAL(7, it[2]);
|
||||
VERIFY_ARE_EQUAL(2, it[3]);
|
||||
VERIFY_ARE_EQUAL(2, it[4]);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorEquality)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
|
||||
auto begin = rle.cbegin();
|
||||
auto end = rle.cend();
|
||||
end -= 5;
|
||||
VERIFY_IS_TRUE(begin == end);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorInequality)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
|
||||
auto begin = rle.cbegin();
|
||||
auto end = rle.cend();
|
||||
VERIFY_IS_TRUE(begin != end);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorLessThan)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
|
||||
auto begin = rle.cbegin();
|
||||
auto end = rle.cend();
|
||||
auto begin2 = end - 5;
|
||||
VERIFY_IS_TRUE(begin < end);
|
||||
VERIFY_IS_FALSE(end < begin);
|
||||
VERIFY_IS_FALSE(begin < begin2);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorGreaterThan)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
|
||||
auto begin = rle.cbegin();
|
||||
auto end = rle.cend();
|
||||
auto begin2 = end - 5;
|
||||
VERIFY_IS_FALSE(begin > end);
|
||||
VERIFY_IS_TRUE(end > begin);
|
||||
VERIFY_IS_FALSE(begin > begin2);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorLessThanEqual)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
|
||||
auto begin = rle.cbegin();
|
||||
auto end = rle.cend();
|
||||
auto begin2 = end - 5;
|
||||
VERIFY_IS_TRUE(begin <= end);
|
||||
VERIFY_IS_FALSE(end <= begin);
|
||||
VERIFY_IS_TRUE(begin <= begin2);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstIteratorGreaterThanEqual)
|
||||
{
|
||||
til::rle<int> rle(5, 2);
|
||||
|
||||
auto begin = rle.cbegin();
|
||||
auto end = rle.cend();
|
||||
auto begin2 = end - 5;
|
||||
VERIFY_IS_FALSE(begin >= end);
|
||||
VERIFY_IS_TRUE(end >= begin);
|
||||
VERIFY_IS_TRUE(begin >= begin2);
|
||||
}
|
||||
|
||||
TEST_METHOD(ConstReverseIterate)
|
||||
{
|
||||
til::rle<int> rle(5, 5);
|
||||
rle.insert(1, 0, 1);
|
||||
rle.insert(2, 1, 1);
|
||||
rle.insert(3, 2, 1);
|
||||
rle.insert(4, 3, 1);
|
||||
|
||||
// 1 2 3 4 5
|
||||
|
||||
auto rit = rle.crbegin();
|
||||
for (int i = 5; i > 0; i--)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(i, *rit);
|
||||
rit++;
|
||||
}
|
||||
|
||||
VERIFY_ARE_EQUAL(rle.crend(), rit);
|
||||
}
|
||||
|
||||
//TEST_METHOD(NonConstIterators)
|
||||
//{
|
||||
// til::rle<int> rle(5, 5);
|
||||
|
||||
// auto iter = rle.begin();
|
||||
// *iter++ = 1;
|
||||
// *iter++ = 2;
|
||||
// *iter++ = 3;
|
||||
// *iter++ = 4;
|
||||
|
||||
// VERIFY_ARE_EQUAL(1, rle.at(0));
|
||||
// VERIFY_ARE_EQUAL(2, rle.at(1));
|
||||
// VERIFY_ARE_EQUAL(3, rle.at(2));
|
||||
// VERIFY_ARE_EQUAL(4, rle.at(3));
|
||||
// VERIFY_ARE_EQUAL(5, rle.at(4));
|
||||
|
||||
// VERIFY_ARE_EQUAL(rle.end(), iter);
|
||||
|
||||
// auto reverseIter = rle.crbegin();
|
||||
// VERIFY_ARE_EQUAL(5, *reverseIter++);
|
||||
// VERIFY_ARE_EQUAL(4, *reverseIter++);
|
||||
// VERIFY_ARE_EQUAL(3, *reverseIter++);
|
||||
// VERIFY_ARE_EQUAL(2, *reverseIter++);
|
||||
// VERIFY_ARE_EQUAL(1, *reverseIter++);
|
||||
|
||||
// VERIFY_ARE_EQUAL(rle.crend(), reverseIter);
|
||||
//}
|
||||
|
||||
TEST_METHOD(IteratorIncDecMultiple)
|
||||
{
|
||||
til::rle<int> rle(10, 1);
|
||||
rle.insert(2, 0, 2);
|
||||
rle.insert(3, 2, 3);
|
||||
rle.insert(4, 5, 4);
|
||||
|
||||
// test array should be 2 2 3 3 3 4 4 4 4 1
|
||||
// or 2 for 2, 3 for 3, 4 for 4, 1 for 1.
|
||||
|
||||
auto it = rle.begin();
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// ^
|
||||
VERIFY_ARE_EQUAL(2, *it, L"Check we're sitting on the first of the first run.");
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// ^->
|
||||
++it;
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// ^
|
||||
VERIFY_ARE_EQUAL(2, *it, L"Move a spot into the run and ensure we're still on the same one.");
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// ^----->
|
||||
it += 3;
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// ^
|
||||
VERIFY_ARE_EQUAL(3, *it, L"Jump forward by 3 and we should still be on the second run of 3s.");
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// ^------->
|
||||
it += 4;
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// ^
|
||||
VERIFY_ARE_EQUAL(4, *it, L"Jump forward by 4 and we should still be on the third run of 4s.");
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// ^--->
|
||||
it += 2;
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// ^
|
||||
VERIFY_ARE_EQUAL(rle.end(), it, L"Jump past the last run of 1 for 1 to what should be the end.");
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// <-----^
|
||||
it -= 3;
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// ^
|
||||
VERIFY_ARE_EQUAL(4, *it, L"Jump back by 3 and we should be in the middle of the 4s run.");
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// <-------^
|
||||
it -= 4;
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// ^
|
||||
VERIFY_ARE_EQUAL(3, *it, L"Jump back by 4 and we should be in the middle of the 3s run.");
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// <-----^
|
||||
it -= 3;
|
||||
|
||||
// 2 2 3 3 3 4 4 4 4 1
|
||||
// ^
|
||||
VERIFY_ARE_EQUAL(2, *it, L"Jump back by 3 and we should be at the beginning of the 2s run.");
|
||||
VERIFY_ARE_EQUAL(rle.begin(), it, L"And it should equal 'begin'");
|
||||
}
|
||||
};
|
||||
@@ -21,7 +21,6 @@ SOURCES = \
|
||||
PointTests.cpp \
|
||||
MathTests.cpp \
|
||||
RectangleTests.cpp \
|
||||
RunLengthEncodingTests.cpp \
|
||||
SizeTests.cpp \
|
||||
SomeTests.cpp \
|
||||
u8u16convertTests.cpp \
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<ClCompile Include="StaticMapTests.cpp" />
|
||||
<ClCompile Include="MathTests.cpp" />
|
||||
<ClCompile Include="RectangleTests.cpp" />
|
||||
<ClCompile Include="RunLengthEncodingTests.cpp" />
|
||||
<ClCompile Include="SizeTests.cpp" />
|
||||
<ClCompile Include="ColorTests.cpp" />
|
||||
<ClCompile Include="CoalesceTests.cpp" />
|
||||
|
||||
@@ -97,29 +97,4 @@
|
||||
<Type Name="til::color">
|
||||
<DisplayString>{{RGB: {(int)r,d}, {(int)g,d}, {(int)b,d}; α: {(int)a,d}}}</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="til::rle<*>">
|
||||
<DisplayString>{{Size: {_size,d}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[size]">_size</Item>
|
||||
<ArrayItems>
|
||||
<Size>_list.m_holder.m_size</Size>
|
||||
<ValuePointer>_list.m_holder.m_start</ValuePointer>
|
||||
</ArrayItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="til::details::rle_const_iterator<*>">
|
||||
<DisplayString>{{ Run of {_it.m_ptr->first,d} for {_it.m_ptr->second,d} at {_usage,d}}}</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="boost::container::small_vector<*>">
|
||||
<Expand>
|
||||
<Item Name="[size]">m_holder.m_size</Item>
|
||||
<ArrayItems>
|
||||
<Size>m_holder.m_size</Size>
|
||||
<ValuePointer>m_holder.m_start</ValuePointer>
|
||||
</ArrayItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
</AutoVisualizer>
|
||||
|
||||
Reference in New Issue
Block a user