A game about forced loneliness, made by TACStudios
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}