diff --git a/SourceGenWPF/DisplayList.cs b/SourceGenWPF/DisplayList.cs
index a52da10..f3a4999 100644
--- a/SourceGenWPF/DisplayList.cs
+++ b/SourceGenWPF/DisplayList.cs
@@ -14,49 +14,276 @@
* limitations under the License.
*/
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
using System.Diagnostics;
-using System.Windows.Media;
+using System.ComponentModel;
+using System.Linq;
using System.Text;
-
-using Asm65;
+using System.Threading.Tasks;
namespace SourceGenWPF {
///
- /// Converts file data and Anattrib contents into a series of strings and format metadata.
- /// This is used as the backing store for ProjectView's codeListView.
+ /// List of items formatted for display.
///
- public class DisplayList {
- ///
- /// List of display lines.
- ///
- private List mLineList;
+ ///
+ /// This is intended to be useful as an ItemSource for a WPF ListView. We need to implement
+ /// plain IList to cause ListView to perform data virtualization, and the property/collection
+ /// changed events so the view will pick up our changes.
+ ///
+ /// The ItemsControl.ItemsSource property wants an IEnumerable (which IList implements).
+ /// According to various articles, if the object implements IList, and the UI element
+ /// is providing UI virtualization, you will also get data virtualization. This behavior
+ /// doesn't seem to be documented anywhere, but the consensus is that it's expected to work.
+ ///
+ /// Implementing generic IList doesn't seem necessary for XAML, but is useful for other
+ /// customers of the data (e.g. the assembler source generator).
+ ///
+ public class DisplayList : IList, IList,
+ INotifyCollectionChanged, INotifyPropertyChanged {
+
+ // TODO: check VirtualizingStackPanel.VirtualizationMode == recycling (page 259)
///
- /// Project that contains the data we're formatting, notably the FileData and
- /// Anattribs arrays.
+ /// List of formatted parts. The idea is that the list is initially populated with
+ /// null references, and FormattedParts objects are generated on demand.
///
- private DisasmProject mProject;
+ private List mList;
///
- /// Code/data formatter.
+ /// Constructs an empty collection, with the default initial capacity.
///
- private Formatter mFormatter;
+ public DisplayList() {
+ mList = new List();
+ }
- ///
- /// If set, prepend cycle counts to EOL comments.
- ///
- private bool mShowCycleCounts;
+ public DisplayList(int count) {
+ mList = new List(count);
+ for (int i = 0; i < count; i++) {
+ mList.Add(null);
+ }
+ }
+
+
+
+ #region Property / Collection Changed
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ // See ObservableCollection class, e.g.
+ // https://github.com/Microsoft/referencesource/blob/master/System/compmod/system/collections/objectmodel/observablecollection.cs
+
+ private const string CountString = "Count";
+ private const string IndexerName = "Item[]";
+
+#if false
+ protected override void ClearItems() {
+ base.ClearItems();
+ OnPropertyChanged(CountString);
+ OnPropertyChanged(IndexerName);
+ OnCollectionReset();
+ }
+
+ protected override void RemoveItem(int index) {
+ FormattedParts removedItem = this[index];
+
+ base.RemoveItem(index);
+
+ OnPropertyChanged(CountString);
+ OnPropertyChanged(IndexerName);
+ OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItem, index);
+ }
+
+ protected override void InsertItem(int index, FormattedParts item) {
+ base.InsertItem(index, item);
+
+ OnPropertyChanged(CountString);
+ OnPropertyChanged(IndexerName);
+ OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
+ }
+
+ protected override void SetItem(int index, FormattedParts item) {
+ FormattedParts originalItem = this[index];
+ base.SetItem(index, item);
+
+ OnPropertyChanged(IndexerName);
+ OnCollectionChanged(NotifyCollectionChangedAction.Replace, originalItem, item, index);
+ }
+#endif
+
+ protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) {
+ PropertyChanged?.Invoke(this, e);
+ }
+
+ private void OnPropertyChanged(string propertyName) {
+ OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
+ }
+
+ protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) {
+ CollectionChanged?.Invoke(this, e);
+ }
+
+ private void OnCollectionChanged(NotifyCollectionChangedAction action,
+ object item, int index) {
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
+ }
+
+ private void OnCollectionChanged(NotifyCollectionChangedAction action,
+ object item, int index, int oldIndex) {
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index, oldIndex));
+ }
+
+ private void OnCollectionChanged(NotifyCollectionChangedAction action,
+ object oldItem, object newItem, int index) {
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
+ }
+
+ private void OnCollectionReset() {
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+
+ #endregion Property / Collection Changed
+
+
+
+ #region IList / IList
+ public int Count => ((IList)mList).Count;
+
+ public bool IsReadOnly => ((IList)mList).IsReadOnly;
+
+ public bool IsFixedSize => ((IList)mList).IsFixedSize;
+
+ public object SyncRoot => ((IList)mList).SyncRoot;
+
+ public bool IsSynchronized => ((IList)mList).IsSynchronized;
+
+ public void Add(FormattedParts item) {
+ ((IList)mList).Add(item);
+ OnPropertyChanged(CountString);
+ OnPropertyChanged(IndexerName);
+ OnCollectionReset();
+ }
+
+ public int Add(object value) {
+ int posn = ((IList)mList).Add(value);
+ if (posn >= 0) {
+ OnPropertyChanged(CountString);
+ OnPropertyChanged(IndexerName);
+ OnCollectionChanged(NotifyCollectionChangedAction.Add, value, posn);
+ }
+ return posn;
+ }
+
+ public void Clear() {
+ ((IList)mList).Clear();
+ OnPropertyChanged(CountString);
+ OnPropertyChanged(IndexerName);
+ OnCollectionReset();
+ }
+
+ public bool Contains(FormattedParts item) {
+ return ((IList)mList).Contains(item);
+ }
+ bool IList.Contains(object value) {
+ return Contains((FormattedParts)value);
+ }
+
+ public void CopyTo(FormattedParts[] array, int arrayIndex) {
+ ((IList)mList).CopyTo(array, arrayIndex);
+ }
+
+ public void CopyTo(Array array, int index) {
+ ((IList)mList).CopyTo(array, index);
+ }
+
+ public IEnumerator GetEnumerator() {
+ // Use the indexer, rather than mList's enumerator, to get on-demand string gen.
+ for (int i = 0; i < Count; i++) {
+ yield return this[i];
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() {
+ return GetEnumerator();
+ }
+
+ public int IndexOf(FormattedParts item) {
+ return ((IList)mList).IndexOf(item);
+ }
+ int IList.IndexOf(object value) {
+ return IndexOf((FormattedParts)value);
+ }
+
+ public void Insert(int index, FormattedParts item) {
+ ((IList)mList).Insert(index, item);
+ OnPropertyChanged(CountString);
+ OnPropertyChanged(IndexerName);
+ OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
+ }
+ void IList.Insert(int index, object value) {
+ Insert(index, (FormattedParts)value);
+ }
+
+ public void RemoveAt(int index) {
+ FormattedParts removed = mList[index];
+ ((IList)mList).RemoveAt(index);
+ OnPropertyChanged(CountString);
+ OnPropertyChanged(IndexerName);
+ OnCollectionChanged(NotifyCollectionChangedAction.Remove, removed, index);
+ }
+
+ public bool Remove(FormattedParts item) {
+ // NotifyCollectionChangedAction.Remove wants an index. We can find the index
+ // of the first matching item and then do a RemoveAt, but this call just isn't
+ // all that interesting for us, so it's easier to ignore it.
+ //return ((IList)mList).Remove(item);
+ throw new NotSupportedException();
+ }
+
+ void IList.Remove(object value) {
+ //Remove((FormattedParts)value);
+ throw new NotSupportedException();
+ }
+
+ object IList.this[int index] {
+ // forward to generic impl
+ get { return this[index]; }
+ set { this[index] = (FormattedParts)value; }
+ }
+
+ // For IList.
+ public FormattedParts this[int index] {
+ get {
+ FormattedParts parts = mList[index];
+ if (parts == null) {
+ parts = mList[index] = GetEntry(index);
+ }
+ return parts;
+ }
+ set {
+ FormattedParts orig = mList[index];
+ mList[index] = value;
+ OnPropertyChanged(IndexerName);
+ OnCollectionChanged(NotifyCollectionChangedAction.Replace, orig, value, index);
+ }
+ }
+
+ #endregion IList / IList
- ///
- /// Names for pseudo-ops.
- ///
- private PseudoOp.PseudoOpNames mPseudoOpNames;
///
- /// Holds a collection of formatted strings. Instances are immutable.
+ /// Retrieves the Nth element.
///
+ private FormattedParts GetEntry(int index) {
+ Debug.WriteLine("GEN " + index);
+ return FormattedParts.Create("off" + index, "addr" + index, "12 34",
+ "vncidmx", "", "yup:", "LDA", "$1234", "a & b");
+ }
+
public class FormattedParts {
public string Offset { get; private set; }
public string Addr { get; private set; }
@@ -68,12 +295,12 @@ namespace SourceGenWPF {
public string Operand { get; private set; }
public string Comment { get; private set; }
- // Use factory methods.
+ // Construct with factory methods.
private FormattedParts() { }
public static FormattedParts Create(string offset, string addr, string bytes,
string flags, string attr, string label, string opcode, string operand,
- string comment, string debug) {
+ string comment) {
FormattedParts parts = new FormattedParts();
parts.Offset = offset;
parts.Addr = addr;
@@ -84,1141 +311,9 @@ namespace SourceGenWPF {
parts.Opcode = opcode;
parts.Operand = operand;
parts.Comment = comment;
+
return parts;
}
-
- public static FormattedParts CreateBlankLine() {
- FormattedParts parts = new FormattedParts();
- return parts;
- }
-
- public static FormattedParts CreateLongComment(string comment) {
- FormattedParts parts = new FormattedParts();
- parts.Comment = comment;
- return parts;
- }
-
- public static FormattedParts CreateDirective(string opstr, string addrStr) {
- FormattedParts parts = new FormattedParts();
- parts.Opcode = opstr;
- parts.Operand = addrStr;
- return parts;
- }
-
- public static FormattedParts CreateEquDirective(string label, string opstr,
- string addrStr, string comment) {
- FormattedParts parts = new FormattedParts();
- parts.Label = label;
- parts.Opcode = opstr;
- parts.Operand = addrStr;
- parts.Comment = comment;
- return parts;
- }
- }
-
- ///
- /// One of these per line of output in the display. It should be possible to draw
- /// all of the output without needing to refer back to the project data. (Currently
- /// making an exception for some selection-dependent field highlighting.)
- ///
- /// Base fields are immutable, but the Parts property is set after creation.
- ///
- public class Line {
- // Extremely-negative offset value ensures it's at the very top.
- public const int HEADER_COMMENT_OFFSET = int.MinValue + 1;
-
- [FlagsAttribute]
- public enum Type {
- Unclassified = 0,
-
- // Primary functional items.
- Code = 1 << 0,
- Data = 1 << 1, // includes inline data
- CodeOrData = (Code | Data),
-
- // Decorative items, added by user or formatter.
- LongComment = 1 << 2,
- Note = 1 << 3,
- Blank = 1 << 4,
-
- // Assembler directives.
- OrgDirective = 1 << 5,
- EquDirective = 1 << 6,
- RegWidthDirective = 1 << 7,
- }
-
- ///
- /// Line type.
- ///
- public Type LineType { get; private set; }
-
- ///
- /// Numeric offset value. Used to map a line item to the Anattrib. Note this is
- /// set for all lines, and is the same for all lines in a multi-line sequence,
- /// e.g. every line in a long comment has the file offset with which it is associated.
- ///
- public int FileOffset { get; private set; }
-
- ///
- /// Number of offsets this line covers. Will be > 0 for code and data, zero for
- /// everything else. The same value is used for all lines in a multi-line sequence.
- ///
- public int OffsetSpan { get; private set; }
-
- ///
- /// For multi-line entries, this indicates which line is represented. For
- /// single-line entries, this will be zero.
- ///
- public int SubLineIndex { get; private set; }
-
- ///
- /// Strings for display. Creation may be deferred. Use the DisplayList
- /// GetFormattedParts() method to access this property.
- ///
- public FormattedParts Parts { get; set; }
-
- ///
- /// Background color, used for notes.
- ///
- public Color BackgroundColor { get; set; }
-
- ///
- /// String for searching. May be created on demand when the Line is first searched.
- ///
- public string SearchString { get; set; }
-
-
- public Line(int offset, int span, Type type) : this(offset, span, type, 0) { }
-
- public Line(int offset, int span, Type type, int subLineIndex) {
- FileOffset = offset;
- OffsetSpan = span;
- LineType = type;
- SubLineIndex = subLineIndex;
- }
-
- ///
- /// True if this line is code or data.
- ///
- public bool IsCodeOrData {
- get {
- return LineType == Type.Code || LineType == Type.Data;
- }
- }
-
- ///
- /// Returns true if the specified offset is represented by this line. There
- /// will be only one code/data line for a given offset, but there may be
- /// multiple others (comments, notes, etc.) associated with it.
- ///
- ///
- ///
- public bool Contains(int offset) {
- // Note OffsetSpan can be zero.
- return (offset == FileOffset ||
- (offset >= FileOffset && offset < FileOffset + OffsetSpan));
- }
-
- public override string ToString() {
- return "Line type=" + LineType + " off=+" + FileOffset.ToString("x6") +
- " span=" + OffsetSpan;
- }
- }
-
- ///
- /// Captures the set of selected lines. Lines are identified by offset and type.
- ///
- /// The idea is to save the selection, rebuild the list -- potentially moving
- /// stuff around -- and then rebuild the selection bitmap by finding matching
- /// items.
- ///
- /// We don't try to identify parts of multi-line things. If you've selected
- /// part of a multi-line string, then when we restore the selection you'll have
- /// the entire string selected. For the operations that are possible across
- /// multiple offsets, this seems like reasonable behavior.
- ///
- /// We can't precisely restore the selection in terms of which file offsets
- /// are selected. If you select one byte and apply a code hint, we'll restore
- /// the selection to a line with 1-4 bytes. This gets weird if you hit "undo",
- /// as you will then have 1-4 bytes selected rather than the original one. It
- /// might be better to just clear the selection on "undo".
- ///
- public class SavedSelection {
- private class Tag {
- public int mOffset;
- public int mSpan;
- public Line.Type mTypes;
-
- public Tag(int offset, int span, Line.Type lineType) {
- //Debug.Assert(offset >= 0);
- Debug.Assert(span >= 0);
- mOffset = offset;
- mSpan = (span == 0) ? 1 : span;
- mTypes = lineType;
- }
- }
-
- private List mSelectionTags = new List();
-
- ///
- /// This is a place to save the file offset associated with the ListView's
- /// TopItem, so we can position the list appropriately.
- ///
- private int mTopOffset;
-
- // Use Generate().
- private SavedSelection() { }
-
- ///
- /// Creates a new SavedSelection object, generating a list of tags from the
- /// lines that are currently selected.
- ///
- /// If nothing is selected, SavedSelection will have no members.
- ///
- /// Display list, with list of Lines.
- /// Bit vector specifying which lines are selected.
- /// New SavedSelection object.
- public static SavedSelection Generate(DisplayList dl, VirtualListViewSelection sel,
- int topOffset) {
- SavedSelection savedSel = new SavedSelection();
- //Debug.Assert(topOffset >= 0);
- savedSel.mTopOffset = topOffset;
-
- List lineList = dl.mLineList;
- Debug.Assert(lineList.Count == sel.Length);
-
- // Generate tags, which are a combination of the offset, span, and a merge
- // of types of all the lines associated with that offset.
- //
- // We may want to consider some sort of optimization for a "select all"
- // operation, although there aren't many changes you can make after selecting
- // all lines in a very large file.
- Tag tag = null;
- int curOffset = -1;
- for (int i = 0; i < lineList.Count; i++) {
- if (!sel[i]) {
- continue;
- }
- Line line = lineList[i];
- // Code hinting can transform code to data and vice-versa, so we
- // want the tag to reflect the fact that both could exist.
- Line.Type lineType = line.LineType;
- if (lineType == Line.Type.Code || lineType == Line.Type.Data) {
- lineType = Line.Type.CodeOrData;
- }
- if (line.FileOffset != curOffset) {
- // advanced to new offset, flush previous
- if (tag != null) {
- savedSel.mSelectionTags.Add(tag);
- }
- curOffset = line.FileOffset;
-
- tag = new Tag(line.FileOffset, line.OffsetSpan, lineType);
- } else {
- // another item at same offset
- tag.mSpan = Math.Max(tag.mSpan, line.OffsetSpan);
- tag.mTypes |= lineType;
- }
- }
- if (curOffset == -1) {
- // It's hard to cause an action that requires save/restore when you don't
- // have anything selected in the ListView. However, this can happen if
- // you do a sequence like:
- // - Open a file that starts with a JMP followed by data.
- // - Click on the blank line below the code, which has the code's offset,
- // and select "remove hint". This causes the blank line to vanish,
- // so the Restore() won't select anything.
- // - Click "undo".
- Debug.WriteLine("NOTE: no selection found");
- } else {
- // Add the in-progress tag to the list.
- savedSel.mSelectionTags.Add(tag);
- }
-
- return savedSel;
- }
-
- ///
- /// Creates a selection set by identifying the set of lines in the display list
- /// that correspond to items in the SavedSelection tag list.
- ///
- /// Display list, with list of Lines.
- /// Set of selected lines.
- public VirtualListViewSelection Restore(DisplayList dl, out int topIndex) {
- List lineList = dl.mLineList;
- VirtualListViewSelection sel = new VirtualListViewSelection(lineList.Count);
-
- topIndex = -1;
-
- // Walk through the tag list, which is ordered by ascending offset, and
- // through the display list, which is similarly ordered.
- int tagIndex = 0;
- int lineIndex = 0;
- while (tagIndex < mSelectionTags.Count && lineIndex < lineList.Count) {
- Tag tag = mSelectionTags[tagIndex];
- int lineOffset = lineList[lineIndex].FileOffset;
-
- // If a line encompassing this offset was at the top of the ListView
- // control before, use this line's index as the top.
- if (topIndex < 0 && lineList[lineIndex].Contains(mTopOffset)) {
- topIndex = lineIndex;
- }
-
- if (lineOffset >= tag.mOffset && lineOffset < tag.mOffset + tag.mSpan) {
- // Intersection. If the line type matches, add it to the set.
- if ((tag.mTypes & lineList[lineIndex].LineType) != 0) {
- sel[lineIndex] = true;
- }
-
- // Advance to the next line entry.
- lineIndex++;
- } else if (tag.mOffset < lineOffset) {
- // advance tag
- tagIndex++;
- } else {
- Debug.Assert(tag.mOffset > lineOffset);
- lineIndex++;
- }
- }
-
- // Continue search for topIndex, if necessary.
- while (topIndex < 0 && lineIndex < lineList.Count) {
- if (lineList[lineIndex].Contains(mTopOffset)) {
- topIndex = lineIndex;
- break;
- }
- lineIndex++;
- }
- Debug.WriteLine("TopOffset +" + mTopOffset.ToString("x6") +
- " --> index " + topIndex);
- if (topIndex < 0) {
- // This can happen if you delete the header comment while scrolled
- // to the top of the list.
- topIndex = 0;
- }
- return sel;
- }
-
- public void DebugDump() {
- Debug.WriteLine("Selection (" + mSelectionTags.Count + " offsets):");
- foreach (Tag tag in mSelectionTags) {
- Debug.WriteLine(" +" + tag.mOffset.ToString("x6") + "/" +
- tag.mSpan + ": " + tag.mTypes);
- }
- }
- }
-
-
-
- ///
- /// Constructor.
- ///
- /// Project object.
- /// Formatter object.
- public DisplayList(DisasmProject proj, Formatter formatter,
- PseudoOp.PseudoOpNames opNames) {
- mProject = proj;
- mFormatter = formatter;
- mPseudoOpNames = opNames;
-
- mLineList = new List();
- mShowCycleCounts = AppSettings.Global.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS,
- false);
- }
-
- ///
- /// Changes the Formatter object. Clears the display list, instigating a full re-render.
- ///
- /// Formatter object.
- public void SetFormatter(Formatter formatter) {
- mFormatter = formatter;
- mLineList.Clear();
-
- // We probably just changed settings, so update this as well.
- mShowCycleCounts = AppSettings.Global.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS,
- false);
- }
-
- ///
- /// Changes the pseudo-op name object. Clears the display list, instigating a
- /// full re-render.
- ///
- /// Pseudo-op names.
- public void SetPseudoOpNames(PseudoOp.PseudoOpNames opNames) {
- mPseudoOpNames = opNames;
- mLineList.Clear();
- }
-
- ///
- /// Number of lines in the list.
- ///
- public int Count { get { return mLineList.Count; } }
-
- ///
- /// Retrieves the Nth element.
- ///
- public Line this[int key] {
- get {
- return mLineList[key];
- }
- }
-
- ///
- /// Returns the Line's FormattedParts object, generating it first if necessary.
- ///
- /// Object with formatted strings.
- public FormattedParts GetFormattedParts(int index) {
- Line line = mLineList[index];
- if (line.Parts == null) {
- FormattedParts parts;
- switch (line.LineType) {
- case Line.Type.Code:
- parts = GenerateInstructionLine(mProject, mFormatter,
- line.FileOffset, line.OffsetSpan, mShowCycleCounts);
- break;
- case Line.Type.Data:
- parts = GenerateDataLine(mProject, mFormatter, mPseudoOpNames,
- line.FileOffset, line.SubLineIndex);
- break;
- case Line.Type.Blank:
- // Nothing to do.
- parts = FormattedParts.CreateBlankLine();
- break;
- case Line.Type.OrgDirective:
- case Line.Type.RegWidthDirective:
- case Line.Type.LongComment:
- case Line.Type.Note:
- // should have been done already
- default:
- Debug.Assert(false);
- parts = FormattedParts.Create("x", "x", "x", "x", "x", "x", "x", "x",
- "x", "x");
- break;
- }
- line.Parts = parts;
- }
- return line.Parts;
- }
-
- ///
- /// Returns a string with the concatenation of the searchable portions of the line.
- /// Different sections are separated with an unlikely unicode character. The goal
- /// is to have a single string per line that can be searched quickly, without having
- /// adjacent fields spill into each other.
- ///
- /// Line index.
- /// Formatted line contents.
- public string GetSearchString(int index) {
- Line line = mLineList[index];
- if (line.SearchString == null) {
- const char sep = '\u203b'; // REFERENCE MARK
-
- FormattedParts parts = GetFormattedParts(index);
- StringBuilder sb = new StringBuilder();
- // Some parts may be null, e.g. for long comments. Append() can deal.
- sb.Append(parts.Label);
- sb.Append(sep);
- sb.Append(parts.Opcode);
- sb.Append(sep);
- sb.Append(parts.Operand);
- sb.Append(sep);
- sb.Append(parts.Comment);
- line.SearchString = sb.ToString();
- }
- return line.SearchString;
- }
-
- ///
- /// Finds the first line entry that encompasses the specified offset.
- ///
- /// Offset to search for. Negative values are allowed.
- /// Line list index, or -1 if not found.
- private static int FindLineByOffset(List lineList, int offset) {
- if (lineList.Count == 0) {
- return -1;
- }
-
- int low = 0;
- int high = lineList.Count - 1;
- int mid = -1;
- bool found = false;
- while (low <= high) {
- mid = (low + high) / 2;
- Line line = lineList[mid];
-
- if (line.Contains(offset)) {
- // found a match
- found = true;
- break;
- } else if (line.FileOffset > offset) {
- // too big, move the high end in
- high = mid - 1;
- } else if (line.FileOffset < offset) {
- // too small, move the low end in
- low = mid + 1;
- } else {
- // WTF
- throw new Exception("Bad binary search");
- }
- }
-
- if (!found) {
- return -1;
- }
-
- // We found *a* matching line. Seek backward to find the *first* matching line.
- while (mid > 0) {
- Line upLine = lineList[mid - 1];
- if (upLine.Contains(offset)) {
- mid--;
- } else {
- break;
- }
- }
-
- return mid;
- }
-
- ///
- /// Finds the first line entry that encompasses the specified offset.
- ///
- /// Offset to search for.
- /// Line list index, or -1 if not found.
- public int FindLineIndexByOffset(int offset) {
- return FindLineByOffset(mLineList, offset);
- }
-
- ///
- /// Finds the code or data line entry that encompasses the specified offset.
- ///
- /// Offset to search for.
- /// Line list index, or -1 if not found.
- public int FindCodeDataIndexByOffset(int offset) {
- if (offset < 0) {
- // Header offset. No code or data here.
- return -1;
- }
- int index = FindLineByOffset(mLineList, offset);
- if (index < 0) {
- return -1;
- }
- while (mLineList[index].LineType != Line.Type.Code &&
- mLineList[index].LineType != Line.Type.Data) {
- index++;
- }
- return index;
- }
-
- ///
- /// Generates Lines for the entire project.
- ///
- public void GenerateAll() {
- mLineList.Clear();
- GenerateHeaderLines(mProject, mFormatter, mPseudoOpNames, mLineList);
- GenerateLineList(mProject, mFormatter, mPseudoOpNames,
- 0, mProject.FileData.Length - 1, mLineList);
-
- Debug.Assert(ValidateLineList(), "Display list failed validation");
- }
-
- ///
- /// Generates a list of Lines for the specified range of offsets, replacing
- /// existing values.
- ///
- /// First offset. Must be the start of an instruction
- /// or data area.
- /// End offset (inclusive).
- public void GenerateRange(int startOffset, int endOffset) {
- if (startOffset < 0) {
- ClearHeaderLines();
- GenerateHeaderLines(mProject, mFormatter, mPseudoOpNames, mLineList);
- if (endOffset < 0) {
- // nothing else to do
- return;
- }
- // do the rest
- startOffset = 0;
- }
- Debug.Assert(startOffset >= 0);
- Debug.Assert(endOffset < mProject.FileData.Length);
- Debug.Assert(endOffset >= startOffset);
- //Debug.WriteLine("DL gen range [" + startOffset + "," + endOffset + "]");
-
- // Find the start index. The start offset should always appear at the
- // start of a Line because it comes from item selection.
- int startIndex = FindLineByOffset(mLineList, startOffset);
- if (startIndex < 0) {
- Debug.Assert(false, "Unable to find startOffset " + startOffset);
- GenerateAll();
- return;
- }
- // Find the end index. The end offset can be part of a multi-line data item, like
- // a long string. Find the first Line that starts at an offset larger than endOffset.
- int endIndex;
- if (startOffset == endOffset) {
- // Simple optimization for single-offset groups.
- endIndex = startIndex;
- } else {
- endIndex = FindLineByOffset(mLineList, endOffset);
- }
- if (endIndex < 0) {
- Debug.Assert(false, "Unable to find endOffset " + endOffset);
- GenerateAll();
- return;
- }
- // There may be more than one line involved, so we need to scan forward.
- for (endIndex++; endIndex < mLineList.Count; endIndex++) {
- if (mLineList[endIndex].FileOffset > endOffset) {
- endIndex--;
- break;
- }
- }
- if (endIndex == mLineList.Count) {
- // whoops, loop ended before we had a chance to decrement
- endIndex = mLineList.Count - 1;
- }
- Debug.WriteLine("GenerateRange: offset [+" + startOffset.ToString("x6") + ",+" +
- endOffset.ToString("x6") +
- "] maps to index [" + startIndex + "," + endIndex + "]");
- Debug.Assert(endIndex >= startIndex);
-
- // Create temporary list to hold new lines. Set the initial capacity to
- // the previous size, on the assumption that it won't change much.
- List newLines = new List(endIndex - startIndex + 1);
- GenerateLineList(mProject, mFormatter, mPseudoOpNames, startOffset, endOffset, newLines);
-
- // Out with the old, in with the new.
- mLineList.RemoveRange(startIndex, endIndex - startIndex + 1);
- mLineList.InsertRange(startIndex, newLines);
-
- Debug.Assert(ValidateLineList(), "Display list failed validation");
- }
-
- ///
- /// Validates the line list, confirming that every offset is represented exactly once.
- ///
- /// True if all is well.
- private bool ValidateLineList() {
- int expectedOffset = 0;
- int lastOffset = Int32.MinValue;
- foreach (Line line in mLineList) {
- // Header lines aren't guaranteed to be sequential and don't have a span.
- // They are expected to be in sorted order, and to be unique (with the
- // notable exception of the header comment, which is multi-line).
- if (line.FileOffset < 0) {
- if (line.FileOffset < lastOffset || (line.LineType != Line.Type.LongComment &&
- line.FileOffset == lastOffset)) {
- Debug.WriteLine("Header offsets went backward: cur=" +
- line.FileOffset + " last=" + lastOffset);
- return false;
- }
- lastOffset = line.FileOffset;
- continue;
- }
-
- // Blank lines and comments can appear before or after code/data. They
- // must have the offset of the associated line, and a span of zero.
- if (line.FileOffset != expectedOffset && line.FileOffset != lastOffset) {
- Debug.WriteLine("ValidateLineList: bad offset " + line.FileOffset +
- " (last=" + lastOffset + ", expected next=" + expectedOffset + ")");
- return false;
- }
-
- if (line.SubLineIndex != 0) {
- // In the middle of a multi-line thing, don't advance last/expected.
- Debug.Assert(line.FileOffset == lastOffset);
- } else {
- lastOffset = expectedOffset;
- expectedOffset += line.OffsetSpan;
- }
- }
-
- if (expectedOffset != mProject.FileData.Length) {
- Debug.WriteLine("ValidateLineList: did not cover entire file: last offset " +
- expectedOffset + ", file has " + mProject.FileData.Length);
- return false;
- }
-
- return true;
- }
-
- ///
- /// Removes all header lines from the display list.
- ///
- private void ClearHeaderLines() {
- // Find the first non-header item.
- int endIndex = FindLineByOffset(mLineList, 0);
- if (endIndex == 0) {
- // no header lines present
- Debug.WriteLine("No header lines found");
- return;
- }
- Debug.WriteLine("Removing " + endIndex + " header lines");
- mLineList.RemoveRange(0, endIndex);
- }
-
- ///
- /// Generates a synthetic offset for the FileOffset field from an index value. The
- /// index arg is the index of an entry in the DisasmProject.ActiveDefSymbolList.
- /// (The exact algorithm isn't too important, as these offsets are not stored in the
- /// project file.)
- ///
- private static int DefSymOffsetFromIndex(int index) {
- Debug.Assert(index >= 0 && index < (1 << 24));
- return index - (1 << 24);
- }
-
- ///
- /// Returns the DisasmProject.ActiveDefSymbolList index for an EQU line with
- /// the specified file offset.
- ///
- public static int DefSymIndexFromOffset(int offset) {
- Debug.Assert(offset < 0);
- return offset + (1 << 24);
- }
-
- ///
- /// Generates the header lines (header comment, EQU directives), and inserts them at
- /// the top of the list.
- ///
- /// This does not currently do incremental generation. Call ClearHeaderLines() before
- /// calling here if you're not starting with an empty list.
- ///
- /// Project reference.
- /// Output formatter.
- /// Pseudo-op names.
- /// List to add output lines to.
- private static void GenerateHeaderLines(DisasmProject proj, Formatter formatter,
- PseudoOp.PseudoOpNames opNames, List fullLines) {
- List tmpLines = new List();
- Line line;
- FormattedParts parts;
-
- // Check for header comment.
- if (proj.LongComments.TryGetValue(Line.HEADER_COMMENT_OFFSET,
- out MultiLineComment headerComment)) {
- List formatted = headerComment.FormatText(formatter, string.Empty);
- StringListToLines(formatted, Line.HEADER_COMMENT_OFFSET, Line.Type.LongComment,
- Color.FromArgb(0, 0, 0, 0), tmpLines);
- }
-
- // Format symbols.
- int index = 0;
- foreach (DefSymbol defSym in proj.ActiveDefSymbolList) {
- line = new Line(DefSymOffsetFromIndex(index), 0, Line.Type.EquDirective);
- // Use an operand length of 1 so things are shown as concisely as possible.
- string valueStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
- null, defSym.DataDescriptor, defSym.Value, 1,
- PseudoOp.FormatNumericOpFlags.None);
- string comment = formatter.FormatEolComment(defSym.Comment);
- parts = FormattedParts.CreateEquDirective(defSym.Label,
- formatter.FormatPseudoOp(opNames.EquDirective),
- valueStr, comment);
- line.Parts = parts;
- tmpLines.Add(line);
- index++;
- }
-
- if (proj.ActiveDefSymbolList.Count != 0) {
- // We had some EQUs, throw a blank line at the end.
- index++;
- line = new Line(DefSymOffsetFromIndex(index), 0, Line.Type.Blank);
- tmpLines.Add(line);
- }
-
- fullLines.InsertRange(0, tmpLines);
- }
-
- ///
- /// Generates lines for the specified range of file offsets.
- ///
- /// Does not generate formatted parts in most cases; that usually happens on demand.
- /// Complicated items, such as word-wrapped long comments, may be generated now
- /// and saved off.
- ///
- /// This still needs a formatter arg even when no text is rendered because some
- /// options, like maximum per-line operand length, might affect how many lines
- /// are generated.
- ///
- /// Project reference.
- /// Output formatter.
- /// Offset of first byte.
- /// Offset of last byte.
- /// List to add output lines to.
- private static void GenerateLineList(DisasmProject proj, Formatter formatter,
- PseudoOp.PseudoOpNames opNames, int startOffset, int endOffset, List lines) {
- //Debug.WriteLine("GenerateRange [+" + startOffset.ToString("x6") + ",+" +
- // endOffset.ToString("x6") + "]");
-
- Debug.Assert(startOffset >= 0);
- Debug.Assert(endOffset >= startOffset);
-
- // Find the previous status flags for M/X tracking.
- StatusFlags prevFlags = StatusFlags.AllIndeterminate;
- if (proj.CpuDef.HasEmuFlag) {
- for (int scanoff = startOffset - 1; scanoff >= 0; scanoff--) {
- Anattrib attr = proj.GetAnattrib(scanoff);
- if (attr.IsInstructionStart) {
- prevFlags = attr.StatusFlags;
- // Apply the same tweak here that we do to curFlags below.
- prevFlags.M = attr.StatusFlags.ShortM ? 1 : 0;
- prevFlags.X = attr.StatusFlags.ShortX ? 1 : 0;
- Debug.WriteLine("GenerateLineList startOff=+" +
- startOffset.ToString("x6") + " using initial flags from +" +
- scanoff.ToString("x6") + ": " + prevFlags);
- break;
- }
- }
- }
-
- // Configure the initial value of addBlank. The specific case we're handling is
- // a no-continue instruction (e.g. JMP) followed by an instruction with a label.
- // When we rename the label, we don't want the blank to disappear during the
- // partial-list generation.
- bool addBlank = false;
- if (startOffset > 0) {
- int baseOff = DataAnalysis.GetBaseOperandOffset(proj, startOffset - 1);
- if (proj.GetAnattrib(baseOff).DoesNotContinue) {
- addBlank = true;
- }
- }
-
- int offset = startOffset;
- while (offset <= endOffset) {
- Anattrib attr = proj.GetAnattrib(offset);
- if (attr.IsInstructionStart && offset > 0 &&
- proj.GetAnattrib(offset - 1).IsData) {
- // Transition from data to code. (Don't add blank line for inline data.)
- lines.Add(GenerateBlankLine(offset));
- } else if (addBlank) {
- // Previous instruction wanted to be followed by a blank line.
- lines.Add(GenerateBlankLine(offset));
- }
- addBlank = false;
-
- // Insert long comments and notes. These may span multiple display lines,
- // and require word-wrap, so it's easiest just to render them fully here.
- if (proj.Notes.TryGetValue(offset, out MultiLineComment noteData)) {
- List formatted = noteData.FormatText(formatter, "NOTE: ");
- StringListToLines(formatted, offset, Line.Type.Note,
- noteData.BackgroundColor, lines);
- }
- if (proj.LongComments.TryGetValue(offset, out MultiLineComment longComment)) {
- List formatted = longComment.FormatText(formatter, string.Empty);
- StringListToLines(formatted, offset, Line.Type.LongComment,
- longComment.BackgroundColor, lines);
- }
-
- if (attr.IsInstructionStart) {
- // Generate reg width directive, if necessary.
- if (proj.CpuDef.HasEmuFlag) {
- // Changing from "ambiguous but assumed short" to "definitively short"
- // merits a directive, notably at the start of the file. The tricky
- // part is that E=1 means definitively M=1 X=1. And maybe
- // indeterminate E also means that.
- //
- // We don't want to mess with Anattrib, but we do need to tell the
- // assembler something. So we tweak our local copy and propagate it.
- string operandStr = string.Empty;
- StatusFlags curFlags = attr.StatusFlags;
- curFlags.M = attr.StatusFlags.ShortM ? 1 : 0;
- curFlags.X = attr.StatusFlags.ShortX ? 1 : 0;
- if (curFlags.M != prevFlags.M) {
- operandStr = (curFlags.M == 0) ? "longm" : "shortm";
- }
-
- if (curFlags.X != prevFlags.X) {
- if (operandStr.Length > 0) {
- operandStr += ",";
- }
- operandStr += (curFlags.X == 0) ? "longx" : "shortx";
- }
-
- if (operandStr.Length > 0) {
- Line rwLine = new Line(offset, 0, Line.Type.RegWidthDirective);
- // FormatPseudoOp isn't quite right for the operand, but there
- // isn't anything more suitable, and there are only eight
- // possible values. Having the operand capitalization match the
- // pseudo-op's feels reasonable.
- rwLine.Parts = FormattedParts.CreateDirective(
- formatter.FormatPseudoOp(opNames.RegWidthDirective),
- formatter.FormatPseudoOp(operandStr));
- lines.Add(rwLine);
- }
- prevFlags = curFlags;
- }
-
- // Look for embedded instructions.
- int len;
- for (len = 1; len < attr.Length; len++) {
- if (proj.GetAnattrib(offset + len).IsInstructionStart) {
- break;
- }
- }
-
- // Create Line entry. Offset span only covers the instruction up to
- // the point where the embedded instruction starts.
- Line line = new Line(offset, len, Line.Type.Code);
- lines.Add(line);
-
- // Insert blank after an instruction that doesn't continue. Provides a
- // break in code, and before a data area.
- // TODO(maybe): Might also want to do this if the next offset is data,
- // to make things look nicer when code runs directly into data.
- //
- // We don't want to add it with the current line's offset. If we do that,
- // the binary search will get confused, because blank lines have a span
- // of zero. If the code is at offset 10 with length 3, and we search for
- // the byte at offset 11, then a blank line (with span=0) at offset 10 will
- // cause the binary search to assume that the target is farther down, when
- // it's actually one line up. We deal with this by setting a flag and
- // generating the blank line on the next trip through the loop.
- if (attr.DoesNotContinue) {
- addBlank = true;
- }
-
- offset += len;
- } else {
- Debug.Assert(attr.DataDescriptor != null);
- int numLines =
- PseudoOp.ComputeRequiredLineCount(formatter, attr.DataDescriptor);
- for (int i = 0; i < numLines; i++) {
- Line line = new Line(offset, attr.Length, Line.Type.Data, i);
- lines.Add(line);
- }
- offset += attr.Length;
- }
- }
-
- // See if there were any address shifts in this section. If so, insert an ORG
- // statement as the first entry for the offset. We're expecting to have very
- // few AddressMap entries (usually just one), so it's more efficient to process
- // them here and walk through the sub-list than it is to ping the address map
- // at every line.
- //
- // It should not be possible for an address map change to appear in the middle
- // of an instruction or data item.
- foreach (AddressMap.AddressMapEntry ent in proj.AddrMap) {
- if (ent.Offset < startOffset || ent.Offset > endOffset) {
- continue;
- }
- int index = FindLineByOffset(lines, ent.Offset);
- if (index < 0) {
- Debug.WriteLine("Couldn't find offset " + ent.Offset +
- " in range we just generated");
- Debug.Assert(false);
- continue;
- }
- if (lines[index].LineType == Line.Type.Blank) {
- index++;
- }
- Line topLine = lines[index];
- Line newLine = new Line(topLine.FileOffset, 0, Line.Type.OrgDirective);
- string addrStr = formatter.FormatHexValue(ent.Addr, 4);
- newLine.Parts = FormattedParts.CreateDirective(
- formatter.FormatPseudoOp(opNames.OrgDirective), addrStr);
- lines.Insert(index, newLine);
-
- // Prepend a blank line if the previous line wasn't already blank, and this
- // isn't the ORG at the start of the file. (This may temporarily do
- // double-spacing if we do a partial update, because we won't be able to
- // "see" the previous line. Harmless.)
- if (ent.Offset != 0 && index > 0 && lines[index-1].LineType != Line.Type.Blank) {
- Line blankLine = new Line(topLine.FileOffset, 0, Line.Type.Blank);
- lines.Insert(index, blankLine);
- }
- }
- }
-
- ///
- /// Generates a blank line entry.
- ///
- private static Line GenerateBlankLine(int offset) {
- return new Line(offset, 0, Line.Type.Blank);
- }
-
- ///
- /// Takes a list of strings and adds them to the Line list as long comments.
- ///
- ///
- ///
- ///
- ///
- ///
- private static void StringListToLines(List list, int offset, Line.Type lineType,
- Color color, List lines) {
- foreach (string str in list) {
- Line line = new Line(offset, 0, lineType);
- FormattedParts parts = FormattedParts.CreateLongComment(str);
- line.Parts = parts;
- line.BackgroundColor = color;
- lines.Add(line);
- }
- }
-
- private static FormattedParts GenerateInstructionLine(DisasmProject proj,
- Formatter formatter, int offset, int instrBytes, bool showCycleCounts) {
- Anattrib attr = proj.GetAnattrib(offset);
- byte[] data = proj.FileData;
-
- string offsetStr = formatter.FormatOffset24(offset);
-
- int addr = attr.Address;
- string addrStr = formatter.FormatAddress(addr, !proj.CpuDef.HasAddr16);
- string bytesStr = formatter.FormatBytes(data, offset, instrBytes);
- string flagsStr = attr.StatusFlags.ToString(proj.CpuDef.HasEmuFlag);
- string attrStr = attr.ToAttrString();
-
- string labelStr = string.Empty;
- if (attr.Symbol != null) {
- labelStr = attr.Symbol.Label;
- }
-
- OpDef op = proj.CpuDef.GetOpDef(data[offset]);
- int operand = op.GetOperand(data, offset, attr.StatusFlags);
- int instrLen = op.GetLength(attr.StatusFlags);
- OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None;
- if (op.IsWidthPotentiallyAmbiguous) {
- wdis = OpDef.GetWidthDisambiguation(instrLen, operand);
- }
-
- string opcodeStr = formatter.FormatOpcode(op, wdis);
- if (attr.Length != instrBytes) {
- // An instruction is embedded inside this one. Note that BRK is a two-byte
- // instruction, so don't freak out if you see it marked as embedded when a
- // $00 is followed by actual code. (But be a little freaked out that your
- // code is running into a BRK.)
- //opcodeStr = opcodeStr + " \u00bb"; // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
- //opcodeStr = opcodeStr + " \u23e9"; // BLACK RIGHT-POINTING DOUBLE TRIANGLE
- opcodeStr = opcodeStr + " \u25bc"; // BLACK DOWN-POINTING TRIANGLE
- }
-
- string formattedOperand = null;
- int operandLen = instrLen - 1;
- PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.None;
-
- // Tweak branch instructions. We want to show the absolute address rather
- // than the relative offset (which happens with the OperandAddress assignment
- // below), and 1-byte branches should always appear as a 4-byte hex value.
- if (op.AddrMode == OpDef.AddressMode.PCRel) {
- Debug.Assert(attr.OperandAddress >= 0);
- operandLen = 2;
- opFlags = PseudoOp.FormatNumericOpFlags.IsPcRel;
- } else if (op.AddrMode == OpDef.AddressMode.PCRelLong ||
- op.AddrMode == OpDef.AddressMode.StackPCRelLong) {
- opFlags = PseudoOp.FormatNumericOpFlags.IsPcRel;
- } else if (op.AddrMode == OpDef.AddressMode.Imm ||
- op.AddrMode == OpDef.AddressMode.ImmLongA ||
- op.AddrMode == OpDef.AddressMode.ImmLongXY) {
- opFlags = PseudoOp.FormatNumericOpFlags.HasHashPrefix;
- }
-
- // Use the OperandAddress when available. This is important for relative branch
- // instructions and PER, where we want to show the target address rather than the
- // operand value.
- int operandForSymbol = operand;
- if (attr.OperandAddress >= 0) {
- operandForSymbol = attr.OperandAddress;
- }
-
- // Check Length to watch for bogus descriptors. ApplyFormatDescriptors() should
- // have discarded anything appropriate, so we might be able to eliminate this test.
- if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length) {
- Debug.Assert(operandLen > 0);
-
- // Format operand as directed.
- if (op.AddrMode == OpDef.AddressMode.BlockMove) {
- // Special handling for the double-operand block move.
- string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
- null, attr.DataDescriptor, operand >> 8, 1,
- PseudoOp.FormatNumericOpFlags.None);
- string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
- null, attr.DataDescriptor, operand & 0xff, 1,
- PseudoOp.FormatNumericOpFlags.None);
- formattedOperand = opstr1 + "," + opstr2;
- } else {
- formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
- null, attr.DataDescriptor, operandForSymbol, operandLen, opFlags);
- }
- } else {
- // Show operand value in hex.
- if (op.AddrMode == OpDef.AddressMode.BlockMove) {
- formattedOperand = formatter.FormatHexValue(operand >> 8, 2) + "," +
- formatter.FormatHexValue(operand & 0xff, 2);
- } else {
- if (operandLen == 2) {
- // This is necessary for 16-bit operands, like "LDA abs" and "PEA val",
- // when outside bank zero. The bank is included in the operand address,
- // but we don't want to show it here.
- operandForSymbol &= 0xffff;
- }
- formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2);
- }
- }
- string operandStr = formatter.FormatOperand(op, formattedOperand, wdis);
-
- string eolComment = proj.Comments[offset];
- if (showCycleCounts) {
- bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00);
- int cycles = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken,
- branchCross);
- if (cycles > 0) {
- eolComment = cycles.ToString() + " " + eolComment;
- } else {
- eolComment = (-cycles).ToString() + "+ " + eolComment;
- }
- }
- string commentStr = formatter.FormatEolComment(eolComment);
-
- string debugStr = string.Empty;
- //debugStr = "opOff=" +
- // (attr.OperandOffset < 0 ? "-" : "+" + attr.OperandOffset.ToString("x6"));
-
- FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
- flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr, debugStr);
- return parts;
- }
-
- private static FormattedParts GenerateDataLine(DisasmProject proj, Formatter formatter,
- PseudoOp.PseudoOpNames opNames, int offset, int subLineIndex) {
- Anattrib attr = proj.GetAnattrib(offset);
- byte[] data = proj.FileData;
-
- string offsetStr, addrStr, bytesStr, flagsStr, attrStr, labelStr, opcodeStr,
- operandStr, commentStr, debugStr;
- offsetStr = addrStr = bytesStr = flagsStr = attrStr = labelStr = opcodeStr =
- operandStr = commentStr = debugStr = string.Empty;
-
- PseudoOp.PseudoOut pout = PseudoOp.FormatDataOp(formatter, opNames, proj.SymbolTable,
- null, attr.DataDescriptor, proj.FileData, offset, subLineIndex);
- if (subLineIndex == 0) {
- offsetStr = formatter.FormatOffset24(offset);
-
- addrStr = formatter.FormatAddress(attr.Address, !proj.CpuDef.HasAddr16);
- if (attr.Symbol != null) {
- labelStr = attr.Symbol.Label;
- }
-
- bytesStr = formatter.FormatBytes(data, offset, attr.Length);
- attrStr = attr.ToAttrString();
-
- opcodeStr = formatter.FormatPseudoOp(pout.Opcode);
- } else {
- opcodeStr = " +";
- }
-
- operandStr = pout.Operand;
-
- if (subLineIndex == 0) {
- commentStr = formatter.FormatEolComment(proj.Comments[offset]);
-
- //debugStr = "opOff=" +
- // (attr.OperandOffset < 0 ? "-" : "+" + attr.OperandOffset.ToString("x6"));
- }
-
- FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
- flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr, debugStr);
- return parts;
}
}
}
diff --git a/SourceGenWPF/DisplayListGen.cs b/SourceGenWPF/DisplayListGen.cs
new file mode 100644
index 0000000..ee46d02
--- /dev/null
+++ b/SourceGenWPF/DisplayListGen.cs
@@ -0,0 +1,1223 @@
+/*
+ * Copyright 2019 faddenSoft
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Windows.Media;
+using System.Text;
+
+using Asm65;
+
+namespace SourceGenWPF {
+ ///
+ /// Converts file data and Anattrib contents into a series of strings and format metadata.
+ ///
+ public class DisplayListGen {
+ ///
+ /// List of display lines.
+ ///
+ private List mLineList;
+
+ ///
+ /// Project that contains the data we're formatting, notably the FileData and
+ /// Anattribs arrays.
+ ///
+ private DisasmProject mProject;
+
+ ///
+ /// Code/data formatter.
+ ///
+ private Formatter mFormatter;
+
+ ///
+ /// If set, prepend cycle counts to EOL comments.
+ ///
+ private bool mShowCycleCounts;
+
+ ///
+ /// Names for pseudo-ops.
+ ///
+ private PseudoOp.PseudoOpNames mPseudoOpNames;
+
+
+ ///
+ /// Holds a collection of formatted strings. Instances are immutable.
+ ///
+ public class FormattedParts {
+ public string Offset { get; private set; }
+ public string Addr { get; private set; }
+ public string Bytes { get; private set; }
+ public string Flags { get; private set; }
+ public string Attr { get; private set; }
+ public string Label { get; private set; }
+ public string Opcode { get; private set; }
+ public string Operand { get; private set; }
+ public string Comment { get; private set; }
+
+ // Use factory methods.
+ private FormattedParts() { }
+
+ public static FormattedParts Create(string offset, string addr, string bytes,
+ string flags, string attr, string label, string opcode, string operand,
+ string comment, string debug) {
+ FormattedParts parts = new FormattedParts();
+ parts.Offset = offset;
+ parts.Addr = addr;
+ parts.Bytes = bytes;
+ parts.Flags = flags;
+ parts.Attr = attr;
+ parts.Label = label;
+ parts.Opcode = opcode;
+ parts.Operand = operand;
+ parts.Comment = comment;
+ return parts;
+ }
+
+ public static FormattedParts CreateBlankLine() {
+ FormattedParts parts = new FormattedParts();
+ return parts;
+ }
+
+ public static FormattedParts CreateLongComment(string comment) {
+ FormattedParts parts = new FormattedParts();
+ parts.Comment = comment;
+ return parts;
+ }
+
+ public static FormattedParts CreateDirective(string opstr, string addrStr) {
+ FormattedParts parts = new FormattedParts();
+ parts.Opcode = opstr;
+ parts.Operand = addrStr;
+ return parts;
+ }
+
+ public static FormattedParts CreateEquDirective(string label, string opstr,
+ string addrStr, string comment) {
+ FormattedParts parts = new FormattedParts();
+ parts.Label = label;
+ parts.Opcode = opstr;
+ parts.Operand = addrStr;
+ parts.Comment = comment;
+ return parts;
+ }
+ }
+
+ ///
+ /// One of these per line of output in the display. It should be possible to draw
+ /// all of the output without needing to refer back to the project data. (Currently
+ /// making an exception for some selection-dependent field highlighting.)
+ ///
+ /// Base fields are immutable, but the Parts property is set after creation.
+ ///
+ public class Line {
+ // Extremely-negative offset value ensures it's at the very top.
+ public const int HEADER_COMMENT_OFFSET = int.MinValue + 1;
+
+ [FlagsAttribute]
+ public enum Type {
+ Unclassified = 0,
+
+ // Primary functional items.
+ Code = 1 << 0,
+ Data = 1 << 1, // includes inline data
+ CodeOrData = (Code | Data),
+
+ // Decorative items, added by user or formatter.
+ LongComment = 1 << 2,
+ Note = 1 << 3,
+ Blank = 1 << 4,
+
+ // Assembler directives.
+ OrgDirective = 1 << 5,
+ EquDirective = 1 << 6,
+ RegWidthDirective = 1 << 7,
+ }
+
+ ///
+ /// Line type.
+ ///
+ public Type LineType { get; private set; }
+
+ ///
+ /// Numeric offset value. Used to map a line item to the Anattrib. Note this is
+ /// set for all lines, and is the same for all lines in a multi-line sequence,
+ /// e.g. every line in a long comment has the file offset with which it is associated.
+ ///
+ public int FileOffset { get; private set; }
+
+ ///
+ /// Number of offsets this line covers. Will be > 0 for code and data, zero for
+ /// everything else. The same value is used for all lines in a multi-line sequence.
+ ///
+ public int OffsetSpan { get; private set; }
+
+ ///
+ /// For multi-line entries, this indicates which line is represented. For
+ /// single-line entries, this will be zero.
+ ///
+ public int SubLineIndex { get; private set; }
+
+ ///
+ /// Strings for display. Creation may be deferred. Use the DisplayList
+ /// GetFormattedParts() method to access this property.
+ ///
+ public FormattedParts Parts { get; set; }
+
+ ///
+ /// Background color, used for notes.
+ ///
+ public Color BackgroundColor { get; set; }
+
+ ///
+ /// String for searching. May be created on demand when the Line is first searched.
+ ///
+ public string SearchString { get; set; }
+
+
+ public Line(int offset, int span, Type type) : this(offset, span, type, 0) { }
+
+ public Line(int offset, int span, Type type, int subLineIndex) {
+ FileOffset = offset;
+ OffsetSpan = span;
+ LineType = type;
+ SubLineIndex = subLineIndex;
+ }
+
+ ///
+ /// True if this line is code or data.
+ ///
+ public bool IsCodeOrData {
+ get {
+ return LineType == Type.Code || LineType == Type.Data;
+ }
+ }
+
+ ///
+ /// Returns true if the specified offset is represented by this line. There
+ /// will be only one code/data line for a given offset, but there may be
+ /// multiple others (comments, notes, etc.) associated with it.
+ ///
+ ///
+ ///
+ public bool Contains(int offset) {
+ // Note OffsetSpan can be zero.
+ return (offset == FileOffset ||
+ (offset >= FileOffset && offset < FileOffset + OffsetSpan));
+ }
+
+ public override string ToString() {
+ return "Line type=" + LineType + " off=+" + FileOffset.ToString("x6") +
+ " span=" + OffsetSpan;
+ }
+ }
+
+ ///
+ /// Captures the set of selected lines. Lines are identified by offset and type.
+ ///
+ /// The idea is to save the selection, rebuild the list -- potentially moving
+ /// stuff around -- and then rebuild the selection bitmap by finding matching
+ /// items.
+ ///
+ /// We don't try to identify parts of multi-line things. If you've selected
+ /// part of a multi-line string, then when we restore the selection you'll have
+ /// the entire string selected. For the operations that are possible across
+ /// multiple offsets, this seems like reasonable behavior.
+ ///
+ /// We can't precisely restore the selection in terms of which file offsets
+ /// are selected. If you select one byte and apply a code hint, we'll restore
+ /// the selection to a line with 1-4 bytes. This gets weird if you hit "undo",
+ /// as you will then have 1-4 bytes selected rather than the original one. It
+ /// might be better to just clear the selection on "undo".
+ ///
+ public class SavedSelection {
+ private class Tag {
+ public int mOffset;
+ public int mSpan;
+ public Line.Type mTypes;
+
+ public Tag(int offset, int span, Line.Type lineType) {
+ //Debug.Assert(offset >= 0);
+ Debug.Assert(span >= 0);
+ mOffset = offset;
+ mSpan = (span == 0) ? 1 : span;
+ mTypes = lineType;
+ }
+ }
+
+ private List mSelectionTags = new List();
+
+ ///
+ /// This is a place to save the file offset associated with the ListView's
+ /// TopItem, so we can position the list appropriately.
+ ///
+ private int mTopOffset;
+
+ // Use Generate().
+ private SavedSelection() { }
+
+ ///
+ /// Creates a new SavedSelection object, generating a list of tags from the
+ /// lines that are currently selected.
+ ///
+ /// If nothing is selected, SavedSelection will have no members.
+ ///
+ /// Display list, with list of Lines.
+ /// Bit vector specifying which lines are selected.
+ /// New SavedSelection object.
+ public static SavedSelection Generate(DisplayListGen dl, VirtualListViewSelection sel,
+ int topOffset) {
+ SavedSelection savedSel = new SavedSelection();
+ //Debug.Assert(topOffset >= 0);
+ savedSel.mTopOffset = topOffset;
+
+ List lineList = dl.mLineList;
+ Debug.Assert(lineList.Count == sel.Length);
+
+ // Generate tags, which are a combination of the offset, span, and a merge
+ // of types of all the lines associated with that offset.
+ //
+ // We may want to consider some sort of optimization for a "select all"
+ // operation, although there aren't many changes you can make after selecting
+ // all lines in a very large file.
+ Tag tag = null;
+ int curOffset = -1;
+ for (int i = 0; i < lineList.Count; i++) {
+ if (!sel[i]) {
+ continue;
+ }
+ Line line = lineList[i];
+ // Code hinting can transform code to data and vice-versa, so we
+ // want the tag to reflect the fact that both could exist.
+ Line.Type lineType = line.LineType;
+ if (lineType == Line.Type.Code || lineType == Line.Type.Data) {
+ lineType = Line.Type.CodeOrData;
+ }
+ if (line.FileOffset != curOffset) {
+ // advanced to new offset, flush previous
+ if (tag != null) {
+ savedSel.mSelectionTags.Add(tag);
+ }
+ curOffset = line.FileOffset;
+
+ tag = new Tag(line.FileOffset, line.OffsetSpan, lineType);
+ } else {
+ // another item at same offset
+ tag.mSpan = Math.Max(tag.mSpan, line.OffsetSpan);
+ tag.mTypes |= lineType;
+ }
+ }
+ if (curOffset == -1) {
+ // It's hard to cause an action that requires save/restore when you don't
+ // have anything selected in the ListView. However, this can happen if
+ // you do a sequence like:
+ // - Open a file that starts with a JMP followed by data.
+ // - Click on the blank line below the code, which has the code's offset,
+ // and select "remove hint". This causes the blank line to vanish,
+ // so the Restore() won't select anything.
+ // - Click "undo".
+ Debug.WriteLine("NOTE: no selection found");
+ } else {
+ // Add the in-progress tag to the list.
+ savedSel.mSelectionTags.Add(tag);
+ }
+
+ return savedSel;
+ }
+
+ ///
+ /// Creates a selection set by identifying the set of lines in the display list
+ /// that correspond to items in the SavedSelection tag list.
+ ///
+ /// Display list, with list of Lines.
+ /// Set of selected lines.
+ public VirtualListViewSelection Restore(DisplayListGen dl, out int topIndex) {
+ List lineList = dl.mLineList;
+ VirtualListViewSelection sel = new VirtualListViewSelection(lineList.Count);
+
+ topIndex = -1;
+
+ // Walk through the tag list, which is ordered by ascending offset, and
+ // through the display list, which is similarly ordered.
+ int tagIndex = 0;
+ int lineIndex = 0;
+ while (tagIndex < mSelectionTags.Count && lineIndex < lineList.Count) {
+ Tag tag = mSelectionTags[tagIndex];
+ int lineOffset = lineList[lineIndex].FileOffset;
+
+ // If a line encompassing this offset was at the top of the ListView
+ // control before, use this line's index as the top.
+ if (topIndex < 0 && lineList[lineIndex].Contains(mTopOffset)) {
+ topIndex = lineIndex;
+ }
+
+ if (lineOffset >= tag.mOffset && lineOffset < tag.mOffset + tag.mSpan) {
+ // Intersection. If the line type matches, add it to the set.
+ if ((tag.mTypes & lineList[lineIndex].LineType) != 0) {
+ sel[lineIndex] = true;
+ }
+
+ // Advance to the next line entry.
+ lineIndex++;
+ } else if (tag.mOffset < lineOffset) {
+ // advance tag
+ tagIndex++;
+ } else {
+ Debug.Assert(tag.mOffset > lineOffset);
+ lineIndex++;
+ }
+ }
+
+ // Continue search for topIndex, if necessary.
+ while (topIndex < 0 && lineIndex < lineList.Count) {
+ if (lineList[lineIndex].Contains(mTopOffset)) {
+ topIndex = lineIndex;
+ break;
+ }
+ lineIndex++;
+ }
+ Debug.WriteLine("TopOffset +" + mTopOffset.ToString("x6") +
+ " --> index " + topIndex);
+ if (topIndex < 0) {
+ // This can happen if you delete the header comment while scrolled
+ // to the top of the list.
+ topIndex = 0;
+ }
+ return sel;
+ }
+
+ public void DebugDump() {
+ Debug.WriteLine("Selection (" + mSelectionTags.Count + " offsets):");
+ foreach (Tag tag in mSelectionTags) {
+ Debug.WriteLine(" +" + tag.mOffset.ToString("x6") + "/" +
+ tag.mSpan + ": " + tag.mTypes);
+ }
+ }
+ }
+
+
+
+ ///
+ /// Constructor.
+ ///
+ /// Project object.
+ /// Formatter object.
+ public DisplayListGen(DisasmProject proj, Formatter formatter,
+ PseudoOp.PseudoOpNames opNames) {
+ mProject = proj;
+ mFormatter = formatter;
+ mPseudoOpNames = opNames;
+
+ mLineList = new List();
+ mShowCycleCounts = AppSettings.Global.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS,
+ false);
+ }
+
+ ///
+ /// Changes the Formatter object. Clears the display list, instigating a full re-render.
+ ///
+ /// Formatter object.
+ public void SetFormatter(Formatter formatter) {
+ mFormatter = formatter;
+ mLineList.Clear();
+
+ // We probably just changed settings, so update this as well.
+ mShowCycleCounts = AppSettings.Global.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS,
+ false);
+ }
+
+ ///
+ /// Changes the pseudo-op name object. Clears the display list, instigating a
+ /// full re-render.
+ ///
+ /// Pseudo-op names.
+ public void SetPseudoOpNames(PseudoOp.PseudoOpNames opNames) {
+ mPseudoOpNames = opNames;
+ mLineList.Clear();
+ }
+
+ ///
+ /// Number of lines in the list.
+ ///
+ public int Count { get { return mLineList.Count; } }
+
+ ///
+ /// Retrieves the Nth element.
+ ///
+ public Line this[int key] {
+ get {
+ return mLineList[key];
+ }
+ }
+
+ ///
+ /// Returns the Line's FormattedParts object, generating it first if necessary.
+ ///
+ /// Object with formatted strings.
+ public FormattedParts GetFormattedParts(int index) {
+ Line line = mLineList[index];
+ if (line.Parts == null) {
+ FormattedParts parts;
+ switch (line.LineType) {
+ case Line.Type.Code:
+ parts = GenerateInstructionLine(mProject, mFormatter,
+ line.FileOffset, line.OffsetSpan, mShowCycleCounts);
+ break;
+ case Line.Type.Data:
+ parts = GenerateDataLine(mProject, mFormatter, mPseudoOpNames,
+ line.FileOffset, line.SubLineIndex);
+ break;
+ case Line.Type.Blank:
+ // Nothing to do.
+ parts = FormattedParts.CreateBlankLine();
+ break;
+ case Line.Type.OrgDirective:
+ case Line.Type.RegWidthDirective:
+ case Line.Type.LongComment:
+ case Line.Type.Note:
+ // should have been done already
+ default:
+ Debug.Assert(false);
+ parts = FormattedParts.Create("x", "x", "x", "x", "x", "x", "x", "x",
+ "x", "x");
+ break;
+ }
+ line.Parts = parts;
+ }
+ return line.Parts;
+ }
+
+ ///
+ /// Returns a string with the concatenation of the searchable portions of the line.
+ /// Different sections are separated with an unlikely unicode character. The goal
+ /// is to have a single string per line that can be searched quickly, without having
+ /// adjacent fields spill into each other.
+ ///
+ /// Line index.
+ /// Formatted line contents.
+ public string GetSearchString(int index) {
+ Line line = mLineList[index];
+ if (line.SearchString == null) {
+ const char sep = '\u203b'; // REFERENCE MARK
+
+ FormattedParts parts = GetFormattedParts(index);
+ StringBuilder sb = new StringBuilder();
+ // Some parts may be null, e.g. for long comments. Append() can deal.
+ sb.Append(parts.Label);
+ sb.Append(sep);
+ sb.Append(parts.Opcode);
+ sb.Append(sep);
+ sb.Append(parts.Operand);
+ sb.Append(sep);
+ sb.Append(parts.Comment);
+ line.SearchString = sb.ToString();
+ }
+ return line.SearchString;
+ }
+
+ ///
+ /// Finds the first line entry that encompasses the specified offset.
+ ///
+ /// Offset to search for. Negative values are allowed.
+ /// Line list index, or -1 if not found.
+ private static int FindLineByOffset(List lineList, int offset) {
+ if (lineList.Count == 0) {
+ return -1;
+ }
+
+ int low = 0;
+ int high = lineList.Count - 1;
+ int mid = -1;
+ bool found = false;
+ while (low <= high) {
+ mid = (low + high) / 2;
+ Line line = lineList[mid];
+
+ if (line.Contains(offset)) {
+ // found a match
+ found = true;
+ break;
+ } else if (line.FileOffset > offset) {
+ // too big, move the high end in
+ high = mid - 1;
+ } else if (line.FileOffset < offset) {
+ // too small, move the low end in
+ low = mid + 1;
+ } else {
+ // WTF
+ throw new Exception("Bad binary search");
+ }
+ }
+
+ if (!found) {
+ return -1;
+ }
+
+ // We found *a* matching line. Seek backward to find the *first* matching line.
+ while (mid > 0) {
+ Line upLine = lineList[mid - 1];
+ if (upLine.Contains(offset)) {
+ mid--;
+ } else {
+ break;
+ }
+ }
+
+ return mid;
+ }
+
+ ///
+ /// Finds the first line entry that encompasses the specified offset.
+ ///
+ /// Offset to search for.
+ /// Line list index, or -1 if not found.
+ public int FindLineIndexByOffset(int offset) {
+ return FindLineByOffset(mLineList, offset);
+ }
+
+ ///
+ /// Finds the code or data line entry that encompasses the specified offset.
+ ///
+ /// Offset to search for.
+ /// Line list index, or -1 if not found.
+ public int FindCodeDataIndexByOffset(int offset) {
+ if (offset < 0) {
+ // Header offset. No code or data here.
+ return -1;
+ }
+ int index = FindLineByOffset(mLineList, offset);
+ if (index < 0) {
+ return -1;
+ }
+ while (mLineList[index].LineType != Line.Type.Code &&
+ mLineList[index].LineType != Line.Type.Data) {
+ index++;
+ }
+ return index;
+ }
+
+ ///
+ /// Generates Lines for the entire project.
+ ///
+ public void GenerateAll() {
+ mLineList.Clear();
+ GenerateHeaderLines(mProject, mFormatter, mPseudoOpNames, mLineList);
+ GenerateLineList(mProject, mFormatter, mPseudoOpNames,
+ 0, mProject.FileData.Length - 1, mLineList);
+
+ Debug.Assert(ValidateLineList(), "Display list failed validation");
+ }
+
+ ///
+ /// Generates a list of Lines for the specified range of offsets, replacing
+ /// existing values.
+ ///
+ /// First offset. Must be the start of an instruction
+ /// or data area.
+ /// End offset (inclusive).
+ public void GenerateRange(int startOffset, int endOffset) {
+ if (startOffset < 0) {
+ ClearHeaderLines();
+ GenerateHeaderLines(mProject, mFormatter, mPseudoOpNames, mLineList);
+ if (endOffset < 0) {
+ // nothing else to do
+ return;
+ }
+ // do the rest
+ startOffset = 0;
+ }
+ Debug.Assert(startOffset >= 0);
+ Debug.Assert(endOffset < mProject.FileData.Length);
+ Debug.Assert(endOffset >= startOffset);
+ //Debug.WriteLine("DL gen range [" + startOffset + "," + endOffset + "]");
+
+ // Find the start index. The start offset should always appear at the
+ // start of a Line because it comes from item selection.
+ int startIndex = FindLineByOffset(mLineList, startOffset);
+ if (startIndex < 0) {
+ Debug.Assert(false, "Unable to find startOffset " + startOffset);
+ GenerateAll();
+ return;
+ }
+ // Find the end index. The end offset can be part of a multi-line data item, like
+ // a long string. Find the first Line that starts at an offset larger than endOffset.
+ int endIndex;
+ if (startOffset == endOffset) {
+ // Simple optimization for single-offset groups.
+ endIndex = startIndex;
+ } else {
+ endIndex = FindLineByOffset(mLineList, endOffset);
+ }
+ if (endIndex < 0) {
+ Debug.Assert(false, "Unable to find endOffset " + endOffset);
+ GenerateAll();
+ return;
+ }
+ // There may be more than one line involved, so we need to scan forward.
+ for (endIndex++; endIndex < mLineList.Count; endIndex++) {
+ if (mLineList[endIndex].FileOffset > endOffset) {
+ endIndex--;
+ break;
+ }
+ }
+ if (endIndex == mLineList.Count) {
+ // whoops, loop ended before we had a chance to decrement
+ endIndex = mLineList.Count - 1;
+ }
+ Debug.WriteLine("GenerateRange: offset [+" + startOffset.ToString("x6") + ",+" +
+ endOffset.ToString("x6") +
+ "] maps to index [" + startIndex + "," + endIndex + "]");
+ Debug.Assert(endIndex >= startIndex);
+
+ // Create temporary list to hold new lines. Set the initial capacity to
+ // the previous size, on the assumption that it won't change much.
+ List newLines = new List(endIndex - startIndex + 1);
+ GenerateLineList(mProject, mFormatter, mPseudoOpNames, startOffset, endOffset, newLines);
+
+ // Out with the old, in with the new.
+ mLineList.RemoveRange(startIndex, endIndex - startIndex + 1);
+ mLineList.InsertRange(startIndex, newLines);
+
+ Debug.Assert(ValidateLineList(), "Display list failed validation");
+ }
+
+ ///
+ /// Validates the line list, confirming that every offset is represented exactly once.
+ ///
+ /// True if all is well.
+ private bool ValidateLineList() {
+ int expectedOffset = 0;
+ int lastOffset = Int32.MinValue;
+ foreach (Line line in mLineList) {
+ // Header lines aren't guaranteed to be sequential and don't have a span.
+ // They are expected to be in sorted order, and to be unique (with the
+ // notable exception of the header comment, which is multi-line).
+ if (line.FileOffset < 0) {
+ if (line.FileOffset < lastOffset || (line.LineType != Line.Type.LongComment &&
+ line.FileOffset == lastOffset)) {
+ Debug.WriteLine("Header offsets went backward: cur=" +
+ line.FileOffset + " last=" + lastOffset);
+ return false;
+ }
+ lastOffset = line.FileOffset;
+ continue;
+ }
+
+ // Blank lines and comments can appear before or after code/data. They
+ // must have the offset of the associated line, and a span of zero.
+ if (line.FileOffset != expectedOffset && line.FileOffset != lastOffset) {
+ Debug.WriteLine("ValidateLineList: bad offset " + line.FileOffset +
+ " (last=" + lastOffset + ", expected next=" + expectedOffset + ")");
+ return false;
+ }
+
+ if (line.SubLineIndex != 0) {
+ // In the middle of a multi-line thing, don't advance last/expected.
+ Debug.Assert(line.FileOffset == lastOffset);
+ } else {
+ lastOffset = expectedOffset;
+ expectedOffset += line.OffsetSpan;
+ }
+ }
+
+ if (expectedOffset != mProject.FileData.Length) {
+ Debug.WriteLine("ValidateLineList: did not cover entire file: last offset " +
+ expectedOffset + ", file has " + mProject.FileData.Length);
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Removes all header lines from the display list.
+ ///
+ private void ClearHeaderLines() {
+ // Find the first non-header item.
+ int endIndex = FindLineByOffset(mLineList, 0);
+ if (endIndex == 0) {
+ // no header lines present
+ Debug.WriteLine("No header lines found");
+ return;
+ }
+ Debug.WriteLine("Removing " + endIndex + " header lines");
+ mLineList.RemoveRange(0, endIndex);
+ }
+
+ ///
+ /// Generates a synthetic offset for the FileOffset field from an index value. The
+ /// index arg is the index of an entry in the DisasmProject.ActiveDefSymbolList.
+ /// (The exact algorithm isn't too important, as these offsets are not stored in the
+ /// project file.)
+ ///
+ private static int DefSymOffsetFromIndex(int index) {
+ Debug.Assert(index >= 0 && index < (1 << 24));
+ return index - (1 << 24);
+ }
+
+ ///
+ /// Returns the DisasmProject.ActiveDefSymbolList index for an EQU line with
+ /// the specified file offset.
+ ///
+ public static int DefSymIndexFromOffset(int offset) {
+ Debug.Assert(offset < 0);
+ return offset + (1 << 24);
+ }
+
+ ///
+ /// Generates the header lines (header comment, EQU directives), and inserts them at
+ /// the top of the list.
+ ///
+ /// This does not currently do incremental generation. Call ClearHeaderLines() before
+ /// calling here if you're not starting with an empty list.
+ ///
+ /// Project reference.
+ /// Output formatter.
+ /// Pseudo-op names.
+ /// List to add output lines to.
+ private static void GenerateHeaderLines(DisasmProject proj, Formatter formatter,
+ PseudoOp.PseudoOpNames opNames, List fullLines) {
+ List tmpLines = new List();
+ Line line;
+ FormattedParts parts;
+
+ // Check for header comment.
+ if (proj.LongComments.TryGetValue(Line.HEADER_COMMENT_OFFSET,
+ out MultiLineComment headerComment)) {
+ List formatted = headerComment.FormatText(formatter, string.Empty);
+ StringListToLines(formatted, Line.HEADER_COMMENT_OFFSET, Line.Type.LongComment,
+ Color.FromArgb(0, 0, 0, 0), tmpLines);
+ }
+
+ // Format symbols.
+ int index = 0;
+ foreach (DefSymbol defSym in proj.ActiveDefSymbolList) {
+ line = new Line(DefSymOffsetFromIndex(index), 0, Line.Type.EquDirective);
+ // Use an operand length of 1 so things are shown as concisely as possible.
+ string valueStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
+ null, defSym.DataDescriptor, defSym.Value, 1,
+ PseudoOp.FormatNumericOpFlags.None);
+ string comment = formatter.FormatEolComment(defSym.Comment);
+ parts = FormattedParts.CreateEquDirective(defSym.Label,
+ formatter.FormatPseudoOp(opNames.EquDirective),
+ valueStr, comment);
+ line.Parts = parts;
+ tmpLines.Add(line);
+ index++;
+ }
+
+ if (proj.ActiveDefSymbolList.Count != 0) {
+ // We had some EQUs, throw a blank line at the end.
+ index++;
+ line = new Line(DefSymOffsetFromIndex(index), 0, Line.Type.Blank);
+ tmpLines.Add(line);
+ }
+
+ fullLines.InsertRange(0, tmpLines);
+ }
+
+ ///
+ /// Generates lines for the specified range of file offsets.
+ ///
+ /// Does not generate formatted parts in most cases; that usually happens on demand.
+ /// Complicated items, such as word-wrapped long comments, may be generated now
+ /// and saved off.
+ ///
+ /// This still needs a formatter arg even when no text is rendered because some
+ /// options, like maximum per-line operand length, might affect how many lines
+ /// are generated.
+ ///
+ /// Project reference.
+ /// Output formatter.
+ /// Offset of first byte.
+ /// Offset of last byte.
+ /// List to add output lines to.
+ private static void GenerateLineList(DisasmProject proj, Formatter formatter,
+ PseudoOp.PseudoOpNames opNames, int startOffset, int endOffset, List lines) {
+ //Debug.WriteLine("GenerateRange [+" + startOffset.ToString("x6") + ",+" +
+ // endOffset.ToString("x6") + "]");
+
+ Debug.Assert(startOffset >= 0);
+ Debug.Assert(endOffset >= startOffset);
+
+ // Find the previous status flags for M/X tracking.
+ StatusFlags prevFlags = StatusFlags.AllIndeterminate;
+ if (proj.CpuDef.HasEmuFlag) {
+ for (int scanoff = startOffset - 1; scanoff >= 0; scanoff--) {
+ Anattrib attr = proj.GetAnattrib(scanoff);
+ if (attr.IsInstructionStart) {
+ prevFlags = attr.StatusFlags;
+ // Apply the same tweak here that we do to curFlags below.
+ prevFlags.M = attr.StatusFlags.ShortM ? 1 : 0;
+ prevFlags.X = attr.StatusFlags.ShortX ? 1 : 0;
+ Debug.WriteLine("GenerateLineList startOff=+" +
+ startOffset.ToString("x6") + " using initial flags from +" +
+ scanoff.ToString("x6") + ": " + prevFlags);
+ break;
+ }
+ }
+ }
+
+ // Configure the initial value of addBlank. The specific case we're handling is
+ // a no-continue instruction (e.g. JMP) followed by an instruction with a label.
+ // When we rename the label, we don't want the blank to disappear during the
+ // partial-list generation.
+ bool addBlank = false;
+ if (startOffset > 0) {
+ int baseOff = DataAnalysis.GetBaseOperandOffset(proj, startOffset - 1);
+ if (proj.GetAnattrib(baseOff).DoesNotContinue) {
+ addBlank = true;
+ }
+ }
+
+ int offset = startOffset;
+ while (offset <= endOffset) {
+ Anattrib attr = proj.GetAnattrib(offset);
+ if (attr.IsInstructionStart && offset > 0 &&
+ proj.GetAnattrib(offset - 1).IsData) {
+ // Transition from data to code. (Don't add blank line for inline data.)
+ lines.Add(GenerateBlankLine(offset));
+ } else if (addBlank) {
+ // Previous instruction wanted to be followed by a blank line.
+ lines.Add(GenerateBlankLine(offset));
+ }
+ addBlank = false;
+
+ // Insert long comments and notes. These may span multiple display lines,
+ // and require word-wrap, so it's easiest just to render them fully here.
+ if (proj.Notes.TryGetValue(offset, out MultiLineComment noteData)) {
+ List formatted = noteData.FormatText(formatter, "NOTE: ");
+ StringListToLines(formatted, offset, Line.Type.Note,
+ noteData.BackgroundColor, lines);
+ }
+ if (proj.LongComments.TryGetValue(offset, out MultiLineComment longComment)) {
+ List formatted = longComment.FormatText(formatter, string.Empty);
+ StringListToLines(formatted, offset, Line.Type.LongComment,
+ longComment.BackgroundColor, lines);
+ }
+
+ if (attr.IsInstructionStart) {
+ // Generate reg width directive, if necessary.
+ if (proj.CpuDef.HasEmuFlag) {
+ // Changing from "ambiguous but assumed short" to "definitively short"
+ // merits a directive, notably at the start of the file. The tricky
+ // part is that E=1 means definitively M=1 X=1. And maybe
+ // indeterminate E also means that.
+ //
+ // We don't want to mess with Anattrib, but we do need to tell the
+ // assembler something. So we tweak our local copy and propagate it.
+ string operandStr = string.Empty;
+ StatusFlags curFlags = attr.StatusFlags;
+ curFlags.M = attr.StatusFlags.ShortM ? 1 : 0;
+ curFlags.X = attr.StatusFlags.ShortX ? 1 : 0;
+ if (curFlags.M != prevFlags.M) {
+ operandStr = (curFlags.M == 0) ? "longm" : "shortm";
+ }
+
+ if (curFlags.X != prevFlags.X) {
+ if (operandStr.Length > 0) {
+ operandStr += ",";
+ }
+ operandStr += (curFlags.X == 0) ? "longx" : "shortx";
+ }
+
+ if (operandStr.Length > 0) {
+ Line rwLine = new Line(offset, 0, Line.Type.RegWidthDirective);
+ // FormatPseudoOp isn't quite right for the operand, but there
+ // isn't anything more suitable, and there are only eight
+ // possible values. Having the operand capitalization match the
+ // pseudo-op's feels reasonable.
+ rwLine.Parts = FormattedParts.CreateDirective(
+ formatter.FormatPseudoOp(opNames.RegWidthDirective),
+ formatter.FormatPseudoOp(operandStr));
+ lines.Add(rwLine);
+ }
+ prevFlags = curFlags;
+ }
+
+ // Look for embedded instructions.
+ int len;
+ for (len = 1; len < attr.Length; len++) {
+ if (proj.GetAnattrib(offset + len).IsInstructionStart) {
+ break;
+ }
+ }
+
+ // Create Line entry. Offset span only covers the instruction up to
+ // the point where the embedded instruction starts.
+ Line line = new Line(offset, len, Line.Type.Code);
+ lines.Add(line);
+
+ // Insert blank after an instruction that doesn't continue. Provides a
+ // break in code, and before a data area.
+ // TODO(maybe): Might also want to do this if the next offset is data,
+ // to make things look nicer when code runs directly into data.
+ //
+ // We don't want to add it with the current line's offset. If we do that,
+ // the binary search will get confused, because blank lines have a span
+ // of zero. If the code is at offset 10 with length 3, and we search for
+ // the byte at offset 11, then a blank line (with span=0) at offset 10 will
+ // cause the binary search to assume that the target is farther down, when
+ // it's actually one line up. We deal with this by setting a flag and
+ // generating the blank line on the next trip through the loop.
+ if (attr.DoesNotContinue) {
+ addBlank = true;
+ }
+
+ offset += len;
+ } else {
+ Debug.Assert(attr.DataDescriptor != null);
+ int numLines =
+ PseudoOp.ComputeRequiredLineCount(formatter, attr.DataDescriptor);
+ for (int i = 0; i < numLines; i++) {
+ Line line = new Line(offset, attr.Length, Line.Type.Data, i);
+ lines.Add(line);
+ }
+ offset += attr.Length;
+ }
+ }
+
+ // See if there were any address shifts in this section. If so, insert an ORG
+ // statement as the first entry for the offset. We're expecting to have very
+ // few AddressMap entries (usually just one), so it's more efficient to process
+ // them here and walk through the sub-list than it is to ping the address map
+ // at every line.
+ //
+ // It should not be possible for an address map change to appear in the middle
+ // of an instruction or data item.
+ foreach (AddressMap.AddressMapEntry ent in proj.AddrMap) {
+ if (ent.Offset < startOffset || ent.Offset > endOffset) {
+ continue;
+ }
+ int index = FindLineByOffset(lines, ent.Offset);
+ if (index < 0) {
+ Debug.WriteLine("Couldn't find offset " + ent.Offset +
+ " in range we just generated");
+ Debug.Assert(false);
+ continue;
+ }
+ if (lines[index].LineType == Line.Type.Blank) {
+ index++;
+ }
+ Line topLine = lines[index];
+ Line newLine = new Line(topLine.FileOffset, 0, Line.Type.OrgDirective);
+ string addrStr = formatter.FormatHexValue(ent.Addr, 4);
+ newLine.Parts = FormattedParts.CreateDirective(
+ formatter.FormatPseudoOp(opNames.OrgDirective), addrStr);
+ lines.Insert(index, newLine);
+
+ // Prepend a blank line if the previous line wasn't already blank, and this
+ // isn't the ORG at the start of the file. (This may temporarily do
+ // double-spacing if we do a partial update, because we won't be able to
+ // "see" the previous line. Harmless.)
+ if (ent.Offset != 0 && index > 0 && lines[index-1].LineType != Line.Type.Blank) {
+ Line blankLine = new Line(topLine.FileOffset, 0, Line.Type.Blank);
+ lines.Insert(index, blankLine);
+ }
+ }
+ }
+
+ ///
+ /// Generates a blank line entry.
+ ///
+ private static Line GenerateBlankLine(int offset) {
+ return new Line(offset, 0, Line.Type.Blank);
+ }
+
+ ///
+ /// Takes a list of strings and adds them to the Line list as long comments.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static void StringListToLines(List list, int offset, Line.Type lineType,
+ Color color, List lines) {
+ foreach (string str in list) {
+ Line line = new Line(offset, 0, lineType);
+ FormattedParts parts = FormattedParts.CreateLongComment(str);
+ line.Parts = parts;
+ line.BackgroundColor = color;
+ lines.Add(line);
+ }
+ }
+
+ private static FormattedParts GenerateInstructionLine(DisasmProject proj,
+ Formatter formatter, int offset, int instrBytes, bool showCycleCounts) {
+ Anattrib attr = proj.GetAnattrib(offset);
+ byte[] data = proj.FileData;
+
+ string offsetStr = formatter.FormatOffset24(offset);
+
+ int addr = attr.Address;
+ string addrStr = formatter.FormatAddress(addr, !proj.CpuDef.HasAddr16);
+ string bytesStr = formatter.FormatBytes(data, offset, instrBytes);
+ string flagsStr = attr.StatusFlags.ToString(proj.CpuDef.HasEmuFlag);
+ string attrStr = attr.ToAttrString();
+
+ string labelStr = string.Empty;
+ if (attr.Symbol != null) {
+ labelStr = attr.Symbol.Label;
+ }
+
+ OpDef op = proj.CpuDef.GetOpDef(data[offset]);
+ int operand = op.GetOperand(data, offset, attr.StatusFlags);
+ int instrLen = op.GetLength(attr.StatusFlags);
+ OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None;
+ if (op.IsWidthPotentiallyAmbiguous) {
+ wdis = OpDef.GetWidthDisambiguation(instrLen, operand);
+ }
+
+ string opcodeStr = formatter.FormatOpcode(op, wdis);
+ if (attr.Length != instrBytes) {
+ // An instruction is embedded inside this one. Note that BRK is a two-byte
+ // instruction, so don't freak out if you see it marked as embedded when a
+ // $00 is followed by actual code. (But be a little freaked out that your
+ // code is running into a BRK.)
+ //opcodeStr = opcodeStr + " \u00bb"; // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ //opcodeStr = opcodeStr + " \u23e9"; // BLACK RIGHT-POINTING DOUBLE TRIANGLE
+ opcodeStr = opcodeStr + " \u25bc"; // BLACK DOWN-POINTING TRIANGLE
+ }
+
+ string formattedOperand = null;
+ int operandLen = instrLen - 1;
+ PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.None;
+
+ // Tweak branch instructions. We want to show the absolute address rather
+ // than the relative offset (which happens with the OperandAddress assignment
+ // below), and 1-byte branches should always appear as a 4-byte hex value.
+ if (op.AddrMode == OpDef.AddressMode.PCRel) {
+ Debug.Assert(attr.OperandAddress >= 0);
+ operandLen = 2;
+ opFlags = PseudoOp.FormatNumericOpFlags.IsPcRel;
+ } else if (op.AddrMode == OpDef.AddressMode.PCRelLong ||
+ op.AddrMode == OpDef.AddressMode.StackPCRelLong) {
+ opFlags = PseudoOp.FormatNumericOpFlags.IsPcRel;
+ } else if (op.AddrMode == OpDef.AddressMode.Imm ||
+ op.AddrMode == OpDef.AddressMode.ImmLongA ||
+ op.AddrMode == OpDef.AddressMode.ImmLongXY) {
+ opFlags = PseudoOp.FormatNumericOpFlags.HasHashPrefix;
+ }
+
+ // Use the OperandAddress when available. This is important for relative branch
+ // instructions and PER, where we want to show the target address rather than the
+ // operand value.
+ int operandForSymbol = operand;
+ if (attr.OperandAddress >= 0) {
+ operandForSymbol = attr.OperandAddress;
+ }
+
+ // Check Length to watch for bogus descriptors. ApplyFormatDescriptors() should
+ // have discarded anything appropriate, so we might be able to eliminate this test.
+ if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length) {
+ Debug.Assert(operandLen > 0);
+
+ // Format operand as directed.
+ if (op.AddrMode == OpDef.AddressMode.BlockMove) {
+ // Special handling for the double-operand block move.
+ string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
+ null, attr.DataDescriptor, operand >> 8, 1,
+ PseudoOp.FormatNumericOpFlags.None);
+ string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
+ null, attr.DataDescriptor, operand & 0xff, 1,
+ PseudoOp.FormatNumericOpFlags.None);
+ formattedOperand = opstr1 + "," + opstr2;
+ } else {
+ formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
+ null, attr.DataDescriptor, operandForSymbol, operandLen, opFlags);
+ }
+ } else {
+ // Show operand value in hex.
+ if (op.AddrMode == OpDef.AddressMode.BlockMove) {
+ formattedOperand = formatter.FormatHexValue(operand >> 8, 2) + "," +
+ formatter.FormatHexValue(operand & 0xff, 2);
+ } else {
+ if (operandLen == 2) {
+ // This is necessary for 16-bit operands, like "LDA abs" and "PEA val",
+ // when outside bank zero. The bank is included in the operand address,
+ // but we don't want to show it here.
+ operandForSymbol &= 0xffff;
+ }
+ formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2);
+ }
+ }
+ string operandStr = formatter.FormatOperand(op, formattedOperand, wdis);
+
+ string eolComment = proj.Comments[offset];
+ if (showCycleCounts) {
+ bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00);
+ int cycles = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken,
+ branchCross);
+ if (cycles > 0) {
+ eolComment = cycles.ToString() + " " + eolComment;
+ } else {
+ eolComment = (-cycles).ToString() + "+ " + eolComment;
+ }
+ }
+ string commentStr = formatter.FormatEolComment(eolComment);
+
+ string debugStr = string.Empty;
+ //debugStr = "opOff=" +
+ // (attr.OperandOffset < 0 ? "-" : "+" + attr.OperandOffset.ToString("x6"));
+
+ FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
+ flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr, debugStr);
+ return parts;
+ }
+
+ private static FormattedParts GenerateDataLine(DisasmProject proj, Formatter formatter,
+ PseudoOp.PseudoOpNames opNames, int offset, int subLineIndex) {
+ Anattrib attr = proj.GetAnattrib(offset);
+ byte[] data = proj.FileData;
+
+ string offsetStr, addrStr, bytesStr, flagsStr, attrStr, labelStr, opcodeStr,
+ operandStr, commentStr, debugStr;
+ offsetStr = addrStr = bytesStr = flagsStr = attrStr = labelStr = opcodeStr =
+ operandStr = commentStr = debugStr = string.Empty;
+
+ PseudoOp.PseudoOut pout = PseudoOp.FormatDataOp(formatter, opNames, proj.SymbolTable,
+ null, attr.DataDescriptor, proj.FileData, offset, subLineIndex);
+ if (subLineIndex == 0) {
+ offsetStr = formatter.FormatOffset24(offset);
+
+ addrStr = formatter.FormatAddress(attr.Address, !proj.CpuDef.HasAddr16);
+ if (attr.Symbol != null) {
+ labelStr = attr.Symbol.Label;
+ }
+
+ bytesStr = formatter.FormatBytes(data, offset, attr.Length);
+ attrStr = attr.ToAttrString();
+
+ opcodeStr = formatter.FormatPseudoOp(pout.Opcode);
+ } else {
+ opcodeStr = " +";
+ }
+
+ operandStr = pout.Operand;
+
+ if (subLineIndex == 0) {
+ commentStr = formatter.FormatEolComment(proj.Comments[offset]);
+
+ //debugStr = "opOff=" +
+ // (attr.OperandOffset < 0 ? "-" : "+" + attr.OperandOffset.ToString("x6"));
+ }
+
+ FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr,
+ flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr, debugStr);
+ return parts;
+ }
+ }
+}
diff --git a/SourceGenWPF/MainController.cs b/SourceGenWPF/MainController.cs
index e50b90c..b57edd4 100644
--- a/SourceGenWPF/MainController.cs
+++ b/SourceGenWPF/MainController.cs
@@ -65,7 +65,7 @@ namespace SourceGenWPF {
///
/// Data backing the codeListView.
///
- private DisplayList mDisplayList;
+ private DisplayListGen mDisplayList;
#endregion Project state
@@ -519,7 +519,7 @@ namespace SourceGenWPF {
proj.Initialize(fileData.Length);
proj.PrepForNew(fileData, Path.GetFileName(dataPathName));
- proj.LongComments.Add(DisplayList.Line.HEADER_COMMENT_OFFSET,
+ proj.LongComments.Add(DisplayListGen.Line.HEADER_COMMENT_OFFSET,
new MultiLineComment("6502bench SourceGen v" + App.ProgramVersion));
// The system definition provides a set of defaults that can be overridden.
@@ -540,7 +540,7 @@ namespace SourceGenWPF {
dlg.ShowDialog();
}
- mDisplayList = new DisplayList(mProject, mOutputFormatter, mPseudoOpNames);
+ mDisplayList = new DisplayListGen(mProject, mOutputFormatter, mPseudoOpNames);
// Prep the symbol table subset object. Replace the old one with a new one.
//mSymbolSubset = new SymbolTableSubset(mProject.SymbolTable);
@@ -625,7 +625,7 @@ namespace SourceGenWPF {
int topItem = 0;
#endif
int topOffset = mDisplayList[topItem].FileOffset;
- DisplayList.SavedSelection savedSel = DisplayList.SavedSelection.Generate(
+ DisplayListGen.SavedSelection savedSel = DisplayListGen.SavedSelection.Generate(
mDisplayList, mCodeViewSelection, topOffset);
//savedSel.DebugDump();
mReanalysisTimer.EndTask("Save selection");
diff --git a/SourceGenWPF/ProjWin/MainWindow.xaml b/SourceGenWPF/ProjWin/MainWindow.xaml
index 13cc2f8..f192b4a 100644
--- a/SourceGenWPF/ProjWin/MainWindow.xaml
+++ b/SourceGenWPF/ProjWin/MainWindow.xaml
@@ -220,15 +220,15 @@ limitations under the License.
Visibility="{Binding Path=CodeListVisibility}">
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/SourceGenWPF/ProjWin/MainWindow.xaml.cs b/SourceGenWPF/ProjWin/MainWindow.xaml.cs
index 9994d84..81ec6df 100644
--- a/SourceGenWPF/ProjWin/MainWindow.xaml.cs
+++ b/SourceGenWPF/ProjWin/MainWindow.xaml.cs
@@ -45,6 +45,8 @@ namespace SourceGenWPF.ProjWin {
this.DataContext = this;
mUI = new MainController(this);
+
+ codeListView.ItemsSource = new DisplayList(500);
}
diff --git a/SourceGenWPF/ProjectFile.cs b/SourceGenWPF/ProjectFile.cs
index 5574063..2f2ecf5 100644
--- a/SourceGenWPF/ProjectFile.cs
+++ b/SourceGenWPF/ProjectFile.cs
@@ -721,7 +721,7 @@ namespace SourceGenWPF {
// Shouldn't allow DisplayList.Line.HEADER_COMMENT_OFFSET on anything but
// LongComment. Maybe "bool allowNegativeKeys"?
if (intKey < fileLen &&
- (intKey >= 0 || intKey == DisplayList.Line.HEADER_COMMENT_OFFSET)) {
+ (intKey >= 0 || intKey == DisplayListGen.Line.HEADER_COMMENT_OFFSET)) {
return true;
} else {
report.Add(FileLoadItem.Type.Warning,
diff --git a/SourceGenWPF/SourceGenWPF.csproj b/SourceGenWPF/SourceGenWPF.csproj
index e9bc7a4..bc70778 100644
--- a/SourceGenWPF/SourceGenWPF.csproj
+++ b/SourceGenWPF/SourceGenWPF.csproj
@@ -62,6 +62,7 @@
App.xaml
Code
+
DataFileLoadIssue.xaml
@@ -96,7 +97,7 @@
-
+