using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Unity.Burst.Editor
{
internal struct SearchCriteria
{
internal string filter;
internal bool isCaseSensitive;
internal bool isWholeWords;
internal bool isRegex;
internal SearchCriteria(string keyword, bool caseSensitive, bool wholeWord, bool regex)
{
filter = keyword;
isCaseSensitive = caseSensitive;
isWholeWords = wholeWord;
isRegex = regex;
}
internal bool Equals(SearchCriteria obj) =>
filter == obj.filter && isCaseSensitive == obj.isCaseSensitive && isWholeWords == obj.isWholeWords && isRegex == obj.isRegex;
public override bool Equals(object obj) =>
obj is SearchCriteria other && Equals(other);
public override int GetHashCode() => base.GetHashCode();
}
internal static class BurstStringSearch
{
///
/// Gets index of line end in given string, both absolute and relative to start of line.
///
/// String to search in.
/// Line to get end index of.
/// (absolute line end index of string, line end index relative to line start).
///
/// Argument must be greater than 0 and less than or equal to number of lines in
/// .
///
internal static (int total, int relative) GetEndIndexOfPlainLine (string str, int line)
{
var lastIdx = -1;
var newIdx = -1;
for (var i = 0; i <= line; i++)
{
lastIdx = newIdx;
newIdx = str.IndexOf('\n', lastIdx + 1);
if (newIdx == -1 && i < line)
{
throw new ArgumentOutOfRangeException(nameof(line),
"Argument must be greater than 0 and less than or equal to number of lines in str.");
}
}
lastIdx++;
return newIdx != -1 ? (newIdx, newIdx - lastIdx) : (str.Length - 1, str.Length - 1 - lastIdx);
}
///
/// Gets index of line end in given string, both absolute and relative to start of line.
/// Adjusts the index so color tags are not included in relative index.
///
/// String to search in.
/// Line to find end of in string.
/// (absolute line end index of string, line end index relative to line start adjusted for color tags).
internal static (int total, int relative) GetEndIndexOfColoredLine(string str, int line)
{
var (total, relative) = GetEndIndexOfPlainLine(str, line);
return RemoveColorTagFromIdx(str, total, relative);
}
///
/// Adjusts index of color tags on line.
///
/// Assumes that is index of something not a color tag.
/// String containing the indexes.
/// Total index of line end.
/// Relative index of line end.
/// (, ) adjusted for color tags on line.
private static (int total, int relative) RemoveColorTagFromIdx(string str, int tidx, int ridx)
{
var lineStartIdx = tidx - ridx;
var colorTagFiller = 0;
var tmp = str.LastIndexOf(" colorTagStart)
{
// color tag end was closest
lastWasStart = false;
colorTagStart = tmp;
}
while (colorTagStart != -1 && colorTagStart >= lineStartIdx)
{
var colorTagEnd = str.IndexOf('>', colorTagStart);
// +1 as the index is zero based.
colorTagFiller += colorTagEnd - colorTagStart + 1;
if (lastWasStart)
{
colorTagStart = str.LastIndexOf("
/// Finds the zero indexed line number of given .
///
/// String to search in.
/// Index to find line number of.
/// Line number of given index in string.
internal static int FindLineNr(string str, int matchIdx)
{
var lineNr = 0;
var idxn = str.IndexOf('\n');
while (idxn != -1 && idxn < matchIdx)
{
lineNr++;
idxn = str.IndexOf('\n', idxn + 1);
}
return lineNr;
}
///
/// Finds first match of in given string.
///
/// String to search in.
/// Search options.
/// Used when specifies regex search.
/// Index to start the search at.
/// (start index of match, length of match)
internal static (int idx, int length) FindMatch(string str, SearchCriteria criteria, Regex regx, int startIdx = 0)
{
var idx = -1;
var len = 0;
if (criteria.isRegex)
{
// regex will have the appropriate options in it if isCaseSensitive or/and isWholeWords is true.
var res = regx.Match(str, startIdx);
if (res.Success) (idx, len) = (res.Index, res.Length);
}
else if (criteria.isWholeWords)
{
(idx, len) = (IndexOfWholeWord(str, startIdx, criteria.filter, criteria.isCaseSensitive
? StringComparison.InvariantCulture
: StringComparison.InvariantCultureIgnoreCase), criteria.filter.Length);
}
else
{
unsafe
{
fixed (char* source = str)
{
fixed (char* target = criteria.filter)
{
(idx, len) = (
IndexOfCustom(source, str.Length, target, criteria.filter.Length, startIdx, criteria.isCaseSensitive)
, criteria.filter.Length);
}
}
}
}
return (idx, len);
}
internal static List<(int idx, int length)> FindAllMatches(string str, SearchCriteria criteria, Regex regx,
int startIdx = 0)
{
var retVal = new List<(int, int)>();
if (criteria.isRegex)
{
var res = regx.Matches(str, startIdx);
foreach (Match match in res)
{
retVal.Add((match.Index, match.Length));
}
}
else if (criteria.isWholeWords)
{
retVal.AddRange(IndexOfWholeWordAll(str, startIdx, criteria.filter,
criteria.isCaseSensitive
? StringComparison.InvariantCulture
: StringComparison.CurrentCultureIgnoreCase));
}
else
{
unsafe
{
fixed (char* source = str)
{
fixed (char* target = criteria.filter)
{
retVal.AddRange(FindAllIndices(source, str.Length, target, criteria.filter.Length, startIdx, criteria.isCaseSensitive));
}
}
}
}
return retVal;
}
private static char ToUpper(char c) => c - 97U > 25U ? c : (char)(c - 32U);
private static unsafe int ScanForFilterInsensitive(char* str, char* filter, int flen, int i)
{
int j = 0;
while (j < flen && ToUpper(str[i + j]) == ToUpper(filter[j]))
{
j++;
}
return j;
}
private static unsafe int ScanForFilter(char* str, char* filter, int flen, int i)
{
int j = 0;
while (j < flen && str[i + j] == filter[j])
{
j++;
}
return j;
}
private static unsafe List<(int, int)> FindAllIndices(char* str, int len, char* filter, int flen, int startIdx, bool caseSensitive)
{
var retVal = new List<(int,int)>();
if (len < flen) { return retVal; }
int stop = len - flen;
if (caseSensitive)
{
for (int i = startIdx; i < stop; i++)
{
if (ScanForFilter(str, filter, flen, i) == flen)
{
retVal.Add((i, flen));
i += flen - 1;
}
}
}
else
{
for (int i = startIdx; i < stop; i++)
{
if (ScanForFilterInsensitive(str, filter, flen, i) == flen)
{
retVal.Add((i, flen));
i += flen-1;
}
}
}
return retVal;
}
///
/// Finds index of first occurence of in .
///
/// String to search through
/// Length of
/// Needle to find
/// Lenght of
/// Index to start search from
/// Whether search ignore casing
/// index of first match or -1
private static unsafe int IndexOfCustom(char* str, int len, char* filter, int flen, int startIdx, bool caseSensitive)
{
if (len < flen) { return -1; }
int stop = len - flen;
if (caseSensitive)
{
for (int i = startIdx; i < stop; i++)
{
if (ScanForFilter(str, filter, flen, i) == flen)
{
return i;
}
}
}
else
{
for (int i = startIdx; i < stop; i++)
{
if (ScanForFilterInsensitive(str, filter, flen, i) == flen)
{
return i;
}
}
}
return -1;
}
///
/// Finds index of matching for whole words.
///
/// String to search in.
/// Index to start search from.
/// Key to search for.
/// Options for string comparison.
/// Index of match or -1.
private static int IndexOfWholeWord(string str, int startIdx, string filter, StringComparison opt)
{
const string wholeWordMatch = @"\w";
var j = startIdx;
var filterLen = filter.Length;
var strLen = str.Length;
while (j < strLen && (j = str.IndexOf(filter, j, opt)) >= 0)
{
var noPrior = true;
if (j != 0)
{
var frontBorder = str[j - 1];
noPrior = !Regex.IsMatch(frontBorder.ToString(), wholeWordMatch);
}
var noAfter = true;
if (j + filterLen != strLen)
{
var endBorder = str[j + filterLen];
noAfter = !Regex.IsMatch(endBorder.ToString(), wholeWordMatch);
}
if (noPrior && noAfter) return j;
j++;
}
return -1;
}
private static List<(int idx, int len)> IndexOfWholeWordAll(string str, int startIdx, string filter, StringComparison opt)
{
const string wholeWordMatch = @"\w";
var retVal = new List<(int, int)>();
var j = startIdx;
var filterLen = filter.Length;
var strLen = str.Length;
while (j < strLen && (j = str.IndexOf(filter, j, opt)) >= 0)
{
var noPrior = true;
if (j != 0)
{
var frontBorder = str[j - 1];
noPrior = !Regex.IsMatch(frontBorder.ToString(), wholeWordMatch);
}
var noAfter = true;
if (j + filterLen != strLen)
{
var endBorder = str[j + filterLen];
noAfter = !Regex.IsMatch(endBorder.ToString(), wholeWordMatch);
}
if (noPrior && noAfter)
{
retVal.Add((j, filterLen));
j += filterLen - 1;
}
j++;
}
return retVal;
}
}
}