diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 46b187e..4984c7c 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,18 @@ "permissions": { "allow": [ "Bash(build.bat)", - "PowerShell(Copy-Item \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\cc65-mouse-driver-demo\\\\mousedemo.c\" \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\cc65-mouse-driver-demo\\\\mouse_driver_demo.c\" -Force)" + "PowerShell(Copy-Item \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\cc65-mouse-driver-demo\\\\mousedemo.c\" \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\cc65-mouse-driver-demo\\\\mouse_driver_demo.c\" -Force)", + "PowerShell($f = \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\kernal-os-skeleton\\\\kernal_os.prg\"; \\(Get-Item $f\\).Length)", + "PowerShell($output = & \"C:\\\\Program Files\\\\GTK3VICE-3.10-win64\\\\bin\\\\x64sc.exe\" -help 2>&1; $output | Where-Object { $_ -match \"device|fs|drive|disk\" } | Select-Object -First 40)", + "PowerShell(cmd /c \"cd /d `\"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\kernal-os-skeleton`\" && build_all.bat\" 2>&1)", + "Bash(cmd.exe /c build_all.bat)", + "Bash(cmd.exe /c \"build_all.bat\")", + "Bash(echo \"exit: $?\")", + "Bash(cmd.exe /c \"ca65 kernal_os.asm -o kernal_os.o && ld65 -C kernal_os.cfg kernal_os.o -o kernal_os.prg && echo kernal_os OK\")", + "Bash(ca65 \"c:/Development/Commodore/os Experiments/c64os/samples/kernal-os-skeleton/kernal_os.asm\" -o \"c:/Development/Commodore/os Experiments/c64os/samples/kernal-os-skeleton/kernal_os.o\")", + "Bash(cmd /c build_all.bat)", + "Bash(cmd /c \"build_all.bat && echo BUILD_OK || echo BUILD_FAILED\")", + "Bash(echo \"EXIT: $?\")" ] } } diff --git a/samples/kernal-os-skeleton/build_all.bat b/samples/kernal-os-skeleton/build_all.bat new file mode 100644 index 0000000..7580c71 --- /dev/null +++ b/samples/kernal-os-skeleton/build_all.bat @@ -0,0 +1,40 @@ +@echo off +setlocal + +set "C1541=C:\Program Files\GTK3VICE-3.10-win64\bin\c1541.exe" +set "DISK=kernal_os.d64" + +rem ---- build kernal_os.prg ---- +call build.bat +if errorlevel 1 goto error + +rem ---- build vim.prg ---- +call build_vim.bat +if errorlevel 1 goto error + +rem ---- verify c1541 ---- +if not exist "%C1541%" ( + echo c1541.exe not found at: + echo %C1541% + exit /b 1 +) + +rem ---- create fresh D64 and write both PRGs ---- +"%C1541%" -format "kernal os,01" d64 "%DISK%" +if errorlevel 1 goto error + +"%C1541%" "%DISK%" -write "kernal_os.prg" "kernal_os" +if errorlevel 1 goto error + +"%C1541%" "%DISK%" -write "vim.prg" "vim" +if errorlevel 1 goto error + +echo. +echo built %DISK% (kernal_os + vim) +goto end + +:error +echo build failed. +exit /b 1 + +:end diff --git a/samples/kernal-os-skeleton/build_vim.bat b/samples/kernal-os-skeleton/build_vim.bat new file mode 100644 index 0000000..4adf7a2 --- /dev/null +++ b/samples/kernal-os-skeleton/build_vim.bat @@ -0,0 +1,15 @@ +@echo off + +ca65 vim.asm -o vim.o +if errorlevel 1 goto error + +ld65 -C vim.cfg vim.o -o vim.prg +if errorlevel 1 goto error + +echo built vim.prg successfully. +goto end + +:error +echo build failed. + +:end diff --git a/samples/kernal-os-skeleton/kernal_os.asm b/samples/kernal-os-skeleton/kernal_os.asm index 956da72..712b0ea 100644 --- a/samples/kernal-os-skeleton/kernal_os.asm +++ b/samples/kernal-os-skeleton/kernal_os.asm @@ -34,6 +34,7 @@ CHKOUT = $FFC9 CLRCH = $FFCC BASIN = $FFCF CLALL = $FFE7 +LOAD = $FFD5 ; ------------------------------------------------------------ ; system locations @@ -55,6 +56,7 @@ INPUT_MAX = 64 DISK_DEV = 8 LFN_DIR = 2 LFN_CMD = 15 +EXEC_AREA = $2000 SYS_ENTRY = 2061 ; ------------------------------------------------------------ @@ -1171,12 +1173,137 @@ check_exit: jmp request_exit command_unknown: - lda #msg_unknown + jmp do_exec + +command_empty: + rts + +; ------------------------------------------------------------ +; external program loader +; ------------------------------------------------------------ +; When the typed word does not match any built-in command, +; search the current disk device for a PRG file with that name. +; If found, load it to EXEC_AREA ($2000) and JSR there. +; The external program should end with RTS to return to the shell. +; All external programs must be assembled for address $2000. +; ------------------------------------------------------------ + +do_exec: + jsr build_exec_filename + bcc exec_try_load + jmp exec_not_found + +exec_try_load: + jsr reset_file_state + + lda #LFN_DIR + sta active_lfn + ldx current_device + ldy #1 + jsr SETLFS + + lda filename_len + ldx #filename_spec + jsr SETNAM + + lda #0 + ldx #EXEC_AREA + jsr LOAD + bcc exec_loaded + + jsr reset_file_state + jmp exec_not_found + +exec_loaded: + jsr reset_file_state + jsr set_os_memory + + lda #msg_exec_run jsr print_string + + ldy #0 +exec_print_name: + lda input_buffer,y + beq exec_print_done + cmp #' ' + beq exec_print_done + jsr CHROUT + iny + bne exec_print_name + +exec_print_done: + lda #CR + jsr CHROUT + + lda #EXEC_AREA + sta PTR1_HI + jsr call_indirect + jsr set_os_memory rts -command_empty: +exec_not_found: + lda #msg_exec_nf + jsr print_string + + ldy #0 +exec_nf_name: + lda input_buffer,y + beq exec_nf_done + cmp #' ' + beq exec_nf_done + jsr CHROUT + iny + bne exec_nf_name + +exec_nf_done: + lda #CR + jsr CHROUT + rts + +; JMP (PTR1) — turns a JSR into an indirect call. +; Caller does: JSR call_indirect +; The called code's RTS returns to call_indirect's caller. +call_indirect: + jmp (PTR1_LO) + +; build filename_spec = "{drive}:{cmdname}" from input_buffer word 0 +build_exec_filename: + ldx #0 + lda current_drive + clc + adc #'0' + sta filename_spec,x + inx + lda #':' + sta filename_spec,x + inx + + ldy #0 + +exec_copy_name: + lda input_buffer,y + beq exec_name_done + cmp #' ' + beq exec_name_done + sta filename_spec,x + inx + iny + cpx #35 + bcc exec_copy_name + sec + rts + +exec_name_done: + stx filename_len + lda #0 + sta filename_spec,x + clc rts ; ------------------------------------------------------------ @@ -1958,9 +2085,6 @@ msg_banner: msg_prompt: .byte "OS> ", 0 -msg_unknown: - .byte "UNKNOWN. HELP", CR, 0 - msg_help: .byte CR .byte "HELP MEM INC DUMP ZERO SIGN", CR @@ -1969,6 +2093,7 @@ msg_help: .byte "HEX RUN RENAME COPY", CR .byte "DEVICE DEV DRIVE DEL ERASE", CR .byte "ABOUT EXIT", CR + .byte "OTHER: LOAD+RUN PRG AT $2000", CR .byte CR .byte 0 @@ -2003,6 +2128,8 @@ msg_about: .byte "KERNAL STAYS MAPPED IN.", CR .byte "SHELL CODE STARTS AT $080D.", CR .byte "USE $A000-$BFFF AS WORK RAM.", CR + .byte "EXTERNAL CMDS LOAD AT $2000.", CR + .byte "ASSEMBLE CMDS WITH *=$2000.", CR .byte CR .byte 0 @@ -2178,6 +2305,12 @@ msg_copy: msg_copy_usage: .byte "USAGE: COPY srcname dstname", CR, 0 +msg_exec_run: + .byte "EXEC: ", 0 + +msg_exec_nf: + .byte "? ", 0 + .segment "BSS" workspace_counter: diff --git a/samples/kernal-os-skeleton/kernal_os.d64 b/samples/kernal-os-skeleton/kernal_os.d64 new file mode 100644 index 0000000..1a1425a Binary files /dev/null and b/samples/kernal-os-skeleton/kernal_os.d64 differ diff --git a/samples/kernal-os-skeleton/kernal_os.o b/samples/kernal-os-skeleton/kernal_os.o index 0c664a2..11d0623 100644 Binary files a/samples/kernal-os-skeleton/kernal_os.o and b/samples/kernal-os-skeleton/kernal_os.o differ diff --git a/samples/kernal-os-skeleton/kernal_os.prg b/samples/kernal-os-skeleton/kernal_os.prg index 5b3d2d4..2c09c37 100644 Binary files a/samples/kernal-os-skeleton/kernal_os.prg and b/samples/kernal-os-skeleton/kernal_os.prg differ diff --git a/samples/kernal-os-skeleton/run.bat b/samples/kernal-os-skeleton/run.bat new file mode 100644 index 0000000..e771423 --- /dev/null +++ b/samples/kernal-os-skeleton/run.bat @@ -0,0 +1,16 @@ +@echo off +setlocal + +set "VICE_EXE=C:\Program Files\GTK3VICE-3.10-win64\bin\x64sc.exe" +set "DISK=kernal_os.d64" + +if not exist "%VICE_EXE%" ( + echo VICE x64sc was not found at: + echo %VICE_EXE% + exit /b 1 +) + +call build_all.bat +if errorlevel 1 exit /b 1 + +"%VICE_EXE%" -autostart "%CD%\%DISK%" diff --git a/samples/kernal-os-skeleton/vim.asm b/samples/kernal-os-skeleton/vim.asm new file mode 100644 index 0000000..c74a9bc --- /dev/null +++ b/samples/kernal-os-skeleton/vim.asm @@ -0,0 +1,907 @@ +; +; vim.asm - minimal modal text editor for the kernal-os-skeleton shell +; +; Build: +; ca65 vim.asm -o vim.o +; ld65 -C vim.cfg vim.o -o vim.prg +; +; From the OS shell type: VIM +; All external commands load at $2000 and end with RTS. +; +; Modes +; NORMAL (default) h j k l or cursor keys = move +; i = insert before cursor +; a = append after cursor +; o = open line below +; x = delete char under cursor +; : = enter command mode +; INSERT type text, DEL = backspace, RUN/STOP = back to NORMAL +; COMMAND :q quit :w NAME save to disk +; RUN/STOP = cancel +; +; Text model: the 24x40 text area of screen RAM IS the buffer. +; Row 24 (bottom line) is the status bar. +; + +.setcpu "6502" + +; ---- KERNAL ---- +GETIN = $FFE4 +STOPKEY = $FFE1 +CHROUT = $FFD2 +SETLFS = $FFBA +SETNAM = $FFBD +OPEN = $FFC0 +CLOSE = $FFC3 +CHKOUT = $FFC9 +CLRCH = $FFCC +CLALL = $FFE7 + +; ---- hardware ---- +SCRRAM = $0400 + +; ---- layout ---- +SCR_COLS = 40 +SCR_ROWS = 24 ; rows 0-23 = text, row 24 = status bar + +; ---- modes ---- +MODE_NORMAL = 0 +MODE_INSERT = 1 +MODE_CMD = 2 + +; ---- PETSCII ---- +CR_CH = $0D +DEL_CH = $14 + +; ---- command buffer ---- +CMD_MAX = 20 + +; ---- save LFN ---- +SAVE_LFN = 5 +SAVE_DEV = 8 + +; ---- zero page (user area, safe under OS banking) ---- +ZP_LO = $FB ; general destination pointer lo +ZP_HI = $FC ; general destination pointer hi +STR_LO = $FD ; string source pointer lo +STR_HI = $FE ; string source pointer hi +ZP_TMP = $02 ; scratch byte + +; ================================================================ +; PRG load address header +; ================================================================ +.segment "LOADADDR" + .word $2000 + +; ================================================================ +; CODE +; ================================================================ +.segment "CODE" + +; ---- entry ---- +vim_main: + jsr CLALL ; close any open channels from shell + jsr vim_init + jsr full_redraw + +main_loop: + lda quit_flag + beq ml_no_quit + jmp vim_do_exit +ml_no_quit: + + jsr get_key ; waits for keypress; $FF = RUN/STOP + cmp #$FF + bne ml_key_ok + ; RUN/STOP pressed + lda cur_mode + beq ml_stop_quit ; normal mode: quit + lda #MODE_NORMAL ; other modes: back to normal + sta cur_mode + jsr full_redraw + jmp main_loop +ml_stop_quit: + lda #1 + sta quit_flag + jmp main_loop +ml_key_ok: + sta last_key + lda cur_mode + cmp #MODE_INSERT + bne ml_not_insert + jmp handle_insert +ml_not_insert: + cmp #MODE_CMD + bne ml_not_cmd + jmp handle_cmd +ml_not_cmd: + ; fall through = normal mode + +; ================================================================ +; NORMAL MODE +; ================================================================ +handle_normal: + lda last_key + cmp #'H' + beq nm_left + cmp #$9D ; cursor-left hardware key + beq nm_left + cmp #'L' + beq nm_right + cmp #$1D ; cursor-right hardware key + beq nm_right + cmp #'K' + beq nm_up + cmp #$91 ; cursor-up hardware key + beq nm_up + cmp #'J' + beq nm_down + cmp #$11 ; cursor-down hardware key + beq nm_down + cmp #'I' + beq nm_ins + cmp #'A' + beq nm_app + cmp #'X' + beq nm_del + cmp #'O' + beq nm_open + cmp #':' + beq nm_cmd + jmp nm_done +nm_left: + jsr cursor_left + jmp nm_done +nm_right: + jsr cursor_right + jmp nm_done +nm_up: + jsr cursor_up + jmp nm_done +nm_down: + jsr cursor_down + jmp nm_done +nm_ins: + lda #MODE_INSERT + sta cur_mode + jmp nm_done +nm_app: + jsr cursor_right + lda #MODE_INSERT + sta cur_mode + jmp nm_done +nm_del: + jsr delete_char + jmp nm_done +nm_open: + jsr open_line_below + lda #MODE_INSERT + sta cur_mode + jmp nm_done +nm_cmd: + lda #MODE_CMD + sta cur_mode + lda #0 + sta cmd_len +nm_done: + jsr full_redraw + jmp main_loop + +; ================================================================ +; INSERT MODE +; ================================================================ +handle_insert: + lda last_key + cmp #CR_CH + beq ins_nl + cmp #DEL_CH + beq ins_bs + cmp #$20 ; ignore control chars below $20 + bcc ins_done + cmp #$7F + bcs ins_done + jsr insert_char ; A = PETSCII char to insert + jsr cursor_right + jmp ins_done +ins_nl: + jsr do_newline + jmp ins_done +ins_bs: + jsr backspace_char +ins_done: + jsr full_redraw + jmp main_loop + +; ================================================================ +; COMMAND MODE +; ================================================================ +handle_cmd: + lda last_key + cmp #CR_CH + beq cmd_exec + cmp #DEL_CH + beq cmd_bs + cmp #$20 + bcc cmd_done + cmp #$7F + bcs cmd_done + ldx cmd_len + cpx #CMD_MAX + bcs cmd_done + sta cmd_buf,x + inc cmd_len + jmp cmd_done +cmd_bs: + lda cmd_len + beq cmd_done + dec cmd_len + jmp cmd_done +cmd_exec: + jsr run_cmd +cmd_done: + jsr full_redraw + jmp main_loop + +; ================================================================ +; EXIT +; ================================================================ +vim_do_exit: + jsr restore_cursor ; de-invert cursor char before leaving + rts ; return to kernal-os-skeleton shell + +; ================================================================ +; COMMAND EXECUTION +; ================================================================ +run_cmd: + lda cmd_len + beq rc_cancel + + ; --- :Q quit --- + cmp #1 + bne rc_not_q + lda cmd_buf + cmp #'Q' + bne rc_not_q + lda #1 + sta quit_flag + lda #MODE_NORMAL + sta cur_mode + rts + +rc_not_q: + ; --- :W [NAME] save --- + lda cmd_buf + cmp #'W' + bne rc_unknown + jsr do_save + lda #MODE_NORMAL + sta cur_mode + rts + +rc_unknown: + ; show "?" in status then return to normal + lda #msg_cmd_err + jsr show_status_text +rc_cancel: + lda #MODE_NORMAL + sta cur_mode + rts + +; ================================================================ +; FILE SAVE +; do_save: reads cmd_buf "W NAME" and writes text to disk +; ================================================================ +do_save: + ; need at least "W N" (3 chars: W, space, one char of name) + lda cmd_len + cmp #3 + bcs ds_len_ok + jmp ds_no_name +ds_len_ok: + lda cmd_buf + 1 + cmp #' ' + beq ds_space_ok + jmp ds_no_name +ds_space_ok: + + ; copy filename from cmd_buf[2..] to save_fn + ldy #2 + ldx #0 +ds_copy_name: + lda cmd_buf,y + beq ds_name_done + cmp #' ' + beq ds_name_done + sta save_fn,x + inx + iny + cpx #15 + bcc ds_copy_name +ds_name_done: + stx save_fn_len + lda #0 + sta save_fn,x + + ; build "0:NAME,S,W" in full_fn + jsr build_save_fname + + ; SETLFS + lda #SAVE_LFN + ldx #SAVE_DEV + ldy #2 + jsr SETLFS + + ; SETNAM + lda full_fn_len + ldx #full_fn + jsr SETNAM + + jsr OPEN + bcc ds_open_ok + ; open error + lda #msg_disk_err + jsr show_status_text + rts + +ds_open_ok: + lda #SAVE_LFN + jsr CHKOUT + bcc ds_write_rows + jsr CLRCH + lda #SAVE_LFN + jsr CLOSE + lda #msg_disk_err + jsr show_status_text + rts + +ds_write_rows: + lda #0 + sta save_row + +ds_row_loop: + ldx save_row + lda row_lo,x + sta ZP_LO + lda row_hi,x + sta ZP_HI + + ; find rightmost non-space column + ldy #SCR_COLS - 1 +ds_find_end: + lda (ZP_LO),y + cmp #$20 + bne ds_row_found + tya + beq ds_row_empty + dey + jmp ds_find_end + +ds_row_empty: + ; empty row: just write CR + lda #CR_CH + jsr CHROUT + jmp ds_row_done + +ds_row_found: + ; write cols 0..Y then CR + sty save_col_end + ldy #0 +ds_write_col: + lda (ZP_LO),y + jsr screen_to_petscii + jsr CHROUT + cpy save_col_end + beq ds_write_cr + iny + jmp ds_write_col + +ds_write_cr: + lda #CR_CH + jsr CHROUT + +ds_row_done: + inc save_row + lda save_row + cmp #SCR_ROWS + bne ds_row_loop + + ; close + jsr CLRCH + lda #SAVE_LFN + jsr CLOSE + + lda #msg_saved + jsr show_status_text + rts + +ds_no_name: + lda #msg_need_name + jsr show_status_text + rts + +; builds "0:save_fn,S,W" into full_fn, length into full_fn_len +build_save_fname: + ldx #0 + lda #'0' + sta full_fn,x + inx + lda #':' + sta full_fn,x + inx + ldy #0 +bsf_loop: + lda save_fn,y + beq bsf_suffix + sta full_fn,x + inx + iny + bne bsf_loop +bsf_suffix: + lda #',' + sta full_fn,x + inx + lda #'S' + sta full_fn,x + inx + lda #',' + sta full_fn,x + inx + lda #'W' + sta full_fn,x + inx + stx full_fn_len + lda #0 + sta full_fn,x + rts + +; ================================================================ +; CURSOR MOVEMENT +; ================================================================ +cursor_left: + lda cur_col + beq cl_wrap + dec cur_col + rts +cl_wrap: + lda cur_row + beq cl_top + dec cur_row + lda #SCR_COLS - 1 + sta cur_col +cl_top: rts + +cursor_right: + lda cur_col + cmp #SCR_COLS - 1 + bcs cr_wrap + inc cur_col + rts +cr_wrap: + lda cur_row + cmp #SCR_ROWS - 1 + bcs cr_bot + inc cur_row + lda #0 + sta cur_col +cr_bot: rts + +cursor_up: + lda cur_row + beq cu_top + dec cur_row +cu_top: rts + +cursor_down: + lda cur_row + cmp #SCR_ROWS - 1 + bcs cd_bot + inc cur_row +cd_bot: rts + +; ================================================================ +; TEXT EDITING +; ================================================================ + +; Set ZP_LO/ZP_HI to the screen RAM base for cur_row +row_ptr: + ldx cur_row + lda row_lo,x + sta ZP_LO + lda row_hi,x + sta ZP_HI + rts + +; Insert PETSCII char (in A) at cursor; shift rest of line right (last char lost) +insert_char: + jsr petscii_to_screen + sta ZP_TMP + jsr row_ptr + ldy #SCR_COLS - 1 +ic_shift: + cpy cur_col + beq ic_write + dey + lda (ZP_LO),y + iny + sta (ZP_LO),y + dey + jmp ic_shift +ic_write: + lda ZP_TMP + ldy cur_col + sta (ZP_LO),y + rts + +; Delete char at cursor; shift rest of line left, fill end with space +delete_char: + jsr row_ptr + ldy cur_col +dc_shift: + cpy #SCR_COLS - 1 + beq dc_space + iny + lda (ZP_LO),y + dey + sta (ZP_LO),y + iny + jmp dc_shift +dc_space: + lda #$20 + sta (ZP_LO),y + rts + +; Backspace: move cursor left then delete +backspace_char: + lda cur_col + beq bs_done + jsr cursor_left + jsr delete_char +bs_done:rts + +; RETURN in insert mode: move to col 0 of next row +do_newline: + lda cur_row + cmp #SCR_ROWS - 1 + bcs nl_done + inc cur_row + lda #0 + sta cur_col +nl_done:rts + +; Open new line below: advance row, clear it, col = 0 +open_line_below: + lda cur_row + cmp #SCR_ROWS - 1 + bcs ol_done + inc cur_row + lda #0 + sta cur_col + jsr clear_current_row +ol_done:rts + +clear_current_row: + jsr row_ptr + ldy #0 + lda #$20 +ccr_lp: sta (ZP_LO),y + iny + cpy #SCR_COLS + bne ccr_lp + rts + +; Clear all 24 text rows to spaces +clear_text: + lda #0 + sta ZP_TMP +ct_loop: + ldx ZP_TMP + lda row_lo,x + sta ZP_LO + lda row_hi,x + sta ZP_HI + ldy #0 + lda #$20 +ct_row: sta (ZP_LO),y + iny + cpy #SCR_COLS + bne ct_row + inc ZP_TMP + lda ZP_TMP + cmp #SCR_ROWS + bne ct_loop + rts + +; ================================================================ +; SCREEN RENDERING +; ================================================================ + +; Restore the saved char at (prev_row, prev_col) +restore_cursor: + ldx prev_row + lda row_lo,x + clc + adc prev_col + sta ZP_LO + lda row_hi,x + adc #0 + sta ZP_HI + lda prev_char + ldy #0 + sta (ZP_LO),y + rts + +; Show cursor by inverting char at (cur_row, cur_col); saves old char +show_cursor: + ldx cur_row + lda row_lo,x + clc + adc cur_col + sta ZP_LO + lda row_hi,x + adc #0 + sta ZP_HI + ldy #0 + lda (ZP_LO),y + sta prev_char + ora #$80 ; set bit 7 = reversed character + sta (ZP_LO),y + lda cur_row + sta prev_row + lda cur_col + sta prev_col + rts + +; Draw status bar at row 24 +draw_status: + ; clear status row + lda row_lo + 24 + sta ZP_LO + lda row_hi + 24 + sta ZP_HI + ldy #0 + lda #$20 +dstat_clr: + sta (ZP_LO),y + iny + cpy #SCR_COLS + bne dstat_clr + + lda cur_mode + cmp #MODE_INSERT + beq dstat_ins + cmp #MODE_CMD + beq dstat_cmd + + ; NORMAL + lda #str_normal + jsr write_str_to_status + rts + +dstat_ins: + lda #str_insert + jsr write_str_to_status + rts + +dstat_cmd: + ; write ":" then cmd_buf + lda row_lo + 24 + sta ZP_LO + lda row_hi + 24 + sta ZP_HI + lda #$3A ; screen code for ':' + ldy #0 + sta (ZP_LO),y + iny + ldx #0 +dstat_cmd_lp: + cpx cmd_len + beq dstat_done + lda cmd_buf,x + jsr petscii_to_screen + sta (ZP_LO),y + iny + inx + cpy #SCR_COLS + bcc dstat_cmd_lp +dstat_done: + rts + +; Write null-terminated PETSCII string (A=lo, Y=hi) to status row +write_str_to_status: + sta STR_LO + sty STR_HI + lda row_lo + 24 + sta ZP_LO + lda row_hi + 24 + sta ZP_HI + ldy #0 +wss_lp: + lda (STR_LO),y + beq wss_done + jsr petscii_to_screen + sta (ZP_LO),y + iny + cpy #SCR_COLS + bcc wss_lp +wss_done: + rts + +; Copy a PETSCII string directly to status (no screen-code conversion) +; Used for messages that already have screen codes (msg_saved etc.) +; Actually we just reuse write_str_to_status since it converts +show_status_text: + jsr write_str_to_status + rts + +; Restore cursor, redraw cursor, redraw status bar +full_redraw: + jsr restore_cursor + jsr show_cursor + jsr draw_status + rts + +; ================================================================ +; INIT +; ================================================================ +vim_init: + lda #0 + sta cur_row + sta cur_col + sta prev_row + sta prev_col + sta cur_mode + sta cmd_len + sta quit_flag + sta save_row + sta save_col_end + lda #$20 ; cursor starts on a space + sta prev_char + jsr clear_text + rts + +; ================================================================ +; KEY INPUT +; ================================================================ +get_key: +gk_loop: + jsr STOPKEY + bne gk_getin + lda #$FF ; RUN/STOP = special exit code + rts +gk_getin: + jsr GETIN + beq gk_loop ; no key yet, keep polling + rts + +; ================================================================ +; CHARACTER CONVERSION +; ================================================================ + +; PETSCII → screen code +; $41-$5A (A-Z unshifted) → $01-$1A (subtract $40) +; $61-$7A (a-z lowercase) → $01-$1A (subtract $60) +; $20-$3F (space,nums,punct) → same +; others: pass through +petscii_to_screen: + cmp #$41 + bcc pts_pass + cmp #$5B + bcc pts_upper + cmp #$61 + bcc pts_pass + cmp #$7B + bcc pts_lower +pts_pass: + rts +pts_upper: + sec + sbc #$40 + rts +pts_lower: + sec + sbc #$60 + rts + +; screen code → PETSCII (inverse of above) +; $01-$1A → $41-$5A (add $40) +; $20-$3F → same +; $00 → '@' +; others: return space $20 +screen_to_petscii: + cmp #$01 + bcc stp_at + cmp #$1B + bcc stp_letter + cmp #$20 + bcc stp_space + cmp #$40 + bcc stp_pass +stp_space: + lda #$20 + rts +stp_at: + lda #$40 ; '@' + rts +stp_letter: + clc + adc #$40 + rts +stp_pass: + rts + +; ================================================================ +; RODATA +; ================================================================ +.segment "RODATA" + +str_normal: + .byte "NORMAL (hjkl=move i=ins x=del :=cmd)", 0 + +str_insert: + .byte "INSERT (type text, DEL=bksp, STOP=normal)", 0 + +msg_cmd_err: + .byte "? unknown command", 0 + +msg_saved: + .byte "saved.", 0 + +msg_disk_err: + .byte "disk error.", 0 + +msg_need_name: + .byte "usage: :w filename", 0 + +; Row-base lookup tables (25 entries: rows 0-23 = text, row 24 = status) +row_lo: + .byte <(SCRRAM + 0*40), <(SCRRAM + 1*40), <(SCRRAM + 2*40) + .byte <(SCRRAM + 3*40), <(SCRRAM + 4*40), <(SCRRAM + 5*40) + .byte <(SCRRAM + 6*40), <(SCRRAM + 7*40), <(SCRRAM + 8*40) + .byte <(SCRRAM + 9*40), <(SCRRAM + 10*40), <(SCRRAM + 11*40) + .byte <(SCRRAM + 12*40), <(SCRRAM + 13*40), <(SCRRAM + 14*40) + .byte <(SCRRAM + 15*40), <(SCRRAM + 16*40), <(SCRRAM + 17*40) + .byte <(SCRRAM + 18*40), <(SCRRAM + 19*40), <(SCRRAM + 20*40) + .byte <(SCRRAM + 21*40), <(SCRRAM + 22*40), <(SCRRAM + 23*40) + .byte <(SCRRAM + 24*40) + +row_hi: + .byte >(SCRRAM + 0*40), >(SCRRAM + 1*40), >(SCRRAM + 2*40) + .byte >(SCRRAM + 3*40), >(SCRRAM + 4*40), >(SCRRAM + 5*40) + .byte >(SCRRAM + 6*40), >(SCRRAM + 7*40), >(SCRRAM + 8*40) + .byte >(SCRRAM + 9*40), >(SCRRAM + 10*40), >(SCRRAM + 11*40) + .byte >(SCRRAM + 12*40), >(SCRRAM + 13*40), >(SCRRAM + 14*40) + .byte >(SCRRAM + 15*40), >(SCRRAM + 16*40), >(SCRRAM + 17*40) + .byte >(SCRRAM + 18*40), >(SCRRAM + 19*40), >(SCRRAM + 20*40) + .byte >(SCRRAM + 21*40), >(SCRRAM + 22*40), >(SCRRAM + 23*40) + .byte >(SCRRAM + 24*40) + +; ================================================================ +; BSS (zero-initialised by the OS / KERNAL CINT on cold start; +; vim_init zeros what it cares about explicitly) +; ================================================================ +.segment "BSS" + +cur_row: .res 1 +cur_col: .res 1 +prev_row: .res 1 +prev_col: .res 1 +prev_char: .res 1 ; screen code saved before cursor inversion +cur_mode: .res 1 +cmd_len: .res 1 +cmd_buf: .res CMD_MAX + 1 +last_key: .res 1 +quit_flag: .res 1 +save_row: .res 1 +save_col_end: .res 1 +save_fn: .res 16 ; extracted filename (no path) +save_fn_len: .res 1 +full_fn: .res 24 ; "0:filename,S,W" +full_fn_len: .res 1 diff --git a/samples/kernal-os-skeleton/vim.cfg b/samples/kernal-os-skeleton/vim.cfg new file mode 100644 index 0000000..93b7bfc --- /dev/null +++ b/samples/kernal-os-skeleton/vim.cfg @@ -0,0 +1,11 @@ +MEMORY { + LOADADDR: start = $1FFE, size = $0002, type = ro, file = %O; + MAIN: start = $2000, size = $7FFF, type = rw, file = %O; +} + +SEGMENTS { + LOADADDR: load = LOADADDR, type = ro; + CODE: load = MAIN, type = ro; + RODATA: load = MAIN, type = ro; + BSS: load = MAIN, type = bss, define = yes; +} diff --git a/samples/kernal-os-skeleton/vim.o b/samples/kernal-os-skeleton/vim.o new file mode 100644 index 0000000..8719f20 Binary files /dev/null and b/samples/kernal-os-skeleton/vim.o differ diff --git a/samples/kernal-os-skeleton/vim.prg b/samples/kernal-os-skeleton/vim.prg new file mode 100644 index 0000000..a5ee53c Binary files /dev/null and b/samples/kernal-os-skeleton/vim.prg differ