1707 lines
50 KiB
C
1707 lines
50 KiB
C
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Name: kilo.c
|
|
// Created: April 2020
|
|
// Author(s): Salvatore Sanfilippo
|
|
// Philip Smart
|
|
// Description: A very simple editor in less than 1-kilo lines of code (as counted
|
|
// by "cloc"). Does not depend on libcurses, directly emits VT100
|
|
// escapes on the terminal.
|
|
// 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:
|
|
// Copyright: Copyright (C) 2016 Salvatore Sanfilippo <antirez at gmail dot com>
|
|
// (c) 2019-2020 Philip Smart <philip.smart@net2net.org> zOS/ZPUTA enhancements, bug
|
|
// fixes, new features and adaptation to the zOS/ZPUTA framework.
|
|
//
|
|
// History: April 2020 - Ported v0.0.1 of Kilo and made quite a few changes for it to work
|
|
// in an embedded environment.
|
|
//
|
|
// 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 KILO_VERSION "1.0"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#if defined(__K64F__)
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.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 "kilo.h"
|
|
|
|
// Utility functions.
|
|
#include "tools.c"
|
|
|
|
// Version info.
|
|
#define VERSION "v1.01"
|
|
#define VERSION_DATE "10/04/2020"
|
|
#define APP_NAME "KILO"
|
|
|
|
/* Syntax highlight types */
|
|
#define HL_NORMAL 0
|
|
#define HL_NONPRINT 1
|
|
#define HL_COMMENT 2 /* Single line comment. */
|
|
#define HL_MLCOMMENT 3 /* Multi-line comment. */
|
|
#define HL_KEYWORD1 4
|
|
#define HL_KEYWORD2 5
|
|
#define HL_STRING 6
|
|
#define HL_NUMBER 7
|
|
#define HL_MATCH 8 /* Search match. */
|
|
|
|
#define HL_HIGHLIGHT_STRINGS (1<<0)
|
|
#define HL_HIGHLIGHT_NUMBERS (1<<1)
|
|
|
|
#define MAX_APPEND_BUFSIZE 1024
|
|
#define KILO_QUIT_TIMES 3
|
|
#define KILO_QUERY_LEN 256
|
|
#define KILO_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. */
|
|
int rsize; /* Size of the rendered row. */
|
|
char *chars; /* Row content. */
|
|
char *render; /* Row content "rendered" for screen (for TABs). */
|
|
unsigned char *hl; /* Syntax highlight type for each character in render.*/
|
|
int hl_oc; /* Row had open comment at end in last syntax highlight check. */
|
|
} erow;
|
|
|
|
typedef struct hlcolor {
|
|
int r,g,b;
|
|
} hlcolor;
|
|
|
|
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);
|
|
}
|
|
|
|
/* =========================== Syntax highlights DB =========================
|
|
*
|
|
* In order to add a new syntax, define two arrays with a list of file name
|
|
* matches and keywords. The file name matches are used in order to match
|
|
* a given syntax with a given file name: if a match pattern starts with a
|
|
* dot, it is matched as the last past of the filename, for example ".c".
|
|
* Otherwise the pattern is just searched inside the filenme, like "Makefile").
|
|
*
|
|
* The list of keywords to highlight is just a list of words, however if they
|
|
* a trailing '|' character is added at the end, they are highlighted in
|
|
* a different color, so that you can have two different sets of keywords.
|
|
*
|
|
* Finally add a stanza in the HLDB global variable with two two arrays
|
|
* of strings, and a set of flags in order to enable highlighting of
|
|
* comments and numbers.
|
|
*
|
|
* The characters for single and multi line comments must be exactly two
|
|
* and must be provided as well (see the C language example).
|
|
*
|
|
* There is no support to highlight patterns currently. */
|
|
|
|
/* C / C++ */
|
|
char *C_HL_extensions[] = {".c",".cpp",NULL};
|
|
char *C_HL_keywords[] = {
|
|
/* A few C / C++ keywords */
|
|
"switch","if","while","for","break","continue","return","else",
|
|
"struct","union","typedef","static","enum","class",
|
|
/* C types */
|
|
"int|","long|","double|","float|","char|","unsigned|","signed|",
|
|
"void|",NULL
|
|
};
|
|
|
|
/* Here we define an array of syntax highlights by extensions, keywords,
|
|
* comments delimiters and flags. */
|
|
struct editorSyntax HLDB[] = {
|
|
{
|
|
/* C / C++ */
|
|
C_HL_extensions,
|
|
C_HL_keywords,
|
|
"//","/*","*/",
|
|
HL_HIGHLIGHT_STRINGS | HL_HIGHLIGHT_NUMBERS
|
|
}
|
|
};
|
|
|
|
#define HLDB_ENTRIES (sizeof(HLDB)/sizeof(HLDB[0]))
|
|
|
|
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 = 25;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ====================== Syntax highlight color scheme ==================== */
|
|
|
|
int is_separator(int c)
|
|
{
|
|
return c == '\0' || isspace(c) || strchr(",.()+-/*=~%[];",c) != NULL;
|
|
}
|
|
|
|
/* Return true if the specified row last char is part of a multi line comment
|
|
* that starts at this row or at one before, and does not end at the end
|
|
* of the row but spawns to the next row. */
|
|
int editorRowHasOpenComment(erow *row)
|
|
{
|
|
if (row->hl && row->rsize && row->hl[row->rsize-1] == HL_MLCOMMENT &&
|
|
(row->rsize < 2 || (row->render[row->rsize-2] != '*' ||
|
|
row->render[row->rsize-1] != '/'))) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Set every byte of row->hl (that corresponds to every character in the line)
|
|
* to the right syntax highlight type (HL_* defines). */
|
|
int editorUpdateSyntax(erow *row)
|
|
{
|
|
if(row->rsize == 0) return 0;
|
|
|
|
row->hl = sys_realloc(row->hl,row->rsize);
|
|
if(row->hl == NULL) { printf("editorUpdateSyntax: Memory exhausted\n"); return 1; }
|
|
memset(row->hl,HL_NORMAL,row->rsize);
|
|
|
|
if (E.syntax == NULL) return 0; /* No syntax, everything is HL_NORMAL. */
|
|
|
|
int i, prev_sep, in_string, in_comment;
|
|
char *p;
|
|
char **keywords = E.syntax->keywords;
|
|
char *scs = E.syntax->singleline_comment_start;
|
|
char *mcs = E.syntax->multiline_comment_start;
|
|
char *mce = E.syntax->multiline_comment_end;
|
|
|
|
/* Point to the first non-space char. */
|
|
p = row->render;
|
|
i = 0; /* Current char offset */
|
|
while(*p && isspace((int)*p))
|
|
{
|
|
p++;
|
|
i++;
|
|
}
|
|
prev_sep = 1; /* Tell the parser if 'i' points to start of word. */
|
|
in_string = 0; /* Are we inside "" or '' ? */
|
|
in_comment = 0; /* Are we inside multi-line comment? */
|
|
|
|
/* If the previous line has an open comment, this line starts
|
|
* with an open comment state.
|
|
*/
|
|
if (row->idx > 0 && editorRowHasOpenComment(&E.row[row->idx-1]))
|
|
in_comment = 1;
|
|
|
|
while(*p)
|
|
{
|
|
/* Handle // comments. */
|
|
if (prev_sep && *p == scs[0] && *(p+1) == scs[1])
|
|
{
|
|
/* From here to end is a comment */
|
|
memset(row->hl+i,HL_COMMENT,row->size-i);
|
|
return 0;
|
|
}
|
|
|
|
/* Handle multi line comments. */
|
|
if (in_comment)
|
|
{
|
|
row->hl[i] = HL_MLCOMMENT;
|
|
if (*p == mce[0] && *(p+1) == mce[1])
|
|
{
|
|
row->hl[i+1] = HL_MLCOMMENT;
|
|
p += 2; i += 2;
|
|
in_comment = 0;
|
|
prev_sep = 1;
|
|
continue;
|
|
} else
|
|
{
|
|
prev_sep = 0;
|
|
p++; i++;
|
|
continue;
|
|
}
|
|
} else if (*p == mcs[0] && *(p+1) == mcs[1])
|
|
{
|
|
row->hl[i] = HL_MLCOMMENT;
|
|
row->hl[i+1] = HL_MLCOMMENT;
|
|
p += 2; i += 2;
|
|
in_comment = 1;
|
|
prev_sep = 0;
|
|
continue;
|
|
}
|
|
|
|
/* Handle "" and '' */
|
|
if (in_string)
|
|
{
|
|
row->hl[i] = HL_STRING;
|
|
if (*p == '\\')
|
|
{
|
|
row->hl[i+1] = HL_STRING;
|
|
p += 2; i += 2;
|
|
prev_sep = 0;
|
|
continue;
|
|
}
|
|
if (*p == in_string) in_string = 0;
|
|
p++; i++;
|
|
continue;
|
|
} else
|
|
{
|
|
if (*p == '"' || *p == '\'')
|
|
{
|
|
in_string = *p;
|
|
row->hl[i] = HL_STRING;
|
|
p++; i++;
|
|
prev_sep = 0;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Handle non printable chars. */
|
|
if (!isprint((int)*p))
|
|
{
|
|
row->hl[i] = HL_NONPRINT;
|
|
p++; i++;
|
|
prev_sep = 0;
|
|
continue;
|
|
}
|
|
|
|
/* Handle numbers */
|
|
if ((isdigit((int)*p) && (prev_sep || row->hl[i-1] == HL_NUMBER)) ||
|
|
(*p == '.' && i >0 && row->hl[i-1] == HL_NUMBER))
|
|
{
|
|
row->hl[i] = HL_NUMBER;
|
|
p++; i++;
|
|
prev_sep = 0;
|
|
continue;
|
|
}
|
|
|
|
/* Handle keywords and lib calls */
|
|
if (prev_sep)
|
|
{
|
|
int j;
|
|
for (j = 0; keywords[j]; j++) {
|
|
int klen = strlen(keywords[j]);
|
|
int kw2 = keywords[j][klen-1] == '|';
|
|
if (kw2) klen--;
|
|
|
|
if (!memcmp(p,keywords[j],klen) &&
|
|
is_separator(*(p+klen)))
|
|
{
|
|
/* Keyword */
|
|
memset(row->hl+i,kw2 ? HL_KEYWORD2 : HL_KEYWORD1,klen);
|
|
p += klen;
|
|
i += klen;
|
|
break;
|
|
}
|
|
}
|
|
if (keywords[j] != NULL) {
|
|
prev_sep = 0;
|
|
continue; /* We had a keyword match */
|
|
}
|
|
}
|
|
|
|
/* Not special chars */
|
|
prev_sep = is_separator(*p);
|
|
p++; i++;
|
|
}
|
|
|
|
/* Propagate syntax change to the next row if the open commen
|
|
* state changed. This may recursively affect all the following rows
|
|
* in the file.
|
|
*/
|
|
int oc = editorRowHasOpenComment(row);
|
|
if (row->hl_oc != oc && row->idx+1 < E.numrows)
|
|
{
|
|
if(editorUpdateSyntax(&E.row[row->idx+1])) { printf("Exit from here\n"); return 1; }
|
|
}
|
|
row->hl_oc = oc;
|
|
return 0;
|
|
}
|
|
|
|
/* Maps syntax highlight token types to terminal colors.
|
|
*/
|
|
int editorSyntaxToColor(int hl)
|
|
{
|
|
switch(hl)
|
|
{
|
|
case HL_COMMENT:
|
|
case HL_MLCOMMENT: return 36; /* cyan */
|
|
case HL_KEYWORD1: return 33; /* yellow */
|
|
case HL_KEYWORD2: return 32; /* green */
|
|
case HL_STRING: return 35; /* magenta */
|
|
case HL_NUMBER: return 31; /* red */
|
|
case HL_MATCH: return 34; /* blue */
|
|
default: return 37; /* white */
|
|
}
|
|
}
|
|
|
|
/* Select the syntax highlight scheme depending on the filename,
|
|
* setting it in the global state E.syntax. */
|
|
void editorSelectSyntaxHighlight(char *filename)
|
|
{
|
|
unsigned int j;
|
|
|
|
for (j = 0; j < HLDB_ENTRIES; j++)
|
|
{
|
|
struct editorSyntax *s = HLDB+j;
|
|
unsigned int i = 0;
|
|
while(s->filematch[i])
|
|
{
|
|
char *p;
|
|
int patlen = strlen(s->filematch[i]);
|
|
if ((p = strstr(filename,s->filematch[i])) != NULL)
|
|
{
|
|
if (s->filematch[i][0] != '.' || p[patlen] == '\0')
|
|
{
|
|
E.syntax = s;
|
|
return;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ======================= Editor rows implementation ======================= */
|
|
|
|
/* Update the rendered version and the syntax highlight of a row. */
|
|
int editorUpdateRow(erow *row)
|
|
{
|
|
int tabs = 0, j, idx;
|
|
|
|
/* Create a version of the row we can directly print on the screen,
|
|
* respecting tabs, substituting non printable characters with '?'.
|
|
*/
|
|
if(row->render != NULL)
|
|
{
|
|
sys_free(row->render);
|
|
row->render = NULL;
|
|
}
|
|
for (j = 0; j < row->size; j++)
|
|
if (row->chars[j] == TAB) tabs++;
|
|
|
|
row->render = sys_malloc(row->size + tabs*KILO_TAB_SIZE + 1);
|
|
if(row->render == NULL) { printf("editorUpdateRow: Memory exhausted\n"); return 1; }
|
|
idx = 0;
|
|
for (j = 0; j < row->size; j++)
|
|
{
|
|
if (row->chars[j] == TAB)
|
|
{
|
|
row->render[idx++] = ' ';
|
|
while((idx+1) % KILO_TAB_SIZE != 0) row->render[idx++] = ' ';
|
|
} else {
|
|
row->render[idx++] = row->chars[j];
|
|
}
|
|
}
|
|
row->rsize = idx;
|
|
row->render[idx] = '\0';
|
|
|
|
/* Update the syntax highlighting attributes of the row.
|
|
*/
|
|
if(editorUpdateSyntax(row)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* 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].hl = NULL;
|
|
E.row[at].hl_oc = 0;
|
|
E.row[at].render = NULL;
|
|
E.row[at].rsize = 0;
|
|
E.row[at].idx = at;
|
|
if(editorUpdateRow(E.row+at)) return 1;
|
|
E.numrows++;
|
|
E.dirty++;
|
|
return 0;
|
|
}
|
|
|
|
/* Free row's heap allocated stuff. */
|
|
void editorFreeRow(erow *row)
|
|
{
|
|
if(row->render != NULL)
|
|
{
|
|
sys_free(row->render);
|
|
row->render = NULL;
|
|
}
|
|
if(row->chars != NULL)
|
|
{
|
|
sys_free(row->chars);
|
|
row->chars = NULL;
|
|
}
|
|
if(row->hl != NULL)
|
|
{
|
|
sys_free(row->hl);
|
|
row->hl = 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);
|
|
}
|
|
if(E.row[idx].render != NULL)
|
|
{
|
|
sys_free(E.row[idx].render);
|
|
}
|
|
if(E.row[idx].hl != NULL)
|
|
{
|
|
sys_free(E.row[idx].hl);
|
|
}
|
|
}
|
|
|
|
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;
|
|
if(editorUpdateRow(row)) return 1;
|
|
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';
|
|
if(editorUpdateRow(row)) return 1;
|
|
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);
|
|
if(editorUpdateRow(row)) return 1;
|
|
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;
|
|
if(editorUpdateRow(row)) return 1;
|
|
}
|
|
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--;
|
|
}
|
|
if (row)
|
|
if(editorUpdateRow(row)) return 1;
|
|
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;
|
|
if(editorUpdateRow(E.row+rowNo)) return 1;
|
|
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) { sys_free(ab); 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, "Kilo editor -- version %s\x1b[0K\r\n", KILO_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->rsize - E.coloff;
|
|
int current_color = -1;
|
|
if (len > 0) {
|
|
if (len > E.screencols) len = E.screencols;
|
|
char *c = r->render+E.coloff;
|
|
unsigned char *hl = r->hl+E.coloff;
|
|
int j;
|
|
for (j = 0; j < len; j++)
|
|
{
|
|
if (hl[j] == HL_NONPRINT)
|
|
{
|
|
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 if (hl[j] == HL_NORMAL)
|
|
{
|
|
if (current_color != -1)
|
|
{
|
|
abAppend("\x1b[39m",5, 0);
|
|
current_color = -1;
|
|
}
|
|
abAppend(c+j,1, 0);
|
|
} else {
|
|
int color = editorSyntaxToColor(hl[j]);
|
|
if (color != current_color)
|
|
{
|
|
char buf[16];
|
|
sprintf(buf,"\x1b[%dm",color);
|
|
int clen = strlen(buf);
|
|
current_color = color;
|
|
abAppend(buf,clen, 0);
|
|
}
|
|
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 += (KILO_TAB_SIZE-1)-((cx)%KILO_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[KILO_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. */
|
|
int saved_hl_line = -1; /* No saved HL */
|
|
char *saved_hl = NULL;
|
|
|
|
#define FIND_RESTORE_HL do { \
|
|
if (saved_hl) { \
|
|
memcpy(E.row[saved_hl_line].hl,saved_hl, E.row[saved_hl_line].rsize); \
|
|
saved_hl = NULL; \
|
|
} \
|
|
} while (0)
|
|
|
|
/* 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;
|
|
}
|
|
FIND_RESTORE_HL;
|
|
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 < KILO_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].render,query);
|
|
if (match) {
|
|
match_offset = match-E.row[current].render;
|
|
break;
|
|
}
|
|
}
|
|
find_next = 0;
|
|
|
|
/* Highlight */
|
|
FIND_RESTORE_HL;
|
|
|
|
if (match) {
|
|
erow *row = &E.row[current];
|
|
last_match = current;
|
|
if (row->hl) {
|
|
saved_hl_line = current;
|
|
saved_hl = sys_malloc(row->rsize);
|
|
if(saved_hl == NULL) { printf("editorFind: Memory exhausted\n"); return; }
|
|
memcpy(saved_hl,row->hl,row->rsize);
|
|
memset(row->hl+match_offset,HL_MATCH,qlen);
|
|
}
|
|
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 = KILO_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 = KILO_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: kilo <file>\n");
|
|
} else
|
|
{
|
|
if((retCode = initEditor()) == 0)
|
|
{
|
|
editorSelectSyntaxHighlight(pathName);
|
|
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 otherwise system memory will not be available
|
|
// to the next app.
|
|
editorCleanup();
|
|
}
|
|
|
|
return(retCode);
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
/* Kilo -- A very simple editor in less than 1-kilo lines of code (as counted
|
|
* by "cloc"). Does not depend on libcurses, directly emits VT100
|
|
* escapes on the terminal.
|
|
*
|
|
* -----------------------------------------------------------------------
|
|
*
|
|
* Copyright (C) 2016 Salvatore Sanfilippo <antirez at gmail dot com>
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|