mirror of
https://github.com/bobbimanners/emailler.git
synced 2024-11-17 00:04:57 +00:00
2851 lines
70 KiB
C
2851 lines
70 KiB
C
////////////////////////////////////////////////////////////////////////////
|
|
// Apple //e Enhanced text editor
|
|
// Supports Apple //e Auxiliary memory and RamWorks style expansions
|
|
// Bobbi July-Sept 2020
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// See GitHub for open issues!!!
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Note: Use my fork of cc65 to get a flashing cursor!!
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <conio.h>
|
|
#include <ctype.h>
|
|
#include <dio.h>
|
|
#include <dirent.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#define NCOLS 80 // Width of editing screen
|
|
#define NROWS 22 // Height of editing screen
|
|
#define CURSORROW 10 // Row cursor initially shown on (if enough text)
|
|
#define PROMPT_ROW NROWS + 1 // Row where input prompt is shown
|
|
#define IOSZ 1024 // Size of chunks to use when loading/saving file
|
|
#define CUTBUFSZ 8192 // Size of cut buffer. Must be >IOSZ.
|
|
|
|
#define INFOADDR 0x0800 // Aux mem address for info about buffer
|
|
#define GAPBUFADDR 0x0880 // Aux mem address for gapbuffer itself
|
|
|
|
#define EOL '\r' // ProDOS uses CR line endings
|
|
|
|
#define BELL 0x07
|
|
#define BACKSPACE 0x08
|
|
#define RETURN 0x0d
|
|
#define ESC 0x1b
|
|
#define DELETE 0x7f
|
|
|
|
// Aux memory from GAPBUFADDR to 0xbffe (0xbfff is zero pad byte)
|
|
#define BUFSZ (0xbffe - GAPBUFADDR)
|
|
char iobuf[CUTBUFSZ]; // Buffer for disk I/O and cut/paste
|
|
uint8_t banktbl[1+8*16]; // Handles up to 8MB. Map of banks.
|
|
uint8_t auxbank = 0; // Currently selected aux bank (physical)
|
|
uint8_t l_auxbank = 1; // Currently selected aux bank (logical)
|
|
uint16_t cutbuflen = 0; // Length of data in cut buffer
|
|
|
|
// The following fields, plus the gap buffer, represent the state of
|
|
// the current buffer. These are stashed in each aux page from 0x200 up.
|
|
// Total 80 + 3 + 2 + 2 + 1 + 2 + 2 = 92 bytes
|
|
#define BUFINFOSZ 92
|
|
char filename[80] = "";
|
|
uint8_t status[3] = {0, 0, 0}; // status[0] is 1 if file has been modified
|
|
// status[1] is 1 if should warn for overwrite
|
|
// status[2] is part # for multi-part files
|
|
uint16_t gapbegin = 0;
|
|
uint16_t gapend = BUFSZ - 1;
|
|
uint8_t canundo = 0; // For OA-Z "Undo"
|
|
uint16_t oldgapbegin = 0; // ditto
|
|
uint16_t oldgapend = BUFSZ - 1; // ditto
|
|
|
|
char userentry[82] = ""; // Couple extra chars so we can store 80 col line
|
|
char search[80] = "";
|
|
char replace[80] = "";
|
|
char startdir[80] = "";
|
|
|
|
uint8_t rowlen[NROWS]; // Number of chars on each row of screen
|
|
|
|
// Interface to read_char_update_pos()
|
|
uint8_t do_print;
|
|
uint16_t pos = 0, startsel = 65535U, endsel = 65535U;
|
|
uint8_t row, col;
|
|
|
|
uint8_t cursrow, curscol; // Cursor position is kept here by draw_screen()
|
|
|
|
uint8_t email_mode; // If 0 quit to ProDOS
|
|
// If 1 quit to EMAIL.SYSTEM
|
|
// If 2 ask about attachments and quit to either
|
|
// ATTACHER.SYSTEM (if y) or EMAIL.SYSTEM (if n)
|
|
|
|
enum editmode {SEL_NONE, SEL_SELECT, SRCH1, SRCH2, SRCH3};
|
|
enum editmode mode;
|
|
|
|
// Mousetext Open-Apple
|
|
char openapple[] = "\x0f\x1b""A\x18\x0e";
|
|
|
|
// Mousetext Closed-Apple
|
|
char closedapple[] = "\x0f\x1b""@\x18\x0e";
|
|
|
|
char openfilemsg[] =
|
|
" Select file from tree browser, or [Tab] to enter filename. [Esc] cancels.";
|
|
|
|
char namefilemsg[] =
|
|
" Enter a new filename to create file. Select an existing file to overwrite it.";
|
|
|
|
// The following are used for reconnecting /RAM and /RAM3 on exit
|
|
uint16_t s3d1vec;
|
|
uint16_t s3d2vec;
|
|
uint8_t s3d1dev;
|
|
uint8_t s3d2dev;
|
|
|
|
/*
|
|
* Return number of bytes of freespace in gapbuf
|
|
*/
|
|
#define FREESPACE() (gapend - gapbegin + 1)
|
|
|
|
/*
|
|
* Return number of bytes of data in gapbuf
|
|
*/
|
|
#define DATASIZE() (gapbegin + (BUFSZ - 1 - gapend))
|
|
|
|
/*
|
|
* Obtain current position in gapbuf
|
|
* This is the positon where the next character will be inserted
|
|
*/
|
|
#define GETPOS() (gapbegin)
|
|
|
|
/*
|
|
* Obtain one byte from the gapbuf[] in aux memory
|
|
* Must be in LC
|
|
* i - Index into gapbuf[]
|
|
* Returns value at gapbuf[i]
|
|
*/
|
|
#pragma code-name (push, "LC")
|
|
char get_gapbuf(uint16_t i) {
|
|
*(uint16_t*)(0xfe) = (uint16_t)GAPBUFADDR + i; // Stuff address in Zero Page
|
|
__asm__("lda %v", auxbank);
|
|
__asm__("sta $c073"); // Set aux bank
|
|
__asm__("sta $c003"); // Read aux mem
|
|
__asm__("lda ($fe)");
|
|
__asm__("sta $ff");
|
|
__asm__("sta $c002"); // Read main mem
|
|
__asm__("lda #$00");
|
|
__asm__("sta $c073"); // Set aux bank back to 0
|
|
return *(uint8_t*)(0xff);
|
|
}
|
|
#pragma code-name (pop)
|
|
|
|
/*
|
|
* Set one byte in the gapbuf[] in aux memory
|
|
* Must be in LC
|
|
* i - Index into gapbuf
|
|
* c - Byte value to set
|
|
*/
|
|
#pragma code-name (push, "LC")
|
|
void set_gapbuf(uint16_t i, char c) {
|
|
__asm__("lda %v", auxbank);
|
|
__asm__("sta $c073"); // Set aux bank
|
|
__asm__("sta $c005"); // Write aux mem
|
|
(*(char*)((char*)GAPBUFADDR + i)) = c;
|
|
__asm__("sta $c004"); // Write main mem
|
|
__asm__("lda #$00");
|
|
__asm__("sta $c073"); // Set aux bank back to 0
|
|
}
|
|
#pragma code-name (pop)
|
|
|
|
/*
|
|
* Do a memmove() on aux memory. Uses indices into gapbuf[].
|
|
* Must be in LC
|
|
* dst - Destination index into gapbuf[]
|
|
* src - Source index into gapbuf[]
|
|
* n - Length in bytes
|
|
*/
|
|
#pragma code-name (push, "LC")
|
|
void move_in_gapbuf(uint16_t dst, uint16_t src, size_t n) {
|
|
if (dst > src) {
|
|
// Start with highest addr and copy downwards
|
|
*(uint16_t*)(0xfa) = n; // Stuff sz in ZP
|
|
*(uint16_t*)(0xfc) = (uint16_t)GAPBUFADDR + src - 1; // Stuff src in ZP
|
|
*(uint16_t*)(0xfe) = (uint16_t)GAPBUFADDR + dst - 1; // Stuff dst in ZP
|
|
__asm__("lda %v", auxbank);
|
|
__asm__("sta $c073"); // Set aux bank
|
|
__asm__("sta $c005"); // Write aux mem
|
|
__asm__("sta $c003"); // Read aux mem
|
|
__asm__("clc"); // Prepare the source pointer
|
|
__asm__("lda $fd"); // MSB of source
|
|
__asm__("adc $fb"); // MSB of n
|
|
__asm__("sta $fd");
|
|
|
|
__asm__("clc"); // Prepare the dest pointer
|
|
__asm__("lda $ff"); // MSB of dest
|
|
__asm__("adc $fb"); // MSB of n
|
|
__asm__("sta $ff");
|
|
|
|
__asm__("ldy $fa"); // LSB of n
|
|
dl1:
|
|
__asm__("lda ($fc),y");
|
|
__asm__("sta ($fe),y");
|
|
__asm__("cpy #$00"); // Copy leftover bytes
|
|
__asm__("beq %g", ds1);
|
|
__asm__("dey");
|
|
__asm__("jmp %g", dl1);
|
|
ds1:
|
|
__asm__("ldx #$00");
|
|
dl2:
|
|
__asm__("cpx $fb"); // MSB of n
|
|
__asm__("beq %g", ds2); // No more complete 256 byte blocks, done!
|
|
__asm__("dec $fd"); // MSB of source
|
|
__asm__("dec $ff"); // MSB of dest
|
|
__asm__("ldy #$00"); // Copy one block of 256 bytes
|
|
dl3:
|
|
__asm__("dey");
|
|
__asm__("lda ($fc),y");
|
|
__asm__("sta ($fe),y");
|
|
__asm__("cpy #$00");
|
|
__asm__("bne %g", dl3);
|
|
__asm__("inx");
|
|
__asm__("jmp %g", dl2);
|
|
ds2:
|
|
__asm__("sta $c002"); // Read main mem
|
|
__asm__("sta $c004"); // Write main mem
|
|
__asm__("lda #$00");
|
|
__asm__("sta $c073"); // Set aux bank back to 0
|
|
} else {
|
|
// Start with lowest addr and copy upwards
|
|
*(uint16_t*)(0xfa) = n; // Stuff sz in ZP
|
|
*(uint16_t*)(0xfc) = (uint16_t)GAPBUFADDR + src; // Stuff src in ZP
|
|
*(uint16_t*)(0xfe) = (uint16_t)GAPBUFADDR + dst; // Stuff dst in ZP
|
|
__asm__("lda %v", auxbank);
|
|
__asm__("sta $c073"); // Set aux bank
|
|
__asm__("sta $c005"); // Write aux mem
|
|
__asm__("sta $c003"); // Read aux mem
|
|
__asm__("ldx #$00");
|
|
al1:
|
|
__asm__("cpx $fb"); // MSB of n
|
|
__asm__("beq %g", as1); // No more complete 256 byte blocks
|
|
|
|
__asm__("ldy #$00"); // Copy one block of 256 bytes
|
|
al2:
|
|
__asm__("lda ($fc),y");
|
|
__asm__("sta ($fe),y");
|
|
__asm__("iny");
|
|
__asm__("bne %g", al2);
|
|
|
|
__asm__("inc $fd"); // MSB of source
|
|
__asm__("inc $ff"); // MSB of dest
|
|
__asm__("inx");
|
|
__asm__("jmp %g", al1);
|
|
as1:
|
|
__asm__("ldy #$00"); // Copy leftover bytes
|
|
al3:
|
|
__asm__("cpy $fa"); // LSB of n
|
|
__asm__("beq %g", as2); // Done!
|
|
__asm__("lda ($fc),y");
|
|
__asm__("sta ($fe),y");
|
|
__asm__("iny");
|
|
__asm__("jmp %g", al3);
|
|
as2:
|
|
__asm__("sta $c002"); // Read main mem
|
|
__asm__("sta $c004"); // Write main mem
|
|
__asm__("lda #$00");
|
|
__asm__("sta $c073"); // Set aux bank back to 0
|
|
}
|
|
}
|
|
#pragma code-name (pop)
|
|
|
|
#pragma data-name (push, "LC")
|
|
char needle[80] = ""; // Must be in LC memory
|
|
#pragma data-name (pop)
|
|
|
|
/*
|
|
* Do a strstr() on aux memory. Uses index into gapbuf[].
|
|
* Must be in LC
|
|
* i - Index into gapbuf[]
|
|
* srch - String to search for
|
|
* loc - Location where string found (index into gapbuf[]) returned here
|
|
* Returns 1 if found, 0 otherwise
|
|
*/
|
|
#pragma code-name (push, "LC")
|
|
#pragma optimize (off)
|
|
uint8_t search_in_gapbuf(uint16_t i, char *srch, uint16_t *loc) {
|
|
|
|
__asm__("lda $c083"); // Read and write LC RAM bank 2
|
|
__asm__("lda $c083"); // Read and write LC RAM bank 2
|
|
memcpy(needle, srch, 80);
|
|
|
|
// ZP usage
|
|
// $f7: TMP1
|
|
// $f8-$f9: PTR4 in original strstr.s code.
|
|
// $fa-$fb: PTR1 in original strstr.s code.
|
|
// $fc-$fd: PTR2 in original strstr.s code.
|
|
// $fe-$ff: PTR3 in original strstr.s code.
|
|
*(uint16_t*)(0xfc) = (uint16_t)needle;
|
|
*(uint16_t*)(0xf8) = (uint16_t)needle; // Another copy of needle ptr
|
|
|
|
*(uint16_t*)(0xfa) = (uint16_t)GAPBUFADDR + i; // Haystack pointer
|
|
__asm__("lda %v", auxbank);
|
|
__asm__("sta $c073"); // Set aux bank
|
|
__asm__("sta $c005"); // Write aux mem
|
|
__asm__("sta $c003"); // Read aux mem
|
|
|
|
__asm__("ldy #$00");
|
|
__asm__("lda ($fc),y"); // Get first byte of needle
|
|
__asm__("beq %g", found); // Needle is empty --> we're done
|
|
|
|
// Search for beginning of string
|
|
__asm__("sta $f7"); // Save start of needle
|
|
l1:
|
|
__asm__("lda ($fa),y"); // Get next char from haystack
|
|
__asm__("beq %g", notfound); // Jump if end
|
|
__asm__("cmp $f7"); // Start of needle found?
|
|
__asm__("beq %g", l2); // Jump if so
|
|
__asm__("iny"); // Next char
|
|
__asm__("bne %g", l1);
|
|
__asm__("inc $fb"); // Bump high byte of haystack
|
|
__asm__("bne %g", l1); // Branch always
|
|
|
|
// We found the start of needle in haystack
|
|
l2:
|
|
__asm__("tya"); // Get offset
|
|
__asm__("clc");
|
|
__asm__("adc $fa");
|
|
__asm__("sta $fa"); // Make ptr1 point to start
|
|
__asm__("bcc %g", l3);
|
|
__asm__("inc $fb");
|
|
|
|
// ptr1 points to the start of needle now. Setup temporary pointers for the
|
|
// search. The low byte of ptr4 is already set.
|
|
l3:
|
|
__asm__("sta $fe"); // LSB PTR3
|
|
__asm__("lda $fb"); // MSB PTR1
|
|
__asm__("sta $ff"); // MSB PTR3
|
|
__asm__("lda $fd"); // MSB PTR2
|
|
__asm__("sta $f9"); // MSB PTR4
|
|
__asm__("ldy #1"); // First char is identical, so start on second
|
|
|
|
// Do the compare
|
|
l4:
|
|
__asm__("lda ($f8),y"); // Get char from needle
|
|
__asm__("beq %g", found); // Jump if end of needle (-> found)
|
|
__asm__("cmp ($fe),y"); // Compare with haystack
|
|
__asm__("bne %g", l5); // Jump if not equal
|
|
__asm__("iny"); // Next char
|
|
__asm__("bne %g", l4);
|
|
__asm__("inc $ff");
|
|
__asm__("inc $f9"); // Bump hi byte of pointers
|
|
__asm__("bne %g", l4); // Next char (branch always)
|
|
|
|
// The strings did not compare equal, search next start of needle
|
|
l5:
|
|
__asm__("ldy #1"); // Start after this char
|
|
__asm__("bne %g", l1); // Branch always
|
|
|
|
// We found the needle
|
|
found:
|
|
__asm__("sta $c002"); // Read main mem
|
|
__asm__("sta $c004"); // Write main mem
|
|
__asm__("lda #$00");
|
|
__asm__("sta $c073"); // Set aux bank back to 0
|
|
*loc = *(uint16_t*)0xfa - GAPBUFADDR;
|
|
return 1;
|
|
|
|
// We reached end of haystack without finding needle
|
|
notfound:
|
|
__asm__("sta $c002"); // Read main mem
|
|
__asm__("sta $c004"); // Write main mem
|
|
__asm__("lda #$00");
|
|
__asm__("sta $c073"); // Set aux bank back to 0
|
|
return 0;
|
|
|
|
//#else
|
|
// char *p = strstr(gapbuf + i, needle);
|
|
// if (p) {
|
|
// *loc = p - gapbuf;
|
|
// return 1;
|
|
// } else
|
|
// return 0;
|
|
//#endif
|
|
}
|
|
#pragma optimize (on)
|
|
#pragma code-name (pop)
|
|
|
|
/*
|
|
* Annoying beep
|
|
*/
|
|
void beep(void) {
|
|
uint8_t *p = (uint8_t*)0xc030; // Speaker
|
|
uint8_t junk;
|
|
uint16_t i;
|
|
for (i = 0; i < 200; ++i) {
|
|
junk = *p;
|
|
for (junk = 0; junk < 50; ++junk); // Reduce pitch
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Clear to EOL
|
|
*/
|
|
void clreol(void) {
|
|
uint8_t x = wherex(), y = wherey();
|
|
cclear(80 - x);
|
|
gotoxy(x, y);
|
|
}
|
|
|
|
/*
|
|
* Clear to EOL, wrap to next line
|
|
*/
|
|
void clreol_wrap(void) {
|
|
uint8_t x = wherex();
|
|
cclear(80 - x);
|
|
gotox(x);
|
|
}
|
|
|
|
/*
|
|
* Clear line
|
|
*/
|
|
void clrline(void) {
|
|
uint8_t x = wherex();
|
|
gotox(0);
|
|
cclear(80);
|
|
gotox(x);
|
|
}
|
|
|
|
/*
|
|
* Put cursor at beginning of PROMPT_ROW
|
|
*/
|
|
void goto_prompt_row(void) {
|
|
gotoxy(0, PROMPT_ROW);
|
|
}
|
|
|
|
/*
|
|
* Refresh the status line at the bottom of the screen
|
|
*/
|
|
void update_status_line(void) {
|
|
uint8_t nofile = 0;
|
|
uint8_t l;
|
|
|
|
static char selmsg1[] = ": Go to end of selection, then [Return]";
|
|
static char selmsg2[] = ": Go to target, then [Return] to ";
|
|
|
|
goto_prompt_row();
|
|
|
|
if (strlen(filename) == 0) {
|
|
strcpy(filename, "<scratch>");
|
|
nofile = 1;
|
|
}
|
|
revers(1);
|
|
switch (mode) {
|
|
case SEL_NONE:
|
|
if (status[2] == 0) {
|
|
cprintf("OA-? Help | [%03u] %c File:%s %2uKB free",
|
|
l_auxbank, status[0] ? '*' : ' ', filename,
|
|
(FREESPACE() + 512) / 1024);
|
|
l = 44 - strlen(filename);
|
|
} else {
|
|
snprintf(userentry, 80, "%s Part:%u", filename, status[2]);
|
|
cprintf("OA-? Help | [%03u] %c File:%s %2uKB free",
|
|
l_auxbank, status[0] ? '*' : ' ', userentry,
|
|
(FREESPACE() + 512) / 1024);
|
|
l = 45 - strlen(userentry);
|
|
}
|
|
break;
|
|
case SEL_SELECT:
|
|
cprintf("Select: OA-[Space] to end");
|
|
l = 80 - 25;
|
|
break;
|
|
case SRCH1:
|
|
cprintf("Searching ...");
|
|
l = 80 - 13;
|
|
break;
|
|
case SRCH2:
|
|
cprintf("Searching - wrapped ...");
|
|
l = 80 - 23;
|
|
break;
|
|
case SRCH3:
|
|
if (status[2] == 0) {
|
|
cprintf("OA-? Help | [%03u] %c File:%s %2uKB free | Not Found",
|
|
l_auxbank, status[0] ? '*' : ' ', filename, (FREESPACE() + 512) / 1024);
|
|
l = 44 - 12 - strlen(filename);
|
|
} else {
|
|
snprintf(userentry, 80, "%s Part:%u", filename, status[2]);
|
|
cprintf("OA-? Help | [%03u] %c File:%s %2uKB free | Not Found",
|
|
l_auxbank, status[0] ? '*' : ' ', userentry,
|
|
(FREESPACE() + 512) / 1024);
|
|
l = 45 - 12 - strlen(userentry);
|
|
}
|
|
break;
|
|
}
|
|
cclear(l);
|
|
revers(0);
|
|
|
|
if (nofile)
|
|
strcpy(filename, "");
|
|
|
|
gotoxy(curscol, cursrow);
|
|
cursor(1);
|
|
}
|
|
|
|
/*
|
|
* Set modified status
|
|
*/
|
|
void set_modified(uint8_t mod) {
|
|
if (status[0] == mod)
|
|
return;
|
|
status[0] = mod;
|
|
update_status_line();
|
|
}
|
|
|
|
/*
|
|
* Prompt for a name in the bottom line of the screen
|
|
* Returns number of chars read.
|
|
* prompt - Message to display before > prompt
|
|
* is_file - if 1, restrict chars to those allowed in ProDOS filename
|
|
* Returns number of chars read, or 255 if ESC pressed
|
|
*/
|
|
uint8_t prompt_for_name(char *prompt, uint8_t is_file) {
|
|
uint16_t i;
|
|
char c;
|
|
cursor(0);
|
|
goto_prompt_row();
|
|
clreol();
|
|
revers(1);
|
|
cprintf("%s>", prompt);
|
|
revers(0);
|
|
gotox(2 + strlen(prompt));
|
|
i = 0;
|
|
while (1) {
|
|
c = cgetc();
|
|
if (is_file && !isalnum(c) && (c != RETURN) && (c != BACKSPACE) &&
|
|
(c != DELETE) && (c != ESC) && (c != '.') && (c != '/')) {
|
|
beep();
|
|
continue;
|
|
}
|
|
switch (c) {
|
|
case RETURN:
|
|
goto done;
|
|
case BACKSPACE:
|
|
case DELETE:
|
|
if (i > 0) {
|
|
gotox(wherex() - 1);
|
|
cputc(' ');
|
|
gotox(wherex() - 1);
|
|
--i;
|
|
} else
|
|
beep();
|
|
break;
|
|
case ESC:
|
|
userentry[0] = '\0';
|
|
i = 255;
|
|
goto esc_pressed;
|
|
default:
|
|
cputc(c);
|
|
userentry[i++] = c;
|
|
}
|
|
if (i == 79)
|
|
goto done;
|
|
}
|
|
done:
|
|
userentry[i] = '\0';
|
|
esc_pressed:
|
|
clrline();
|
|
gotoxy(curscol, cursrow);
|
|
cursor(1);
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* Prompt ok?
|
|
* Returns 0 for yes
|
|
* 1 for no
|
|
* 2 for ESC
|
|
*/
|
|
char prompt_okay(char *msg) {
|
|
char c;
|
|
cursor(0);
|
|
goto_prompt_row();
|
|
clreol();
|
|
revers(1);
|
|
cprintf("%s (y/n/ESC)", msg);
|
|
revers(0);
|
|
while (1) {
|
|
c = cgetc();
|
|
if ((c == 'y') || (c == 'Y') || (c == 'n') || (c == 'N') || c == ESC)
|
|
break;
|
|
beep();
|
|
}
|
|
if ((c == 'y') || (c == 'Y'))
|
|
c = 0;
|
|
else if (c == ESC)
|
|
c = 2;
|
|
else
|
|
c = 1;
|
|
update_status_line();
|
|
return c;
|
|
}
|
|
|
|
/*
|
|
* Error message
|
|
*/
|
|
void show_error(char *msg) {
|
|
cursor(0);
|
|
goto_prompt_row();
|
|
clreol();
|
|
beep();
|
|
revers(1);
|
|
cprintf("%s [Press Any Key]", msg);
|
|
revers(0);
|
|
cgetc();
|
|
update_status_line();
|
|
}
|
|
|
|
/*
|
|
* Insert a character into gapbuf at current position
|
|
* c - character to insert
|
|
*/
|
|
void insert_char(char c) {
|
|
if (FREESPACE()) {
|
|
set_gapbuf(gapbegin++, c);
|
|
return;
|
|
}
|
|
beep();
|
|
}
|
|
|
|
/*
|
|
* Delete the character to the left of the current position
|
|
*/
|
|
void delete_char(void) {
|
|
if (gapbegin == 0) {
|
|
beep();
|
|
return;
|
|
}
|
|
--gapbegin;
|
|
}
|
|
|
|
/*
|
|
* Delete the character to the right of the current position
|
|
*/
|
|
void delete_char_right(void) {
|
|
if (gapend == BUFSZ - 1) {
|
|
beep();
|
|
return;
|
|
}
|
|
++gapend;
|
|
}
|
|
|
|
/*
|
|
* Move the current position
|
|
* pos - position to which to move
|
|
*/
|
|
void jump_pos(uint16_t pos) {
|
|
uint16_t l;
|
|
if (pos > BUFSZ - 1) {
|
|
beep();
|
|
return;
|
|
}
|
|
if (pos == GETPOS())
|
|
return;
|
|
if (pos > GETPOS()) {
|
|
l = pos - gapbegin;
|
|
move_in_gapbuf(gapbegin, gapend + 1, l);
|
|
gapbegin += l;
|
|
gapend += l;
|
|
} else {
|
|
l = gapbegin - pos;
|
|
move_in_gapbuf(gapend - l + 1, gapbegin - l, l);
|
|
gapbegin -= l;
|
|
gapend -= l;
|
|
}
|
|
if (mode == SEL_SELECT)
|
|
endsel = gapbegin;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Go to next tabstop
|
|
*/
|
|
uint8_t next_tabstop(uint8_t col) {
|
|
return (col / 8) * 8 + 8;
|
|
}
|
|
|
|
#define FROMAUX 0
|
|
#define TOAUX 1
|
|
/*
|
|
* Copy to/from aux memory using AUXMOVE
|
|
*/
|
|
void copyaux(char *src, char *dst, uint16_t len, uint8_t dir) {
|
|
char **a1 = (char**)0x3c;
|
|
char **a2 = (char**)0x3e;
|
|
char **a4 = (char**)0x42;
|
|
*a1 = src;
|
|
*a2 = src + len - 1; // AUXMOVE moves length+1 bytes!!
|
|
*a4 = dst;
|
|
if (dir == TOAUX) {
|
|
__asm__("sec"); // Copy main->aux
|
|
__asm__("jsr $c311"); // AUXMOVE
|
|
} else {
|
|
__asm__("clc"); // Copy aux->main
|
|
__asm__("jsr $c311"); // AUXMOVE
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Translate logic aux bank number to physical.
|
|
* This is necessary because there may be 'holes' in the banks
|
|
* Logical banks are numbered 1..maxbank contiguously.
|
|
* Physical banks start at 0 and may be non-contiguous.
|
|
*/
|
|
uint8_t bank_log_to_phys(uint8_t l) {
|
|
return banktbl[l];
|
|
}
|
|
|
|
/*
|
|
* Change the active bank of aux memory
|
|
* Current physical bank is in auxbank
|
|
* logbank - desired logical bank
|
|
*/
|
|
#pragma code-name (push, "LC")
|
|
void change_aux_bank(uint8_t logbank) {
|
|
if (logbank > banktbl[0]) {
|
|
show_error("Nonexistent bank");
|
|
return;
|
|
}
|
|
l_auxbank = logbank;
|
|
// Saves filename[], status, gapbegin, gapend (BUFINFOSZ bytes)
|
|
__asm__("lda %v", auxbank);
|
|
__asm__("sta $c073"); // Set aux bank
|
|
copyaux(filename, (void*)INFOADDR, BUFINFOSZ, TOAUX);
|
|
// Load new filename[], status, gapbegin, gapend (BUFINFOSZ bytes)
|
|
auxbank = bank_log_to_phys(l_auxbank);
|
|
__asm__("lda %v", auxbank);
|
|
__asm__("sta $c073"); // Set aux bank
|
|
copyaux((void*)INFOADDR, filename, BUFINFOSZ, FROMAUX);
|
|
__asm__("lda #$00");
|
|
__asm__("sta $c073"); // Set aux bank back to 0
|
|
}
|
|
#pragma code-name (pop)
|
|
|
|
/*
|
|
* Used by load_file() when opening a new bank for a large file
|
|
* and also by CA-E "Extend file"
|
|
* Returns 0 if all is well, 1 if we run into an already occupied bank
|
|
*/
|
|
uint8_t open_new_aux_bank(uint8_t partnum) {
|
|
strcpy(userentry, filename);
|
|
status[2] = partnum;
|
|
if (l_auxbank >= banktbl[0])
|
|
return 1;
|
|
change_aux_bank(++l_auxbank);
|
|
if (DATASIZE() > 0) {
|
|
change_aux_bank(--l_auxbank);
|
|
return 1;
|
|
}
|
|
strcpy(filename, userentry);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Spinner while loading / saving
|
|
* saving - 1 if we are saving, 0 if we are loading
|
|
* copymode - 1 if this is cut/paste, 0 otherwise
|
|
*/
|
|
void spinner(uint32_t sz, uint8_t saving, uint8_t copymode) {
|
|
static char chars[] = "|/-\\";
|
|
static char buf[80] = "";
|
|
static uint8_t i = 0;
|
|
gotoxy(0, PROMPT_ROW);
|
|
if (copymode)
|
|
snprintf(buf, 80, "%s clipboard: %c [%lu]",
|
|
(saving ? "Copying to" : "Pasting from"), chars[(i++) % 4], sz);
|
|
else
|
|
snprintf(buf, 80, "%s '%s': %c [%lu]",
|
|
(saving ? "Saving" : "Opening"), filename, chars[(i++) % 4], sz);
|
|
revers(1);
|
|
cprintf("%s", buf);
|
|
cclear(79 - strlen(buf));
|
|
revers(0);
|
|
}
|
|
|
|
// Forward declaration
|
|
void draw_screen(void);
|
|
|
|
/*
|
|
* Load a file from disk into the gapbuf
|
|
* filename - name of file to load
|
|
* replace - if 1, replace old file.
|
|
* copymode - if 1, then load from CLIPBOARD
|
|
* Returns 0 on success
|
|
* 1 if file can't be opened
|
|
*/
|
|
#pragma code-name (push, "LC")
|
|
uint8_t load_file(char *fname, uint8_t replace, uint8_t copymode) {
|
|
uint8_t partctr = 0;
|
|
uint8_t col;
|
|
char *p;
|
|
uint16_t i, j, s;
|
|
uint8_t c, cont;
|
|
FILE *fp = fopen(fname, "r");
|
|
if (!fp)
|
|
return 1;
|
|
if (!replace)
|
|
col = curscol;
|
|
goto_prompt_row();
|
|
if (replace) {
|
|
gapbegin = 0;
|
|
gapend = BUFSZ - 1;
|
|
col = 0;
|
|
}
|
|
cutbuflen = 0;
|
|
p = iobuf;
|
|
do {
|
|
spinner(DATASIZE(), 0, copymode);
|
|
s = fread(p, 1, IOSZ, fp);
|
|
cont = (s == IOSZ ? 1 : 0);
|
|
for (i = 0; i < s; ++i) {
|
|
switch (p[i]) {
|
|
case '\r': // Native Apple2 files
|
|
case '\n': // UNIX files
|
|
set_gapbuf(gapbegin++, '\r');
|
|
col = 0;
|
|
if (replace && (FREESPACE() < 15000) && (banktbl[0] > 1)) {
|
|
draw_screen();
|
|
if (open_new_aux_bank(++partctr) == 1) {
|
|
snprintf(userentry, 80,
|
|
"Buffer [%03u] not avail. Truncating file.", l_auxbank + 1);
|
|
show_error(userentry);
|
|
if (partctr == 1) // If truncated to one part ...
|
|
status[2] = 0; // Make it a singleton
|
|
partctr = 0; // Prevent status[2] increment below
|
|
goto done;
|
|
}
|
|
}
|
|
break;
|
|
case '\t':
|
|
c = next_tabstop(col) - col;
|
|
for (j = 0; j < c; ++j)
|
|
set_gapbuf(gapbegin++, ' ');
|
|
col += c;
|
|
break;
|
|
default:
|
|
set_gapbuf(gapbegin++, p[i]);
|
|
++col;
|
|
}
|
|
if (FREESPACE() < IOSZ * 2) {
|
|
show_error("File truncated");
|
|
goto done;
|
|
}
|
|
}
|
|
} while (cont);
|
|
done:
|
|
fclose(fp);
|
|
if (replace) {
|
|
if (partctr > 0)
|
|
status[2] = ++partctr;
|
|
jump_pos(0);
|
|
pos = 0;
|
|
set_modified(0);
|
|
status[1] = 0; // No need to prompt for overwrite on save
|
|
startsel = endsel = 65535U;
|
|
mode = SEL_NONE;
|
|
}
|
|
return 0;
|
|
}
|
|
#pragma code-name (pop)
|
|
|
|
/*
|
|
* Save gapbuf to file
|
|
* If copymode is 0, use filename[] otherwise "CLIPBOARD"
|
|
* copymode - if 1, copy test from startsel to endsel only
|
|
* append - if 1, append to file instead of overwriting
|
|
* Returns 0 on success
|
|
* 1 if file can't be opened
|
|
*/
|
|
uint8_t save_file(uint8_t copymode, uint8_t append) {
|
|
uint16_t pos = gapbegin;
|
|
uint16_t sz;
|
|
uint8_t retval = 1;
|
|
uint8_t i;
|
|
uint16_t p, j;
|
|
FILE *fp;
|
|
_filetype = PRODOS_T_TXT;
|
|
_auxtype = 0;
|
|
fp = fopen((copymode ? "CLIPBOARD" : filename), (append ? "a" : "w"));
|
|
if (!fp)
|
|
goto done;
|
|
jump_pos(copymode == 1 ? startsel : 0);
|
|
goto_prompt_row();
|
|
cutbuflen = 0;
|
|
p = gapend + 1;
|
|
if (copymode)
|
|
sz = endsel - startsel;
|
|
else
|
|
sz = DATASIZE();
|
|
for (i = 0; i < sz / IOSZ; ++i) {
|
|
spinner(i * IOSZ, 1, copymode);
|
|
for (j = 0; j < IOSZ; ++j)
|
|
iobuf[j] = get_gapbuf(p++);
|
|
if (fwrite(iobuf, IOSZ, 1, fp) != 1)
|
|
goto done;
|
|
}
|
|
spinner(i * IOSZ, 1, copymode);
|
|
for (j = 0; j < sz - (IOSZ * i); ++j)
|
|
iobuf[j] = get_gapbuf(p++);
|
|
if (fwrite(iobuf, sz - (IOSZ * i), 1, fp) != 1)
|
|
goto done;
|
|
retval = 0;
|
|
status[0] = 0; // No longer marked modified
|
|
done:
|
|
fclose(fp);
|
|
jump_pos(pos);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Obtain the first bank number for the current file
|
|
*/
|
|
uint8_t find_first_bank(void) {
|
|
if (status[2] == 0) // Not a multi-bank file
|
|
return l_auxbank;
|
|
if (status[2] == 1) // First bank of multi-bank file
|
|
return l_auxbank;
|
|
return l_auxbank - status[2] + 1; // Assumes banks allocated in order
|
|
}
|
|
|
|
/*
|
|
* Save a large file that spans multiple banks
|
|
*/
|
|
uint8_t save_multibank_file(void) {
|
|
uint8_t bank = find_first_bank();
|
|
uint8_t origbank = l_auxbank;
|
|
uint8_t retval = 0, modified = 0, first = 1;
|
|
uint8_t filebank;
|
|
if (bank != origbank) {
|
|
change_aux_bank(bank);
|
|
draw_screen();
|
|
}
|
|
if (status[2] == 0) // Oh, it's actually just a single bank file
|
|
return save_file(0, 0);
|
|
if (status[2] != 1) {
|
|
show_error("SBF error"); // Should never happen
|
|
retval = 1;
|
|
goto done;
|
|
}
|
|
// Look at all banks and see if any are modified
|
|
do {
|
|
filebank = status[2];
|
|
if (status[1] == 1) {
|
|
modified = 1;
|
|
break;
|
|
}
|
|
change_aux_bank(++l_auxbank);
|
|
} while (status[2] == filebank + 1);
|
|
change_aux_bank(bank);
|
|
do {
|
|
filebank = status[2];
|
|
draw_screen();
|
|
if (save_file(0, (first == 1 ? 0 : 1)) == 1) {
|
|
retval = 1;
|
|
goto done;
|
|
}
|
|
first = 0;
|
|
change_aux_bank(++l_auxbank);
|
|
} while (status[2] == filebank + 1);
|
|
done:
|
|
if (origbank != l_auxbank) {
|
|
change_aux_bank(origbank);
|
|
draw_screen();
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Read next char from gapbuf[] and update state.
|
|
* Returns 1 on EOL, 0 otherwise
|
|
* If do_print is set, print char on the screen.
|
|
* Interface is via the following globals:
|
|
* do_print - if 1, update screen
|
|
* pos - position in buffer, advanced by one
|
|
* row, col - position on screen
|
|
* rowlen[] - length of each screen row in chars
|
|
*/
|
|
uint8_t read_char_update_pos(void) {
|
|
uint16_t delta = gapend - gapbegin;
|
|
uint16_t s = startsel, e = endsel;
|
|
char c;
|
|
if (startsel > endsel) {
|
|
s = endsel;
|
|
e = startsel;
|
|
}
|
|
if ((c = get_gapbuf(pos++)) == EOL) {
|
|
if (do_print) {
|
|
rowlen[row] = col + 1;
|
|
clreol_wrap();
|
|
gotox(0);
|
|
}
|
|
++row;
|
|
col = 0;
|
|
return 1;
|
|
}
|
|
if (((pos > s) && (pos <= e)) || // Left of cursor
|
|
((pos - delta > s) && (pos - delta - 1 <= e))) // Right of cursor
|
|
revers(1);
|
|
else
|
|
revers(0);
|
|
if (do_print)
|
|
cputc(c);
|
|
revers(0);
|
|
++col;
|
|
if (do_print)
|
|
rowlen[row] = col;
|
|
if (col == NCOLS) {
|
|
++row;
|
|
col = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Draw screenful of text
|
|
*/
|
|
void draw_screen(void) {
|
|
uint16_t startpos;
|
|
uint8_t rowsabove, cursorrow;
|
|
|
|
// Initialize all rows to length 0 to cover those rows that may have
|
|
// no text and would otherwise be unitialized.
|
|
for (rowsabove = 0; rowsabove < NROWS; ++rowsabove)
|
|
rowlen[rowsabove] = 0;
|
|
|
|
// First we have to scan back to work out where in the buffer to
|
|
// start drawing on the screen at the top left. This is at most
|
|
// CURSORROW * NCOLS chars.
|
|
startpos = gapbegin;
|
|
if (startpos > (CURSORROW + 2) * NCOLS)
|
|
startpos -= (CURSORROW + 2) * NCOLS;
|
|
else
|
|
startpos = 0;
|
|
|
|
// Format the text and work out the number of rows it takes up
|
|
pos = startpos;
|
|
row = col = 0;
|
|
do_print = 0;
|
|
while (pos < gapbegin)
|
|
read_char_update_pos();
|
|
rowsabove = row; // Number of complete rows above cursor
|
|
|
|
if (rowsabove <= CURSORROW) {
|
|
pos = 0;
|
|
cursorrow = rowsabove;
|
|
} else {
|
|
cursorrow = CURSORROW;
|
|
|
|
// Now repeat the formatting of the text, eating the first
|
|
// (rowsabove - cursorrow) lines
|
|
pos = startpos;
|
|
row = col = 0;
|
|
while (row < (rowsabove - cursorrow))
|
|
read_char_update_pos();
|
|
}
|
|
|
|
// Finally actually write the text to the screen!
|
|
videomode(VIDEOMODE_80COL);
|
|
clrscr();
|
|
revers(0);
|
|
row = col = 0;
|
|
do_print = 1;
|
|
while (pos < gapbegin)
|
|
read_char_update_pos();
|
|
|
|
curscol = col;
|
|
cursrow = row;
|
|
|
|
// Now write the text after the cursor up to the end of the screen
|
|
pos = gapend + 1;
|
|
while ((pos < BUFSZ) && (row < NROWS))
|
|
read_char_update_pos();
|
|
|
|
update_status_line();
|
|
}
|
|
|
|
/*
|
|
* Scroll screen up and update rowlen[]
|
|
*/
|
|
void scroll_up() {
|
|
draw_screen();
|
|
}
|
|
|
|
/*
|
|
* Scroll screen down and update rowlen[]
|
|
*/
|
|
void scroll_down() {
|
|
draw_screen();
|
|
}
|
|
|
|
/*
|
|
* Returns 1 if current position in gap buffer is on the last line of
|
|
* the file (ie: no following CR), 0 otherwise
|
|
*/
|
|
uint8_t is_last_line(void) {
|
|
uint16_t p = gapend + 1;
|
|
while (p < BUFSZ) {
|
|
if (get_gapbuf(p++) == '\r')
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Update screen after delete_char_right()
|
|
*/
|
|
void update_after_delete_char_right(void) {
|
|
uint8_t eol = 0;
|
|
uint8_t i;
|
|
col = curscol;
|
|
row = cursrow;
|
|
do_print = 1;
|
|
|
|
// Print rest of line up to EOL
|
|
pos = gapend + 1;
|
|
if (pos == BUFSZ)
|
|
--rowlen[row];
|
|
else
|
|
while (!eol && (pos < BUFSZ) && (row < NROWS)) {
|
|
i = col;
|
|
eol = read_char_update_pos();
|
|
}
|
|
|
|
if (is_last_line())
|
|
clreol_wrap();
|
|
|
|
// If necessary, print rest of screen
|
|
if ((get_gapbuf(gapend) == EOL) || (i == NCOLS - 1)) {
|
|
while ((pos < BUFSZ) && (row < NROWS))
|
|
read_char_update_pos();
|
|
|
|
clreol_wrap();
|
|
|
|
// Erase the rest of the screen (if any)
|
|
for (i = row + 1; i < NROWS; ++i) {
|
|
gotoxy(0, i);
|
|
clrline();
|
|
}
|
|
}
|
|
|
|
gotoxy(curscol, cursrow);
|
|
cursor(1);
|
|
}
|
|
|
|
/*
|
|
* Update screen after delete_char()
|
|
*/
|
|
void update_after_delete_char(void) {
|
|
uint8_t eol = 0;
|
|
uint8_t i;
|
|
col = curscol;
|
|
row = cursrow;
|
|
|
|
if (get_gapbuf(gapbegin) == EOL) {
|
|
// Special handling if we deleted an EOL
|
|
if (row > 0)
|
|
col = rowlen[--row] - 1;
|
|
else {
|
|
scroll_up();
|
|
return;
|
|
}
|
|
} else {
|
|
// Erase char to left of cursor & update row, col
|
|
gotox(wherex() - 1);
|
|
cputc(' ');
|
|
gotox(wherex() - 1);
|
|
if (col > 0)
|
|
--col;
|
|
else {
|
|
col = NCOLS - 1;
|
|
if (row > 0)
|
|
--row;
|
|
else {
|
|
scroll_up();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
curscol = col;
|
|
cursrow = row;
|
|
gotoxy(curscol, cursrow);
|
|
|
|
do_print = 1;
|
|
|
|
// Print rest of line up to EOL
|
|
pos = gapend + 1;
|
|
if (pos == BUFSZ)
|
|
--rowlen[row];
|
|
else
|
|
while (!eol && (pos < BUFSZ) && (row < NROWS)) {
|
|
i = col;
|
|
eol = read_char_update_pos();
|
|
}
|
|
|
|
if (is_last_line())
|
|
clreol_wrap();
|
|
|
|
// If necessary, print rest of screen
|
|
if ((get_gapbuf(gapbegin) == EOL) || (i == NCOLS - 1)) {
|
|
while ((pos < BUFSZ) && (row < NROWS))
|
|
read_char_update_pos();
|
|
|
|
clreol_wrap();
|
|
|
|
// Erase the rest of the screen (if any)
|
|
for (i = row + 1; i < NROWS; ++i) {
|
|
gotoxy(0, i);
|
|
clrline();
|
|
}
|
|
}
|
|
|
|
gotoxy(curscol, cursrow);
|
|
cursor(1);
|
|
}
|
|
|
|
/*
|
|
* Update screen after insert_char()
|
|
*/
|
|
void update_after_insert_char(void) {
|
|
uint8_t eol = 0;
|
|
uint8_t prevcol;
|
|
col = curscol;
|
|
row = cursrow;
|
|
|
|
// Print character just inserted
|
|
pos = gapbegin - 1;
|
|
do_print = 1;
|
|
read_char_update_pos();
|
|
|
|
curscol = col;
|
|
cursrow = row;
|
|
gotoxy(curscol, cursrow);
|
|
|
|
if (cursrow == NROWS) {
|
|
scroll_down();
|
|
return;
|
|
}
|
|
|
|
// Print rest of line up to EOL
|
|
pos = gapend + 1;
|
|
while (!eol && (pos < BUFSZ) && (row < NROWS)) {
|
|
prevcol = col;
|
|
eol = read_char_update_pos();
|
|
}
|
|
|
|
// If necessary, print rest of screen
|
|
if ((get_gapbuf(gapbegin - 1) == EOL) || (prevcol == 0))
|
|
while ((pos < BUFSZ) && (row < NROWS))
|
|
read_char_update_pos();
|
|
|
|
gotoxy(curscol, cursrow);
|
|
cursor(1);
|
|
}
|
|
|
|
/*
|
|
* Move the cursor left
|
|
*/
|
|
#pragma code-name (push, "LC")
|
|
void cursor_left(void) {
|
|
if (gapbegin > 0)
|
|
set_gapbuf(gapend--, get_gapbuf(--gapbegin));
|
|
else {
|
|
beep();
|
|
gotoxy(curscol, cursrow);
|
|
return;
|
|
}
|
|
if (curscol == 0) {
|
|
if (cursrow == 0) {
|
|
scroll_up();
|
|
if (cursrow == 0) {
|
|
beep();
|
|
return;
|
|
}
|
|
}
|
|
--cursrow;
|
|
curscol = rowlen[cursrow] - 1;
|
|
} else
|
|
--curscol;
|
|
gotoxy(curscol, cursrow);
|
|
if (mode == SEL_SELECT) {
|
|
endsel = gapbegin;
|
|
revers(gapbegin < startsel ? 1 : 0);
|
|
cputc(get_gapbuf(gapbegin));
|
|
}
|
|
revers(0);
|
|
gotoxy(curscol, cursrow);
|
|
}
|
|
#pragma code-name (pop)
|
|
|
|
/*
|
|
* Move the cursor right
|
|
*/
|
|
#pragma code-name (push, "LC")
|
|
void cursor_right(void) {
|
|
if (gapend < BUFSZ - 1)
|
|
set_gapbuf(gapbegin++, get_gapbuf(++gapend));
|
|
else {
|
|
beep();
|
|
goto done;
|
|
}
|
|
++curscol;
|
|
if ((gapend == BUFSZ - 1) && (get_gapbuf(gapend) != EOL))
|
|
goto done;
|
|
if (curscol == rowlen[cursrow]) {
|
|
if (cursrow == NROWS - 1)
|
|
scroll_down();
|
|
++cursrow;
|
|
curscol = 0;
|
|
}
|
|
done:
|
|
if (mode == SEL_SELECT) {
|
|
endsel = gapbegin;
|
|
revers(gapbegin > startsel ? 1 : 0);
|
|
cputc(get_gapbuf(gapbegin - 1));
|
|
}
|
|
revers(0);
|
|
gotoxy(curscol, cursrow);
|
|
}
|
|
#pragma code-name (pop)
|
|
|
|
/*
|
|
* Move the cursor up
|
|
* Returns 1 if at top, 0 otherwise
|
|
*/
|
|
#pragma code-name (push, "LC")
|
|
uint8_t cursor_up(void) {
|
|
uint8_t i;
|
|
if (cursrow == 0) {
|
|
scroll_up();
|
|
if (cursrow == 0) {
|
|
beep();
|
|
gotoxy(curscol, cursrow);
|
|
return 1;
|
|
}
|
|
}
|
|
for (i = curscol; i > 0; --i) {
|
|
set_gapbuf(gapend--, get_gapbuf(--gapbegin));
|
|
if (mode == SEL_SELECT) {
|
|
gotoxy(i - 1, cursrow);
|
|
revers(gapbegin < startsel ? 1 : 0);
|
|
cputc(get_gapbuf(gapbegin));
|
|
}
|
|
}
|
|
--cursrow;
|
|
// Short line ...
|
|
if (curscol > rowlen[cursrow] - 1)
|
|
curscol = rowlen[cursrow] - 1;
|
|
for (i = rowlen[cursrow]; i > curscol; --i) {
|
|
set_gapbuf(gapend--, get_gapbuf(--gapbegin));
|
|
if (mode == SEL_SELECT) {
|
|
gotoxy(i - 1, cursrow);
|
|
revers(gapbegin < startsel ? 1 : 0);
|
|
cputc(get_gapbuf(gapbegin));
|
|
}
|
|
}
|
|
if (mode == SEL_SELECT) {
|
|
endsel = gapbegin;
|
|
}
|
|
revers(0);
|
|
gotoxy(curscol, cursrow);
|
|
return 0;
|
|
}
|
|
#pragma code-name (pop)
|
|
|
|
/*
|
|
* Move the cursor down
|
|
* Returns 1 if at bottom, 0 otherwise
|
|
*/
|
|
#pragma code-name (push, "LC")
|
|
uint8_t cursor_down(void) {
|
|
uint8_t i;
|
|
if (cursrow == NROWS - 1)
|
|
scroll_down();
|
|
if ((get_gapbuf(gapend + rowlen[cursrow] - curscol) != EOL) &&
|
|
(rowlen[cursrow] != NCOLS))
|
|
return 1; // Last line
|
|
|
|
if (rowlen[cursrow] == 0) {
|
|
beep();
|
|
return 1;
|
|
}
|
|
for (i = 0; i < rowlen[cursrow] - curscol; ++i) {
|
|
if (gapend < BUFSZ - 1) {
|
|
set_gapbuf(gapbegin++, get_gapbuf(++gapend));
|
|
if (mode == SEL_SELECT) {
|
|
revers(gapbegin > startsel ? 1 : 0);
|
|
cputc(get_gapbuf(gapbegin - 1));
|
|
}
|
|
}
|
|
else {
|
|
beep();
|
|
return 1;
|
|
}
|
|
}
|
|
++cursrow;
|
|
// Short line ...
|
|
if (rowlen[cursrow] == 0)
|
|
curscol = 0;
|
|
else if (curscol > rowlen[cursrow] - 1)
|
|
curscol = rowlen[cursrow] - 1;
|
|
gotoxy(0, cursrow);
|
|
for (i = 0; i < curscol; ++i) {
|
|
if (gapend < BUFSZ - 1) {
|
|
set_gapbuf(gapbegin++, get_gapbuf(++gapend));
|
|
if (mode == SEL_SELECT) {
|
|
revers(gapbegin > startsel ? 1 : 0);
|
|
cputc(get_gapbuf(gapbegin - 1));
|
|
}
|
|
}
|
|
}
|
|
if (mode == SEL_SELECT) {
|
|
endsel = gapbegin;
|
|
}
|
|
revers(0);
|
|
gotoxy(curscol, cursrow);
|
|
return 0;
|
|
}
|
|
#pragma code-name (pop)
|
|
|
|
/*
|
|
* Goto beginning of line
|
|
*/
|
|
void goto_bol(void) {
|
|
while (curscol > 0)
|
|
cursor_left();
|
|
}
|
|
|
|
/*
|
|
* Goto end of line
|
|
*/
|
|
void goto_eol(void) {
|
|
while (curscol < rowlen[cursrow] - 1)
|
|
cursor_right();
|
|
}
|
|
|
|
/*
|
|
* Word left
|
|
*/
|
|
void word_left(void) {
|
|
do {
|
|
cursor_left();
|
|
} while ((get_gapbuf(gapbegin) != ' ') && (get_gapbuf(gapbegin) != EOL) &&
|
|
(gapbegin > 0));
|
|
}
|
|
|
|
/*
|
|
* Word right
|
|
*/
|
|
void word_right(void) {
|
|
do {
|
|
cursor_right();
|
|
} while ((get_gapbuf(gapbegin) != ' ') && (get_gapbuf(gapbegin) != EOL) &&
|
|
(gapend < BUFSZ - 1));
|
|
}
|
|
|
|
/*
|
|
* Jump forward 15 screen lines
|
|
*/
|
|
void page_down(void) {
|
|
uint8_t i;
|
|
for (i = 0; i < 15; ++i)
|
|
if (cursor_down() == 1)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Jump back 15 screen lines
|
|
*/
|
|
void page_up(void) {
|
|
uint8_t i;
|
|
for (i = 0; i < 15; ++i)
|
|
if (cursor_up() == 1)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Perform word-wrapping on current paragraph
|
|
* addbreaks - if 1, add breaks to do word wrap, otherwise it 'unwraps'
|
|
* (ie: removes all carriage returns in paragraph)
|
|
*/
|
|
void word_wrap_para(uint8_t addbreaks) {
|
|
uint16_t i = gapbegin;
|
|
uint8_t rets = 0, col = 0;
|
|
uint16_t startpara = 0;
|
|
// Find start of paragraph ("\r\r" delimiter)
|
|
while (i > 0) {
|
|
--i;
|
|
if (get_gapbuf(i) == '\r')
|
|
++rets;
|
|
else
|
|
rets = 0;
|
|
if (rets == 2) {
|
|
startpara = i + 2;
|
|
break;
|
|
}
|
|
}
|
|
// Iterate through para, up to the cursor
|
|
i = startpara;
|
|
while (i < gapbegin) {
|
|
if (get_gapbuf(i) == '\r')
|
|
set_gapbuf(i, ' ');
|
|
++col;
|
|
if (addbreaks && (col == 76)) {
|
|
// Backtrack to find a space
|
|
while ((get_gapbuf(i) != ' ') && (i > startpara))
|
|
--i;
|
|
if (i == startpara) {
|
|
beep();
|
|
return;
|
|
}
|
|
set_gapbuf(i, '\r'); // Break line
|
|
col = 0;
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Help screen
|
|
* EDITHELP.TXT is expected to contain lines of exactly 80 chars
|
|
*/
|
|
void help(uint8_t num) {
|
|
FILE *fp;
|
|
char *p;
|
|
char c;
|
|
uint16_t i, s;
|
|
uint8_t cont;
|
|
revers(0);
|
|
cursor(0);
|
|
clrscr();
|
|
snprintf(iobuf, 80, "%s/EDITHELP%u.TXT", startdir, num);
|
|
fp = fopen(iobuf, "rb");
|
|
if (!fp) {
|
|
printf("Can't open help file\n\n");
|
|
goto done;
|
|
}
|
|
cutbuflen = 0;
|
|
p = iobuf;
|
|
do {
|
|
if (FREESPACE() < IOSZ) {
|
|
beep();
|
|
goto done;
|
|
}
|
|
s = fread(p, 1, IOSZ, fp);
|
|
cont = (s == IOSZ ? 1 : 0);
|
|
for (i = 0; i < s; ++i) {
|
|
c = p[i];
|
|
if (c == '{')
|
|
printf("%s", openapple);
|
|
else if (c == '}')
|
|
printf("%s", closedapple);
|
|
else if ((c != '\r') && (c != '\n'))
|
|
putchar(c);
|
|
}
|
|
} while (cont);
|
|
done:
|
|
fclose(fp);
|
|
}
|
|
|
|
/*
|
|
* Chain EMAIL.SYSTEM
|
|
*/
|
|
void load_email(void) {
|
|
revers(0);
|
|
clrscr();
|
|
chdir(startdir);
|
|
exec("EMAIL.SYSTEM", NULL);
|
|
}
|
|
|
|
/*
|
|
* Chain ATTACHER.SYSTEM
|
|
*/
|
|
void load_attacher(void) {
|
|
revers(0);
|
|
clrscr();
|
|
chdir(startdir);
|
|
exec("ATTACHER.SYSTEM", filename);
|
|
}
|
|
|
|
void file_ui(char *, char *, char *); // Forward declaration
|
|
void name_file(void); // Forward declaration
|
|
|
|
/*
|
|
* Save file to disk, handle user interface
|
|
*/
|
|
void save(void) {
|
|
uint8_t rc;
|
|
FILE *fp;
|
|
if (email_mode == 1) {
|
|
show_error("Read-only file");
|
|
return;
|
|
}
|
|
if (strlen(filename) == 0) {
|
|
status[1] = 1; // Prompt if save will overwrite existing file
|
|
name_file();
|
|
if (strlen(filename) == 0)
|
|
return;
|
|
}
|
|
// If status[1] is set, check for overwrite
|
|
if (status[1]) {
|
|
fp = fopen(filename, "r");
|
|
if (fp) {
|
|
fclose(fp);
|
|
beep();
|
|
snprintf(userentry, 80, "File '%s' exists, overwrite", filename);
|
|
if (prompt_okay(userentry) != 0)
|
|
return;
|
|
}
|
|
fclose(fp);
|
|
}
|
|
rc = save_multibank_file();
|
|
switch (rc) {
|
|
case 0: // Success
|
|
status[1] = 0; // No need to prompt for overwrite next time
|
|
set_modified(0);
|
|
break;
|
|
case 1: // Save error
|
|
snprintf(userentry, 80, "Can't save '%s'", filename);
|
|
show_error(userentry);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Perform replace part of search/replace
|
|
* pos - position where search term was found
|
|
* r - if 0 then perform replace operation
|
|
* ask - if 1 then prompt for each replacement
|
|
* Returns 1 if search should continue, 0 otherwise
|
|
*/
|
|
uint8_t finish_search_replace(uint16_t pos, uint8_t r, uint8_t ask) {
|
|
uint8_t i;
|
|
mode = SEL_NONE;
|
|
jump_pos(pos);
|
|
startsel = gapend + 1;
|
|
endsel = gapend + 1 + strlen(search);
|
|
draw_screen();
|
|
if (r == 0) { // Replace mode
|
|
if (ask) {
|
|
i = prompt_okay("Replace");
|
|
if (i == 1) { // 'n'
|
|
cursor_right();
|
|
return 1; // Continue
|
|
}
|
|
if (i == 2) { // ESC
|
|
startsel = endsel = 65535U;
|
|
return 0; // Abort
|
|
}
|
|
}
|
|
for (i = 0; i < strlen(search); ++i)
|
|
delete_char_right();
|
|
for (i = 0; i < strlen(replace); ++i)
|
|
insert_char(replace[i]);
|
|
set_modified(1);
|
|
draw_screen();
|
|
cursor_right();
|
|
return 1; // Continue
|
|
}
|
|
startsel = endsel = 65535U;
|
|
return 0; // Do not continue
|
|
}
|
|
|
|
/*
|
|
* Perform search/replace operation
|
|
* r - if 0 then perform replace operation
|
|
* ask - if 1 then prompt for each replacement
|
|
*/
|
|
void do_search_replace(uint8_t r, uint8_t ask) {
|
|
uint8_t wrapcount = 0;
|
|
uint16_t foundpos;
|
|
search:
|
|
mode = SRCH1; // Searching ..
|
|
update_status_line();
|
|
set_gapbuf(BUFSZ + 1, '\0'); // NULL term for search_in_gapbuf()
|
|
if (search_in_gapbuf(gapend + 1, search, &foundpos) == 0) {
|
|
if (wrapcount > 0) {
|
|
mode = SEL_NONE;
|
|
return; // Wrapped already. Give up.
|
|
}
|
|
++wrapcount;
|
|
mode = SRCH2; // Wrapped ..
|
|
update_status_line();
|
|
set_gapbuf(gapbegin, '\0'); // NULL term for search_in_gapbuf()
|
|
if (search_in_gapbuf(0, search, &foundpos) == 0) {
|
|
mode = SRCH3; // Not found ..
|
|
return;
|
|
}
|
|
if (finish_search_replace(foundpos, r, ask) == 1)
|
|
goto search;
|
|
mode = SEL_NONE;
|
|
return;
|
|
}
|
|
if (finish_search_replace(gapbegin + foundpos - (gapend + 1), r, ask) == 1)
|
|
goto search;
|
|
mode = SEL_NONE;
|
|
}
|
|
|
|
/*
|
|
* Check if there are files on a RAM disk
|
|
* dev - Device number of RAM disk
|
|
*/
|
|
void check_ramdisk(uint8_t dev) {
|
|
dhandle_t dio_hdl;
|
|
uint8_t c;
|
|
dio_hdl = dio_open(dev);
|
|
dio_read(dio_hdl, 2, iobuf);
|
|
dio_close(dio_hdl);
|
|
c = iobuf[0x25] + 256 * iobuf[0x26]; // File count
|
|
if (c > 0) {
|
|
fputs("\n/",stdout);
|
|
for (c = 0; c < (iobuf[0x04] & 0x0f); ++c)
|
|
putchar(iobuf[0x05 + c]);
|
|
printf(" is not empty.\n");
|
|
printf("[Q] to quit, and preserve RAMDisk\n");
|
|
beep();
|
|
c = cgetc();
|
|
if ((c == 'Q') || (c == 'q'))
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Disconnect RAM disks /RAM and/or /RAM3
|
|
* Note that both /RAM and /RAM3 may be active at the same time!!
|
|
*/
|
|
void disconnect_ramdisk(void) {
|
|
uint8_t i, j;
|
|
uint8_t *devcnt = (uint8_t*)0xbf31; // Number of devices
|
|
uint8_t *devlst = (uint8_t*)0xbf32; // Disk device numbers
|
|
uint16_t *s0d1 = (uint16_t*)0xbf10; // s0d1 driver vector
|
|
uint16_t *s3d1 = (uint16_t*)0xbf16; // s3d1 driver vector
|
|
uint16_t *s3d2 = (uint16_t*)0xbf26; // s3d2 driver vector
|
|
if (*s0d1 != *s3d2)
|
|
check_ramdisk(3 + (2 - 1) * 8); // s3d2
|
|
if (*s0d1 != *s3d1)
|
|
check_ramdisk(3 + (1 - 1) * 8); // s3d1
|
|
if (*s0d1 == *s3d2) {
|
|
s3d2dev = 0;
|
|
goto s3d1; // No /RAM
|
|
}
|
|
for (i = 0; i <= *devcnt; ++i)
|
|
if ((devlst[i] & 0xf0) == 0xb0) {
|
|
s3d2dev = devlst[i];
|
|
for (j = i; j <= *devcnt; ++j)
|
|
devlst[j] = devlst[j + 1];
|
|
break;
|
|
}
|
|
s3d2vec = *s3d2;
|
|
*s3d2 = *s0d1;
|
|
--(*devcnt);
|
|
s3d1:
|
|
if (*s0d1 == *s3d1) {
|
|
s3d1dev = 0;
|
|
return; // No /RAM3
|
|
}
|
|
for (i = 0; i <= *devcnt; ++i)
|
|
if ((devlst[i] & 0xf0) == 0x30) {
|
|
s3d1dev = devlst[i];
|
|
for (j = i; j <= *devcnt; ++j)
|
|
devlst[j] = devlst[j + 1];
|
|
break;
|
|
}
|
|
s3d1vec = *s3d1;
|
|
*s3d1 = *s0d1;
|
|
--(*devcnt);
|
|
}
|
|
|
|
/*
|
|
* Stub to invoke the slot 3 drive 1 device driver
|
|
*/
|
|
void s3d1driver(void) {
|
|
__asm__("jmp (%v)", s3d1vec);
|
|
}
|
|
|
|
/*
|
|
* Stub to invoke the slot 3 drive 2 device driver
|
|
*/
|
|
|
|
void s3d2driver(void) {
|
|
__asm__("jmp (%v)", s3d2vec);
|
|
}
|
|
|
|
/*
|
|
* Reconnect RAMdisk on exit
|
|
*/
|
|
#pragma optimize (off)
|
|
void reconnect_ramdisk(void) {
|
|
uint8_t *devcnt = (uint8_t*)0xbf31; // Number of devices
|
|
uint8_t *devlst = (uint8_t*)0xbf32; // Disk device numbers
|
|
uint16_t *s3d1 = (uint16_t*)0xbf16; // s3d1 driver vector
|
|
uint16_t *s3d2 = (uint16_t*)0xbf26; // s3d2 driver vector
|
|
if (s3d2dev) {
|
|
*s3d2 = s3d2vec;
|
|
++(*devcnt);
|
|
devlst[*devcnt] = s3d2dev;
|
|
__asm__("lda #$03"); // FORMAT request
|
|
__asm__("sta $42");
|
|
__asm__("lda #$b0"); // Unit number (s3d2)
|
|
__asm__("sta $43");
|
|
__asm__("lda #$00"); // LSB of buffer pointer
|
|
__asm__("sta $44");
|
|
__asm__("lda #$20"); // MSB of buffer pointer
|
|
__asm__("sta $45");
|
|
__asm__("lda $c08b"); // R/W enable LC, bank 1 on
|
|
__asm__("lda $c08b");
|
|
__asm__("jsr %v", s3d2driver); // Call driver
|
|
__asm__("bit $c082"); // ROM back online
|
|
__asm__("bcc %g", s3d1); // If no error ...
|
|
beep();
|
|
printf("Unable to reconnect S3D2");
|
|
}
|
|
s3d1:
|
|
if (s3d1dev) {
|
|
*s3d1 = s3d1vec;
|
|
++(*devcnt);
|
|
devlst[*devcnt] = s3d1dev;
|
|
__asm__("lda #$03"); // FORMAT request
|
|
__asm__("sta $42");
|
|
__asm__("lda #$30"); // Unit number (s3d1)
|
|
__asm__("sta $43");
|
|
__asm__("lda #$00"); // LSB of buffer pointer
|
|
__asm__("sta $44");
|
|
__asm__("lda #$20"); // MSB of buffer pointer
|
|
__asm__("sta $45");
|
|
__asm__("lda $c08b"); // R/W enable LC, bank 1 on
|
|
__asm__("lda $c08b");
|
|
__asm__("jsr %v", s3d1driver); // Call driver
|
|
__asm__("bit $c082"); // ROM back online
|
|
__asm__("bcc %g", done); // If no error ...
|
|
beep();
|
|
printf("Unable to reconnect S3D1");
|
|
}
|
|
done:
|
|
return;
|
|
}
|
|
#pragma optimize (on)
|
|
|
|
/*
|
|
* Available aux banks. Taken from RamWorks III Manual p47
|
|
* banktbl is populated as follows:
|
|
* First byte is number of banks
|
|
* Following bytes are the logical bank numbers in physical bank order
|
|
* Final byte is $ff terminator
|
|
*/
|
|
void avail_aux_banks(void) {
|
|
__asm__("sta $c009"); // Store in ALTZP
|
|
__asm__("ldy #$7f"); // Maximum valid bank
|
|
findbanks:
|
|
__asm__("sty $c073"); // Select bank
|
|
__asm__("sty $00"); // Store bank num in ALTZP
|
|
__asm__("tya");
|
|
__asm__("eor #$ff");
|
|
__asm__("sta $01"); // Store the inverse in ALTZP too
|
|
__asm__("dey");
|
|
__asm__("bpl %g", findbanks);
|
|
// Read back the bytes we wrote to find valid banks
|
|
__asm__("lda #$00");
|
|
__asm__("tay");
|
|
__asm__("tax");
|
|
findthem:
|
|
__asm__("sty $c073"); // Select bank
|
|
__asm__("sta $c076"); // Why?
|
|
__asm__("cpy $00");
|
|
__asm__("bne %g", notone);
|
|
__asm__("tya");
|
|
__asm__("eor #$ff");
|
|
__asm__("cmp $01");
|
|
__asm__("bne %g", notone);
|
|
__asm__("inx");
|
|
__asm__("tya");
|
|
__asm__("sta %v,x", banktbl);
|
|
__asm__("cpx #128"); // 8MB max
|
|
__asm__("bcs %g", done);
|
|
notone: // 'not one', not 'no tone' !
|
|
__asm__("iny");
|
|
__asm__("bpl %g", findthem);
|
|
done:
|
|
__asm__("lda #$00");
|
|
__asm__("sta $c073"); // Back to aux bank 0
|
|
__asm__("sta $c008"); // Turn off ALTZP
|
|
__asm__("stx %v", banktbl); // Number of banks
|
|
__asm__("lda #$ff");
|
|
__asm__("inx");
|
|
__asm__("sta %v,x", banktbl); // Terminator
|
|
}
|
|
|
|
/*
|
|
* Initialize the data at 0x200 in each aux bank so that all buffers
|
|
* are empty, unmodified and without a filename
|
|
*/
|
|
void init_aux_banks(void) {
|
|
uint8_t i;
|
|
uint16_t count;
|
|
clrscr();
|
|
revers(1);
|
|
cprintf("EDIT.SYSTEM v1.27 Bobbi 2020");
|
|
revers(0);
|
|
cprintf("\n\n\n %u x 64KB aux banks -> %uKB\n", banktbl[0], banktbl[0]*64);
|
|
for (i = 1; i <= banktbl[0]; ++i) {
|
|
auxbank = bank_log_to_phys(i);
|
|
// Saves filename[], gapbegin, gapend and modified (BUFINFOSZ bytes)
|
|
__asm__("lda %v", auxbank);
|
|
__asm__("sta $c073"); // Set aux bank
|
|
copyaux(filename, (void*)INFOADDR, BUFINFOSZ, TOAUX);
|
|
}
|
|
auxbank = 0;
|
|
l_auxbank = 1;
|
|
__asm__("lda #$00");
|
|
__asm__("sta $c073"); // Set aux bank back to 0
|
|
for (count = 0; count < 25000; ++count); // Delay so user can read message
|
|
}
|
|
|
|
/*
|
|
* Generate buffer list
|
|
*/
|
|
#pragma code-name (push, "LC")
|
|
void buffer_list(void) {
|
|
uint8_t o_aux = l_auxbank, row = 0;
|
|
uint8_t i;
|
|
cursor(0);
|
|
for (i = 1; i <= banktbl[0]; ++i) {
|
|
if (row == 0) {
|
|
clrscr();
|
|
revers(1);
|
|
cprintf("Active Buffer List (Total number of buffers: %u)", banktbl[0]);
|
|
revers(0);
|
|
gotoxy(0, row += 2);
|
|
cprintf(" Buf Size Mod Part Filename\r");
|
|
gotoxy(0, ++row);
|
|
}
|
|
change_aux_bank(i);
|
|
if (DATASIZE() > 0) {
|
|
cprintf("[%03u] %05u | %c | %3u | %s\r",
|
|
i, DATASIZE(), (status[0] ? '*' : ' '), status[2], filename);
|
|
gotoxy(0, ++row);
|
|
}
|
|
if (row == 22) {
|
|
cprintf("[Press Any Key]");
|
|
cgetc();
|
|
row = 0;
|
|
}
|
|
}
|
|
change_aux_bank(o_aux);
|
|
gotoxy(0, 23);
|
|
cprintf("[Press Any Key]");
|
|
cgetc();
|
|
}
|
|
#pragma code-name (pop)
|
|
|
|
/*
|
|
* Rename a file, taking care of multi-bank files
|
|
*/
|
|
void name_file(void) {
|
|
uint8_t bank = find_first_bank();
|
|
uint8_t origbank = l_auxbank;
|
|
uint8_t retval = 0, modified = 0, first = 1;
|
|
uint8_t filebank;
|
|
file_ui("Set Filename", openfilemsg, namefilemsg);
|
|
if (strlen(userentry) == 0) {
|
|
draw_screen();
|
|
return;
|
|
}
|
|
if (bank != origbank)
|
|
change_aux_bank(bank);
|
|
if (status[2] == 0) { // Oh, it's actually just a single bank file
|
|
strcpy(filename, userentry);
|
|
status[1] = 1; // Should prompt if overwriting file on save
|
|
draw_screen();
|
|
return;
|
|
}
|
|
if (status[2] != 1) {
|
|
draw_screen();
|
|
show_error("SBF error"); // Should never happen
|
|
return;
|
|
}
|
|
do {
|
|
filebank = status[2];
|
|
strcpy(filename, userentry);
|
|
status[1] = 1; // Should prompt if overwriting file on save
|
|
change_aux_bank(++l_auxbank);
|
|
} while (status[2] == filebank + 1);
|
|
change_aux_bank(origbank);
|
|
draw_screen();
|
|
}
|
|
|
|
/*
|
|
* Save all modified buffers
|
|
*/
|
|
void save_all(void) {
|
|
uint8_t o_aux = l_auxbank;
|
|
uint8_t i;
|
|
cursor(0);
|
|
for (i = 1; i <= banktbl[0]; ++i) {
|
|
change_aux_bank(i);
|
|
if (status[0]) { // If buffer is modified
|
|
if (strlen(filename) > 0)
|
|
snprintf(userentry, 80, "Save '%s'", filename);
|
|
else
|
|
strcpy(userentry, "Save <scratch> buffer");
|
|
if (prompt_okay(userentry) != 0)
|
|
continue;
|
|
draw_screen();
|
|
save();
|
|
}
|
|
}
|
|
change_aux_bank(o_aux);
|
|
}
|
|
|
|
/*
|
|
* Sort startsel / endsel in ascending order to allow backwards selection
|
|
*/
|
|
void order_selection(void) {
|
|
uint16_t tmp;
|
|
if (startsel > endsel) {
|
|
tmp = endsel;
|
|
endsel = startsel;
|
|
startsel = tmp;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Remember current gapbuf settings to support undo
|
|
*/
|
|
void mark_undo(void) {
|
|
oldgapbegin = gapbegin;
|
|
oldgapend = gapend;
|
|
canundo = 1;
|
|
}
|
|
|
|
struct tabent {
|
|
char name[16];
|
|
uint8_t type;
|
|
uint32_t size;
|
|
} *entry;
|
|
|
|
// It is best if FILESPERPAGE is a multiple of FILELINES
|
|
#define FILELINES 16 // Number of lines displayed in file_ui
|
|
#define FILESPERPAGE 160 // Number of files loaded into iobuf[]
|
|
|
|
/*
|
|
* Draw one line in file chooser UI
|
|
* i - index of file to draw
|
|
* first - index of first file on screen
|
|
* selected - index of currently selected file
|
|
* entries - total number of file entries in directory
|
|
*/
|
|
void file_ui_draw(uint16_t i, uint16_t first, uint16_t selected, uint16_t entries) {
|
|
struct tabent *entry;
|
|
gotoxy(5, i - first + 6);
|
|
if (i < entries) {
|
|
entry = (struct tabent*)iobuf + i;
|
|
if (entry->type == 0x0f) {
|
|
snprintf(userentry, 80, "[ %s ] ", entry->name);
|
|
userentry[34] = '\0';
|
|
} else {
|
|
snprintf(userentry, 80, " %s ", entry->name);
|
|
snprintf(&userentry[18], 60, " %8lu ", entry->size);
|
|
switch (entry->type) {
|
|
case 0x04:
|
|
sprintf(&userentry[30], "TXT ");
|
|
break;
|
|
case 0x06:
|
|
sprintf(&userentry[30], "BIN ");
|
|
break;
|
|
case 0x19:
|
|
sprintf(&userentry[30], "ADB ");
|
|
break;
|
|
case 0x1a:
|
|
sprintf(&userentry[30], "AWP ");
|
|
break;
|
|
case 0x1b:
|
|
sprintf(&userentry[30], "ASP ");
|
|
break;
|
|
case 0xfc:
|
|
sprintf(&userentry[30], "BAS ");
|
|
break;
|
|
case 0xff:
|
|
sprintf(&userentry[30], "SYS ");
|
|
break;
|
|
default:
|
|
sprintf(&userentry[30], "$%02x ", entry->type);
|
|
}
|
|
}
|
|
if (i == selected)
|
|
revers(1);
|
|
cputs(userentry);
|
|
if (i == selected)
|
|
revers(0);
|
|
} else {
|
|
strcpy(userentry, " ");
|
|
cputs(userentry);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* File chooser UI
|
|
* first - index of first file on screen
|
|
* selected - index of currently selected file
|
|
* entries - total number of file entries in directory
|
|
*/
|
|
void file_ui_draw_all(uint16_t first, uint16_t selected, uint16_t entries) {
|
|
uint16_t i;
|
|
uint16_t last = first + FILELINES;
|
|
for (i = first; i < last; ++i)
|
|
file_ui_draw(i, first, selected, entries);
|
|
}
|
|
|
|
/*
|
|
* Perform ProDOS MLI ON_LINE call to
|
|
* write all online volume names into iobuf[]
|
|
* Return the number of entries
|
|
*/
|
|
uint16_t online(void) {
|
|
uint16_t entries = 0;
|
|
struct tabent *entry;
|
|
uint8_t i, j, len;
|
|
__asm__("lda #$00"); // All devices
|
|
__asm__("sta mliparam + 1");
|
|
__asm__("lda #<%v", iobuf); // iobuf LSB
|
|
__asm__("sta mliparam + 2");
|
|
__asm__("lda #>%v", iobuf); // iobuf MSB
|
|
__asm__("ina"); // Bump up 256 bytes into iobuf
|
|
__asm__("sta mliparam + 3");
|
|
__asm__("lda #$c5"); // ON_LINE
|
|
__asm__("ldx #$02"); // Two parms
|
|
__asm__("jsr callmli");
|
|
entry = (struct tabent*)iobuf;
|
|
for (i = 0; i < 16; ++i) {
|
|
len = iobuf[256 + i * 16] & 0x0f;
|
|
if (len > 0) {
|
|
entry->type = 0x0f;
|
|
entry->name[0] = '/';
|
|
for (j = 0; j < len; ++j)
|
|
entry->name[j + 1] = iobuf[256 + i * 16 + j + 1];
|
|
entry->name[j + 1] = '\0';
|
|
++entry;
|
|
++entries;
|
|
}
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
/*
|
|
* Read directory entries and populate table in iobuf[]
|
|
* for file_ui()
|
|
* page - page index, each page has FILESPERPAGE entries
|
|
* page is 0, 1, 2, 3 ...
|
|
* num_entries - number of entries read
|
|
* Returns 0 there are more files, 1 if end of directory reached
|
|
*/
|
|
uint8_t read_dir(uint8_t page, uint16_t *num_entries) {
|
|
DIR *dp;
|
|
struct dirent *ent;
|
|
struct tabent *entry;
|
|
uint8_t rc = 0;
|
|
uint16_t entries = 0, i = 0;
|
|
entry = (struct tabent*)iobuf;
|
|
if (page == 0) {
|
|
strcpy(entry->name, ".."); // Add fake '..' entry
|
|
entry->type = 0x0f;
|
|
++entry;
|
|
++entries;
|
|
}
|
|
dp = opendir(".");
|
|
while (1) {
|
|
ent = readdir(dp);
|
|
if (!ent) {
|
|
rc = 1;
|
|
break;
|
|
}
|
|
if (++i < page * FILESPERPAGE)
|
|
continue;
|
|
if (entries >= FILESPERPAGE)
|
|
break;
|
|
memcpy(entry->name, ent->d_name, 16);
|
|
entry->type = ent->d_type;
|
|
entry->size = ent->d_size;
|
|
++entry;
|
|
++entries;
|
|
}
|
|
closedir(dp);
|
|
*num_entries = entries;
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* File chooser UI
|
|
* Leaves file name in userentry[], or empty string if error/cancel
|
|
* msg1 - Message for top line
|
|
* msg2 - Message for second line
|
|
* msg3 - Message for third line
|
|
*/
|
|
void file_ui(char *msg1, char *msg2, char *msg3) {
|
|
struct tabent *entry;
|
|
char c;
|
|
uint16_t entries;
|
|
uint8_t end_of_dir;
|
|
uint16_t first = 0, current = 0;
|
|
uint8_t toplevel = 0, page = 0;
|
|
cutbuflen = 0;
|
|
restart:
|
|
clrscr();
|
|
cursor(0);
|
|
gotoxy(0,0);
|
|
revers(1);
|
|
cprintf("%s", msg1);
|
|
revers(0);
|
|
gotoxy(0,1);
|
|
cprintf("%s", msg2);
|
|
gotoxy(0,2);
|
|
cprintf("%s", msg3);
|
|
getcwd(userentry, 80);
|
|
gotoxy(0,4);
|
|
revers(1);
|
|
cprintf("%s", (toplevel ? "Volumes" : userentry));
|
|
revers(0);
|
|
if (toplevel)
|
|
entries = online();
|
|
else
|
|
end_of_dir = read_dir(page, &entries);
|
|
redraw:
|
|
file_ui_draw_all(first, current, entries);
|
|
while (1) {
|
|
c = cgetc();
|
|
switch (c) {
|
|
case 0x0b: // Up
|
|
if (current > 0)
|
|
--current;
|
|
else
|
|
if (page > 0) {
|
|
--page;
|
|
current = FILESPERPAGE - 1;
|
|
first = current - FILELINES + 1;
|
|
goto restart;
|
|
}
|
|
if (current < first) {
|
|
if (first > FILELINES)
|
|
first -= FILELINES;
|
|
else
|
|
first = 0;
|
|
goto redraw;
|
|
}
|
|
file_ui_draw(current, first, current, entries);
|
|
file_ui_draw(current + 1, first, current, entries);
|
|
break;
|
|
case 0x0a: // Down
|
|
if (current < entries - 1)
|
|
++current;
|
|
else if (!end_of_dir) {
|
|
++page;
|
|
first = current = 0;
|
|
goto restart;
|
|
}
|
|
if (current >= first + FILELINES) {
|
|
first += FILELINES;
|
|
goto redraw;
|
|
}
|
|
file_ui_draw(current - 1, first, current, entries);
|
|
file_ui_draw(current, first, current, entries);
|
|
break;
|
|
case EOL:
|
|
entry = (struct tabent*)iobuf + current;
|
|
switch (entry->type) {
|
|
case 0x0f: // Directory
|
|
getcwd(userentry, 80);
|
|
if (strcmp(entry->name, "..") == 0) {
|
|
for (c = strlen(userentry); c > 0; --c)
|
|
if (userentry[c] == '/') {
|
|
userentry[c] = '\0';
|
|
break;
|
|
}
|
|
if (c <= 1)
|
|
toplevel = 1;
|
|
else
|
|
chdir(userentry);
|
|
first = current = 0;
|
|
goto restart;
|
|
} else {
|
|
if (toplevel) {
|
|
strcpy(userentry, entry->name);
|
|
chdir(userentry);
|
|
} else {
|
|
getcwd(userentry, 80);
|
|
strcat(userentry, "/");
|
|
strcat(userentry, entry->name);
|
|
chdir(userentry);
|
|
}
|
|
toplevel = 0;
|
|
first = current = 0;
|
|
goto restart;
|
|
}
|
|
break;
|
|
case 0x04: // ASCII text
|
|
getcwd(userentry, 80);
|
|
strcat(userentry, "/");
|
|
strcat(userentry, entry->name);
|
|
goto done;
|
|
break;
|
|
default:
|
|
if (prompt_okay("Not a text file, open anyhow") != 0) {
|
|
strcpy(userentry, "");
|
|
goto done;
|
|
}
|
|
getcwd(userentry, 80);
|
|
strcat(userentry, "/");
|
|
strcat(userentry, entry->name);
|
|
goto done;
|
|
}
|
|
break;
|
|
case ESC:
|
|
strcpy(userentry, "");
|
|
goto done;
|
|
break;
|
|
case 0x09: // Tab
|
|
if (prompt_for_name("Enter filename", 0) == 255)
|
|
goto restart; // ESC pressed
|
|
if (userentry[0] == '/') // Absolute path
|
|
goto done;
|
|
getcwd(replace, 80); // Otherwise relative path
|
|
strcat(replace, "/");
|
|
strcat(replace, userentry);
|
|
strcpy(userentry, replace);
|
|
strcpy(replace, "");
|
|
goto done;
|
|
}
|
|
}
|
|
done:
|
|
clrscr();
|
|
cursor(1);
|
|
}
|
|
|
|
/*
|
|
* Main editor routine
|
|
* fname - filename to open or ""
|
|
*/
|
|
int edit(char *fname) {
|
|
char c;
|
|
uint16_t position, tmp;
|
|
uint8_t i, ask;
|
|
videomode(VIDEOMODE_80COL);
|
|
if (fname) {
|
|
strcpy(filename, fname);
|
|
cprintf("Loading file %s ", filename);
|
|
if (load_file(filename, 1, 0)) {
|
|
snprintf(userentry, 80, "Can't load '%s'", filename);
|
|
show_error(userentry);
|
|
strcpy(filename, "");
|
|
}
|
|
}
|
|
draw_screen();
|
|
while (1) {
|
|
cursor(1);
|
|
c = cgetc();
|
|
if (mode == SRCH3) // Reset 'Not found'
|
|
mode = SEL_NONE;
|
|
switch (c) {
|
|
case ESC: // Escape from block mode operations
|
|
startsel = endsel = 65535U;
|
|
mode = SEL_NONE;
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + '1': // Top
|
|
jump_pos(0);
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + '2':
|
|
jump_pos(DATASIZE() / 8);
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + '3':
|
|
jump_pos(2L * DATASIZE() / 8);
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + '4':
|
|
jump_pos(3L * DATASIZE() / 8);
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + '5':
|
|
jump_pos(4L * DATASIZE() / 8);
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + '6':
|
|
jump_pos(5L * DATASIZE() / 8);
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + '7':
|
|
jump_pos(6L * DATASIZE() / 8);
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + '8':
|
|
jump_pos(7L * DATASIZE() / 8);
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + '9': // Bottom
|
|
jump_pos(DATASIZE());
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + 0x08: // OA-Left "Word left"
|
|
word_left();
|
|
break;
|
|
case 0x80 + 0x15: // OA-Right "Word right"
|
|
word_right();
|
|
break;
|
|
case 0x80 + ',': // OA-< "Home"
|
|
case 0x80 + '<':
|
|
case 0x01: // Ctrl-A (Emacs style)
|
|
goto_bol();
|
|
break;
|
|
case 0x80 + '.': // OA-> "End"
|
|
case 0x80 + '>':
|
|
case 0x05: // Ctrl-E (Emacs style)
|
|
goto_eol();
|
|
break;
|
|
case 0x8b: // OA-Up "Page Up"
|
|
page_up();
|
|
break;
|
|
case 0x8a: // OA-Down "Page Down"
|
|
page_down();
|
|
break;
|
|
case 0x80 + ' ': // OA-SPACE start/end selection
|
|
tmp = (startsel == 65535U ? 0 : 1); // Prev selection active?
|
|
if (tmp) {
|
|
endsel = gapbegin;
|
|
mode = SEL_NONE;
|
|
draw_screen();
|
|
} else {
|
|
startsel = endsel = gapbegin;
|
|
mode = SEL_SELECT;
|
|
update_status_line();
|
|
}
|
|
break;
|
|
case 0x80 + 'A': // OA-A "Select All"
|
|
case 0x80 + 'a': // OA-a
|
|
startsel = 0;
|
|
endsel = DATASIZE();
|
|
mode = SEL_NONE;
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + 'X': // OA-X "Cut"
|
|
case 0x80 + 'x': // OA-x
|
|
case 0x18: // ^X "Cut"
|
|
tmp = 65535U;
|
|
case 0x80 + 'C': // OA-C "Copy"
|
|
case 0x80 + 'c': // OA-c
|
|
case 0x03: // ^C "Copy"
|
|
++tmp;
|
|
mode = SEL_NONE;
|
|
if (startsel == 65535U) { // No selection
|
|
beep();
|
|
break;
|
|
}
|
|
order_selection();
|
|
position = gapbegin;
|
|
if (endsel - startsel <= CUTBUFSZ) {
|
|
cutbuflen = endsel - startsel;
|
|
jump_pos(startsel);
|
|
__asm__("lda %v", auxbank);
|
|
__asm__("sta $c073"); // Set aux bank
|
|
copyaux((char*)GAPBUFADDR + gapend + 1, iobuf, cutbuflen, FROMAUX);
|
|
__asm__("lda #$00");
|
|
__asm__("sta $c073"); // Set aux bank back to 0
|
|
} else {
|
|
cutbuflen = 0;
|
|
if (save_file(1, 0) == 1) {
|
|
show_error("Can't save CLIPBOARD");
|
|
draw_screen();
|
|
break;
|
|
}
|
|
}
|
|
if (tmp == 0) { // Cut
|
|
mark_undo();
|
|
jump_pos(startsel);
|
|
gapend += (endsel - startsel);
|
|
set_modified(1);
|
|
} else // Copy
|
|
jump_pos(position);
|
|
startsel = endsel = 65535U;
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + 'V': // OA-V "Paste"
|
|
case 0x80 + 'v': // OA-v
|
|
case 0x16: // ^V "Paste"
|
|
mode = SEL_NONE;
|
|
mark_undo();
|
|
if (cutbuflen > 0) {
|
|
__asm__("lda %v", auxbank);
|
|
__asm__("sta $c073"); // Set aux bank
|
|
copyaux(iobuf, (char*)GAPBUFADDR + gapbegin, cutbuflen, TOAUX);
|
|
__asm__("lda #$00");
|
|
__asm__("sta $c073"); // Set aux bank back to 0
|
|
gapbegin += cutbuflen;
|
|
} else {
|
|
if (load_file("CLIPBOARD", 0, 1))
|
|
show_error("Can't open CLIPBOARD");
|
|
}
|
|
startsel = endsel = 65535U;
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + 'R': // OA-R "Replace"
|
|
case 0x80 + 'r': // OA-r
|
|
tmp = 65535U;
|
|
case 0x80 + 'F': // OA-F "Find"
|
|
case 0x80 + 'f': // OA-F "Find"
|
|
++tmp;
|
|
snprintf(userentry, 80, "Find (%s)", search);
|
|
if (prompt_for_name(userentry, 0) == 255)
|
|
break; // ESC pressed
|
|
if (strlen(userentry) > 0)
|
|
strcpy(search, userentry);
|
|
else if (strlen(search) == 0)
|
|
break;
|
|
cursor_right();
|
|
if (tmp == 0) { // Replace mode
|
|
snprintf(userentry, 80, "Replace (%s)", replace);
|
|
if (prompt_for_name(userentry, 0) == 255)
|
|
break; // ESC pressed
|
|
if (strlen(userentry) > 0)
|
|
strcpy(replace, userentry);
|
|
else if (strlen(replace) == 0)
|
|
break;
|
|
ask = 0;
|
|
if (prompt_okay("Ask for each") == 0)
|
|
ask = 1;
|
|
canundo = 0;
|
|
}
|
|
do_search_replace(tmp, ask);
|
|
update_status_line();
|
|
break;
|
|
case 0x80 + 'I': // OA-I "Insert file"
|
|
case 0x80 + 'i':
|
|
file_ui("Insert File at Cursor", "", openfilemsg);
|
|
if (strlen(userentry) == 0) {
|
|
draw_screen();
|
|
break;
|
|
}
|
|
mark_undo();
|
|
if (load_file(userentry, 0, 0)) {
|
|
snprintf(iobuf, 80, "Can't open '%s'", userentry);
|
|
show_error(iobuf);
|
|
}
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + 'O': // OA-O "Open"
|
|
case 0x80 + 'o':
|
|
if (status[0]) { // Buffer is modified
|
|
if (strlen(filename) == 0)
|
|
strcpy(userentry, "Save <scratch> buffer");
|
|
else
|
|
snprintf(userentry, 80, "Save '%s'", filename);
|
|
if (prompt_okay(userentry) == 0)
|
|
save();
|
|
}
|
|
file_ui("Open File", "", openfilemsg);
|
|
if (strlen(userentry) == 0) {
|
|
draw_screen();
|
|
break;
|
|
}
|
|
strcpy(filename, userentry);
|
|
if (load_file(filename, 1, 0)) {
|
|
snprintf(userentry, 80, "Can't open '%s'", filename);
|
|
show_error(userentry);
|
|
strcpy(filename, "");
|
|
}
|
|
canundo = 0;
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + 'N': // OA-N "Name"
|
|
case 0x80 + 'n': // OA-n
|
|
name_file();
|
|
if (email_mode == 1)
|
|
email_mode = 3;
|
|
break;
|
|
case 0x80 + 'Q': // OA-Q "Quit"
|
|
case 0x80 + 'q': // OA-q
|
|
save_all();
|
|
switch (email_mode) {
|
|
case 2:
|
|
if (prompt_okay("Add attachments") == 0)
|
|
load_attacher();
|
|
// Fall through
|
|
case 1:
|
|
case 3:
|
|
if (prompt_okay("Return to EMAIL") == 0)
|
|
load_email();
|
|
break;
|
|
default:
|
|
if (prompt_okay("Quit to ProDOS") == 0) {
|
|
revers(0);
|
|
clrscr();
|
|
reconnect_ramdisk();
|
|
exit(0);
|
|
}
|
|
}
|
|
break;
|
|
case 0x80 + 'U': // OA-U "Unwrap"
|
|
case 0x80 + 'u': // OA-w
|
|
canundo = 0;
|
|
word_wrap_para(0);
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + 'W': // OA-W "Wrap"
|
|
case 0x80 + 'w': // OA-w
|
|
canundo = 0;
|
|
word_wrap_para(1);
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + 'S': // OA-S "Save"
|
|
case 0x80 + 's': // OA-s
|
|
save();
|
|
draw_screen();
|
|
break;
|
|
case 0x80 + 'Z': // OA-Z "Undo"
|
|
case 0x80 + 'z': // OA-z
|
|
case 0x1a: // Ctrl-Z
|
|
if (canundo) {
|
|
tmp = gapbegin;
|
|
gapbegin = oldgapbegin;
|
|
oldgapbegin = tmp;
|
|
tmp = gapend;
|
|
gapend = oldgapend;
|
|
oldgapend = tmp;
|
|
draw_screen();
|
|
} else
|
|
beep();
|
|
break;
|
|
case 0x80 + DELETE: // OA-Backspace
|
|
case 0x04: // Ctrl-D "DELETE"
|
|
if (mode == SEL_NONE) {
|
|
mark_undo();
|
|
delete_char_right();
|
|
update_after_delete_char_right();
|
|
set_modified(1);
|
|
}
|
|
break;
|
|
case 0x80 + '?': // OA-? "Help"
|
|
help1:
|
|
help(1);
|
|
c = cgetc();
|
|
if (c == ESC)
|
|
goto donehelp;
|
|
help(2);
|
|
c = cgetc();
|
|
switch (c) {
|
|
case 0x0b: // Up
|
|
case 'p':
|
|
case 'P':
|
|
goto help1;
|
|
}
|
|
donehelp:
|
|
draw_screen();
|
|
break;
|
|
case 0x0c: // Ctrl-L "REFRESH"
|
|
draw_screen();
|
|
break;
|
|
case DELETE: // DEL "BACKSPACE"
|
|
if (startsel == 65535U) { // No selection
|
|
if (mode == SEL_NONE) {
|
|
mark_undo();
|
|
delete_char();
|
|
update_after_delete_char();
|
|
set_modified(1);
|
|
}
|
|
} else {
|
|
snprintf(userentry, 80,
|
|
"Delete selection (%d chars)", abs(endsel - startsel));
|
|
if (prompt_okay(userentry) != 0)
|
|
break;
|
|
order_selection();
|
|
mode = SEL_NONE;
|
|
mark_undo();
|
|
jump_pos(startsel);
|
|
gapend += (endsel - startsel);
|
|
set_modified(1);
|
|
startsel = endsel = 65535U;
|
|
draw_screen();
|
|
break;
|
|
}
|
|
break;
|
|
case 0x09: // Tab
|
|
if (mode == SEL_NONE) {
|
|
mark_undo();
|
|
c = next_tabstop(curscol) - curscol;
|
|
for (i = 0; i < c; ++i) {
|
|
insert_char(' ');
|
|
update_after_insert_char();
|
|
}
|
|
set_modified(1);
|
|
}
|
|
break;
|
|
case 0x08: // Left
|
|
cursor_left();
|
|
break;
|
|
case 0x15: // Right
|
|
cursor_right();
|
|
break;
|
|
case 0x0b: // Up
|
|
cursor_up();
|
|
break;
|
|
case 0x0a: // Down
|
|
cursor_down();
|
|
break;
|
|
case EOL: // Return
|
|
if (mode == SEL_NONE) {
|
|
mark_undo();
|
|
insert_char(c);
|
|
update_after_insert_char();
|
|
set_modified(1);
|
|
}
|
|
break;
|
|
default:
|
|
if (*(uint8_t*)0xc062 & 0x80) { // Closed Apple depressed
|
|
// CA-number (1-9) - quick jump to first 9 buffers
|
|
if ((c >= '1') && (c <= '9')) {
|
|
change_aux_bank(c - '0');
|
|
startsel = endsel = 65535U;
|
|
draw_screen();
|
|
} else if ((c == 'B') || (c == 'b')) { // CA-B "Buffer"
|
|
snprintf(userentry, 80, "Buffer # (1 - %u)", banktbl[0]);
|
|
if (prompt_for_name(userentry, 0) == 255) {
|
|
update_status_line();
|
|
break;
|
|
}
|
|
if (strlen(userentry) == 0) {
|
|
update_status_line();
|
|
break;
|
|
}
|
|
tmp = atoi(userentry);
|
|
if ((tmp < 1) || (tmp > banktbl[0])) {
|
|
beep();
|
|
update_status_line();
|
|
break;
|
|
}
|
|
change_aux_bank(tmp);
|
|
startsel = endsel = 65535U;
|
|
draw_screen();
|
|
} else if ((c == '-') || (c == '_')) { // CA-minus
|
|
tmp = (l_auxbank - 2) % banktbl[0] + 1;
|
|
change_aux_bank(tmp);
|
|
startsel = endsel = 65535U;
|
|
draw_screen();
|
|
} else if ((c == '+') || (c == '=')) { // CA-plus
|
|
tmp = l_auxbank % banktbl[0] + 1;
|
|
change_aux_bank(tmp);
|
|
startsel = endsel = 65535U;
|
|
draw_screen();
|
|
} else if ((c == 'E') || (c == 'e')) { // CA-E "Extend file"
|
|
i = status[2];
|
|
if (i == 0) // If this was single buf file, make it part 1
|
|
i = 1;
|
|
if (open_new_aux_bank(i) == 1) {
|
|
snprintf(userentry, 80,
|
|
"Buffer [%03u] not avail. Can't extend.", l_auxbank + 1);
|
|
show_error(userentry);
|
|
break;
|
|
}
|
|
status[2] = i + 1;
|
|
draw_screen();
|
|
} else if ((c == 'L') || (c == 'l')) {
|
|
buffer_list();
|
|
draw_screen();
|
|
} else if ((c == 'T') || (c == 't')) { // CA-T "Truncate file"
|
|
if (prompt_okay("Truncate file here") == 0) {
|
|
tmp = l_auxbank;
|
|
gapend = BUFSZ - 1;
|
|
draw_screen();
|
|
if (status[2] > 0) { // If multipart
|
|
if (status[2] == 1) // Truncating in first bank
|
|
status[2] = 0; // Make it a singleton
|
|
change_aux_bank(++l_auxbank);
|
|
do {
|
|
gapbegin = 0;
|
|
gapend = BUFSZ - 1;
|
|
filename[0] = '\0';
|
|
i = status[2];
|
|
status[0] = status[1] = status[2] = 0;
|
|
change_aux_bank(++l_auxbank);
|
|
} while (status[2] == i + 1);
|
|
change_aux_bank(tmp);
|
|
}
|
|
}
|
|
set_modified(1);
|
|
canundo = 0;
|
|
} else if ((c =='S') || (c == 's')) { // CA-S "Save all"
|
|
save_all();
|
|
draw_screen();
|
|
}
|
|
}
|
|
else if ((c >= 0x20) && (c < 0x80) && (mode == SEL_NONE)) {
|
|
mark_undo();
|
|
insert_char(c);
|
|
update_after_insert_char();
|
|
set_modified(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Usage message
|
|
*/
|
|
void usage(void) {
|
|
printf("Usage: -EDIT.SYSTEM [filename.txt]");
|
|
printf(" or -EDIT.SYSTEM [-reademail|-email|-news] filename.txt");
|
|
reconnect_ramdisk();
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* Command line arguments:
|
|
* -reademail - Open file read-only. Load EMAIL.SYSTEM on quit.
|
|
* -email - Prompt for attachments, load ATTACHER.SYSTEM
|
|
* or EMAIL.SYSTEM on quit
|
|
* -news - Load EMAIL.SYSTEM on quit
|
|
*/
|
|
void main(int argc, char *argv[]) {
|
|
uint8_t *pp = (uint8_t*)0xbf98;
|
|
if (!(*pp & 0x02)) {
|
|
printf("Need 80 cols");
|
|
return;
|
|
}
|
|
if ((*pp & 0x30) != 0x30) {
|
|
printf("Need 128K");
|
|
return;
|
|
}
|
|
disconnect_ramdisk();
|
|
avail_aux_banks();
|
|
init_aux_banks();
|
|
getcwd(startdir, 80);
|
|
email_mode = 0;
|
|
switch (argc) {
|
|
case 3:
|
|
if (strcmp(argv[1], "-reademail") == 0)
|
|
email_mode = 1;
|
|
else if (strcmp(argv[1], "-email") == 0)
|
|
email_mode = 2;
|
|
else if (strcmp(argv[1], "-news") == 0)
|
|
email_mode = 3;
|
|
else
|
|
usage();
|
|
edit(argv[2]);
|
|
break;
|
|
case 2:
|
|
if (argv[1][0] == '-')
|
|
usage();
|
|
edit(argv[1]);
|
|
break;
|
|
case 1:
|
|
edit(NULL);
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
|