391 lines
14 KiB
C#
391 lines
14 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Gets index of line end in given string, both absolute and relative to start of line.
|
|
/// </summary>
|
|
/// <param name="str">String to search in.</param>
|
|
/// <param name="line">Line to get end index of.</param>
|
|
/// <returns>(absolute line end index of string, line end index relative to line start).</returns>
|
|
/// <exception cref="ArgumentOutOfRangeException">
|
|
/// Argument must be greater than 0 and less than or equal to number of lines in
|
|
/// <paramref name="str" />.
|
|
/// </exception>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="str">String to search in.</param>
|
|
/// <param name="line">Line to find end of in string.</param>
|
|
/// <returns>(absolute line end index of string, line end index relative to line start adjusted for color tags).</returns>
|
|
internal static (int total, int relative) GetEndIndexOfColoredLine(string str, int line)
|
|
{
|
|
var (total, relative) = GetEndIndexOfPlainLine(str, line);
|
|
return RemoveColorTagFromIdx(str, total, relative);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adjusts index of color tags on line.
|
|
/// </summary>
|
|
/// <remarks>Assumes that <see cref="tidx"/> is index of something not a color tag.</remarks>
|
|
/// <param name="str">String containing the indexes.</param>
|
|
/// <param name="tidx">Total index of line end.</param>
|
|
/// <param name="ridx">Relative index of line end.</param>
|
|
/// <returns>(<see cref="tidx"/>, <see cref="ridx"/>) adjusted for color tags on line.</returns>
|
|
private static (int total, int relative) RemoveColorTagFromIdx(string str, int tidx, int ridx)
|
|
{
|
|
var lineStartIdx = tidx - ridx;
|
|
var colorTagFiller = 0;
|
|
|
|
var tmp = str.LastIndexOf("</color", tidx);
|
|
var lastWasStart = true;
|
|
var colorTagStart = str.LastIndexOf("<color=", tidx);
|
|
|
|
if (tmp > 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("</color", colorTagStart);
|
|
lastWasStart = false;
|
|
}
|
|
else
|
|
{
|
|
colorTagStart = str.LastIndexOf("<color=", colorTagStart);
|
|
lastWasStart = true;
|
|
}
|
|
}
|
|
return (tidx - colorTagFiller, ridx - colorTagFiller);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the zero indexed line number of given <see cref="matchIdx"/>.
|
|
/// </summary>
|
|
/// <param name="str">String to search in.</param>
|
|
/// <param name="matchIdx">Index to find line number of.</param>
|
|
/// <returns>Line number of given index in string.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds first match of <see cref="criteria"/> in given string.
|
|
/// </summary>
|
|
/// <param name="str">String to search in.</param>
|
|
/// <param name="criteria">Search options.</param>
|
|
/// <param name="regx">Used when <see cref="criteria"/> specifies regex search.</param>
|
|
/// <param name="startIdx">Index to start the search at.</param>
|
|
/// <returns>(start index of match, length of match)</returns>
|
|
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;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Finds index of first occurence of <see cref="filter"/> in <see cref="str"/>.
|
|
/// </summary>
|
|
/// <param name="str">String to search through</param>
|
|
/// <param name="len">Length of <see cref="str"/></param>
|
|
/// <param name="filter">Needle to find</param>
|
|
/// <param name="flen">Lenght of <see cref="filter"/></param>
|
|
/// <param name="startIdx">Index to start search from</param>
|
|
/// <param name="caseSensitive">Whether search ignore casing</param>
|
|
/// <returns>index of first match or -1</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds index of <see cref="filter"/> matching for whole words.
|
|
/// </summary>
|
|
/// <param name="str">String to search in.</param>
|
|
/// <param name="startIdx">Index to start search from.</param>
|
|
/// <param name="filter">Key to search for.</param>
|
|
/// <param name="opt">Options for string comparison.</param>
|
|
/// <returns>Index of match or -1.</returns>
|
|
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;
|
|
}
|
|
}
|
|
} |