public static void HandleDebugKeys(this SpectrumGenericToolWindowViewModel vm, KeyEventArgs args) { if (!vm.VmPaused) { return; } if (args.Key == Key.F5 && Keyboard.Modifiers == ModifierKeys.None) { // --- Run vm.MachineViewModel.StartDebugVm(); args.Handled = true; } else if (args.Key == Key.F11 && Keyboard.Modifiers == ModifierKeys.None) { // --- Step into vm.MachineViewModel.StepInto(); args.Handled = true; } else if (args.Key == Key.System && args.SystemKey == Key.F10 && Keyboard.Modifiers == ModifierKeys.None) { // --- Step over vm.MachineViewModel.StepOver(); args.Handled = true; } if (args.Handled) { SpectNetPackage.UpdateCommandUi(); } }
/// <summary> /// Gets the hierarchy information for the selected item. /// </summary> /// <param name="hierarchy">Hierarchy object</param> /// <param name="itemId">Hierarchy item id</param> /// <remarks> /// If the selected item is the project, it retrieves the hierarchy information for the /// default code file /// </remarks> public void GetCodeItem(out IVsHierarchy hierarchy, out uint itemId) { SpectNetPackage.IsSingleItemSelection(AllowProjectItem, out hierarchy, out itemId); if (itemId == VSConstants.VSITEMID_ROOT) { // --- We have a project item, let's query the default code file var currentProject = Package.CodeDiscoverySolution.CurrentProject; currentProject.GetHierarchyByIdentity(currentProject.DefaultZ80CodeItem.Identity, out hierarchy, out itemId); } }
/// <summary> /// Override this method to execute the command /// </summary> protected override void OnExecute() { var cancel = IsCancelled = false; PrepareCommandOnMainThread(ref cancel); if (cancel) { IsCancelled = true; return; } JoinableTaskFactory.RunAsync(async() => { try { await Task.Yield(); // get off the caller's callstack. await ExecuteAsync(); await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); CompleteOnMainThread(); } catch (OperationCanceledException) { // --- This exception is expected because we signaled the cancellation token IsCancelled = true; OnCancellation(); } catch (AggregateException ex) { // --- ignore AggregateException containing only OperationCanceledExceptionI if (ex.InnerException is OperationCanceledException) { IsCancelled = true; OnCancellation(); } else { OnException(ex); } } catch (Exception ex) { var x = 1; } finally { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); await FinallyOnMainThreadAsync(); if (UpdateUiWhenComplete) { SpectNetPackage.UpdateCommandUi(); } } }); }
/// <summary> /// Checks if the active project has changed /// </summary> /// <param name="oldActiveProject">The file name of the old active project</param> private void CheckActiveProjectChange(string oldActiveProject) { _lastCollectedActiveProject = ActiveProject?.Root?.FileName; if (oldActiveProject == _lastCollectedActiveProject) return; var oldProject = Projects.FirstOrDefault(p => p.Root.FileName == oldActiveProject); var newProject = Projects.FirstOrDefault(p => p.Root.FileName == _lastCollectedActiveProject); ActiveProjectChanged?.Invoke(this, new ActiveProjectChangedEventArgs(oldProject, newProject)); if (newProject == null) return; SpectNetPackage.Log($"New active project: {newProject.Root.FileName}"); }
/// <summary> /// Override this method to prepare assembler options /// </summary> /// <returns>Options to use with the assembler</returns> protected virtual AssemblerOptions PrepareOptions() { var options = new AssemblerOptions { CurrentModel = SpectNetPackage.GetCurrentSpectrumModelType() }; var runOptions = SpectNetPackage.Default.Options.RunSymbols; if (runOptions != null) { var symbols = runOptions.Split(new [] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (var symbol in symbols) { if (!options.PredefinedSymbols.Contains(symbol)) { options.PredefinedSymbols.Add(symbol); } } } return(options); }
/// <summary> /// Compiles the program code /// </summary> /// <returns>True, if compilation successful; otherwise, false</returns> public async Task <bool> CompileCode() { var start = DateTime.Now; SpectNetPackage.Log(_compiler.ServiceName); var output = await _compiler.CompileDocument(_itemFullPath, PrepareAssemblerOptions()); var duration = (DateTime.Now - start).TotalMilliseconds; SpectNetPackage.Log($"Compile time: {duration}ms"); var compiled = output != null; if (compiled) { // --- Sign that compilation was successful HostPackage.DebugInfoProvider.CompiledOutput = Output = output; CreateCompilationListFile(_hierarchy, _itemId); } HostPackage.CodeManager.RaiseCompilationCompleted(output); return(compiled); }
/// <summary> /// Processes the command text /// </summary> /// <param name="commandText">The command text</param> /// <param name="validationMessage"> /// Null, if the command is valid; otherwise the validation message to show /// </param> /// <param name="topAddress"> /// Non-null value indicates that the view should be scrolled to that address /// </param> /// <returns> /// True, if the command has been handled; otherwise, false /// </returns> public bool ProcessCommandline(string commandText, out string validationMessage, out ushort?topAddress) { const string INV_S48_COMMAND = "This command cannot be used for a Spectrum 48K model."; const string INV_RUN_COMMAND = "This command can only be used when the virtual machine is running."; // --- Prepare command handling validationMessage = null; topAddress = null; var isSpectrum48 = SpectNetPackage.IsSpectrum48Model(); var banks = MachineViewModel.SpectrumVm.MemoryConfiguration.RamBanks; var roms = MachineViewModel.SpectrumVm.RomConfiguration.NumberOfRoms; var parser = new MemoryCommandParser(commandText); switch (parser.Command) { case MemoryCommandType.Invalid: validationMessage = "Invalid command syntax"; return(false); case MemoryCommandType.Goto: topAddress = parser.Address; break; case MemoryCommandType.GotoSymbol: if (CompilerOutput == null) { validationMessage = "No compilation has been done, symbols cannot be used with the 'G' command"; return(false); } if (!CompilerOutput.Symbols.TryGetValue(parser.Arg1, out var symbolValue)) { validationMessage = $"Cannot find symbol '{parser.Arg1}'"; return(false); } topAddress = symbolValue; break; case MemoryCommandType.SetRomPage: if (isSpectrum48) { validationMessage = INV_S48_COMMAND; return(false); } if (parser.Address > roms - 1) { validationMessage = $"This machine does not have a ROM bank #{parser.Address}"; return(false); } SetRomViewMode(parser.Address); topAddress = 0; break; case MemoryCommandType.SetRamBank: if (isSpectrum48) { validationMessage = INV_S48_COMMAND; return(false); } if (VmStopped) { validationMessage = INV_RUN_COMMAND; return(false); } if (parser.Address > banks - 1) { validationMessage = $"This machine does not have a RAM bank #{parser.Address}"; return(false); } SetRamBankViewMode(parser.Address); topAddress = 0; break; case MemoryCommandType.MemoryMode: if (isSpectrum48) { validationMessage = INV_S48_COMMAND; return(false); } if (VmStopped) { validationMessage = INV_RUN_COMMAND; return(false); } SetFullViewMode(); break; default: return(false); } return(true); }
/// <summary> /// Processes the command text /// </summary> /// <param name="commandText">The command text</param> /// <param name="validationMessage"> /// Null, if the command is valid; otherwise the validation message to show /// </param> /// <param name="topAddress"> /// Non-null value indicates that the view should be scrolled to that address /// </param> /// <returns> /// True, if the command has been handled; otherwise, false /// </returns> public bool ProcessCommandline(string commandText, out string validationMessage, out ushort?topAddress) { // --- Prepare command handling validationMessage = null; topAddress = null; var isSpectrum48 = SpectNetPackage.IsSpectrum48Model(); var banks = SpectrumVm.MemoryConfiguration.RamBanks; var roms = SpectrumVm.RomConfiguration.NumberOfRoms; var command = ParseCommand(commandText); if (command is CompactToolCommand compactCommand) { command = ParseCommand(compactCommand.CommandText); } if (command == null || command.HasSemanticError) { validationMessage = INV_SYNTAX; return(false); } switch (command) { case GotoToolCommand gotoCommand: if (gotoCommand.Symbol != null) { if (ResolveSymbol(gotoCommand.Symbol, out var symbolValue)) { topAddress = symbolValue; break; } validationMessage = string.Format(UNDEF_SYMBOL, gotoCommand.Symbol); return(false); } topAddress = gotoCommand.Address; break; case RomPageToolCommand romPageCommand: if (isSpectrum48) { validationMessage = INV_S48_COMMAND; return(false); } if (romPageCommand.Page > roms - 1) { validationMessage = $"This machine does not have a ROM bank #{romPageCommand.Page}"; return(false); } SetRomViewMode(romPageCommand.Page); topAddress = 0; break; case BankPageToolCommand bankPageCommand: if (isSpectrum48) { validationMessage = INV_S48_COMMAND; return(false); } if (MachineState == VmState.Stopped) { validationMessage = INV_RUN_COMMAND; return(false); } if (bankPageCommand.Page > banks - 1) { validationMessage = $"This machine does not have a RAM bank #{bankPageCommand.Page}"; return(false); } SetRamBankViewMode(bankPageCommand.Page); topAddress = 0; break; case MemoryModeToolCommand _: if (isSpectrum48) { validationMessage = INV_S48_COMMAND; return(false); } if (MachineState == VmState.Stopped) { validationMessage = INV_RUN_COMMAND; return(false); } SetFullViewMode(); break; case ExportToolCommand exportMemoryCommand: { if (!ObtainAddress(exportMemoryCommand.From, null, out var startAddress, out validationMessage)) { return(false); } if (!ObtainAddress(exportMemoryCommand.To, null, out var endAddress, out validationMessage)) { return(false); } if (DisplayExportMemoryDialog(out var vm, startAddress, endAddress)) { // --- Export cancelled break; } var exporter = new MemoryExporter(vm); exporter.ExportMemory(EmulatorViewModel.Machine.SpectrumVm); ExportMemoryViewModel.LatestFolder = Path.GetDirectoryName(vm.Filename); break; } default: validationMessage = string.Format(INV_CONTEXT, "ZX Spectrum Memory window"); return(false); } return(true); }
/// <summary> /// Compiles the Z80 code file /// </summary> protected override async Task ExecuteAsync() { // --- Prepare the appropriate file to compile/run GetCodeItem(out var hierarchy, out var itemId); // --- Step #1: Compile the code if (!CompileCode(hierarchy, itemId)) { return; } // --- Step #2: Check machine compatibility var modelName = SpectNetPackage.Default.CodeDiscoverySolution?.CurrentProject?.ModelName; SpectrumModelType modelType; if (Output.ModelType == null) { switch (modelName) { case SpectrumModels.ZX_SPECTRUM_48: modelType = SpectrumModelType.Spectrum48; break; case SpectrumModels.ZX_SPECTRUM_128: modelType = SpectrumModelType.Spectrum128; break; case SpectrumModels.ZX_SPECTRUM_P3_E: modelType = SpectrumModelType.SpectrumP3; break; case SpectrumModels.ZX_SPECTRUM_NEXT: modelType = SpectrumModelType.Next; break; default: modelType = SpectrumModelType.Spectrum48; break; } } else { modelType = Output.ModelType.Value; } if (!SpectNetPackage.IsCurrentModelCompatibleWith(modelType)) { VsxDialogs.Show("The model type defined in the code is not compatible with the " + "Spectum virtual machine of this project.", "Cannot run code."); return; } // --- Step #3: Check for zero code length if (Output.Segments.Sum(s => s.EmittedCode.Count) == 0) { VsxDialogs.Show("The lenght of the compiled code is 0, " + "so there is no code to inject into the virtual machine and run.", "No code to run."); return; } // --- Step #4: Check non-zero displacements var options = Package.Options; if (Output.Segments.Any(s => (s.Displacement ?? 0) != 0) && options.ConfirmNonZeroDisplacement) { var answer = VsxDialogs.Show("The compiled code contains non-zero displacement" + "value, so the displaced code may fail. Are you sure you want to run the code?", "Non-zero displacement found", MessageBoxButton.YesNo, VsxMessageBoxIcon.Question, 1); if (answer == VsxDialogResult.No) { return; } } // --- Step #5: Stop the virtual machine if required await SwitchToMainThreadAsync(); Package.ShowToolWindow <SpectrumEmulatorToolWindow>(); var pane = OutputWindow.GetPane <SpectrumVmOutputPane>(); var vm = Package.MachineViewModel; var stopped = await Package.CodeManager.StopSpectrumVm(options.ConfirmMachineRestart); if (!stopped) { return; } // --- Step #6: Start the virtual machine so that later we can load the program pane.WriteLine("Starting the virtual machine in code injection mode."); // --- Use specific startup for each model. bool started = true; try { switch (modelName) { case SpectrumModels.ZX_SPECTRUM_48: await Package.StateFileManager.SetSpectrum48StartupState(); break; case SpectrumModels.ZX_SPECTRUM_128: if (modelType == SpectrumModelType.Spectrum48) { await Package.StateFileManager.SetSpectrum128In48StartupState(); } else { await Package.StateFileManager.SetSpectrum128In128StartupState(); } break; case SpectrumModels.ZX_SPECTRUM_P3_E: if (modelType == SpectrumModelType.Spectrum48) { await Package.StateFileManager.SetSpectrumP3In48StartupState(); } else { await Package.StateFileManager.SetSpectrumP3InP3StartupState(); } break; case SpectrumModels.ZX_SPECTRUM_NEXT: // --- Implement later return; default: // --- Implement later return; } } catch (Exception) { started = false; } if (!started) { return; } // --- Step #7: Inject the code into the memory, and force // --- new disassembly pane.WriteLine("Injecting code into the Spectrum virtual machine."); Package.CodeManager.InjectCodeIntoVm(Output); // --- Step #8: Jump to execute the code var continuationPoint = GetContinuationAddress(); if (continuationPoint.HasValue) { vm.SpectrumVm.Cpu.Registers.PC = continuationPoint.Value; pane.WriteLine($"Resuming code execution at address {vm.SpectrumVm.Cpu.Registers.PC:X4}."); } ResumeVm(); }
/// <summary> /// Updates the command UI whenever the selected item has changed. /// </summary> private void OnSelectedItemChanged(object sender, EventArgs e) { SpectNetPackage.UpdateCommandUi(); }
/// <summary> /// Respond to changes in any test file's contents /// </summary> private void OnTestFileChanged(object sender, FileChangedEventArgs e) { Caption = BaseCaption + "*"; Vm.HasAnyTestFileChanged = true; SpectNetPackage.UpdateCommandUi(); }
/// <summary> /// Obtains the current state of the virtual machine /// </summary> /// <param name="package">Package instance</param> /// <returns>Virtual machine state</returns> public static VmState GetVmState(SpectNetPackage package) => package.MachineViewModel?.MachineState ?? VmState.None;
/// <summary> /// Initializes the provider /// </summary> public VsIntegratedTapeProvider() { _package = SpectNetPackage.Default; }
/// <summary> /// Compiles the Z80 code file /// </summary> protected override async Task ExecuteAsync() { // --- Prepare the appropriate file to compile/run GetCodeItem(out var hierarchy, out var itemId); // --- Step #1: Compile the code if (!CompileCode(hierarchy, itemId)) { return; } // --- Step #2: Check machine compatibility var modelName = SpectNetPackage.Default.CodeDiscoverySolution?.CurrentProject?.ModelName; SpectrumModelType modelType; if (Output.ModelType == null) { switch (modelName) { case SpectrumModels.ZX_SPECTRUM_48: modelType = SpectrumModelType.Spectrum48; break; case SpectrumModels.ZX_SPECTRUM_128: modelType = SpectrumModelType.Spectrum128; break; case SpectrumModels.ZX_SPECTRUM_P3: modelType = SpectrumModelType.SpectrumP3; break; case SpectrumModels.ZX_SPECTRUM_NEXT: modelType = SpectrumModelType.Next; break; default: modelType = SpectrumModelType.Spectrum48; break; } } else { modelType = Output.ModelType.Value; } if (!SpectNetPackage.IsCurrentModelCompatibleWith(modelType)) { VsxDialogs.Show("The model type defined in the code is not compatible with the " + "Spectum virtual machine of this project.", "Cannot run code."); return; } // --- Step #3: Check for zero code length if (Output.Segments.Sum(s => s.EmittedCode.Count) == 0) { VsxDialogs.Show("The lenght of the compiled code is 0, " + "so there is no code to inject into the virtual machine and run.", "No code to run."); return; } // --- Step #4: Check non-zero displacements var options = Package.Options; if (Output.Segments.Any(s => (s.Displacement ?? 0) != 0) && options.ConfirmNonZeroDisplacement) { var answer = VsxDialogs.Show("The compiled code contains non-zero displacement" + "value, so the displaced code may fail. Are you sure you want to run the code?", "Non-zero displacement found", MessageBoxButton.YesNo, VsxMessageBoxIcon.Question, 1); if (answer == VsxDialogResult.No) { return; } } // --- Step #5: Stop the virtual machine if required await SwitchToMainThreadAsync(); Package.ShowToolWindow <SpectrumEmulatorToolWindow>(); var pane = OutputWindow.GetPane <SpectrumVmOutputPane>(); var vm = Package.MachineViewModel; var machineState = vm.VmState; if ((machineState == VmState.Running || machineState == VmState.Paused)) { if (options.ConfirmMachineRestart) { var answer = VsxDialogs.Show("Are you sure, you want to restart " + "the ZX Spectrum virtual machine?", "The ZX Spectum virtual machine is running", MessageBoxButton.YesNo, VsxMessageBoxIcon.Question, 1); if (answer == VsxDialogResult.No) { return; } } // --- Stop the machine and allow 50ms to stop. Package.MachineViewModel.StopVm(); await Task.Delay(50); if (vm.VmState != VmState.Stopped) { const string MESSAGE = "The ZX Spectrum virtual machine did not stop."; pane.WriteLine(MESSAGE); VsxDialogs.Show(MESSAGE, "Unexpected issue", MessageBoxButton.OK, VsxMessageBoxIcon.Error); return; } } // --- Step #6: Start the virtual machine so that later we can load the program pane.WriteLine("Starting the virtual machine in code injection mode."); // --- Use specific startup for each model. switch (modelName) { case SpectrumModels.ZX_SPECTRUM_48: // --- Run in ZX Spectrum 48K mode vm.RestartVmAndRunToTerminationPoint(0, SP48_MAIN_EXEC_ADDR); if (!await WaitStart()) { return; } break; case SpectrumModels.ZX_SPECTRUM_128: // --- Wait while the main menu appears vm.RestartVmAndRunToTerminationPoint(0, SP128_MAIN_WAITING_LOOP); if (!await WaitStart()) { return; } if (modelType == SpectrumModelType.Spectrum48) { vm.RunVmToTerminationPoint(1, SP48_MAIN_EXEC_ADDR); // --- Move to Spectrum 48 mode QueueKeyStroke(SpectrumKeyCode.N6, SpectrumKeyCode.CShift); await Task.Delay(WAIT_FOR_MENU_KEY); QueueKeyStroke(SpectrumKeyCode.N6, SpectrumKeyCode.CShift); await Task.Delay(WAIT_FOR_MENU_KEY); QueueKeyStroke(SpectrumKeyCode.N6, SpectrumKeyCode.CShift); await Task.Delay(WAIT_FOR_MENU_KEY); QueueKeyStroke(SpectrumKeyCode.Enter); if (!await WaitStart()) { return; } } else { vm.RunVmToTerminationPoint(0, SP128_RETURN_TO_EDITOR); // --- Move to Spectrum 128 mode QueueKeyStroke(SpectrumKeyCode.N6, SpectrumKeyCode.CShift); await Task.Delay(WAIT_FOR_MENU_KEY); QueueKeyStroke(SpectrumKeyCode.Enter); if (!await WaitStart()) { return; } } break; case SpectrumModels.ZX_SPECTRUM_P3: // --- Implement later return; case SpectrumModels.ZX_SPECTRUM_NEXT: // --- Implement later return; default: // --- Implement later return; } // --- Step #7: Inject the code into the memory, and force // --- new disassembly pane.WriteLine("Injecting code into the Spectrum virtual machine."); Package.CodeManager.InjectCodeIntoVm(Output); // --- Step #8: Jump to execute the code var continuationPoint = GetContinuationAddress(); if (continuationPoint.HasValue) { vm.SpectrumVm.Cpu.Registers.PC = continuationPoint.Value; pane.WriteLine($"Resuming code execution at address {vm.SpectrumVm.Cpu.Registers.PC:X4}."); } ResumeVm(); }
/// <summary> /// Gets the hierarchy information for the selected item. /// </summary> /// <param name="hierarchy">Hierarchy object</param> /// <param name="itemId">Hierarchy item id</param> /// <remarks> /// If the selected item is the project, it retrieves the hierarchy information for the /// default code file /// </remarks> public void GetCodeItem(out IVsHierarchy hierarchy, out uint itemId) { SpectNetPackage.IsSingleItemSelection(AllowProjectItem, out hierarchy, out itemId); }