Files
zSoft/apps/ed/ed.c

1280 lines
36 KiB
C

///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name: ed.c
// Created: April 2020
// Author(s): Philip Smart
// Description: A stripped down memory lean version of the Kilo editor. Kilo uses more than 3x the
// size of the file being editted through malloc which can be a problem on the zpu
// depending upon model and also limits the filesize to 64K on the K4F. Hence stripping
// out nicities such as highlights and render.
//
// This program implements a loadable application which can be loaded from SD card by
// the zOS/ZPUTA application. The idea is that commands or programs can be stored on the
// SD card and executed by zOS/ZPUTA just like an OS such as Linux. The primary purpose
// is to be able to minimise the size of zOS/ZPUTA for applications where minimal ram is
// available.
//
// Credits: Salvatore Sanfilippo <antirez at gmail dot com> 2016 for his 0.0.1 verson of Kilo
// upon which this editor is based.
// Copyright: (c) 2019-2020 Philip Smart <philip.smart@net2net.org> zOS/ZPUTA enhancements, bug
// fixes and adaptation to the zOS/ZPUTA framework.
//
// History: April 2020 - Ported v0.0.1 of Kilo and stripped it of features to get a leaner
// memory using editor. Some functionality has been lost from the
// original editor but it is still very useable.
//
// Notes: See Makefile to enable/disable conditional components
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// This source file is free software: you can redistribute it and#or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#define ED_VERSION "1.0"
#ifdef __cplusplus
extern "C" {
#endif
#if defined(__K64F__)
#include <stdio.h>
#include <ctype.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include "k64f_soc.h"
#include <../../libraries/include/stdmisc.h>
#elif defined(__ZPU__)
#include <stdint.h>
#include <stdio.h>
#include "zpu_soc.h"
#include <stdlib.h>
#include <ctype.h>
#include <stdmisc.h>
#else
#error "Target CPU not defined, use __ZPU__ or __K64F__"
#endif
#include "interrupts.h"
#include "ff.h" /* Declarations of FatFs API */
#include "utils.h"
//
#if defined __ZPUTA__
#include "zputa_app.h"
#elif defined __ZOS__
#include "zOS_app.h"
#else
#error OS not defined, use __ZPUTA__ or __ZOS__
#endif
//
#include "app.h"
#include "ed.h"
// Utility functions.
#include "tools.c"
// Version info.
#define VERSION "v1.0"
#define VERSION_DATE "22/04/2020"
#define APP_NAME "ED"
#define MAX_APPEND_BUFSIZE 1024
#define ED_QUIT_TIMES 3
#define ED_QUERY_LEN 256
#define ED_TAB_SIZE 4
struct editorSyntax {
char **filematch;
char **keywords;
char singleline_comment_start[2];
char multiline_comment_start[3];
char multiline_comment_end[3];
int flags;
};
/* This structure represents a single line of the file we are editing. */
typedef struct erow {
int idx; /* Row index in the file, zero-based. */
int size; /* Size of the row, excluding the null term. */
char *chars; /* Row content. */
} erow;
struct editorConfig {
int cx,cy; /* Cursor x and y position in characters */
int rowoff; /* Offset of row displayed. */
int coloff; /* Offset of column displayed. */
int screenrows; /* Number of rows that we can show */
int screencols; /* Number of cols that we can show */
int numrows; /* Number of rows */
erow *row; /* Rows */
int dirty; /* File modified but not saved. */
char *filename; /* Currently open filename */
char statusmsg[80];
uint32_t statusmsg_time;
struct editorSyntax *syntax; /* Current syntax highlight, or NULL. */
};
static struct editorConfig E;
enum KEY_ACTION{
KEY_NULL = 0, /* NULL */
CTRL_C = 3, /* Ctrl-c */
CTRL_D = 4, /* Ctrl-d */
CTRL_F = 6, /* Ctrl-f */
CTRL_H = 8, /* Ctrl-h */
TAB = 9, /* Tab */
CTRL_L = 12, /* Ctrl+l */
ENTER = 13, /* Enter */
CTRL_Q = 17, /* Ctrl-q */
CTRL_S = 19, /* Ctrl-s */
CTRL_U = 21, /* Ctrl-u */
ESC = 27, /* Escape */
BACKSPACE = 127, /* Backspace */
/* The following are just soft codes, not really reported by the
* terminal directly.
*/
ARROW_LEFT = 1000,
ARROW_RIGHT,
ARROW_UP,
ARROW_DOWN,
DEL_KEY,
HOME_KEY,
INSERT_KEY,
END_KEY,
PAGE_UP,
PAGE_DOWN,
F1_KEY,
F2_KEY,
F3_KEY,
F4_KEY,
F5_KEY,
F6_KEY,
F7_KEY,
F8_KEY,
F9_KEY,
F10_KEY,
F11_KEY,
F12_KEY
};
// A method to access the millisecond counter within the hardware or main OS.
//
uint32_t sysmillis(void)
{
uint32_t milliSec;
#if defined __ZPU__
milliSec = (uint32_t)RTC_MILLISECONDS;
#elif defined __K64F__
milliSec = (uint32_t)*G->millis;
#else
#error "Target CPU not defined, use __ZPU__ or __K64F__"
#endif
return milliSec;
}
// Simple method to create a local delay timer not dependent on system libraries.
//
void syswait(uint32_t wait)
{
uint32_t startTime;
startTime = sysmillis();
while((sysmillis() - startTime) < wait);
}
int8_t getKeyChar(uint32_t waitTime)
{
int8_t keyIn;
uint32_t timeout = sysmillis();
do {
#if defined __SHARPMZ__
// Get key from system, request ansi key sequence + non-blocking (=2).
keyIn = getKey(2);
#else
// Get key from system, request non-blocking (=0).
keyIn = getKey(0);
#endif
} while(keyIn == -1 && (sysmillis() - timeout) < waitTime);
return(keyIn);
}
/* Read a key from the terminal put in raw mode, trying to handle
* escape sequences. */
int editorReadKey(void)
{
int8_t seq[3];
int8_t c;
// Wait for a key to be pressed.
while((c = (char)getKeyChar(500)) == -1);
/* escape sequence */
if((char)c == ESC)
{
/* If this is just an ESC, we'll timeout here. */
if((seq[0] = getKeyChar(500)) == -1) return ESC;
if((seq[1] = getKeyChar(500)) == -1) return ESC;
/* ESC [ sequences. */
if ((char)seq[0] == '[')
{
if ((char)seq[1] >= '0' && (char)seq[1] <= '9')
{
/* Extended escape, read additional byte. */
if((seq[2] = getKeyChar(500)) == -1) return ESC;
if ((char)seq[2] == '~')
{
switch((char)seq[1])
{
case '1': return HOME_KEY;
case '2': return INSERT_KEY;
case '3': return DEL_KEY;
case '5': return PAGE_UP;
case '6': return PAGE_DOWN;
}
}
} else {
switch((char)seq[1])
{
case 'A': return ARROW_UP;
case 'B': return ARROW_DOWN;
case 'C': return ARROW_RIGHT;
case 'D': return ARROW_LEFT;
case 'H': return HOME_KEY;
case 'F': return END_KEY;
}
}
}
/* ESC O sequences. */
else if ((char)seq[0] == 'O')
{
switch((char)seq[1])
{
case 'H': return HOME_KEY;
case 'F': return END_KEY;
case 'Q': return F2_KEY;
case 'R': return F3_KEY;
case 'S': return F3_KEY;
}
}
return ESC;
} else
{
return (int)c;
}
}
/* Use the ESC [6n escape sequence to query the horizontal cursor position
* and return it. On error -1 is returned, on success the position of the
* cursor is stored at *rows and *cols and 0 is returned.
*/
int getCursorPosition(uint32_t *rows, uint32_t *cols)
{
char buf[32];
char *ptr;
unsigned int i = 0;
int c;
// Save cursor position.
//
printf("%c%c", 0x1b, '7');
// Cursor to maximum X/Y location, report cursor location
//
fputs("\x1b[0;0H", stdout);
syswait(10);
fputs("\x1b[999;999H", stdout);
syswait(10);
fputs("\x1b[6n", stdout);
// Read the response: ESC [ rows ; cols R
//
while (i < sizeof(buf)-1)
{
if((c = getKeyChar(2000)) == -1) break;
if((i == 0 && (char)c != ESC) || (i == 1 && (char)c != '[')) return -1;
if((char)c == ';')
buf[i] = ' ';
else
buf[i] = (char)c;
if (buf[i] == 'R') break;
i++;
}
buf[i] = 0x00;
// Parse it.
//
ptr = &buf[2];
if(!uxatoi(&ptr, rows)) return -1;
if(!uxatoi(&ptr, cols)) return -1;
// Restore cursor.
//
printf("%c%c", 0x1b, '8');
return 0;
}
/* Try to get the number of columns in the current terminal. If the ioctl()
* call fails the function will try to query the terminal itself.
* Returns 0 on success, -1 on error.
*/
int getWindowSize(int *rows, int *cols)
{
uint32_t termRows, termCols;
if(getCursorPosition(&termRows,&termCols) == 0)
{
*cols = (int)termCols;
*rows = (int)termRows;
} else
{
*cols = 80;
*rows = 24;
}
return 0;
}
/* ======================= Editor rows implementation ======================= */
/* Insert a row at the specified position, shifting the other rows on the bottom
* if required.
*/
int editorInsertRow(int at, char *s, size_t len)
{
int j;
if (at > E.numrows) return 0;
E.row = sys_realloc(E.row,sizeof(erow)*(E.numrows+1));
if(E.row == NULL) { printf("editorInsertRow: Memory exhausted\n"); return 1; }
if (at != E.numrows)
{
memmove(E.row+at+1,E.row+at,sizeof(E.row[0])*(E.numrows-at));
for (j = at+1; j <= E.numrows; j++) E.row[j].idx++;
}
E.row[at].size = len;
E.row[at].chars = sys_malloc(len+1);
if(E.row[at].chars == NULL) { printf("editorInsertRow: Memory exhausted\n"); return 1; }
memcpy(E.row[at].chars,s,len+1);
E.row[at].idx = at;
E.numrows++;
E.dirty++;
return 0;
}
/* Free row's heap allocated stuff. */
void editorFreeRow(erow *row)
{
if(row->chars != NULL)
{
sys_free(row->chars);
row->chars = NULL;
}
}
// Method to release all used memory back to the heap.
//
void editorCleanup(void)
{
int idx;
// Free the editor config structure.
if(E.row)
{
// Go through and free all row memory.
//
for(idx=0; idx < E.numrows; idx++)
{
if(E.row[idx].chars != NULL)
{
sys_free(E.row[idx].chars);
}
}
sys_free(E.row);
}
// Reset necessary variables.
E.cx = 0;
E.cy = 0;
E.rowoff = 0;
E.coloff = 0;
E.numrows = 0;
E.row = NULL;
E.dirty = 0;
return;
}
/* Remove the row at the specified position, shifting the remainign on the
* top. */
void editorDelRow(int at) {
int j;
erow *row;
if (at >= E.numrows) return;
row = E.row+at;
editorFreeRow(row);
memmove(E.row+at,E.row+at+1,sizeof(E.row[0])*(E.numrows-at-1));
for (j = at; j < E.numrows-1; j++) E.row[j].idx++;
E.numrows--;
E.dirty++;
}
/* Insert a character at the specified position in a row, moving the remaining
* chars on the right if needed.
*/
int editorRowInsertChar(erow *row, int at, int c)
{
if (at > row->size)
{
/* Pad the string with spaces if the insert location is outside the
* current length by more than a single character.
*/
int padlen = at-row->size;
/* In the next line +2 means: new char and null term.
*/
row->chars = sys_realloc(row->chars,row->size+padlen+2);
if(row->chars == NULL) { printf("editorRowInsertChar: Memory exhausted\n"); return 1; }
memset(row->chars+row->size,' ',padlen);
row->chars[row->size+padlen+1] = '\0';
row->size += padlen+1;
} else {
/* If we are in the middle of the string just make space for 1 new
* char plus the (already existing) null term.
*/
row->chars = sys_realloc(row->chars,row->size+2);
if(row->chars == NULL) { printf("editorRowInsertChar: Memory exhausted\n"); return 1; }
memmove(row->chars+at+1,row->chars+at,row->size-at+1);
row->size++;
}
row->chars[at] = c;
E.dirty++;
return 0;
}
/* Append the string 's' at the end of a row */
int editorRowAppendString(erow *row, char *s, size_t len)
{
row->chars = sys_realloc(row->chars,row->size+len+1);
if(row->chars == NULL) { printf("editorRowAppendString: Memory exhausted\n"); return 1; }
memcpy(row->chars+row->size,s,len);
row->size += len;
row->chars[row->size] = '\0';
E.dirty++;
return 0;
}
/* Delete the character at offset 'at' from the specified row. */
int editorRowDelChar(erow *row, int at)
{
if (row->size <= at) return 0;
memmove(row->chars+at,row->chars+at+1,row->size-at);
row->size--;
E.dirty++;
return 0;
}
/* Insert the specified char at the current prompt position. */
void editorInsertChar(int c)
{
int filerow = E.rowoff+E.cy;
int filecol = E.coloff+E.cx;
erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
/* If the row where the cursor is currently located does not exist in our
* logical representaion of the file, add enough empty rows as needed. */
if (!row)
{
while(E.numrows <= filerow)
{
if(editorInsertRow(E.numrows,"",0) != 0) return;
}
}
row = &E.row[filerow];
editorRowInsertChar(row,filecol,c);
if (E.cx == E.screencols-1)
E.coloff++;
else
E.cx++;
E.dirty++;
}
/* Inserting a newline is slightly complex as we have to handle inserting a
* newline in the middle of a line, splitting the line as needed.
*/
int editorInsertNewline(void)
{
int filerow = E.rowoff+E.cy;
int filecol = E.coloff+E.cx;
erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
if (!row)
{
if (filerow == E.numrows)
{
if(editorInsertRow(filerow,"",0) != 0) return 1;
goto fixcursor;
}
return 0;
}
/* If the cursor is over the current line size, we want to conceptually
* think it's just over the last character. */
if (filecol >= row->size) filecol = row->size;
if (filecol == 0)
{
if(editorInsertRow(filerow,"",0) != 0) return 1;
} else
{
/* We are in the middle of a line. Split it between two rows. */
if(editorInsertRow(filerow+1,row->chars+filecol,row->size-filecol) != 0) return 1;
row = &E.row[filerow];
row->chars[filecol] = '\0';
row->size = filecol;
}
fixcursor:
if (E.cy == E.screenrows-1)
{
E.rowoff++;
} else
{
E.cy++;
}
E.cx = 0;
E.coloff = 0;
return 0;
}
/* Delete the char at the current prompt position. */
int editorDelChar()
{
int filerow = E.rowoff+E.cy;
int filecol = E.coloff+E.cx;
erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
if (!row || (filecol == 0 && filerow == 0)) return 0;
if (filecol == 0)
{
/* Handle the case of column 0, we need to move the current line
* on the right of the previous one.
*/
filecol = E.row[filerow-1].size;
editorRowAppendString(&E.row[filerow-1],row->chars,row->size);
editorDelRow(filerow);
row = NULL;
if (E.cy == 0)
E.rowoff--;
else
E.cy--;
E.cx = filecol;
if (E.cx >= E.screencols)
{
int shift = (E.screencols-E.cx)+1;
E.cx -= shift;
E.coloff += shift;
}
} else
{
editorRowDelChar(row,filecol-1);
if (E.cx == 0 && E.coloff)
E.coloff--;
else
E.cx--;
}
E.dirty++;
return 0;
}
/* Load the specified program in the editor memory and returns 0 on success
* or 1 on error.
*/
int editorOpen(char *filename)
{
FIL fp;
FRESULT fr;
char buf[132];
uint8_t linelen;
int rowNo;
E.dirty = 0;
E.filename = filename;
fr = f_open(&fp, filename, FA_OPEN_ALWAYS | FA_READ);
if(fr)
{
printf("Failed to open file:%s\n", filename);
return 2;
}
// First parse to get number of lines in file to better allocate memory.
//
E.numrows = 0;
while(f_gets(buf, sizeof(buf), &fp) != NULL)
{
E.numrows++;
}
fr = f_lseek(&fp, 0);
if(fr)
{
printf("Failed to rewind file:%s\n", filename);
return 3;
}
// If there is data in the file, load it.
//
if(E.numrows)
{
// Allocate memory for the row array.
//
E.row = sys_malloc(sizeof(erow)*(E.numrows));
if(E.row == NULL) { printf("editorOpen: Memory exhausted\n"); return 1; }
memset(E.row, '\0', sizeof(erow)*(E.numrows));
// Now go through the file again and populate each row with the data.
//
rowNo = 0;
while(f_gets(buf, sizeof(buf), &fp) != NULL)
{
linelen = strlen(buf);
if (linelen && (buf[linelen-1] == '\n' || buf[linelen-1] == '\r'))
buf[--linelen] = '\0';
E.row[rowNo].size = linelen;
E.row[rowNo].chars = sys_malloc(linelen+1);
if(E.row[rowNo].chars == NULL) { printf("editorOpen: Memory exhausted\n"); return 1; }
memcpy(E.row[rowNo].chars, buf, linelen+1);
E.row[rowNo].idx = rowNo;
rowNo++;
}
}
f_close(&fp);
return 0;
}
/* Save the current file on disk. Return 0 on success, 1 on error.
*/
int editorSave(char *newFileName)
{
FIL fp;
FRESULT fr;
unsigned int bytes;
int totlen = 0;
int j;
// Open existing file or create.
fr = f_open(&fp, (newFileName == NULL ? E.filename : newFileName), FA_OPEN_ALWAYS | FA_WRITE | FA_READ);
if(fr)
{
sprintf(E.statusmsg, "Failed to open file:%s\n", (newFileName == NULL ? E.filename : newFileName));
E.statusmsg_time = sysmillis();
return 1;
}
/* Use truncate to zero the file the loop through the line buffers and
* write the contents to the file.
*/
if (f_truncate(&fp) != 0) goto writeerr;
// Loop through the row records and write out the characters.
//
totlen = 0;
for (j = 0; j < E.numrows; j++)
{
fr = f_write(&fp, E.row[j].chars, E.row[j].size, &bytes);
if(fr != 0) goto writeerr;
fr = f_putc('\n', &fp);
if(fr == -1) goto writeerr;
totlen += bytes + 1;
}
f_close(&fp);
E.dirty = 0;
sprintf(E.statusmsg, "%d bytes written on disk", totlen);
E.statusmsg_time = sysmillis();
return 0;
writeerr:
f_close(&fp);
sprintf(E.statusmsg, "Can't save! I/O error");
E.statusmsg_time = sysmillis();
return 1;
}
/* ============================= Terminal update ============================ */
/* Output buffer append method. The idea behind this is to avoid flickering
* effects on the VT100 terminal. The original method buffered everything
* and sent it in one go, but on MPU's the resources are limited, so the
* buffer is smaller and more flushes are made as needed.
*/
struct abuf {
char *b;
int len;
};
void abAppend(const char *s, int len, int flush)
{
static struct abuf *ab = NULL;
// First call, allocate memory for the buffer.
if(ab == NULL)
{
ab = sys_malloc(sizeof(struct abuf));
if(ab == NULL) { printf("abAppend: Memory exhausted\n"); return; }
ab->b = sys_malloc(MAX_APPEND_BUFSIZE);
if(ab->b == NULL) { printf("abAppend: Memory exhausted\n"); return; }
ab->len = 0;
}
// If adding this new text will overflow the buffer, flush before adding.
if((ab->len + len) >= MAX_APPEND_BUFSIZE || flush)
{
const char *ptr = ab->b;
for(ptr=ab->b; ptr < ab->b+ab->len; ptr++)
fputc(*ptr, stdout);
// If we are flushing then write out the additional data passed in to the method.
//
if(flush)
{
for(ptr=s; ptr < s+len; ptr++)
fputc(*ptr, stdout);
}
ab->len = 0;
}
// If we arent flushing, tag the passed data onto the buffer.
//
if(!flush)
{
memcpy(ab->b + ab->len, s, len);
ab->len += len;
} else
{
// On flush we free up the memory, extra cycles in alloc and free but keeps the heap balanced and no memory leaks when we exit.
//
sys_free(ab->b);
sys_free(ab);
ab = NULL;
}
}
/* This function writes the whole screen using VT100 escape characters
* starting from the logical state of the editor in the global state 'E'.
*/
int editorRefreshScreen(void)
{
int y;
int lastLine = -1;
erow *r;
char buf[32];
abAppend("\x1b[?25l",6, 0); /* Hide cursor. */
abAppend("\x1b[H",3, 0); /* Go home. */
for (y = 0; y < E.screenrows; y++)
{
int filerow = E.rowoff+y;
if (filerow >= E.numrows) {
if (E.numrows == 0 && y == E.screenrows/3)
{
char welcome[80];
sprintf(welcome, "Ed(itor) -- version %s\x1b[0K\r\n", ED_VERSION);
int welcomelen = strlen(welcome);
int padding = (E.screencols-welcomelen)/2;
if (padding)
{
abAppend("~",1, 0);
padding--;
}
while(padding--) abAppend(" ",1, 0);
abAppend(welcome,welcomelen, 0);
} else {
if(lastLine == -1) lastLine = y;
abAppend("~\x1b[0K\r\n",7, 0);
}
continue;
}
r = &E.row[filerow];
int len = r->size - E.coloff;
if (len > 0) {
if (len > E.screencols) len = E.screencols;
char *c = r->chars+E.coloff;
int idx, j;
for (j = 0; j < len; j++)
{
if(c[j] == TAB)
{
for(idx=0; idx < ED_TAB_SIZE; idx++) buf[idx] = ' ';
buf[ED_TAB_SIZE] = '\0';
abAppend(buf, ED_TAB_SIZE, 0);
}
else if (!isprint((int)c[j]))
{
char sym;
abAppend("\x1b[7m",4, 0);
if (c[j] <= 26)
sym = '@'+c[j];
else
sym = '?';
abAppend(&sym,1, 0);
abAppend("\x1b[0m",4, 0);
} else
{
abAppend(c+j,1, 0);
}
}
}
abAppend("\x1b[39m",5, 0);
abAppend("\x1b[0K",4, 0);
abAppend("\r\n",2, 0);
}
/* Create a two rows status. First row: */
abAppend("\x1b[0K",4, 0);
abAppend("\x1b[7m",4, 0);
char status[80], rstatus[80];
sprintf(status, "%-20s - %d lines %s", E.filename, E.numrows, E.dirty > 0 ? "(modified)" : "");
int len = strlen(status);
sprintf(rstatus, "%d/%d",E.rowoff+E.cy+1,E.numrows);
int rlen = strlen(rstatus);
if (len > E.screencols) len = E.screencols;
abAppend(status,len, 0);
while(len < E.screencols) {
if (E.screencols - len == rlen) {
abAppend(rstatus,rlen, 0);
break;
} else {
abAppend(" ",1, 0);
len++;
}
}
abAppend("\x1b[0m\r\n",6, 0);
/* Second row depends on E.statusmsg and the status message update time. */
abAppend("\x1b[0K",4, 0);
int msglen = strlen(E.statusmsg);
if (msglen && sysmillis() - E.statusmsg_time < 5000)
abAppend(E.statusmsg,msglen <= E.screencols ? msglen : E.screencols, 0);
/* Put cursor at its current position. Note that the horizontal position
* at which the cursor is displayed may be different compared to 'E.cx'
* because of TABs.
*/
int j;
int cx = 1;
int filerow = E.rowoff+E.cy;
erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
if (row)
{
for (j = E.coloff; j < (E.cx+E.coloff); j++)
{
if (j < row->size && row->chars[j] == TAB) cx += ((ED_TAB_SIZE)-((cx)%ED_TAB_SIZE));
cx++;
}
}
sprintf(buf,"\x1b[%d;%dH",E.cy+1,cx);
abAppend(buf,strlen(buf), 0);
/* Show cursor and Flush to complete.
*/
abAppend("\x1b[?25h",6, 1);
return(lastLine);
}
/* =============================== Find mode ================================ */
void editorFind(void) {
char query[ED_QUERY_LEN+1] = {0};
int qlen = 0;
int last_match = -1; /* Last line where a match was found. -1 for none. */
int find_next = 0; /* if 1 search next, if -1 search prev. */
/* Save the cursor position in order to restore it later. */
int saved_cx = E.cx, saved_cy = E.cy;
int saved_coloff = E.coloff, saved_rowoff = E.rowoff;
while(1)
{
sprintf(E.statusmsg, "Search: %s (Use ESC/Arrows/Enter)", query);
E.statusmsg_time = sysmillis();
editorRefreshScreen();
int c = editorReadKey();
if (c == DEL_KEY || c == CTRL_H || c == BACKSPACE) {
if (qlen != 0) query[--qlen] = '\0';
last_match = -1;
} else if (c == ESC || c == ENTER) {
if (c == ESC) {
E.cx = saved_cx; E.cy = saved_cy;
E.coloff = saved_coloff; E.rowoff = saved_rowoff;
}
E.statusmsg[0] = '\0';
E.statusmsg_time = sysmillis();
return;
} else if (c == ARROW_RIGHT || c == ARROW_DOWN) {
find_next = 1;
} else if (c == ARROW_LEFT || c == ARROW_UP) {
find_next = -1;
} else if (isprint(c)) {
if (qlen < ED_QUERY_LEN) {
query[qlen++] = c;
query[qlen] = '\0';
last_match = -1;
}
}
/* Search occurrence. */
if (last_match == -1) find_next = 1;
if (find_next) {
char *match = NULL;
int match_offset = 0;
int i, current = last_match;
for (i = 0; i < E.numrows; i++) {
current += find_next;
if (current == -1) current = E.numrows-1;
else if (current == E.numrows) current = 0;
match = strstr(E.row[current].chars,query);
if (match) {
match_offset = match-E.row[current].chars;
break;
}
}
find_next = 0;
if (match) {
last_match = current;
E.cy = 0;
E.cx = match_offset;
E.rowoff = current;
E.coloff = 0;
/* Scroll horizontally as needed. */
if (E.cx > E.screencols) {
int diff = E.cx - E.screencols;
E.cx -= diff;
E.coloff += diff;
}
}
}
}
}
/* ========================= Editor events handling ======================== */
/* Handle cursor position change because arrow keys were pressed. */
void editorMoveCursor(int key)
{
int filerow = E.rowoff+E.cy;
int filecol = E.coloff+E.cx;
int rowlen;
erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
switch(key)
{
case ARROW_LEFT:
if (E.cx == 0) {
if (E.coloff) {
E.coloff--;
} else {
if (filerow > 0) {
E.cy--;
E.cx = E.row[filerow-1].size;
if (E.cx > E.screencols-1) {
E.coloff = E.cx-E.screencols+1;
E.cx = E.screencols-1;
}
}
}
} else {
E.cx -= 1;
}
break;
case ARROW_RIGHT:
if (row && filecol < row->size) {
if (E.cx == E.screencols-1) {
E.coloff++;
} else {
E.cx += 1;
}
} else if (row && filecol == row->size) {
E.cx = 0;
E.coloff = 0;
if (E.cy == E.screenrows-1) {
E.rowoff++;
} else {
E.cy += 1;
}
}
break;
case ARROW_UP:
if (E.cy == 0) {
if (E.rowoff) E.rowoff--;
} else {
E.cy -= 1;
}
break;
case ARROW_DOWN:
if (filerow < E.numrows) {
if (E.cy == E.screenrows-1) {
E.rowoff++;
} else {
E.cy += 1;
}
}
break;
break;
E.cx = E.screencols-1;
E.coloff = 0;
break;
case HOME_KEY:
E.cx = 0;
E.coloff = 0;
break;
case END_KEY:
E.cx = row->size;
E.coloff = 0;
if (E.cx > E.screencols-1)
{
E.coloff = E.cx-E.screencols+1;
E.cx = E.screencols-1;
}
break;
}
/* Fix cx if the current line has not enough chars. */
filerow = E.rowoff+E.cy;
filecol = E.coloff+E.cx;
row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
rowlen = row ? row->size : 0;
if (filecol > rowlen)
{
E.cx -= filecol-rowlen;
if (E.cx < 0)
{
E.coloff += E.cx;
E.cx = 0;
}
}
}
/* Process events arriving from the standard input, which is, the user
* is typing stuff on the terminal.
*/
uint32_t editorProcessKeypress(void)
{
/* When the file is modified, requires Ctrl-q to be pressed N times
* before actually quitting.
*/
static int quit_times = ED_QUIT_TIMES;
int cxSave, cySave;
int lastLine;
int c;
c = editorReadKey();
switch(c)
{
case ENTER: /* Enter */
editorInsertNewline();
break;
case CTRL_C: /* Ctrl-c */
/* We ignore ctrl-c, it can't be so simple to lose the changes
* to the edited file.
*/
break;
case CTRL_Q: /* Ctrl-q */
/* Quit if the file was already saved.
*/
if (E.dirty > 0 && quit_times > 0)
{
sprintf(E.statusmsg, "WARNING!!! File has unsaved changes. Press Ctrl-Q %d more times to quit.", quit_times);
E.statusmsg_time = sysmillis();
quit_times--;
}
if(quit_times == 0 || E.dirty == 0)
{
cxSave = E.cx;
cySave = E.cy;
E.cy = E.screenrows-1;
lastLine = editorRefreshScreen();
printf("\x1b[%03d;%03dH", (lastLine == -1 ? E.screenrows-1 : lastLine+1), 1);
printf("\x1b[0J");
// Restore the cursor so it opens in the same place when edit is restarted.
E.cx = cxSave;
E.cy = cySave;
return(1);
} else
return(0);
case CTRL_S: /* Ctrl-s */
editorSave(NULL);
break;
case CTRL_F:
editorFind();
break;
case BACKSPACE: /* Backspace */
case CTRL_H: /* Ctrl-h */
editorDelChar();
break;
case DEL_KEY:
editorMoveCursor(ARROW_RIGHT);
editorDelChar();
break;
case PAGE_UP:
case PAGE_DOWN:
if (c == PAGE_UP && E.cy != 0)
E.cy = 0;
else if (c == PAGE_DOWN && E.cy != E.screenrows-1)
E.cy = E.screenrows-1;
{
int times = E.screenrows;
while(times--)
editorMoveCursor(c == PAGE_UP ? ARROW_UP: ARROW_DOWN);
}
break;
case HOME_KEY:
case END_KEY:
case ARROW_UP:
case ARROW_DOWN:
case ARROW_LEFT:
case ARROW_RIGHT:
editorMoveCursor(c);
break;
case CTRL_L: /* ctrl+l, clear screen */
/* Just refresh the line as side effect. */
break;
case ESC:
/* Nothing to do for ESC in this mode. */
break;
default:
editorInsertChar(c);
break;
}
quit_times = ED_QUIT_TIMES; /* Reset it to the original value. */
return(0);
}
int editorFileWasModified(void) {
return E.dirty;
}
uint32_t initEditor(void)
{
E.cx = 0;
E.cy = 0;
E.rowoff = 0;
E.coloff = 0;
E.numrows = 0;
E.row = NULL;
E.dirty = 0;
E.filename = NULL;
E.syntax = NULL;
if (getWindowSize(&E.screenrows,&E.screencols) == -1)
{
printf("Unable to query the screen for size (columns / rows)");
return(1);
}
E.screenrows -= 2; /* Get room for status bar. */
return(0);
}
// Main entry and start point of a zOS/ZPUTA Application. Only 2 parameters are catered for and a 32bit return code, additional parameters can be added by changing the appcrt0.s
// startup code to add them to the stack prior to app() call.
//
// Return code for the ZPU is saved in _memreg by the C compiler, this is transferred to _memreg in zOS/ZPUTA in appcrt0.s prior to return.
// The K64F ARM processor uses the standard register passing conventions, return code is stored in R0.
//
uint32_t app(uint32_t param1, uint32_t param2)
{
// Initialisation.
//
char *ptr = (char *)param1;
char *pathName;
uint32_t retCode = 1;
// Get name of file to load.
//
pathName = getStrParam(&ptr);
if(strlen(pathName) == 0)
{
printf("Usage: ed <file>\n");
} else
{
if((retCode = initEditor()) == 0)
{
if((retCode = editorOpen(pathName)) == 0)
{
sprintf(E.statusmsg, "HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find");
E.statusmsg_time = sysmillis();
while(retCode == 0)
{
editorRefreshScreen();
retCode = editorProcessKeypress();
}
// Clear screen on exit.
fputs("\x1b[2J", stdout);
} else
{
// Memory exhausted?
if(retCode == 1)
{
printf("Insufficient memory to process this file.\n");
} else
{
printf("Failed to create or open file:%s\n", pathName);
}
}
}
// Important to release all memory back to the heap otherwise future apps wont be able to allocate it.
//
editorCleanup();
}
return(retCode);
}
#ifdef __cplusplus
}
#endif