From bb830a29dbd786d5362c000a2a6b5a655534dfa2 Mon Sep 17 00:00:00 2001
From: Andy McFadden
Date: Sun, 7 Jun 2020 16:28:29 -0700
Subject: [PATCH] Add Navigate > Jump to Operand
If you double-click on the opcode of an instruction whose operand is
an address or equate, the selection jumps to that address. This
feature is now available in the Navigate menu, with the keyboard
shortcut Ctrl+J.
While testing the feature I noticed that the keyboard focus wasn't
following the selection, so if you jumped to an address and then
used the up/down arrows, you jumped back to the previous location.
(This was true when double-clicking an opcode to jump; it was just
less noticeable since the next action was likely mouse-based.) This
has been fixed by updating the ListView item focus when we jump to a
new location.
See also issue #63 and issue #72.
---
SourceGen/MainController.cs | 73 ++++++++++++++++++++-----
SourceGen/RuntimeData/Help/mainwin.html | 4 ++
SourceGen/Tests/GenTest.cs | 1 +
SourceGen/WpfGui/MainWindow.xaml | 8 +++
SourceGen/WpfGui/MainWindow.xaml.cs | 15 ++++-
5 files changed, 86 insertions(+), 15 deletions(-)
diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs
index c9ef7cb..d6be468 100644
--- a/SourceGen/MainController.cs
+++ b/SourceGen/MainController.cs
@@ -1559,7 +1559,7 @@ namespace SourceGen {
}
break;
case CodeListColumn.Opcode:
- HandleDoubleClickOnOpcode(line);
+ JumpToOperandTarget(line, false);
break;
case CodeListColumn.Operand:
if (CanEditOperand()) {
@@ -1581,11 +1581,17 @@ namespace SourceGen {
}
}
- private void HandleDoubleClickOnOpcode(LineListGen.Line line) {
+ ///
+ /// Jumps to the line referenced by the operand of the selected line.
+ ///
+ /// Selected line.
+ /// If set, don't actually do the goto.
+ /// True if a jump is available for this line.
+ private bool JumpToOperandTarget(LineListGen.Line line, bool testOnly) {
if (line.FileOffset < 0) {
// Double-click on project symbol EQUs and the file header comment are handled
// elsewhere.
- return;
+ return false;
}
Anattrib attr = mProject.GetAnattrib(line.FileOffset);
@@ -1595,20 +1601,29 @@ namespace SourceGen {
// (Resolve it as a numeric reference.)
if (attr.OperandOffset >= 0) {
// Yup, find the line for that offset and jump to it.
- GoToLocation(new NavStack.Location(attr.OperandOffset, 0, false),
- GoToMode.JumpToCodeData, true);
+ if (!testOnly) {
+ GoToLocation(new NavStack.Location(attr.OperandOffset, 0, false),
+ GoToMode.JumpToCodeData, true);
+ }
+ return true;
} else if (dfd != null && dfd.HasSymbol) {
// Operand has a symbol, do a symbol lookup.
if (dfd.SymbolRef.IsVariable) {
- GoToVarDefinition(line.FileOffset, dfd.SymbolRef, true);
+ if (!testOnly) {
+ GoToVarDefinition(line.FileOffset, dfd.SymbolRef, true);
+ }
+ return true;
} else {
if (mProject.SymbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym)) {
if (sym.SymbolSource == Symbol.Source.User ||
sym.SymbolSource == Symbol.Source.Auto) {
int labelOffset = mProject.FindLabelOffsetByName(dfd.SymbolRef.Label);
if (labelOffset >= 0) {
- GoToLocation(new NavStack.Location(labelOffset, 0, false),
- GoToMode.JumpToCodeData, true);
+ if (!testOnly) {
+ GoToLocation(new NavStack.Location(labelOffset, 0, false),
+ GoToMode.JumpToCodeData, true);
+ }
+ return true;
}
} else if (sym.SymbolSource == Symbol.Source.Platform ||
sym.SymbolSource == Symbol.Source.Project) {
@@ -1616,9 +1631,11 @@ namespace SourceGen {
for (int i = 0; i < mProject.ActiveDefSymbolList.Count; i++) {
if (mProject.ActiveDefSymbolList[i] == sym) {
int offset = LineListGen.DefSymOffsetFromIndex(i);
- GoToLocation(new NavStack.Location(offset, 0, false),
- GoToMode.JumpToCodeData, true);
- break;
+ if (!testOnly) {
+ GoToLocation(new NavStack.Location(offset, 0, false),
+ GoToMode.JumpToCodeData, true);
+ }
+ return true;
}
}
} else {
@@ -1635,10 +1652,15 @@ namespace SourceGen {
// previous clause, but Address entries would not have been.)
int operandOffset = DataAnalysis.GetDataOperandOffset(mProject, line.FileOffset);
if (operandOffset >= 0) {
- GoToLocation(new NavStack.Location(operandOffset, 0, false),
- GoToMode.JumpToCodeData, true);
+ if (!testOnly) {
+ GoToLocation(new NavStack.Location(operandOffset, 0, false),
+ GoToMode.JumpToCodeData, true);
+ }
+ return true;
}
}
+
+ return false;
}
public bool CanDeleteMlc() {
@@ -2704,6 +2726,10 @@ namespace SourceGen {
// Update the selection.
mMainWin.CodeListView_SelectRange(topLineIndex, lastLineIndex - topLineIndex);
+ // Set the focus to the first selected item. The focus is used by the keyboard
+ // handler to decide what the up/down arrows select next.
+ mMainWin.CodeListView_SetSelectionFocus();
+
if (doPush) {
// Update the back stack and associated controls.
mNavStack.Push(prevLoc);
@@ -2783,6 +2809,27 @@ namespace SourceGen {
}
}
+ public bool CanJumpToOperand() {
+ if (SelectionAnalysis.mNumItemsSelected != 1) {
+ return false;
+ }
+ LineListGen.Line.Type lineType = SelectionAnalysis.mLineType;
+ if (lineType != LineListGen.Line.Type.Code &&
+ lineType != LineListGen.Line.Type.Data) {
+ return false;
+ }
+
+ int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex();
+ Debug.Assert(selIndex >= 0);
+ return JumpToOperandTarget(CodeLineList[selIndex], true);
+ }
+
+ public void JumpToOperand() {
+ int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex();
+ Debug.Assert(selIndex >= 0);
+ JumpToOperandTarget(CodeLineList[selIndex], false);
+ }
+
///
/// Calculates the currently-selected location.
///
diff --git a/SourceGen/RuntimeData/Help/mainwin.html b/SourceGen/RuntimeData/Help/mainwin.html
index 4f53bbf..e7646bc 100644
--- a/SourceGen/RuntimeData/Help/mainwin.html
+++ b/SourceGen/RuntimeData/Help/mainwin.html
@@ -381,6 +381,10 @@ address, like "FEED", the label takes precedence. To jump to the address
write "$FEED" instead. If you enter a non-unique label, the selection
will jump to the nearest instance.
+If an instruction or data line has an operand that references an address
+in the file, you can navigate to the operand's location with
+Navigate > Jump to Operand.
+
When you edit something, lines throughout the listing can change. This
is different from a source code editor, where editing a line just changes
that line. To allow you to watch the effects changes have, the undo/redo
diff --git a/SourceGen/Tests/GenTest.cs b/SourceGen/Tests/GenTest.cs
index 9dc1378..d2fe0d7 100644
--- a/SourceGen/Tests/GenTest.cs
+++ b/SourceGen/Tests/GenTest.cs
@@ -20,6 +20,7 @@ using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Media;
+
using Asm65;
using CommonUtil;
using SourceGen.AsmGen;
diff --git a/SourceGen/WpfGui/MainWindow.xaml b/SourceGen/WpfGui/MainWindow.xaml
index fead078..6f9b1c5 100644
--- a/SourceGen/WpfGui/MainWindow.xaml
+++ b/SourceGen/WpfGui/MainWindow.xaml
@@ -143,6 +143,11 @@ limitations under the License.
+
+
+ Ctrl+J
+
+
Alt+Left
@@ -274,6 +279,8 @@ limitations under the License.
CanExecute="CanHintAsDataStart" Executed="HintAsDataStartCmd_Executed"/>
+
+
diff --git a/SourceGen/WpfGui/MainWindow.xaml.cs b/SourceGen/WpfGui/MainWindow.xaml.cs
index 3586546..3259a11 100644
--- a/SourceGen/WpfGui/MainWindow.xaml.cs
+++ b/SourceGen/WpfGui/MainWindow.xaml.cs
@@ -881,6 +881,10 @@ namespace SourceGen.WpfGui {
mIsSplitterBeingDragged = false;
}
+ public void CodeListView_SetSelectionFocus() {
+ ItemContainerGenerator_StatusChanged(null, null);
+ }
+
///
/// Returns the index of the line that's currently at the top of the control.
///
@@ -1089,9 +1093,12 @@ namespace SourceGen.WpfGui {
(counts.mCodeHints != 0 || counts.mDataHints != 0 || counts.mInlineDataHints != 0);
}
+ private void CanJumpToOperand(object sender, CanExecuteRoutedEventArgs e) {
+ e.CanExecute = IsProjectOpen() && mMainCtrl.CanJumpToOperand();
+ }
+
private void CanSaveProject(object sender, CanExecuteRoutedEventArgs e) {
- e.CanExecute = mMainCtrl != null && mMainCtrl.IsProjectOpen &&
- !mMainCtrl.IsProjectReadOnly;
+ e.CanExecute = IsProjectOpen() && !mMainCtrl.IsProjectReadOnly;
}
private void CanToggleSingleByteFormat(object sender, CanExecuteRoutedEventArgs e) {
@@ -1258,6 +1265,10 @@ namespace SourceGen.WpfGui {
mMainCtrl.MarkAsType(CodeAnalysis.TypeHint.InlineData, false);
}
+ private void JumpToOperandCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
+ mMainCtrl.JumpToOperand();
+ }
+
private void NavigateBackwardCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.NavigateBackward();
}