Warum eine Mini-IDE für den Agon Light?

In einem früheren Beitrag habe ich mein Python-Skript agon_automate.py vorgestellt, mit dem ich Kommandos an den Agon Light schicken und Abläufe automatisieren kann. Damit lassen sich Sachen auf dem Agon erledigen, ohne die Tastatur zu wechseln oder alles von Hand einzutippen.

Trotzdem wird es mühsam, wenn ich in meinem Editor sitze und ein Programm mal eben auf den physischen Agon Light (oder die Console 8) übertragen und starten will. Vor allem in der Entwicklungsphase, wenn ich das ständig mache.

Als Fan von vi, vim und besonders neovim habe ich mir deshalb eine kleine „IDE" für neovim gebaut. Damit schreibe ich meine Programme in BBC Basic in Ruhe auf dem Rechner und schicke sie mit einem kurzen Befehl direkt aus dem Editor an den Agon. Kein Wechseln, kein Dateiname angeben.

Einbindung des Automation-Skripts in neovim

Ich arbeite fast ausschließlich mit neovim. Da manche aber lieber VIM nutzen, habe ich das Skript auch für VIM nachgebaut. Zuerst erkläre ich alles aus Sicht von neovim, weil ich das Skript nativ in Lua geschrieben habe. Die VIM-Variante kommt am Ende des Beitrags. Weniger getestet, sollte aber laufen.

-- Save file, transfer to Agon und Run it.
vim.cmd [[ command! AgonRun :w ++ff=dos | :!python ~/bin/agon_automate.py script ~/bin/agonbasic.script % ]]

-- Send one short command to Agon
vim.cmd [[command! -nargs=1 AgonCommand execute ':!python ~/bin/agon_automate.py direct <args>' | redraw!]]

-- Add the command AgonPaste to NeoVim
vim.api.nvim_create_user_command('AgonPaste', function()
  -- Determine the path for the temporary file in the temp directory
  local temp_file_path = vim.fn.tempname() .. "_AgonPaste.tmp"

  -- Open the temporary file in write mode
  local file = io.open(temp_file_path, "w")
  if not file then
    print("Error opening file: " .. temp_file_path)
    return
  end

  -- Get the content of the register
  local register_content = vim.fn.getreg('"')

  -- Write the content to the file and close the file
  file:write(register_content)
  file:close()

  -- Execute the external Python command with the script mode
  local command = "python " .. vim.loop.os_homedir() .. "/bin/agon_automate.py script " .. temp_file_path
  vim.fn.system(command)

  -- Remove the temporary file after sending
  os.remove(temp_file_path)
end, {})

Das Skript kommt an eine Stelle, an der Deine neovim-Konfiguration es lädt. Meist ist das init.lua unter .config/nvim/init.lua. Bei komplexeren Setups liegt es eventuell woanders, zum Beispiel in einem Ordner oder einer Datei unter .config/nvim/lua/.

Achtung: Wenn bei Dir eine init.vim liegt, arbeitet neovim mit VimScript und nicht nativ mit Lua. Da init.lua zuerst gezogen wird, greift Deine bestehende Config dann eventuell nicht mehr. In dem Fall hilft diese Zeile in der init.lua: vim.cmd('source ~/.config/nvim/init.vim').

Einen einheitlichen Standard für neovim-Configs gibt es leider nicht, bei komplexeren Setups kann die passende Datei auch anders heißen.

Bitte das Skript noch an Deine Umgebung anpassen. Also python oder python3 und die richtigen Pfade zu agon_automate.py und dem eigentlichen Automation-Skript (bei mir agonbasic.script). Konkret diese drei Zeilen:

vim.cmd [[ command! AgonRun :w ++ff=dos | :!python ~/bin/agon_automate.py script ~/bin/agonbasic.script % ]]
...
vim.cmd [[command! -nargs=1 AgonCommand execute ':!python ~/bin/agon_automate.py direct <args>' | redraw!]]
...
local command = "python " .. vim.loop.os_homedir() .. "/bin/agon_automate.py script " .. temp_file_path

Danach neovim neu laden, einen BBC-Basic-Code öffnen und loslegen.

Was kann ich damit konkret machen?

Das Skript liefert drei Befehle. In vi startest Du Befehle im Normal-Modus mit einem Doppelpunkt. Also statt i für Insert drückst Du : und tippst einen der folgenden Befehle ein.

AgonCommand

Damit schickst Du kurze Kommandos direkt aus dem Editor an den Agon Light.

:AgonCommand LIST

Das schickt LIST an den Agon, um ein Programm in BBC Basic aufzulisten. Wer agon_automate.py kennt: Es ist nichts anderes als python agon_automate.py direct "LIST" im Terminal.

AgonRun

Dieser Befehl speichert die aktuelle Datei im DOS-Format (der Agon hat Probleme mit Unix-Zeilenumbrüchen) und startet die Automation.

Dafür musst Du im Skript den Pfad zu agon_automate.py und den Namen Deines Automation-Skripts anpassen (bei mir ~/bin/agonbasic.script). Das Automation-Skript sieht bei mir so aus:

*BYE
hexload vdp {FILENAME}
{COMMAND python ~/bin/send.py {FILENAME} {DEVICE}}{WAIT 2}
load /bbcbasic.bin
run
LOAD "{FILENAME}"
{WAIT 2}
RUN

Das kannst Du natürlich auch für andere Zwecke bauen, nicht nur für Basic. Bei mir ist es genau auf meinen BBC-BASIC-Workflow zugeschnitten.

Der Dateiname der aktuellen Datei wird automatisch übergeben.

:AgonRun

Der Befehl braucht keine weiteren Parameter. Die Datei wird gespeichert, übertragen, geladen und ausgeführt, oder was auch immer Deine Automation macht. Das Ganze entspricht python agon_automate.py script agonbasic.script filename.

AgonPaste

Das ist der eigentliche Clou und spart richtig Zeit. Du hast Deinen Code mit AgonRun auf den Agon übertragen und gestartet. Jetzt willst Du nur eine einzige Zeile ändern.

Statt die Änderung direkt auf dem Agon zu machen (und am Ende zwei Versionen zu haben) oder gleich wieder alles zu übertragen, kopierst Du die Zeile in neovim mit yy (yank line) und rufst auf:

:AgonPaste

Kurz zur Einordnung: In vi geht vieles über den Standard-Buffer, sowas wie eine Zwischenablage. Alles was Du mit dd löschst oder mit yy kopierst, landet dort. AgonPaste schickt genau den Inhalt dieses Buffers an den Agon. Du kannst also auch mehrere Zeilen mit V markieren, mit y in den Buffer holen und übertragen.

Im Video ändere ich eine Zeile im BASIC-Programm Matrix, drücke auf der Zeile yy und führe :AgonPaste aus. Die Änderung wird übertragen. Anschließend starte ich das Programm auf dem Agon neu mit :AgonCommand RUN.

Wer vi kennt, dem ist klar, dass Du hier auch mehr als eine Zeile übertragen kannst (5yy für fünf Zeilen oder V für den Visual-Modus und dann y).

VIM

Funktional ist alles gleich. Nur das Skript wird in VimScript geschrieben, weil VIM nativ kein Lua kann. Die passende Datei ist ~/.vimrc oder ~/.config/nvim/init.vim. Dort das folgende Skript einbauen, der Rest funktioniert wie oben beschrieben.

Das Ganze ist bei mir auf den BBC-Basic-Workflow zugeschnitten. Du kannst es natürlich auch für andere Abläufe umbauen.

" Save the current file with DOS file format and run a Python script with the current file as an argument
command! AgonRun w ++ff=dos | !python3 ~/bin/agon_automate.py script ~/bin/agonbasic.script %

" Send a single command to the Python script directly
command! -nargs=1 AgonCommand execute '!python3 ~/bin/agon_automate.py direct <args>' | redraw!

" Define a function to paste the content of the default register into a temporary file and execute a Python script with it
function! AgonPaste()
  " Create a temporary file path
  let temp_file_path = tempname() . "_AgonPaste.tmp"

  " Write the content of the default register to the temporary file
  call writefile([getreg('"')], temp_file_path)

  " Execute the Python script with the temporary file
  let command = 'python3 ' . $HOME . '/bin/agon_automate.py script ' . temp_file_path
  call system(command)

  " Remove the temporary file
  call delete(temp_file_path)
endfunction
command! AgonPaste call AgonPaste()

Ich habe das VIM-Skript kurz getestet, es sollte sauber laufen. Da ich aber fast immer in neovim unterwegs bin, kann ich zur Langzeit-Stabilität wenig sagen.

Bei Fragen, Problemen oder nettem Feedback gerne einen Kommentar hinterlassen.