A game about forced loneliness, made by TACStudios
at master 3268 lines 131 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Runtime.CompilerServices; 4using System.Text; 5using System.Text.RegularExpressions; 6using UnityEngine; 7using UnityEditor; 8 9[assembly: InternalsVisibleTo("Unity.Burst.Editor.Tests")] 10 11namespace Unity.Burst.Editor 12{ 13 internal class LongTextArea 14 { 15 internal const float naturalEnhancedPad = 20f; 16 private const int kMaxFragment = 2048; 17 18 internal struct Fragment 19 { 20 public int lineCount; 21 public string text; 22 } 23 public enum Direction 24 { 25 Left, 26 Right, 27 Up, 28 Down 29 } 30 internal struct Branch 31 { 32 public BurstDisassembler.AsmEdge Edge; 33 34 public Rect StartHorizontal; 35 public Rect VerticalLine; 36 public Rect EndHorizontal; 37 38 public Rect UpperLine; 39 public Rect LowerLine; 40 public float UpperAngle; 41 public float LowerAngle; 42 43 public Branch(BurstDisassembler.AsmEdge edge, Rect startHorizontal, Rect verticalLine, Rect endHorizontal, Rect upperLine, Rect lowerLine, float angle1, float angle2) 44 { 45 Edge = edge; 46 47 StartHorizontal = startHorizontal; 48 VerticalLine = verticalLine; 49 EndHorizontal = endHorizontal; 50 51 UpperLine = upperLine; 52 LowerLine = lowerLine; 53 UpperAngle = angle1; 54 LowerAngle = angle2; 55 } 56 } 57 58 internal float fontHeight = 0.0f; 59 internal float fontWidth = 0.0f; 60 61 public string GetText => m_Text; 62 63 private string m_Text = ""; 64 private int _mTextLines = 0; 65 private List<Fragment> m_Fragments = null; 66 private bool invalidated = true; 67 internal Vector2 finalAreaSize; 68 69 private static readonly Texture2D backgroundTexture = Texture2D.whiteTexture; 70 private static readonly GUIStyle textureStyle = new GUIStyle { normal = new GUIStyleState { background = backgroundTexture } }; 71 72 internal float horizontalPad = 50.0f; 73 74 internal int[] lineDepth = null; 75 internal bool[] _folded = null; 76 internal int[] blockLine = null; 77 private List<Fragment>[] _blocksFragments = null; 78 79 private Fragment[] _blocksFragmentsStart = null; 80 81 private Fragment GetFragmentStart(int blockIdx) 82 { 83 AddFoldedString(blockIdx); 84 85 return _blocksFragmentsStart[blockIdx]; 86 } 87 88 // Need two separate caches for start of blocks fragments, as we possibly differentiate between 89 // rendered line and copied. 90 private Fragment[] _blocksFragmentsStartPlain = null; 91 private Fragment GetFragmentStartPlain(int blockIdx) 92 { 93 AddFoldedStringColorless(blockIdx); 94 95 return _blocksFragmentsStartPlain[blockIdx]; 96 } 97 98 // Used for searching when text is colored: 99 internal List<Fragment>[] blocksFragmentsPlain = null; 100 101 internal List<Fragment> GetPlainFragments(int blockIdx) 102 { 103 blocksFragmentsPlain[blockIdx] ??= RecomputeFragmentsFromBlock(blockIdx, false); 104 105 return blocksFragmentsPlain[blockIdx]; 106 } 107 108 private BurstDisassembler _disassembler; 109 110 private int _selectBlockStart = -1; 111 private float _selectStartY = -1f; 112 private int _selectBlockEnd = -1; 113 private float _selectEndY = -1f; 114 115 private float _renderStartY = 0.0f; 116 private int _renderBlockStart = -1; 117 internal int _renderBlockEnd = -1; 118 private int _initialLineCount = -1; 119 120 private bool _mouseDown = false; 121 private bool _mouseOutsideBounds = true; 122 internal Vector2 selectPos = Vector2.zero; 123 internal Vector2 selectDragPos = Vector2.zero; 124 125 public bool HasSelection { get; private set; } 126 127 private Color _selectionColor; 128 private readonly Color _selectionColorDarkmode = new Color(0f, .6f, .9f, .5f); 129 private readonly Color _selectionColorLightmode = new Color(0f, 0f, .9f, .2f); 130 131 private readonly Color _lineHighlightColor = new Color(.56f, .56f, .56f, 0.2f); 132 133 private readonly Color[] _regsColourWheel = 134 { 135 new Color(0f, 0f, .9f, .2f), 136 new Color(0f, .9f, 0f, .2f), 137 new Color(.9f, 0f, 0f, .2f) 138 }; 139 140 private Color _hoverBoxColor; 141 private Color _hoverTextColor; 142 private readonly Color _hoverBoxColorDarkMode = new Color(.33f, .33f, .33f); 143 private readonly Color _hoverBoxColorLightMode = new Color(.88f, .88f, .88f); 144 145 // Current active hit should just be selection color 146 private readonly Color _searchHitColor = new Color(.5f, .2f, .2f, .5f); 147 148 private SearchCriteria _prevCriteria; 149 internal int _activeSearchHitIdx = -1; 150 internal readonly Dictionary<int, List<(int startIdx, int endIdx, int nr)>> searchHits = new Dictionary<int, List<(int startIdx, int endIdx, int nr)>>(); 151 152 private int _nrSearchHits = 0; 153 154 public int NrSearchHits => _nrSearchHits; 155 public int ActiveSearchNr => _activeSearchHitIdx; 156 157 private readonly Color[] _colourWheel = new Color[] 158 {Color.blue, Color.cyan, Color.green, Color.magenta, Color.red, Color.yellow, Color.white, Color.black}; 159 160 internal (int idx, int length) textSelectionIdx = (0, 0); 161 private bool _textSelectionIdxValid = true; 162 163 internal (int blockIdx, int textIdx) enhancedTextSelectionIdxStart = (0, 0); 164 internal (int blockIdx, int textIdx) enhancedTextSelectionIdxEnd = (0, 0); 165 166 internal bool CopyColorTags { get; private set; } = true; 167 168 private bool _oldShowBranchMarkers = false; 169 170 internal const int regLineThickness = 2; 171 private const int highlightLineThickness = 3; 172 private float _verticalPad = 0; 173 174 internal Branch hoveredBranch; 175 private BurstDisassembler.AsmEdge _prevHoveredEdge; 176 177 private string _jobName; 178 179 // Have this field, as NextHit needs the actual horizontal padding 180 // in order to have correct horizontal scroll. 181 private float _actualHorizontalPad = 0; 182 183 internal int MaxLineDepth; 184 185 /// <returns> 186 /// (idx in regards to whole <see cref="str"/> , where color tags are removed from this line, idx from this line with color tags removed) 187 /// </returns> 188 internal readonly Func<string, int, (int total, int relative)> GetEndIndexOfColoredLine = BurstStringSearch.GetEndIndexOfColoredLine; 189 190 191 internal readonly Func<string, int, (int total, int relative)> GetEndIndexOfPlainLine = BurstStringSearch.GetEndIndexOfPlainLine; 192 193 194 internal bool IsTextSet(string jobName) 195 { 196 return _jobName == jobName; 197 } 198 199 public void SetText(string jobName, string textToRender, bool isDarkMode, BurstDisassembler disassembler, bool useDisassembler) 200 { 201 selectDragPos = Vector2.zero; 202 StopSearching(); 203 StopSelection(); 204 ClearLinePress(); 205 206 _jobName = jobName; 207 208 209 if (!useDisassembler) 210 { 211 m_Text = textToRender; 212 _disassembler = null; 213 m_Fragments = RecomputeFragments(m_Text); 214 horizontalPad = 0.0f; 215 } 216 else 217 { 218 m_Fragments = null; 219 SetDisassembler(disassembler); 220 221 (_selectionColor, _hoverBoxColor, _hoverTextColor) = isDarkMode 222 ? (_selectionColorDarkmode, _hoverBoxColorDarkMode, _hoverBoxColorLightMode) 223 : (_selectionColorLightmode, _hoverBoxColorLightMode, _hoverBoxColorDarkMode); 224 } 225 226 invalidated = true; 227 } 228 229 public void ExpandAllBlocks() 230 { 231 StopSelection(); 232 ClearLinePress(); 233 234 int blockIdx = 0; 235 foreach (var block in _disassembler.Blocks) 236 { 237 var changed = _folded[blockIdx]; 238 var blockLongEnoughForFold = block.Length > 1; 239 240 _folded[blockIdx] = false; 241 if (changed && blockLongEnoughForFold) 242 { 243 finalAreaSize.y += Math.Max(block.Length - 1, 1) * fontHeight; 244 } 245 blockIdx++; 246 } 247 } 248 249 private void ClearLinePress() 250 { 251 _lineRegCache.Clear(); 252 _pressedLine = -1; 253 } 254 255 public void FocusCodeBlocks() 256 { 257 StopSelection(); 258 ClearLinePress(); 259 260 var blockIdx = 0; 261 foreach (var block in _disassembler.Blocks) 262 { 263 bool changed = false; 264 switch (block.Kind) 265 { 266 case BurstDisassembler.AsmBlockKind.None: 267 case BurstDisassembler.AsmBlockKind.Directive: 268 case BurstDisassembler.AsmBlockKind.Block: 269 case BurstDisassembler.AsmBlockKind.Data: 270 if (!_folded[blockIdx]) 271 { 272 changed = true; 273 } 274 _folded[blockIdx] = true; 275 break; 276 case BurstDisassembler.AsmBlockKind.Code: 277 if (_folded[blockIdx]) 278 { 279 changed = true; 280 } 281 _folded[blockIdx] = false; 282 break; 283 } 284 285 if (changed) 286 { 287 if (_folded[blockIdx]) 288 { 289 finalAreaSize.y -= Math.Max(block.Length - 1, 1) * fontHeight; 290 } 291 else 292 { 293 finalAreaSize.y += Math.Max(block.Length - 1, 1) * fontHeight; 294 } 295 296 } 297 blockIdx++; 298 } 299 } 300 301 private void ComputeInitialLineCount() 302 { 303 var blockIdx = 0; 304 _initialLineCount = 0; 305 foreach (var block in _disassembler.Blocks) 306 { 307 switch (block.Kind) 308 { 309 case BurstDisassembler.AsmBlockKind.None: 310 case BurstDisassembler.AsmBlockKind.Directive: 311 case BurstDisassembler.AsmBlockKind.Block: 312 case BurstDisassembler.AsmBlockKind.Data: 313 _folded[blockIdx] = true; 314 break; 315 case BurstDisassembler.AsmBlockKind.Code: 316 _folded[blockIdx] = false; 317 break; 318 } 319 var blockLongEnoughForFold = block.Length > 1; 320 _folded[blockIdx] = _folded[blockIdx] && blockLongEnoughForFold; 321 322 _initialLineCount += _folded[blockIdx] ? 1 : block.Length; 323 blockIdx++; 324 } 325 } 326 327 public void SetDisassembler(BurstDisassembler disassembler) 328 { 329 _disassembler = disassembler; 330 if (disassembler == null) 331 { 332 return; 333 } 334 335 var numBlocks = _disassembler.Blocks.Count; 336 var numLinesFromBlock = new int[numBlocks]; 337 lineDepth = new int[numBlocks]; 338 _folded = new bool[numBlocks]; 339 blockLine = new int[numBlocks]; 340 _blocksFragments = new List<Fragment>[numBlocks]; 341 _blocksFragmentsStart = new Fragment[numBlocks]; 342 _blocksFragmentsStartPlain = new Fragment[numBlocks]; 343 blocksFragmentsPlain = new List<Fragment>[numBlocks]; 344 345 ComputeInitialLineCount(); 346 347 // Count edges 348 var edgeCount = 0; 349 StringBuilder sb = new StringBuilder(); 350 for (int i = 0; i < numBlocks; i++) 351 { 352 sb.Append(_disassembler.GetOrRenderBlockToTextUncached(i, false)); 353 354 var block = _disassembler.Blocks[i]; 355 if (block.Edges != null) 356 { 357 foreach (var edge in block.Edges) 358 { 359 if (edge.Kind == BurstDisassembler.AsmEdgeKind.OutBound) 360 { 361 edgeCount++; 362 } 363 } 364 } 365 } 366 m_Text = sb.ToString(); 367 368 var edgeArray = new BurstDisassembler.AsmEdge[edgeCount]; 369 var edgeIndex = 0; 370 foreach (var block in _disassembler.Blocks) 371 { 372 if (block.Edges != null) 373 { 374 foreach (var edge in block.Edges) 375 { 376 if (edge.Kind == BurstDisassembler.AsmEdgeKind.OutBound) 377 { 378 edgeArray[edgeIndex++] = edge; 379 } 380 } 381 } 382 } 383 384 Array.Sort(edgeArray, (a, b) => 385 { 386 var src1BlockIdx = a.OriginRef.BlockIndex; 387 var src1Line = _disassembler.Blocks[src1BlockIdx].LineIndex; 388 src1Line += a.OriginRef.LineIndex; 389 var dst1BlockIdx = a.LineRef.BlockIndex; 390 var dst1Line = _disassembler.Blocks[dst1BlockIdx].LineIndex; 391 dst1Line += a.LineRef.LineIndex; 392 var Len1 = Math.Abs(src1Line - dst1Line); 393 var src2BlockIdx = b.OriginRef.BlockIndex; 394 var src2Line = _disassembler.Blocks[src2BlockIdx].LineIndex; 395 src2Line += b.OriginRef.LineIndex; 396 var dst2BlockIdx = b.LineRef.BlockIndex; 397 var dst2Line = _disassembler.Blocks[dst2BlockIdx].LineIndex; 398 dst2Line += b.LineRef.LineIndex; 399 var Len2 = Math.Abs(src2Line - dst2Line); 400 return Len1 - Len2; 401 }); 402 403 // Iterate through the blocks to precompute the widths for branches 404 var maxLine = 0; 405 foreach (var edge in edgeArray) 406 { 407 if (edge.Kind != BurstDisassembler.AsmEdgeKind.OutBound) continue; 408 409 var s = edge.OriginRef.BlockIndex; 410 var e = edge.LineRef.BlockIndex; 411 if (e == s + 1) 412 { 413 continue; // don't render if its pointing to next line 414 } 415 416 var m = 0; 417 418 var l = s; 419 var le = e; 420 if (e < s) 421 { 422 l = e; 423 le = s; 424 } 425 426 for (; l <= le; l++) 427 { 428 numLinesFromBlock[l]++; 429 if (m < numLinesFromBlock[l]) 430 { 431 m = numLinesFromBlock[l]; 432 } 433 if (maxLine < m) 434 { 435 maxLine = m; 436 } 437 } 438 439 lineDepth[s] = m; 440 } 441 442 MaxLineDepth = maxLine; 443 444 horizontalPad = naturalEnhancedPad + maxLine * 10; 445 } 446 447 // Changing the font size doesn't update the text field, so added this to force a recalculation 448 public void Invalidate() 449 { 450 // Assumed to only happen on altering font size (or similar actions not leading to altered number of 451 // assembly lines in view). 452 // Hence we only have to clear the cached rects for highlighting, and not the actual line number _linePressed, 453 // as that will stay constant. 454 _lineRegCache.Clear(); 455 invalidated = true; 456 } 457 458 private struct HoverBox 459 { 460 public Rect box; 461 public string info; 462 /// <summary> 463 /// Indicates start and end column of hovered token. 464 /// </summary> 465 public (int start, int end) columnRange; 466 public int lineNumber; 467 public bool valid; 468 } 469 470 private HoverBox hover; 471 472 public void Interact(Rect workingArea, EventType eventT) 473 { 474 if (_disassembler == null) 475 { 476 return; 477 } 478 479 var pos = GetMousePosForHover(); 480 if (!workingArea.Contains(pos)) 481 { 482 // Mouse position outside of assembly view 483 return; 484 } 485 486 // lineNumber (absolute) 487 var lineNumber = GetLineNumber(pos); 488 489 // Mouse just pressed this line. 490 if (eventT == EventType.MouseDown) 491 { 492 var lineStr = GetLineString(_disassembler.Lines[lineNumber]); 493 var lineLen = (lineStr.Length) * fontWidth; 494 495 var blockIdx = 0; 496 var len = _disassembler.Blocks.Count; 497 for (; blockIdx < len-1; blockIdx++) 498 { 499 if (_disassembler.Blocks[blockIdx+1].LineIndex > lineNumber) 500 { 501 break; 502 } 503 } 504 505 if (_folded[blockIdx]) 506 { 507 lineLen += 4*fontWidth; 508 } 509 if (pos.x < _actualHorizontalPad || pos.x > _actualHorizontalPad + lineLen) 510 { 511 _pressedLine = -1; 512 } 513 else 514 { 515 _pressedLine = lineNumber; 516 } 517 } 518 519 var column = Mathf.FloorToInt((pos.x - horizontalPad) / fontWidth); 520 521 var sameToken = hover.lineNumber == lineNumber && 522 BurstMath.WithinRange(hover.columnRange.start, hover.columnRange.end, column); 523 if (hover.valid && (sameToken || hover.box.Contains(pos))) 524 { 525 // Use the cached info text. 526 return; 527 } 528 // Cached instruction info text is not valid. So try query new. 529 hover.valid = false; 530 531 if (column < 0 || lineNumber < 0 || lineNumber >= _disassembler.Lines.Count) 532 { 533 return; 534 } 535 536 int tokIdx; 537 try 538 { 539 // block 0 is queried as the absolut line number is given to the function. 540 // This will give us the correct assembly token, as the function queries 541 // assembly line based on offset from block start => line lineNumber is queried. 542 tokIdx = _disassembler.GetTokenIndexFromColumn(0, lineNumber, column, out _); 543 } 544 catch (ArgumentOutOfRangeException) 545 { 546 return; 547 } 548 if (tokIdx < 0) 549 { 550 return; 551 } 552 // Found match 553 554 var token = _disassembler.GetToken(tokIdx); 555 var tokenString = _disassembler.GetTokenAsText(token); 556 557 if (token.Kind != BurstDisassembler.AsmTokenKind.Instruction && 558 token.Kind != BurstDisassembler.AsmTokenKind.BranchInstruction && 559 token.Kind != BurstDisassembler.AsmTokenKind.CallInstruction && 560 token.Kind != BurstDisassembler.AsmTokenKind.FunctionBegin && 561 token.Kind != BurstDisassembler.AsmTokenKind.FunctionEnd && 562 token.Kind != BurstDisassembler.AsmTokenKind.JumpInstruction && 563 token.Kind != BurstDisassembler.AsmTokenKind.ReturnInstruction && 564 token.Kind != BurstDisassembler.AsmTokenKind.InstructionSIMD) 565 { 566 return; 567 } 568 if (_disassembler.GetInstructionInformation(tokenString, out var info)) 569 { 570 hover.valid = true; 571 // Don't know the actual size needed yet. 572 hover.box = new Rect(pos, Vector2.zero); 573 hover.info = info; 574 hover.lineNumber = lineNumber; 575 576 // Find column range of instruction: 577 var line = _disassembler.Lines[lineNumber]; 578 var idx = line.ColumnIndex + (tokIdx - line.TokenIndex - 1); 579 var colStart = _disassembler.ColumnIndices[idx]; 580 var colEnd = colStart + token.Length - 1; 581 hover.columnRange = (colStart, colEnd); 582 } 583 } 584 585 /// Not clipped, so use only if you know the line will be within the UI element 586 private static void DrawLine(Vector2 start, Vector2 end, int width) 587 { 588 var matrix = GUI.matrix; 589 Vector2 distance = end - start; 590 float angle = Mathf.Rad2Deg * Mathf.Atan(distance.y / distance.x); 591 if (distance.x < 0) 592 { 593 angle += 180; 594 } 595 596 int width2 = (int)Mathf.Ceil(width / 2); 597 598 Rect pos = new Rect(start.x, start.y - width2, distance.magnitude, width); 599 600 GUIUtility.RotateAroundPivot(angle, start); 601 GUI.DrawTexture(pos, backgroundTexture); 602 GUI.matrix = matrix; // restore initial matrix to avoid floating point inaccuracies with RotateAroundPivot(...) 603 } 604 605 private void DrawLine(Rect line, float angle) 606 { 607 var matrix = GUI.matrix; 608 GUIUtility.RotateAroundPivot(angle, new Vector2(line.x, line.center.y)); 609 GUI.DrawTexture(line, backgroundTexture); 610 GUI.matrix = matrix; // restore initial matrix to avoid floating point inaccuracies with RotateAroundPivot(...) 611 } 612 613 private void DrawHover(GUIStyle style, Rect workingArea) 614 { 615 if (!hover.valid) 616 { 617 return; 618 } 619 if (hover.box.size == Vector2.zero) 620 { 621 // Hover size has not been initialized yet. 622 var sizex = (hover.info.Length + 1.5f) * fontWidth; 623 var sizey = 2f * fontHeight; 624 var posx = hover.box.x + 10; 625 var posy = hover.box.y; 626 if (posx + sizex > workingArea.width) 627 { 628 // Box exceeds rightmost bound so cramp it in 629 var availableSpace = workingArea.xMax - posx; 630 var possiblyCharacters = Mathf.FloorToInt(availableSpace / fontWidth); 631 var neededLines = hover.info.Length / possiblyCharacters + 2; // +2 to ensure enough lines for the label 632 633 var availableSpacey = workingArea.yMax - posy; 634 var possibleLines = Mathf.FloorToInt(availableSpacey / fontHeight); 635 636 var tmp = neededLines - possibleLines; 637 if (tmp > 0) 638 { 639 posy -= tmp * fontHeight; 640 } 641 642 sizey = neededLines * fontHeight + fontHeight; 643 sizex = availableSpace; 644 } 645 hover.box.size = new Vector2(sizex, sizey); 646 hover.box.position = new Vector2(posx, posy); 647 } 648 var col = GUI.color; 649 GUI.color = _hoverBoxColor; 650 651 var pos = hover.box.position; 652 var size = hover.box.size; 653 GUI.Box(hover.box, "", textureStyle); 654 655 GUI.color = _hoverTextColor; 656 var hoverStyle = style; 657 hoverStyle.wordWrap = true; 658 GUI.Label( 659 new Rect(pos.x + fontWidth * 0.5f, 660 pos.y + fontHeight * 0.5f, 661 size.x - fontWidth/2f, 662 size.y - fontHeight), 663 hover.info, hoverStyle); 664 665 GUI.color = col; 666 } 667 668 private void MakeBranchThin(ref Branch branch) 669 { 670 int lineThickness = regLineThickness; 671 672 branch.StartHorizontal.height = lineThickness; 673 branch.VerticalLine.width = lineThickness; 674 branch.EndHorizontal.height = lineThickness; 675 676 branch.UpperLine.height = lineThickness; 677 branch.LowerLine.height = lineThickness; 678 679 // Adjusting position for arrow tip for thicker lines. 680 branch.UpperLine.y += (highlightLineThickness - regLineThickness); 681 682 branch.UpperLine.position -= new Vector2(.5f, .5f); 683 branch.LowerLine.position -= new Vector2(.5f, -.5f); 684 685 // Make end part of arrow expand upwards. 686 branch.EndHorizontal.y += (highlightLineThickness - regLineThickness); 687 } 688 689 /// <summary> 690 /// Use this for hover, as there is a slight visual mis-balance 691 /// between cursor position and visual cursor. 692 /// </summary> 693 private Vector2 GetMousePosForHover() 694 { 695 Vector2 mousePos = Event.current.mousePosition; 696 mousePos.y -= 0.5f; 697 mousePos.x += 0.5f; 698 return mousePos; 699 } 700 701 /// <summary> 702 /// Calculate the position and size of an edge, and return it as a branch. 703 /// </summary> 704 /// <param name="edge"> The edge to base branch on. </param> 705 /// <param name="x"> Start x position of branch. </param> 706 /// <param name="y1"> Start y position of branch. </param> 707 /// <param name="y2"> End y position of branch. </param> 708 /// <param name="w"> Depth of branch. </param> 709 private Branch CalculateBranch(BurstDisassembler.AsmEdge edge, float x, float y1, float y2, int w) 710 { 711 bool isEdgeHovered = edge.Equals(_prevHoveredEdge); 712 713 int lineThickness = isEdgeHovered 714 ? highlightLineThickness 715 : regLineThickness; 716 717 // Calculate rectangles for branch arrows: 718 var start = new Vector2(x, y1 + _verticalPad); 719 var end = start + new Vector2(-(w * 10), 0); 720 721 var branchStartPos = new Rect(end.x - lineThickness / 2, start.y - 1, start.x - end.x + lineThickness / 2, lineThickness); 722 723 start = end; 724 end = start + new Vector2(0, y2 - y1); 725 726 var branchVerticalPartPos = end.y < start.y 727 ? new Rect(start.x - 1, end.y, lineThickness, start.y - end.y) 728 : new Rect(start.x - 1, start.y, lineThickness, end.y - start.y); 729 730 start = end; 731 end = start + new Vector2(w * 10, 0); 732 733 var branchEndPos = new Rect(start.x - lineThickness / 2, start.y - 1, end.x - start.x, lineThickness); 734 735 // Calculate rectangles for arrow tip. 736 Vector2 lowerArrowTipStart = end; 737 Vector2 upperArrowtipStart = end; 738 739 // Moving the arrow tips closer together. 740 upperArrowtipStart += new Vector2(0, 0.5f); 741 lowerArrowTipStart -= new Vector2(0, 0.5f); 742 743 // Upper arrow tip. 744 var upperArrowTipEnd = upperArrowtipStart + new Vector2(-5, -5); 745 746 var upperLine = new Rect(upperArrowtipStart.x, upperArrowtipStart.y - (int)Mathf.Ceil(lineThickness / 2), (upperArrowTipEnd - upperArrowtipStart).magnitude, lineThickness); 747 748 // Lower arrow tip. 749 var lowerArrowtipEnd = lowerArrowTipStart + new Vector2(-5, 5); 750 var lowerLine = new Rect(lowerArrowTipStart.x, lowerArrowTipStart.y - (int)Mathf.Ceil(lineThickness / 2), (lowerArrowtipEnd - lowerArrowTipStart).magnitude, lineThickness); 751 752 if (isEdgeHovered) 753 { 754 // Adjusting position for arrow tip for thicker lines. 755 upperArrowtipStart.y -= (highlightLineThickness - regLineThickness); 756 upperArrowTipEnd.y -= (highlightLineThickness - regLineThickness); 757 upperLine.y -= (highlightLineThickness - regLineThickness); 758 759 upperLine.position += new Vector2(.5f, .5f); 760 lowerLine.position += new Vector2(.5f, -.5f); 761 762 // Make end part of arrow expand upwards. 763 branchEndPos.y -= (highlightLineThickness - regLineThickness); 764 } 765 766 var branch = new Branch(edge, branchStartPos, branchVerticalPartPos, branchEndPos, upperLine, lowerLine, 767 BurstMath.CalculateAngle(upperArrowtipStart, upperArrowTipEnd), BurstMath.CalculateAngle(lowerArrowTipStart, lowerArrowtipEnd)); 768 769 // Handling whether mouse is hovering over edge. 770 Vector2 mousePos = GetMousePosForHover(); 771 772 // Rotate mousePos so it seems like lower arrow tip is not rotated. 773 Vector2 lowerArrowTipPivot = lowerArrowTipStart; 774 lowerArrowTipPivot.y -= (int)Mathf.Ceil(lineThickness / 2); 775 Vector2 angledMouseLower = BurstMath.AnglePoint(BurstMath.CalculateAngle(lowerArrowTipPivot, lowerArrowtipEnd), mousePos, new Vector2(lowerLine.x, lowerLine.center.y)); 776 angledMouseLower.y -= (int)Mathf.Ceil(lineThickness / 2); 777 778 Vector2 upperArrowTipPivot = upperArrowtipStart; 779 upperArrowTipPivot.y += (int)Mathf.Ceil(lineThickness / 2); 780 Vector2 angleMouseUpper = BurstMath.AnglePoint(BurstMath.CalculateAngle(upperArrowTipPivot, upperArrowTipEnd) - 360, mousePos, new Vector2(upperLine.x, upperLine.center.y)); 781 angleMouseUpper.y += (int)Mathf.Ceil(lineThickness / 2); 782 783 if (BurstMath.AdjustedContains(branchStartPos, mousePos) || BurstMath.AdjustedContains(branchVerticalPartPos, mousePos) || BurstMath.AdjustedContains(branchEndPos, mousePos) 784 || BurstMath.AdjustedContains(lowerLine, angledMouseLower) || BurstMath.AdjustedContains(upperLine, angleMouseUpper)) 785 { 786 // Handling whether another branch was already made thick is done in DrawBranch(...). 787 if (!hoveredBranch.Edge.Equals(default(BurstDisassembler.AsmEdge)) && hoveredBranch.Edge.Equals(_prevHoveredEdge)) 788 { 789 return branch; 790 } 791 hoveredBranch = branch; 792 } 793 return branch; 794 } 795 796 797 798 /// <summary> 799 /// Draws a branch as a jump-able set of boxes. 800 /// </summary> 801 /// <param name="branch"> The branch to draw. </param> 802 /// <param name="w"> Depth of the branch. </param> 803 /// <param name="colourWheel"> Array of possible colors for branches. </param> 804 /// <param name="workingArea"> Current view in inspector. </param> 805 private void DrawBranch(Branch branch, int w, Rect workingArea) 806 { 807 bool isBranchHovered = branch.Edge.Equals(hoveredBranch.Edge); 808 Vector2 scrollPos = workingArea.position; 809 810 int lineThickness = isBranchHovered 811 ? highlightLineThickness 812 : regLineThickness; 813 814 GUI.color = _colourWheel[w % _colourWheel.Length]; 815 816 // Check if hovered but not made thick yet: 817 if (isBranchHovered && !branch.Edge.Equals(_prevHoveredEdge)) 818 { 819 // alter thickness as edge is hovered over. 820 branch.StartHorizontal.height = highlightLineThickness; 821 branch.VerticalLine.width = highlightLineThickness; 822 branch.EndHorizontal.height = highlightLineThickness; 823 824 branch.UpperLine.height = highlightLineThickness; 825 branch.LowerLine.height = highlightLineThickness; 826 827 // Adjusting position for arrow tip for thicker lines. 828 branch.UpperLine.y -= (highlightLineThickness - regLineThickness); 829 830 branch.UpperLine.position += new Vector2(.5f, .5f); 831 branch.LowerLine.position += new Vector2(.5f, -.5f); 832 833 // Make end part of arrow expand upwards. 834 branch.EndHorizontal.y -= (highlightLineThickness - regLineThickness); 835 } 836 else if (branch.EndHorizontal.height == highlightLineThickness && !isBranchHovered) 837 { 838 // the branch was previously hovered, but is now hidden behind 839 // another branch, that is the one being visually hovered. 840 MakeBranchThin(ref branch); 841 } 842 843 // Render actual arrows: 844 GUI.Box(branch.StartHorizontal, "", textureStyle); 845 GUI.Box(branch.VerticalLine, "", textureStyle); 846 847 float yy = branch.EndHorizontal.y - scrollPos.y; 848 if (yy >= 0 && yy < workingArea.height) 849 { 850 GUI.Box(branch.EndHorizontal, "", textureStyle); 851 DrawLine(branch.UpperLine, branch.UpperAngle); 852 DrawLine(branch.LowerLine, branch.LowerAngle); 853 } 854 855 // Use below instead of buttons, to make the currently hovered edge the jump-able, 856 // and not the edge rendered first i.e. when two edges overlap. 857 if (Event.current.type == EventType.MouseDown && isBranchHovered) 858 { 859 Vector2 mousePos = GetMousePosForHover(); 860 861 // Rotate mousePos so it seems like lower arrow tip is not rotated. 862 Vector2 lowerLineEnd = branch.LowerLine.position; 863 lowerLineEnd.y += (int)Mathf.Ceil(lineThickness / 2); 864 lowerLineEnd += new Vector2(-5, 5); 865 866 Vector2 angledMouseLower = BurstMath.AnglePoint(BurstMath.CalculateAngle(branch.LowerLine.position, lowerLineEnd), 867 mousePos, new Vector2(branch.LowerLine.x, branch.LowerLine.center.y)); 868 angledMouseLower.y -= (int)Mathf.Ceil(lineThickness / 2); 869 870 // Rotate mousePos so it seems like upper arrow tip isn't rotated. 871 Vector2 upperArrowTipPivot = branch.UpperLine.position; 872 upperArrowTipPivot.y += 2 * (int)Mathf.Ceil(lineThickness / 2); 873 874 Vector2 upperLineEnd = branch.UpperLine.position; 875 upperLineEnd.y += (int)Mathf.Ceil(lineThickness / 2); 876 upperLineEnd += new Vector2(-5, -5); 877 878 Vector2 angleMouseUpper = BurstMath.AnglePoint(BurstMath.CalculateAngle(upperArrowTipPivot, upperLineEnd) - 360, 879 Event.current.mousePosition, new Vector2(branch.UpperLine.x, branch.UpperLine.center.y)); 880 angleMouseUpper.y += (int)Mathf.Ceil(lineThickness / 2); 881 882 // Se if a jump should be made and jump. 883 if (BurstMath.AdjustedContains(branch.StartHorizontal, mousePos)) 884 { 885 // make end arrow be at mouse cursor. 886 var target = branch.EndHorizontal; 887 target.y += branch.StartHorizontal.y < branch.EndHorizontal.y 888 ? (workingArea.yMax - mousePos.y) 889 : (workingArea.yMin - mousePos.y + highlightLineThickness / 2f); 890 GUI.ScrollTo(target); 891 Event.current.Use(); 892 } 893 else if (BurstMath.AdjustedContains(branch.EndHorizontal, mousePos) || BurstMath.AdjustedContains(branch.LowerLine, angledMouseLower) 894 || BurstMath.AdjustedContains(branch.UpperLine, angleMouseUpper) || BurstMath.AdjustedContains(branch.VerticalLine, mousePos)) 895 { 896 var target = branch.StartHorizontal; 897 target.y += branch.StartHorizontal.y < branch.EndHorizontal.y 898 ? workingArea.yMin - mousePos.y + highlightLineThickness / 2 899 : workingArea.yMax - mousePos.y; 900 GUI.ScrollTo(target); 901 Event.current.Use(); 902 } 903 } 904 } 905 906 private bool DrawFold(float x, float y, bool state, BurstDisassembler.AsmBlockKind kind) 907 { 908 var currentBg = GUI.backgroundColor; 909 switch (kind) 910 { 911 case BurstDisassembler.AsmBlockKind.None: 912 case BurstDisassembler.AsmBlockKind.Directive: 913 case BurstDisassembler.AsmBlockKind.Block: 914 GUI.backgroundColor = Color.grey; 915 break; 916 case BurstDisassembler.AsmBlockKind.Code: 917 GUI.backgroundColor = Color.green; 918 break; 919 case BurstDisassembler.AsmBlockKind.Data: 920 GUI.backgroundColor = Color.magenta; 921 break; 922 } 923 924 var pressed = false; 925 if (state) 926 { 927 pressed = GUI.Button(new Rect(x - fontWidth, y, fontWidth, fontHeight), "+"); 928 } 929 else 930 { 931 pressed = GUI.Button(new Rect(x - fontWidth, y, fontWidth, fontHeight), "-"); 932 } 933 934 GUI.backgroundColor = currentBg; 935 936 return pressed; 937 } 938 939 internal void Layout(GUIStyle style, float hPad) 940 { 941 var oldFontHeight = fontHeight; 942 var oldFontWidth = fontWidth; 943 944#if UNITY_2023_1_OR_NEWER 945 var cacheWidth0 = style.CalcSize(new GUIContent("W")); 946 var cacheWidth1 = style.CalcSize(new GUIContent("WW")); 947 948 var cacheHeight0 = style.CalcSize(new GUIContent("\n")); 949 var cacheHeight1 = style.CalcSize(new GUIContent("\n\n")); 950 951 fontHeight = cacheHeight1.y - cacheHeight0.y; 952 fontWidth = cacheWidth1.x - cacheWidth0.x; 953#else 954 var cacheSize0 = style.CalcSize(new GUIContent("W\n")); 955 var cacheSize1 = style.CalcSize(new GUIContent("WW\n\n")); 956 957 fontHeight = cacheSize1.y - cacheSize0.y; 958 fontWidth = cacheSize1.x - cacheSize0.x; 959#endif 960 961 _verticalPad = fontHeight * 0.5f; 962 963 // oldFontWidth == 0 means we picked the first target after opening inspector. 964 var diffX = oldFontWidth != 0 ? fontWidth / oldFontWidth : 0.0f; 965 966 if (HasSelection && (oldFontWidth != fontWidth || oldFontHeight != fontHeight)) 967 { 968 float diffY = fontHeight / oldFontHeight; 969 970 // We only have to take padding into account for x-axis, as it's the only one with it. 971 selectPos = new Vector2(((selectPos.x - hPad) * diffX) + hPad, diffY * selectPos.y); 972 selectDragPos = new Vector2(((selectDragPos.x - hPad) * diffX) + hPad, diffY * selectDragPos.y); 973 } 974 975 invalidated = false; 976 var oldFinalAreaSizeX = finalAreaSize.x; 977 finalAreaSize = new Vector2(0.0f, 0.0f); 978 979 if (_disassembler == null) 980 { 981 LayoutPlain(style); 982 } 983 else 984 { 985 finalAreaSize.y = _initialLineCount * fontHeight; 986 finalAreaSize.x = oldFinalAreaSizeX * diffX; 987 } 988 } 989 990 /// <summary> 991 /// Find accumulated fragment number the block start at. 992 /// </summary> 993 /// <param name="start">Block index to start search at. Should match <see cref="acc"/>.</param> 994 /// <param name="acc">Accumulated fragment number that block <see cref="start"/> start at.</param> 995 internal int GetFragNrFromBlockIdx(int blockIdx, int start, int acc, ref float positionY) 996 { 997 int fragNr = acc; 998 999 for (int b = start; b < blockIdx; b++) 1000 { 1001 if (_folded[b]) 1002 { 1003 fragNr += GetPlainFragments(b).Count; 1004 positionY += fontHeight; 1005 } 1006 else 1007 { 1008 foreach (var frag in GetPlainFragments(b)) 1009 { 1010 fragNr++; 1011 positionY += frag.lineCount * fontHeight; 1012 } 1013 } 1014 } 1015 1016 return fragNr; 1017 } 1018 1019 internal bool SearchText(SearchCriteria searchCriteria, Regex regx, ref Rect workingArea, bool stopFirstMatch = false, bool focusTop = false) => 1020 _disassembler == null 1021 ? SearchTextPlain(searchCriteria, regx, ref workingArea, stopFirstMatch, focusTop) 1022 : SearchTextEnhanced(searchCriteria, regx, ref workingArea, stopFirstMatch, focusTop); 1023 1024 /// <summary> 1025 /// Searches <see cref="m_Text"/> for every match, and saves each match in <see cref="searchHits"/>. 1026 /// </summary> 1027 /// <param name="criteria">Search options.</param> 1028 /// <param name="regx">Used if <see cref="criteria"/> specifies a regex search.</param> 1029 /// <param name="workingArea">Inspector View.</param> 1030 /// <param name="stopFirstMatch">Whether search should stop at first match.</param> 1031 /// <param name="focusTop">Whether match focus should be at the top of view.</param> 1032 private bool SearchTextEnhanced(SearchCriteria criteria, Regex regx, ref Rect workingArea, bool stopFirstMatch = false, bool focusTop = false) 1033 { 1034 searchHits.Clear(); 1035 _nrSearchHits = 0; 1036 _activeSearchHitIdx = -1; 1037 1038 var matches = stopFirstMatch 1039 ? new List<(int, int)> { BurstStringSearch.FindMatch(m_Text, criteria, regx, 0) } 1040 : BurstStringSearch.FindAllMatches(m_Text, criteria, regx); 1041 1042 var renderStartY = workingArea.y; 1043 1044 bool doRepaint = false; 1045 1046 float positionY = 0f; 1047 int accFragNr = 0; 1048 int accBlockNr = 0; 1049 int accIdx; 1050 foreach (var (idx, len) in matches) 1051 { 1052 if (len <= 0) 1053 { 1054 continue; 1055 } 1056 int idxEnd = idx + len; 1057 1058 // Find block + fragment match starts in: 1059 var blockStartInfo = _disassembler.GetBlockIdxFromTextIdx(idx, accBlockNr); 1060 // search hit within a folded block => unfold block. 1061 if (_folded[blockStartInfo.idx]) FoldUnfoldBlock(blockStartInfo.idx); 1062 // Finds the accumulated fragment number of the first fragment in block 1063 accFragNr = GetFragNrFromBlockIdx(blockStartInfo.idx, accBlockNr, accFragNr, ref positionY); 1064 1065 int fragNrStart; 1066 float fragPositionY; 1067 // Finds actual accumulated fragment we just reached and it's index 1068 (fragNrStart, accIdx, fragPositionY) = GetFragNrFromEnhancedTextIdx(idx, blockStartInfo.idx, accFragNr, blockStartInfo.l, positionY); 1069 1070 var frag1Text = GetPlainFragments(blockStartInfo.idx)[fragNrStart - accFragNr].text; 1071 1072 // Make idx relative to fragment: 1073 int idxStartInFrag1 = idx - accIdx; 1074 int idxEndInFrag1 = Math.Min(idxStartInFrag1 + len, frag1Text.Length); 1075 1076 // positionY should be stationed at top of fragment we have hit in. 1077 // 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!!! 1078 int exactLineNr = BurstStringSearch.FindLineNr(frag1Text, idxStartInFrag1); 1079 float exactPositionY = fragPositionY + exactLineNr * fontHeight; 1080 1081 // Find block + fragment match ends in: 1082 var blockEndInfo = _disassembler.GetBlockIdxFromTextIdx(idxEnd, blockStartInfo.idx); 1083 // Finds the accumulated fragment number of match's last fragment 1084 accFragNr = GetFragNrFromBlockIdx(blockEndInfo.idx, blockStartInfo.idx, accFragNr, ref positionY); 1085 int fragNrEnd; 1086 // Finds the actual accumulated fragment number of match last fragment 1087 (fragNrEnd, accIdx, _) = GetFragNrFromEnhancedTextIdx(idxEnd, blockEndInfo.idx, accFragNr, blockEndInfo.l, positionY); 1088 1089 accBlockNr = blockEndInfo.idx; 1090 1091 if (!searchHits.ContainsKey(fragNrStart)) searchHits.Add(fragNrStart, new List<(int startIdx, int endIdx, int nr)>()); 1092 searchHits[fragNrStart].Add((idxStartInFrag1, idxEndInFrag1, _nrSearchHits)); 1093 1094 if (fragNrStart < fragNrEnd) 1095 { 1096 // match span multiple fragments 1097 fragNrStart++; // Already captured this fragment 1098 1099 // For every fragment in-between first and last we capture the whole fragment 1100 for (; fragNrStart < fragNrEnd; fragNrStart++) 1101 { 1102 if (!searchHits.ContainsKey(fragNrStart)) searchHits.Add(fragNrStart, new List<(int startIdx, int endIdx, int nr)>()); 1103 searchHits[fragNrStart].Add((0, int.MaxValue, _nrSearchHits)); // Max value to indicate it's the entire fragment 1104 } 1105 int idxStartInFragN = accIdx; 1106 int idxEndInFragN = idxEnd - idxStartInFragN; 1107 1108 if (!searchHits.ContainsKey(fragNrStart)) searchHits.Add(fragNrStart, new List<(int startIdx, int endIdx, int nr)>()); 1109 searchHits[fragNrStart].Add((0, idxEndInFragN, _nrSearchHits)); 1110 } 1111 1112 if (_activeSearchHitIdx < 0 && exactPositionY >= renderStartY) 1113 { 1114 _activeSearchHitIdx = _nrSearchHits; 1115 doRepaint = SetScrollOnView(ref workingArea, frag1Text, exactLineNr, idxStartInFrag1, exactPositionY); 1116 } 1117 _nrSearchHits++; 1118 } 1119 return doRepaint; 1120 } 1121 1122 private bool SetScrollOnView(ref Rect workingArea, string text, int lineNr, int matchIdx, float goToPosY) 1123 { 1124 // Find x position of search word: 1125 var startOfLine = lineNr > 0 ? GetEndIndexOfPlainLine(text, lineNr - 1).total + 1 : 0; 1126 float goToPosX = (matchIdx - startOfLine) * fontWidth + _actualHorizontalPad; 1127 1128 // Set x position of view if search outside: 1129 bool leftOfView = workingArea.xMin > goToPosX; 1130 bool rightOfView = goToPosX > workingArea.xMax; 1131 workingArea.x = leftOfView 1132 ? goToPosX - 5 * fontWidth 1133 : rightOfView 1134 ? goToPosX - workingArea.width + 5 * fontWidth 1135 : workingArea.x; 1136 1137 // Set y position of view if search outside: 1138 bool aboveView = workingArea.yMin > goToPosY; 1139 bool belowView = goToPosY > workingArea.yMax; 1140 workingArea.y = aboveView 1141 ? goToPosY - 5 * fontHeight 1142 : belowView 1143 ? goToPosY - workingArea.height + 5*fontHeight 1144 : workingArea.y; 1145 1146 return aboveView || belowView || leftOfView || rightOfView; 1147 } 1148 1149 /// <remarks>Works on non-colored text.</remarks> 1150 /// <param name="goal">Index we want fragment number from.</param> 1151 /// <param name="blockNrStart">Block to start search in.</param> 1152 /// <param name="fragNrStart">Accumulated fragment number of <see cref="blockNrStart"/>.</param> 1153 /// <param name="start">Start index of <see cref="blockNrStart"/>.</param> 1154 /// <returns>(accumulated fragment number, total index for returning fragment number)</returns> 1155 internal (int fragNr, int idx, float positionY) GetFragNrFromEnhancedTextIdx(int goal, int blockNrStart, int fragNrStart, int start, float positionY) 1156 { 1157 bool goThroughfrags(int goal, int blockIdx, ref int start, ref int f, ref float positionY) 1158 { 1159 var frags = GetPlainFragments(blockIdx); 1160 for (int i = 0; i < frags.Count; i++) 1161 { 1162 Fragment frag = frags[i]; 1163 int len = frag.text.Length; 1164 if (start + len >= goal) 1165 { 1166 return true; 1167 } 1168 start += len + 1; // +1 because of newline 1169 f++; 1170 positionY += frag.lineCount * fontHeight; 1171 } 1172 return false; 1173 } 1174 1175 int f = fragNrStart; 1176 1177 for (int b = blockNrStart; b < blocksFragmentsPlain.Length; b++) 1178 { 1179 if (goThroughfrags(goal, b, ref start, ref f, ref positionY)) break; 1180 } 1181 1182 return (f, start, positionY); 1183 } 1184 1185 /// <returns>(accumulated fragment number, total index for returning fragment number)</returns> 1186 private (int fragNr, int idx) GetFragNrFromPlainTextIdx(int goal, int fragNrStart, int start, ref float positionY) 1187 { 1188 int i = fragNrStart; 1189 for (; i < m_Fragments.Count; i++) 1190 { 1191 Fragment frag = m_Fragments[i]; 1192 1193 int len = frag.text.Length; 1194 if (start + len >= goal) 1195 { 1196 break; 1197 } 1198 start += len + 1; // +1 because of newline 1199 positionY += frag.lineCount * fontHeight; 1200 } 1201 1202 return (i, start); 1203 } 1204 1205 private bool SearchTextPlain(SearchCriteria criteria, Regex regx, ref Rect workingArea, bool stopFirstMatch = false, bool focusTop = false) 1206 { 1207 _nrSearchHits = 0; 1208 searchHits.Clear(); 1209 _activeSearchHitIdx = -1; 1210 1211 var matches = stopFirstMatch 1212 ? new List<(int, int)> { BurstStringSearch.FindMatch(m_Text, criteria, regx, 0) } 1213 : BurstStringSearch.FindAllMatches(m_Text, criteria, regx); 1214 1215 bool doRepaint = false; 1216 1217 float positionY = 0f; 1218 int accFragNr = 0; 1219 int accFragIdx = 0; 1220 var renderStartY = workingArea.y; 1221 foreach (var (idx, len) in matches) 1222 { 1223 if (len <= 0) 1224 { 1225 continue; 1226 } 1227 int idxEnd = idx + len; 1228 1229 // Find fragment match start in: 1230 (accFragNr, accFragIdx) = GetFragNrFromPlainTextIdx(idx, accFragNr, accFragIdx, ref positionY); 1231 int fragNrStart = accFragNr; 1232 1233 string frag1Text = m_Fragments[fragNrStart].text; 1234 1235 // Get relative index in fragment: 1236 int idxStartInFrag1 = idx - accFragIdx; 1237 int idxEndInFrag1 = Math.Min(idxStartInFrag1 + len, frag1Text.Length); 1238 1239 int exactLineNr = BurstStringSearch.FindLineNr(frag1Text, idxStartInFrag1); 1240 float exactPositionY = positionY + exactLineNr * fontHeight; 1241 1242 // Find fragment match end in: 1243 (accFragNr, accFragIdx) = GetFragNrFromPlainTextIdx(idxEnd, accFragNr, accFragIdx, ref positionY); 1244 int fragNrEnd = accFragNr; 1245 1246 1247 if (!searchHits.ContainsKey(fragNrStart)) searchHits.Add(fragNrStart, new List<(int startIdx, int endIdx, int nr)>()); 1248 searchHits[fragNrStart].Add((idxStartInFrag1, idxEndInFrag1, _nrSearchHits)); 1249 1250 if (fragNrStart < fragNrEnd) 1251 { 1252 // They must directly follow each other 1253 int fragNr = fragNrStart + 1; 1254 for (; fragNr < fragNrEnd; fragNr++) 1255 { 1256 if (!searchHits.ContainsKey(fragNr)) searchHits.Add(fragNr, new List<(int startIdx, int endIdx, int nr)>()); 1257 searchHits[fragNr].Add((0, m_Fragments[fragNr].text.Length, _nrSearchHits)); 1258 } 1259 1260 int idxStartInFrag2 = idxEnd - accFragIdx; 1261 int idxEndInFrag2 = idxEnd - accFragIdx; 1262 1263 if (!searchHits.ContainsKey(fragNr)) searchHits.Add(fragNr, new List<(int startIdx, int endIdx, int nr)>()); 1264 searchHits[fragNr].Add((idxStartInFrag2, idxEndInFrag2, _nrSearchHits)); 1265 } 1266 1267 if (_activeSearchHitIdx < 0 && exactPositionY >= renderStartY) 1268 { 1269 _activeSearchHitIdx = _nrSearchHits; 1270 doRepaint = SetScrollOnView(ref workingArea, frag1Text, exactLineNr, idxStartInFrag1, exactPositionY); 1271 } 1272 _nrSearchHits++; 1273 } 1274 return doRepaint; 1275 } 1276 1277 private void LayoutPlain(GUIStyle style) 1278 { 1279 foreach (var frag in m_Fragments) 1280 { 1281 // Calculate the size as we have hidden control codes in the string 1282 var size = style.CalcSize(new GUIContent(frag.text)); 1283 finalAreaSize.x = Math.Max(finalAreaSize.x, size.x); 1284 finalAreaSize.y += frag.lineCount * fontHeight; 1285 } 1286 } 1287 1288 private void AddFoldedStringColorless(int blockIdx) 1289 { 1290 if (_blocksFragmentsStartPlain[blockIdx].lineCount != 0) return; 1291 1292 string text = null; 1293 if (!_disassembler.IsColored) 1294 { 1295 _blocksFragments[blockIdx] ??= RecomputeFragmentsFromBlock(blockIdx); 1296 text = _blocksFragments[blockIdx][0].text; 1297 } 1298 else 1299 { 1300 text = GetPlainFragments(blockIdx)[0].text; 1301 } 1302 var endOfFirstLineIdx = text.IndexOf('\n'); 1303 if (endOfFirstLineIdx == -1) endOfFirstLineIdx = text.Length; 1304 1305 _blocksFragmentsStartPlain[blockIdx] = new Fragment() { lineCount = 1, text = text.Substring(0, endOfFirstLineIdx) + " ..." }; 1306 } 1307 1308 private void AddFoldedString(int blockIdx) 1309 { 1310 if (_blocksFragmentsStart[blockIdx].lineCount != 0) return; 1311 1312 // precomputing every block to avoid having runtime check at every access later on 1313 _blocksFragments[blockIdx] ??= RecomputeFragmentsFromBlock(blockIdx); 1314 1315 // precompute string to present for folded block. 1316 var text = _blocksFragments[blockIdx][0].text; 1317 1318 var endOfFirstLineIdx = text.IndexOf('\n'); 1319 if (endOfFirstLineIdx == -1) endOfFirstLineIdx = text.Length; 1320 1321 _blocksFragmentsStart[blockIdx] = new Fragment() { lineCount = 1, text = text.Substring(0, endOfFirstLineIdx) + " ..." }; 1322 } 1323 1324 internal void LayoutEnhanced(GUIStyle style, Rect workingArea, bool showBranchMarkers) 1325 { 1326 Vector2 scrollPos = workingArea.position; 1327 if (showBranchMarkers != _oldShowBranchMarkers) 1328 { 1329 // need to alter selection according to padding on x-axis. 1330 if (showBranchMarkers) 1331 { 1332 selectPos.x += (horizontalPad - naturalEnhancedPad); 1333 selectDragPos.x += (horizontalPad - naturalEnhancedPad); 1334 } 1335 else 1336 { 1337 selectPos.x -= (horizontalPad - naturalEnhancedPad); 1338 selectDragPos.x -= (horizontalPad - naturalEnhancedPad); 1339 } 1340 // register cache needs to be cleared: 1341 _lineRegCache.Clear(); 1342 } 1343 _oldShowBranchMarkers = showBranchMarkers; 1344 1345 // Also computes the first and last blocks to render this time and ensures 1346 float positionY = 0.0f; 1347 int lNum = 0; 1348 _renderBlockStart = -1; 1349 _renderBlockEnd = -1; 1350 1351 _selectBlockStart = -1; 1352 _selectStartY = -1f; 1353 _selectBlockEnd = -1; 1354 _selectEndY = -1f; 1355 1356 for (int idx = 0; idx<_disassembler.Blocks.Count; idx++) 1357 { 1358 var block = _disassembler.Blocks[idx]; 1359 var blockHeight = block.Length * fontHeight; 1360 var lHeight = block.Length; 1361 1362 if (_folded[idx]) 1363 { 1364 blockHeight = fontHeight; 1365 lHeight = 1; 1366 } 1367 1368 blockLine[idx] = lNum; 1369 1370 if (_selectBlockStart == -1 && selectPos.y - blockHeight <= positionY) 1371 { 1372 _selectBlockStart = idx; 1373 _selectStartY = positionY; 1374 } 1375 if (_selectBlockEnd == -1 && (selectDragPos.y - blockHeight <= positionY || idx == _disassembler.Blocks.Count - 1)) 1376 { 1377 _selectBlockEnd = idx; 1378 _selectEndY = positionY; 1379 } 1380 1381 // Whole block is above view, or block starts below view. 1382 // If at last block and _renderBlockStart == -1, we must have had all block above our scrollPos. 1383 // Happens when Scroll view is at bottom and fontSize is decreased. As this forces the scroll view upwards, 1384 // we simply set the last block as the one being rendered (avoids an indexOutOfBounds exception). 1385 if (((positionY - scrollPos.y + blockHeight) < 0 || (positionY - scrollPos.y) > workingArea.size.y) 1386 && !(idx == _disassembler.Blocks.Count - 1 && _renderBlockStart == -1)) 1387 { 1388 positionY += blockHeight; 1389 lNum += lHeight; 1390 continue; 1391 } 1392 1393 if (_renderBlockStart == -1) 1394 { 1395 _renderBlockStart = idx; 1396 _renderStartY = positionY; 1397 } 1398 1399 _renderBlockEnd = idx; 1400 1401 if (_blocksFragments[idx] == null) 1402 { 1403 AddFoldedString(idx); 1404 1405 foreach (var fragment in _blocksFragments[idx]) 1406 { 1407 style.CalcMinMaxWidth(new GUIContent(fragment.text), out _, out var maxWidth); 1408 if (finalAreaSize.x < maxWidth) 1409 { 1410 finalAreaSize.x = maxWidth; 1411 } 1412 } 1413 } 1414 1415 positionY += blockHeight; 1416 lNum += lHeight; 1417 } 1418 // selection is below last line of assembly. 1419 if (_selectBlockStart == -1) 1420 { 1421 _selectBlockStart = _disassembler.Blocks.Count - 1; 1422 } 1423 } 1424 1425 internal string GetStartingColorTag(int blockIdx, int textIdx) 1426 { 1427 if (!CopyColorTags || !(_disassembler?.IsColored ?? false)) return ""; 1428 1429 const int colorTagLength = 15; 1430 1431 var text = _folded[blockIdx] 1432 ? GetFragmentStart(blockIdx).text// _blocksFragmentsStart[blockIdx].text 1433 : _disassembler.GetOrRenderBlockToText(blockIdx); 1434 1435 var colorTagIdx = text.LastIndexOf("<color=", textIdx); 1436 if (colorTagIdx == -1) return ""; 1437 1438 // Checking that found tas is actually for this idx 1439 return text.IndexOf("</color>", colorTagIdx, textIdx - colorTagIdx) == -1 1440 ? text.Substring(colorTagIdx, colorTagLength) 1441 : ""; 1442 } 1443 1444 internal string GetEndingColorTag(int blockIdx, int textIdx) 1445 { 1446 if (!CopyColorTags || !(_disassembler?.IsColored ?? false)) return ""; 1447 1448 var text = _folded[blockIdx] 1449 ? GetFragmentStart(blockIdx).text// _blocksFragmentsStart[blockIdx].text 1450 : _disassembler.GetOrRenderBlockToText(blockIdx); 1451 1452 var endColorTagIdx = text.IndexOf("</color>", textIdx); 1453 if (endColorTagIdx == -1) return ""; 1454 1455 // Check that tag actually belongs for thid idx 1456 return text.IndexOf("<color=", textIdx, endColorTagIdx - textIdx) == -1 1457 ? "</color>" 1458 : ""; 1459 } 1460 1461 internal void ChangeCopyMode() 1462 { 1463 CopyColorTags = !CopyColorTags; 1464 1465 // Need to update text selection fields as it should now ignore/use color tags 1466 _textSelectionIdxValid = false; 1467 UpdateEnhancedSelectTextIdx(_oldShowBranchMarkers ? horizontalPad : naturalEnhancedPad); 1468 } 1469 1470 internal void DoSelectionCopy() 1471 { 1472 // textIdxE = -1 to indicate we want till the end of the block of text. 1473 string GetStringFromBlock(int blockIdx, int textIdxS, int textIdxE = -1) 1474 { 1475 string text; 1476 if (_folded[blockIdx]) 1477 { 1478 text = (CopyColorTags 1479 ? GetFragmentStart(blockIdx).text 1480 : GetFragmentStartPlain(blockIdx).text) 1481 + '\n'; 1482 1483 return textIdxE == -1 1484 ? text.Substring(textIdxS, text.Length - textIdxS) 1485 : text.Substring(textIdxS, textIdxE - textIdxS); 1486 } 1487 1488 if (CopyColorTags) 1489 { 1490 text = _disassembler.GetOrRenderBlockToText(blockIdx); 1491 if (textIdxE == -1) textIdxE = text.Length; 1492 } 1493 else 1494 { 1495 text = m_Text; 1496 1497 // Transform textIdxS and textIdxE to absolute index into m_Text 1498 var (blockStart, blockEnd) = _disassembler.BlockIdxs[blockIdx]; 1499 textIdxS += blockStart; 1500 if (textIdxE == -1) textIdxE = blockEnd + 1; // +1 to take newline as well 1501 else textIdxE += blockStart; 1502 } 1503 1504 return text.Substring(textIdxS, textIdxE - textIdxS); 1505 } 1506 1507 if (!HasSelection) return; 1508 1509 var sb = new StringBuilder(); 1510 1511 if (_disassembler != null && enhancedTextSelectionIdxStart != enhancedTextSelectionIdxEnd) 1512 { 1513 if (enhancedTextSelectionIdxStart.blockIdx < enhancedTextSelectionIdxEnd.blockIdx) 1514 { 1515 // Multiple block selection 1516 var blockIdx = enhancedTextSelectionIdxStart.blockIdx; 1517 1518 sb.Append( 1519 GetStartingColorTag(enhancedTextSelectionIdxStart.blockIdx, enhancedTextSelectionIdxStart.textIdx) + 1520 GetStringFromBlock(blockIdx++, enhancedTextSelectionIdxStart.textIdx)); 1521 1522 for (; blockIdx < enhancedTextSelectionIdxEnd.blockIdx; blockIdx++) 1523 { 1524 sb.Append(GetStringFromBlock(blockIdx, 0)); 1525 } 1526 1527 sb.Append( 1528 GetStringFromBlock(blockIdx, 0, enhancedTextSelectionIdxEnd.textIdx) + 1529 GetEndingColorTag(enhancedTextSelectionIdxEnd.blockIdx, enhancedTextSelectionIdxEnd.textIdx)); 1530 } 1531 else 1532 { 1533 // Single block selection 1534 sb.Append( 1535 GetStartingColorTag(enhancedTextSelectionIdxStart.blockIdx, enhancedTextSelectionIdxStart.textIdx) + 1536 GetStringFromBlock( 1537 enhancedTextSelectionIdxStart.blockIdx, 1538 enhancedTextSelectionIdxStart.textIdx, 1539 enhancedTextSelectionIdxEnd.textIdx) + 1540 GetEndingColorTag(enhancedTextSelectionIdxEnd.blockIdx, enhancedTextSelectionIdxEnd.textIdx)); 1541 } 1542 } 1543 else 1544 { 1545 sb.Append(m_Text.Substring(textSelectionIdx.idx, textSelectionIdx.length)); 1546 } 1547 EditorGUIUtility.systemCopyBuffer = sb.ToString(); 1548 } 1549 1550 internal void SelectAll() 1551 { 1552 HasSelection = true; 1553 selectPos = Vector2.zero; 1554 selectDragPos = finalAreaSize; 1555 1556 if (_disassembler != null) 1557 { 1558 enhancedTextSelectionIdxStart = (0, 0); 1559 var blockIdx = _disassembler.Blocks.Count - 1; 1560 1561 enhancedTextSelectionIdxEnd.blockIdx = blockIdx; 1562 // Make sure selection works on appropriate text: 1563 enhancedTextSelectionIdxEnd.textIdx = _folded[blockIdx] 1564 ? CopyColorTags 1565 ? GetFragmentStart(blockIdx).text.Length 1566 : GetFragmentStartPlain(blockIdx).text.Length 1567 : CopyColorTags 1568 ? _disassembler.GetOrRenderBlockToText(blockIdx).Length 1569 : _disassembler.BlockIdxs[blockIdx].endIdx - _disassembler.BlockIdxs[blockIdx].startIdx; 1570 1571 _selectBlockStart = 0; 1572 _selectBlockEnd = blockIdx; 1573 _selectEndY = finalAreaSize.y - 1574 (_folded[blockIdx] ? fontHeight : _disassembler.Blocks[blockIdx].Length * fontHeight); 1575 } 1576 else 1577 { 1578 textSelectionIdx = (0, m_Text.Length); 1579 } 1580 _textSelectionIdxValid = true; 1581 } 1582 1583 private void ScrollDownToSelection(Rect workingArea) 1584 { 1585 if (!workingArea.Contains(selectDragPos + new Vector2(fontWidth, fontHeight / 2))) 1586 { 1587 GUI.ScrollTo(new Rect(selectDragPos + new Vector2(fontWidth, fontHeight), Vector2.zero)); 1588 } 1589 1590 _textSelectionIdxValid = false; 1591 } 1592 1593 private void ScrollUpToSelection(Rect workingArea) 1594 { 1595 if (!workingArea.Contains(selectDragPos - new Vector2(0, fontHeight / 2))) 1596 { 1597 GUI.ScrollTo(new Rect(selectDragPos - new Vector2(0, fontHeight), Vector2.zero)); 1598 } 1599 1600 _textSelectionIdxValid = false; 1601 } 1602 1603 1604 internal void MoveSelectionLeft(Rect workingArea, bool showBranchMarkers) 1605 { 1606 HasSelection = true; 1607 if (_disassembler != null) 1608 { 1609 float hPad = showBranchMarkers ? horizontalPad : naturalEnhancedPad; 1610 string text; 1611 int prevLineIdx = Mathf.FloorToInt((selectDragPos.y - _selectEndY) / fontHeight) - 1; 1612 1613 if (selectDragPos.x < hPad + fontWidth) 1614 { 1615 if (prevLineIdx < 0 && _selectBlockEnd == 0) 1616 { 1617 // we are at the beginning of the text. 1618 return; 1619 } 1620 1621 if (prevLineIdx < 0 && _selectBlockEnd > 0) 1622 { 1623 // get text from previous block, and do calculations for that. 1624 _selectBlockEnd--; 1625 _selectEndY -= _folded[_selectBlockEnd] 1626 ? fontHeight 1627 : _disassembler.Blocks[_selectBlockEnd].Length * fontHeight; 1628 1629 (text, prevLineIdx) = !_folded[_selectBlockEnd] 1630 ? (_disassembler.GetOrRenderBlockToText(_selectBlockEnd), _disassembler.Blocks[_selectBlockEnd].Length - 1) 1631 : (GetFragmentStart(_selectBlockEnd).text + '\n', 0); 1632 } 1633 else 1634 { 1635 text = _folded[_selectBlockEnd] 1636 ? GetFragmentStart(_selectBlockEnd).text + '\n' 1637 : _disassembler.GetOrRenderBlockToText(_selectBlockEnd); 1638 } 1639 var charsInLine = GetEndIndexOfColoredLine(text, prevLineIdx).relative; 1640 1641 selectDragPos.x = charsInLine * fontWidth + hPad + fontWidth / 2; 1642 selectDragPos.y -= fontHeight; 1643 } 1644 else 1645 { 1646 // simply move selection left. 1647 text = _folded[_selectBlockEnd] 1648 ? GetFragmentStart(_selectBlockEnd).text + '\n' 1649 : _disassembler.GetOrRenderBlockToText(_selectBlockEnd); 1650 var charsInLine = GetEndIndexOfColoredLine(text, prevLineIdx + 1).relative; 1651 1652 if (selectDragPos.x > charsInLine * fontWidth + hPad) 1653 selectDragPos.x = hPad + charsInLine * fontWidth + fontWidth / 2; 1654 1655 selectDragPos.x -= fontWidth; 1656 } 1657 } 1658 else 1659 { 1660 int prevLineIdx = Mathf.FloorToInt((selectDragPos.y) / fontHeight) - 1; 1661 1662 if (selectDragPos.x < fontWidth) 1663 { 1664 if (prevLineIdx < 0) 1665 { 1666 // we are at beginning of the text 1667 return; 1668 } 1669 1670 int charsInLine = GetEndIndexOfPlainLine(m_Text, prevLineIdx).relative; 1671 1672 selectDragPos.x = charsInLine * fontWidth + fontWidth / 2; 1673 selectDragPos.y -= fontHeight; 1674 } 1675 else 1676 { 1677 // simply move selection left. 1678 int charsInLine = GetEndIndexOfPlainLine(m_Text, prevLineIdx+1).relative; 1679 if (selectDragPos.x > charsInLine * fontWidth) 1680 { 1681 selectDragPos.x = charsInLine * fontWidth + fontWidth / 2; 1682 } 1683 1684 selectDragPos.x -= fontWidth; 1685 } 1686 } 1687 1688 // check if we moved outside of view and scroll if true. 1689 ScrollUpToSelection(workingArea); 1690 } 1691 1692 internal void MoveSelectionRight(Rect workingArea, bool showBranchMarkers) 1693 { 1694 HasSelection = true; 1695 if (_disassembler != null) 1696 { 1697 var hPad = showBranchMarkers ? horizontalPad : naturalEnhancedPad; 1698 1699 var text = _folded[_selectBlockEnd] 1700 ? GetFragmentStart(_selectBlockEnd).text + '\n' 1701 : _disassembler.GetOrRenderBlockToText(_selectBlockEnd); 1702 1703 var thisLine = Mathf.FloorToInt((selectDragPos.y - _selectEndY) / fontHeight); 1704 var charsInLine = GetEndIndexOfColoredLine(text, thisLine).relative; 1705 1706 if (selectDragPos.x >= hPad + charsInLine * fontWidth) 1707 { 1708 // move down a line: 1709 thisLine++; 1710 1711 int lineCount = _folded[_selectBlockEnd] ? 1 : _disassembler.Blocks[_selectBlockEnd].Length; 1712 1713 if (thisLine > lineCount && _selectBlockEnd == _disassembler.Blocks.Count - 1) 1714 { 1715 // We are at the end of the text 1716 return; 1717 } 1718 1719 if (thisLine > lineCount) 1720 { 1721 // selected into next block. 1722 _selectBlockEnd++; 1723 _selectEndY += _folded[_selectBlockEnd - 1] 1724 ? fontHeight 1725 : _disassembler.Blocks[_selectBlockEnd - 1].Length * fontHeight; 1726 } 1727 1728 selectDragPos.x = hPad + fontWidth/2; 1729 selectDragPos.y += fontHeight; 1730 } 1731 else 1732 { 1733 // simply move selection right. 1734 if (selectDragPos.x < hPad) 1735 { 1736 selectDragPos.x = hPad + fontWidth / 2; 1737 } 1738 1739 selectDragPos.x += fontWidth; 1740 } 1741 } 1742 else 1743 { 1744 int thisLine = Mathf.FloorToInt((selectDragPos.y) / fontHeight); 1745 1746 int charsInLine = GetEndIndexOfColoredLine(m_Text, thisLine).relative; 1747 1748 if (selectDragPos.x >= charsInLine*fontWidth) 1749 { 1750 thisLine++; 1751 1752 if (thisLine >= _mTextLines) 1753 { 1754 // we are at end of the text 1755 return; 1756 } 1757 1758 selectDragPos.x = 0f; 1759 selectDragPos.y += fontHeight; 1760 1761 } 1762 else 1763 { 1764 // simply move selection right. 1765 selectDragPos.x += fontWidth; 1766 } 1767 } 1768 // check if we moved outside of view and scroll if true. 1769 ScrollDownToSelection(workingArea); 1770 } 1771 1772 internal void MoveSelectionUp(Rect workingArea, bool showBranchMarkers) 1773 { 1774 HasSelection = true; 1775 if (_disassembler != null) 1776 { 1777 int thisLine = Mathf.FloorToInt((selectDragPos.y - _selectEndY) / fontHeight); 1778 1779 // At the first line 1780 if (thisLine == 0 && _selectBlockEnd == 0) return; 1781 1782 if (thisLine == 0) 1783 { 1784 // We are moving on to a block above 1785 _selectBlockEnd--; 1786 _selectEndY -= _folded[_selectBlockEnd] 1787 ? fontHeight 1788 : _disassembler.Blocks[_selectBlockEnd].Length * fontHeight; 1789 } 1790 } 1791 else 1792 { 1793 int thisLine = Mathf.FloorToInt((selectDragPos.y) / fontHeight) - 1; 1794 1795 if (thisLine < 0) return; 1796 } 1797 1798 selectDragPos.y -= fontHeight; 1799 1800 // check if we moved outside of view and scroll if true. 1801 ScrollUpToSelection(workingArea); 1802 } 1803 1804 internal void MoveSelectionDown(Rect workingArea, bool showBranchMarkers) 1805 { 1806 HasSelection = true; 1807 if (_disassembler != null) 1808 { 1809 int thisLine = Mathf.FloorToInt((selectDragPos.y - _selectEndY) / fontHeight) + 1; 1810 1811 int lineCount = _folded[_selectBlockEnd] ? 0 : _disassembler.Blocks[_selectBlockEnd].Length - 1; 1812 1813 if (thisLine > lineCount) 1814 { 1815 // At the last line 1816 if (_selectBlockEnd == _disassembler.Blocks.Count - 1) { return; } 1817 1818 // Moved into next block 1819 _selectBlockEnd++; 1820 _selectEndY += _folded[_selectBlockEnd - 1] 1821 ? fontHeight 1822 : _disassembler.Blocks[_selectBlockEnd - 1].Length * fontHeight; 1823 } 1824 } 1825 else 1826 { 1827 int thisLine = Mathf.FloorToInt(selectDragPos.y / fontHeight) + 1; 1828 1829 if (thisLine > _mTextLines) return; 1830 } 1831 1832 selectDragPos.y += fontHeight; 1833 1834 // check if we moved outside of view and scroll if true. 1835 ScrollDownToSelection(workingArea); 1836 } 1837 1838 internal bool MouseOutsideView(Rect workingArea, Vector2 mousePos, int controlID) 1839 { 1840 if (!workingArea.Contains(mousePos)) 1841 { 1842 if (GUIUtility.hotControl == controlID && Event.current.rawType == EventType.MouseUp) 1843 { 1844 _mouseDown = false; 1845 } 1846 1847 _mouseOutsideBounds = true; 1848 return true; 1849 } 1850 1851 _mouseOutsideBounds = false; 1852 return false; 1853 } 1854 1855 private Vector2 MouseClickAndClamp(bool showBranchMarkers, Vector2 mousePos) 1856 { 1857 HasSelection = true; 1858 if (_disassembler != null) 1859 { 1860 float hPad = showBranchMarkers ? horizontalPad : naturalEnhancedPad; 1861 1862 // Make sure we cannot set cursor in the horizontal padding: 1863 if (mousePos.x < hPad) mousePos.x = hPad; 1864 } 1865 return mousePos; 1866 } 1867 1868 private int GetLineNumber(Vector2 mousePos) 1869 { 1870 var lineNumber = Mathf.FloorToInt(mousePos.y / fontHeight); 1871 var len = _disassembler.Blocks.Count; 1872 for (var idx = 0; idx < len; idx++) 1873 { 1874 var block = _disassembler.Blocks[idx]; 1875 if (lineNumber <= block.LineIndex) 1876 { 1877 break; 1878 } 1879 if (_folded[idx]) 1880 { 1881 lineNumber += block.Length - 1; 1882 } 1883 } 1884 return lineNumber; 1885 } 1886 internal void MouseClicked(bool showBranchMarkers, bool withShift, Vector2 mousePos, int controlID) 1887 { 1888 if (_mouseOutsideBounds || mousePos.y > finalAreaSize.y) { return; } 1889 1890 GUIUtility.hotControl = controlID; 1891 // FocusControl is to take keyboard focus away from the TreeView. 1892 GUI.FocusControl("long text"); 1893 if (withShift) 1894 { 1895 mousePos = MouseClickAndClamp(showBranchMarkers, mousePos); 1896 1897 selectDragPos = mousePos; 1898 _textSelectionIdxValid = false; 1899 } 1900 else 1901 { 1902 selectDragPos = mousePos; 1903 StopSelection(); 1904 } 1905 _mouseDown = true; 1906 } 1907 1908 1909 internal void DragMouse(Vector2 mousePos, bool showBranchMarkers) 1910 { 1911 if (!_mouseDown || mousePos.y > finalAreaSize.y) { return; } 1912 1913 mousePos = MouseClickAndClamp(showBranchMarkers, mousePos); 1914 1915 selectDragPos = mousePos; 1916 _textSelectionIdxValid = false; 1917 } 1918 1919 internal void MouseReleased() 1920 { 1921 GUIUtility.hotControl = 0; 1922 _mouseDown = false; 1923 } 1924 1925 internal void DoScroll(Rect workingArea, float mouseRelMoveY) 1926 { 1927 if (!_mouseDown) { return; } 1928 1929 var movingDown = mouseRelMoveY > 0; 1930 1931 var room = Mathf.Max(0, movingDown 1932 ? finalAreaSize.y - workingArea.yMax 1933 : workingArea.yMin); 1934 1935 // naturalEnhancedPad magic number taken from unity engine (GUI.cs under EndScrollView). 1936 var dist = Mathf.Min(Mathf.Abs(mouseRelMoveY * naturalEnhancedPad), room); 1937 selectDragPos.y += movingDown ? dist : -dist; 1938 1939 _textSelectionIdxValid = false; 1940 } 1941 1942 public void MoveView(Direction dir, Rect workingArea) 1943 { 1944 switch (dir) 1945 { 1946 case Direction.Left: 1947 GUI.ScrollTo(new Rect(workingArea.xMin - 2*fontWidth, workingArea.y, 0, 0)); 1948 break; 1949 1950 case Direction.Right: 1951 GUI.ScrollTo(new Rect(workingArea.xMax + 2*fontWidth, workingArea.y, 0, 0)); 1952 break; 1953 1954 case Direction.Up: 1955 GUI.ScrollTo(new Rect(workingArea.x, workingArea.yMin - 2*fontWidth, 0, 0)); 1956 break; 1957 1958 case Direction.Down: 1959 GUI.ScrollTo(new Rect(workingArea.x, workingArea.yMax + 2*fontWidth, 0, 0)); 1960 break; 1961 } 1962 } 1963 1964 public void StopSelection() 1965 { 1966 selectPos = selectDragPos; 1967 textSelectionIdx = (0, 0); 1968 _textSelectionIdxValid = true; 1969 HasSelection = false; 1970 } 1971 1972 1973 1974 /// <summary> 1975 /// Renders a blue box relative to text at (positionX, positionY) from start idx to end idx. 1976 /// </summary> 1977 private void RenderLineSelection(float positionX, float positionY, int start, int end, bool IsSelection = true) 1978 { 1979 const int alignmentPad = 2; 1980 var oldColor = GUI.color; 1981 GUI.color = IsSelection ? _selectionColor : _searchHitColor; 1982 GUI.Box(new Rect(positionX + alignmentPad + start*fontWidth, positionY, (end - start)*fontWidth, fontHeight), "", textureStyle); 1983 GUI.color = oldColor; 1984 } 1985 1986 internal struct FragmentSelectionInfo 1987 { 1988 public float startY; 1989 public float lastY; 1990 public float botY; 1991 1992 public int startLineEndIdxRel; 1993 public int startLine; 1994 public int lastLine; 1995 1996 public int charsIn; 1997 public int charsInDrag; 1998 } 1999 2000 internal FragmentSelectionInfo PrepareInfoForSelection(float positionX, float positionY, float fragHeight, Fragment frag, Func<string, int, (int total, int relative)> GetEndIndexOfLine) 2001 { 2002 const float distanceFromBorder = 0.001f; 2003 var text = frag.text; 2004 2005 var top = selectPos; 2006 var bot = selectDragPos; 2007 if (selectPos.y > selectDragPos.y) 2008 { 2009 top = selectDragPos; 2010 bot = selectPos; 2011 } 2012 // fixing so we only look at things within this current fragment. 2013 var start = top.y < positionY ? new Vector2(positionX, positionY) : top; 2014 // `y = ... - adjustBottomSlightly` so it is within current fragments outer "border". 2015 var last = bot.y < positionY + fragHeight ? bot : new Vector2(finalAreaSize.x, positionY + fragHeight - distanceFromBorder); 2016 2017 // Make sure this cannot go beyond number of lines in fragment (zero indexed). 2018 var startLine = Math.Min(Mathf.FloorToInt((start.y - positionY) / fontHeight), frag.lineCount-1); 2019 var lastLine = Math.Min(Mathf.FloorToInt((last.y - positionY) / fontHeight), frag.lineCount-1); 2020 2021 if (startLine == lastLine && start.x > last.x) 2022 (start, last) = (last, start); 2023 2024 // Used for making sure charsIn and charsInDrag does not exceed line length. 2025 var (_, startLineEndIdxRel) = GetEndIndexOfLine(text, startLine); 2026 var (_, lastLineEndIdxRel) = GetEndIndexOfLine(text, lastLine); 2027 2028 // Each fragment does not end on a newline, so we need to add one to idx. 2029 if (startLine == frag.lineCount - 1) startLineEndIdxRel++; 2030 if (lastLine == frag.lineCount - 1) lastLineEndIdxRel++; 2031 2032 var charsIn = Math.Min(Mathf.FloorToInt((start.x - positionX) / fontWidth), startLineEndIdxRel); 2033 var charsInDrag = Math.Min(Mathf.FloorToInt((last.x - positionX) / fontWidth), lastLineEndIdxRel); 2034 charsIn = charsIn < 0 ? 0 : charsIn; 2035 charsInDrag = charsInDrag < 0 ? 0 : charsInDrag; 2036 2037 return new FragmentSelectionInfo() {startY = start.y, lastY = last.y, botY = bot.y, 2038 startLineEndIdxRel = startLineEndIdxRel, startLine = startLine, lastLine = lastLine, 2039 charsIn = charsIn, charsInDrag = charsInDrag}; 2040 } 2041 2042 private void SelectText(float positionX, float positionY, float fragHeight, Fragment frag, Func<string, int, (int total, int relative)> GetEndIndexOfLine) 2043 { 2044 if (!HasSelection || !(BurstMath.WithinRange(selectPos.y, selectDragPos.y, positionY) || 2045 BurstMath.WithinRange(selectPos.y, selectDragPos.y, positionY + fragHeight) 2046 || BurstMath.WithinRange(positionY, positionY + fragHeight, selectPos.y))) 2047 { 2048 return; 2049 } 2050 2051 var inf = PrepareInfoForSelection(positionX, positionY, fragHeight, frag, GetEndIndexOfLine); 2052 var text = frag.text; 2053 2054 if (BurstMath.RoundDownToNearest(inf.lastY, fontHeight) > BurstMath.RoundDownToNearest(inf.startY, fontHeight)) 2055 { 2056 // Multi line selection in this text. 2057 int lineEndIdx = inf.startLineEndIdxRel; 2058 if (inf.startY != positionY) 2059 { 2060 // Selection started in this fragment. 2061 RenderLineSelection(positionX, positionY + (inf.startLine * fontHeight), inf.charsIn, lineEndIdx + 1); 2062 inf.startLine++; 2063 } 2064 2065 for (; inf.startLine < inf.lastLine; inf.startLine++) 2066 { 2067 lineEndIdx = GetEndIndexOfLine(text, inf.startLine).relative; 2068 RenderLineSelection(positionX, positionY + (inf.startLine * fontHeight), 0, lineEndIdx + 1); 2069 } 2070 2071 if (positionY + fragHeight < inf.botY) 2072 { 2073 // select going into next fragment 2074 inf.charsInDrag++; 2075 } 2076 2077 RenderLineSelection(positionX, positionY + (inf.startLine * fontHeight), 0, inf.charsInDrag); 2078 } 2079 else 2080 { 2081 // Single line selection in this text segment. 2082 int startIdx = inf.charsIn; 2083 int endIdx = inf.charsInDrag; 2084 if (inf.startY == positionY) 2085 { 2086 // Selection started in text segment above. 2087 startIdx = 0; 2088 } 2089 2090 if (positionY + fragHeight < inf.botY) 2091 { 2092 // Selection going into next text segment. 2093 endIdx = inf.startLineEndIdxRel + 1; 2094 } 2095 2096 RenderLineSelection(positionX, positionY + (inf.startLine * fontHeight), startIdx, endIdx); 2097 } 2098 } 2099 2100 /// <summary> 2101 /// Updates _textSelectionIdx based on the position of _selectPos and _selectDragPos. 2102 /// </summary> 2103 /// <param name="GetEndIndexOfLine"> either GetEndIndexOfPlainLine or GetEndIndexOfColoredLine</param> 2104 private void UpdateSelectTextIdx(Func<string, int, (int total, int relative)> GetEndIndexOfLine) 2105 { 2106 if (!_textSelectionIdxValid && HasSelection) 2107 { 2108 var start = selectPos; 2109 var last = selectDragPos; 2110 if (last.y < start.y) 2111 { 2112 start = selectDragPos; 2113 last = selectPos; 2114 } 2115 int startLine = Mathf.FloorToInt(start.y / fontHeight); 2116 int lastLine = Mathf.FloorToInt(last.y / fontHeight); 2117 2118 if (startLine == lastLine && start.x > last.x) 2119 { 2120 (start, last) = (last, start); 2121 } 2122 2123 var (startLineEndIdxTotal, startLineEndIdxRel) = GetEndIndexOfLine(m_Text, startLine); 2124 var (lastLineEndIdxTotal, lastLineEndIdxRel) = GetEndIndexOfLine(m_Text, lastLine); 2125 2126 int charsIn = Math.Min(Mathf.FloorToInt(start.x / fontWidth), startLineEndIdxRel); 2127 int charsInDrag = Math.Min(Mathf.FloorToInt(last.x / fontWidth), lastLineEndIdxRel); 2128 charsIn = charsIn < 0 ? 0 : charsIn; 2129 charsInDrag = charsInDrag < 0 ? 0 : charsInDrag; 2130 2131 int selectStartIdx = startLineEndIdxTotal - (startLineEndIdxRel - charsIn); 2132 textSelectionIdx = (selectStartIdx, lastLineEndIdxTotal - (lastLineEndIdxRel - charsInDrag) - selectStartIdx); 2133 2134 _textSelectionIdxValid = true; 2135 } 2136 } 2137 2138 public void NextSearchHit(bool shift, Rect workingArea) 2139 { 2140 if (_nrSearchHits <= 0) return; 2141 2142 // If shift is held down: Go backwards through active search 2143 _activeSearchHitIdx = shift 2144 ? (Math.Max(_activeSearchHitIdx, 0) - 1 + _nrSearchHits) % _nrSearchHits 2145 : (_activeSearchHitIdx + 1) % _nrSearchHits; 2146 2147 // Find fragment number of _activeSearchHit 2148 var fragIdx = 0; 2149 int prevFragLastHitNr = -1; 2150 var activeSearchIdx = _activeSearchHitIdx; 2151 foreach (var key in searchHits.Keys) 2152 { 2153 var hits = searchHits[key]; 2154 2155 var nrHits = hits.Count; 2156 var hitsLastNr = hits[nrHits - 1].nr; 2157 2158 // If last hit continues into this fragment we subtracted too much 2159 if (hits[0].nr == prevFragLastHitNr) { activeSearchIdx++; } 2160 2161 if (hitsLastNr >= _activeSearchHitIdx) 2162 { 2163 fragIdx = key; 2164 break; 2165 } 2166 activeSearchIdx -= nrHits; 2167 prevFragLastHitNr = hitsLastNr; 2168 } 2169 2170 var hit = searchHits[fragIdx][activeSearchIdx]; 2171 2172 // Find y position of _activeSearchHit. 2173 int exactLineNumber; 2174 string text; 2175 var positionY = 0f; 2176 if (_disassembler == null) 2177 { 2178 int i = 0; 2179 for (; i < fragIdx; i++) 2180 { 2181 positionY += m_Fragments[i].lineCount * fontHeight; 2182 } 2183 text = m_Fragments[i].text; 2184 exactLineNumber = BurstStringSearch.FindLineNr(text, hit.startIdx); 2185 positionY += exactLineNumber * fontHeight; 2186 } 2187 else 2188 { 2189 int blockIdx = 0; 2190 int innerFragIdx = 0; 2191 int tmpFragIdx = 0; 2192 2193 for (; tmpFragIdx < fragIdx; blockIdx++) 2194 { 2195 var frags = GetPlainFragments(blockIdx); 2196 2197 if (_folded[blockIdx]) 2198 { 2199 positionY += fontHeight; 2200 tmpFragIdx += frags.Count; 2201 } 2202 else 2203 { 2204 for (innerFragIdx = 0; ; innerFragIdx++, tmpFragIdx++) 2205 { 2206 if (innerFragIdx >= frags.Count) 2207 { 2208 innerFragIdx = 0; 2209 break; 2210 } 2211 if (tmpFragIdx >= fragIdx) break; 2212 positionY += frags[innerFragIdx].lineCount * fontHeight; 2213 2214 } 2215 } 2216 if (innerFragIdx != 0) break; 2217 } 2218 text = GetPlainFragments(blockIdx)[innerFragIdx].text; 2219 exactLineNumber = BurstStringSearch.FindLineNr(text, hit.startIdx); 2220 positionY += exactLineNumber * fontHeight; 2221 } 2222 2223 int startOfLine = exactLineNumber > 0 ? GetEndIndexOfPlainLine(text, exactLineNumber-1).total + 1 : 0; 2224 int hitLength = hit.endIdx - hit.startIdx; 2225 2226 float positionX = hit.startIdx - startOfLine; 2227 if (positionX * fontWidth > workingArea.x) positionX += hitLength + 3 * fontWidth; 2228 else positionX -= 3 * fontWidth; 2229 2230 GUI.ScrollTo(new Rect(positionX * fontWidth + _actualHorizontalPad, positionY < workingArea.yMin ? positionY - 5 * fontHeight : positionY + 5 * fontHeight, 0, 0)); 2231 } 2232 2233 public void StopSearching() 2234 { 2235 _nrSearchHits = 0; 2236 searchHits.Clear(); 2237 _activeSearchHitIdx = -1; 2238 _prevCriteria.filter = String.Empty; 2239 } 2240 2241 private void RenderMultipleLinesSelection(string text, float positionX, float positionY, int startIdx, int endIdx, bool IsSelection = true) 2242 { 2243 int lineStart = BurstStringSearch.FindLineNr(text, startIdx); 2244 int lineEnd = BurstStringSearch.FindLineNr(text, endIdx); 2245 2246 // Make idx relative to line 2247 int startOfLine = lineStart == 0 ? 0 : GetEndIndexOfPlainLine(text, lineStart-1).total + 1; 2248 startIdx = startIdx - startOfLine; 2249 2250 startOfLine = lineEnd == 0 ? 0 : GetEndIndexOfPlainLine(text, lineEnd-1).total + 1; 2251 endIdx = endIdx - startOfLine; 2252 2253 2254 positionY += (lineStart * fontHeight); 2255 2256 if (lineStart == lineEnd) 2257 { 2258 RenderLineSelection(positionX, positionY, startIdx, endIdx, IsSelection); 2259 } 2260 else 2261 { 2262 int endOfLine = GetEndIndexOfPlainLine(text, lineStart).relative; 2263 RenderLineSelection(positionX, positionY, startIdx, endOfLine, IsSelection); 2264 lineStart++; 2265 positionY += fontHeight; 2266 2267 for (; lineStart < lineEnd; lineStart++) 2268 { 2269 endOfLine = GetEndIndexOfPlainLine(text, lineStart).relative; 2270 RenderLineSelection(positionX, positionY, 0, endOfLine, IsSelection); 2271 positionY += fontHeight; 2272 } 2273 2274 RenderLineSelection(positionX, positionY, 0, endIdx, IsSelection); 2275 } 2276 } 2277 2278 private void RenderPlainOldFragments(GUIStyle style, Rect workingArea, bool doSearch) 2279 { 2280 Vector2 scrollPos = workingArea.position; 2281 float positionY = 0.0f; 2282 for (int i = 0; i < m_Fragments.Count; i++) 2283 { 2284 // if fragment is below view 2285 if ((positionY - scrollPos.y) > workingArea.size.y) break; 2286 2287 var fragment = m_Fragments[i]; 2288 2289 float fragHeight = fragment.lineCount * fontHeight; 2290 2291 // if whole fragment is above view 2292 if ((positionY - scrollPos.y + fragHeight) < 0) 2293 { 2294 positionY += fragHeight; 2295 continue; 2296 } 2297 2298 if (doSearch && searchHits.TryGetValue(i, out var lst)) 2299 { 2300 foreach (var (startI, endI, nr) in lst) 2301 { 2302 bool isActive = nr == _activeSearchHitIdx; 2303 RenderMultipleLinesSelection(fragment.text, horizontalPad, positionY, startI, endI, isActive); 2304 } 2305 } 2306 // we append \n here, as we want it for the selection but not the rendering. 2307 SelectText(horizontalPad, positionY, fragHeight, fragment, GetEndIndexOfPlainLine); 2308 2309 GUI.Label(new Rect(horizontalPad, positionY, finalAreaSize.x, fragHeight + fontHeight*0.5f), fragment.text, style); 2310 positionY += fragHeight; 2311 } 2312 UpdateSelectTextIdx(GetEndIndexOfPlainLine); 2313 } 2314 2315 /// <returns>Whether the GUI should be forced to repaint during this frame.</returns> 2316 public bool Render(GUIStyle style, Rect workingArea, bool showBranchMarkers, bool doSearch, SearchCriteria searchCriteria, Regex regx) 2317 { 2318 _actualHorizontalPad = showBranchMarkers ? horizontalPad : naturalEnhancedPad; 2319 style.richText = true; 2320 2321 if (invalidated) 2322 { 2323 Layout(style, _actualHorizontalPad); 2324 } 2325 2326 if (_disassembler != null) 2327 { 2328 // We always need to call this as its sets up the correct horizontal bar and block rendering 2329 LayoutEnhanced(style, workingArea, showBranchMarkers); 2330 } 2331 2332 if (Event.current.type == EventType.Layout) 2333 { 2334 // working area will be valid only during repaint, for the layout event we don't draw the labels 2335 // Add correct size Rect to GUILayoutUtillity.topLevel for retrieval with next GetRect(.) call. 2336 GUILayoutUtility.GetRect(finalAreaSize.x + (showBranchMarkers ? horizontalPad : 20.0f), finalAreaSize.y); 2337 return false; 2338 } 2339 2340 var newSearch = doSearch && (searchCriteria.filter != "" && !searchCriteria.Equals(_prevCriteria)); 2341 _prevCriteria = searchCriteria; 2342 2343 var doRepaint = false; 2344 if (newSearch) 2345 { 2346 doRepaint = SearchText(searchCriteria, regx, ref workingArea); 2347 if (doRepaint) 2348 { 2349 // Search found at least one match, so move to the first 2350 GUI.ScrollTo(workingArea); 2351 } 2352 } 2353 2354 if (_disassembler == null) 2355 { 2356 RenderPlainOldFragments(style, workingArea, doSearch); 2357 } 2358 else 2359 { 2360 RenderEnhanced(style, workingArea, showBranchMarkers, _actualHorizontalPad, doSearch); 2361 RenderRegisterHighlight(workingArea, _actualHorizontalPad); 2362 RenderLineHighlight(_actualHorizontalPad); 2363 } 2364 2365 DrawHover(style, workingArea); 2366 return doRepaint; 2367 } 2368 2369 internal int GetLinesBlockIdx(int pressedLine) 2370 { 2371 // Find block that was pressed: 2372 var b = _renderBlockStart; 2373 var numBlocks = _blocksFragments.Length; 2374 for (; b <= _renderBlockEnd && b < numBlocks-1; b++) 2375 { 2376 var block = _disassembler.Blocks[b]; 2377 if (block.LineIndex <= pressedLine && block.LineIndex + block.Length > pressedLine) 2378 { 2379 break; 2380 } 2381 } 2382 2383 return b; 2384 } 2385 2386 internal Rect GetLineHighlight(ref LineRegRectsCache cache, float hPad, int pressedLine) 2387 { 2388 // Find block that was pressed: 2389 var b = GetLinesBlockIdx(pressedLine); 2390 2391 if (cache.IsLineHighlightCached(pressedLine, _folded[b])) 2392 { 2393 // Same old line 2394 return cache.lineHighlight; 2395 } 2396 2397 var pressedLineRel = pressedLine - _disassembler.Blocks[b].LineIndex; 2398 var positionY = 2399 _renderStartY + (blockLine[b] - blockLine[_renderBlockStart] + pressedLineRel) * fontHeight; 2400 const float lineHeight = 2f; 2401 2402 // Cannot use tokens in disassembler, as it's not updated with the proper output created by 2403 // RenderLine(.). 2404 var line = _disassembler.Lines[pressedLine]; 2405 var lineStr = _folded[b] ? GetFragmentStartPlain(b).text : GetLineString(line); 2406 var xEnd = lineStr.Length * fontWidth; 2407 var xStart = hPad; 2408 var yPos = positionY + fontHeight; 2409 var rect = new Rect(xStart, yPos, xEnd, lineHeight); 2410 cache.UpdateLineHighlight(pressedLine, rect, _folded[b]); 2411 return rect; 2412 } 2413 2414 private void RenderLineHighlight(float hPad) 2415 { 2416 if (_pressedLine == -1) 2417 { 2418 return; 2419 } 2420 var firstRenderedLine = _disassembler.Blocks[_renderBlockStart].LineIndex; 2421 var tmp = _disassembler.Blocks[_renderBlockEnd]; 2422 var lastRenderedLine = tmp.LineIndex + tmp.Length; 2423 if (!BurstMath.WithinRange(firstRenderedLine, lastRenderedLine, _pressedLine)) 2424 { 2425 return; 2426 } 2427 2428 var col = GUI.color; 2429 GUI.color = _lineHighlightColor; 2430 GUI.Box(GetLineHighlight(ref _lineRegCache, hPad, _pressedLine), "", textureStyle); 2431 GUI.color = col; 2432 } 2433 2434 internal LineRegRectsCache _lineRegCache; 2435 internal int _pressedLine = -1; 2436 2437 private void RenderRegisterHighlight(Rect workingArea, float hPad) 2438 { 2439 if (_pressedLine == -1) 2440 { 2441 return; 2442 } 2443 2444 if (_disassembler.LineUsesRegs(_pressedLine, out var usedRegs) && _disassembler.Lines[_pressedLine].Kind != BurstDisassembler.AsmLineKind.Directive) 2445 { 2446 var oldColor = GUI.color; 2447 var regRects = GetRegisterRects(hPad, ref _lineRegCache, _pressedLine, usedRegs); 2448 2449 var i = 0; 2450 foreach (var rects in regRects) 2451 { 2452 GUI.color = _regsColourWheel[i % _regsColourWheel.Length]; 2453 foreach (var rect in rects) 2454 { 2455 if (!workingArea.Contains(rect.position)) 2456 { 2457 continue; 2458 } 2459 GUI.Box(rect, "", textureStyle); 2460 } 2461 2462 i++; 2463 } 2464 GUI.color = oldColor; 2465 } 2466 else if (_pressedLine != _lineRegCache.chosenLine) 2467 { 2468 // We need to clear the cache, as we pressed somewhere new. 2469 // Alternatively we only need to do this if a fold changed, or the view changed! 2470 _lineRegCache.Clear(); 2471 } 2472 } 2473 2474 internal string GetLineString(BurstDisassembler.AsmLine line) 2475 { 2476 _disassembler._output.Clear(); 2477 2478 _disassembler.RenderLine(ref line, false); 2479 2480 var str = _disassembler._output.ToString(); 2481 _disassembler._output.Length = 0; 2482 return str; 2483 } 2484 2485 private (int start, int end) FindBlockIdx(int startBlock, int firstLine, int lastLine) 2486 { 2487 var blockIdx = startBlock; 2488 for (; blockIdx > 0; blockIdx--) 2489 { 2490 if (blockLine[blockIdx] <= firstLine) 2491 { 2492 break; 2493 } 2494 } 2495 2496 var blockIdxEnd = _renderBlockEnd; 2497 for (; blockIdxEnd < _blocksFragments.Length - 1; blockIdxEnd++) 2498 { 2499 var bLen = _folded[blockIdxEnd] 2500 ? 1 2501 : _disassembler.Blocks[blockIdxEnd].Length; 2502 if (blockLine[blockIdxEnd] + bLen >= lastLine) 2503 { 2504 break; 2505 } 2506 } 2507 2508 return (blockIdx, blockIdxEnd); 2509 } 2510 2511 /// <summary> 2512 /// Finds all usages of <see cref="regs"/> within view, and returns a list of <see cref="Rect"/> 2513 /// presenting these usages. 2514 /// </summary> 2515 /// <param name="hPad">Horizontal padding in text area.</param> 2516 /// <param name="cache">Cache handling saving register rects.</param> 2517 /// <param name="pressedLine">The currently selected line.</param> 2518 /// <param name="regs">Registers to match against.</param> 2519 /// <remarks> 2520 /// This will return representation of ALL matching registers within <see cref="workingArea"/>. 2521 /// 2522 /// Depends on: 2523 /// <list type="bullet"> 2524 /// <item> 2525 /// <see cref="_burstDisassember"/>. 2526 /// </item> 2527 /// <item> 2528 /// <see cref="LayoutEnhanced"/> been called at least once before this. 2529 /// </item> 2530 /// </list> 2531 /// </remarks> 2532 internal List<Rect>[] GetRegisterRects(float hPad, ref LineRegRectsCache cache, int pressedLine, List<string> regs) 2533 { 2534 const int extraLines = 5; 2535 const float horizontalAlignmentPad = 2f; 2536 2537 var firstLine = Math.Max(blockLine[_renderBlockStart] - extraLines, 0); 2538 var lastLine = blockLine[_renderBlockEnd] + (_folded[_renderBlockEnd] 2539 ? 1 2540 : _disassembler.Blocks[_renderBlockEnd].Length-1) + extraLines; 2541 2542 var (blockIdx, blockIdxEnd) = FindBlockIdx(_renderBlockStart, firstLine, lastLine); 2543 2544 var positionY = Math.Max(_renderStartY - (extraLines * fontHeight), 0); 2545 // Convert lineIdx to absolut indexes. Needed for _disassembler.Lines[...] 2546 for (var i = 0; i < blockIdxEnd; i++) 2547 { 2548 if (_folded[i]) 2549 { 2550 var len = _disassembler.Blocks[i].Length - 1; 2551 if (i < blockIdx) { firstLine += len; } 2552 lastLine += len; 2553 } 2554 } 2555 // Make sure LastLine does not exceed number of lines 2556 lastLine = Math.Min(lastLine, _disassembler.Lines.Count - 1); 2557 2558 if (cache.IsRegisterCachedOrClear(pressedLine, firstLine, lastLine)) 2559 { 2560 return cache.rects; 2561 } 2562 2563 // Clean up regs, so e.g. "rcx" and "ecx" only counts as one: 2564 regs = _disassembler.CleanRegs(regs); 2565 2566 var currentLine = firstLine; 2567 var lineInBlock = firstLine - _disassembler.Blocks[blockIdx].LineIndex; 2568 2569 var rects = cache.rects; 2570 if (rects == null) 2571 { 2572 rects = new List<Rect>[regs.Count]; 2573 for (var i = 0; i < regs.Count; i++) 2574 { 2575 rects[i] = new List<Rect>(); 2576 } 2577 } 2578 2579 while (blockIdx < _blocksFragments.Length && currentLine <= lastLine) 2580 { 2581 var lineNotCached = !cache.LinesRegsCached(currentLine); 2582 var line = _disassembler.Lines[currentLine]; 2583 if (lineNotCached && line.Kind != BurstDisassembler.AsmLineKind.Directive) 2584 { 2585 // Loop body 2586 var lineStart = _disassembler.Tokens[line.TokenIndex].AlignedPosition; 2587 var i = 0; 2588 foreach (var reg in regs) 2589 { 2590 var regUsedNr = _disassembler.LineUsedReg(currentLine, reg); 2591 if (regUsedNr > 0) 2592 { 2593 var tmpIdx = 0; 2594 for (var r = 0; r < regUsedNr; r++) 2595 { 2596 // Find position and width/height of reg: 2597 var height = fontHeight; 2598 var width = reg.Length * fontWidth; 2599 var y = positionY; 2600 2601 var tokenRegIdx = _disassembler.GetRegisterTokenIndex(line, reg, tmpIdx); 2602 if (tokenRegIdx == -1) 2603 { 2604 throw new Exception($"Could not find token index of \"{reg}\" on line {currentLine}."); 2605 } 2606 2607 var regIdx = _disassembler.Tokens[tokenRegIdx].AlignedPosition - lineStart; 2608 var regPos = regIdx * fontWidth; 2609 var x = hPad + regPos + horizontalAlignmentPad; 2610 rects[i].Add(new Rect(x, y, width, height)); 2611 2612 tmpIdx = tokenRegIdx + 1; 2613 } 2614 } 2615 i++; 2616 } 2617 } 2618 2619 // Loop end statements 2620 if (lineInBlock >= _disassembler.Blocks[blockIdx].Length) 2621 { 2622 // Moved into next block 2623 blockIdx++; 2624 lineInBlock = 0; 2625 } 2626 2627 if (_folded[blockIdx]) 2628 { 2629 currentLine += _disassembler.Blocks[blockIdx].Length; 2630 blockIdx++; 2631 lineInBlock = 0; 2632 } 2633 else 2634 { 2635 currentLine++; 2636 lineInBlock++; 2637 } 2638 positionY += fontHeight; 2639 } 2640 cache.UpdateRegisters(firstLine, lastLine, pressedLine, rects); 2641 2642 return cache.rects; 2643 } 2644 2645 internal struct LineRegRectsCache 2646 { 2647 [Flags] 2648 private enum CachedItem 2649 { 2650 None = 0, 2651 Line = 1, 2652 Registers = 2 2653 } 2654 2655 private CachedItem _isCached; 2656 private bool _folded; 2657 /// <summary> 2658 /// First line we have cached up till. 2659 /// </summary> 2660 public int startLine; 2661 /// <summary> 2662 /// Last line we have cached down till. 2663 /// </summary> 2664 public int endLine; 2665 /// <summary> 2666 /// Line we have in focus. 2667 /// </summary> 2668 public int chosenLine; 2669 2670 public Rect lineHighlight; 2671 /// <summary> 2672 /// Cached rects. Each row represent one register, and each column is a rect for that register. 2673 /// </summary> 2674 public List<Rect>[] rects; 2675 2676 public bool LinesRegsCached(int pressedLine) 2677 { 2678 return (_isCached & CachedItem.Registers) > 0 && BurstMath.WithinRange(startLine, endLine, pressedLine); 2679 } 2680 2681 public bool IsRegisterCachedOrClear(int pressedLine, int firstLine, int lastLine) 2682 { 2683 var isCached = false; 2684 if (!IsRegistersCached(pressedLine)) 2685 { 2686 // New line was pressed! 2687 Clear(); 2688 } 2689 else if (!ShouldUpdateCache(firstLine, lastLine)) 2690 { 2691 isCached = true; 2692 } 2693 2694 return isCached; 2695 } 2696 2697 public bool IsLineHighlightCached(int linePressed, bool folded) => 2698 folded == _folded && (_isCached & CachedItem.Line) > 0 && linePressed == chosenLine; 2699 public bool IsRegistersCached(int linePressed) => (_isCached & CachedItem.Registers) > 0 && linePressed == chosenLine; 2700 2701 private bool ShouldUpdateCache(int viewLineStart, int viewLineEnd) => viewLineStart < startLine || viewLineEnd > endLine; 2702 2703 public void UpdateRegisters(int firstLine, int lastLine, int pressedLine, List<Rect>[] RegRects) 2704 { 2705 if (chosenLine != pressedLine && (_isCached & CachedItem.Line) > 0) 2706 { 2707 _isCached ^= CachedItem.Line; 2708 } 2709 startLine = Math.Min(firstLine, startLine); 2710 endLine = Math.Max(lastLine, endLine); 2711 chosenLine = pressedLine; 2712 rects = RegRects; 2713 _isCached |= CachedItem.Registers; 2714 } 2715 2716 public void UpdateLineHighlight(int pressedLine, Rect rect, bool folded) 2717 { 2718 _isCached |= CachedItem.Line; 2719 if (chosenLine != pressedLine && (_isCached & CachedItem.Registers) > 0) 2720 { 2721 // line just changed so register cache is no longer valid. 2722 _isCached ^= CachedItem.Registers; 2723 } 2724 2725 lineHighlight = rect; 2726 chosenLine = pressedLine; 2727 _folded = folded; 2728 } 2729 2730 public void Clear() 2731 { 2732 rects = null; 2733 chosenLine = -1; 2734 startLine = int.MaxValue; 2735 endLine = -1; 2736 _isCached = CachedItem.None; 2737 } 2738 } 2739 2740 private void TestSelUnderscore(GUIStyle style, Vector2 scrollPos, Rect workingArea) 2741 { 2742 var current = GUI.color; 2743 2744 // Selection 2745 GUI.color = Color.blue; 2746 GUI.Box( 2747 new Rect(horizontalPad + style.padding.left + fontWidth * 8, style.padding.top + fontHeight * 19, 2748 3 * fontWidth, 1 * fontHeight), "", textureStyle); 2749 2750 // Underscored 2751 Vector2 start = new Vector2(horizontalPad + style.padding.left + fontWidth * 8, 2752 style.padding.top + fontHeight * 20 - 2); 2753 Vector2 end = start + new Vector2((3 + 22) * fontWidth, 0 * fontHeight); 2754 2755 GUI.color = Color.red; 2756 DrawLine(start, end, 2); 2757 2758 GUI.color = current; 2759 } 2760 2761 internal void RenderBranches(Rect workingArea) 2762 { 2763 var color = GUI.color; 2764 List<Branch> branches = new List<Branch>(); 2765 hoveredBranch = default; 2766 for (int idx = 0;idx<_disassembler.Blocks.Count; idx++) 2767 { 2768 var block = _disassembler.Blocks[idx]; 2769 if (block.Edges != null) 2770 { 2771 foreach (var edge in block.Edges) 2772 { 2773 if (edge.Kind == BurstDisassembler.AsmEdgeKind.OutBound) 2774 { 2775 var srcLine = blockLine[idx]; 2776 if (!_folded[idx]) 2777 { 2778 srcLine += edge.OriginRef.LineIndex; 2779 } 2780 var dstBlockIdx = edge.LineRef.BlockIndex; 2781 var dstLine = blockLine[dstBlockIdx]; 2782 if (!_folded[dstBlockIdx]) 2783 { 2784 dstLine += edge.LineRef.LineIndex; 2785 } 2786 2787 int arrowMinY = srcLine; 2788 int arrowMaxY = dstLine; 2789 if (srcLine > dstLine) 2790 { 2791 (arrowMinY, arrowMaxY) = (dstLine, srcLine); 2792 } 2793 2794 if ((dstBlockIdx == idx + 1 && edge.LineRef.LineIndex == 0) // pointing to next line 2795 || !(workingArea.yMin <= arrowMaxY * fontHeight && // Arrow not inside view. 2796 workingArea.yMax >= arrowMinY * fontHeight)) 2797 { 2798 continue; 2799 } 2800 branches.Add(CalculateBranch(edge, horizontalPad - (4 + fontWidth), srcLine * fontHeight, 2801 dstLine * fontHeight, lineDepth[idx])); 2802 } 2803 } 2804 } 2805 } 2806 2807 // Drawing branches while making sure the hovered is rendered at top. 2808 foreach (var branch in branches) 2809 { 2810 if (!branch.Edge.Equals(hoveredBranch.Edge)) 2811 { 2812 DrawBranch(branch, lineDepth[branch.Edge.OriginRef.BlockIndex], workingArea); 2813 } 2814 } 2815 if (!hoveredBranch.Edge.Equals(default(BurstDisassembler.AsmEdge))) 2816 { 2817 DrawBranch(hoveredBranch, lineDepth[hoveredBranch.Edge.OriginRef.BlockIndex], workingArea); 2818 } 2819 2820 _prevHoveredEdge = hoveredBranch.Edge; 2821 GUI.color = color; 2822 } 2823 2824 internal int BumpSelectionXByColorTag(string text, int lineIdxTotal, int charsIn) 2825 { 2826 bool lastWasStart = true; 2827 int colorTagStart = text.IndexOf("<color=", lineIdxTotal); 2828 2829 while (colorTagStart != -1 && colorTagStart - lineIdxTotal < charsIn) 2830 { 2831 int colorTagEnd = text.IndexOf('>', colorTagStart + 1); 2832 // +1 as the index calculation is zero based. 2833 charsIn += colorTagEnd - colorTagStart + 1; 2834 2835 if (lastWasStart) 2836 { 2837 colorTagStart = text.IndexOf("</color>", colorTagEnd + 1); 2838 lastWasStart = false; 2839 } 2840 else 2841 { 2842 colorTagStart = text.IndexOf("<color=", colorTagEnd + 1); 2843 lastWasStart = true; 2844 } 2845 } 2846 return charsIn; 2847 } 2848 2849 internal void UpdateEnhancedSelectTextIdx(float hPad) 2850 { 2851 if (_textSelectionIdxValid || !HasSelection) return; 2852 2853 int blockIdxStart = _selectBlockStart; 2854 int blockIdxEnd = _selectBlockEnd; 2855 float blockStartPosY = _selectStartY; 2856 float blockEndPosY = _selectEndY; 2857 2858 var start = selectPos; 2859 var last = selectDragPos; 2860 if (last.y < start.y) 2861 { 2862 // we selected upwards. 2863 (start, last) = (last, start); 2864 (blockIdxStart, blockIdxEnd, blockStartPosY, blockEndPosY) = (blockIdxEnd, blockIdxStart, blockEndPosY, blockStartPosY); 2865 } 2866 2867 // Math.Min to make sure line number does not exceed number of line in block (zero indexed). 2868 var blockStartline = Math.Min(Mathf.FloorToInt((start.y - blockStartPosY) / fontHeight), 2869 _folded[blockIdxStart] 2870 ? 0 2871 : _disassembler.Blocks[blockIdxStart].Length-1); 2872 var blockEndLine = Math.Min(Mathf.FloorToInt((last.y - blockEndPosY) / fontHeight), 2873 _folded[blockIdxEnd] 2874 ? 0 2875 : _disassembler.Blocks[blockIdxEnd].Length-1); 2876 2877 if (blockStartline == blockEndLine && blockIdxStart == blockIdxEnd && start.x > last.x) 2878 { 2879 // _selectDragPos was above and behind _selectPos on same line. 2880 (start, last) = (last, start); 2881 } 2882 2883 var text = _folded[blockIdxStart] 2884 ? CopyColorTags 2885 ? GetFragmentStart(blockIdxStart).text + '\n' 2886 : GetFragmentStartPlain(blockIdxStart).text + '\n' 2887 : CopyColorTags 2888 ? _disassembler.GetOrRenderBlockToText(blockIdxStart) 2889 : _disassembler.GetOrRenderBlockToTextUncached(blockIdxStart, false); 2890 2891 var (startLineEndIdxTotal, startLineEndIdxRel) = GetEndIndexOfColoredLine(text, blockStartline); 2892 int startOfLineIdx = startLineEndIdxTotal - startLineEndIdxRel; 2893 2894 int charsIn = Math.Min(Mathf.FloorToInt((start.x - hPad) / fontWidth), startLineEndIdxRel); 2895 charsIn = charsIn < 0 ? 0 : charsIn; 2896 2897 // Adjust charsIn so it takes color tags into considerations. 2898 charsIn = BumpSelectionXByColorTag(text, startOfLineIdx, charsIn + 1) - 1; // +1 -1 to not bump charsIn when selecting char just after color tag. 2899 2900 enhancedTextSelectionIdxStart = (blockIdxStart, startOfLineIdx + charsIn); 2901 2902 if (blockIdxStart < blockIdxEnd) 2903 { 2904 text = _folded[blockIdxEnd] 2905 ? CopyColorTags 2906 ? GetFragmentStart(blockIdxEnd).text + '\n' 2907 : GetFragmentStartPlain(blockIdxEnd).text + '\n' 2908 : CopyColorTags 2909 ? _disassembler.GetOrRenderBlockToText(blockIdxEnd) 2910 : _disassembler.GetOrRenderBlockToTextUncached(blockIdxEnd, false); 2911 } 2912 2913 var (lastLineEndIdxTotal, lastLineEndIdxRel) = GetEndIndexOfColoredLine(text, blockEndLine); 2914 startOfLineIdx = lastLineEndIdxTotal - lastLineEndIdxRel; 2915 2916 int charsInDrag = Math.Min(Mathf.FloorToInt((last.x - hPad) / fontWidth), lastLineEndIdxRel); 2917 charsInDrag = charsInDrag < 0 ? 0 : charsInDrag; 2918 2919 // Adjust charsInDrag so it takes color tags into considerations. 2920 charsInDrag = BumpSelectionXByColorTag(text, startOfLineIdx, charsInDrag); 2921 2922 enhancedTextSelectionIdxEnd = (blockIdxEnd, startOfLineIdx + charsInDrag); 2923 2924 _textSelectionIdxValid = true; 2925 } 2926 2927 /// <summary> 2928 /// Updates selection based on whether <see cref="blockIdx"/> was folded or unfolded. 2929 /// </summary> 2930 /// <param name="hPad">horizontal padding.</param> 2931 /// <param name="blockIdx">Block idx of folded/unfolded block.</param> 2932 /// <param name="block">folded/unfolded block.</param> 2933 /// <param name="positionY">Blocks y-position in textarea.</param> 2934 private void UpdateSelectionByFolding(float hPad, int blockIdx, BurstDisassembler.AsmBlock block, float positionY) 2935 { 2936 // cursor start and end position of selection 2937 var start = selectPos; 2938 var end = selectDragPos; 2939 2940 // selection block start and end 2941 var blockStart = _selectBlockStart; 2942 var blockEnd = _selectBlockEnd; 2943 2944 // top y-coordinate of selections end block 2945 var blockEndY = _selectEndY; 2946 2947 var rightWaySelect = true; 2948 if (start.y > end.y) 2949 { 2950 (start, end) = (end, start); 2951 (blockStart, blockEnd) = (blockEnd, blockStart); 2952 blockEndY = _selectStartY; 2953 rightWaySelect = false; 2954 } 2955 2956 if (BurstMath.RoundDownToNearest(end.y, fontHeight) - BurstMath.RoundDownToNearest(start.y, fontHeight) < fontHeight 2957 && end.x < start.x) 2958 { 2959 (start, end) = (end, start); 2960 rightWaySelect = !rightWaySelect; 2961 } 2962 2963 var changeY = _folded[blockIdx] 2964 ? -Math.Max(block.Length - 1, 1) * fontHeight 2965 : Math.Max(block.Length - 1, 1) * fontHeight; 2966 2967 var charsInLine = GetEndIndexOfColoredLine( 2968 _folded[blockIdx] 2969 ? GetFragmentStart(blockIdx).text + '\n' 2970 : _disassembler.GetOrRenderBlockToText(blockIdx), 2971 0).relative; 2972 2973 var thisLineSelect = Mathf.FloorToInt((start.y - positionY) / fontHeight); 2974 var thisLineDrag = Mathf.FloorToInt((end.y - positionY) / fontHeight); 2975 2976 var selectPosCharsIn = Mathf.FloorToInt((start.x - hPad) / fontWidth); 2977 var selectDragPosCharsIn = Mathf.FloorToInt((end.x - hPad) / fontWidth); 2978 2979 if (blockStart < blockIdx) 2980 { 2981 if (blockEnd == blockIdx) 2982 { 2983 if (thisLineDrag > 0 || (end.x - hPad) / fontWidth > charsInLine) 2984 { 2985 /* selection starts above touched block but ends in the middle of it. 2986 * 2987 * b-1 _xxx b-1 _xxx 2988 * b xxx_ => b xxx 2989 * b+1 ____ b+1 ____ 2990 */ 2991 2992 // Move selectDragPos onto end of this line. 2993 end.y -= thisLineDrag * fontHeight; 2994 end.x = charsInLine * fontWidth + hPad + fontWidth / 2; 2995 } 2996 } 2997 else if (blockEnd > blockIdx) 2998 { 2999 /* selection starts above touched block and ends below it. 3000 * 3001 * b-1 _xxx 3002 * b xxxx 3003 * b+1 xx__ 3004 */ 3005 end.y += changeY; 3006 blockEndY += changeY; 3007 } 3008 } 3009 else if (blockStart == blockIdx) 3010 { 3011 if (blockEnd == blockIdx) 3012 { 3013 if (thisLineSelect > 0 || selectPosCharsIn > charsInLine) 3014 { 3015 /* selection starts and ends in the middle of the touched block 3016 * 3017 * b-1 ____ b-1 ____ 3018 * b _xx_ => b _ 3019 * b+1 ____ b+1 ____ 3020 */ 3021 // Make cursor go to the end of line 3022 end.y -= thisLineDrag * fontHeight; 3023 end.x = charsInLine * fontWidth + hPad + fontWidth / 2; 3024 3025 StopSelection(); 3026 } 3027 else if (thisLineDrag > 0 || selectDragPosCharsIn > charsInLine) 3028 { 3029 /* selection starts at first line and ends within 3030 * 3031 * b-1 ____ b-1 ____ 3032 * b _xx_ => b x 3033 * xxx_ 3034 * b+1 ____ b+1 ____ 3035 */ 3036 // Move selectDragPos onto end of this line. 3037 end.y -= thisLineDrag * fontHeight; 3038 end.x = charsInLine * fontWidth + hPad + fontWidth / 2; 3039 } 3040 } 3041 else if (blockEnd > blockIdx) 3042 { 3043 // selection starts either in first line or middle of touched block and ends below it 3044 end.y += changeY; 3045 3046 if (thisLineSelect > 0 || selectPosCharsIn > charsInLine) 3047 { 3048 /* selection starts in middle of touched block and ends below it 3049 * 3050 * b-1 ____ b-1 ____ 3051 * b ____ => b ___x 3052 * x___ 3053 * b+1 xx__ b+1 xx__ 3054 */ 3055 // Move selectPos onto end of this line. 3056 start.y -= thisLineSelect * fontHeight; 3057 start.x = charsInLine * fontWidth + hPad + fontWidth / 2; 3058 } 3059 } 3060 } 3061 else 3062 { 3063 start.y += changeY; 3064 end.y += changeY; 3065 } 3066 3067 // Write back changes made during folding/unfolding 3068 if (rightWaySelect) 3069 { 3070 selectPos = start; 3071 selectDragPos = end; 3072 _selectEndY = blockEndY; 3073 } 3074 else 3075 { 3076 selectPos = end; 3077 selectDragPos = start; 3078 _selectStartY = blockEndY; 3079 } 3080 3081 if (!HasSelection) selectPos = selectDragPos; 3082 else _textSelectionIdxValid = false; 3083 } 3084 3085 private void FoldUnfoldBlock(int blockIdx) 3086 { 3087 var block = _disassembler.Blocks[blockIdx]; 3088 3089 _folded[blockIdx] = !_folded[blockIdx]; 3090 if (_folded[blockIdx]) 3091 { 3092 finalAreaSize.y -= Math.Max(block.Length - 1, 1) * fontHeight; 3093 AddFoldedString(blockIdx); 3094 } 3095 else 3096 { 3097 finalAreaSize.y += Math.Max(block.Length - 1, 1) * fontHeight; 3098 } 3099 } 3100 3101 private void RenderEnhanced(GUIStyle style, Rect workingArea, bool showBranchMarkers, float hPad, bool doSearch) 3102 { 3103 //TestSelUnderscore(); 3104 if (showBranchMarkers) 3105 { 3106 RenderBranches(workingArea); 3107 } 3108 3109 var positionY = _renderStartY; 3110 var fragNr = 0; 3111 for (var i = 0; i <= _renderBlockEnd; i++) 3112 { 3113 if (i < _renderBlockStart) 3114 { 3115 if (doSearch) 3116 { 3117 foreach (var frag in GetPlainFragments(i)) 3118 { 3119 fragNr++; 3120 } 3121 } 3122 3123 continue; 3124 } 3125 3126 var block = _disassembler.Blocks[i]; 3127 3128 var blockLongEnoughForFold = block.Length > 1; 3129 if (blockLongEnoughForFold) 3130 { 3131 var pressed = DrawFold(hPad - 2, positionY, _folded[i], block.Kind); 3132 if (pressed) 3133 { 3134 FoldUnfoldBlock(i); 3135 3136 // Make sure cursor and selection is updated according to the changed folding. 3137 UpdateSelectionByFolding(hPad, i, block, positionY); 3138 } 3139 } 3140 3141 if (doSearch) 3142 { 3143 var tmpPositionY = positionY; 3144 for (var tmp = 0; tmp < GetPlainFragments(i).Count; fragNr++, tmp++) 3145 { 3146 if (searchHits.TryGetValue(fragNr, out var hits)) 3147 { 3148 if (_folded[i]) FoldUnfoldBlock(i); 3149 3150 foreach (var (startIdx, endIdx, nr) in hits) 3151 { 3152 var isActive = nr == _activeSearchHitIdx; 3153 var realEndIdx = endIdx; 3154 3155 if (endIdx == int.MaxValue) 3156 { 3157 var text = GetPlainFragments(i)[tmp].text; 3158 realEndIdx = text.Length; 3159 RenderMultipleLinesSelection(text, horizontalPad, tmpPositionY, startIdx, 3160 realEndIdx, isActive); 3161 } 3162 else 3163 { 3164 RenderMultipleLinesSelection( 3165 GetPlainFragments(i)[tmp].text, 3166 horizontalPad, 3167 tmpPositionY, 3168 startIdx, 3169 realEndIdx, 3170 isActive); 3171 } 3172 } 3173 } 3174 tmpPositionY += GetPlainFragments(i)[tmp].lineCount * fontHeight; 3175 } 3176 } 3177 3178 if (!_folded[i] || !blockLongEnoughForFold) 3179 { 3180 for (var j = 0; j < _blocksFragments[i].Count; j++) 3181 { 3182 var fragment = _blocksFragments[i][j]; 3183 3184 var fragLineCount = fragment.lineCount; 3185 var fragHeight = fragLineCount * fontHeight; 3186 3187 SelectText(hPad, positionY, fragHeight, fragment, 3188 _disassembler.IsColored 3189 ? GetEndIndexOfColoredLine 3190 : GetEndIndexOfPlainLine); 3191 3192 GUI.Label(new Rect(hPad, positionY, finalAreaSize.x, fragHeight + fontHeight*0.5f), 3193 fragment.text, style); 3194 positionY += fragHeight; 3195 } 3196 } 3197 else 3198 { 3199 var frag = GetFragmentStart(i); 3200 3201 SelectText(hPad, positionY, fontHeight, frag, 3202 _disassembler.IsColored 3203 ? GetEndIndexOfColoredLine 3204 : GetEndIndexOfPlainLine); 3205 3206 float fragHeight = fontHeight; 3207 3208 GUI.Label(new Rect(hPad, positionY, finalAreaSize.x, fragHeight*1.5f), frag.text, style); 3209 positionY += fragHeight; 3210 } 3211 } 3212 UpdateEnhancedSelectTextIdx(hPad); 3213 } 3214 3215 private List<Fragment> RecomputeFragments(string text) 3216 { 3217 List<Fragment> result = new List<Fragment>(); 3218 3219 string[] pieces = text.Split('\n'); 3220 _mTextLines = pieces.Length; 3221 3222 StringBuilder b = new StringBuilder(); 3223 3224 int lineCount = 0; 3225 for (int a = 0; a < pieces.Length; a++) 3226 { 3227 if (b.Length >= kMaxFragment) 3228 { 3229 b.Remove(b.Length - 1, 1); 3230 AddFragment(b, lineCount, result); 3231 lineCount = 0; 3232 } 3233 3234 b.Append(pieces[a]); 3235 b.Append('\n'); 3236 lineCount++; 3237 } 3238 3239 if (b.Length > 0) 3240 { 3241 b.Remove(b.Length - 1, 1); 3242 AddFragment(b, lineCount, result); 3243 } 3244 3245 return result; 3246 } 3247 3248 private List<Fragment> RecomputeFragmentsFromBlock(int blockIdx, bool colored) 3249 { 3250 var text = _disassembler.GetOrRenderBlockToTextUncached(blockIdx, colored).TrimEnd('\n'); 3251 3252 return RecomputeFragments(text); 3253 } 3254 3255 internal List<Fragment> RecomputeFragmentsFromBlock(int blockIdx) 3256 { 3257 var text = _disassembler.GetOrRenderBlockToText(blockIdx).TrimEnd('\n'); 3258 3259 return RecomputeFragments(text); 3260 } 3261 3262 private static void AddFragment(StringBuilder b, int lineCount, List<Fragment> result) 3263 { 3264 result.Add(new Fragment() { text = b.ToString(), lineCount = lineCount }); 3265 b.Length = 0; 3266 } 3267 } 3268}