Skip to content

Commit

Permalink
interval trees
Browse files Browse the repository at this point in the history
  • Loading branch information
PankajBhojwani committed Sep 28, 2020
1 parent 137fcb9 commit 8b95bd9
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 72 deletions.
17 changes: 8 additions & 9 deletions src/buffer/out/textBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::tuple<size_t, COORD, COORD>> 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<std::tuple<size_t, COORD, COORD>> result;
til::IntervalTree tree;
til::IntervalTree::ITNode* result = NULL;

std::wstring concatAll;
const auto rowSize = GetRowByOffset(0).size();
Expand All @@ -2396,17 +2398,14 @@ const std::vector<std::tuple<size_t, COORD, COORD>> 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)
Expand All @@ -2432,7 +2431,7 @@ const std::vector<std::tuple<size_t, COORD, COORD>> TextBuffer::UpdatePatterns(c
const auto endCol = gsl::narrow<SHORT>(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;
Expand Down
2 changes: 1 addition & 1 deletion src/buffer/out/textBuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class TextBuffer final
std::optional<std::reference_wrapper<PositionInformation>> positionInfo);

const size_t AddPatternRecognizer(const std::string_view regexString);
const std::vector<std::tuple<size_t, COORD, COORD>> 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();
Expand Down
65 changes: 11 additions & 54 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {};
}
Expand Down Expand Up @@ -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<void(std::wstring&)> pfn) noexcept
{
_pfnWriteInput.swap(pfn);
Expand Down
5 changes: 2 additions & 3 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ class Microsoft::Terminal::Core::Terminal final :
bool _suppressApplicationTitle;

size_t _hyperlinkPatternId;
std::vector<std::tuple<size_t, COORD, COORD>> _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)
Expand Down Expand Up @@ -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<SMALL_RECT> _GetSelectionRects() const noexcept;
Expand Down
8 changes: 3 additions & 5 deletions src/cascadia/TerminalCore/terminalrenderdata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions src/inc/til.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
{
Expand Down
147 changes: 147 additions & 0 deletions src/inc/til/interval_tree.h
Original file line number Diff line number Diff line change
@@ -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;
}
};
}

1 comment on commit 8b95bd9

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New misspellings found, please review:

  • geeksforgeeks
  • wsregex
To accept these changes, run the following commands
perl -e '
my @expect_files=qw('".github/actions/spell-check/expect/alphabet.txt
.github/actions/spell-check/expect/expect.txt
.github/actions/spell-check/expect/web.txt"');
@ARGV=@expect_files;
my @stale=qw('"Autogenerated debugbreak DECLL DECSMBV Inplace notypeopt restrictederrorinfo Scs Switchto Wlk "');
my $re=join "|", @stale;
my $suffix=".".time();
my $previous="";
sub maybe_unlink { unlink($_[0]) if $_[0]; }
while (<>) {
  if ($ARGV ne $old_argv) { maybe_unlink($previous); $previous="$ARGV$suffix"; rename($ARGV, $previous); open(ARGV_OUT, ">$ARGV"); select(ARGV_OUT); $old_argv = $ARGV; }
  next if /^($re)(?:$| .*)/; print;
}; maybe_unlink($previous);'
perl -e '
my $new_expect_file=".github/actions/spell-check/expect/8b95bd9719f5ee335555723cc5ac7fe0a218a53f.txt";
open FILE, q{<}, $new_expect_file; chomp(my @words = <FILE>); close FILE;
my @add=qw('"autogenerated geeksforgeeks inplace wsregex "');
my %items; @items{@words} = @words x (1); @items{@add} = @add x (1);
@words = sort {lc($a) cmp lc($b)} keys %items;
open FILE, q{>}, $new_expect_file; for my $word (@words) { print FILE "$word\n" if $word =~ /\w/; };
close FILE;'
git add .github/actions/spell-check/expect || echo '... you want to ensure .github/actions/spell-check/expect/8b95bd9719f5ee335555723cc5ac7fe0a218a53f.txt is added to your repository...'
✏️ Contributor please read this

By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.

⚠️ The command is written for posix shells. You can copy the contents of each perl command excluding the outer ' marks and dropping any '"/"' quotation mark pairs into a file and then run perl file.pl from the root of the repository to run the code. Alternatively, you can manually insert the items...

If the listed items are:

  • ... misspelled, then please correct them instead of using the command.
  • ... names, please add them to .github/actions/spell-check/dictionary/names.txt.
  • ... APIs, you can add them to a file in .github/actions/spell-check/dictionary/.
  • ... just things you're using, please add them to an appropriate file in .github/actions/spell-check/expect/.
  • ... tokens you only need in one place and shouldn't generally be used, you can add an item in an appropriate file in .github/actions/spell-check/patterns/.

See the README.md in each directory for more information.

🔬 You can test your commits without appending to a PR by creating a new branch with that extra change and pushing it to your fork. The :check-spelling action will run in response to your push -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. 😉

⚠️ Reviewers

At present, the action that triggered this message will not show its ❌ in this PR unless the branch is within this repository.
Thus, you should make sure that this comment has been addressed before encouraging the merge bot to merge this PR.

Please sign in to comment.