633 lines
20 KiB
C#
633 lines
20 KiB
C#
![]() |
/*
|
||
|
Copyright (C) 2014-2019 de4dot@gmail.com
|
||
|
|
||
|
This file is part of dnSpy
|
||
|
|
||
|
dnSpy is free software: you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation, either version 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
dnSpy is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Collections.ObjectModel;
|
||
|
using System.Diagnostics;
|
||
|
using VST = Microsoft.VisualStudio.Text;
|
||
|
|
||
|
namespace dnSpy.Contracts.Hex {
|
||
|
/// <summary>
|
||
|
/// Line information
|
||
|
/// </summary>
|
||
|
public abstract class HexBufferLine {
|
||
|
/// <summary>
|
||
|
/// Constructor
|
||
|
/// </summary>
|
||
|
protected HexBufferLine() { }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the <see cref="HexBufferLineFormatter"/> instance that created this line
|
||
|
/// </summary>
|
||
|
public abstract HexBufferLineFormatter LineProvider { get; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the buffer
|
||
|
/// </summary>
|
||
|
public HexBuffer Buffer => LineProvider.Buffer;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the line number
|
||
|
/// </summary>
|
||
|
public abstract HexPosition LineNumber { get; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the column order
|
||
|
/// </summary>
|
||
|
public abstract ReadOnlyCollection<HexColumnType> ColumnOrder { get; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Buffer span
|
||
|
/// </summary>
|
||
|
public abstract HexBufferSpan BufferSpan { get; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the start position
|
||
|
/// </summary>
|
||
|
public HexBufferPoint BufferStart => BufferSpan.Start;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the end position
|
||
|
/// </summary>
|
||
|
public HexBufferPoint BufferEnd => BufferSpan.End;
|
||
|
|
||
|
/// <summary>
|
||
|
/// All raw bytes
|
||
|
/// </summary>
|
||
|
public abstract HexBytes HexBytes { get; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Text shown in the UI. The positions of the offset column, values column
|
||
|
/// and ASCII column are not fixed, use one of the GetXXX methods to get
|
||
|
/// the spans.
|
||
|
/// </summary>
|
||
|
public abstract string Text { get; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets a span covering <see cref="Text"/>
|
||
|
/// </summary>
|
||
|
public VST.Span TextSpan => new VST.Span(0, Text.Length);
|
||
|
|
||
|
/// <summary>
|
||
|
/// true if the offset column is present
|
||
|
/// </summary>
|
||
|
public abstract bool IsOffsetColumnPresent { get; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// true if the values column is present
|
||
|
/// </summary>
|
||
|
public abstract bool IsValuesColumnPresent { get; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// true if the ASCII column is present
|
||
|
/// </summary>
|
||
|
public abstract bool IsAsciiColumnPresent { get; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns true if a column is present
|
||
|
/// </summary>
|
||
|
/// <param name="column">Column</param>
|
||
|
/// <returns></returns>
|
||
|
public bool IsColumnPresent(HexColumnType column) {
|
||
|
switch (column) {
|
||
|
case HexColumnType.Offset: return IsOffsetColumnPresent;
|
||
|
case HexColumnType.Values: return IsValuesColumnPresent;
|
||
|
case HexColumnType.Ascii: return IsAsciiColumnPresent;
|
||
|
default: throw new ArgumentOutOfRangeException(nameof(column));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the value of the offset shown in <see cref="Text"/>. The real offset
|
||
|
/// is stored in <see cref="BufferSpan"/>
|
||
|
/// </summary>
|
||
|
public abstract HexPosition LogicalOffset { get; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the span of a column
|
||
|
/// </summary>
|
||
|
/// <param name="column">Colum</param>
|
||
|
/// <param name="onlyVisibleCells">true to only include visible values, false to include the full column</param>
|
||
|
/// <returns></returns>
|
||
|
public VST.Span GetSpan(HexColumnType column, bool onlyVisibleCells) {
|
||
|
switch (column) {
|
||
|
case HexColumnType.Offset: return GetOffsetSpan();
|
||
|
case HexColumnType.Values: return GetValuesSpan(onlyVisibleCells);
|
||
|
case HexColumnType.Ascii: return GetAsciiSpan(onlyVisibleCells);
|
||
|
default: throw new ArgumentOutOfRangeException(nameof(column));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the span of the offset in <see cref="Text"/>. This can be an empty span if
|
||
|
/// the offset column isn't shown.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
public abstract VST.Span GetOffsetSpan();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the span of the values column
|
||
|
/// </summary>
|
||
|
/// <param name="onlyVisibleCells">true to only include visible values, false to include the full column</param>
|
||
|
/// <returns></returns>
|
||
|
public abstract VST.Span GetValuesSpan(bool onlyVisibleCells);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets values spans
|
||
|
/// </summary>
|
||
|
/// <param name="span">Span and selection flags</param>
|
||
|
/// <returns></returns>
|
||
|
public IEnumerable<TextAndHexSpan> GetValuesSpans(HexBufferSpanSelection span) =>
|
||
|
GetValuesSpans(span.BufferSpan, span.SelectionFlags);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets values spans
|
||
|
/// </summary>
|
||
|
/// <param name="span">Buffer span</param>
|
||
|
/// <param name="flags">Flags</param>
|
||
|
/// <returns></returns>
|
||
|
public IEnumerable<TextAndHexSpan> GetValuesSpans(HexBufferSpan span, HexSpanSelectionFlags flags) =>
|
||
|
GetTextAndHexSpans(IsValuesColumnPresent, ValueCells, span, flags, GetValuesSpan(onlyVisibleCells: true), GetValuesSpan(onlyVisibleCells: false));
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the span of the ASCII column. This can be an empty span
|
||
|
/// if the ASCII column isn't shown.
|
||
|
/// </summary>
|
||
|
/// <param name="onlyVisibleCells">true to only include visible characters, false to include the full column</param>
|
||
|
/// <returns></returns>
|
||
|
public abstract VST.Span GetAsciiSpan(bool onlyVisibleCells);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets ASCII spans
|
||
|
/// </summary>
|
||
|
/// <param name="span">Span and selection flags</param>
|
||
|
/// <returns></returns>
|
||
|
public IEnumerable<TextAndHexSpan> GetAsciiSpans(HexBufferSpanSelection span) =>
|
||
|
GetAsciiSpans(span.BufferSpan, span.SelectionFlags);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets ASCII spans
|
||
|
/// </summary>
|
||
|
/// <param name="span">Buffer span</param>
|
||
|
/// <param name="flags">Flags</param>
|
||
|
/// <returns></returns>
|
||
|
public IEnumerable<TextAndHexSpan> GetAsciiSpans(HexBufferSpan span, HexSpanSelectionFlags flags) =>
|
||
|
GetTextAndHexSpans(IsAsciiColumnPresent, AsciiCells, span, flags, GetAsciiSpan(onlyVisibleCells: true), GetAsciiSpan(onlyVisibleCells: false));
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets column spans in column order
|
||
|
/// </summary>
|
||
|
/// <param name="span">Span and selection flags</param>
|
||
|
/// <returns></returns>
|
||
|
public IEnumerable<TextAndHexSpan> GetSpans(HexBufferSpanSelection span) =>
|
||
|
GetSpans(span.BufferSpan, span.SelectionFlags);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets column spans in column order
|
||
|
/// </summary>
|
||
|
/// <param name="span">Buffer span</param>
|
||
|
/// <param name="flags">Flags</param>
|
||
|
/// <returns></returns>
|
||
|
public IEnumerable<TextAndHexSpan> GetSpans(HexBufferSpan span, HexSpanSelectionFlags flags) {
|
||
|
if (span.IsDefault)
|
||
|
throw new ArgumentException();
|
||
|
if (span.Buffer != Buffer)
|
||
|
throw new ArgumentException();
|
||
|
|
||
|
var overlapSpan = BufferSpan.Overlap(span);
|
||
|
if (overlapSpan is null)
|
||
|
yield break;
|
||
|
|
||
|
foreach (var column in ColumnOrder) {
|
||
|
switch (column) {
|
||
|
case HexColumnType.Offset:
|
||
|
if ((flags & HexSpanSelectionFlags.Offset) != 0 && IsOffsetColumnPresent) {
|
||
|
if (BufferSpan.Contains(overlapSpan.Value))
|
||
|
yield return new TextAndHexSpan(GetOffsetSpan(), BufferSpan);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case HexColumnType.Values:
|
||
|
if ((flags & HexSpanSelectionFlags.Values) != 0) {
|
||
|
foreach (var info in GetValuesSpans(overlapSpan.Value, flags))
|
||
|
yield return info;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case HexColumnType.Ascii:
|
||
|
if ((flags & HexSpanSelectionFlags.Ascii) != 0) {
|
||
|
foreach (var info in GetAsciiSpans(overlapSpan.Value, flags))
|
||
|
yield return info;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the value cell collection
|
||
|
/// </summary>
|
||
|
public abstract HexCellCollection ValueCells { get; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the ASCII cell collection
|
||
|
/// </summary>
|
||
|
public abstract HexCellCollection AsciiCells { get; }
|
||
|
|
||
|
TextAndHexSpan Create(HexCellCollection collection, HexCell first, HexCell last, HexBufferSpan bufferSpan) {
|
||
|
var firstCellSpan = first.FullSpan;
|
||
|
var lastCellSpan = last.FullSpan;
|
||
|
var startPos = HexPosition.MaxEndPosition;
|
||
|
var endPos = HexPosition.Zero;
|
||
|
for (int i = first.Index; i <= last.Index; i++) {
|
||
|
var cell = collection[i];
|
||
|
if (!cell.HasData)
|
||
|
continue;
|
||
|
startPos = HexPosition.Min(startPos, cell.BufferStart);
|
||
|
endPos = HexPosition.Max(endPos, cell.BufferEnd);
|
||
|
}
|
||
|
var resultBufferSpan = startPos <= endPos ?
|
||
|
new HexBufferSpan(new HexBufferPoint(Buffer, startPos), new HexBufferPoint(Buffer, endPos)) :
|
||
|
bufferSpan;
|
||
|
return new TextAndHexSpan(VST.Span.FromBounds(firstCellSpan.Start, lastCellSpan.End), resultBufferSpan);
|
||
|
}
|
||
|
|
||
|
IEnumerable<TextAndHexSpan> GetTextAndHexSpans(bool isColumnPresent, HexCellCollection collection, HexBufferSpan span, HexSpanSelectionFlags flags, VST.Span visibleSpan, VST.Span fullSpan) {
|
||
|
if (span.IsDefault)
|
||
|
throw new ArgumentException();
|
||
|
if (span.Buffer != Buffer)
|
||
|
throw new ArgumentException();
|
||
|
|
||
|
if (!isColumnPresent)
|
||
|
yield break;
|
||
|
|
||
|
var overlapSpan = BufferSpan.Overlap(span);
|
||
|
if (overlapSpan is null)
|
||
|
yield break;
|
||
|
|
||
|
if ((flags & (HexSpanSelectionFlags.Group0 | HexSpanSelectionFlags.Group1)) != 0) {
|
||
|
bool group0 = (flags & HexSpanSelectionFlags.Group0) != 0;
|
||
|
bool group1 = (flags & HexSpanSelectionFlags.Group1) != 0;
|
||
|
|
||
|
IEnumerable<HexCell> cells;
|
||
|
if ((flags & HexSpanSelectionFlags.AllCells) != 0) {
|
||
|
cells = collection.GetCells();
|
||
|
overlapSpan = BufferSpan;
|
||
|
}
|
||
|
else if ((flags & HexSpanSelectionFlags.AllVisibleCells) != 0) {
|
||
|
cells = collection.GetVisibleCells();
|
||
|
overlapSpan = BufferSpan;
|
||
|
}
|
||
|
else
|
||
|
cells = collection.GetCells(overlapSpan.Value);
|
||
|
HexCell? firstCell = null;
|
||
|
HexCell? lastCell = null;
|
||
|
foreach (var cell in cells) {
|
||
|
if (!((cell.GroupIndex == 0 && group0) || (cell.GroupIndex == 1 && group1)))
|
||
|
continue;
|
||
|
if (firstCell is null) {
|
||
|
firstCell = cell;
|
||
|
lastCell = cell;
|
||
|
}
|
||
|
else if (lastCell!.Index + 1 == cell.Index && lastCell.GroupIndex == cell.GroupIndex)
|
||
|
lastCell = cell;
|
||
|
else {
|
||
|
yield return Create(collection, firstCell, lastCell, overlapSpan.Value);
|
||
|
firstCell = lastCell = cell;
|
||
|
}
|
||
|
}
|
||
|
if (firstCell is not null)
|
||
|
yield return Create(collection, firstCell, lastCell!, overlapSpan.Value);
|
||
|
yield break;
|
||
|
}
|
||
|
if ((flags & HexSpanSelectionFlags.AllVisibleCells) != 0) {
|
||
|
yield return new TextAndHexSpan(visibleSpan, BufferSpan);
|
||
|
yield break;
|
||
|
}
|
||
|
if ((flags & HexSpanSelectionFlags.AllCells) != 0) {
|
||
|
yield return new TextAndHexSpan(fullSpan, BufferSpan);
|
||
|
yield break;
|
||
|
}
|
||
|
|
||
|
if ((flags & HexSpanSelectionFlags.OneValue) != 0) {
|
||
|
foreach (var cell in collection.GetCells(overlapSpan.Value)) {
|
||
|
if (!cell.HasData)
|
||
|
continue;
|
||
|
var cellSpan = cell.GetSpan(flags);
|
||
|
yield return new TextAndHexSpan(cellSpan, new HexBufferSpan(Buffer, cell.BufferSpan));
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
int textStart = int.MaxValue;
|
||
|
int textEnd = int.MinValue;
|
||
|
var posStart = HexPosition.MaxValue;
|
||
|
var posEnd = HexPosition.MinValue;
|
||
|
foreach (var cell in collection.GetCells(overlapSpan.Value)) {
|
||
|
if (!cell.HasData)
|
||
|
continue;
|
||
|
var cellSpan = cell.GetSpan(flags);
|
||
|
textStart = Math.Min(textStart, cellSpan.Start);
|
||
|
textEnd = Math.Max(textEnd, cellSpan.End);
|
||
|
|
||
|
posStart = HexPosition.Min(posStart, cell.BufferStart);
|
||
|
posEnd = HexPosition.Max(posEnd, cell.BufferEnd);
|
||
|
}
|
||
|
|
||
|
if (textStart > textEnd || posStart > posEnd)
|
||
|
yield break;
|
||
|
yield return new TextAndHexSpan(VST.Span.FromBounds(textStart, textEnd), new HexBufferSpan(Buffer, HexSpan.FromBounds(posStart, posEnd)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets a text line position or null
|
||
|
/// </summary>
|
||
|
/// <param name="position">Position</param>
|
||
|
/// <returns></returns>
|
||
|
public int? GetLinePosition(HexCellPosition position) {
|
||
|
if (position.IsDefault)
|
||
|
throw new ArgumentException();
|
||
|
|
||
|
HexCellCollection collection;
|
||
|
switch (position.Column) {
|
||
|
case HexColumnType.Values: collection = ValueCells; break;
|
||
|
case HexColumnType.Ascii: collection = AsciiCells; break;
|
||
|
case HexColumnType.Offset:
|
||
|
default:
|
||
|
throw new ArgumentOutOfRangeException(nameof(position));
|
||
|
}
|
||
|
|
||
|
var cell = collection.GetCell(position.BufferPosition);
|
||
|
if (cell is null)
|
||
|
return null;
|
||
|
if (position.CellPosition >= cell.CellSpan.Length)
|
||
|
return null;
|
||
|
return cell.CellSpan.Start + position.CellPosition;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a <see cref="HexLinePositionInfo"/>
|
||
|
/// </summary>
|
||
|
/// <param name="linePosition">Line position</param>
|
||
|
/// <returns></returns>
|
||
|
public HexLinePositionInfo GetLinePositionInfo(int linePosition) {
|
||
|
if (linePosition >= Text.Length)
|
||
|
return HexLinePositionInfo.CreateVirtualSpace(linePosition, Text.Length);
|
||
|
|
||
|
if (IsOffsetColumnPresent) {
|
||
|
var span = GetOffsetSpan();
|
||
|
if (span.Contains(linePosition))
|
||
|
return HexLinePositionInfo.CreateOffset(linePosition, linePosition - span.Start);
|
||
|
}
|
||
|
|
||
|
if (IsValuesColumnPresent) {
|
||
|
var valuesSpan = GetValuesSpan(onlyVisibleCells: false);
|
||
|
if (valuesSpan.Contains(linePosition)) {
|
||
|
int cellIndex = (linePosition - valuesSpan.Start) / LineProvider.GetCharsPerCellIncludingSeparator(HexColumnType.Values);
|
||
|
var cell = ValueCells[cellIndex];
|
||
|
if (cell.SeparatorSpan.Contains(linePosition))
|
||
|
return HexLinePositionInfo.CreateValueCellSeparator(linePosition, cell);
|
||
|
return HexLinePositionInfo.CreateValue(linePosition, cell);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (IsAsciiColumnPresent) {
|
||
|
var asciiSpan = GetAsciiSpan(onlyVisibleCells: false);
|
||
|
if (asciiSpan.Contains(linePosition)) {
|
||
|
int cellIndex = linePosition - asciiSpan.Start;
|
||
|
var cell = AsciiCells[cellIndex];
|
||
|
return HexLinePositionInfo.CreateAscii(linePosition, cell);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach (var column in ColumnOrder) {
|
||
|
var span = GetSpan(column, onlyVisibleCells: false);
|
||
|
if (span.End == linePosition)
|
||
|
return HexLinePositionInfo.CreateColumnSeparator(linePosition);
|
||
|
}
|
||
|
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the closest cell position
|
||
|
/// </summary>
|
||
|
/// <param name="linePosition">Line position</param>
|
||
|
/// <param name="onlyVisibleCells">true to only return cells with data</param>
|
||
|
/// <returns></returns>
|
||
|
public HexCellPosition? GetClosestCellPosition(int linePosition, bool onlyVisibleCells) {
|
||
|
var posInfo = GetLinePositionInfo(linePosition);
|
||
|
return GetClosestCellPosition(posInfo, onlyVisibleCells);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the closest cell position
|
||
|
/// </summary>
|
||
|
/// <param name="position">Position</param>
|
||
|
/// <param name="onlyVisibleCells">true to only return cells with data</param>
|
||
|
/// <returns></returns>
|
||
|
public HexCellPosition? GetClosestCellPosition(HexLinePositionInfo position, bool onlyVisibleCells) {
|
||
|
switch (position.Type) {
|
||
|
case HexLinePositionInfoType.ValueCell:
|
||
|
case HexLinePositionInfoType.AsciiCell:
|
||
|
break;
|
||
|
|
||
|
case HexLinePositionInfoType.ValueCellSeparator:
|
||
|
Debug2.Assert(position.Cell is not null);
|
||
|
Debug.Assert(position.Cell.CellSpan.End == position.Position);
|
||
|
position = HexLinePositionInfo.CreateValue(position.Cell.CellSpan.End - 1, position.Cell);
|
||
|
break;
|
||
|
|
||
|
case HexLinePositionInfoType.Offset:
|
||
|
case HexLinePositionInfoType.ColumnSeparator:
|
||
|
case HexLinePositionInfoType.VirtualSpace:
|
||
|
var closestPos = GetClosestCellPosition(position.Position);
|
||
|
if (closestPos is null)
|
||
|
return null;
|
||
|
Debug.Assert(closestPos.Value.IsAsciiCell || closestPos.Value.IsValueCell);
|
||
|
position = closestPos.Value;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
|
||
|
var cell = position.Cell;
|
||
|
int cellPosition = position.CellPosition;
|
||
|
switch (position.Type) {
|
||
|
case HexLinePositionInfoType.AsciiCell:
|
||
|
if (!IsAsciiColumnPresent)
|
||
|
return null;
|
||
|
Debug2.Assert(cell is not null);
|
||
|
if (onlyVisibleCells && !cell.HasData) {
|
||
|
var visible = GetVisible(AsciiCells, cell);
|
||
|
if (visible is null)
|
||
|
return null;
|
||
|
cell = visible.Value.cell;
|
||
|
cellPosition = visible.Value.cellPosition;
|
||
|
}
|
||
|
return new HexCellPosition(HexColumnType.Ascii, cell.BufferStart, cellPosition);
|
||
|
|
||
|
case HexLinePositionInfoType.ValueCell:
|
||
|
Debug2.Assert(cell is not null);
|
||
|
if (!IsValuesColumnPresent)
|
||
|
return null;
|
||
|
if (onlyVisibleCells && !cell.HasData) {
|
||
|
var visible = GetVisible(ValueCells, cell);
|
||
|
if (visible is null)
|
||
|
return null;
|
||
|
cell = visible.Value.cell;
|
||
|
cellPosition = visible.Value.cellPosition;
|
||
|
}
|
||
|
return new HexCellPosition(HexColumnType.Values, LineProvider.GetValueBufferSpan(cell, cellPosition).Start, cellPosition);
|
||
|
|
||
|
case HexLinePositionInfoType.Offset:
|
||
|
case HexLinePositionInfoType.ValueCellSeparator:
|
||
|
case HexLinePositionInfoType.ColumnSeparator:
|
||
|
case HexLinePositionInfoType.VirtualSpace:
|
||
|
default:
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static (HexCell cell, int cellPosition)? GetVisible(HexCellCollection collection, HexCell cell) {
|
||
|
if (cell.HasData)
|
||
|
throw new ArgumentException();
|
||
|
for (int i = cell.Index + 1; i < collection.Count; i++) {
|
||
|
var c = collection[i];
|
||
|
if (!c.HasData)
|
||
|
continue;
|
||
|
return (c, 0);
|
||
|
}
|
||
|
for (int i = cell.Index - 1; i >= 0; i--) {
|
||
|
var c = collection[i];
|
||
|
if (!c.HasData)
|
||
|
continue;
|
||
|
return (c, c.CellSpan.Length - 1);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
HexLinePositionInfo? GetClosestCellPosition(int linePosition) {
|
||
|
(HexColumnType columnType, HexCell cell)? closest = null;
|
||
|
int cellPosition = -1;
|
||
|
foreach (var info in GetCells()) {
|
||
|
var cell = info.cell;
|
||
|
if (closest is null || Compare(linePosition, cell, closest.Value.cell) < 0) {
|
||
|
closest = info;
|
||
|
cellPosition = linePosition - info.cell.CellSpan.Start;
|
||
|
if (cellPosition < 0)
|
||
|
cellPosition = 0;
|
||
|
else if (cellPosition >= info.cell.CellSpan.Length)
|
||
|
cellPosition = info.cell.CellSpan.Length - 1;
|
||
|
}
|
||
|
}
|
||
|
if (closest is null)
|
||
|
return null;
|
||
|
if (cellPosition < 0 || cellPosition >= closest.Value.cell.CellSpan.Length)
|
||
|
throw new InvalidOperationException();
|
||
|
int pos = closest.Value.cell.CellSpan.Start + cellPosition;
|
||
|
if (closest.Value.columnType == HexColumnType.Values)
|
||
|
return HexLinePositionInfo.CreateValue(pos, closest.Value.cell);
|
||
|
if (closest.Value.columnType == HexColumnType.Ascii)
|
||
|
return HexLinePositionInfo.CreateAscii(pos, closest.Value.cell);
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
|
||
|
static int Compare(int linePosition, HexCell a, HexCell b) {
|
||
|
int da = GetLength(linePosition, a);
|
||
|
int db = GetLength(linePosition, b);
|
||
|
return da - db;
|
||
|
}
|
||
|
|
||
|
static int GetLength(int linePosition, HexCell a) {
|
||
|
int sl = Math.Abs(linePosition - a.FullSpan.Start);
|
||
|
int el = Math.Abs(linePosition - (a.FullSpan.End - 1));
|
||
|
return Math.Min(sl, el);
|
||
|
}
|
||
|
|
||
|
IEnumerable<(HexColumnType columnType, HexCell cell)> GetCells() {
|
||
|
foreach (var column in ColumnOrder) {
|
||
|
switch (column) {
|
||
|
case HexColumnType.Offset:
|
||
|
break;
|
||
|
|
||
|
case HexColumnType.Values:
|
||
|
if (IsValuesColumnPresent) {
|
||
|
foreach (var cell in ValueCells.GetCells())
|
||
|
yield return (HexColumnType.Values, cell);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case HexColumnType.Ascii:
|
||
|
if (IsAsciiColumnPresent) {
|
||
|
foreach (var cell in AsciiCells.GetCells())
|
||
|
yield return (HexColumnType.Ascii, cell);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw new InvalidOperationException();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the text. All cells outside the input range are cleared.
|
||
|
/// </summary>
|
||
|
/// <param name="visibleBytes">Visible bytes</param>
|
||
|
/// <returns></returns>
|
||
|
public string GetText(HexBufferSpan visibleBytes) {
|
||
|
if (visibleBytes.Contains(BufferSpan))
|
||
|
return Text;
|
||
|
|
||
|
var chars = Text.ToCharArray();
|
||
|
|
||
|
ClearCells(chars, IsValuesColumnPresent, ValueCells, visibleBytes);
|
||
|
ClearCells(chars, IsAsciiColumnPresent, AsciiCells, visibleBytes);
|
||
|
|
||
|
return new string(chars);
|
||
|
}
|
||
|
|
||
|
void ClearCells(char[] chars, bool isColumnPresent, HexCellCollection cells, HexBufferSpan visibleBytes) {
|
||
|
if (!isColumnPresent)
|
||
|
return;
|
||
|
foreach (var cell in cells.GetVisibleCells()) {
|
||
|
// Don't clear it if the cell is in the visible bytes span, this includes the case
|
||
|
// where only some of its bytes are visible.
|
||
|
if (visibleBytes.Contains(cell.BufferSpan))
|
||
|
continue;
|
||
|
|
||
|
for (int i = cell.TextSpan.Start; i < cell.TextSpan.End; i++)
|
||
|
chars[i] = ' ';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns <see cref="Text"/>
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
public override string ToString() => Text;
|
||
|
}
|
||
|
}
|