You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2819 lines
111 KiB
2819 lines
111 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Runtime.CompilerServices; |
|
using System.Text; |
|
using System.Text.RegularExpressions; |
|
using UnityEngine; |
|
using UnityEditor; |
|
|
|
[assembly: InternalsVisibleTo("Unity.Burst.Editor.Tests")] |
|
|
|
namespace Unity.Burst.Editor |
|
{ |
|
internal class LongTextArea |
|
{ |
|
internal const float naturalEnhancedPad = 20f; |
|
private const int kMaxFragment = 2048; |
|
|
|
internal struct Fragment |
|
{ |
|
public int lineCount; |
|
public string text; |
|
} |
|
public enum Direction |
|
{ |
|
Left, |
|
Right, |
|
Up, |
|
Down |
|
} |
|
internal struct Branch |
|
{ |
|
public BurstDisassembler.AsmEdge Edge; |
|
|
|
public Rect StartHorizontal; |
|
public Rect VerticalLine; |
|
public Rect EndHorizontal; |
|
|
|
public Rect UpperLine; |
|
public Rect LowerLine; |
|
public float UpperAngle; |
|
public float LowerAngle; |
|
|
|
public Branch(BurstDisassembler.AsmEdge edge, Rect startHorizontal, Rect verticalLine, Rect endHorizontal, Rect upperLine, Rect lowerLine, float angle1, float angle2) |
|
{ |
|
Edge = edge; |
|
|
|
StartHorizontal = startHorizontal; |
|
VerticalLine = verticalLine; |
|
EndHorizontal = endHorizontal; |
|
|
|
UpperLine = upperLine; |
|
LowerLine = lowerLine; |
|
UpperAngle = angle1; |
|
LowerAngle = angle2; |
|
} |
|
} |
|
|
|
internal float fontHeight = 0.0f; |
|
internal float fontWidth = 0.0f; |
|
|
|
public string GetText => m_Text; |
|
|
|
private string m_Text = ""; |
|
private int _mTextLines = 0; |
|
private List<Fragment> m_Fragments = null; |
|
private bool invalidated = true; |
|
internal Vector2 finalAreaSize; |
|
|
|
private static readonly Texture2D backgroundTexture = Texture2D.whiteTexture; |
|
private static readonly GUIStyle textureStyle = new GUIStyle { normal = new GUIStyleState { background = backgroundTexture } }; |
|
|
|
internal float horizontalPad = 50.0f; |
|
|
|
internal int[] lineDepth = null; |
|
private bool[] _folded = null; |
|
internal int[] blockLine = null; |
|
private List<Fragment>[] _blocksFragments = null; |
|
|
|
private Fragment[] _blocksFragmentsStart = null; |
|
|
|
private Fragment GetFragmentStart(int blockIdx) |
|
{ |
|
AddFoldedString(blockIdx); |
|
|
|
return _blocksFragmentsStart[blockIdx]; |
|
} |
|
|
|
// Need two separate caches for start of blocks fragments, as we possibly differentiate between |
|
// rendered line and copied. |
|
private Fragment[] _blocksFragmentsStartPlain = null; |
|
private Fragment GetFragmentStartPlain(int blockIdx) |
|
{ |
|
AddFoldedStringColorless(blockIdx); |
|
|
|
return _blocksFragmentsStartPlain[blockIdx]; |
|
} |
|
|
|
// Used for searching when text is colored: |
|
internal List<Fragment>[] blocksFragmentsPlain = null; |
|
|
|
internal List<Fragment> GetPlainFragments(int blockIdx) |
|
{ |
|
blocksFragmentsPlain[blockIdx] ??= RecomputeFragmentsFromBlock(blockIdx, false); |
|
|
|
return blocksFragmentsPlain[blockIdx]; |
|
} |
|
|
|
private BurstDisassembler _disassembler; |
|
|
|
private int _selectBlockStart = -1; |
|
private float _selectStartY = -1f; |
|
private int _selectBlockEnd = -1; |
|
private float _selectEndY = -1f; |
|
|
|
private float _renderStartY = 0.0f; |
|
private int _renderBlockStart = -1; |
|
private int _renderBlockEnd = -1; |
|
private int _initialLineCount = -1; |
|
|
|
private bool _mouseDown = false; |
|
private bool _mouseOutsideBounds = true; |
|
internal Vector2 selectPos = Vector2.zero; |
|
internal Vector2 selectDragPos = Vector2.zero; |
|
|
|
public bool HasSelection { get; private set; } |
|
|
|
private Color _selectionColor; |
|
private readonly Color _selectionColorDarkmode = new Color(0f, .6f, .9f, .5f); |
|
private readonly Color _selectionColorLightmode = new Color(0f, 0f, .9f, .2f); |
|
|
|
private Color _hoverBoxColor; |
|
private Color _hoverTextColor; |
|
private readonly Color _hoverBoxColorDarkMode = new Color(.33f, .33f, .33f); |
|
private readonly Color _hoverBoxColorLightMode = new Color(.88f, .88f, .88f); |
|
|
|
// Current active hit should just be selection color |
|
private readonly Color _searchHitColor = new Color(.5f, .2f, .2f, .5f); |
|
|
|
private SearchCriteria _prevCriteria; |
|
private int _activeSearchHitIdx = -1; |
|
internal readonly Dictionary<int, List<(int startIdx, int endIdx, int nr)>> searchHits = new Dictionary<int, List<(int startIdx, int endIdx, int nr)>>(); |
|
|
|
private int _nrSearchHits = 0; |
|
|
|
public int NrSearchHits => _nrSearchHits; |
|
public int ActiveSearchNr => _activeSearchHitIdx; |
|
|
|
private readonly Color[] _colourWheel = new Color[] |
|
{Color.blue, Color.cyan, Color.green, Color.magenta, Color.red, Color.yellow, Color.white, Color.black}; |
|
|
|
internal (int idx, int length) textSelectionIdx = (0, 0); |
|
private bool _textSelectionIdxValid = true; |
|
|
|
internal (int blockIdx, int textIdx) enhancedTextSelectionIdxStart = (0, 0); |
|
internal (int blockIdx, int textIdx) enhancedTextSelectionIdxEnd = (0, 0); |
|
|
|
internal bool CopyColorTags { get; private set; } = true; |
|
|
|
private bool _oldShowBranchMarkers = false; |
|
|
|
internal const int regLineThickness = 2; |
|
private const int highlightLineThickness = 3; |
|
private float _verticalPad = 0; |
|
|
|
internal Branch hoveredBranch; |
|
private BurstDisassembler.AsmEdge _prevHoveredEdge; |
|
|
|
private string _jobName; |
|
|
|
// Have this field, as NextHit needs the actual horizontal padding |
|
// in order to have correct horizontal scroll. |
|
private float _actualHorizontalPad = 0; |
|
|
|
internal int MaxLineDepth; |
|
|
|
/// <returns> |
|
/// (idx in regards to whole <see cref="str"/> , where color tags are removed from this line, idx from this line with color tags removed) |
|
/// </returns> |
|
internal readonly Func<string, int, (int total, int relative)> GetEndIndexOfColoredLine = BurstStringSearch.GetEndIndexOfColoredLine; |
|
|
|
|
|
internal readonly Func<string, int, (int total, int relative)> GetEndIndexOfPlainLine = BurstStringSearch.GetEndIndexOfPlainLine; |
|
|
|
|
|
internal bool IsTextSet(string jobName) |
|
{ |
|
return _jobName == jobName; |
|
} |
|
|
|
public void SetText(string jobName, string textToRender, bool isDarkMode, BurstDisassembler disassembler, bool useDisassembler) |
|
{ |
|
selectDragPos = Vector2.zero; |
|
StopSearching(); |
|
StopSelection(); |
|
|
|
_jobName = jobName; |
|
|
|
|
|
if (!useDisassembler) |
|
{ |
|
m_Text = textToRender; |
|
_disassembler = null; |
|
m_Fragments = RecomputeFragments(m_Text); |
|
horizontalPad = 0.0f; |
|
} |
|
else |
|
{ |
|
m_Fragments = null; |
|
SetDisassembler(disassembler); |
|
|
|
(_selectionColor, _hoverBoxColor, _hoverTextColor) = isDarkMode |
|
? (_selectionColorDarkmode, _hoverBoxColorDarkMode, _hoverBoxColorLightMode) |
|
: (_selectionColorLightmode, _hoverBoxColorLightMode, _hoverBoxColorDarkMode); |
|
} |
|
|
|
invalidated = true; |
|
} |
|
|
|
public void ExpandAllBlocks() |
|
{ |
|
StopSelection(); |
|
int blockIdx = 0; |
|
foreach (var block in _disassembler.Blocks) |
|
{ |
|
var changed = _folded[blockIdx]; |
|
var blockLongEnoughForFold = block.Length > 1; |
|
|
|
_folded[blockIdx] = false; |
|
if (changed && blockLongEnoughForFold) |
|
{ |
|
finalAreaSize.y += Math.Max(block.Length - 1, 1) * fontHeight; |
|
} |
|
blockIdx++; |
|
} |
|
} |
|
|
|
public void FocusCodeBlocks() |
|
{ |
|
StopSelection(); |
|
var blockIdx = 0; |
|
foreach (var block in _disassembler.Blocks) |
|
{ |
|
bool changed = false; |
|
switch (block.Kind) |
|
{ |
|
case BurstDisassembler.AsmBlockKind.None: |
|
case BurstDisassembler.AsmBlockKind.Directive: |
|
case BurstDisassembler.AsmBlockKind.Block: |
|
case BurstDisassembler.AsmBlockKind.Data: |
|
if (!_folded[blockIdx]) |
|
{ |
|
changed = true; |
|
} |
|
_folded[blockIdx] = true; |
|
break; |
|
case BurstDisassembler.AsmBlockKind.Code: |
|
if (_folded[blockIdx]) |
|
{ |
|
changed = true; |
|
} |
|
_folded[blockIdx] = false; |
|
break; |
|
} |
|
|
|
if (changed) |
|
{ |
|
if (_folded[blockIdx]) |
|
{ |
|
finalAreaSize.y -= Math.Max(block.Length - 1, 1) * fontHeight; |
|
} |
|
else |
|
{ |
|
finalAreaSize.y += Math.Max(block.Length - 1, 1) * fontHeight; |
|
} |
|
|
|
} |
|
blockIdx++; |
|
} |
|
} |
|
|
|
private void ComputeInitialLineCount() |
|
{ |
|
var blockIdx = 0; |
|
_initialLineCount = 0; |
|
foreach (var block in _disassembler.Blocks) |
|
{ |
|
switch (block.Kind) |
|
{ |
|
case BurstDisassembler.AsmBlockKind.None: |
|
case BurstDisassembler.AsmBlockKind.Directive: |
|
case BurstDisassembler.AsmBlockKind.Block: |
|
case BurstDisassembler.AsmBlockKind.Data: |
|
_folded[blockIdx] = true; |
|
break; |
|
case BurstDisassembler.AsmBlockKind.Code: |
|
_folded[blockIdx] = false; |
|
break; |
|
} |
|
_initialLineCount += _folded[blockIdx] ? 1 : block.Length; |
|
blockIdx++; |
|
} |
|
} |
|
|
|
public void SetDisassembler(BurstDisassembler disassembler) |
|
{ |
|
_disassembler = disassembler; |
|
if (disassembler == null) |
|
{ |
|
return; |
|
} |
|
|
|
var numBlocks = _disassembler.Blocks.Count; |
|
var numLinesFromBlock = new int[numBlocks]; |
|
lineDepth = new int[numBlocks]; |
|
_folded = new bool[numBlocks]; |
|
blockLine = new int[numBlocks]; |
|
_blocksFragments = new List<Fragment>[numBlocks]; |
|
_blocksFragmentsStart = new Fragment[numBlocks]; |
|
_blocksFragmentsStartPlain = new Fragment[numBlocks]; |
|
blocksFragmentsPlain = new List<Fragment>[numBlocks]; |
|
|
|
ComputeInitialLineCount(); |
|
|
|
// Count edges |
|
var edgeCount = 0; |
|
StringBuilder sb = new StringBuilder(); |
|
for (int i = 0; i < numBlocks; i++) |
|
{ |
|
sb.Append(_disassembler.GetOrRenderBlockToTextUncached(i, false)); |
|
|
|
var block = _disassembler.Blocks[i]; |
|
if (block.Edges != null) |
|
{ |
|
foreach (var edge in block.Edges) |
|
{ |
|
if (edge.Kind == BurstDisassembler.AsmEdgeKind.OutBound) |
|
{ |
|
edgeCount++; |
|
} |
|
} |
|
} |
|
} |
|
m_Text = sb.ToString(); |
|
|
|
var edgeArray = new BurstDisassembler.AsmEdge[edgeCount]; |
|
var edgeIndex = 0; |
|
foreach (var block in _disassembler.Blocks) |
|
{ |
|
if (block.Edges != null) |
|
{ |
|
foreach (var edge in block.Edges) |
|
{ |
|
if (edge.Kind == BurstDisassembler.AsmEdgeKind.OutBound) |
|
{ |
|
edgeArray[edgeIndex++] = edge; |
|
} |
|
} |
|
} |
|
} |
|
|
|
Array.Sort(edgeArray, (a, b) => |
|
{ |
|
var src1BlockIdx = a.OriginRef.BlockIndex; |
|
var src1Line = _disassembler.Blocks[src1BlockIdx].LineIndex; |
|
src1Line += a.OriginRef.LineIndex; |
|
var dst1BlockIdx = a.LineRef.BlockIndex; |
|
var dst1Line = _disassembler.Blocks[dst1BlockIdx].LineIndex; |
|
dst1Line += a.LineRef.LineIndex; |
|
var Len1 = Math.Abs(src1Line - dst1Line); |
|
var src2BlockIdx = b.OriginRef.BlockIndex; |
|
var src2Line = _disassembler.Blocks[src2BlockIdx].LineIndex; |
|
src2Line += b.OriginRef.LineIndex; |
|
var dst2BlockIdx = b.LineRef.BlockIndex; |
|
var dst2Line = _disassembler.Blocks[dst2BlockIdx].LineIndex; |
|
dst2Line += b.LineRef.LineIndex; |
|
var Len2 = Math.Abs(src2Line - dst2Line); |
|
return Len1 - Len2; |
|
}); |
|
|
|
// Iterate through the blocks to precompute the widths for branches |
|
var maxLine = 0; |
|
foreach (var edge in edgeArray) |
|
{ |
|
if (edge.Kind != BurstDisassembler.AsmEdgeKind.OutBound) continue; |
|
|
|
var s = edge.OriginRef.BlockIndex; |
|
var e = edge.LineRef.BlockIndex; |
|
if (e == s + 1) |
|
{ |
|
continue; // don't render if its pointing to next line |
|
} |
|
|
|
var m = 0; |
|
|
|
var l = s; |
|
var le = e; |
|
if (e < s) |
|
{ |
|
l = e; |
|
le = s; |
|
} |
|
|
|
for (; l <= le; l++) |
|
{ |
|
numLinesFromBlock[l]++; |
|
if (m < numLinesFromBlock[l]) |
|
{ |
|
m = numLinesFromBlock[l]; |
|
} |
|
if (maxLine < m) |
|
{ |
|
maxLine = m; |
|
} |
|
} |
|
|
|
lineDepth[s] = m; |
|
} |
|
|
|
MaxLineDepth = maxLine; |
|
|
|
horizontalPad = naturalEnhancedPad + maxLine * 10; |
|
} |
|
|
|
// Changing the font size doesn't update the text field, so added this to force a recalculation |
|
public void Invalidate() |
|
{ |
|
invalidated = true; |
|
} |
|
|
|
private struct HoverBox |
|
{ |
|
public Rect box; |
|
public string info; |
|
/// <summary> |
|
/// Indicates start and end column of hovered token. |
|
/// </summary> |
|
public (int start, int end) columnRange; |
|
public int lineNumber; |
|
public bool valid; |
|
} |
|
|
|
private HoverBox hover; |
|
|
|
public void Interact(Rect workingArea) |
|
{ |
|
if (_disassembler == null) |
|
{ |
|
return; |
|
} |
|
|
|
var pos = GetMousePosForHover(); |
|
if (!workingArea.Contains(pos)) |
|
{ |
|
// Mouse position outside of assembly view |
|
return; |
|
} |
|
|
|
// lineNumber (absolute) |
|
var lineNumber = Mathf.FloorToInt(pos.y / fontHeight); |
|
for (int idx = 0; idx < _disassembler.Blocks.Count; idx++) |
|
{ |
|
var block = _disassembler.Blocks[idx]; |
|
if (lineNumber < block.LineIndex) |
|
{ |
|
break; |
|
} |
|
if (_folded[idx]) |
|
{ |
|
lineNumber += block.Length - 1; |
|
} |
|
} |
|
var column = Mathf.FloorToInt((pos.x - horizontalPad) / fontWidth); |
|
|
|
var sameToken = hover.lineNumber == lineNumber && |
|
BurstMath.WithinRange(hover.columnRange.start, hover.columnRange.end, column); |
|
if (hover.valid && (sameToken || hover.box.Contains(pos))) |
|
{ |
|
// Use the cached info text. |
|
return; |
|
} |
|
// Cached instruction info text is not valid. So try query new. |
|
hover.valid = false; |
|
|
|
if (column < 0 || lineNumber < 0 || lineNumber >= _disassembler.Lines.Count) |
|
{ |
|
return; |
|
} |
|
|
|
int tokIdx; |
|
try |
|
{ |
|
// block 0 is queried as the absolut line number is given to the function. |
|
// This will give us the correct assembly token, as the function queries |
|
// assembly line based on offset from block start => line lineNumber is queried. |
|
tokIdx = _disassembler.GetTokenIndexFromColumn(0, lineNumber, column, out _); |
|
} |
|
catch (ArgumentOutOfRangeException) |
|
{ |
|
return; |
|
} |
|
if (tokIdx < 0) |
|
{ |
|
return; |
|
} |
|
// Found match |
|
|
|
var token = _disassembler.GetToken(tokIdx); |
|
var tokenString = _disassembler.GetTokenAsText(token); |
|
|
|
if (token.Kind != BurstDisassembler.AsmTokenKind.Instruction && |
|
token.Kind != BurstDisassembler.AsmTokenKind.BranchInstruction && |
|
token.Kind != BurstDisassembler.AsmTokenKind.CallInstruction && |
|
token.Kind != BurstDisassembler.AsmTokenKind.FunctionBegin && |
|
token.Kind != BurstDisassembler.AsmTokenKind.FunctionEnd && |
|
token.Kind != BurstDisassembler.AsmTokenKind.JumpInstruction && |
|
token.Kind != BurstDisassembler.AsmTokenKind.ReturnInstruction && |
|
token.Kind != BurstDisassembler.AsmTokenKind.InstructionSIMD) |
|
{ |
|
return; |
|
} |
|
if (_disassembler.GetInstructionInformation(tokenString, out var info)) |
|
{ |
|
hover.valid = true; |
|
// Don't know the actual size needed yet. |
|
hover.box = new Rect(pos, Vector2.zero); |
|
hover.info = info; |
|
hover.lineNumber = lineNumber; |
|
|
|
// Find column range of instruction: |
|
var line = _disassembler.Lines[lineNumber]; |
|
var idx = line.ColumnIndex + (tokIdx - line.TokenIndex - 1); |
|
var colStart = _disassembler.ColumnIndices[idx]; |
|
var colEnd = colStart + token.Length - 1; |
|
hover.columnRange = (colStart, colEnd); |
|
} |
|
} |
|
|
|
// Not clipped, so use only if you know the line will be within the UI element |
|
private void DrawLine(Vector2 start, Vector2 end, int width) |
|
{ |
|
var matrix = GUI.matrix; |
|
Vector2 distance = end - start; |
|
float angle = Mathf.Rad2Deg * Mathf.Atan(distance.y / distance.x); |
|
if (distance.x < 0) |
|
{ |
|
angle += 180; |
|
} |
|
|
|
int width2 = (int)Mathf.Ceil(width / 2); |
|
|
|
Rect pos = new Rect(start.x, start.y - width2, distance.magnitude, width); |
|
|
|
GUIUtility.RotateAroundPivot(angle, start); |
|
GUI.DrawTexture(pos, backgroundTexture); |
|
GUI.matrix = matrix; // restore initial matrix to avoid floating point inaccuracies with RotateAroundPivot(...) |
|
} |
|
|
|
private void DrawLine(Rect line, float angle) |
|
{ |
|
var matrix = GUI.matrix; |
|
GUIUtility.RotateAroundPivot(angle, new Vector2(line.x, line.center.y)); |
|
GUI.DrawTexture(line, backgroundTexture); |
|
GUI.matrix = matrix; // restore initial matrix to avoid floating point inaccuracies with RotateAroundPivot(...) |
|
} |
|
|
|
private void DrawHover(GUIStyle style, Rect workingArea) |
|
{ |
|
if (!hover.valid) |
|
{ |
|
return; |
|
} |
|
if (hover.box.size == Vector2.zero) |
|
{ |
|
// Hover size has not been initialized yet. |
|
var sizex = (hover.info.Length + 1.5f) * fontWidth; |
|
var sizey = 2f * fontHeight; |
|
var posx = hover.box.x + 10; |
|
var posy = hover.box.y; |
|
if (posx + sizex > workingArea.width) |
|
{ |
|
// Box exceeds rightmost bound so cramp it in |
|
var availableSpace = workingArea.xMax - posx; |
|
var possiblyCharacters = Mathf.FloorToInt(availableSpace / fontWidth); |
|
var neededLines = hover.info.Length / possiblyCharacters + 2; // +2 to ensure enough lines for the label |
|
|
|
var availableSpacey = workingArea.yMax - posy; |
|
var possibleLines = Mathf.FloorToInt(availableSpacey / fontHeight); |
|
|
|
var tmp = neededLines - possibleLines; |
|
if (tmp > 0) |
|
{ |
|
posy -= tmp * fontHeight; |
|
} |
|
|
|
sizey = neededLines * fontHeight + fontHeight; |
|
sizex = availableSpace; |
|
} |
|
hover.box.size = new Vector2(sizex, sizey); |
|
hover.box.position = new Vector2(posx, posy); |
|
} |
|
var col = GUI.color; |
|
GUI.color = _hoverBoxColor; |
|
|
|
var pos = hover.box.position; |
|
var size = hover.box.size; |
|
GUI.Box(hover.box, "", textureStyle); |
|
|
|
GUI.color = _hoverTextColor; |
|
var hoverStyle = style; |
|
hoverStyle.wordWrap = true; |
|
GUI.Label( |
|
new Rect(pos.x + fontWidth * 0.5f, |
|
pos.y + fontHeight * 0.5f, |
|
size.x - fontWidth/2f, |
|
size.y - fontHeight), |
|
hover.info, hoverStyle); |
|
|
|
GUI.color = col; |
|
} |
|
|
|
private void MakeBranchThin(ref Branch branch) |
|
{ |
|
int lineThickness = regLineThickness; |
|
|
|
branch.StartHorizontal.height = lineThickness; |
|
branch.VerticalLine.width = lineThickness; |
|
branch.EndHorizontal.height = lineThickness; |
|
|
|
branch.UpperLine.height = lineThickness; |
|
branch.LowerLine.height = lineThickness; |
|
|
|
// Adjusting position for arrow tip for thicker lines. |
|
branch.UpperLine.y += (highlightLineThickness - regLineThickness); |
|
|
|
branch.UpperLine.position -= new Vector2(.5f, .5f); |
|
branch.LowerLine.position -= new Vector2(.5f, -.5f); |
|
|
|
// Make end part of arrow expand upwards. |
|
branch.EndHorizontal.y += (highlightLineThickness - regLineThickness); |
|
} |
|
|
|
/// <summary> |
|
/// Use this for hover, as there is a slight visual mis-balance |
|
/// between cursor position and visual cursor. |
|
/// </summary> |
|
private Vector2 GetMousePosForHover() |
|
{ |
|
Vector2 mousePos = Event.current.mousePosition; |
|
mousePos.y -= 0.5f; |
|
mousePos.x += 0.5f; |
|
return mousePos; |
|
} |
|
|
|
/// <summary> |
|
/// Calculate the position and size of an edge, and return it as a branch. |
|
/// </summary> |
|
/// <param name="edge"> The edge to base branch on. </param> |
|
/// <param name="x"> Start x position of branch. </param> |
|
/// <param name="y1"> Start y position of branch. </param> |
|
/// <param name="y2"> End y position of branch. </param> |
|
/// <param name="w"> Depth of branch. </param> |
|
private Branch CalculateBranch(BurstDisassembler.AsmEdge edge, float x, float y1, float y2, int w) |
|
{ |
|
bool isEdgeHovered = edge.Equals(_prevHoveredEdge); |
|
|
|
int lineThickness = isEdgeHovered |
|
? highlightLineThickness |
|
: regLineThickness; |
|
|
|
// Calculate rectangles for branch arrows: |
|
var start = new Vector2(x, y1 + _verticalPad); |
|
var end = start + new Vector2(-(w * 10), 0); |
|
|
|
var branchStartPos = new Rect(end.x - lineThickness / 2, start.y - 1, start.x - end.x + lineThickness / 2, lineThickness); |
|
|
|
start = end; |
|
end = start + new Vector2(0, y2 - y1); |
|
|
|
var branchVerticalPartPos = end.y < start.y |
|
? new Rect(start.x - 1, end.y, lineThickness, start.y - end.y) |
|
: new Rect(start.x - 1, start.y, lineThickness, end.y - start.y); |
|
|
|
start = end; |
|
end = start + new Vector2(w * 10, 0); |
|
|
|
var branchEndPos = new Rect(start.x - lineThickness / 2, start.y - 1, end.x - start.x, lineThickness); |
|
|
|
// Calculate rectangles for arrow tip. |
|
Vector2 lowerArrowTipStart = end; |
|
Vector2 upperArrowtipStart = end; |
|
|
|
// Moving the arrow tips closer together. |
|
upperArrowtipStart += new Vector2(0, 0.5f); |
|
lowerArrowTipStart -= new Vector2(0, 0.5f); |
|
|
|
// Upper arrow tip. |
|
var upperArrowTipEnd = upperArrowtipStart + new Vector2(-5, -5); |
|
|
|
var upperLine = new Rect(upperArrowtipStart.x, upperArrowtipStart.y - (int)Mathf.Ceil(lineThickness / 2), (upperArrowTipEnd - upperArrowtipStart).magnitude, lineThickness); |
|
|
|
// Lower arrow tip. |
|
var lowerArrowtipEnd = lowerArrowTipStart + new Vector2(-5, 5); |
|
var lowerLine = new Rect(lowerArrowTipStart.x, lowerArrowTipStart.y - (int)Mathf.Ceil(lineThickness / 2), (lowerArrowtipEnd - lowerArrowTipStart).magnitude, lineThickness); |
|
|
|
if (isEdgeHovered) |
|
{ |
|
// Adjusting position for arrow tip for thicker lines. |
|
upperArrowtipStart.y -= (highlightLineThickness - regLineThickness); |
|
upperArrowTipEnd.y -= (highlightLineThickness - regLineThickness); |
|
upperLine.y -= (highlightLineThickness - regLineThickness); |
|
|
|
upperLine.position += new Vector2(.5f, .5f); |
|
lowerLine.position += new Vector2(.5f, -.5f); |
|
|
|
// Make end part of arrow expand upwards. |
|
branchEndPos.y -= (highlightLineThickness - regLineThickness); |
|
} |
|
|
|
var branch = new Branch(edge, branchStartPos, branchVerticalPartPos, branchEndPos, upperLine, lowerLine, |
|
BurstMath.CalculateAngle(upperArrowtipStart, upperArrowTipEnd), BurstMath.CalculateAngle(lowerArrowTipStart, lowerArrowtipEnd)); |
|
|
|
// Handling whether mouse is hovering over edge. |
|
Vector2 mousePos = GetMousePosForHover(); |
|
|
|
// Rotate mousePos so it seems like lower arrow tip is not rotated. |
|
Vector2 lowerArrowTipPivot = lowerArrowTipStart; |
|
lowerArrowTipPivot.y -= (int)Mathf.Ceil(lineThickness / 2); |
|
Vector2 angledMouseLower = BurstMath.AnglePoint(BurstMath.CalculateAngle(lowerArrowTipPivot, lowerArrowtipEnd), mousePos, new Vector2(lowerLine.x, lowerLine.center.y)); |
|
angledMouseLower.y -= (int)Mathf.Ceil(lineThickness / 2); |
|
|
|
Vector2 upperArrowTipPivot = upperArrowtipStart; |
|
upperArrowTipPivot.y += (int)Mathf.Ceil(lineThickness / 2); |
|
Vector2 angleMouseUpper = BurstMath.AnglePoint(BurstMath.CalculateAngle(upperArrowTipPivot, upperArrowTipEnd) - 360, mousePos, new Vector2(upperLine.x, upperLine.center.y)); |
|
angleMouseUpper.y += (int)Mathf.Ceil(lineThickness / 2); |
|
|
|
if (BurstMath.AdjustedContains(branchStartPos, mousePos) || BurstMath.AdjustedContains(branchVerticalPartPos, mousePos) || BurstMath.AdjustedContains(branchEndPos, mousePos) |
|
|| BurstMath.AdjustedContains(lowerLine, angledMouseLower) || BurstMath.AdjustedContains(upperLine, angleMouseUpper)) |
|
{ |
|
// Handling whether another branch was already made thick is done in DrawBranch(...). |
|
if (!hoveredBranch.Edge.Equals(default(BurstDisassembler.AsmEdge)) && hoveredBranch.Edge.Equals(_prevHoveredEdge)) |
|
{ |
|
return branch; |
|
} |
|
hoveredBranch = branch; |
|
} |
|
return branch; |
|
} |
|
|
|
|
|
|
|
/// <summary> |
|
/// Draws a branch as a jump-able set of boxes. |
|
/// </summary> |
|
/// <param name="branch"> The branch to draw. </param> |
|
/// <param name="w"> Depth of the branch. </param> |
|
/// <param name="colourWheel"> Array of possible colors for branches. </param> |
|
/// <param name="workingArea"> Current view in inspector. </param> |
|
private void DrawBranch(Branch branch, int w, Rect workingArea) |
|
{ |
|
bool isBranchHovered = branch.Edge.Equals(hoveredBranch.Edge); |
|
Vector2 scrollPos = workingArea.position; |
|
|
|
int lineThickness = isBranchHovered |
|
? highlightLineThickness |
|
: regLineThickness; |
|
|
|
GUI.color = _colourWheel[w % _colourWheel.Length]; |
|
|
|
// Check if hovered but not made thick yet: |
|
if (isBranchHovered && !branch.Edge.Equals(_prevHoveredEdge)) |
|
{ |
|
// alter thickness as edge is hovered over. |
|
branch.StartHorizontal.height = highlightLineThickness; |
|
branch.VerticalLine.width = highlightLineThickness; |
|
branch.EndHorizontal.height = highlightLineThickness; |
|
|
|
branch.UpperLine.height = highlightLineThickness; |
|
branch.LowerLine.height = highlightLineThickness; |
|
|
|
// Adjusting position for arrow tip for thicker lines. |
|
branch.UpperLine.y -= (highlightLineThickness - regLineThickness); |
|
|
|
branch.UpperLine.position += new Vector2(.5f, .5f); |
|
branch.LowerLine.position += new Vector2(.5f, -.5f); |
|
|
|
// Make end part of arrow expand upwards. |
|
branch.EndHorizontal.y -= (highlightLineThickness - regLineThickness); |
|
} |
|
else if (branch.EndHorizontal.height == highlightLineThickness && !isBranchHovered) |
|
{ |
|
// the branch was previously hovered, but is now hidden behind |
|
// another branch, that is the one being visually hovered. |
|
MakeBranchThin(ref branch); |
|
} |
|
|
|
// Render actual arrows: |
|
GUI.Box(branch.StartHorizontal, "", textureStyle); |
|
GUI.Box(branch.VerticalLine, "", textureStyle); |
|
|
|
float yy = branch.EndHorizontal.y - scrollPos.y; |
|
if (yy >= 0 && yy < workingArea.height) |
|
{ |
|
GUI.Box(branch.EndHorizontal, "", textureStyle); |
|
DrawLine(branch.UpperLine, branch.UpperAngle); |
|
DrawLine(branch.LowerLine, branch.LowerAngle); |
|
} |
|
|
|
// Use below instead of buttons, to make the currently hovered edge the jump-able, |
|
// and not the edge rendered first i.e. when two edges overlap. |
|
if (Event.current.type == EventType.MouseDown && isBranchHovered) |
|
{ |
|
Vector2 mousePos = GetMousePosForHover(); |
|
|
|
// Rotate mousePos so it seems like lower arrow tip is not rotated. |
|
Vector2 lowerLineEnd = branch.LowerLine.position; |
|
lowerLineEnd.y += (int)Mathf.Ceil(lineThickness / 2); |
|
lowerLineEnd += new Vector2(-5, 5); |
|
|
|
Vector2 angledMouseLower = BurstMath.AnglePoint(BurstMath.CalculateAngle(branch.LowerLine.position, lowerLineEnd), |
|
mousePos, new Vector2(branch.LowerLine.x, branch.LowerLine.center.y)); |
|
angledMouseLower.y -= (int)Mathf.Ceil(lineThickness / 2); |
|
|
|
// Rotate mousePos so it seems like upper arrow tip isn't rotated. |
|
Vector2 upperArrowTipPivot = branch.UpperLine.position; |
|
upperArrowTipPivot.y += 2 * (int)Mathf.Ceil(lineThickness / 2); |
|
|
|
Vector2 upperLineEnd = branch.UpperLine.position; |
|
upperLineEnd.y += (int)Mathf.Ceil(lineThickness / 2); |
|
upperLineEnd += new Vector2(-5, -5); |
|
|
|
Vector2 angleMouseUpper = BurstMath.AnglePoint(BurstMath.CalculateAngle(upperArrowTipPivot, upperLineEnd) - 360, |
|
Event.current.mousePosition, new Vector2(branch.UpperLine.x, branch.UpperLine.center.y)); |
|
angleMouseUpper.y += (int)Mathf.Ceil(lineThickness / 2); |
|
|
|
// Se if a jump should be made and jump. |
|
if (BurstMath.AdjustedContains(branch.StartHorizontal, mousePos)) |
|
{ |
|
// make end arrow be at mouse cursor. |
|
var target = branch.EndHorizontal; |
|
target.y += branch.StartHorizontal.y < branch.EndHorizontal.y |
|
? (workingArea.yMax - mousePos.y) |
|
: (workingArea.yMin - mousePos.y + highlightLineThickness / 2f); |
|
GUI.ScrollTo(target); |
|
Event.current.Use(); |
|
} |
|
else if (BurstMath.AdjustedContains(branch.EndHorizontal, mousePos) || BurstMath.AdjustedContains(branch.LowerLine, angledMouseLower) |
|
|| BurstMath.AdjustedContains(branch.UpperLine, angleMouseUpper) || BurstMath.AdjustedContains(branch.VerticalLine, mousePos)) |
|
{ |
|
var target = branch.StartHorizontal; |
|
target.y += branch.StartHorizontal.y < branch.EndHorizontal.y |
|
? workingArea.yMin - mousePos.y + highlightLineThickness / 2 |
|
: workingArea.yMax - mousePos.y; |
|
GUI.ScrollTo(target); |
|
Event.current.Use(); |
|
} |
|
} |
|
} |
|
|
|
private bool DrawFold(float x, float y, bool state, BurstDisassembler.AsmBlockKind kind) |
|
{ |
|
var currentBg = GUI.backgroundColor; |
|
switch (kind) |
|
{ |
|
case BurstDisassembler.AsmBlockKind.None: |
|
case BurstDisassembler.AsmBlockKind.Directive: |
|
case BurstDisassembler.AsmBlockKind.Block: |
|
GUI.backgroundColor = Color.grey; |
|
break; |
|
case BurstDisassembler.AsmBlockKind.Code: |
|
GUI.backgroundColor = Color.green; |
|
break; |
|
case BurstDisassembler.AsmBlockKind.Data: |
|
GUI.backgroundColor = Color.magenta; |
|
break; |
|
} |
|
|
|
var pressed = false; |
|
if (state) |
|
{ |
|
pressed = GUI.Button(new Rect(x - fontWidth, y, fontWidth, fontHeight), "+"); |
|
} |
|
else |
|
{ |
|
pressed = GUI.Button(new Rect(x - fontWidth, y, fontWidth, fontHeight), "-"); |
|
} |
|
|
|
GUI.backgroundColor = currentBg; |
|
|
|
return pressed; |
|
} |
|
|
|
internal void Layout(GUIStyle style, float hPad) |
|
{ |
|
var oldFontHeight = fontHeight; |
|
var oldFontWidth = fontWidth; |
|
|
|
#if UNITY_2023_1_OR_NEWER |
|
var cacheWidth0 = style.CalcSize(new GUIContent("W")); |
|
var cacheWidth1 = style.CalcSize(new GUIContent("WW")); |
|
|
|
var cacheHeight0 = style.CalcSize(new GUIContent("\n")); |
|
var cacheHeight1 = style.CalcSize(new GUIContent("\n\n")); |
|
|
|
fontHeight = cacheHeight1.y - cacheHeight0.y; |
|
fontWidth = cacheWidth1.x - cacheWidth0.x; |
|
#else |
|
var cacheSize0 = style.CalcSize(new GUIContent("W\n")); |
|
var cacheSize1 = style.CalcSize(new GUIContent("WW\n\n")); |
|
|
|
fontHeight = cacheSize1.y - cacheSize0.y; |
|
fontWidth = cacheSize1.x - cacheSize0.x; |
|
#endif |
|
|
|
_verticalPad = fontHeight * 0.5f; |
|
|
|
// oldFontWidth == 0 means we picked the first target after opening inspector. |
|
var diffX = oldFontWidth != 0 ? fontWidth / oldFontWidth : 0.0f; |
|
|
|
if (HasSelection && (oldFontWidth != fontWidth || oldFontHeight != fontHeight)) |
|
{ |
|
float diffY = fontHeight / oldFontHeight; |
|
|
|
// We only have to take padding into account for x-axis, as it's the only one with it. |
|
selectPos = new Vector2(((selectPos.x - hPad) * diffX) + hPad, diffY * selectPos.y); |
|
selectDragPos = new Vector2(((selectDragPos.x - hPad) * diffX) + hPad, diffY * selectDragPos.y); |
|
} |
|
|
|
invalidated = false; |
|
var oldFinalAreaSizeX = finalAreaSize.x; |
|
finalAreaSize = new Vector2(0.0f, 0.0f); |
|
|
|
if (_disassembler == null) |
|
{ |
|
LayoutPlain(style); |
|
} |
|
else |
|
{ |
|
finalAreaSize.y = _initialLineCount * fontHeight; |
|
finalAreaSize.x = oldFinalAreaSizeX * diffX; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Find accumulated fragment number the block start at. |
|
/// </summary> |
|
/// <param name="start">Block index to start search at. Should match <see cref="acc"/>.</param> |
|
/// <param name="acc">Accumulated fragment number that block <see cref="start"/> start at.</param> |
|
internal int GetFragNrFromBlockIdx(int blockIdx, int start, int acc, ref float positionY) |
|
{ |
|
int fragNr = acc; |
|
|
|
for (int b = start; b < blockIdx; b++) |
|
{ |
|
if (_folded[b]) |
|
{ |
|
fragNr += GetPlainFragments(b).Count; |
|
positionY += fontHeight; |
|
} |
|
else |
|
{ |
|
foreach (var frag in GetPlainFragments(b)) |
|
{ |
|
fragNr++; |
|
positionY += frag.lineCount * fontHeight; |
|
} |
|
} |
|
} |
|
|
|
return fragNr; |
|
} |
|
|
|
internal bool SearchText(SearchCriteria searchCriteria, Regex regx, ref Rect workingArea, bool stopFirstMatch = false, bool focusTop = false) => |
|
_disassembler == null |
|
? SearchTextPlain(searchCriteria, regx, ref workingArea, stopFirstMatch, focusTop) |
|
: SearchTextEnhanced(searchCriteria, regx, ref workingArea, stopFirstMatch, focusTop); |
|
|
|
/// <summary> |
|
/// Searches <see cref="m_Text"/> for every match, and saves each match in <see cref="searchHits"/>. |
|
/// </summary> |
|
/// <param name="criteria">Search options.</param> |
|
/// <param name="regx">Used if <see cref="criteria"/> specifies a regex search.</param> |
|
/// <param name="workingArea">Inspector View.</param> |
|
/// <param name="stopFirstMatch">Whether search should stop at first match.</param> |
|
/// <param name="focusTop">Whether match focus should be at the top of view.</param> |
|
private bool SearchTextEnhanced(SearchCriteria criteria, Regex regx, ref Rect workingArea, bool stopFirstMatch = false, bool focusTop = false) |
|
{ |
|
var renderStartY = workingArea.y; |
|
var renderStartX = workingArea.x; |
|
|
|
_nrSearchHits = 0; |
|
searchHits.Clear(); |
|
_activeSearchHitIdx = -1; |
|
|
|
bool doRepaint = false; |
|
|
|
float positionY = 0f; |
|
int accFragNr = 0; |
|
int accBlockNr = 0; |
|
int accIdx; |
|
int idx = 0; |
|
int len; |
|
|
|
var run = true; |
|
while (run && idx < m_Text.Length && ((idx, len) = BurstStringSearch.FindMatch(m_Text, criteria, regx, idx)).idx != -1) |
|
{ |
|
if (stopFirstMatch) run = false; |
|
if (len <= 0) |
|
{ |
|
idx++; |
|
continue; |
|
} |
|
int idxEnd = idx + len; |
|
|
|
// Find block + fragment match starts in: |
|
var blockStartInfo = _disassembler.GetBlockIdxFromTextIdx(idx, accBlockNr, blocksFragmentsPlain.Length-1); |
|
// search hit within a folded block => unfold block. |
|
if (_folded[blockStartInfo.idx]) FoldUnfoldBlock(blockStartInfo.idx); |
|
// Finds the accumulated fragment number of the first fragment in block |
|
accFragNr = GetFragNrFromBlockIdx(blockStartInfo.idx, accBlockNr, accFragNr, ref positionY); |
|
|
|
int fragNrStart; |
|
// Finds actual accumulated fragment we just reached and it's index |
|
(fragNrStart, accIdx) = GetFragNrFromEnhancedTextIdx(idx, blockStartInfo.idx, accFragNr, blockStartInfo.l, ref positionY); |
|
|
|
var frag1Text = GetPlainFragments(blockStartInfo.idx)[fragNrStart - accFragNr].text; |
|
|
|
// Make idx relative to fragment: |
|
int idxStartInFrag1 = idx - accIdx; |
|
int idxEndInFrag1 = Math.Min(idxStartInFrag1 + len, frag1Text.Length); |
|
|
|
// positionY should be stationed at top of fragment we have hit in. |
|
// Need to push it down to the specific line the hit is in. But keep this in a temporary variable so as to not fuck up the property of positionY!!! |
|
int exactLineNr = BurstStringSearch.FindLineNr(frag1Text, idxStartInFrag1); |
|
float exactPositionY = positionY + exactLineNr * fontHeight; |
|
|
|
// Find block + fragment match ends in: |
|
var blockEndInfo = _disassembler.GetBlockIdxFromTextIdx(idxEnd, blockStartInfo.idx, blocksFragmentsPlain.Length-1); |
|
// Finds the accumulated fragment number of match's last fragment |
|
accFragNr = GetFragNrFromBlockIdx(blockEndInfo.idx, blockStartInfo.idx, accFragNr, ref positionY); |
|
int fragNrEnd; |
|
// Finds the actual accumulated fragment number of match last fragment |
|
(fragNrEnd, accIdx) = GetFragNrFromEnhancedTextIdx(idxEnd, blockEndInfo.idx, accFragNr, blockEndInfo.l, ref positionY); |
|
|
|
accBlockNr = blockEndInfo.idx; |
|
|
|
if (!searchHits.ContainsKey(fragNrStart)) searchHits.Add(fragNrStart, new List<(int startIdx, int endIdx, int nr)>()); |
|
searchHits[fragNrStart].Add((idxStartInFrag1, idxEndInFrag1, _nrSearchHits)); |
|
|
|
if (fragNrStart < fragNrEnd) |
|
{ |
|
// match span multiple fragments |
|
fragNrStart++; // Already captured this fragment |
|
|
|
// For every fragment in-between first and last we capture the whole fragment |
|
for (; fragNrStart < fragNrEnd; fragNrStart++) |
|
{ |
|
if (!searchHits.ContainsKey(fragNrStart)) searchHits.Add(fragNrStart, new List<(int startIdx, int endIdx, int nr)>()); |
|
searchHits[fragNrStart].Add((0, int.MaxValue, _nrSearchHits)); // Max value to indicate it's the entire fragment |
|
} |
|
int idxStartInFragN = accIdx; |
|
int idxEndInFragN = idxEnd - idxStartInFragN; |
|
|
|
if (!searchHits.ContainsKey(fragNrStart)) searchHits.Add(fragNrStart, new List<(int startIdx, int endIdx, int nr)>()); |
|
searchHits[fragNrStart].Add((0, idxEndInFragN, _nrSearchHits)); |
|
} |
|
|
|
if (_activeSearchHitIdx < 0 && exactPositionY >= renderStartY) |
|
{ |
|
workingArea = SetScrollOnView(workingArea, frag1Text, exactLineNr, idxStartInFrag1, renderStartX, exactPositionY); |
|
doRepaint = true; |
|
} |
|
idx += len; |
|
_nrSearchHits++; |
|
} |
|
return doRepaint; |
|
} |
|
|
|
private Rect SetScrollOnView(Rect workingArea, string text, int lineNr, int matchIdx, float renderStartX, float goToPosY) |
|
{ |
|
_activeSearchHitIdx = _nrSearchHits; |
|
// Make it scroll to this hit |
|
var startOfLine = lineNr > 0 ? GetEndIndexOfPlainLine(text, lineNr - 1).total + 1 : 0; |
|
|
|
|
|
float positionX = matchIdx - startOfLine; |
|
if (positionX * fontWidth > renderStartX) positionX += matchIdx + 3 * fontWidth; |
|
else positionX -= 3 * fontWidth; |
|
|
|
workingArea.x = positionX; |
|
workingArea.y = goToPosY + 5 * fontHeight; |
|
|
|
return workingArea; |
|
} |
|
|
|
/// <remarks>Works on non-colored text.</remarks> |
|
/// <param name="goal">Index we want fragment number from.</param> |
|
/// <param name="blockNrStart">Block to start search in.</param> |
|
/// <param name="fragNrStart">Accumulated fragment number of <see cref="blockNrStart"/>.</param> |
|
/// <param name="start">Start index of <see cref="blockNrStart"/>.</param> |
|
/// <returns>(accumulated fragment number, total index for returning fragment number)</returns> |
|
internal (int fragNr, int idx) GetFragNrFromEnhancedTextIdx(int goal, int blockNrStart, int fragNrStart, int start, ref float positionY) |
|
{ |
|
bool goThroughfrags(int goal, int blockIdx, ref int start, ref int f, ref float positionY) |
|
{ |
|
var frags = GetPlainFragments(blockIdx); |
|
for (int i = 0; i < frags.Count; i++) |
|
{ |
|
Fragment frag = frags[i]; |
|
int len = frag.text.Length; |
|
if (start + len >= goal) |
|
{ |
|
return true; |
|
} |
|
start += len + 1; // +1 because of newline |
|
f++; |
|
positionY += frag.lineCount * fontHeight; |
|
} |
|
return false; |
|
} |
|
|
|
int f = fragNrStart; |
|
|
|
for (int b = blockNrStart; b < blocksFragmentsPlain.Length; b++) |
|
{ |
|
if (goThroughfrags(goal, b, ref start, ref f, ref positionY)) break; |
|
} |
|
|
|
return (f, start); |
|
} |
|
|
|
/// <returns>(accumulated fragment number, total index for returning fragment number)</returns> |
|
private (int fragNr, int idx) GetFragNrFromPlainTextIdx(int goal, int fragNrStart, int start, ref float positionY) |
|
{ |
|
int i = fragNrStart; |
|
for (; i < m_Fragments.Count; i++) |
|
{ |
|
Fragment frag = m_Fragments[i]; |
|
|
|
int len = frag.text.Length; |
|
if (start + len >= goal) |
|
{ |
|
break; |
|
} |
|
start += len + 1; // +1 because of newline |
|
positionY += frag.lineCount * fontHeight; |
|
} |
|
|
|
return (i, start); |
|
} |
|
|
|
private bool SearchTextPlain(SearchCriteria criteria, Regex regx, ref Rect workingArea, bool stopFirstMatch = false, bool focusTop = false) |
|
{ |
|
var renderStartX = workingArea.x; |
|
var renderStartY = workingArea.y; |
|
|
|
_nrSearchHits = 0; |
|
searchHits.Clear(); |
|
_activeSearchHitIdx = -1; |
|
|
|
bool doRepaint = false; |
|
|
|
float positionY = 0f; |
|
int idx = 0; |
|
int len; |
|
int accFragNr = 0; |
|
int accFragIdx = 0; |
|
var run = true; |
|
while (run && idx < m_Text.Length && ((idx, len) = BurstStringSearch.FindMatch(m_Text, criteria, regx, idx)).idx != -1) |
|
{ |
|
if (stopFirstMatch) run = false; |
|
if (len <= 0) |
|
{ |
|
idx++; |
|
continue; |
|
} |
|
int idxEnd = idx + len; |
|
|
|
// Find fragment match start in: |
|
(accFragNr, accFragIdx) = GetFragNrFromPlainTextIdx(idx, accFragNr, accFragIdx, ref positionY); |
|
int fragNrStart = accFragNr; |
|
|
|
string frag1Text = m_Fragments[fragNrStart].text; |
|
|
|
// Get relative index in fragment: |
|
int idxStartInFrag1 = idx - accFragIdx; |
|
int idxEndInFrag1 = Math.Min(idxStartInFrag1 + len, frag1Text.Length); |
|
|
|
int exactLineNr = BurstStringSearch.FindLineNr(frag1Text, idxStartInFrag1); |
|
float exactPositionY = positionY + exactLineNr * fontHeight; |
|
|
|
// Find fragment match end in: |
|
(accFragNr, accFragIdx) = GetFragNrFromPlainTextIdx(idxEnd, accFragNr, accFragIdx, ref positionY); |
|
int fragNrEnd = accFragNr; |
|
|
|
|
|
if (!searchHits.ContainsKey(fragNrStart)) searchHits.Add(fragNrStart, new List<(int startIdx, int endIdx, int nr)>()); |
|
searchHits[fragNrStart].Add((idxStartInFrag1, idxEndInFrag1, _nrSearchHits)); |
|
|
|
if (fragNrStart < fragNrEnd) |
|
{ |
|
// They must directly follow each other |
|
int fragNr = fragNrStart + 1; |
|
for (; fragNr < fragNrEnd; fragNr++) |
|
{ |
|
if (!searchHits.ContainsKey(fragNr)) searchHits.Add(fragNr, new List<(int startIdx, int endIdx, int nr)>()); |
|
searchHits[fragNr].Add((0, m_Fragments[fragNr].text.Length, _nrSearchHits)); |
|
} |
|
|
|
int idxStartInFrag2 = idxEnd - accFragIdx; |
|
int idxEndInFrag2 = idxEnd - accFragIdx; |
|
|
|
if (!searchHits.ContainsKey(fragNr)) searchHits.Add(fragNr, new List<(int startIdx, int endIdx, int nr)>()); |
|
searchHits[fragNr].Add((idxStartInFrag2, idxEndInFrag2, _nrSearchHits)); |
|
} |
|
|
|
if (_activeSearchHitIdx < 0 && exactPositionY >= renderStartY) |
|
{ |
|
workingArea = SetScrollOnView(workingArea, frag1Text, exactLineNr, idxStartInFrag1, renderStartX, exactPositionY); |
|
// ScrollToDuringRepaint(frag1Text, exactLineNr, idxStartInFrag1, len, renderStartX, exactPositionY, workingArea.height, focusTop); |
|
doRepaint = true; |
|
} |
|
idx += len; |
|
_nrSearchHits++; |
|
} |
|
return doRepaint; |
|
} |
|
|
|
private void LayoutPlain(GUIStyle style) |
|
{ |
|
foreach (var frag in m_Fragments) |
|
{ |
|
// Calculate the size as we have hidden control codes in the string |
|
var size = style.CalcSize(new GUIContent(frag.text)); |
|
finalAreaSize.x = Math.Max(finalAreaSize.x, size.x); |
|
finalAreaSize.y += frag.lineCount * fontHeight; |
|
} |
|
} |
|
|
|
private void AddFoldedStringColorless(int blockIdx) |
|
{ |
|
if (_blocksFragmentsStartPlain[blockIdx].lineCount != 0) return; |
|
|
|
string text = null; |
|
if (!_disassembler.IsColored) |
|
{ |
|
_blocksFragments[blockIdx] ??= RecomputeFragmentsFromBlock(blockIdx); |
|
text = _blocksFragments[blockIdx][0].text; |
|
} |
|
else |
|
{ |
|
text = GetPlainFragments(blockIdx)[0].text; |
|
} |
|
var endOfFirstLineIdx = text.IndexOf('\n'); |
|
if (endOfFirstLineIdx == -1) endOfFirstLineIdx = text.Length; |
|
|
|
_blocksFragmentsStartPlain[blockIdx] = new Fragment() { lineCount = 1, text = text.Substring(0, endOfFirstLineIdx) + " ..." }; |
|
} |
|
|
|
private void AddFoldedString(int blockIdx) |
|
{ |
|
if (_blocksFragmentsStart[blockIdx].lineCount != 0) return; |
|
|
|
// precomputing every block to avoid having runtime check at every access later on |
|
_blocksFragments[blockIdx] ??= RecomputeFragmentsFromBlock(blockIdx); |
|
|
|
// precompute string to present for folded block. |
|
var text = _blocksFragments[blockIdx][0].text; |
|
|
|
var endOfFirstLineIdx = text.IndexOf('\n'); |
|
if (endOfFirstLineIdx == -1) endOfFirstLineIdx = text.Length; |
|
|
|
_blocksFragmentsStart[blockIdx] = new Fragment() { lineCount = 1, text = text.Substring(0, endOfFirstLineIdx) + " ..." }; |
|
} |
|
|
|
internal void LayoutEnhanced(GUIStyle style, Rect workingArea, bool showBranchMarkers) |
|
{ |
|
Vector2 scrollPos = workingArea.position; |
|
if (showBranchMarkers != _oldShowBranchMarkers) |
|
{ |
|
// need to alter selection according to padding on x-axis. |
|
if (showBranchMarkers) |
|
{ |
|
selectPos.x += (horizontalPad - naturalEnhancedPad); |
|
selectDragPos.x += (horizontalPad - naturalEnhancedPad); |
|
} |
|
else |
|
{ |
|
selectPos.x -= (horizontalPad - naturalEnhancedPad); |
|
selectDragPos.x -= (horizontalPad - naturalEnhancedPad); |
|
} |
|
} |
|
_oldShowBranchMarkers = showBranchMarkers; |
|
|
|
// Also computes the first and last blocks to render this time and ensures |
|
float positionY = 0.0f; |
|
int lNum = 0; |
|
_renderBlockStart = -1; |
|
_renderBlockEnd = -1; |
|
|
|
_selectBlockStart = -1; |
|
_selectStartY = -1f; |
|
_selectBlockEnd = -1; |
|
_selectEndY = -1f; |
|
|
|
for (int idx = 0; idx<_disassembler.Blocks.Count; idx++) |
|
{ |
|
var block = _disassembler.Blocks[idx]; |
|
var blockHeight = block.Length * fontHeight; |
|
var lHeight = block.Length; |
|
|
|
if (_folded[idx]) |
|
{ |
|
blockHeight = fontHeight; |
|
lHeight = 1; |
|
} |
|
|
|
blockLine[idx] = lNum; |
|
|
|
if (_selectBlockStart == -1 && selectPos.y - blockHeight <= positionY) |
|
{ |
|
_selectBlockStart = idx; |
|
_selectStartY = positionY; |
|
} |
|
if (_selectBlockEnd == -1 && (selectDragPos.y - blockHeight <= positionY || idx == _disassembler.Blocks.Count - 1)) |
|
{ |
|
_selectBlockEnd = idx; |
|
_selectEndY = positionY; |
|
} |
|
|
|
// Whole block is above view, or block starts below view. |
|
// If at last block and _renderBlockStart == -1, we must have had all block above our scrollPos. |
|
// Happens when Scroll view is at bottom and fontSize is decreased. As this forces the scroll view upwards, |
|
// we simply set the last block as the one being rendered (avoids an indexOutOfBounds exception). |
|
if (((positionY - scrollPos.y + blockHeight) < 0 || (positionY - scrollPos.y) > workingArea.size.y) |
|
&& !(idx == _disassembler.Blocks.Count - 1 && _renderBlockStart == -1)) |
|
{ |
|
positionY += blockHeight; |
|
lNum += lHeight; |
|
continue; |
|
} |
|
|
|
if (_renderBlockStart == -1) |
|
{ |
|
_renderBlockStart = idx; |
|
_renderStartY = positionY; |
|
} |
|
|
|
_renderBlockEnd = idx; |
|
|
|
if (_blocksFragments[idx] == null) |
|
{ |
|
AddFoldedString(idx); |
|
|
|
foreach (var fragment in _blocksFragments[idx]) |
|
{ |
|
style.CalcMinMaxWidth(new GUIContent(fragment.text), out _, out var maxWidth); |
|
if (finalAreaSize.x < maxWidth) |
|
{ |
|
finalAreaSize.x = maxWidth; |
|
} |
|
} |
|
} |
|
|
|
positionY += blockHeight; |
|
lNum += lHeight; |
|
} |
|
// selection is below last line of assembly. |
|
if (_selectBlockStart == -1) |
|
{ |
|
_selectBlockStart = _disassembler.Blocks.Count - 1; |
|
} |
|
} |
|
|
|
internal string GetStartingColorTag(int blockIdx, int textIdx) |
|
{ |
|
if (!CopyColorTags || !(_disassembler?.IsColored ?? false)) return ""; |
|
|
|
const int colorTagLength = 15; |
|
|
|
var text = _folded[blockIdx] |
|
? GetFragmentStart(blockIdx).text// _blocksFragmentsStart[blockIdx].text |
|
: _disassembler.GetOrRenderBlockToText(blockIdx); |
|
|
|
var colorTagIdx = text.LastIndexOf("<color=", textIdx); |
|
if (colorTagIdx == -1) return ""; |
|
|
|
// Checking that found tas is actually for this idx |
|
return text.IndexOf("</color>", colorTagIdx, textIdx - colorTagIdx) == -1 |
|
? text.Substring(colorTagIdx, colorTagLength) |
|
: ""; |
|
} |
|
|
|
internal string GetEndingColorTag(int blockIdx, int textIdx) |
|
{ |
|
if (!CopyColorTags || !(_disassembler?.IsColored ?? false)) return ""; |
|
|
|
var text = _folded[blockIdx] |
|
? GetFragmentStart(blockIdx).text// _blocksFragmentsStart[blockIdx].text |
|
: _disassembler.GetOrRenderBlockToText(blockIdx); |
|
|
|
var endColorTagIdx = text.IndexOf("</color>", textIdx); |
|
if (endColorTagIdx == -1) return ""; |
|
|
|
// Check that tag actually belongs for thid idx |
|
return text.IndexOf("<color=", textIdx, endColorTagIdx - textIdx) == -1 |
|
? "</color>" |
|
: ""; |
|
} |
|
|
|
internal void ChangeCopyMode() |
|
{ |
|
CopyColorTags = !CopyColorTags; |
|
|
|
// Need to update text selection fields as it should now ignore/use color tags |
|
_textSelectionIdxValid = false; |
|
UpdateEnhancedSelectTextIdx(_oldShowBranchMarkers ? horizontalPad : naturalEnhancedPad); |
|
} |
|
|
|
internal void DoSelectionCopy() |
|
{ |
|
// textIdxE = -1 to indicate we want till the end of the block of text. |
|
string GetStringFromBlock(int blockIdx, int textIdxS, int textIdxE = -1) |
|
{ |
|
string text; |
|
if (_folded[blockIdx]) |
|
{ |
|
text = (CopyColorTags |
|
? GetFragmentStart(blockIdx).text |
|
: GetFragmentStartPlain(blockIdx).text) |
|
+ '\n'; |
|
|
|
return textIdxE == -1 |
|
? text.Substring(textIdxS, text.Length - textIdxS) |
|
: text.Substring(textIdxS, textIdxE - textIdxS); |
|
} |
|
|
|
if (CopyColorTags) |
|
{ |
|
text = _disassembler.GetOrRenderBlockToText(blockIdx); |
|
if (textIdxE == -1) textIdxE = text.Length; |
|
} |
|
else |
|
{ |
|
text = m_Text; |
|
|
|
// Transform textIdxS and textIdxE to absolute index into m_Text |
|
var (blockStart, blockEnd) = _disassembler.BlockIdxs[blockIdx]; |
|
textIdxS += blockStart; |
|
if (textIdxE == -1) textIdxE = blockEnd + 1; // +1 to take newline as well |
|
else textIdxE += blockStart; |
|
} |
|
|
|
return text.Substring(textIdxS, textIdxE - textIdxS); |
|
} |
|
|
|
if (!HasSelection) return; |
|
|
|
var sb = new StringBuilder(); |
|
|
|
if (_disassembler != null && enhancedTextSelectionIdxStart != enhancedTextSelectionIdxEnd) |
|
{ |
|
if (enhancedTextSelectionIdxStart.blockIdx < enhancedTextSelectionIdxEnd.blockIdx) |
|
{ |
|
// Multiple block selection |
|
var blockIdx = enhancedTextSelectionIdxStart.blockIdx; |
|
|
|
sb.Append( |
|
GetStartingColorTag(enhancedTextSelectionIdxStart.blockIdx, enhancedTextSelectionIdxStart.textIdx) + |
|
GetStringFromBlock(blockIdx++, enhancedTextSelectionIdxStart.textIdx)); |
|
|
|
for (; blockIdx < enhancedTextSelectionIdxEnd.blockIdx; blockIdx++) |
|
{ |
|
sb.Append(GetStringFromBlock(blockIdx, 0)); |
|
} |
|
|
|
sb.Append( |
|
GetStringFromBlock(blockIdx, 0, enhancedTextSelectionIdxEnd.textIdx) + |
|
GetEndingColorTag(enhancedTextSelectionIdxEnd.blockIdx, enhancedTextSelectionIdxEnd.textIdx)); |
|
} |
|
else |
|
{ |
|
// Single block selection |
|
sb.Append( |
|
GetStartingColorTag(enhancedTextSelectionIdxStart.blockIdx, enhancedTextSelectionIdxStart.textIdx) + |
|
GetStringFromBlock( |
|
enhancedTextSelectionIdxStart.blockIdx, |
|
enhancedTextSelectionIdxStart.textIdx, |
|
enhancedTextSelectionIdxEnd.textIdx) + |
|
GetEndingColorTag(enhancedTextSelectionIdxEnd.blockIdx, enhancedTextSelectionIdxEnd.textIdx)); |
|
} |
|
} |
|
else |
|
{ |
|
sb.Append(m_Text.Substring(textSelectionIdx.idx, textSelectionIdx.length)); |
|
} |
|
EditorGUIUtility.systemCopyBuffer = sb.ToString(); |
|
} |
|
|
|
internal void SelectAll() |
|
{ |
|
HasSelection = true; |
|
selectPos = Vector2.zero; |
|
selectDragPos = finalAreaSize; |
|
|
|
if (_disassembler != null) |
|
{ |
|
enhancedTextSelectionIdxStart = (0, 0); |
|
var blockIdx = _disassembler.Blocks.Count - 1; |
|
enhancedTextSelectionIdxEnd = (blockIdx, |
|
!_folded[blockIdx] |
|
? _disassembler.GetOrRenderBlockToText(blockIdx).Length |
|
: GetFragmentStart(blockIdx).text.Length); |
|
_selectBlockStart = 0; |
|
_selectBlockEnd = blockIdx; |
|
_selectEndY = finalAreaSize.y - |
|
(_folded[blockIdx] ? fontHeight : _disassembler.Blocks[blockIdx].Length * fontHeight); |
|
} |
|
else |
|
{ |
|
textSelectionIdx = (0, m_Text.Length); |
|
} |
|
_textSelectionIdxValid = true; |
|
} |
|
|
|
private void ScrollDownToSelection(Rect workingArea) |
|
{ |
|
if (!workingArea.Contains(selectDragPos + new Vector2(fontWidth, fontHeight / 2))) |
|
{ |
|
GUI.ScrollTo(new Rect(selectDragPos + new Vector2(fontWidth, fontHeight), Vector2.zero)); |
|
} |
|
|
|
_textSelectionIdxValid = false; |
|
} |
|
|
|
private void ScrollUpToSelection(Rect workingArea) |
|
{ |
|
if (!workingArea.Contains(selectDragPos - new Vector2(0, fontHeight / 2))) |
|
{ |
|
GUI.ScrollTo(new Rect(selectDragPos - new Vector2(0, fontHeight), Vector2.zero)); |
|
} |
|
|
|
_textSelectionIdxValid = false; |
|
} |
|
|
|
|
|
internal void MoveSelectionLeft(Rect workingArea, bool showBranchMarkers) |
|
{ |
|
HasSelection = true; |
|
if (_disassembler != null) |
|
{ |
|
float hPad = showBranchMarkers ? horizontalPad : naturalEnhancedPad; |
|
string text; |
|
int prevLineIdx = Mathf.FloorToInt((selectDragPos.y - _selectEndY) / fontHeight) - 1; |
|
|
|
if (selectDragPos.x < hPad + fontWidth) |
|
{ |
|
if (prevLineIdx < 0 && _selectBlockEnd == 0) |
|
{ |
|
// we are at the beginning of the text. |
|
return; |
|
} |
|
|
|
if (prevLineIdx < 0 && _selectBlockEnd > 0) |
|
{ |
|
// get text from previous block, and do calculations for that. |
|
_selectBlockEnd--; |
|
_selectEndY -= _folded[_selectBlockEnd] |
|
? fontHeight |
|
: _disassembler.Blocks[_selectBlockEnd].Length * fontHeight; |
|
|
|
(text, prevLineIdx) = !_folded[_selectBlockEnd] |
|
? (_disassembler.GetOrRenderBlockToText(_selectBlockEnd), _disassembler.Blocks[_selectBlockEnd].Length - 1) |
|
: (GetFragmentStart(_selectBlockEnd).text + '\n', 0); |
|
} |
|
else |
|
{ |
|
text = _folded[_selectBlockEnd] |
|
? GetFragmentStart(_selectBlockEnd).text + '\n' |
|
: _disassembler.GetOrRenderBlockToText(_selectBlockEnd); |
|
} |
|
var charsInLine = GetEndIndexOfColoredLine(text, prevLineIdx).relative; |
|
|
|
selectDragPos.x = charsInLine * fontWidth + hPad + fontWidth / 2; |
|
selectDragPos.y -= fontHeight; |
|
} |
|
else |
|
{ |
|
// simply move selection left. |
|
text = _folded[_selectBlockEnd] |
|
? GetFragmentStart(_selectBlockEnd).text + '\n' |
|
: _disassembler.GetOrRenderBlockToText(_selectBlockEnd); |
|
var charsInLine = GetEndIndexOfColoredLine(text, prevLineIdx + 1).relative; |
|
|
|
if (selectDragPos.x > charsInLine * fontWidth + hPad) |
|
selectDragPos.x = hPad + charsInLine * fontWidth + fontWidth / 2; |
|
|
|
selectDragPos.x -= fontWidth; |
|
} |
|
} |
|
else |
|
{ |
|
int prevLineIdx = Mathf.FloorToInt((selectDragPos.y) / fontHeight) - 1; |
|
|
|
if (selectDragPos.x < fontWidth) |
|
{ |
|
if (prevLineIdx < 0) |
|
{ |
|
// we are at beginning of the text |
|
return; |
|
} |
|
|
|
int charsInLine = GetEndIndexOfPlainLine(m_Text, prevLineIdx).relative; |
|
|
|
selectDragPos.x = charsInLine * fontWidth + fontWidth / 2; |
|
selectDragPos.y -= fontHeight; |
|
} |
|
else |
|
{ |
|
// simply move selection left. |
|
int charsInLine = GetEndIndexOfPlainLine(m_Text, prevLineIdx+1).relative; |
|
if (selectDragPos.x > charsInLine * fontWidth) |
|
{ |
|
selectDragPos.x = charsInLine * fontWidth + fontWidth / 2; |
|
} |
|
|
|
selectDragPos.x -= fontWidth; |
|
} |
|
} |
|
|
|
// check if we moved outside of view and scroll if true. |
|
ScrollUpToSelection(workingArea); |
|
} |
|
|
|
internal void MoveSelectionRight(Rect workingArea, bool showBranchMarkers) |
|
{ |
|
HasSelection = true; |
|
if (_disassembler != null) |
|
{ |
|
var hPad = showBranchMarkers ? horizontalPad : naturalEnhancedPad; |
|
|
|
var text = _folded[_selectBlockEnd] |
|
? GetFragmentStart(_selectBlockEnd).text + '\n' |
|
: _disassembler.GetOrRenderBlockToText(_selectBlockEnd); |
|
|
|
var thisLine = Mathf.FloorToInt((selectDragPos.y - _selectEndY) / fontHeight); |
|
var charsInLine = GetEndIndexOfColoredLine(text, thisLine).relative; |
|
|
|
if (selectDragPos.x >= hPad + charsInLine * fontWidth) |
|
{ |
|
// move down a line: |
|
thisLine++; |
|
|
|
int lineCount = _folded[_selectBlockEnd] ? 1 : _disassembler.Blocks[_selectBlockEnd].Length; |
|
|
|
if (thisLine > lineCount && _selectBlockEnd == _disassembler.Blocks.Count - 1) |
|
{ |
|
// We are at the end of the text |
|
return; |
|
} |
|
|
|
if (thisLine > lineCount) |
|
{ |
|
// selected into next block. |
|
_selectBlockEnd++; |
|
_selectEndY += _folded[_selectBlockEnd - 1] |
|
? fontHeight |
|
: _disassembler.Blocks[_selectBlockEnd - 1].Length * fontHeight; |
|
} |
|
|
|
selectDragPos.x = hPad + fontWidth/2; |
|
selectDragPos.y += fontHeight; |
|
} |
|
else |
|
{ |
|
// simply move selection right. |
|
if (selectDragPos.x < hPad) |
|
{ |
|
selectDragPos.x = hPad + fontWidth / 2; |
|
} |
|
|
|
selectDragPos.x += fontWidth; |
|
} |
|
} |
|
else |
|
{ |
|
int thisLine = Mathf.FloorToInt((selectDragPos.y) / fontHeight); |
|
|
|
int charsInLine = GetEndIndexOfColoredLine(m_Text, thisLine).relative; |
|
|
|
if (selectDragPos.x >= charsInLine*fontWidth) |
|
{ |
|
thisLine++; |
|
|
|
if (thisLine >= _mTextLines) |
|
{ |
|
// we are at end of the text |
|
return; |
|
} |
|
|
|
selectDragPos.x = 0f; |
|
selectDragPos.y += fontHeight; |
|
|
|
} |
|
else |
|
{ |
|
// simply move selection right. |
|
selectDragPos.x += fontWidth; |
|
} |
|
} |
|
// check if we moved outside of view and scroll if true. |
|
ScrollDownToSelection(workingArea); |
|
} |
|
|
|
internal void MoveSelectionUp(Rect workingArea, bool showBranchMarkers) |
|
{ |
|
HasSelection = true; |
|
if (_disassembler != null) |
|
{ |
|
int thisLine = Mathf.FloorToInt((selectDragPos.y - _selectEndY) / fontHeight); |
|
|
|
// At the first line |
|
if (thisLine == 0 && _selectBlockEnd == 0) return; |
|
|
|
if (thisLine == 0) |
|
{ |
|
// We are moving on to a block above |
|
_selectBlockEnd--; |
|
_selectEndY -= _folded[_selectBlockEnd] |
|
? fontHeight |
|
: _disassembler.Blocks[_selectBlockEnd].Length * fontHeight; |
|
} |
|
} |
|
else |
|
{ |
|
int thisLine = Mathf.FloorToInt((selectDragPos.y) / fontHeight) - 1; |
|
|
|
if (thisLine < 0) return; |
|
} |
|
|
|
selectDragPos.y -= fontHeight; |
|
|
|
// check if we moved outside of view and scroll if true. |
|
ScrollUpToSelection(workingArea); |
|
} |
|
|
|
internal void MoveSelectionDown(Rect workingArea, bool showBranchMarkers) |
|
{ |
|
HasSelection = true; |
|
if (_disassembler != null) |
|
{ |
|
int thisLine = Mathf.FloorToInt((selectDragPos.y - _selectEndY) / fontHeight) + 1; |
|
|
|
int lineCount = _folded[_selectBlockEnd] ? 0 : _disassembler.Blocks[_selectBlockEnd].Length - 1; |
|
|
|
if (thisLine > lineCount) |
|
{ |
|
// At the last line |
|
if (_selectBlockEnd == _disassembler.Blocks.Count - 1) { return; } |
|
|
|
// Moved into next block |
|
_selectBlockEnd++; |
|
_selectEndY += _folded[_selectBlockEnd - 1] |
|
? fontHeight |
|
: _disassembler.Blocks[_selectBlockEnd - 1].Length * fontHeight; |
|
} |
|
} |
|
else |
|
{ |
|
int thisLine = Mathf.FloorToInt(selectDragPos.y / fontHeight) + 1; |
|
|
|
if (thisLine > _mTextLines) return; |
|
} |
|
|
|
selectDragPos.y += fontHeight; |
|
|
|
// check if we moved outside of view and scroll if true. |
|
ScrollDownToSelection(workingArea); |
|
} |
|
|
|
internal bool MouseOutsideView(Rect workingArea, Vector2 mousePos, int controlID) |
|
{ |
|
if (_mouseDown && !workingArea.Contains(mousePos)) |
|
{ |
|
// Mouse was dragged outside of the view. |
|
if (GUIUtility.hotControl == controlID && Event.current.rawType == EventType.MouseUp) |
|
{ |
|
_mouseDown = false; |
|
} |
|
|
|
_mouseOutsideBounds = true; |
|
return true; |
|
} |
|
|
|
_mouseOutsideBounds = false; |
|
return false; |
|
} |
|
|
|
private Vector2 MouseClickAndClamp(bool showBranchMarkers, Vector2 mousePos) |
|
{ |
|
HasSelection = true; |
|
if (_disassembler != null) |
|
{ |
|
float hPad = showBranchMarkers ? horizontalPad : naturalEnhancedPad; |
|
|
|
// Make sure we cannot set cursor in the horizontal padding: |
|
if (mousePos.x < hPad) mousePos.x = hPad; |
|
} |
|
return mousePos; |
|
} |
|
|
|
internal void MouseClicked(bool showBranchMarkers, bool withShift, Vector2 mousePos, int controlID) |
|
{ |
|
if (_mouseOutsideBounds || mousePos.y > finalAreaSize.y) { return; } |
|
|
|
GUIUtility.hotControl = controlID; |
|
// FocusControl is to take keyboard focus away from the TreeView. |
|
GUI.FocusControl("long text"); |
|
if (withShift) |
|
{ |
|
mousePos = MouseClickAndClamp(showBranchMarkers, mousePos); |
|
|
|
selectDragPos = mousePos; |
|
_textSelectionIdxValid = false; |
|
} |
|
else |
|
{ |
|
selectDragPos = mousePos; |
|
StopSelection(); |
|
} |
|
_mouseDown = true; |
|
} |
|
|
|
|
|
internal void DragMouse(Vector2 mousePos, bool showBranchMarkers) |
|
{ |
|
if (!_mouseDown || mousePos.y > finalAreaSize.y) { return; } |
|
|
|
mousePos = MouseClickAndClamp(showBranchMarkers, mousePos); |
|
|
|
selectDragPos = mousePos; |
|
_textSelectionIdxValid = false; |
|
} |
|
|
|
internal void MouseReleased() |
|
{ |
|
GUIUtility.hotControl = 0; |
|
_mouseDown = false; |
|
} |
|
|
|
internal void DoScroll(Rect workingArea, float mouseRelMoveY) |
|
{ |
|
if (!_mouseDown) { return; } |
|
|
|
var movingDown = mouseRelMoveY > 0; |
|
|
|
var room = Mathf.Max(0, movingDown |
|
? finalAreaSize.y - workingArea.yMax |
|
: workingArea.yMin); |
|
|
|
// naturalEnhancedPad magic number taken from unity engine (GUI.cs under EndScrollView). |
|
var dist = Mathf.Min(Mathf.Abs(mouseRelMoveY * naturalEnhancedPad), room); |
|
selectDragPos.y += movingDown ? dist : -dist; |
|
|
|
_textSelectionIdxValid = false; |
|
} |
|
|
|
public void MoveView(Direction dir, Rect workingArea) |
|
{ |
|
switch (dir) |
|
{ |
|
case Direction.Left: |
|
GUI.ScrollTo(new Rect(workingArea.xMin - 2*fontWidth, workingArea.y, 0, 0)); |
|
break; |
|
|
|
case Direction.Right: |
|
GUI.ScrollTo(new Rect(workingArea.xMax + 2*fontWidth, workingArea.y, 0, 0)); |
|
break; |
|
|
|
case Direction.Up: |
|
GUI.ScrollTo(new Rect(workingArea.x, workingArea.yMin - 2*fontWidth, 0, 0)); |
|
break; |
|
|
|
case Direction.Down: |
|
GUI.ScrollTo(new Rect(workingArea.x, workingArea.yMax + 2*fontWidth, 0, 0)); |
|
break; |
|
} |
|
} |
|
|
|
public void StopSelection() |
|
{ |
|
selectPos = selectDragPos; |
|
textSelectionIdx = (0, 0); |
|
_textSelectionIdxValid = true; |
|
HasSelection = false; |
|
} |
|
|
|
|
|
|
|
/// <summary> |
|
/// Renders a blue box relative to text at (positionX, positionY) from start idx to end idx. |
|
/// </summary> |
|
private void RenderLineSelection(float positionX, float positionY, int start, int end, bool IsSelection = true) |
|
{ |
|
const int alignmentPad = 2; |
|
var oldColor = GUI.color; |
|
GUI.color = IsSelection ? _selectionColor : _searchHitColor; |
|
GUI.Box(new Rect(positionX + alignmentPad + start*fontWidth, positionY, (end - start)*fontWidth, fontHeight), "", textureStyle); |
|
GUI.color = oldColor; |
|
} |
|
|
|
internal struct FragmentSelectionInfo |
|
{ |
|
public float startY; |
|
public float lastY; |
|
public float botY; |
|
|
|
public int startLineEndIdxRel; |
|
public int startLine; |
|
public int lastLine; |
|
|
|
public int charsIn; |
|
public int charsInDrag; |
|
} |
|
|
|
internal FragmentSelectionInfo PrepareInfoForSelection(float positionX, float positionY, float fragHeight, Fragment frag, Func<string, int, (int total, int relative)> GetEndIndexOfLine) |
|
{ |
|
const float distanceFromBorder = 0.001f; |
|
var text = frag.text; |
|
|
|
var top = selectPos; |
|
var bot = selectDragPos; |
|
if (selectPos.y > selectDragPos.y) |
|
{ |
|
top = selectDragPos; |
|
bot = selectPos; |
|
} |
|
// fixing so we only look at things within this current fragment. |
|
var start = top.y < positionY ? new Vector2(positionX, positionY) : top; |
|
// `y = ... - adjustBottomSlightly` so it is within current fragments outer "border". |
|
var last = bot.y < positionY + fragHeight ? bot : new Vector2(finalAreaSize.x, positionY + fragHeight - distanceFromBorder); |
|
|
|
// Make sure this cannot go beyond number of lines in fragment (zero indexed). |
|
var startLine = Math.Min(Mathf.FloorToInt((start.y - positionY) / fontHeight), frag.lineCount-1); |
|
var lastLine = Math.Min(Mathf.FloorToInt((last.y - positionY) / fontHeight), frag.lineCount-1); |
|
|
|
if (startLine == lastLine && start.x > last.x) |
|
(start, last) = (last, start); |
|
|
|
// Used for making sure charsIn and charsInDrag does not exceed line length. |
|
var (_, startLineEndIdxRel) = GetEndIndexOfLine(text, startLine); |
|
var (_, lastLineEndIdxRel) = GetEndIndexOfLine(text, lastLine); |
|
|
|
// Each fragment does not end on a newline, so we need to add one to idx. |
|
if (startLine == frag.lineCount - 1) startLineEndIdxRel++; |
|
if (lastLine == frag.lineCount - 1) lastLineEndIdxRel++; |
|
|
|
var charsIn = Math.Min(Mathf.FloorToInt((start.x - positionX) / fontWidth), startLineEndIdxRel); |
|
var charsInDrag = Math.Min(Mathf.FloorToInt((last.x - positionX) / fontWidth), lastLineEndIdxRel); |
|
charsIn = charsIn < 0 ? 0 : charsIn; |
|
charsInDrag = charsInDrag < 0 ? 0 : charsInDrag; |
|
|
|
return new FragmentSelectionInfo() {startY = start.y, lastY = last.y, botY = bot.y, |
|
startLineEndIdxRel = startLineEndIdxRel, startLine = startLine, lastLine = lastLine, |
|
charsIn = charsIn, charsInDrag = charsInDrag}; |
|
} |
|
|
|
private void SelectText(float positionX, float positionY, float fragHeight, Fragment frag, Func<string, int, (int total, int relative)> GetEndIndexOfLine) |
|
{ |
|
if (!HasSelection || !(BurstMath.WithinRange(selectPos.y, selectDragPos.y, positionY) || |
|
BurstMath.WithinRange(selectPos.y, selectDragPos.y, positionY + fragHeight) |
|
|| BurstMath.WithinRange(positionY, positionY + fragHeight, selectPos.y))) |
|
{ |
|
return; |
|
} |
|
|
|
var inf = PrepareInfoForSelection(positionX, positionY, fragHeight, frag, GetEndIndexOfLine); |
|
var text = frag.text; |
|
|
|
if (BurstMath.RoundDownToNearest(inf.lastY, fontHeight) > BurstMath.RoundDownToNearest(inf.startY, fontHeight)) |
|
{ |
|
// Multi line selection in this text. |
|
int lineEndIdx = inf.startLineEndIdxRel; |
|
if (inf.startY != positionY) |
|
{ |
|
// Selection started in this fragment. |
|
RenderLineSelection(positionX, positionY + (inf.startLine * fontHeight), inf.charsIn, lineEndIdx + 1); |
|
inf.startLine++; |
|
} |
|
|
|
for (; inf.startLine < inf.lastLine; inf.startLine++) |
|
{ |
|
lineEndIdx = GetEndIndexOfLine(text, inf.startLine).relative; |
|
RenderLineSelection(positionX, positionY + (inf.startLine * fontHeight), 0, lineEndIdx + 1); |
|
} |
|
|
|
if (positionY + fragHeight < inf.botY) |
|
{ |
|
// select going into next fragment |
|
inf.charsInDrag++; |
|
} |
|
|
|
RenderLineSelection(positionX, positionY + (inf.startLine * fontHeight), 0, inf.charsInDrag); |
|
} |
|
else |
|
{ |
|
// Single line selection in this text segment. |
|
int startIdx = inf.charsIn; |
|
int endIdx = inf.charsInDrag; |
|
if (inf.startY == positionY) |
|
{ |
|
// Selection started in text segment above. |
|
startIdx = 0; |
|
} |
|
|
|
if (positionY + fragHeight < inf.botY) |
|
{ |
|
// Selection going into next text segment. |
|
endIdx = inf.startLineEndIdxRel + 1; |
|
} |
|
|
|
RenderLineSelection(positionX, positionY + (inf.startLine * fontHeight), startIdx, endIdx); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Updates _textSelectionIdx based on the position of _selectPos and _selectDragPos. |
|
/// </summary> |
|
/// <param name="GetEndIndexOfLine"> either GetEndIndexOfPlainLine or GetEndIndexOfColoredLine</param> |
|
private void UpdateSelectTextIdx(Func<string, int, (int total, int relative)> GetEndIndexOfLine) |
|
{ |
|
if (!_textSelectionIdxValid && HasSelection) |
|
{ |
|
var start = selectPos; |
|
var last = selectDragPos; |
|
if (last.y < start.y) |
|
{ |
|
start = selectDragPos; |
|
last = selectPos; |
|
} |
|
int startLine = Mathf.FloorToInt(start.y / fontHeight); |
|
int lastLine = Mathf.FloorToInt(last.y / fontHeight); |
|
|
|
if (startLine == lastLine && start.x > last.x) |
|
{ |
|
(start, last) = (last, start); |
|
} |
|
|
|
var (startLineEndIdxTotal, startLineEndIdxRel) = GetEndIndexOfLine(m_Text, startLine); |
|
var (lastLineEndIdxTotal, lastLineEndIdxRel) = GetEndIndexOfLine(m_Text, lastLine); |
|
|
|
int charsIn = Math.Min(Mathf.FloorToInt(start.x / fontWidth), startLineEndIdxRel); |
|
int charsInDrag = Math.Min(Mathf.FloorToInt(last.x / fontWidth), lastLineEndIdxRel); |
|
charsIn = charsIn < 0 ? 0 : charsIn; |
|
charsInDrag = charsInDrag < 0 ? 0 : charsInDrag; |
|
|
|
int selectStartIdx = startLineEndIdxTotal - (startLineEndIdxRel - charsIn); |
|
textSelectionIdx = (selectStartIdx, lastLineEndIdxTotal - (lastLineEndIdxRel - charsInDrag) - selectStartIdx); |
|
|
|
_textSelectionIdxValid = true; |
|
} |
|
} |
|
|
|
public void NextSearchHit(bool shift, Rect workingArea) |
|
{ |
|
if (_nrSearchHits <= 0) return; |
|
|
|
// If shift is held down: Go backwards through active search |
|
_activeSearchHitIdx = shift |
|
? (Math.Max(_activeSearchHitIdx, 0) - 1 + _nrSearchHits) % _nrSearchHits |
|
: (_activeSearchHitIdx + 1) % _nrSearchHits; |
|
|
|
// Find fragment number of _activeSearchHit |
|
var fragIdx = 0; |
|
int prevFragLastHitNr = -1; |
|
var activeSearchIdx = _activeSearchHitIdx; |
|
foreach (var key in searchHits.Keys) |
|
{ |
|
var hits = searchHits[key]; |
|
|
|
var nrHits = hits.Count; |
|
var hitsLastNr = hits.Last().nr; |
|
|
|
// If last hit continues into this fragment we subtracted too much |
|
if (hits.First().nr == prevFragLastHitNr) activeSearchIdx++; |
|
|
|
if (hitsLastNr >= _activeSearchHitIdx) |
|
{ |
|
fragIdx = key; |
|
break; |
|
} |
|
activeSearchIdx -= nrHits; |
|
prevFragLastHitNr = hitsLastNr; |
|
} |
|
|
|
var hit = searchHits[fragIdx][activeSearchIdx]; |
|
|
|
// Find y position of _activeSearchHit. |
|
int exactLineNumber; |
|
string text; |
|
var positionY = 0f; |
|
if (_disassembler == null) |
|
{ |
|
int i = 0; |
|
for (; i < fragIdx; i++) |
|
{ |
|
positionY += m_Fragments[i].lineCount * fontHeight; |
|
} |
|
text = m_Fragments[i].text; |
|
exactLineNumber = BurstStringSearch.FindLineNr(text, hit.startIdx); |
|
positionY += exactLineNumber * fontHeight; |
|
} |
|
else |
|
{ |
|
int blockIdx = 0; |
|
int innerFragIdx = 0; |
|
int tmpFragIdx = 0; |
|
|
|
for (; tmpFragIdx < fragIdx; blockIdx++) |
|
{ |
|
var frags = GetPlainFragments(blockIdx); |
|
|
|
if (_folded[blockIdx]) |
|
{ |
|
positionY += fontHeight; |
|
tmpFragIdx += frags.Count; |
|
} |
|
else |
|
{ |
|
for (innerFragIdx = 0; ; innerFragIdx++, tmpFragIdx++) |
|
{ |
|
if (innerFragIdx >= frags.Count) |
|
{ |
|
innerFragIdx = 0; |
|
break; |
|
} |
|
if (tmpFragIdx >= fragIdx) break; |
|
positionY += frags[innerFragIdx].lineCount * fontHeight; |
|
|
|
} |
|
} |
|
if (innerFragIdx != 0) break; |
|
} |
|
text = GetPlainFragments(blockIdx)[innerFragIdx].text; |
|
exactLineNumber = BurstStringSearch.FindLineNr(text, hit.startIdx); |
|
positionY += exactLineNumber * fontHeight; |
|
} |
|
|
|
int startOfLine = exactLineNumber > 0 ? GetEndIndexOfPlainLine(text, exactLineNumber-1).total + 1 : 0; |
|
int hitLength = hit.endIdx - hit.startIdx; |
|
|
|
float positionX = hit.startIdx - startOfLine; |
|
if (positionX * fontWidth > workingArea.x) positionX += hitLength + 3 * fontWidth; |
|
else positionX -= 3 * fontWidth; |
|
|
|
GUI.ScrollTo(new Rect(positionX * fontWidth + _actualHorizontalPad, positionY < workingArea.yMin ? positionY - 5 * fontHeight : positionY + 5 * fontHeight, 0, 0)); |
|
} |
|
|
|
public void StopSearching() |
|
{ |
|
_nrSearchHits = 0; |
|
searchHits.Clear(); |
|
_activeSearchHitIdx = -1; |
|
_prevCriteria.filter = String.Empty; |
|
} |
|
|
|
private void RenderMultipleLinesSelection(string text, float positionX, float positionY, int startIdx, int endIdx, bool IsSelection = true) |
|
{ |
|
int lineStart = BurstStringSearch.FindLineNr(text, startIdx); |
|
int lineEnd = BurstStringSearch.FindLineNr(text, endIdx); |
|
|
|
// Make idx relative to line |
|
int startOfLine = lineStart == 0 ? 0 : GetEndIndexOfPlainLine(text, lineStart-1).total + 1; |
|
startIdx = startIdx - startOfLine; |
|
|
|
startOfLine = lineEnd == 0 ? 0 : GetEndIndexOfPlainLine(text, lineEnd-1).total + 1; |
|
endIdx = endIdx - startOfLine; |
|
|
|
|
|
positionY += (lineStart * fontHeight); |
|
|
|
if (lineStart == lineEnd) |
|
{ |
|
RenderLineSelection(positionX, positionY, startIdx, endIdx, IsSelection); |
|
} |
|
else |
|
{ |
|
int endOfLine = GetEndIndexOfPlainLine(text, lineStart).relative; |
|
RenderLineSelection(positionX, positionY, startIdx, endOfLine, IsSelection); |
|
lineStart++; |
|
positionY += fontHeight; |
|
|
|
for (; lineStart < lineEnd; lineStart++) |
|
{ |
|
endOfLine = GetEndIndexOfPlainLine(text, lineStart).relative; |
|
RenderLineSelection(positionX, positionY, 0, endOfLine, IsSelection); |
|
positionY += fontHeight; |
|
} |
|
|
|
RenderLineSelection(positionX, positionY, 0, endIdx, IsSelection); |
|
} |
|
} |
|
|
|
private void RenderPlainOldFragments(GUIStyle style, Rect workingArea, bool doSearch) |
|
{ |
|
Vector2 scrollPos = workingArea.position; |
|
float positionY = 0.0f; |
|
for (int i = 0; i < m_Fragments.Count; i++) |
|
{ |
|
// if fragment is below view |
|
if ((positionY - scrollPos.y) > workingArea.size.y) break; |
|
|
|
var fragment = m_Fragments[i]; |
|
|
|
float fragHeight = fragment.lineCount * fontHeight; |
|
|
|
// if whole fragment is above view |
|
if ((positionY - scrollPos.y + fragHeight) < 0) |
|
{ |
|
positionY += fragHeight; |
|
continue; |
|
} |
|
|
|
if (doSearch && searchHits.TryGetValue(i, out var lst)) |
|
{ |
|
foreach (var (startI, endI, nr) in lst) |
|
{ |
|
bool isActive = nr == _activeSearchHitIdx; |
|
RenderMultipleLinesSelection(fragment.text, horizontalPad, positionY, startI, endI, isActive); |
|
} |
|
} |
|
// we append \n here, as we want it for the selection but not the rendering. |
|
SelectText(horizontalPad, positionY, fragHeight, fragment, GetEndIndexOfPlainLine); |
|
|
|
GUI.Label(new Rect(horizontalPad, positionY, finalAreaSize.x, fragHeight), fragment.text, style); |
|
positionY += fragHeight; |
|
} |
|
UpdateSelectTextIdx(GetEndIndexOfPlainLine); |
|
} |
|
|
|
/// <returns>Whether the GUI should be forced to repaint during this frame.</returns> |
|
public bool Render(GUIStyle style, Rect workingArea, bool showBranchMarkers, bool doSearch, SearchCriteria searchCriteria, Regex regx) |
|
{ |
|
_actualHorizontalPad = showBranchMarkers ? horizontalPad : naturalEnhancedPad; |
|
style.richText = true; |
|
|
|
if (invalidated) |
|
{ |
|
Layout(style, _actualHorizontalPad); |
|
} |
|
|
|
if (_disassembler != null) |
|
{ |
|
// We always need to call this as its sets up the correct horizontal bar and block rendering |
|
LayoutEnhanced(style, workingArea, showBranchMarkers); |
|
} |
|
|
|
if (Event.current.type == EventType.Layout) |
|
{ |
|
// working area will be valid only during repaint, for the layout event we don't draw the labels |
|
// Add correct size Rect to GUILayoutUtillity.topLevel for retrieval with next GetRect(.) call. |
|
GUILayoutUtility.GetRect(finalAreaSize.x + (showBranchMarkers ? horizontalPad : 20.0f), finalAreaSize.y); |
|
return false; |
|
} |
|
|
|
var newSearch = doSearch && (searchCriteria.filter != "" && !searchCriteria.Equals(_prevCriteria)); |
|
_prevCriteria = searchCriteria; |
|
|
|
var doRepaint = false; |
|
if (newSearch) |
|
{ |
|
doRepaint = SearchText(searchCriteria, regx, ref workingArea); |
|
if (doRepaint) |
|
{ |
|
// Search found at least one match, so move to the first |
|
GUI.ScrollTo(workingArea); |
|
} |
|
} |
|
|
|
if (_disassembler == null) |
|
{ |
|
RenderPlainOldFragments(style, workingArea, doSearch); |
|
} |
|
else |
|
{ |
|
RenderEnhanced(style, workingArea, showBranchMarkers, _actualHorizontalPad, doSearch); |
|
} |
|
|
|
DrawHover(style, workingArea); |
|
return doRepaint; |
|
} |
|
|
|
private void TestSelUnderscore(GUIStyle style, Vector2 scrollPos, Rect workingArea) |
|
{ |
|
var current = GUI.color; |
|
|
|
// Selection |
|
GUI.color = Color.blue; |
|
GUI.Box( |
|
new Rect(horizontalPad + style.padding.left + fontWidth * 8, style.padding.top + fontHeight * 19, |
|
3 * fontWidth, 1 * fontHeight), "", textureStyle); |
|
|
|
// Underscored |
|
Vector2 start = new Vector2(horizontalPad + style.padding.left + fontWidth * 8, |
|
style.padding.top + fontHeight * 20 - 2); |
|
Vector2 end = start + new Vector2((3 + 22) * fontWidth, 0 * fontHeight); |
|
|
|
GUI.color = Color.red; |
|
DrawLine(start, end, 2); |
|
|
|
GUI.color = current; |
|
} |
|
|
|
internal void RenderBranches(Rect workingArea) |
|
{ |
|
var color = GUI.color; |
|
List<Branch> branches = new List<Branch>(); |
|
hoveredBranch = default; |
|
for (int idx = 0;idx<_disassembler.Blocks.Count; idx++) |
|
{ |
|
var block = _disassembler.Blocks[idx]; |
|
if (block.Edges != null) |
|
{ |
|
foreach (var edge in block.Edges) |
|
{ |
|
if (edge.Kind == BurstDisassembler.AsmEdgeKind.OutBound) |
|
{ |
|
var srcLine = blockLine[idx]; |
|
if (!_folded[idx]) |
|
{ |
|
srcLine += edge.OriginRef.LineIndex; |
|
} |
|
var dstBlockIdx = edge.LineRef.BlockIndex; |
|
var dstLine = blockLine[dstBlockIdx]; |
|
if (!_folded[dstBlockIdx]) |
|
{ |
|
dstLine += edge.LineRef.LineIndex; |
|
} |
|
|
|
int arrowMinY = srcLine; |
|
int arrowMaxY = dstLine; |
|
if (srcLine > dstLine) |
|
{ |
|
(arrowMinY, arrowMaxY) = (dstLine, srcLine); |
|
} |
|
|
|
if ((dstBlockIdx == idx + 1 && edge.LineRef.LineIndex == 0) // pointing to next line |
|
|| !(workingArea.yMin <= arrowMaxY * fontHeight && // Arrow not inside view. |
|
workingArea.yMax >= arrowMinY * fontHeight)) |
|
{ |
|
continue; |
|
} |
|
branches.Add(CalculateBranch(edge, horizontalPad - (4 + fontWidth), srcLine * fontHeight, |
|
dstLine * fontHeight, lineDepth[idx])); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Drawing branches while making sure the hovered is rendered at top. |
|
foreach (var branch in branches) |
|
{ |
|
if (!branch.Edge.Equals(hoveredBranch.Edge)) |
|
{ |
|
DrawBranch(branch, lineDepth[branch.Edge.OriginRef.BlockIndex], workingArea); |
|
} |
|
} |
|
if (!hoveredBranch.Edge.Equals(default(BurstDisassembler.AsmEdge))) |
|
{ |
|
DrawBranch(hoveredBranch, lineDepth[hoveredBranch.Edge.OriginRef.BlockIndex], workingArea); |
|
} |
|
|
|
_prevHoveredEdge = hoveredBranch.Edge; |
|
GUI.color = color; |
|
} |
|
|
|
internal int BumpSelectionXByColorTag(string text, int lineIdxTotal, int charsIn) |
|
{ |
|
bool lastWasStart = true; |
|
int colorTagStart = text.IndexOf("<color=", lineIdxTotal); |
|
|
|
while (colorTagStart != -1 && colorTagStart - lineIdxTotal < charsIn) |
|
{ |
|
int colorTagEnd = text.IndexOf('>', colorTagStart + 1); |
|
// +1 as the index calculation is zero based. |
|
charsIn += colorTagEnd - colorTagStart + 1; |
|
|
|
if (lastWasStart) |
|
{ |
|
colorTagStart = text.IndexOf("</color>", colorTagEnd + 1); |
|
lastWasStart = false; |
|
} |
|
else |
|
{ |
|
colorTagStart = text.IndexOf("<color=", colorTagEnd + 1); |
|
lastWasStart = true; |
|
} |
|
} |
|
return charsIn; |
|
} |
|
|
|
internal void UpdateEnhancedSelectTextIdx(float hPad) |
|
{ |
|
if (_textSelectionIdxValid || !HasSelection) return; |
|
|
|
int blockIdxStart = _selectBlockStart; |
|
int blockIdxEnd = _selectBlockEnd; |
|
float blockStartPosY = _selectStartY; |
|
float blockEndPosY = _selectEndY; |
|
|
|
var start = selectPos; |
|
var last = selectDragPos; |
|
if (last.y < start.y) |
|
{ |
|
// we selected upwards. |
|
(start, last) = (last, start); |
|
(blockIdxStart, blockIdxEnd, blockStartPosY, blockEndPosY) = (blockIdxEnd, blockIdxStart, blockEndPosY, blockStartPosY); |
|
} |
|
|
|
// Math.Min to make sure line number does not exceed number of line in block (zero indexed). |
|
var blockStartline = Math.Min(Mathf.FloorToInt((start.y - blockStartPosY) / fontHeight), |
|
_folded[blockIdxStart] |
|
? 0 |
|
: _disassembler.Blocks[blockIdxStart].Length-1); |
|
var blockEndLine = Math.Min(Mathf.FloorToInt((last.y - blockEndPosY) / fontHeight), |
|
_folded[blockIdxEnd] |
|
? 0 |
|
: _disassembler.Blocks[blockIdxEnd].Length-1); |
|
|
|
if (blockStartline == blockEndLine && blockIdxStart == blockIdxEnd && start.x > last.x) |
|
{ |
|
// _selectDragPos was above and behind _selectPos on same line. |
|
(start, last) = (last, start); |
|
} |
|
|
|
var text = _folded[blockIdxStart] |
|
? CopyColorTags |
|
? GetFragmentStart(blockIdxStart).text + '\n' |
|
: GetFragmentStartPlain(blockIdxStart).text + '\n' |
|
: CopyColorTags |
|
? _disassembler.GetOrRenderBlockToText(blockIdxStart) |
|
: _disassembler.GetOrRenderBlockToTextUncached(blockIdxStart, false); |
|
|
|
var (startLineEndIdxTotal, startLineEndIdxRel) = GetEndIndexOfColoredLine(text, blockStartline); |
|
int startOfLineIdx = startLineEndIdxTotal - startLineEndIdxRel; |
|
|
|
int charsIn = Math.Min(Mathf.FloorToInt((start.x - hPad) / fontWidth), startLineEndIdxRel); |
|
charsIn = charsIn < 0 ? 0 : charsIn; |
|
|
|
// Adjust charsIn so it takes color tags into considerations. |
|
charsIn = BumpSelectionXByColorTag(text, startOfLineIdx, charsIn + 1) - 1; // +1 -1 to not bump charsIn when selecting char just after color tag. |
|
|
|
enhancedTextSelectionIdxStart = (blockIdxStart, startOfLineIdx + charsIn); |
|
|
|
if (blockIdxStart < blockIdxEnd) |
|
{ |
|
text = _folded[blockIdxEnd] |
|
? CopyColorTags |
|
? GetFragmentStart(blockIdxEnd).text + '\n' |
|
: GetFragmentStartPlain(blockIdxEnd).text + '\n' |
|
: CopyColorTags |
|
? _disassembler.GetOrRenderBlockToText(blockIdxEnd) |
|
: _disassembler.GetOrRenderBlockToTextUncached(blockIdxEnd, false); |
|
} |
|
|
|
var (lastLineEndIdxTotal, lastLineEndIdxRel) = GetEndIndexOfColoredLine(text, blockEndLine); |
|
startOfLineIdx = lastLineEndIdxTotal - lastLineEndIdxRel; |
|
|
|
int charsInDrag = Math.Min(Mathf.FloorToInt((last.x - hPad) / fontWidth), lastLineEndIdxRel); |
|
charsInDrag = charsInDrag < 0 ? 0 : charsInDrag; |
|
|
|
// Adjust charsInDrag so it takes color tags into considerations. |
|
charsInDrag = BumpSelectionXByColorTag(text, startOfLineIdx, charsInDrag); |
|
|
|
enhancedTextSelectionIdxEnd = (blockIdxEnd, startOfLineIdx + charsInDrag); |
|
|
|
_textSelectionIdxValid = true; |
|
} |
|
|
|
/// <summary> |
|
/// Updates selection based on whether <see cref="blockIdx"/> was folded or unfolded. |
|
/// </summary> |
|
/// <param name="hPad">horizontal padding.</param> |
|
/// <param name="blockIdx">Block idx of folded/unfolded block.</param> |
|
/// <param name="block">folded/unfolded block.</param> |
|
/// <param name="positionY">Blocks y-position in textarea.</param> |
|
private void UpdateSelectionByFolding(float hPad, int blockIdx, BurstDisassembler.AsmBlock block, float positionY) |
|
{ |
|
// cursor start and end position of selection |
|
var start = selectPos; |
|
var end = selectDragPos; |
|
|
|
// selection block start and end |
|
var blockStart = _selectBlockStart; |
|
var blockEnd = _selectBlockEnd; |
|
|
|
// top y-coordinate of selections end block |
|
var blockEndY = _selectEndY; |
|
|
|
var rightWaySelect = true; |
|
if (start.y > end.y) |
|
{ |
|
(start, end) = (end, start); |
|
(blockStart, blockEnd) = (blockEnd, blockStart); |
|
blockEndY = _selectStartY; |
|
rightWaySelect = false; |
|
} |
|
|
|
if (BurstMath.RoundDownToNearest(end.y, fontHeight) - BurstMath.RoundDownToNearest(start.y, fontHeight) < fontHeight |
|
&& end.x < start.x) |
|
{ |
|
(start, end) = (end, start); |
|
rightWaySelect = !rightWaySelect; |
|
} |
|
|
|
var changeY = _folded[blockIdx] |
|
? -Math.Max(block.Length - 1, 1) * fontHeight |
|
: Math.Max(block.Length - 1, 1) * fontHeight; |
|
|
|
var charsInLine = GetEndIndexOfColoredLine( |
|
_folded[blockIdx] |
|
? GetFragmentStart(blockIdx).text + '\n' |
|
: _disassembler.GetOrRenderBlockToText(blockIdx), |
|
0).relative; |
|
|
|
var thisLineSelect = Mathf.FloorToInt((start.y - positionY) / fontHeight); |
|
var thisLineDrag = Mathf.FloorToInt((end.y - positionY) / fontHeight); |
|
|
|
var selectPosCharsIn = Mathf.FloorToInt((start.x - hPad) / fontWidth); |
|
var selectDragPosCharsIn = Mathf.FloorToInt((end.x - hPad) / fontWidth); |
|
|
|
if (blockStart < blockIdx) |
|
{ |
|
if (blockEnd == blockIdx) |
|
{ |
|
if (thisLineDrag > 0 || (end.x - hPad) / fontWidth > charsInLine) |
|
{ |
|
/* selection starts above touched block but ends in the middle of it. |
|
* |
|
* b-1 _xxx b-1 _xxx |
|
* b xxx_ => b xxx |
|
* b+1 ____ b+1 ____ |
|
*/ |
|
|
|
// Move selectDragPos onto end of this line. |
|
end.y -= thisLineDrag * fontHeight; |
|
end.x = charsInLine * fontWidth + hPad + fontWidth / 2; |
|
} |
|
} |
|
else if (blockEnd > blockIdx) |
|
{ |
|
/* selection starts above touched block and ends below it. |
|
* |
|
* b-1 _xxx |
|
* b xxxx |
|
* b+1 xx__ |
|
*/ |
|
end.y += changeY; |
|
blockEndY += changeY; |
|
} |
|
} |
|
else if (blockStart == blockIdx) |
|
{ |
|
if (blockEnd == blockIdx) |
|
{ |
|
if (thisLineSelect > 0 || selectPosCharsIn > charsInLine) |
|
{ |
|
/* selection starts and ends in the middle of the touched block |
|
* |
|
* b-1 ____ b-1 ____ |
|
* b _xx_ => b _ |
|
* b+1 ____ b+1 ____ |
|
*/ |
|
// Make cursor go to the end of line |
|
end.y -= thisLineDrag * fontHeight; |
|
end.x = charsInLine * fontWidth + hPad + fontWidth / 2; |
|
|
|
StopSelection(); |
|
} |
|
else if (thisLineDrag > 0 || selectDragPosCharsIn > charsInLine) |
|
{ |
|
/* selection starts at first line and ends within |
|
* |
|
* b-1 ____ b-1 ____ |
|
* b _xx_ => b x |
|
* xxx_ |
|
* b+1 ____ b+1 ____ |
|
*/ |
|
// Move selectDragPos onto end of this line. |
|
end.y -= thisLineDrag * fontHeight; |
|
end.x = charsInLine * fontWidth + hPad + fontWidth / 2; |
|
} |
|
} |
|
else if (blockEnd > blockIdx) |
|
{ |
|
// selection starts either in first line or middle of touched block and ends below it |
|
end.y += changeY; |
|
|
|
if (thisLineSelect > 0 || selectPosCharsIn > charsInLine) |
|
{ |
|
/* selection starts in middle of touched block and ends below it |
|
* |
|
* b-1 ____ b-1 ____ |
|
* b ____ => b ___x |
|
* x___ |
|
* b+1 xx__ b+1 xx__ |
|
*/ |
|
// Move selectPos onto end of this line. |
|
start.y -= thisLineSelect * fontHeight; |
|
start.x = charsInLine * fontWidth + hPad + fontWidth / 2; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
start.y += changeY; |
|
end.y += changeY; |
|
} |
|
|
|
// Write back changes made during folding/unfolding |
|
if (rightWaySelect) |
|
{ |
|
selectPos = start; |
|
selectDragPos = end; |
|
_selectEndY = blockEndY; |
|
} |
|
else |
|
{ |
|
selectPos = end; |
|
selectDragPos = start; |
|
_selectStartY = blockEndY; |
|
} |
|
|
|
if (!HasSelection) selectPos = selectDragPos; |
|
else _textSelectionIdxValid = false; |
|
} |
|
|
|
private void FoldUnfoldBlock(int blockIdx) |
|
{ |
|
var block = _disassembler.Blocks[blockIdx]; |
|
|
|
_folded[blockIdx] = !_folded[blockIdx]; |
|
if (_folded[blockIdx]) |
|
{ |
|
finalAreaSize.y -= Math.Max(block.Length - 1, 1) * fontHeight; |
|
AddFoldedString(blockIdx); |
|
} |
|
else |
|
{ |
|
finalAreaSize.y += Math.Max(block.Length - 1, 1) * fontHeight; |
|
} |
|
} |
|
|
|
private void RenderEnhanced(GUIStyle style, Rect workingArea, bool showBranchMarkers, float hPad, bool doSearch) |
|
{ |
|
//TestSelUnderscore(); |
|
if (showBranchMarkers) |
|
{ |
|
RenderBranches(workingArea); |
|
} |
|
|
|
var positionY = _renderStartY; |
|
var fragNr = 0; |
|
for (var i = 0; i <= _renderBlockEnd; i++) |
|
{ |
|
if (i < _renderBlockStart) |
|
{ |
|
if (doSearch) |
|
{ |
|
foreach (var frag in GetPlainFragments(i)) |
|
{ |
|
fragNr++; |
|
} |
|
} |
|
|
|
continue; |
|
} |
|
|
|
var block = _disassembler.Blocks[i]; |
|
|
|
var blockLongEnoughForFold = block.Length > 1; |
|
if (blockLongEnoughForFold) |
|
{ |
|
var pressed = DrawFold(hPad - 2, positionY, _folded[i], block.Kind); |
|
if (pressed) |
|
{ |
|
FoldUnfoldBlock(i); |
|
|
|
// Make sure cursor and selection is updated according to the changed folding. |
|
UpdateSelectionByFolding(hPad, i, block, positionY); |
|
} |
|
} |
|
|
|
if (doSearch) |
|
{ |
|
var tmpPositionY = positionY; |
|
for (var tmp = 0; tmp < GetPlainFragments(i).Count; fragNr++, tmp++) |
|
{ |
|
if (searchHits.TryGetValue(fragNr, out var hits)) |
|
{ |
|
if (_folded[i]) FoldUnfoldBlock(i); |
|
|
|
foreach (var (startIdx, endIdx, nr) in hits) |
|
{ |
|
var isActive = nr == _activeSearchHitIdx; |
|
var realEndIdx = endIdx; |
|
|
|
if (endIdx == int.MaxValue) |
|
{ |
|
var text = GetPlainFragments(i)[tmp].text; |
|
realEndIdx = text.Length; |
|
RenderMultipleLinesSelection(text, horizontalPad, tmpPositionY, startIdx, |
|
realEndIdx, isActive); |
|
} |
|
else |
|
{ |
|
RenderMultipleLinesSelection( |
|
GetPlainFragments(i)[tmp].text, |
|
horizontalPad, |
|
tmpPositionY, |
|
startIdx, |
|
realEndIdx, |
|
isActive); |
|
} |
|
} |
|
} |
|
tmpPositionY += GetPlainFragments(i)[tmp].lineCount * fontHeight; |
|
} |
|
} |
|
|
|
if (!_folded[i] || !blockLongEnoughForFold) |
|
{ |
|
for (var j = 0; j < _blocksFragments[i].Count; j++) |
|
{ |
|
var fragment = _blocksFragments[i][j]; |
|
|
|
var fragLineCount = fragment.lineCount; |
|
var fragHeight = fragLineCount * fontHeight; |
|
|
|
SelectText(hPad, positionY, fragHeight, fragment, |
|
_disassembler.IsColored |
|
? GetEndIndexOfColoredLine |
|
: GetEndIndexOfPlainLine); |
|
|
|
GUI.Label(new Rect(hPad, positionY, finalAreaSize.x, fragHeight), |
|
fragment.text, style); |
|
positionY += fragHeight; |
|
} |
|
} |
|
else |
|
{ |
|
var frag = GetFragmentStart(i); |
|
|
|
SelectText(hPad, positionY, fontHeight, frag, |
|
_disassembler.IsColored |
|
? GetEndIndexOfColoredLine |
|
: GetEndIndexOfPlainLine); |
|
|
|
GUI.Label(new Rect(hPad, positionY, finalAreaSize.x, fontHeight), frag.text, style); |
|
positionY += fontHeight; |
|
} |
|
} |
|
UpdateEnhancedSelectTextIdx(hPad); |
|
} |
|
|
|
private List<Fragment> RecomputeFragments(string text) |
|
{ |
|
List<Fragment> result = new List<Fragment>(); |
|
|
|
string[] pieces = text.Split('\n'); |
|
_mTextLines = pieces.Length; |
|
|
|
StringBuilder b = new StringBuilder(); |
|
|
|
int lineCount = 0; |
|
for (int a = 0; a < pieces.Length; a++) |
|
{ |
|
if (b.Length >= kMaxFragment) |
|
{ |
|
b.Remove(b.Length - 1, 1); |
|
AddFragment(b, lineCount, result); |
|
lineCount = 0; |
|
} |
|
|
|
b.Append(pieces[a]); |
|
b.Append('\n'); |
|
lineCount++; |
|
} |
|
|
|
if (b.Length > 0) |
|
{ |
|
b.Remove(b.Length - 1, 1); |
|
AddFragment(b, lineCount, result); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
private List<Fragment> RecomputeFragmentsFromBlock(int blockIdx, bool colored) |
|
{ |
|
var text = _disassembler.GetOrRenderBlockToTextUncached(blockIdx, colored).TrimEnd('\n'); |
|
|
|
return RecomputeFragments(text); |
|
} |
|
|
|
internal List<Fragment> RecomputeFragmentsFromBlock(int blockIdx) |
|
{ |
|
var text = _disassembler.GetOrRenderBlockToText(blockIdx).TrimEnd('\n'); |
|
|
|
return RecomputeFragments(text); |
|
} |
|
|
|
private static void AddFragment(StringBuilder b, int lineCount, List<Fragment> result) |
|
{ |
|
result.Add(new Fragment() { text = b.ToString(), lineCount = lineCount }); |
|
b.Length = 0; |
|
} |
|
} |
|
}
|
|
|