diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 5b22cfe15fe..0d8b66f8613 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -2379,9 +2379,11 @@ const size_t TextBuffer::AddPatternRecognizer(const std::string_view regexString // tuple<0>: ID of the pattern // tuple<1>: start coordinate of the pattern // tuple<2>: end coordinate of the pattern -const std::vector> TextBuffer::UpdatePatterns(const size_t firstRow, const size_t lastRow) const + +til::IntervalTree::ITNode* TextBuffer::UpdatePatterns(const size_t firstRow, const size_t lastRow) const { - std::vector> result; + til::IntervalTree tree; + til::IntervalTree::ITNode* result = NULL; std::wstring concatAll; const auto rowSize = GetRowByOffset(0).size(); @@ -2396,17 +2398,14 @@ const std::vector> TextBuffer::UpdatePatterns(c concatAll += row.GetCharRow().GetText(); } - // convert the string into something the regex iterator can work with - const auto constAll = til::u16u8(concatAll); - // for each pattern we know of, iterate through the string for (auto idAndPattern : _IdsAndPatterns) { - std::regex regexObj{ idAndPattern.second }; + std::wregex regexObj{ til::u8u16(idAndPattern.second) }; // search through the run with our regex object - auto words_begin = std::sregex_iterator(constAll.begin(), constAll.end(), regexObj); - auto words_end = std::sregex_iterator(); + 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) @@ -2432,7 +2431,7 @@ const std::vector> TextBuffer::UpdatePatterns(c const auto endCol = gsl::narrow(end % rowSize); const COORD startCoord{ startCol, startRow }; const COORD endCoord{ endCol, endRow }; - result.push_back(std::make_tuple(idAndPattern.first, startCoord, endCoord)); + result = tree.insert(result, til::IntervalTree::Interval{ startCoord, endCoord }, idAndPattern.first); } } return result; diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 452dfb264ff..4079d81bd0b 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -183,7 +183,7 @@ class TextBuffer final std::optional> positionInfo); const size_t AddPatternRecognizer(const std::string_view regexString); - const std::vector> UpdatePatterns(const size_t firstRow, const size_t lastRow) const; + til::IntervalTree::ITNode* UpdatePatterns(const size_t firstRow, const size_t lastRow) const; private: void _UpdateSize(); diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 463243ca2e0..e1d47959395 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -424,24 +424,20 @@ std::wstring Terminal::GetHyperlinkAtPosition(const COORD position) return uri; } // also look through our known pattern locations - for (auto pattern : _patternsAndLocations) + const auto found = _tree.overlapSearch(_patternsAndLocations, til::IntervalTree::Interval{ position, position }); + if (found != NULL && found->patternId == _hyperlinkPatternId) { - if (std::get<0>(pattern) == _hyperlinkPatternId) + const auto start = found->i->low; + const auto end = found->i->high; + std::wstring uri; + + const auto startIter = _buffer->GetCellDataAt(_ConvertToBufferCell(start)); + const auto endIter = _buffer->GetCellDataAt(_ConvertToBufferCell(end)); + for (auto iter = startIter; iter != endIter; ++iter) { - const auto start = std::get<1>(pattern); - const auto end = std::get<2>(pattern); - if (_IsLocationWithinCoordinates(position, start, end)) - { - 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; - } + uri += iter->Chars(); } + return uri; } return {}; } @@ -983,45 +979,6 @@ void Terminal::_NotifyTerminalCursorPositionChanged() noexcept } } -// Method Description: -// - Implements the logic to determine if a location is in a given region -// Arguments: -// - The location -// - The start and end coordinates of the region -// Return value: -// - True if the location is within those coordinates, false otherwise -bool Microsoft::Terminal::Core::Terminal::_IsLocationWithinCoordinates(const COORD location, const COORD first, const COORD second) const noexcept -{ - if (first.Y == second.Y) - { - const auto sameRow = location.Y == first.Y; - const auto inRange = (first.X <= location.X && location.X < second.X); - return (sameRow && inRange); - } - else - { - // check first row - if (location.Y == first.Y && (first.X <= location.X)) - { - return true; - } - // check rows in between first row and last row - for (auto curRow = first.Y + 1; curRow < second.Y; ++curRow) - { - if (location.Y == curRow) - { - return true; - } - } - // check last row - if (location.Y == second.Y && location.X < second.X) - { - return true; - } - } - return false; -} - void Terminal::SetWriteInputCallback(std::function pfn) noexcept { _pfnWriteInput.swap(pfn); diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 2646ad59eae..973cad418f3 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -233,7 +233,8 @@ class Microsoft::Terminal::Core::Terminal final : bool _suppressApplicationTitle; size_t _hyperlinkPatternId; - std::vector> _patternsAndLocations; + til::IntervalTree _tree; + til::IntervalTree::ITNode* _patternsAndLocations; #pragma region Text Selection // a selection is represented as a range between two COORDs (start and end) @@ -308,8 +309,6 @@ class Microsoft::Terminal::Core::Terminal final : void _NotifyTerminalCursorPositionChanged() noexcept; - bool _IsLocationWithinCoordinates(const COORD location, const COORD first, const COORD second) const noexcept; - #pragma region TextSelection // These methods are defined in TerminalSelection.cpp std::vector _GetSelectionRects() const noexcept; diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index 3053bf2a3de..ed7bebc939c 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -139,12 +139,10 @@ const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkCustomId(uin // - The pattern ID of the location const size_t Microsoft::Terminal::Core::Terminal::GetPatternId(const COORD location) const noexcept { - for (auto found : _patternsAndLocations) + const auto found = _tree.overlapSearch(_patternsAndLocations, til::IntervalTree::Interval{ location, location }); + if (found != NULL) { - if (_IsLocationWithinCoordinates(location, std::get<1>(found), std::get<2>(found))) - { - return std::get<0>(found); - } + return found->patternId; } return 0; } diff --git a/src/inc/til.h b/src/inc/til.h index 1816d2e0736..252935a152e 100644 --- a/src/inc/til.h +++ b/src/inc/til.h @@ -18,6 +18,7 @@ #include "til/spsc.h" #include "til/coalesce.h" #include "til/replace.h" +#include "til/interval_tree.h" namespace til // Terminal Implementation Library. Also: "Today I Learned" { diff --git a/src/inc/til/interval_tree.h b/src/inc/til/interval_tree.h new file mode 100644 index 00000000000..ec9e60fe48e --- /dev/null +++ b/src/inc/til/interval_tree.h @@ -0,0 +1,147 @@ +// Code here is taken from https://www.geeksforgeeks.org/interval-tree/ and edited + +#pragma once + +namespace til +{ + class IntervalTree + { + public: + // Structure to represent an interval + struct Interval + { + COORD low, high; + }; + + // Structure to represent a node in Interval Search Tree + struct ITNode + { + Interval* i; + COORD max; + ITNode *left, *right; + size_t patternId; + }; + + // A utility function to create a new Interval Search Tree Node + ITNode* newNode(Interval i, size_t id) + { + ITNode* temp = new ITNode; + temp->i = new Interval(i); + temp->max = i.high; + temp->left = temp->right = NULL; + temp->patternId = id; + return temp; + }; + + // A utility function to insert a new Interval Search Tree Node + // This is similar to BST Insert. Here the low value of interval + // is used to maintain BST property + ITNode* insert(ITNode* root, Interval i, size_t id) + { + // Base case: Tree is empty, new node becomes root + if (root == NULL) + return newNode(i, id); + + // Get low value of interval at root + COORD l = root->i->low; + + // If root's low value is smaller, then new interval goes to + // left subtree + if (lessThan(i.low, l)) + root->left = insert(root->left, i, id); + + // Else, new node goes to right subtree. + else + root->right = insert(root->right, i, id); + + // Update the max value of this ancestor if needed + if (lessThan(root->max, i.high)) + root->max = i.high; + + return root; + } + + // A utility function to check if given two intervals overlap + bool doOverlap(Interval i1, Interval i2) const + { + if (lessThanOrEqual(i1.low, i2.low) && lessThan(i2.high, i1.high)) + return true; + return false; + } + + // The main function that searches a given interval i in a given + // Interval Tree. + ITNode* overlapSearch(ITNode* root, Interval i) const + { + // Base Case, tree is empty + if (root == NULL) + return NULL; + + // If given interval overlaps with root + if (doOverlap(*(root->i), i)) + return root; + + // If left child of root is present and max of left child is + // greater than or equal to given interval, then i may + // overlap with an interval is left subtree + if (root->left != NULL && greaterThanOrEqual(root->left->max, i.high)) + return overlapSearch(root->left, i); + + // Else interval can only overlap with right subtree + return overlapSearch(root->right, i); + } + + private: + bool lessThan(const COORD x, const COORD y) const + { + if (x.Y < y.Y) + { + return true; + } + else if (x.Y == y.Y) + { + return x.X < y.X; + } + return false; + } + + bool lessThanOrEqual(const COORD x, const COORD y) const + { + if (x.Y < y.Y) + { + return true; + } + else if (x.Y == y.Y) + { + return x.X <= y.X; + } + return false; + } + + bool greaterThan(const COORD x, const COORD y) const + { + if (x.Y > y.Y) + { + return true; + } + else if (x.Y == y.Y) + { + return x.X > y.X; + } + return false; + } + + bool greaterThanOrEqual(const COORD x, const COORD y) const + { + if (x.Y > y.Y) + { + return true; + } + else if (x.Y == y.Y) + { + return x.X >= y.X; + } + return false; + } + }; +}