Add support for autodetecting URLs and making hyperlinks (#7691)
This pull request is the initial implementation of hyperlink auto detection Overall design: - Upon startup, TerminalCore gives the TextBuffer some patterns it should know about - Whenever something in the viewport changes (i.e. text output/scrolling), TerminalControl tells TerminalCore (through a throttled function for performance) to retrieve the visible pattern locations from the TextBuffer - When the renderer encounters a region that is associated with a pattern, it paints that region differently References #5001 Closes #574
This commit is contained in:
parent
8e3f27f8fb
commit
2bf5d18c84
|
@ -53,6 +53,7 @@ rx
|
||||||
serializer
|
serializer
|
||||||
SIZENS
|
SIZENS
|
||||||
spsc
|
spsc
|
||||||
|
sregex
|
||||||
STDCPP
|
STDCPP
|
||||||
strchr
|
strchr
|
||||||
syscall
|
syscall
|
||||||
|
@ -62,6 +63,7 @@ tx
|
||||||
UPDATEINIFILE
|
UPDATEINIFILE
|
||||||
userenv
|
userenv
|
||||||
wcstoui
|
wcstoui
|
||||||
|
wsregex
|
||||||
XDocument
|
XDocument
|
||||||
XElement
|
XElement
|
||||||
XParse
|
XParse
|
||||||
|
|
|
@ -152433,6 +152433,7 @@ ft-lb
|
||||||
ftncmd
|
ftncmd
|
||||||
ftnerr
|
ftnerr
|
||||||
FTP
|
FTP
|
||||||
|
ftp
|
||||||
ft-pdl
|
ft-pdl
|
||||||
FTPI
|
FTPI
|
||||||
FTS
|
FTS
|
||||||
|
|
|
@ -8,6 +8,7 @@ dhowett
|
||||||
Diviness
|
Diviness
|
||||||
dsafa
|
dsafa
|
||||||
duhowett
|
duhowett
|
||||||
|
ekg
|
||||||
ethanschoonover
|
ethanschoonover
|
||||||
Firefox
|
Firefox
|
||||||
Gatta
|
Gatta
|
||||||
|
|
2
.github/linters/.markdown-lint.yml
vendored
2
.github/linters/.markdown-lint.yml
vendored
|
@ -28,11 +28,13 @@ MD007:
|
||||||
indent: 2 # Unordered list indentation
|
indent: 2 # Unordered list indentation
|
||||||
MD013:
|
MD013:
|
||||||
line_length: 400 # Line length 80 is far to short
|
line_length: 400 # Line length 80 is far to short
|
||||||
|
MD024: false # Allow multiple headings with same content
|
||||||
MD026:
|
MD026:
|
||||||
punctuation: ".,;:!。,;:" # List of not allowed
|
punctuation: ".,;:!。,;:" # List of not allowed
|
||||||
MD029: false # Ordered list item prefix
|
MD029: false # Ordered list item prefix
|
||||||
MD033: false # Allow inline HTML
|
MD033: false # Allow inline HTML
|
||||||
MD036: false # Emphasis used instead of a heading
|
MD036: false # Emphasis used instead of a heading
|
||||||
|
MD040: false # Allow ``` blocks in md files with no language specified
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Rules by tags #
|
# Rules by tags #
|
||||||
|
|
43
NOTICE.md
43
NOTICE.md
|
@ -2,7 +2,7 @@
|
||||||
Do Not Translate or Localize
|
Do Not Translate or Localize
|
||||||
|
|
||||||
This software incorporates material from third parties. Microsoft makes certain
|
This software incorporates material from third parties. Microsoft makes certain
|
||||||
open source code available at http://3rdpartysource.microsoft.com, or you may
|
open source code available at [http://3rdpartysource.microsoft.com](http://3rdpartysource.microsoft.com), or you may
|
||||||
send a check or money order for US $5.00, including the product name, the open
|
send a check or money order for US $5.00, including the product name, the open
|
||||||
source component name, and version number, to:
|
source component name, and version number, to:
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ General Public License.
|
||||||
|
|
||||||
## jsoncpp
|
## jsoncpp
|
||||||
|
|
||||||
**Source**: https://github.com/open-source-parsers/jsoncpp
|
**Source**: [https://github.com/open-source-parsers/jsoncpp](https://github.com/open-source-parsers/jsoncpp)
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ SOFTWARE.
|
||||||
|
|
||||||
## chromium/base/numerics
|
## chromium/base/numerics
|
||||||
|
|
||||||
**Source**: https://github.com/chromium/chromium/tree/master/base/numerics
|
**Source**: [https://github.com/chromium/chromium/tree/master/base/numerics](https://github.com/chromium/chromium/tree/master/base/numerics)
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
## kimwalisch/libpopcnt
|
## kimwalisch/libpopcnt
|
||||||
|
|
||||||
**Source**: https://github.com/kimwalisch/libpopcnt
|
**Source**: [https://github.com/kimwalisch/libpopcnt](https://github.com/kimwalisch/libpopcnt)
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
## dynamic_bitset
|
## dynamic_bitset
|
||||||
|
|
||||||
**Source**: https://github.com/pinam45/dynamic_bitset
|
**Source**: [https://github.com/pinam45/dynamic_bitset](https://github.com/pinam45/dynamic_bitset)
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
@ -151,9 +151,9 @@ SOFTWARE.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## {fmt}
|
## \{fmt\}
|
||||||
|
|
||||||
**Source**: https://github.com/fmtlib/fmt
|
**Source**: [https://github.com/fmtlib/fmt](https://github.com/fmtlib/fmt)
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
@ -188,3 +188,32 @@ of this Software are embedded into a machine-executable object form of such
|
||||||
source code, you may redistribute such embedded portions in such object form
|
source code, you may redistribute such embedded portions in such object form
|
||||||
without including the above copyright and permission notices.
|
without including the above copyright and permission notices.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## interval_tree
|
||||||
|
|
||||||
|
**Source**: [https://github.com/ekg/IntervalTree](https://github.com/ekg/IntervalTree)
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
```
|
||||||
|
Copyright (c) 2011 Erik Garrison
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
434
oss/interval_tree/IntervalTree.h
Normal file
434
oss/interval_tree/IntervalTree.h
Normal file
|
@ -0,0 +1,434 @@
|
||||||
|
#ifndef __INTERVAL_TREE_H
|
||||||
|
#define __INTERVAL_TREE_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#ifdef USE_INTERVAL_TREE_NAMESPACE
|
||||||
|
namespace interval_tree
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
template<class Scalar, typename Value>
|
||||||
|
class Interval
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Scalar start;
|
||||||
|
Scalar stop;
|
||||||
|
Value value;
|
||||||
|
Interval(const Scalar& s, const Scalar& e, const Value& v) :
|
||||||
|
start(std::min(s, e)), stop(std::max(s, e)), value(v)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Interval()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator==(const Interval& other) const noexcept
|
||||||
|
{
|
||||||
|
return start == other.start &&
|
||||||
|
stop == other.stop &&
|
||||||
|
value == other.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator!=(const Interval& other) const noexcept
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Scalar, typename Value>
|
||||||
|
Value intervalStart(const Interval<Scalar, Value>& i)
|
||||||
|
{
|
||||||
|
return i.start;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Scalar, typename Value>
|
||||||
|
Value intervalStop(const Interval<Scalar, Value>& i)
|
||||||
|
{
|
||||||
|
return i.stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Scalar, typename Value>
|
||||||
|
std::ostream& operator<<(std::ostream& out, const Interval<Scalar, Value>& i)
|
||||||
|
{
|
||||||
|
out << "Interval(" << i.start << ", " << i.stop << "): " << i.value;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Scalar, class Value>
|
||||||
|
class IntervalTree
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef Interval<Scalar, Value> interval;
|
||||||
|
typedef std::vector<interval> interval_vector;
|
||||||
|
|
||||||
|
struct IntervalStartCmp
|
||||||
|
{
|
||||||
|
bool operator()(const interval& a, const interval& b)
|
||||||
|
{
|
||||||
|
return a.start < b.start;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IntervalStopCmp
|
||||||
|
{
|
||||||
|
bool operator()(const interval& a, const interval& b)
|
||||||
|
{
|
||||||
|
return a.stop < b.stop;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IntervalTree() :
|
||||||
|
left(nullptr), right(nullptr), center()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~IntervalTree() = default;
|
||||||
|
|
||||||
|
std::unique_ptr<IntervalTree> clone() const
|
||||||
|
{
|
||||||
|
return std::unique_ptr<IntervalTree>(new IntervalTree(*this));
|
||||||
|
}
|
||||||
|
|
||||||
|
IntervalTree(const IntervalTree& other) :
|
||||||
|
intervals(other.intervals),
|
||||||
|
left(other.left ? other.left->clone() : nullptr),
|
||||||
|
right(other.right ? other.right->clone() : nullptr),
|
||||||
|
center(other.center)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IntervalTree& operator=(IntervalTree&&) = default;
|
||||||
|
IntervalTree(IntervalTree&&) = default;
|
||||||
|
|
||||||
|
IntervalTree& operator=(const IntervalTree& other)
|
||||||
|
{
|
||||||
|
center = other.center;
|
||||||
|
intervals = other.intervals;
|
||||||
|
left = other.left ? other.left->clone() : nullptr;
|
||||||
|
right = other.right ? other.right->clone() : nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntervalTree(
|
||||||
|
interval_vector&& ivals,
|
||||||
|
std::size_t depth = 16,
|
||||||
|
std::size_t minbucket = 64,
|
||||||
|
std::size_t maxbucket = 512,
|
||||||
|
Scalar leftextent = {},
|
||||||
|
Scalar rightextent = {}) :
|
||||||
|
left(nullptr), right(nullptr)
|
||||||
|
{
|
||||||
|
--depth;
|
||||||
|
const auto minmaxStop = std::minmax_element(ivals.begin(), ivals.end(), IntervalStopCmp());
|
||||||
|
const auto minmaxStart = std::minmax_element(ivals.begin(), ivals.end(), IntervalStartCmp());
|
||||||
|
if (!ivals.empty())
|
||||||
|
{
|
||||||
|
center = (minmaxStart.first->start + minmaxStop.second->stop) / 2;
|
||||||
|
}
|
||||||
|
if (leftextent == Scalar{} && rightextent == Scalar{})
|
||||||
|
{
|
||||||
|
// sort intervals by start
|
||||||
|
std::sort(ivals.begin(), ivals.end(), IntervalStartCmp());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(std::is_sorted(ivals.begin(), ivals.end(), IntervalStartCmp()));
|
||||||
|
}
|
||||||
|
if (depth == 0 || (ivals.size() < minbucket && ivals.size() < maxbucket))
|
||||||
|
{
|
||||||
|
std::sort(ivals.begin(), ivals.end(), IntervalStartCmp());
|
||||||
|
intervals = std::move(ivals);
|
||||||
|
assert(is_valid().first);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Scalar leftp = Scalar{};
|
||||||
|
Scalar rightp = Scalar{};
|
||||||
|
|
||||||
|
if (leftextent != Scalar{} || rightextent != Scalar{})
|
||||||
|
{
|
||||||
|
leftp = leftextent;
|
||||||
|
rightp = rightextent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
leftp = ivals.front().start;
|
||||||
|
rightp = std::max_element(ivals.begin(), ivals.end(), IntervalStopCmp())->stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
interval_vector lefts;
|
||||||
|
interval_vector rights;
|
||||||
|
|
||||||
|
for (typename interval_vector::const_iterator i = ivals.begin();
|
||||||
|
i != ivals.end();
|
||||||
|
++i)
|
||||||
|
{
|
||||||
|
const interval& interval = *i;
|
||||||
|
if (interval.stop < center)
|
||||||
|
{
|
||||||
|
lefts.push_back(interval);
|
||||||
|
}
|
||||||
|
else if (interval.start > center)
|
||||||
|
{
|
||||||
|
rights.push_back(interval);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(interval.start <= center);
|
||||||
|
assert(center <= interval.stop);
|
||||||
|
intervals.push_back(interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lefts.empty())
|
||||||
|
{
|
||||||
|
left.reset(new IntervalTree(std::move(lefts),
|
||||||
|
depth,
|
||||||
|
minbucket,
|
||||||
|
maxbucket,
|
||||||
|
leftp,
|
||||||
|
center));
|
||||||
|
}
|
||||||
|
if (!rights.empty())
|
||||||
|
{
|
||||||
|
right.reset(new IntervalTree(std::move(rights),
|
||||||
|
depth,
|
||||||
|
minbucket,
|
||||||
|
maxbucket,
|
||||||
|
center,
|
||||||
|
rightp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(is_valid().first);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call f on all intervals near the range [start, stop]:
|
||||||
|
template<class UnaryFunction>
|
||||||
|
void visit_near(const Scalar& start, const Scalar& stop, UnaryFunction f) const
|
||||||
|
{
|
||||||
|
if (!intervals.empty() && !(stop < intervals.front().start))
|
||||||
|
{
|
||||||
|
for (auto& i : intervals)
|
||||||
|
{
|
||||||
|
f(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (left && start <= center)
|
||||||
|
{
|
||||||
|
left->visit_near(start, stop, f);
|
||||||
|
}
|
||||||
|
if (right && stop >= center)
|
||||||
|
{
|
||||||
|
right->visit_near(start, stop, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call f on all intervals crossing pos
|
||||||
|
template<class UnaryFunction>
|
||||||
|
void visit_overlapping(const Scalar& pos, UnaryFunction f) const
|
||||||
|
{
|
||||||
|
visit_overlapping(pos, pos, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call f on all intervals overlapping [start, stop]
|
||||||
|
template<class UnaryFunction>
|
||||||
|
void visit_overlapping(const Scalar& start, const Scalar& stop, UnaryFunction f) const
|
||||||
|
{
|
||||||
|
auto filterF = [&](const interval& interval) {
|
||||||
|
if (interval.stop >= start && interval.start <= stop)
|
||||||
|
{
|
||||||
|
// Only apply f if overlapping
|
||||||
|
f(interval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
visit_near(start, stop, filterF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call f on all intervals contained within [start, stop]
|
||||||
|
template<class UnaryFunction>
|
||||||
|
void visit_contained(const Scalar& start, const Scalar& stop, UnaryFunction f) const
|
||||||
|
{
|
||||||
|
auto filterF = [&](const interval& interval) {
|
||||||
|
if (start <= interval.start && interval.stop <= stop)
|
||||||
|
{
|
||||||
|
f(interval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
visit_near(start, stop, filterF);
|
||||||
|
}
|
||||||
|
|
||||||
|
interval_vector findOverlapping(const Scalar& start, const Scalar& stop) const
|
||||||
|
{
|
||||||
|
interval_vector result;
|
||||||
|
visit_overlapping(start, stop, [&](const interval& interval) {
|
||||||
|
result.emplace_back(interval);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
interval_vector findContained(const Scalar& start, const Scalar& stop) const
|
||||||
|
{
|
||||||
|
interval_vector result;
|
||||||
|
visit_contained(start, stop, [&](const interval& interval) {
|
||||||
|
result.push_back(interval);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
if (left && !left->empty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!intervals.empty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (right && !right->empty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class UnaryFunction>
|
||||||
|
void visit_all(UnaryFunction f) const
|
||||||
|
{
|
||||||
|
if (left)
|
||||||
|
{
|
||||||
|
left->visit_all(f);
|
||||||
|
}
|
||||||
|
std::for_each(intervals.begin(), intervals.end(), f);
|
||||||
|
if (right)
|
||||||
|
{
|
||||||
|
right->visit_all(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<Scalar, Scalar> extentBruitForce() const
|
||||||
|
{
|
||||||
|
struct Extent
|
||||||
|
{
|
||||||
|
std::pair<Scalar, Scalar> x = { std::numeric_limits<Scalar>::max(),
|
||||||
|
std::numeric_limits<Scalar>::min() };
|
||||||
|
void operator()(const interval& interval)
|
||||||
|
{
|
||||||
|
x.first = std::min(x.first, interval.start);
|
||||||
|
x.second = std::max(x.second, interval.stop);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Extent extent;
|
||||||
|
|
||||||
|
visit_all([&](const interval& interval) { extent(interval); });
|
||||||
|
return extent.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all constraints.
|
||||||
|
// If first is false, second is invalid.
|
||||||
|
std::pair<bool, std::pair<Scalar, Scalar>> is_valid() const
|
||||||
|
{
|
||||||
|
const auto minmaxStop = std::minmax_element(intervals.begin(), intervals.end(), IntervalStopCmp());
|
||||||
|
const auto minmaxStart = std::minmax_element(intervals.begin(), intervals.end(), IntervalStartCmp());
|
||||||
|
|
||||||
|
std::pair<bool, std::pair<Scalar, Scalar>> result = { true, { std::numeric_limits<Scalar>::max(), std::numeric_limits<Scalar>::min() } };
|
||||||
|
if (!intervals.empty())
|
||||||
|
{
|
||||||
|
result.second.first = std::min(result.second.first, minmaxStart.first->start);
|
||||||
|
result.second.second = std::min(result.second.second, minmaxStop.second->stop);
|
||||||
|
}
|
||||||
|
if (left)
|
||||||
|
{
|
||||||
|
auto valid = left->is_valid();
|
||||||
|
result.first &= valid.first;
|
||||||
|
result.second.first = std::min(result.second.first, valid.second.first);
|
||||||
|
result.second.second = std::min(result.second.second, valid.second.second);
|
||||||
|
if (!result.first)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (valid.second.second >= center)
|
||||||
|
{
|
||||||
|
result.first = false;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (right)
|
||||||
|
{
|
||||||
|
auto valid = right->is_valid();
|
||||||
|
result.first &= valid.first;
|
||||||
|
result.second.first = std::min(result.second.first, valid.second.first);
|
||||||
|
result.second.second = std::min(result.second.second, valid.second.second);
|
||||||
|
if (!result.first)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (valid.second.first <= center)
|
||||||
|
{
|
||||||
|
result.first = false;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!std::is_sorted(intervals.begin(), intervals.end(), IntervalStartCmp()))
|
||||||
|
{
|
||||||
|
result.first = false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, const IntervalTree& itree)
|
||||||
|
{
|
||||||
|
return writeOut(os, itree);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend std::ostream& writeOut(std::ostream& os, const IntervalTree& itree, std::size_t depth = 0)
|
||||||
|
{
|
||||||
|
auto pad = [&]() { for (std::size_t i = 0; i != depth; ++i) { os << ' '; } };
|
||||||
|
pad();
|
||||||
|
os << "center: " << itree.center << '\n';
|
||||||
|
for (const interval& inter : itree.intervals)
|
||||||
|
{
|
||||||
|
pad();
|
||||||
|
os << inter << '\n';
|
||||||
|
}
|
||||||
|
if (itree.left)
|
||||||
|
{
|
||||||
|
pad();
|
||||||
|
os << "left:\n";
|
||||||
|
writeOut(os, *itree.left, depth + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pad();
|
||||||
|
os << "left: nullptr\n";
|
||||||
|
}
|
||||||
|
if (itree.right)
|
||||||
|
{
|
||||||
|
pad();
|
||||||
|
os << "right:\n";
|
||||||
|
writeOut(os, *itree.right, depth + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pad();
|
||||||
|
os << "right: nullptr\n";
|
||||||
|
}
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
interval_vector intervals;
|
||||||
|
std::unique_ptr<IntervalTree> left;
|
||||||
|
std::unique_ptr<IntervalTree> right;
|
||||||
|
Scalar center;
|
||||||
|
};
|
||||||
|
#ifdef USE_INTERVAL_TREE_NAMESPACE
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
17
oss/interval_tree/MAINTAINER_README.md
Normal file
17
oss/interval_tree/MAINTAINER_README.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Notes for Future Maintainers
|
||||||
|
|
||||||
|
This was originally imported by @PankajBhojwani in September 2020.
|
||||||
|
|
||||||
|
The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme.
|
||||||
|
Please update the provenance information in that file when ingesting an updated version of the dependent library.
|
||||||
|
That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards.
|
||||||
|
|
||||||
|
## What should be done to update this in the future?
|
||||||
|
|
||||||
|
1. Go to ekg/intervaltreerepository on GitHub.
|
||||||
|
2. Take the file IntervalTree.h wholesale and drop it into the directory here.
|
||||||
|
3. Don't change anything about it.
|
||||||
|
4. Validate that the license in the root of the repository didn't change and update it if so. It is sitting in the same directory as this readme.
|
||||||
|
If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage.
|
||||||
|
5. Submit the pull.
|
||||||
|
|
13
oss/interval_tree/cgmanifest.json
Normal file
13
oss/interval_tree/cgmanifest.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{"Registrations":[
|
||||||
|
{
|
||||||
|
"component": {
|
||||||
|
"type": "git",
|
||||||
|
"git": {
|
||||||
|
"repositoryUrl": "https://github.com/ekg/intervaltree",
|
||||||
|
"commitHash": "b90527f9e6d51cd36ecbb50429e4524d3a418ea5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Version": 1
|
||||||
|
}
|
|
@ -14,6 +14,8 @@
|
||||||
using namespace Microsoft::Console;
|
using namespace Microsoft::Console;
|
||||||
using namespace Microsoft::Console::Types;
|
using namespace Microsoft::Console::Types;
|
||||||
|
|
||||||
|
using PointTree = interval_tree::IntervalTree<til::point, size_t>;
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
// - Creates a new instance of TextBuffer
|
// - Creates a new instance of TextBuffer
|
||||||
// Arguments:
|
// Arguments:
|
||||||
|
@ -35,7 +37,8 @@ TextBuffer::TextBuffer(const COORD screenBufferSize,
|
||||||
_unicodeStorage{},
|
_unicodeStorage{},
|
||||||
_renderTarget{ renderTarget },
|
_renderTarget{ renderTarget },
|
||||||
_size{},
|
_size{},
|
||||||
_currentHyperlinkId{ 1 }
|
_currentHyperlinkId{ 1 },
|
||||||
|
_currentPatternId{ 0 }
|
||||||
{
|
{
|
||||||
// initialize ROWs
|
// initialize ROWs
|
||||||
for (size_t i = 0; i < static_cast<size_t>(screenBufferSize.Y); ++i)
|
for (size_t i = 0; i < static_cast<size_t>(screenBufferSize.Y); ++i)
|
||||||
|
@ -2189,6 +2192,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||||
// Finish copying remaining parameters from the old text buffer to the new one
|
// Finish copying remaining parameters from the old text buffer to the new one
|
||||||
newBuffer.CopyProperties(oldBuffer);
|
newBuffer.CopyProperties(oldBuffer);
|
||||||
newBuffer.CopyHyperlinkMaps(oldBuffer);
|
newBuffer.CopyHyperlinkMaps(oldBuffer);
|
||||||
|
newBuffer.CopyPatterns(oldBuffer);
|
||||||
|
|
||||||
// If we found where to put the cursor while placing characters into the buffer,
|
// If we found where to put the cursor while placing characters into the buffer,
|
||||||
// just put the cursor there. Otherwise we have to advance manually.
|
// just put the cursor there. Otherwise we have to advance manually.
|
||||||
|
@ -2360,3 +2364,86 @@ void TextBuffer::CopyHyperlinkMaps(const TextBuffer& other)
|
||||||
_hyperlinkCustomIdMap = other._hyperlinkCustomIdMap;
|
_hyperlinkCustomIdMap = other._hyperlinkCustomIdMap;
|
||||||
_currentHyperlinkId = other._currentHyperlinkId;
|
_currentHyperlinkId = other._currentHyperlinkId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Adds a regex pattern we should search for
|
||||||
|
// - The searching does not happen here, we only search when asked to by TerminalCore
|
||||||
|
// Arguments:
|
||||||
|
// - The regex pattern
|
||||||
|
// Return value:
|
||||||
|
// - An ID that the caller should associate with the given pattern
|
||||||
|
const size_t TextBuffer::AddPatternRecognizer(const std::wstring_view regexString)
|
||||||
|
{
|
||||||
|
++_currentPatternId;
|
||||||
|
_idsAndPatterns.emplace(std::make_pair(_currentPatternId, regexString));
|
||||||
|
return _currentPatternId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Copies the patterns the other buffer knows about into this one
|
||||||
|
// Arguments:
|
||||||
|
// - The other buffer
|
||||||
|
void TextBuffer::CopyPatterns(const TextBuffer& OtherBuffer)
|
||||||
|
{
|
||||||
|
_idsAndPatterns = OtherBuffer._idsAndPatterns;
|
||||||
|
_currentPatternId = OtherBuffer._currentPatternId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Finds patterns within the requested region of the text buffer
|
||||||
|
// Arguments:
|
||||||
|
// - The firstRow to start searching from
|
||||||
|
// - The lastRow to search
|
||||||
|
// Return value:
|
||||||
|
// - An interval tree containing the patterns found
|
||||||
|
PointTree TextBuffer::GetPatterns(const size_t firstRow, const size_t lastRow) const
|
||||||
|
{
|
||||||
|
PointTree::interval_vector intervals;
|
||||||
|
|
||||||
|
std::wstring concatAll;
|
||||||
|
const auto rowSize = GetRowByOffset(0).size();
|
||||||
|
concatAll.reserve(rowSize * (lastRow - firstRow + 1));
|
||||||
|
|
||||||
|
// to deal with text that spans multiple lines, we will first concatenate
|
||||||
|
// all the text into one string and find the patterns in that string
|
||||||
|
for (auto i = firstRow; i <= lastRow; ++i)
|
||||||
|
{
|
||||||
|
auto row = GetRowByOffset(i);
|
||||||
|
concatAll += row.GetCharRow().GetText();
|
||||||
|
}
|
||||||
|
|
||||||
|
// for each pattern we know of, iterate through the string
|
||||||
|
for (const auto& idAndPattern : _idsAndPatterns)
|
||||||
|
{
|
||||||
|
std::wregex regexObj{ idAndPattern.second };
|
||||||
|
|
||||||
|
// search through the run with our regex object
|
||||||
|
auto words_begin = std::wsregex_iterator(concatAll.begin(), concatAll.end(), regexObj);
|
||||||
|
auto words_end = std::wsregex_iterator();
|
||||||
|
|
||||||
|
size_t lenUpToThis = 0;
|
||||||
|
for (auto i = words_begin; i != words_end; ++i)
|
||||||
|
{
|
||||||
|
// record the locations -
|
||||||
|
// when we find a match, the prefix is text that is between this
|
||||||
|
// match and the previous match, so we use the size of the prefix
|
||||||
|
// along with the size of the match to determine the locations
|
||||||
|
const auto prefixSize = i->prefix().str().size();
|
||||||
|
const auto start = lenUpToThis + prefixSize;
|
||||||
|
const auto end = start + i->str().size();
|
||||||
|
lenUpToThis = end;
|
||||||
|
|
||||||
|
const til::point startCoord{ gsl::narrow<SHORT>(start % rowSize), gsl::narrow<SHORT>(start / rowSize) };
|
||||||
|
const til::point endCoord{ gsl::narrow<SHORT>(end % rowSize), gsl::narrow<SHORT>(end / rowSize) };
|
||||||
|
|
||||||
|
// store the intervals
|
||||||
|
// NOTE: these intervals are relative to the VIEWPORT not the buffer
|
||||||
|
// Keeping these relative to the viewport for now because its the renderer
|
||||||
|
// that actually uses these locations and the renderer works relative to
|
||||||
|
// the viewport
|
||||||
|
intervals.push_back(PointTree::interval(startCoord, endCoord, idAndPattern.first));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PointTree result(std::move(intervals));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -182,6 +182,10 @@ public:
|
||||||
const std::optional<Microsoft::Console::Types::Viewport> lastCharacterViewport,
|
const std::optional<Microsoft::Console::Types::Viewport> lastCharacterViewport,
|
||||||
std::optional<std::reference_wrapper<PositionInformation>> positionInfo);
|
std::optional<std::reference_wrapper<PositionInformation>> positionInfo);
|
||||||
|
|
||||||
|
const size_t AddPatternRecognizer(const std::wstring_view regexString);
|
||||||
|
void CopyPatterns(const TextBuffer& OtherBuffer);
|
||||||
|
interval_tree::IntervalTree<til::point, size_t> GetPatterns(const size_t firstRow, const size_t lastRow) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void _UpdateSize();
|
void _UpdateSize();
|
||||||
Microsoft::Console::Types::Viewport _size;
|
Microsoft::Console::Types::Viewport _size;
|
||||||
|
@ -229,6 +233,9 @@ private:
|
||||||
|
|
||||||
void _PruneHyperlinks();
|
void _PruneHyperlinks();
|
||||||
|
|
||||||
|
std::unordered_map<size_t, std::wstring> _idsAndPatterns;
|
||||||
|
size_t _currentPatternId;
|
||||||
|
|
||||||
#ifdef UNIT_TESTING
|
#ifdef UNIT_TESTING
|
||||||
friend class TextBufferTests;
|
friend class TextBufferTests;
|
||||||
friend class UiaTextRangeTests;
|
friend class UiaTextRangeTests;
|
||||||
|
|
|
@ -35,6 +35,9 @@ constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8);
|
||||||
// The minimum delay between updating the TSF input control.
|
// The minimum delay between updating the TSF input control.
|
||||||
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100);
|
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100);
|
||||||
|
|
||||||
|
// The minimum delay between updating the locations of regex patterns
|
||||||
|
constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds(500);
|
||||||
|
|
||||||
DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::TerminalControl::CopyFormat);
|
DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::TerminalControl::CopyFormat);
|
||||||
|
|
||||||
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||||
|
@ -107,6 +110,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||||
// This event is explicitly revoked in the destructor: does not need weak_ref
|
// This event is explicitly revoked in the destructor: does not need weak_ref
|
||||||
auto onReceiveOutputFn = [this](const hstring str) {
|
auto onReceiveOutputFn = [this](const hstring str) {
|
||||||
_terminal->Write(str);
|
_terminal->Write(str);
|
||||||
|
_updatePatternLocations->Run();
|
||||||
};
|
};
|
||||||
_connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn);
|
_connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn);
|
||||||
|
|
||||||
|
@ -146,6 +150,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||||
TsfRedrawInterval,
|
TsfRedrawInterval,
|
||||||
Dispatcher());
|
Dispatcher());
|
||||||
|
|
||||||
|
_updatePatternLocations = std::make_shared<ThrottledFunc<>>(
|
||||||
|
[weakThis = get_weak()]() {
|
||||||
|
if (auto control{ weakThis.get() })
|
||||||
|
{
|
||||||
|
control->UpdatePatternLocations();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpdatePatternLocationsInterval,
|
||||||
|
Dispatcher());
|
||||||
|
|
||||||
_updateScrollBar = std::make_shared<ThrottledFunc<ScrollBarUpdate>>(
|
_updateScrollBar = std::make_shared<ThrottledFunc<ScrollBarUpdate>>(
|
||||||
[weakThis = get_weak()](const auto& update) {
|
[weakThis = get_weak()](const auto& update) {
|
||||||
if (auto control{ weakThis.get() })
|
if (auto control{ weakThis.get() })
|
||||||
|
@ -1342,13 +1356,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||||
_lastHoveredCell = terminalPos;
|
_lastHoveredCell = terminalPos;
|
||||||
|
|
||||||
const auto newId = _terminal->GetHyperlinkIdAtPosition(terminalPos);
|
const auto newId = _terminal->GetHyperlinkIdAtPosition(terminalPos);
|
||||||
// If the hyperlink ID changed, trigger a redraw all (so this will happen both when we move
|
const auto newInterval = _terminal->GetHyperlinkIntervalFromPosition(terminalPos);
|
||||||
// onto a link and when we move off a link)
|
// If the hyperlink ID changed or the interval changed, trigger a redraw all
|
||||||
if (newId != _lastHoveredId)
|
// (so this will happen both when we move onto a link and when we move off a link)
|
||||||
|
if (newId != _lastHoveredId || (newInterval != _lastHoveredInterval))
|
||||||
{
|
{
|
||||||
_renderEngine->UpdateHyperlinkHoveredId(newId);
|
|
||||||
_renderer->TriggerRedrawAll();
|
|
||||||
_lastHoveredId = newId;
|
_lastHoveredId = newId;
|
||||||
|
_lastHoveredInterval = newInterval;
|
||||||
|
_renderEngine->UpdateHyperlinkHoveredId(newId);
|
||||||
|
_renderer->UpdateLastHoveredInterval(newInterval);
|
||||||
|
_renderer->TriggerRedrawAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1534,6 +1551,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||||
return _DoMouseWheel(location, modifiers, delta, state);
|
return _DoMouseWheel(location, modifiers, delta, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Tell TerminalCore to update its knowledge about the locations of visible regex patterns
|
||||||
|
// - We should call this (through the throttled function) when something causes the visible
|
||||||
|
// region to change, such as when new text enters the buffer or the viewport is scrolled
|
||||||
|
void TermControl::UpdatePatternLocations()
|
||||||
|
{
|
||||||
|
_terminal->UpdatePatterns();
|
||||||
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
// - Adjust the opacity of the acrylic background in response to a mouse
|
// - Adjust the opacity of the acrylic background in response to a mouse
|
||||||
// scrolling event.
|
// scrolling event.
|
||||||
|
@ -1660,6 +1686,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the regex pattern tree so the renderer does not try to render them while scrolling
|
||||||
|
_terminal->ClearPatternTree();
|
||||||
|
|
||||||
const auto newValue = static_cast<int>(args.NewValue());
|
const auto newValue = static_cast<int>(args.NewValue());
|
||||||
|
|
||||||
// This is a scroll event that wasn't initiated by the terminal
|
// This is a scroll event that wasn't initiated by the terminal
|
||||||
|
@ -1671,6 +1700,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||||
_updateScrollBar->ModifyPending([](auto& update) {
|
_updateScrollBar->ModifyPending([](auto& update) {
|
||||||
update.newValue.reset();
|
update.newValue.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_updatePatternLocations->Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
|
@ -2251,6 +2282,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the regex pattern tree so the renderer does not try to render them while scrolling
|
||||||
|
_terminal->ClearPatternTree();
|
||||||
|
|
||||||
_scrollPositionChangedHandlers(viewTop, viewHeight, bufferSize);
|
_scrollPositionChangedHandlers(viewTop, viewHeight, bufferSize);
|
||||||
|
|
||||||
ScrollBarUpdate update;
|
ScrollBarUpdate update;
|
||||||
|
@ -2261,6 +2295,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||||
update.newValue = viewTop;
|
update.newValue = viewTop;
|
||||||
|
|
||||||
_updateScrollBar->Run(update);
|
_updateScrollBar->Run(update);
|
||||||
|
_updatePatternLocations->Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
|
|
|
@ -118,6 +118,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||||
|
|
||||||
bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown);
|
bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown);
|
||||||
|
|
||||||
|
void UpdatePatternLocations();
|
||||||
|
|
||||||
~TermControl();
|
~TermControl();
|
||||||
|
|
||||||
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
|
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
|
||||||
|
@ -180,6 +182,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||||
|
|
||||||
std::shared_ptr<ThrottledFunc<>> _tsfTryRedrawCanvas;
|
std::shared_ptr<ThrottledFunc<>> _tsfTryRedrawCanvas;
|
||||||
|
|
||||||
|
std::shared_ptr<ThrottledFunc<>> _updatePatternLocations;
|
||||||
|
|
||||||
struct ScrollBarUpdate
|
struct ScrollBarUpdate
|
||||||
{
|
{
|
||||||
std::optional<double> newValue;
|
std::optional<double> newValue;
|
||||||
|
@ -213,6 +217,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||||
// Track the last hyperlink ID we hovered over
|
// Track the last hyperlink ID we hovered over
|
||||||
uint16_t _lastHoveredId;
|
uint16_t _lastHoveredId;
|
||||||
|
|
||||||
|
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _lastHoveredInterval;
|
||||||
|
|
||||||
using Timestamp = uint64_t;
|
using Timestamp = uint64_t;
|
||||||
|
|
||||||
// imported from WinUser
|
// imported from WinUser
|
||||||
|
|
|
@ -20,6 +20,8 @@ using namespace Microsoft::Console::Render;
|
||||||
using namespace Microsoft::Console::Types;
|
using namespace Microsoft::Console::Types;
|
||||||
using namespace Microsoft::Console::VirtualTerminal;
|
using namespace Microsoft::Console::VirtualTerminal;
|
||||||
|
|
||||||
|
using PointTree = interval_tree::IntervalTree<til::point, size_t>;
|
||||||
|
|
||||||
static std::wstring _KeyEventsToText(std::deque<std::unique_ptr<IInputEvent>>& inEventsToWrite)
|
static std::wstring _KeyEventsToText(std::deque<std::unique_ptr<IInputEvent>>& inEventsToWrite)
|
||||||
{
|
{
|
||||||
std::wstring wstr = L"";
|
std::wstring wstr = L"";
|
||||||
|
@ -78,6 +80,10 @@ void Terminal::Create(COORD viewportSize, SHORT scrollbackLines, IRenderTarget&
|
||||||
const TextAttribute attr{};
|
const TextAttribute attr{};
|
||||||
const UINT cursorSize = 12;
|
const UINT cursorSize = 12;
|
||||||
_buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, renderTarget);
|
_buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, renderTarget);
|
||||||
|
// Add regex pattern recognizers to the buffer
|
||||||
|
// For now, we only add the URI regex pattern
|
||||||
|
std::wstring_view linkPattern{ LR"(\b(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|$!:,.;]*[A-Za-z0-9+&@#/%=~_|$])" };
|
||||||
|
_hyperlinkPatternId = _buffer->AddPatternRecognizer(linkPattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
|
@ -410,9 +416,9 @@ bool Terminal::IsTrackingMouseInput() const noexcept
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
// - If the clicked text is a hyperlink, open it
|
// - Given a coord, get the URI at that location
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// - The position of the clicked text
|
// - The position
|
||||||
std::wstring Terminal::GetHyperlinkAtPosition(const COORD position)
|
std::wstring Terminal::GetHyperlinkAtPosition(const COORD position)
|
||||||
{
|
{
|
||||||
auto attr = _buffer->GetCellDataAt(_ConvertToBufferCell(position))->TextAttr();
|
auto attr = _buffer->GetCellDataAt(_ConvertToBufferCell(position))->TextAttr();
|
||||||
|
@ -421,6 +427,22 @@ std::wstring Terminal::GetHyperlinkAtPosition(const COORD position)
|
||||||
auto uri = _buffer->GetHyperlinkUriFromId(attr.GetHyperlinkId());
|
auto uri = _buffer->GetHyperlinkUriFromId(attr.GetHyperlinkId());
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
// also look through our known pattern locations in our pattern interval tree
|
||||||
|
const auto result = GetHyperlinkIntervalFromPosition(position);
|
||||||
|
if (result.has_value() && result->value == _hyperlinkPatternId)
|
||||||
|
{
|
||||||
|
const auto start = result->start;
|
||||||
|
const auto end = result->stop;
|
||||||
|
std::wstring uri;
|
||||||
|
|
||||||
|
const auto startIter = _buffer->GetCellDataAt(_ConvertToBufferCell(start));
|
||||||
|
const auto endIter = _buffer->GetCellDataAt(_ConvertToBufferCell(end));
|
||||||
|
for (auto iter = startIter; iter != endIter; ++iter)
|
||||||
|
{
|
||||||
|
uri += iter->Chars();
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,6 +457,28 @@ uint16_t Terminal::GetHyperlinkIdAtPosition(const COORD position)
|
||||||
return _buffer->GetCellDataAt(_ConvertToBufferCell(position))->TextAttr().GetHyperlinkId();
|
return _buffer->GetCellDataAt(_ConvertToBufferCell(position))->TextAttr().GetHyperlinkId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method description:
|
||||||
|
// - Given a position in a URI pattern, gets the start and end coordinates of the URI
|
||||||
|
// Arguments:
|
||||||
|
// - The position
|
||||||
|
// Return value:
|
||||||
|
// - The interval representing the start and end coordinates
|
||||||
|
std::optional<PointTree::interval> Terminal::GetHyperlinkIntervalFromPosition(const COORD position)
|
||||||
|
{
|
||||||
|
const auto results = _patternIntervalTree.findOverlapping(COORD{ position.X + 1, position.Y }, position);
|
||||||
|
if (results.size() > 0)
|
||||||
|
{
|
||||||
|
for (const auto& result : results)
|
||||||
|
{
|
||||||
|
if (result.value == _hyperlinkPatternId)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
// - Send this particular (non-character) key event to the terminal.
|
// - Send this particular (non-character) key event to the terminal.
|
||||||
// - The terminal will translate the key and the modifiers pressed into the
|
// - The terminal will translate the key and the modifiers pressed into the
|
||||||
|
@ -598,6 +642,53 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro
|
||||||
return handledDown || handledUp;
|
return handledDown || handledUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Invalidates the regions described in the given pattern tree for the rendering purposes
|
||||||
|
// Arguments:
|
||||||
|
// - The interval tree containing regions that need to be invalidated
|
||||||
|
void Terminal::_InvalidatePatternTree(interval_tree::IntervalTree<til::point, size_t>& tree)
|
||||||
|
{
|
||||||
|
const auto vis = _VisibleStartIndex();
|
||||||
|
auto invalidate = [=](const PointTree::interval& interval) {
|
||||||
|
COORD startCoord{ gsl::narrow<SHORT>(interval.start.x()), gsl::narrow<SHORT>(interval.start.y() + vis) };
|
||||||
|
COORD endCoord{ gsl::narrow<SHORT>(interval.stop.x()), gsl::narrow<SHORT>(interval.stop.y() + vis) };
|
||||||
|
_InvalidateFromCoords(startCoord, endCoord);
|
||||||
|
};
|
||||||
|
tree.visit_all(invalidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Given start and end coords, invalidates all the regions between them
|
||||||
|
// Arguments:
|
||||||
|
// - The start and end coords
|
||||||
|
void Terminal::_InvalidateFromCoords(const COORD start, const COORD end)
|
||||||
|
{
|
||||||
|
if (start.Y == end.Y)
|
||||||
|
{
|
||||||
|
SMALL_RECT region{ start.X, start.Y, end.X, end.Y };
|
||||||
|
_buffer->GetRenderTarget().TriggerRedraw(Viewport::FromInclusive(region));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto rowSize = gsl::narrow<SHORT>(_buffer->GetRowByOffset(0).size());
|
||||||
|
|
||||||
|
// invalidate the first line
|
||||||
|
SMALL_RECT region{ start.X, start.Y, rowSize - 1, start.Y };
|
||||||
|
_buffer->GetRenderTarget().TriggerRedraw(Viewport::FromInclusive(region));
|
||||||
|
|
||||||
|
if ((end.Y - start.Y) > 1)
|
||||||
|
{
|
||||||
|
// invalidate the lines in between the first and last line
|
||||||
|
region = til::rectangle(0, start.Y + 1, rowSize - 1, end.Y - 1);
|
||||||
|
_buffer->GetRenderTarget().TriggerRedraw(Viewport::FromInclusive(region));
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalidate the last line
|
||||||
|
region = til::rectangle(0, end.Y, end.X, end.Y);
|
||||||
|
_buffer->GetRenderTarget().TriggerRedraw(Viewport::FromInclusive(region));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
// - Returns the keyboard's scan code for the given virtual key code.
|
// - Returns the keyboard's scan code for the given virtual key code.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
|
@ -856,6 +947,9 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)
|
||||||
proposedCursorPosition.Y--;
|
proposedCursorPosition.Y--;
|
||||||
rowsPushedOffTopOfBuffer++;
|
rowsPushedOffTopOfBuffer++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// manually erase our pattern intervals since the locations have changed now
|
||||||
|
_patternIntervalTree = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Cursor Position
|
// Update Cursor Position
|
||||||
|
@ -1041,6 +1135,30 @@ bool Terminal::IsCursorBlinkingAllowed() const noexcept
|
||||||
return cursor.IsBlinkingAllowed();
|
return cursor.IsBlinkingAllowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Update our internal knowledge about where regex patterns are on the screen
|
||||||
|
// - This is called by TerminalControl (through a throttled function) when the visible
|
||||||
|
// region changes (for example by text entering the buffer or scrolling)
|
||||||
|
void Terminal::UpdatePatterns() noexcept
|
||||||
|
{
|
||||||
|
auto lock = LockForWriting();
|
||||||
|
auto oldTree = _patternIntervalTree;
|
||||||
|
_patternIntervalTree = _buffer->GetPatterns(_VisibleStartIndex(), _VisibleEndIndex());
|
||||||
|
_InvalidatePatternTree(oldTree);
|
||||||
|
_InvalidatePatternTree(_patternIntervalTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Clears and invalidates the interval pattern tree
|
||||||
|
// - This is called to prevent the renderer from rendering patterns while the
|
||||||
|
// visible region is changing
|
||||||
|
void Terminal::ClearPatternTree() noexcept
|
||||||
|
{
|
||||||
|
auto oldTree = _patternIntervalTree;
|
||||||
|
_patternIntervalTree = {};
|
||||||
|
_InvalidatePatternTree(oldTree);
|
||||||
|
}
|
||||||
|
|
||||||
const std::optional<til::color> Terminal::GetTabColor() const noexcept
|
const std::optional<til::color> Terminal::GetTabColor() const noexcept
|
||||||
{
|
{
|
||||||
return _tabColor;
|
return _tabColor;
|
||||||
|
|
|
@ -132,6 +132,7 @@ public:
|
||||||
|
|
||||||
std::wstring GetHyperlinkAtPosition(const COORD position);
|
std::wstring GetHyperlinkAtPosition(const COORD position);
|
||||||
uint16_t GetHyperlinkIdAtPosition(const COORD position);
|
uint16_t GetHyperlinkIdAtPosition(const COORD position);
|
||||||
|
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> GetHyperlinkIntervalFromPosition(const COORD position);
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
#pragma region IBaseData(base to IRenderData and IUiaData)
|
#pragma region IBaseData(base to IRenderData and IUiaData)
|
||||||
|
@ -161,6 +162,7 @@ public:
|
||||||
const bool IsGridLineDrawingAllowed() noexcept override;
|
const bool IsGridLineDrawingAllowed() noexcept override;
|
||||||
const std::wstring GetHyperlinkUri(uint16_t id) const noexcept override;
|
const std::wstring GetHyperlinkUri(uint16_t id) const noexcept override;
|
||||||
const std::wstring GetHyperlinkCustomId(uint16_t id) const noexcept override;
|
const std::wstring GetHyperlinkCustomId(uint16_t id) const noexcept override;
|
||||||
|
const std::vector<size_t> GetPatternId(const COORD location) const noexcept override;
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
#pragma region IUiaData
|
#pragma region IUiaData
|
||||||
|
@ -187,6 +189,9 @@ public:
|
||||||
void SetCursorOn(const bool isOn);
|
void SetCursorOn(const bool isOn);
|
||||||
bool IsCursorBlinkingAllowed() const noexcept;
|
bool IsCursorBlinkingAllowed() const noexcept;
|
||||||
|
|
||||||
|
void UpdatePatterns() noexcept;
|
||||||
|
void ClearPatternTree() noexcept;
|
||||||
|
|
||||||
const std::optional<til::color> GetTabColor() const noexcept;
|
const std::optional<til::color> GetTabColor() const noexcept;
|
||||||
|
|
||||||
Microsoft::Console::Render::BlinkingState& GetBlinkingState() const noexcept;
|
Microsoft::Console::Render::BlinkingState& GetBlinkingState() const noexcept;
|
||||||
|
@ -235,6 +240,8 @@ private:
|
||||||
bool _altGrAliasing;
|
bool _altGrAliasing;
|
||||||
bool _suppressApplicationTitle;
|
bool _suppressApplicationTitle;
|
||||||
|
|
||||||
|
size_t _hyperlinkPatternId;
|
||||||
|
|
||||||
#pragma region Text Selection
|
#pragma region Text Selection
|
||||||
// a selection is represented as a range between two COORDs (start and end)
|
// a selection is represented as a range between two COORDs (start and end)
|
||||||
// the pivot is the COORD that remains selected when you extend a selection in any direction
|
// the pivot is the COORD that remains selected when you extend a selection in any direction
|
||||||
|
@ -276,6 +283,10 @@ private:
|
||||||
// underneath them, while others would prefer to anchor it in place.
|
// underneath them, while others would prefer to anchor it in place.
|
||||||
// Either way, we should make this behavior controlled by a setting.
|
// Either way, we should make this behavior controlled by a setting.
|
||||||
|
|
||||||
|
interval_tree::IntervalTree<til::point, size_t> _patternIntervalTree;
|
||||||
|
void _InvalidatePatternTree(interval_tree::IntervalTree<til::point, size_t>& tree);
|
||||||
|
void _InvalidateFromCoords(const COORD start, const COORD end);
|
||||||
|
|
||||||
// Since virtual keys are non-zero, you assume that this field is empty/invalid if it is.
|
// Since virtual keys are non-zero, you assume that this field is empty/invalid if it is.
|
||||||
struct KeyEventCodes
|
struct KeyEventCodes
|
||||||
{
|
{
|
||||||
|
|
|
@ -133,6 +133,32 @@ const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkCustomId(uin
|
||||||
return _buffer->GetCustomIdFromId(id);
|
return _buffer->GetCustomIdFromId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Gets the regex pattern ids of a location
|
||||||
|
// Arguments:
|
||||||
|
// - The location
|
||||||
|
// Return value:
|
||||||
|
// - The pattern IDs of the location
|
||||||
|
const std::vector<size_t> Terminal::GetPatternId(const COORD location) const noexcept
|
||||||
|
{
|
||||||
|
// Look through our interval tree for this location
|
||||||
|
const auto intervals = _patternIntervalTree.findOverlapping(COORD{ location.X + 1, location.Y }, location);
|
||||||
|
if (intervals.size() == 0)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::vector<size_t> result{};
|
||||||
|
for (const auto& interval : intervals)
|
||||||
|
{
|
||||||
|
result.emplace_back(interval.value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Microsoft::Console::Types::Viewport> Terminal::GetSelectionRects() noexcept
|
std::vector<Microsoft::Console::Types::Viewport> Terminal::GetSelectionRects() noexcept
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -90,7 +90,7 @@
|
||||||
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
|
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
|
||||||
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
|
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
|
||||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||||
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\libpopcnt;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\interval_tree;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
|
||||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||||
<MinimalRebuild>false</MinimalRebuild>
|
<MinimalRebuild>false</MinimalRebuild>
|
||||||
<RuntimeTypeInfo>false</RuntimeTypeInfo>
|
<RuntimeTypeInfo>false</RuntimeTypeInfo>
|
||||||
|
|
|
@ -348,6 +348,12 @@ const std::wstring RenderData::GetHyperlinkCustomId(uint16_t id) const noexcept
|
||||||
return gci.GetActiveOutputBuffer().GetTextBuffer().GetCustomIdFromId(id);
|
return gci.GetActiveOutputBuffer().GetTextBuffer().GetCustomIdFromId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For now, we ignore regex patterns in conhost
|
||||||
|
const std::vector<size_t> RenderData::GetPatternId(const COORD /*location*/) const noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
// - Converts a text attribute into the RGB values that should be presented, applying
|
// - Converts a text attribute into the RGB values that should be presented, applying
|
||||||
// relevant table translation information and preferences.
|
// relevant table translation information and preferences.
|
||||||
|
|
|
@ -58,6 +58,8 @@ public:
|
||||||
|
|
||||||
const std::wstring GetHyperlinkUri(uint16_t id) const noexcept override;
|
const std::wstring GetHyperlinkUri(uint16_t id) const noexcept override;
|
||||||
const std::wstring GetHyperlinkCustomId(uint16_t id) const noexcept override;
|
const std::wstring GetHyperlinkCustomId(uint16_t id) const noexcept override;
|
||||||
|
|
||||||
|
const std::vector<size_t> GetPatternId(const COORD location) const noexcept override;
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
#pragma region IUiaData
|
#pragma region IUiaData
|
||||||
|
|
|
@ -401,6 +401,11 @@ public:
|
||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<size_t> GetPatternId(const COORD /*location*/) const noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void VtIoTests::RendererDtorAndThread()
|
void VtIoTests::RendererDtorAndThread()
|
||||||
|
|
|
@ -91,6 +91,9 @@
|
||||||
// {fmt}, a C++20-compatible formatting library
|
// {fmt}, a C++20-compatible formatting library
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#define USE_INTERVAL_TREE_NAMESPACE
|
||||||
|
#include <IntervalTree.h>
|
||||||
|
|
||||||
// SAL
|
// SAL
|
||||||
#include <sal.h>
|
#include <sal.h>
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,38 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr bool operator<=(const point& other) const noexcept
|
||||||
|
{
|
||||||
|
if (_y < other._y)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (_y > other._y)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _x <= other._x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator>=(const point& other) const noexcept
|
||||||
|
{
|
||||||
|
if (_y > other._y)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (_y < other._y)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _x >= other._x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
point operator+(const point& other) const
|
point operator+(const point& other) const
|
||||||
{
|
{
|
||||||
ptrdiff_t x;
|
ptrdiff_t x;
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
using namespace Microsoft::Console::Render;
|
using namespace Microsoft::Console::Render;
|
||||||
using namespace Microsoft::Console::Types;
|
using namespace Microsoft::Console::Types;
|
||||||
|
|
||||||
|
using PointTree = interval_tree::IntervalTree<til::point, size_t>;
|
||||||
|
|
||||||
static constexpr auto maxRetriesForRenderEngine = 3;
|
static constexpr auto maxRetriesForRenderEngine = 3;
|
||||||
// The renderer will wait this number of milliseconds * how many tries have elapsed before trying again.
|
// The renderer will wait this number of milliseconds * how many tries have elapsed before trying again.
|
||||||
static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 };
|
static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 };
|
||||||
|
@ -693,6 +695,8 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
||||||
|
|
||||||
// Retrieve the first color.
|
// Retrieve the first color.
|
||||||
auto color = it->TextAttr();
|
auto color = it->TextAttr();
|
||||||
|
// Retrieve the first pattern id
|
||||||
|
auto patternIds = _pData->GetPatternId(target);
|
||||||
|
|
||||||
// And hold the point where we should start drawing.
|
// And hold the point where we should start drawing.
|
||||||
auto screenPoint = target;
|
auto screenPoint = target;
|
||||||
|
@ -706,6 +710,9 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
||||||
// when we go to draw gridlines for the length of the run.
|
// when we go to draw gridlines for the length of the run.
|
||||||
const auto currentRunColor = color;
|
const auto currentRunColor = color;
|
||||||
|
|
||||||
|
// Hold onto the current pattern id as well
|
||||||
|
const auto currentPatternId = patternIds;
|
||||||
|
|
||||||
// Update the drawing brushes with our color.
|
// Update the drawing brushes with our color.
|
||||||
THROW_IF_FAILED(_UpdateDrawingBrushes(pEngine, currentRunColor, false));
|
THROW_IF_FAILED(_UpdateDrawingBrushes(pEngine, currentRunColor, false));
|
||||||
|
|
||||||
|
@ -731,16 +738,20 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
|
||||||
|
|
||||||
// This inner loop will accumulate clusters until the color changes.
|
// This inner loop will accumulate clusters until the color changes.
|
||||||
// When the color changes, it will save the new color off and break.
|
// When the color changes, it will save the new color off and break.
|
||||||
|
// We also accumulate clusters according to regex patterns
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (color != it->TextAttr())
|
COORD thisPoint{ screenPoint.X + gsl::narrow<SHORT>(cols), screenPoint.Y };
|
||||||
|
const auto thisPointPatterns = _pData->GetPatternId(thisPoint);
|
||||||
|
if (color != it->TextAttr() || patternIds != thisPointPatterns)
|
||||||
{
|
{
|
||||||
auto newAttr{ it->TextAttr() };
|
auto newAttr{ it->TextAttr() };
|
||||||
// foreground doesn't matter for runs of spaces (!)
|
// foreground doesn't matter for runs of spaces (!)
|
||||||
// if we trick it . . . we call Paint far fewer times for cmatrix
|
// if we trick it . . . we call Paint far fewer times for cmatrix
|
||||||
if (!_IsAllSpaces(it->Chars()) || !newAttr.HasIdenticalVisualRepresentationForBlankSpace(color, globalInvert))
|
if (!_IsAllSpaces(it->Chars()) || !newAttr.HasIdenticalVisualRepresentationForBlankSpace(color, globalInvert) || patternIds != thisPointPatterns)
|
||||||
{
|
{
|
||||||
color = newAttr;
|
color = newAttr;
|
||||||
|
patternIds = thisPointPatterns;
|
||||||
break; // vend this run
|
break; // vend this run
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -900,6 +911,22 @@ void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngin
|
||||||
{
|
{
|
||||||
// Convert console grid line representations into rendering engine enum representations.
|
// Convert console grid line representations into rendering engine enum representations.
|
||||||
IRenderEngine::GridLines lines = Renderer::s_GetGridlines(textAttribute);
|
IRenderEngine::GridLines lines = Renderer::s_GetGridlines(textAttribute);
|
||||||
|
|
||||||
|
// For now, we dash underline patterns and switch to regular underline on hover
|
||||||
|
if (_pData->GetPatternId(coordTarget).size() > 0)
|
||||||
|
{
|
||||||
|
if (_hoveredInterval.has_value() &&
|
||||||
|
_hoveredInterval.value().start <= til::point{ coordTarget } &&
|
||||||
|
til::point{ coordTarget } <= _hoveredInterval.value().stop)
|
||||||
|
{
|
||||||
|
lines |= IRenderEngine::GridLines::Underline;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lines |= IRenderEngine::GridLines::HyperlinkUnderline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return early if there are no lines to paint.
|
// Return early if there are no lines to paint.
|
||||||
if (lines != IRenderEngine::GridLines::None)
|
if (lines != IRenderEngine::GridLines::None)
|
||||||
{
|
{
|
||||||
|
@ -1216,6 +1243,11 @@ void Renderer::ResetErrorStateAndResume()
|
||||||
EnablePainting();
|
EnablePainting();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Renderer::UpdateLastHoveredInterval(const std::optional<PointTree::interval>& newInterval)
|
||||||
|
{
|
||||||
|
_hoveredInterval = newInterval;
|
||||||
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
// - Blocks until the engines are able to render without blocking.
|
// - Blocks until the engines are able to render without blocking.
|
||||||
void Renderer::WaitUntilCanRender()
|
void Renderer::WaitUntilCanRender()
|
||||||
|
|
|
@ -80,6 +80,8 @@ namespace Microsoft::Console::Render
|
||||||
void SetRendererEnteredErrorStateCallback(std::function<void()> pfn);
|
void SetRendererEnteredErrorStateCallback(std::function<void()> pfn);
|
||||||
void ResetErrorStateAndResume();
|
void ResetErrorStateAndResume();
|
||||||
|
|
||||||
|
void UpdateLastHoveredInterval(const std::optional<interval_tree::IntervalTree<til::point, size_t>::interval>& newInterval);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::deque<IRenderEngine*> _rgpEngines;
|
std::deque<IRenderEngine*> _rgpEngines;
|
||||||
|
|
||||||
|
@ -88,6 +90,8 @@ namespace Microsoft::Console::Render
|
||||||
std::unique_ptr<IRenderThread> _pThread;
|
std::unique_ptr<IRenderThread> _pThread;
|
||||||
bool _destructing = false;
|
bool _destructing = false;
|
||||||
|
|
||||||
|
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _hoveredInterval;
|
||||||
|
|
||||||
void _NotifyPaintFrame();
|
void _NotifyPaintFrame();
|
||||||
|
|
||||||
[[nodiscard]] HRESULT _PaintFrameForEngine(_In_ IRenderEngine* const pEngine) noexcept;
|
[[nodiscard]] HRESULT _PaintFrameForEngine(_In_ IRenderEngine* const pEngine) noexcept;
|
||||||
|
|
|
@ -69,6 +69,8 @@ namespace Microsoft::Console::Render
|
||||||
virtual const std::wstring GetHyperlinkUri(uint16_t id) const noexcept = 0;
|
virtual const std::wstring GetHyperlinkUri(uint16_t id) const noexcept = 0;
|
||||||
virtual const std::wstring GetHyperlinkCustomId(uint16_t id) const noexcept = 0;
|
virtual const std::wstring GetHyperlinkCustomId(uint16_t id) const noexcept = 0;
|
||||||
|
|
||||||
|
virtual const std::vector<size_t> GetPatternId(const COORD location) const noexcept = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
IRenderData() = default;
|
IRenderData() = default;
|
||||||
};
|
};
|
||||||
|
|
|
@ -175,6 +175,82 @@ class PointTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_METHOD(LessThanOrEqual)
|
||||||
|
{
|
||||||
|
Log::Comment(L"0.) Equal.");
|
||||||
|
{
|
||||||
|
const til::point s1{ 5, 10 };
|
||||||
|
const til::point s2{ 5, 10 };
|
||||||
|
VERIFY_IS_TRUE(s1 <= s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Comment(L"1.) Left Width changed.");
|
||||||
|
{
|
||||||
|
const til::point s1{ 4, 10 };
|
||||||
|
const til::point s2{ 5, 10 };
|
||||||
|
VERIFY_IS_TRUE(s1 <= s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Comment(L"2.) Right Width changed.");
|
||||||
|
{
|
||||||
|
const til::point s1{ 5, 10 };
|
||||||
|
const til::point s2{ 6, 10 };
|
||||||
|
VERIFY_IS_TRUE(s1 <= s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Comment(L"3.) Left Height changed.");
|
||||||
|
{
|
||||||
|
const til::point s1{ 5, 9 };
|
||||||
|
const til::point s2{ 5, 10 };
|
||||||
|
VERIFY_IS_TRUE(s1 <= s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Comment(L"4.) Right Height changed.");
|
||||||
|
{
|
||||||
|
const til::point s1{ 5, 10 };
|
||||||
|
const til::point s2{ 5, 11 };
|
||||||
|
VERIFY_IS_TRUE(s1 <= s2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_METHOD(GreaterThanOrEqual)
|
||||||
|
{
|
||||||
|
Log::Comment(L"0.) Equal.");
|
||||||
|
{
|
||||||
|
const til::point s1{ 5, 10 };
|
||||||
|
const til::point s2{ 5, 10 };
|
||||||
|
VERIFY_IS_TRUE(s1 >= s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Comment(L"1.) Left Width changed.");
|
||||||
|
{
|
||||||
|
const til::point s1{ 4, 10 };
|
||||||
|
const til::point s2{ 5, 10 };
|
||||||
|
VERIFY_IS_FALSE(s1 >= s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Comment(L"2.) Right Width changed.");
|
||||||
|
{
|
||||||
|
const til::point s1{ 5, 10 };
|
||||||
|
const til::point s2{ 6, 10 };
|
||||||
|
VERIFY_IS_FALSE(s1 >= s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Comment(L"3.) Left Height changed.");
|
||||||
|
{
|
||||||
|
const til::point s1{ 5, 9 };
|
||||||
|
const til::point s2{ 5, 10 };
|
||||||
|
VERIFY_IS_FALSE(s1 >= s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Comment(L"4.) Right Height changed.");
|
||||||
|
{
|
||||||
|
const til::point s1{ 5, 10 };
|
||||||
|
const til::point s2{ 5, 11 };
|
||||||
|
VERIFY_IS_FALSE(s1 >= s2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_METHOD(Addition)
|
TEST_METHOD(Addition)
|
||||||
{
|
{
|
||||||
Log::Comment(L"0.) Addition of two things that should be in bounds.");
|
Log::Comment(L"0.) Addition of two things that should be in bounds.");
|
||||||
|
|
Loading…
Reference in a new issue