BizHawk/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs

1512 lines
39 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Client.EmuHawk.ToolExtensions;
using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.EmuHawk
{
public partial class LuaConsole : ToolFormBase, IToolFormAutoConfig
{
private const string IconColumnName = "Icon";
private const string ScriptColumnName = "Script";
private const string PathColumnName = "PathName";
private static readonly FilesystemFilterSet SessionsFSFilterSet = new FilesystemFilterSet(new FilesystemFilter("Lua Session Files", new[] { "luases" }));
private readonly LuaAutocompleteInstaller _luaAutoInstaller = new LuaAutocompleteInstaller();
private readonly List<FileSystemWatcher> _watches = new List<FileSystemWatcher>();
[RequiredService]
private IEmulator Emulator { get; set; }
private bool _sortReverse;
private string _lastColumnSorted;
private readonly List<string> _consoleCommandHistory = new List<string>();
private int _consoleCommandHistoryIndex = -1;
public ToolDialogSettings.ColumnList Columns { get; set; }
public class LuaConsoleSettings
{
public LuaConsoleSettings()
{
Columns = new List<RollColumn>
{
new RollColumn { Name = IconColumnName, Text = " ", Visible = true, UnscaledWidth = 22, Type = ColumnType.Image },
new RollColumn { Name = ScriptColumnName, Text = "Script", Visible = true, UnscaledWidth = 92, Type = ColumnType.Text },
new RollColumn { Name = PathColumnName, Text = "Path", Visible = true, UnscaledWidth = 300, Type = ColumnType.Text }
};
}
public List<RollColumn> Columns { get; set; }
public bool ReloadOnScriptFileChange { get; set; }
public bool ToggleAllIfNoneSelected { get; set; } = true;
}
[ConfigPersist]
public LuaConsoleSettings Settings { get; set; }
public LuaConsole()
{
Settings = new LuaConsoleSettings();
_sortReverse = false;
_lastColumnSorted = "";
InitializeComponent();
Closing += (o, e) =>
{
if (AskSaveChanges())
{
Settings.Columns = LuaListView.AllColumns;
GlobalWin.DisplayManager.ClearLuaSurfaces();
if (GlobalWin.DisplayManager.ClientExtraPadding != Padding.Empty)
{
GlobalWin.DisplayManager.ClientExtraPadding = new Padding(0);
MainForm.FrameBufferResized();
}
if (GlobalWin.DisplayManager.GameExtraPadding != Padding.Empty)
{
GlobalWin.DisplayManager.GameExtraPadding = new Padding(0);
MainForm.FrameBufferResized();
}
LuaImp.GuiLibrary.DrawFinish();
LuaImp?.Close();
GlobalWin.OSD.ClearGuiText();
}
else
{
e.Cancel = true;
}
};
LuaListView.QueryItemText += LuaListView_QueryItemText;
LuaListView.QueryItemBkColor += LuaListView_QueryItemBkColor;
LuaListView.QueryItemIcon += LuaListView_QueryItemImage;
// this is bad, in case we ever have more than one gui part running lua.. not sure how much other badness there is like that
LuaSandbox.DefaultLogger = WriteToOutputWindow;
}
public PlatformEmuLuaLibrary LuaImp { get; private set; }
public bool UpdateBefore => true;
private IEnumerable<LuaFile> SelectedItems => LuaListView.SelectedRows.Select(index => LuaImp.ScriptList[index]);
private IEnumerable<LuaFile> SelectedFiles => SelectedItems.Where(x => !x.IsSeparator);
public void NewUpdate(ToolFormUpdateType type) { }
public void UpdateValues()
{
// Do nothing
}
public void FastUpdate()
{
// Do nothing
}
private void LuaConsole_Load(object sender, EventArgs e)
{
// Hack for previous config settings
if (Settings.Columns.Any(c => c.Text == null))
{
Settings = new LuaConsoleSettings();
}
LuaImp.ScriptList.ChangedCallback = SessionChangedCallback;
LuaImp.ScriptList.LoadCallback = ClearOutputWindow;
if (Config.RecentLuaSession.AutoLoad && !Config.RecentLuaSession.Empty)
{
LoadSessionFromRecent(Config.RecentLuaSession.MostRecent);
}
else if (Config.RecentLua.AutoLoad)
{
if (!Config.RecentLua.Empty)
{
LoadLuaFile(Config.RecentLua.MostRecent);
}
}
if (OSTailoredCode.IsUnixHost)
{
OpenSessionMenuItem.Enabled = false;
RecentSessionsSubMenu.Enabled = false;
RecentScriptsSubMenu.Enabled = false;
NewScriptMenuItem.Enabled = false;
OpenScriptMenuItem.Enabled = false;
NewScriptToolbarItem.Enabled = false;
OpenScriptToolbarItem.Enabled = false;
WriteToOutputWindow("The Lua environment can currently only be created on Windows. You may not load scripts.");
}
LuaListView.AllColumns.Clear();
SetColumns();
}
public void Restart()
{
var runningScripts = new List<LuaFile>();
if (LuaImp != null) // Things we need to do with the existing LuaImp before we can make a new one
{
if (LuaImp.IsRebootingCore)
{
// Even if the lua console is self-rebooting from client.reboot_core() we still want to re-inject dependencies
LuaImp.Restart(Emulator.ServiceProvider);
return;
}
if (LuaImp.GuiLibrary != null && LuaImp.GuiLibrary.HasLuaSurface)
{
LuaImp.GuiLibrary.DrawFinish();
}
runningScripts = LuaImp.RunningScripts.ToList();
foreach (var file in runningScripts)
{
LuaImp.CallExitEvent(file);
LuaImp.RegisteredFunctions.RemoveForFile(file);
UpdateRegisteredFunctionsDialog();
file.Stop();
}
}
var currentScripts = LuaImp?.ScriptList; // Temp fix for now
LuaImp = OSTailoredCode.IsUnixHost ? (PlatformEmuLuaLibrary) new NotReallyLuaLibrary() : new EmuLuaLibrary(Emulator.ServiceProvider, MainForm);
LuaImp.ScriptList.AddRange(currentScripts ?? Enumerable.Empty<LuaFile>());
InputBox.AutoCompleteCustomSource.AddRange(LuaImp.Docs.Select(a => $"{a.Library}.{a.Name}").ToArray());
foreach (var file in runningScripts)
{
string pathToLoad = ProcessPath(file.Path);
try
{
LuaSandbox.Sandbox(file.Thread, () =>
{
LuaImp.SpawnAndSetFileThread(pathToLoad, file);
LuaSandbox.CreateSandbox(file.Thread, Path.GetDirectoryName(pathToLoad));
file.State = LuaFile.RunState.Running;
}, () =>
{
file.State = LuaFile.RunState.Disabled;
});
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
UpdateDialog();
}
private void SetColumns()
{
foreach (var column in Settings.Columns)
{
if (LuaListView.AllColumns[column.Name] == null)
{
LuaListView.AllColumns.Add(column);
}
}
}
private void AddFileWatches()
{
if (Settings.ReloadOnScriptFileChange)
{
_watches.Clear();
foreach (var item in LuaImp.ScriptList.Where(s => !s.IsSeparator))
{
var processedPath = Config.PathEntries.TryMakeRelative(item.Path);
string pathToLoad = ProcessPath(processedPath);
CreateFileWatcher(pathToLoad);
}
}
}
private void CreateFileWatcher(string path)
{
var watcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(path),
Filter = Path.GetFileName(path),
NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName,
EnableRaisingEvents = true,
};
// TODO, Deleted and Renamed events
watcher.Changed += OnChanged;
_watches.Add(watcher);
}
private void OnChanged(object source, FileSystemEventArgs e)
{
// Even after _watches is cleared, these callbacks hang around! So this check is necessary
var script = LuaImp.ScriptList.FirstOrDefault(s => s.Path == e.FullPath && s.Enabled);
if (script != null)
{
Invoke(new MethodInvoker(delegate { RefreshScriptMenuItem_Click(null, null); }));
}
}
public void LoadLuaFile(string path)
{
var processedPath = Config.PathEntries.TryMakeRelative(path);
var alreadyInSession = LuaImp.ScriptList.Any(t => processedPath == t.Path);
if (alreadyInSession)
{
foreach (var file in LuaImp.ScriptList
.Where(file => processedPath == file.Path
&& file.Enabled == false
&& !Config.DisableLuaScriptsOnLoad))
{
if (file.Thread != null)
{
file.Toggle();
}
break;
}
RunLuaScripts();
}
else
{
var luaFile = new LuaFile("", processedPath);
LuaImp.ScriptList.Add(luaFile);
LuaListView.RowCount = LuaImp.ScriptList.Count;
Config.RecentLua.Add(processedPath);
if (!Config.DisableLuaScriptsOnLoad)
{
luaFile.State = LuaFile.RunState.Running;
EnableLuaFile(luaFile);
}
else
{
luaFile.State = LuaFile.RunState.Disabled;
}
if (Settings.ReloadOnScriptFileChange)
{
CreateFileWatcher(processedPath);
}
}
UpdateDialog();
}
private void UpdateDialog()
{
LuaListView.RowCount = LuaImp.ScriptList.Count;
UpdateNumberOfScripts();
UpdateRegisteredFunctionsDialog();
}
private void RunLuaScripts()
{
foreach (var file in LuaImp.ScriptList.Where(s => !s.IsSeparator))
{
if (!file.Enabled && file.Thread == null)
{
try
{
LuaSandbox.Sandbox(null, () =>
{
string pathToLoad = ProcessPath(file.Path);
LuaImp.SpawnAndSetFileThread(file.Path, file);
LuaSandbox.CreateSandbox(file.Thread, Path.GetDirectoryName(pathToLoad));
}, () =>
{
file.State = LuaFile.RunState.Disabled;
});
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
else
{
file.Stop();
}
}
}
private void SessionChangedCallback()
{
OutputMessages.Text =
(LuaImp.ScriptList.Changes ? "* " : "") +
Path.GetFileName(LuaImp.ScriptList.Filename);
}
private void LuaListView_QueryItemImage(int index, RollColumn column, ref Bitmap bitmap, ref int offsetX, ref int offsetY)
{
if (column.Name != IconColumnName)
{
return;
}
if (LuaImp.ScriptList[index].IsSeparator)
{
return;
}
if (LuaImp.ScriptList[index].Paused)
{
bitmap = Properties.Resources.Pause;
}
else if (LuaImp.ScriptList[index].Enabled)
{
bitmap = Properties.Resources.ts_h_arrow_green;
}
else
{
bitmap = Properties.Resources.StopButton;
}
}
private void LuaListView_QueryItemBkColor(int index, RollColumn column, ref Color color)
{
if (LuaImp.ScriptList[index].IsSeparator)
{
color = BackColor;
}
else if (LuaImp.ScriptList[index].Enabled && !LuaImp.ScriptList[index].Paused)
{
color = Color.LightCyan;
}
else if (LuaImp.ScriptList[index].Enabled && LuaImp.ScriptList[index].Paused)
{
color = Color.LightPink;
}
}
private void LuaListView_QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY)
{
text = "";
if (LuaImp.ScriptList[index].IsSeparator)
{
return;
}
if (column.Name == ScriptColumnName)
{
text = Path.GetFileNameWithoutExtension(LuaImp.ScriptList[index].Path); // TODO: how about allow the user to name scripts?
}
else if (column.Name == PathColumnName)
{
text = DressUpRelative(LuaImp.ScriptList[index].Path);
}
}
private string DressUpRelative(string path)
{
return path.StartsWith(".\\") ? path.Replace(".\\", "") : path;
}
private void UpdateNumberOfScripts()
{
var message = "";
var total = LuaImp.ScriptList.Count(file => !file.IsSeparator);
var active = LuaImp.ScriptList.Count(file => !file.IsSeparator && file.Enabled);
var paused = LuaImp.ScriptList.Count(file => !file.IsSeparator && file.Enabled && file.Paused);
if (total == 1)
{
message += $"{total} script ({active} active, {paused} paused)";
}
else if (total == 0)
{
message += $"{total} scripts";
}
else
{
message += $"{total} scripts ({active} active, {paused} paused)";
}
NumberOfScripts.Text = message;
}
private void WriteLine(string message) => WriteToOutputWindow(message + "\n");
private int _messageCount;
private const int MaxCount = 50;
public void WriteToOutputWindow(string message)
{
if (!OutputBox.IsHandleCreated || OutputBox.IsDisposed)
{
return;
}
OutputBox.Invoke(() =>
{
_messageCount++;
if (_messageCount <= MaxCount)
{
OutputBox.Text += message;
OutputBox.SelectionStart = OutputBox.Text.Length;
OutputBox.ScrollToCaret();
}
});
}
public void ClearOutputWindow()
{
if (!OutputBox.IsHandleCreated || OutputBox.IsDisposed)
{
return;
}
OutputBox.Invoke(() =>
{
OutputBox.Text = "";
OutputBox.Refresh();
});
}
public bool LoadLuaSession(string path)
{
var result = LuaImp.ScriptList.LoadLuaSession(path);
RunLuaScripts();
UpdateDialog();
LuaImp.ScriptList.Changes = false;
return result;
}
/// <summary>
/// resumes suspended Co-routines
/// </summary>
/// <param name="includeFrameWaiters">should frame waiters be waken up? only use this immediately before a frame of emulation</param>
public void ResumeScripts(bool includeFrameWaiters)
{
if (!LuaImp.ScriptList.Any())
{
return;
}
if (LuaImp.GuiLibrary.SurfaceIsNull)
{
LuaImp.GuiLibrary.DrawNew("emu");
}
foreach (var lf in LuaImp.ScriptList.Where(l => l.Enabled && l.Thread != null && !l.Paused))
{
try
{
LuaSandbox.Sandbox(lf.Thread, () =>
{
var prohibit = lf.FrameWaiting && !includeFrameWaiters;
if (!prohibit)
{
var result = LuaImp.ResumeScript(lf);
if (result.Terminated)
{
LuaImp.CallExitEvent(lf);
lf.Stop();
UpdateDialog();
}
lf.FrameWaiting = result.WaitForFrame;
}
}, () =>
{
lf.Stop();
});
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
_messageCount = 0;
}
private FileInfo GetSaveFileFromUser()
{
var sfd = new SaveFileDialog();
if (!string.IsNullOrWhiteSpace(LuaImp.ScriptList.Filename))
{
sfd.FileName = Path.GetFileNameWithoutExtension(LuaImp.ScriptList.Filename);
sfd.InitialDirectory = Path.GetDirectoryName(LuaImp.ScriptList.Filename);
}
else if (Global.Game != null)
{
sfd.FileName = PathManager.FilesystemSafeName(Global.Game);
sfd.InitialDirectory = Config.PathEntries.LuaAbsolutePath();
}
else
{
sfd.FileName = "NULL";
sfd.InitialDirectory = Config.PathEntries.LuaAbsolutePath();
}
sfd.Filter = SessionsFSFilterSet.ToString();
sfd.RestoreDirectory = true;
var result = sfd.ShowHawkDialog();
if (result != DialogResult.OK)
{
return null;
}
return new FileInfo(sfd.FileName);
}
private void SaveSessionAs()
{
var file = GetSaveFileFromUser();
if (file != null)
{
LuaImp.ScriptList.SaveSession(file.FullName);
OutputMessages.Text = $"{Path.GetFileName(LuaImp.ScriptList.Filename)} saved.";
}
}
private void LoadSessionFromRecent(string path)
{
var load = true;
if (LuaImp.ScriptList.Changes)
{
load = AskSaveChanges();
}
if (load)
{
if (!LuaImp.ScriptList.LoadLuaSession(path))
{
Config.RecentLuaSession.HandleLoadError(path);
}
else
{
RunLuaScripts();
UpdateDialog();
LuaImp.ScriptList.Changes = false;
}
}
AddFileWatches();
}
public bool AskSaveChanges()
{
if (LuaImp.ScriptList.Changes && !string.IsNullOrEmpty(LuaImp.ScriptList.Filename))
{
GlobalWin.Sound.StopSound();
var result = MessageBox.Show("Save changes to session?", "Lua Console", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button3);
GlobalWin.Sound.StartSound();
if (result == DialogResult.Yes)
{
if (!string.IsNullOrWhiteSpace(LuaImp.ScriptList.Filename))
{
LuaImp.ScriptList.SaveSession();
}
else
{
SaveSessionAs();
}
return true;
}
if (result == DialogResult.No)
{
LuaImp.ScriptList.Changes = false;
return true;
}
if (result == DialogResult.Cancel)
{
return false;
}
}
return true;
}
private static void UpdateRegisteredFunctionsDialog()
{
foreach (var form in Application.OpenForms.OfType<LuaRegisteredFunctionsList>().ToList())
{
form.UpdateValues();
}
}
#region Events
#region File Menu
private void FileSubMenu_DropDownOpened(object sender, EventArgs e)
{
SaveSessionMenuItem.Enabled = LuaImp.ScriptList.Changes;
}
private void RecentSessionsSubMenu_DropDownOpened(object sender, EventArgs e)
{
RecentSessionsSubMenu.DropDownItems.Clear();
RecentSessionsSubMenu.DropDownItems.AddRange(Config.RecentLuaSession.RecentMenu(LoadSessionFromRecent, "Session"));
}
private void RecentScriptsSubMenu_DropDownOpened(object sender, EventArgs e)
{
RecentScriptsSubMenu.DropDownItems.Clear();
RecentScriptsSubMenu.DropDownItems.AddRange(Config.RecentLua.RecentMenu(LoadLuaFile, "Script"));
}
private void NewSessionMenuItem_Click(object sender, EventArgs e)
{
var result = !LuaImp.ScriptList.Changes || AskSaveChanges();
if (result)
{
LuaImp.ScriptList.Clear();
ClearOutputWindow();
UpdateDialog();
}
}
private void OpenSessionMenuItem_Click(object sender, EventArgs e)
{
var ofd = new OpenFileDialog
{
InitialDirectory = Config.PathEntries.LuaAbsolutePath(),
Filter = SessionsFSFilterSet.ToString(),
RestoreDirectory = true,
Multiselect = false
};
if (!Directory.Exists(ofd.InitialDirectory))
{
Directory.CreateDirectory(ofd.InitialDirectory);
}
var result = ofd.ShowHawkDialog();
if (result == DialogResult.OK && !string.IsNullOrWhiteSpace(ofd.FileName))
{
LuaImp.ScriptList.LoadLuaSession(ofd.FileName);
RunLuaScripts();
UpdateDialog();
LuaImp.ScriptList.Changes = false;
}
}
private void SaveSessionMenuItem_Click(object sender, EventArgs e)
{
if (LuaImp.ScriptList.Changes)
{
if (!string.IsNullOrWhiteSpace(LuaImp.ScriptList.Filename))
{
LuaImp.ScriptList.SaveSession();
}
else
{
SaveSessionAs();
}
OutputMessages.Text = $"{Path.GetFileName(LuaImp.ScriptList.Filename)} saved.";
}
}
private void SaveSessionAsMenuItem_Click(object sender, EventArgs e)
{
SaveSessionAs();
}
private void ExitMenuItem_Click(object sender, EventArgs e)
{
Close();
}
#endregion
#region Script
private void ScriptSubMenu_DropDownOpened(object sender, EventArgs e)
{
ToggleScriptMenuItem.Enabled =
PauseScriptMenuItem.Enabled =
EditScriptMenuItem.Enabled =
SelectedFiles.Any();
RemoveScriptMenuItem.Enabled =
DuplicateScriptMenuItem.Enabled =
MoveUpMenuItem.Enabled =
MoveDownMenuItem.Enabled =
LuaListView.SelectedRows.Any();
SelectAllMenuItem.Enabled = LuaImp.ScriptList.Any();
StopAllScriptsMenuItem.Enabled = LuaImp.ScriptList.Any(script => script.Enabled);
RegisteredFunctionsMenuItem.Enabled = LuaImp.RegisteredFunctions.Any();
}
private void NewScriptMenuItem_Click(object sender, EventArgs e)
{
var sfd = new SaveFileDialog
{
InitialDirectory = !string.IsNullOrWhiteSpace(LuaImp.ScriptList.Filename) ?
Path.GetDirectoryName(LuaImp.ScriptList.Filename) :
Config.PathEntries.LuaAbsolutePath(),
DefaultExt = ".lua",
FileName = !string.IsNullOrWhiteSpace(LuaImp.ScriptList.Filename) ?
Path.GetFileNameWithoutExtension(LuaImp.ScriptList.Filename) :
Path.GetFileNameWithoutExtension(Global.Game.Name),
OverwritePrompt = true,
Filter = new FilesystemFilterSet(FilesystemFilter.LuaScripts).ToString()
};
var result = sfd.ShowHawkDialog();
if (result == DialogResult.OK
&& !string.IsNullOrWhiteSpace(sfd.FileName))
{
string defaultTemplate = "while true do\n\temu.frameadvance();\nend";
File.WriteAllText(sfd.FileName, defaultTemplate);
LuaImp.ScriptList.Add(new LuaFile(Path.GetFileNameWithoutExtension(sfd.FileName), sfd.FileName));
UpdateDialog();
Process.Start(new ProcessStartInfo
{
Verb = "Open",
FileName = sfd.FileName
});
AddFileWatches();
}
}
private void OpenScriptMenuItem_Click(object sender, EventArgs e)
{
var ofd = new OpenFileDialog
{
InitialDirectory = Config.PathEntries.LuaAbsolutePath(),
Filter = new FilesystemFilterSet(FilesystemFilter.LuaScripts, FilesystemFilter.TextFiles).ToString(),
RestoreDirectory = true,
Multiselect = true
};
if (!Directory.Exists(ofd.InitialDirectory))
{
Directory.CreateDirectory(ofd.InitialDirectory);
}
var result = ofd.ShowHawkDialog();
if (result == DialogResult.OK && ofd.FileNames != null)
{
foreach (var file in ofd.FileNames)
{
LoadLuaFile(file);
}
UpdateDialog();
}
}
private void ToggleScriptMenuItem_Click(object sender, EventArgs e)
{
var files = !SelectedFiles.Any() && Settings.ToggleAllIfNoneSelected
? LuaImp.ScriptList
: SelectedFiles;
foreach (var file in files)
{
ToggleLuaScript(file);
}
UpdateDialog();
}
private void EnableLuaFile(LuaFile item)
{
try
{
LuaSandbox.Sandbox(null, () =>
{
string pathToLoad = Path.IsPathRooted(item.Path)
? item.Path
: PathManager.MakeProgramRelativePath(item.Path);
LuaImp.SpawnAndSetFileThread(pathToLoad, item);
LuaSandbox.CreateSandbox(item.Thread, Path.GetDirectoryName(pathToLoad));
}, () =>
{
item.State = LuaFile.RunState.Disabled;
});
ReDraw();
}
catch (IOException)
{
WriteLine($"Unable to access file {item.Path}");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void ReDraw()
{
// Shenanigans
// We want any gui.text messages from a script to immediately update even when paused
GlobalWin.OSD.ClearGuiText();
Tools.UpdateToolsAfter();
LuaImp.EndLuaDrawing();
LuaImp.StartLuaDrawing();
}
private void PauseScriptMenuItem_Click(object sender, EventArgs e)
{
foreach (var x in SelectedFiles)
{
x.TogglePause();
}
UpdateDialog();
}
private string ProcessPath(string path)
{
return Path.IsPathRooted(path)
? path
: PathManager.MakeProgramRelativePath(path);
}
private void EditScriptMenuItem_Click(object sender, EventArgs e)
{
foreach (var file in SelectedFiles)
{
Process.Start(new ProcessStartInfo
{
Verb = "Open",
FileName = ProcessPath(file.Path)
});
}
}
private void RemoveScriptMenuItem_Click(object sender, EventArgs e)
{
var items = SelectedItems.ToList();
if (items.Any())
{
foreach (var item in items)
{
LuaImp.RegisteredFunctions.RemoveForFile(item);
LuaImp.ScriptList.Remove(item);
}
UpdateRegisteredFunctionsDialog();
UpdateDialog();
}
}
private void DuplicateScriptMenuItem_Click(object sender, EventArgs e)
{
if (LuaListView.SelectedRows.Any())
{
var script = SelectedFiles.First();
var sfd = new SaveFileDialog
{
InitialDirectory = Path.GetDirectoryName(script.Path),
DefaultExt = ".lua",
FileName = $"{Path.GetFileNameWithoutExtension(script.Path)} (1)",
OverwritePrompt = true,
Filter = new FilesystemFilterSet(FilesystemFilter.LuaScripts).ToString()
};
if (sfd.ShowDialog().IsOk())
{
string text = File.ReadAllText(script.Path);
File.WriteAllText(sfd.FileName, text);
LuaImp.ScriptList.Add(new LuaFile(Path.GetFileNameWithoutExtension(sfd.FileName), sfd.FileName));
UpdateDialog();
Process.Start(new ProcessStartInfo
{
Verb = "Open",
FileName = sfd.FileName
});
}
}
}
private void InsertSeparatorMenuItem_Click(object sender, EventArgs e)
{
var indices = LuaListView.SelectedRows.ToList();
if (indices.Any() && indices.Last() < LuaImp.ScriptList.Count)
{
LuaImp.ScriptList.Insert(indices.Last(), LuaFile.SeparatorInstance);
}
else
{
LuaImp.ScriptList.Add(LuaFile.SeparatorInstance);
}
UpdateDialog();
}
private void MoveUpMenuItem_Click(object sender, EventArgs e)
{
var indices = LuaListView.SelectedRows.ToList();
if (indices.Count == 0 || indices[0] == 0)
{
return;
}
foreach (var index in indices)
{
var file = LuaImp.ScriptList[index];
LuaImp.ScriptList.Remove(file);
LuaImp.ScriptList.Insert(index - 1, file);
}
var newIndices = indices.Select(t => t - 1);
LuaListView.DeselectAll();
foreach (var i in newIndices)
{
LuaListView.SelectRow(i, true);
}
UpdateDialog();
}
private void MoveDownMenuItem_Click(object sender, EventArgs e)
{
var indices = LuaListView.SelectedRows.ToList();
if (indices.Count == 0 || indices.Last() == LuaImp.ScriptList.Count - 1)
{
return;
}
for (var i = indices.Count - 1; i >= 0; i--)
{
var file = LuaImp.ScriptList[indices[i]];
LuaImp.ScriptList.Remove(file);
LuaImp.ScriptList.Insert(indices[i] + 1, file);
}
var newIndices = indices.Select(t => t + 1);
LuaListView.DeselectAll();
foreach (var i in newIndices)
{
LuaListView.SelectRow(i, true);
}
UpdateDialog();
}
private void SelectAllMenuItem_Click(object sender, EventArgs e)
{
LuaListView.SelectAll();
}
private void StopAllScriptsMenuItem_Click(object sender, EventArgs e)
{
LuaImp.ScriptList.StopAllScripts();
UpdateDialog();
}
private void RegisteredFunctionsMenuItem_Click(object sender, EventArgs e)
{
if (LuaImp.RegisteredFunctions.Any())
{
var alreadyOpen = false;
foreach (Form form in Application.OpenForms)
{
if (form is LuaRegisteredFunctionsList)
{
alreadyOpen = true;
form.Focus();
}
}
if (!alreadyOpen)
{
new LuaRegisteredFunctionsList(LuaImp.RegisteredFunctions)
{
StartLocation = this.ChildPointToScreen(LuaListView)
}.Show();
}
}
}
#endregion
#region Options
private void OptionsSubMenu_DropDownOpened(object sender, EventArgs e)
{
DisableScriptsOnLoadMenuItem.Checked = Config.DisableLuaScriptsOnLoad;
ReturnAllIfNoneSelectedMenuItem.Checked = Settings.ToggleAllIfNoneSelected;
ReloadWhenScriptFileChangesMenuItem.Checked = Settings.ReloadOnScriptFileChange;
}
private void DisableScriptsOnLoadMenuItem_Click(object sender, EventArgs e)
{
Config.DisableLuaScriptsOnLoad ^= true;
}
private void ToggleAllIfNoneSelectedMenuItem_Click(object sender, EventArgs e)
{
Settings.ToggleAllIfNoneSelected ^= true;
}
private void ReloadWhenScriptFileChangesMenuItem_Click(object sender, EventArgs e)
{
Settings.ReloadOnScriptFileChange ^= true;
if (Settings.ReloadOnScriptFileChange)
{
AddFileWatches();
}
else
{
_watches.Clear();
}
}
private void RegisterToTextEditorsSubMenu_DropDownOpened(object sender, EventArgs e)
{
// Hide until this one is implemented
RegisterNotePadMenuItem.Visible = false;
if (_luaAutoInstaller.IsInstalled(LuaAutocompleteInstaller.TextEditors.Sublime2))
{
if (_luaAutoInstaller.IsBizLuaRegistered(LuaAutocompleteInstaller.TextEditors.Sublime2))
{
RegisterSublimeText2MenuItem.Text = "Sublime Text 2 (installed)";
RegisterSublimeText2MenuItem.Font = new Font(RegisterSublimeText2MenuItem.Font, FontStyle.Regular);
RegisterSublimeText2MenuItem.Image = Properties.Resources.GreenCheck;
}
else
{
RegisterSublimeText2MenuItem.Text = "Sublime Text 2 (detected)";
RegisterSublimeText2MenuItem.Font = new Font(RegisterSublimeText2MenuItem.Font, FontStyle.Italic);
RegisterSublimeText2MenuItem.Image = null;
}
}
else
{
RegisterSublimeText2MenuItem.Text = "Sublime Text 2";
RegisterSublimeText2MenuItem.Font = new Font(RegisterSublimeText2MenuItem.Font, FontStyle.Regular);
RegisterSublimeText2MenuItem.Image = null;
}
if (_luaAutoInstaller.IsInstalled(LuaAutocompleteInstaller.TextEditors.NotePad))
{
if (_luaAutoInstaller.IsBizLuaRegistered(LuaAutocompleteInstaller.TextEditors.NotePad))
{
RegisterNotePadMenuItem.Text = "Notepad++ (installed)";
RegisterNotePadMenuItem.Font = new Font(RegisterNotePadMenuItem.Font, FontStyle.Regular);
RegisterNotePadMenuItem.Image = Properties.Resources.GreenCheck;
}
else
{
RegisterNotePadMenuItem.Text = "Notepad++ (detected)";
RegisterNotePadMenuItem.Font = new Font(RegisterNotePadMenuItem.Font, FontStyle.Italic);
RegisterNotePadMenuItem.Image = null;
}
}
else
{
RegisterNotePadMenuItem.Text = "Notepad++";
RegisterNotePadMenuItem.Font = new Font(RegisterNotePadMenuItem.Font, FontStyle.Regular);
RegisterNotePadMenuItem.Image = null;
}
}
private void RegisterSublimeText2MenuItem_Click(object sender, EventArgs e)
{
_luaAutoInstaller.InstallBizLua(LuaAutocompleteInstaller.TextEditors.Sublime2, LuaImp.Docs);
}
private void RegisterNotePadMenuItem_Click(object sender, EventArgs e)
{
_luaAutoInstaller.InstallBizLua(LuaAutocompleteInstaller.TextEditors.NotePad, LuaImp.Docs);
}
#endregion
#region Help
private void FunctionsListMenuItem_Click(object sender, EventArgs e)
{
new LuaFunctionsForm(LuaImp.Docs).Show();
}
private void OnlineDocsMenuItem_Click(object sender, EventArgs e)
{
Process.Start("http://tasvideos.org/BizHawk/LuaFunctions.html");
}
#endregion
#region Toolbar and Context Menu
private void ScriptListContextMenu_Opening(object sender, CancelEventArgs e)
{
ToggleScriptContextItem.Enabled =
PauseScriptContextItem.Enabled =
EditScriptContextItem.Enabled =
SelectedFiles.Any();
StopAllScriptsContextItem.Visible =
ScriptContextSeparator.Visible =
LuaImp.ScriptList.Any(file => file.Enabled);
ClearRegisteredFunctionsContextItem.Enabled =
LuaImp.RegisteredFunctions.Any();
}
private void ConsoleContextMenu_Opening(object sender, CancelEventArgs e)
{
RegisteredFunctionsContextItem.Enabled = LuaImp.RegisteredFunctions.Any();
CopyContextItem.Enabled = OutputBox.SelectedText.Any();
ClearConsoleContextItem.Enabled =
SelectAllContextItem.Enabled =
OutputBox.Text.Any();
ClearRegisteredFunctionsLogContextItem.Enabled =
LuaImp.RegisteredFunctions.Any();
}
private void ClearConsoleContextItem_Click(object sender, EventArgs e)
{
ClearOutputWindow();
}
private void SelectAllContextItem_Click(object sender, EventArgs e)
{
if (!OutputBox.IsHandleCreated || OutputBox.IsDisposed)
{
return;
}
OutputBox.Invoke(() =>
{
OutputBox.SelectAll();
OutputBox.Refresh();
});
}
private void CopyContextItem_Click(object sender, EventArgs e)
{
if (!OutputBox.IsHandleCreated || OutputBox.IsDisposed)
{
return;
}
OutputBox.Invoke(() =>
{
OutputBox.Copy();
OutputBox.Refresh();
});
}
private void ClearRegisteredFunctionsContextMenuItem_Click(object sender, EventArgs e)
{
LuaImp.RegisteredFunctions.Clear();
}
#endregion
#region Dialog, Listview, OutputBox, InputBox
private void LuaConsole_DragDrop(object sender, DragEventArgs e)
{
if (OSTailoredCode.IsUnixHost)
{
Console.WriteLine("The Lua environment can currently only be created on Windows, no scripts will be loaded.");
return;
}
var filePaths = (string[])e.Data.GetData(DataFormats.FileDrop);
try
{
foreach (var path in filePaths)
{
if (Path.GetExtension(path)?.ToLower() == ".lua" || Path.GetExtension(path)?.ToLower() == ".txt")
{
LoadLuaFile(path);
UpdateDialog();
}
else if (Path.GetExtension(path)?.ToLower() == ".luases")
{
LuaImp.ScriptList.LoadLuaSession(path);
RunLuaScripts();
UpdateDialog();
LuaImp.ScriptList.Changes = false;
return;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void LuaListView_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Delete && !e.Control && !e.Alt && !e.Shift)
{
RemoveScriptMenuItem_Click(null, null);
}
else if (e.KeyCode == Keys.A && e.Control && !e.Alt && !e.Shift) // Select All
{
SelectAllMenuItem_Click(null, null);
}
else if (e.KeyCode == Keys.F12 && !e.Control && !e.Alt && !e.Shift) // F12
{
RegisteredFunctionsMenuItem_Click(null, null);
}
}
private void OutputBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.F12 && !e.Control && !e.Alt && !e.Shift) // F12
{
RegisteredFunctionsMenuItem_Click(null, null);
}
}
/// <summary>
/// Sorts the column Ascending on the first click and Descending on the second click.
/// </summary>
private void LuaListView_ColumnClick(object sender, InputRoll.ColumnClickEventArgs e)
{
var columnToSort = e.Column.Name;
var luaListTemp = new List<LuaFile>();
if (columnToSort != _lastColumnSorted)
{
_sortReverse = false;
}
// For getting the name of the .lua file, for some reason this field is kept blank in LuaFile.cs?
// The Name variable gets emptied again near the end just in case it would break something.
for (var i = 0; i < LuaImp.ScriptList.Count; i++)
{
var words = Regex.Split(LuaImp.ScriptList[i].Path, ".lua");
var split = words[0].Split(Path.DirectorySeparatorChar);
luaListTemp.Add(LuaImp.ScriptList[i]);
luaListTemp[i].Name = split[split.Length - 1];
}
// Script, Path
switch (columnToSort)
{
case "Script":
luaListTemp = _sortReverse
? luaListTemp.OrderByDescending(lf => lf.Name).ThenBy(lf => lf.Path).ToList()
: luaListTemp.OrderBy(lf => lf.Name).ThenBy(lf => lf.Path).ToList();
break;
case "Path":
luaListTemp = _sortReverse
? luaListTemp.OrderByDescending(lf => lf.Path).ThenBy(lf => lf.Name).ToList()
: luaListTemp.OrderBy(lf => lf.Path).ThenBy(lf => lf.Name).ToList();
break;
}
for (var i = 0; i < LuaImp.ScriptList.Count; i++)
{
LuaImp.ScriptList[i] = luaListTemp[i];
LuaImp.ScriptList[i].Name = "";
}
UpdateDialog();
_lastColumnSorted = columnToSort;
_sortReverse = !_sortReverse;
}
private void RefreshScriptMenuItem_Click(object sender, EventArgs e)
{
ToggleScriptMenuItem_Click(sender, e);
ToggleScriptMenuItem_Click(sender, e);
}
private void InputBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
string consoleBeforeCall = OutputBox.Text;
// TODO: Maybe make these try-catches more general
if (!string.IsNullOrWhiteSpace(InputBox.Text))
{
if (InputBox.Text.Contains("emu.frameadvance("))
{
WriteLine("emu.frameadvance() can not be called from the console");
return;
}
LuaSandbox.Sandbox(null, () =>
{
LuaImp.ExecuteString($"console.log({InputBox.Text})");
}, () =>
{
LuaSandbox.Sandbox(null, () =>
{
LuaImp.ExecuteString(InputBox.Text);
if (OutputBox.Text == consoleBeforeCall)
{
WriteLine("Command successfully executed");
}
});
});
_consoleCommandHistory.Insert(0, InputBox.Text);
_consoleCommandHistoryIndex = -1;
InputBox.Clear();
}
}
else if (e.KeyCode == Keys.Up)
{
if (_consoleCommandHistoryIndex < _consoleCommandHistory.Count - 1)
{
_consoleCommandHistoryIndex++;
InputBox.Text = _consoleCommandHistory[_consoleCommandHistoryIndex];
InputBox.Select(InputBox.Text.Length, 0);
}
e.Handled = true;
}
else if (e.KeyCode == Keys.Down)
{
if (_consoleCommandHistoryIndex == 0)
{
_consoleCommandHistoryIndex--;
InputBox.Text = "";
}
else if (_consoleCommandHistoryIndex > 0)
{
_consoleCommandHistoryIndex--;
InputBox.Text = _consoleCommandHistory[_consoleCommandHistoryIndex];
InputBox.Select(InputBox.Text.Length, 0);
}
e.Handled = true;
}
else if (e.KeyCode == Keys.Tab)
{
ProcessTabKey(false);
e.Handled = true;
}
}
// For whatever reason an auto-complete TextBox doesn't respond to delete
// Which is annoying but worse is that it let's the key propagate
// If a script is highlighted in the ListView, and the user presses
// delete, it will remove the script without this hack
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Delete && InputBox.Focused)
{
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
protected override bool ProcessTabKey(bool forward)
{
// TODO: Make me less dirty (please)
return false;
}
#endregion
private void EraseToolbarItem_Click(object sender, EventArgs e)
{
GlobalWin.DisplayManager.ClearLuaSurfaces();
}
// Stupid designer
protected void DragEnterWrapper(object sender, DragEventArgs e)
{
GenericDragEnter(sender, e);
}
#endregion
private void LuaListView_DoubleClick(object sender, EventArgs e)
{
var index = LuaListView.CurrentCell?.RowIndex;
if (index < LuaImp.ScriptList.Count)
{
var file = LuaImp.ScriptList[index.Value];
ToggleLuaScript(file);
UpdateDialog();
}
}
private void ToggleLuaScript(LuaFile file)
{
if (file.IsSeparator)
{
return;
}
file.Toggle();
if (file.Enabled && file.Thread == null)
{
LuaImp.RegisteredFunctions.RemoveForFile(file); // First remove any existing registered functions for this file
EnableLuaFile(file);
UpdateRegisteredFunctionsDialog();
}
else if (!file.Enabled && file.Thread != null)
{
LuaImp.CallExitEvent(file);
LuaImp.RegisteredFunctions.RemoveForFile(file);
UpdateRegisteredFunctionsDialog();
LuaImp.CallExitEvent(file);
file.Stop();
ReDraw();
}
}
[RestoreDefaults]
private void RestoreDefaults()
{
Settings = new LuaConsoleSettings();
LuaListView.AllColumns.Clear();
SetColumns();
UpdateDialog();
}
}
}