Experimental rline replacement with syntax highlighting
This commit is contained in:
parent
88ecd95dbc
commit
b7c642c273
11
apps/sh.c
11
apps/sh.c
@ -37,6 +37,7 @@
|
||||
#include <toaru/list.h>
|
||||
#include <toaru/kbd.h>
|
||||
#include <toaru/rline.h>
|
||||
#include <toaru/rline_exp.h>
|
||||
|
||||
#define PIPE_TOKEN "\xFF\xFFPIPE\xFF\xFF"
|
||||
#define STAR_TOKEN "\xFF\xFFSTAR\xFF\xFF"
|
||||
@ -59,6 +60,7 @@ int shell_interactive = 1;
|
||||
int last_ret = 0;
|
||||
char ** shell_argv = NULL;
|
||||
int shell_argc = 0;
|
||||
int experimental_rline = 0;
|
||||
|
||||
|
||||
int pid; /* Process ID of the shell */
|
||||
@ -514,7 +516,7 @@ int read_entry(char * buffer) {
|
||||
tab_complete_func, redraw_prompt_func, NULL,
|
||||
NULL, NULL, NULL, NULL, NULL
|
||||
};
|
||||
int buffer_size = rline((char *)buffer, LINE_LEN, &callbacks);
|
||||
int buffer_size = experimental_rline ? rline_experimental(buffer, LINE_LEN) : rline((char *)buffer, LINE_LEN, &callbacks);
|
||||
return buffer_size;
|
||||
}
|
||||
|
||||
@ -523,7 +525,7 @@ int read_entry_continued(char * buffer) {
|
||||
tab_complete_func, redraw_prompt_func_c, NULL,
|
||||
NULL, NULL, NULL, NULL, NULL
|
||||
};
|
||||
int buffer_size = rline((char *)buffer, LINE_LEN, &callbacks);
|
||||
int buffer_size = experimental_rline ? rline_experimental(buffer, LINE_LEN) : rline((char *)buffer, LINE_LEN, &callbacks);
|
||||
return buffer_size;
|
||||
}
|
||||
|
||||
@ -1147,8 +1149,11 @@ int main(int argc, char ** argv) {
|
||||
|
||||
if (argc > 1) {
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "c:v?")) != -1) {
|
||||
while ((c = getopt(argc, argv, "Rc:v?")) != -1) {
|
||||
switch (c) {
|
||||
case 'R':
|
||||
experimental_rline = 1;
|
||||
break;
|
||||
case 'c':
|
||||
shell_interactive = 0;
|
||||
{
|
||||
|
3
base/usr/include/toaru/rline_exp.h
Normal file
3
base/usr/include/toaru/rline_exp.h
Normal file
@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
extern int rline_experimental(char * buffer, int buf_size);
|
925
lib/rline_exp.c
Normal file
925
lib/rline_exp.c
Normal file
@ -0,0 +1,925 @@
|
||||
/* vim: tabstop=4 shiftwidth=4 noexpandtab
|
||||
* This file is part of ToaruOS and is released under the terms
|
||||
* of the NCSA / University of Illinois License - see LICENSE.md
|
||||
* Copyright (C) 2018 K. Lange
|
||||
*
|
||||
* EXPERIMENTAL rline replacement with syntax highlight, based on
|
||||
* bim's lightlighting and line editing.
|
||||
*
|
||||
* Still needs tab completion, history, etc. integration, and a
|
||||
* LOT of code cleanup because this is all basically just cut
|
||||
* and pasted directly out of bim.
|
||||
*
|
||||
* Some key bindings should also be added (some of which are missing
|
||||
* in bim as well) like ^W.
|
||||
*/
|
||||
#define _XOPEN_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <ctype.h>
|
||||
#include <termios.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
#include <unistd.h>
|
||||
#include <locale.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#define ENTER_KEY '\n'
|
||||
#define BACKSPACE_KEY 0x08
|
||||
#define DELETE_KEY 0x7F
|
||||
|
||||
typedef struct {
|
||||
uint32_t display_width:4;
|
||||
uint32_t flags:7;
|
||||
uint32_t codepoint:21;
|
||||
} __attribute__((packed)) char_t;
|
||||
|
||||
typedef struct {
|
||||
int available;
|
||||
int actual;
|
||||
int istate;
|
||||
char_t text[0];
|
||||
} line_t;
|
||||
|
||||
line_t * the_line = NULL;
|
||||
|
||||
static int column = 0;
|
||||
static int offset = 0;
|
||||
static int width = 0;
|
||||
|
||||
/**
|
||||
* TODO: Need to make prompt configurable.
|
||||
* Ideally, sh, etc. should generate
|
||||
* a prompt string from $PS1 and also
|
||||
* calculate it's width; we need \[\]
|
||||
* for that to work correctly.
|
||||
*/
|
||||
static int prompt_width = 2;
|
||||
static char * prompt = "> ";
|
||||
static int prompt_right_width = 4;
|
||||
static char * prompt_right = " :) ";
|
||||
|
||||
static int to_eight(uint32_t codepoint, char * out) {
|
||||
memset(out, 0x00, 7);
|
||||
|
||||
if (codepoint < 0x0080) {
|
||||
out[0] = (char)codepoint;
|
||||
} else if (codepoint < 0x0800) {
|
||||
out[0] = 0xC0 | (codepoint >> 6);
|
||||
out[1] = 0x80 | (codepoint & 0x3F);
|
||||
} else if (codepoint < 0x10000) {
|
||||
out[0] = 0xE0 | (codepoint >> 12);
|
||||
out[1] = 0x80 | ((codepoint >> 6) & 0x3F);
|
||||
out[2] = 0x80 | (codepoint & 0x3F);
|
||||
} else if (codepoint < 0x200000) {
|
||||
out[0] = 0xF0 | (codepoint >> 18);
|
||||
out[1] = 0x80 | ((codepoint >> 12) & 0x3F);
|
||||
out[2] = 0x80 | ((codepoint >> 6) & 0x3F);
|
||||
out[3] = 0x80 | ((codepoint) & 0x3F);
|
||||
} else if (codepoint < 0x4000000) {
|
||||
out[0] = 0xF8 | (codepoint >> 24);
|
||||
out[1] = 0x80 | (codepoint >> 18);
|
||||
out[2] = 0x80 | ((codepoint >> 12) & 0x3F);
|
||||
out[3] = 0x80 | ((codepoint >> 6) & 0x3F);
|
||||
out[4] = 0x80 | ((codepoint) & 0x3F);
|
||||
} else {
|
||||
out[0] = 0xF8 | (codepoint >> 30);
|
||||
out[1] = 0x80 | ((codepoint >> 24) & 0x3F);
|
||||
out[2] = 0x80 | ((codepoint >> 18) & 0x3F);
|
||||
out[3] = 0x80 | ((codepoint >> 12) & 0x3F);
|
||||
out[4] = 0x80 | ((codepoint >> 6) & 0x3F);
|
||||
out[5] = 0x80 | ((codepoint) & 0x3F);
|
||||
}
|
||||
|
||||
return strlen(out);
|
||||
}
|
||||
|
||||
#define UTF8_ACCEPT 0
|
||||
#define UTF8_REJECT 1
|
||||
|
||||
static inline uint32_t decode(uint32_t* state, uint32_t* codep, uint32_t byte) {
|
||||
static int state_table[32] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xxxxxxx */
|
||||
1,1,1,1,1,1,1,1, /* 10xxxxxx */
|
||||
2,2,2,2, /* 110xxxxx */
|
||||
3,3, /* 1110xxxx */
|
||||
4, /* 11110xxx */
|
||||
1 /* 11111xxx */
|
||||
};
|
||||
|
||||
static int mask_bytes[32] = {
|
||||
0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
|
||||
0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x1F,0x1F,0x1F,0x1F,
|
||||
0x0F,0x0F,
|
||||
0x07,
|
||||
0x00
|
||||
};
|
||||
|
||||
static int next[5] = {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
3
|
||||
};
|
||||
|
||||
if (*state == UTF8_ACCEPT) {
|
||||
*codep = byte & mask_bytes[byte >> 3];
|
||||
*state = state_table[byte >> 3];
|
||||
} else if (*state > 0) {
|
||||
*codep = (byte & 0x3F) | (*codep << 6);
|
||||
*state = next[*state];
|
||||
}
|
||||
return *state;
|
||||
}
|
||||
|
||||
static int codepoint_width(wchar_t codepoint) {
|
||||
if (codepoint == '\t') {
|
||||
return 1; /* Recalculate later */
|
||||
}
|
||||
if (codepoint < 32) {
|
||||
/* We render these as ^@ */
|
||||
return 2;
|
||||
}
|
||||
if (codepoint == 0x7F) {
|
||||
/* Renders as ^? */
|
||||
return 2;
|
||||
}
|
||||
if (codepoint > 0x7f && codepoint < 0xa0) {
|
||||
/* Upper control bytes <xx> */
|
||||
return 4;
|
||||
}
|
||||
if (codepoint == 0xa0) {
|
||||
/* Non-breaking space _ */
|
||||
return 1;
|
||||
}
|
||||
/* Skip wcwidth for anything under 256 */
|
||||
if (codepoint > 256) {
|
||||
/* Higher codepoints may be wider (eg. Japanese) */
|
||||
int out = wcwidth(codepoint);
|
||||
if (out >= 1) return out;
|
||||
/* Invalid character, render as [U+ABCD] or [U+ABCDEF] */
|
||||
return (codepoint < 0x10000) ? 8 : 10;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const char * COLOR_FG = "@9";
|
||||
static const char * COLOR_BG = "@9";
|
||||
static const char * COLOR_ALT_FG = "@5";
|
||||
static const char * COLOR_ALT_BG = "@9";
|
||||
static const char * COLOR_NUMBER_FG = "@3";
|
||||
static const char * COLOR_NUMBER_BG = "@9";
|
||||
static const char * COLOR_STATUS_FG = "@7";
|
||||
static const char * COLOR_STATUS_BG = "@4";
|
||||
static const char * COLOR_TABBAR_BG = "@4";
|
||||
static const char * COLOR_TAB_BG = "@4";
|
||||
static const char * COLOR_KEYWORD = "@4";
|
||||
static const char * COLOR_STRING = "@2";
|
||||
static const char * COLOR_COMMENT = "@5";
|
||||
static const char * COLOR_TYPE = "@3";
|
||||
static const char * COLOR_PRAGMA = "@1";
|
||||
static const char * COLOR_NUMERAL = "@1";
|
||||
static const char * COLOR_ERROR_FG = "@7";
|
||||
static const char * COLOR_ERROR_BG = "@1";
|
||||
static const char * COLOR_SEARCH_FG = "@0";
|
||||
static const char * COLOR_SEARCH_BG = "@3";
|
||||
static const char * COLOR_SELECTBG = "@7";
|
||||
static const char * COLOR_SELECTFG = "@0";
|
||||
static const char * COLOR_RED = "@1";
|
||||
static const char * COLOR_GREEN = "@2";
|
||||
|
||||
static void load_colorscheme_sunsmoke(void) {
|
||||
COLOR_FG = "2;230;230;230";
|
||||
COLOR_BG = "2;31;31;31";
|
||||
COLOR_ALT_FG = "2;122;122;122";
|
||||
COLOR_ALT_BG = "2;46;43;46";
|
||||
COLOR_NUMBER_FG = "2;150;139;57";
|
||||
COLOR_NUMBER_BG = "2;0;0;0";
|
||||
COLOR_STATUS_FG = "2;230;230;230";
|
||||
COLOR_STATUS_BG = "2;71;64;58";
|
||||
COLOR_TABBAR_BG = "2;71;64;58";
|
||||
COLOR_TAB_BG = "2;71;64;58";
|
||||
COLOR_KEYWORD = "2;51;162;230";
|
||||
COLOR_STRING = "2;72;176;72";
|
||||
COLOR_COMMENT = "2;158;153;129;3";
|
||||
COLOR_TYPE = "2;230;206;110";
|
||||
COLOR_PRAGMA = "2;194;70;54";
|
||||
COLOR_NUMERAL = "2;230;43;127";
|
||||
|
||||
COLOR_ERROR_FG = "5;15";
|
||||
COLOR_ERROR_BG = "5;196";
|
||||
COLOR_SEARCH_FG = "5;234";
|
||||
COLOR_SEARCH_BG = "5;226";
|
||||
|
||||
COLOR_SELECTFG = "2;0;43;54";
|
||||
COLOR_SELECTBG = "2;147;161;161";
|
||||
|
||||
COLOR_RED = "2;222;53;53";
|
||||
COLOR_GREEN = "2;55;167;0";
|
||||
}
|
||||
/**
|
||||
* Syntax highlighting flags.
|
||||
*/
|
||||
#define FLAG_NONE 0
|
||||
#define FLAG_KEYWORD 1
|
||||
#define FLAG_STRING 2
|
||||
#define FLAG_COMMENT 3
|
||||
#define FLAG_TYPE 4
|
||||
#define FLAG_PRAGMA 5
|
||||
#define FLAG_NUMERAL 6
|
||||
#define FLAG_SELECT 7
|
||||
#define FLAG_STRING2 8
|
||||
#define FLAG_DIFFPLUS 9
|
||||
#define FLAG_DIFFMINUS 10
|
||||
|
||||
#define FLAG_CONTINUES (1 << 6)
|
||||
|
||||
/**
|
||||
* Syntax definition for ToaruOS shell
|
||||
*/
|
||||
static char * syn_sh_keywords[] = {
|
||||
"cd","exit","export","help","history","if",
|
||||
"empty?","equals?","return","export-cmd",
|
||||
"source","exec","not","while","then","else",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int syn_sh_extended(line_t * line, int i, int c, int last, int * out_left) {
|
||||
(void)last;
|
||||
|
||||
if (c == '#') {
|
||||
*out_left = (line->actual + 1) - i;
|
||||
return FLAG_COMMENT;
|
||||
}
|
||||
|
||||
if (line->text[i].codepoint == '\'') {
|
||||
int last = 0;
|
||||
for (int j = i+1; j < line->actual + 1; ++j) {
|
||||
int c = line->text[j].codepoint;
|
||||
if (last != '\\' && c == '\'') {
|
||||
*out_left = j - i;
|
||||
return FLAG_STRING;
|
||||
}
|
||||
if (last == '\\' && c == '\\') {
|
||||
last = 0;
|
||||
}
|
||||
last = c;
|
||||
}
|
||||
*out_left = (line->actual + 1) - i; /* unterminated string */
|
||||
return FLAG_STRING;
|
||||
}
|
||||
|
||||
if (line->text[i].codepoint == '"') {
|
||||
int last = 0;
|
||||
for (int j = i+1; j < line->actual + 1; ++j) {
|
||||
int c = line->text[j].codepoint;
|
||||
if (last != '\\' && c == '"') {
|
||||
*out_left = j - i;
|
||||
return FLAG_STRING;
|
||||
}
|
||||
if (last == '\\' && c == '\\') {
|
||||
last = 0;
|
||||
}
|
||||
last = c;
|
||||
}
|
||||
*out_left = (line->actual + 1) - i; /* unterminated string */
|
||||
return FLAG_STRING;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int syn_sh_iskeywordchar(int c) {
|
||||
if (isalnum(c)) return 1;
|
||||
if (c == '-') return 1;
|
||||
if (c == '_') return 1;
|
||||
if (c == '?') return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert syntax hilighting flag to color code
|
||||
*/
|
||||
static const char * flag_to_color(int _flag) {
|
||||
int flag = _flag & 0x3F;
|
||||
switch (flag) {
|
||||
case FLAG_KEYWORD:
|
||||
return COLOR_KEYWORD;
|
||||
case FLAG_STRING:
|
||||
case FLAG_STRING2: /* allows python to differentiate " and ' */
|
||||
return COLOR_STRING;
|
||||
case FLAG_COMMENT:
|
||||
return COLOR_COMMENT;
|
||||
case FLAG_TYPE:
|
||||
return COLOR_TYPE;
|
||||
case FLAG_NUMERAL:
|
||||
return COLOR_NUMERAL;
|
||||
case FLAG_PRAGMA:
|
||||
return COLOR_PRAGMA;
|
||||
case FLAG_DIFFPLUS:
|
||||
return COLOR_GREEN;
|
||||
case FLAG_DIFFMINUS:
|
||||
return COLOR_RED;
|
||||
case FLAG_SELECT:
|
||||
return COLOR_FG;
|
||||
default:
|
||||
return COLOR_FG;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void set_colors(const char * fg, const char * bg) {
|
||||
printf("\033[22;23;");
|
||||
if (*bg == '@') {
|
||||
int _bg = atoi(bg+1);
|
||||
if (_bg < 10) {
|
||||
printf("4%d;", _bg);
|
||||
} else {
|
||||
printf("10%d;", _bg-10);
|
||||
}
|
||||
} else {
|
||||
printf("48;%s;", bg);
|
||||
}
|
||||
if (*fg == '@') {
|
||||
int _fg = atoi(fg+1);
|
||||
if (_fg < 10) {
|
||||
printf("3%dm", _fg);
|
||||
} else {
|
||||
printf("9%dm", _fg-10);
|
||||
}
|
||||
} else {
|
||||
printf("38;%sm", fg);
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set just the foreground color
|
||||
*
|
||||
* (See set_colors above)
|
||||
*/
|
||||
static void set_fg_color(const char * fg) {
|
||||
printf("\033[22;23;");
|
||||
if (*fg == '@') {
|
||||
int _fg = atoi(fg+1);
|
||||
if (_fg < 10) {
|
||||
printf("3%dm", _fg);
|
||||
} else {
|
||||
printf("9%dm", _fg-10);
|
||||
}
|
||||
} else {
|
||||
printf("38;%sm", fg);
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void draw_prompt(void) {
|
||||
printf("\033[0m\r%s", prompt);
|
||||
}
|
||||
|
||||
static void render_line(void) {
|
||||
printf("\033[?25l");
|
||||
draw_prompt();
|
||||
|
||||
int i = 0; /* Offset in char_t line data entries */
|
||||
int j = 0; /* Offset in terminal cells */
|
||||
|
||||
const char * last_color = NULL;
|
||||
|
||||
/* Set default text colors */
|
||||
set_colors(COLOR_FG, COLOR_BG);
|
||||
|
||||
/*
|
||||
* When we are rendering in the middle of a wide character,
|
||||
* we render -'s to fill the remaining amount of the
|
||||
* charater's width
|
||||
*/
|
||||
int remainder = 0;
|
||||
|
||||
line_t * line = the_line;
|
||||
|
||||
/* For each character in the line ... */
|
||||
while (i < line->actual) {
|
||||
|
||||
/* If there is remaining text... */
|
||||
if (remainder) {
|
||||
|
||||
/* If we should be drawing by now... */
|
||||
if (j >= offset) {
|
||||
/* Fill remainder with -'s */
|
||||
set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
|
||||
printf("-");
|
||||
set_colors(COLOR_FG, COLOR_BG);
|
||||
}
|
||||
|
||||
/* One less remaining width cell to fill */
|
||||
remainder--;
|
||||
|
||||
/* Terminal offset moves forward */
|
||||
j++;
|
||||
|
||||
/*
|
||||
* If this was the last remaining character, move to
|
||||
* the next codepoint in the line
|
||||
*/
|
||||
if (remainder == 0) {
|
||||
i++;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Get the next character to draw */
|
||||
char_t c = line->text[i];
|
||||
|
||||
/* If we should be drawing by now... */
|
||||
if (j >= offset) {
|
||||
|
||||
/* If this character is going to fall off the edge of the screen... */
|
||||
if (j - offset + c.display_width >= width - prompt_width) {
|
||||
/* We draw this with special colors so it isn't ambiguous */
|
||||
set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
|
||||
|
||||
/* If it's wide, draw ---> as needed */
|
||||
while (j - offset < width - prompt_width - 1) {
|
||||
printf("-");
|
||||
j++;
|
||||
}
|
||||
|
||||
/* End the line with a > to show it overflows */
|
||||
printf(">");
|
||||
set_colors(COLOR_FG, COLOR_BG);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Syntax hilighting */
|
||||
const char * color = flag_to_color(c.flags);
|
||||
if (!last_color || strcmp(color, last_color)) {
|
||||
set_fg_color(color);
|
||||
last_color = color;
|
||||
}
|
||||
|
||||
/* Render special characters */
|
||||
if (c.codepoint == '\t') {
|
||||
set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
|
||||
printf("»");
|
||||
for (int i = 1; i < c.display_width; ++i) {
|
||||
printf("·");
|
||||
}
|
||||
set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
|
||||
} else if (c.codepoint < 32) {
|
||||
/* Codepoints under 32 to get converted to ^@ escapes */
|
||||
set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
|
||||
printf("^%c", '@' + c.codepoint);
|
||||
set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
|
||||
} else if (c.codepoint == 0x7f) {
|
||||
set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
|
||||
printf("^?");
|
||||
set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
|
||||
} else if (c.codepoint > 0x7f && c.codepoint < 0xa0) {
|
||||
set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
|
||||
printf("<%2x>", c.codepoint);
|
||||
set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
|
||||
} else if (c.codepoint == 0xa0) {
|
||||
set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
|
||||
printf("_");
|
||||
set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
|
||||
} else if (c.display_width == 8) {
|
||||
set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
|
||||
printf("[U+%04x]", c.codepoint);
|
||||
set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
|
||||
} else if (c.display_width == 10) {
|
||||
set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
|
||||
printf("[U+%06x]", c.codepoint);
|
||||
set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
|
||||
} else if (c.codepoint == ' ' && i == line->actual - 1) {
|
||||
/* Special case: space at end of line */
|
||||
set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
|
||||
printf("·");
|
||||
set_colors(COLOR_FG, COLOR_BG);
|
||||
} else {
|
||||
/* Normal characters get output */
|
||||
char tmp[7]; /* Max six bytes, use 7 to ensure last is always nil */
|
||||
to_eight(c.codepoint, tmp);
|
||||
printf("%s", tmp);
|
||||
}
|
||||
|
||||
/* Advance the terminal cell offset by the render width of this character */
|
||||
j += c.display_width;
|
||||
|
||||
/* Advance to the next character */
|
||||
i++;
|
||||
} else if (c.display_width > 1) {
|
||||
/*
|
||||
* If this is a wide character but we aren't ready to render yet,
|
||||
* we may need to draw some filler text for the remainder of its
|
||||
* width to ensure we don't jump around when horizontally scrolling
|
||||
* past wide characters.
|
||||
*/
|
||||
remainder = c.display_width - 1;
|
||||
j++;
|
||||
} else {
|
||||
/* Regular character, not ready to draw, advance without doing anything */
|
||||
j++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
for (; j < width + offset - prompt_width; ++j) {
|
||||
printf(" ");
|
||||
}
|
||||
printf("\033[0m%s", prompt_right);
|
||||
}
|
||||
|
||||
static int check_line(line_t * line, int c, char * str, int last) {
|
||||
if (syn_sh_iskeywordchar(last)) return 0;
|
||||
for (int i = c; i < line->actual; ++i, ++str) {
|
||||
if (*str == '\0' && !syn_sh_iskeywordchar(line->text[i].codepoint)) return 1;
|
||||
if (line->text[i].codepoint == *str) continue;
|
||||
return 0;
|
||||
}
|
||||
if (*str == '\0') return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void recalculate_syntax(line_t * line) {
|
||||
|
||||
/* Start from the line's stored in initial state */
|
||||
int state = line->istate;
|
||||
int left = 0;
|
||||
int last = 0;
|
||||
|
||||
for (int i = 0; i < line->actual; last = line->text[i++].codepoint) {
|
||||
if (!left) state = 0;
|
||||
|
||||
if (state) {
|
||||
/* Currently hilighting, have `left` characters remaining with this state */
|
||||
left--;
|
||||
line->text[i].flags = state;
|
||||
|
||||
if (!left) {
|
||||
/* Done hilighting this state, go back to parsing on next character */
|
||||
state = 0;
|
||||
}
|
||||
|
||||
/* If we are hilighting something, don't parse */
|
||||
continue;
|
||||
}
|
||||
|
||||
int c = line->text[i].codepoint;
|
||||
line->text[i].flags = FLAG_NONE;
|
||||
|
||||
/* Language-specific syntax hilighting */
|
||||
int s = syn_sh_extended(line,i,c,last,&left);
|
||||
if (s) {
|
||||
state = s;
|
||||
goto _continue;
|
||||
}
|
||||
|
||||
/* Keywords */
|
||||
for (char ** kw = syn_sh_keywords; *kw; kw++) {
|
||||
int c = check_line(line, i, *kw, last);
|
||||
if (c == 1) {
|
||||
left = strlen(*kw)-1;
|
||||
state = FLAG_KEYWORD;
|
||||
goto _continue;
|
||||
}
|
||||
}
|
||||
|
||||
_continue:
|
||||
line->text[i].flags = state;
|
||||
}
|
||||
|
||||
state = 0;
|
||||
}
|
||||
|
||||
static line_t * line_create(void) {
|
||||
line_t * line = malloc(sizeof(line_t) + sizeof(char_t) * 32);
|
||||
line->available = 32;
|
||||
line->actual = 0;
|
||||
line->istate = 0;
|
||||
return line;
|
||||
}
|
||||
|
||||
static line_t * line_insert(line_t * line, char_t c, int offset) {
|
||||
|
||||
/* If there is not enough space... */
|
||||
if (line->actual == line->available) {
|
||||
/* Expand the line buffer */
|
||||
line->available *= 2;
|
||||
line = realloc(line, sizeof(line_t) + sizeof(char_t) * line->available);
|
||||
}
|
||||
|
||||
/* If this was not the last character, then shift remaining characters forward. */
|
||||
if (offset < line->actual) {
|
||||
memmove(&line->text[offset+1], &line->text[offset], sizeof(char_t) * (line->actual - offset));
|
||||
}
|
||||
|
||||
/* Insert the new character */
|
||||
line->text[offset] = c;
|
||||
|
||||
/* There is one new character in the line */
|
||||
line->actual += 1;
|
||||
|
||||
recalculate_syntax(line);
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
static void place_cursor_actual(void) {
|
||||
int x = prompt_width + 1 - offset;
|
||||
for (int i = 0; i < column; ++i) {
|
||||
char_t * c = &the_line->text[i];
|
||||
x += c->display_width;
|
||||
}
|
||||
|
||||
if (x > width - 1) {
|
||||
/* Adjust the offset appropriately to scroll horizontally */
|
||||
int diff = x - (width - 1);
|
||||
offset += diff;
|
||||
x -= diff;
|
||||
render_line();
|
||||
}
|
||||
|
||||
/* Same for scrolling horizontally to the left */
|
||||
if (x < prompt_width + 1) {
|
||||
int diff = (prompt_width + 1) - x;
|
||||
offset -= diff;
|
||||
x += diff;
|
||||
render_line();
|
||||
}
|
||||
|
||||
printf("\033[?25h\033[%dG", x);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void line_delete(line_t * line, int offset) {
|
||||
|
||||
/* Can't delete character before start of line. */
|
||||
if (offset == 0) return;
|
||||
|
||||
/* If this isn't the last character, we need to move all subsequent characters backwards */
|
||||
if (offset < line->actual) {
|
||||
memmove(&line->text[offset-1], &line->text[offset], sizeof(char_t) * (line->actual - offset));
|
||||
}
|
||||
|
||||
/* The line is one character shorter */
|
||||
line->actual -= 1;
|
||||
|
||||
recalculate_syntax(line);
|
||||
}
|
||||
|
||||
static void delete_at_cursor(void) {
|
||||
if (column > 0) {
|
||||
line_delete(the_line, column);
|
||||
column--;
|
||||
if (offset > 0) offset--;
|
||||
render_line();
|
||||
place_cursor_actual();
|
||||
}
|
||||
}
|
||||
|
||||
static void insert_char(uint32_t c) {
|
||||
char_t _c;
|
||||
_c.codepoint = c;
|
||||
_c.flags = 0;
|
||||
_c.display_width = codepoint_width(c);
|
||||
|
||||
the_line = line_insert(the_line, _c, column);
|
||||
|
||||
column++;
|
||||
render_line();
|
||||
place_cursor_actual();
|
||||
}
|
||||
|
||||
static void cursor_left(void) {
|
||||
if (column > 0) column--;
|
||||
place_cursor_actual();
|
||||
}
|
||||
|
||||
static void cursor_right(void) {
|
||||
if (column < the_line->actual) column++;
|
||||
place_cursor_actual();
|
||||
}
|
||||
|
||||
static void word_left(void) {
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
static void word_right(void) {
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
static void cursor_home(void) {
|
||||
column = 0;
|
||||
place_cursor_actual();
|
||||
}
|
||||
|
||||
static void cursor_end(void) {
|
||||
column = the_line->actual;
|
||||
place_cursor_actual();
|
||||
}
|
||||
|
||||
static int handle_escape(int * this_buf, int * timeout, int c) {
|
||||
if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c == '\033') {
|
||||
this_buf[*timeout] = c;
|
||||
(*timeout)++;
|
||||
return 1;
|
||||
}
|
||||
if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c != '[') {
|
||||
*timeout = 0;
|
||||
ungetc(c, stdin);
|
||||
return 1;
|
||||
}
|
||||
if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c == '[') {
|
||||
*timeout = 1;
|
||||
this_buf[*timeout] = c;
|
||||
(*timeout)++;
|
||||
return 0;
|
||||
}
|
||||
if (*timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[' &&
|
||||
(isdigit(c) || c == ';')) {
|
||||
this_buf[*timeout] = c;
|
||||
(*timeout)++;
|
||||
return 0;
|
||||
}
|
||||
if (*timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[') {
|
||||
switch (c) {
|
||||
#if 0
|
||||
case 'A': // up
|
||||
cursor_up();
|
||||
break;
|
||||
case 'B': // down
|
||||
cursor_down();
|
||||
break;
|
||||
#endif
|
||||
case 'C': // right
|
||||
if (this_buf[*timeout-1] == '5') {
|
||||
word_right();
|
||||
} else {
|
||||
cursor_right();
|
||||
}
|
||||
break;
|
||||
case 'D': // left
|
||||
if (this_buf[*timeout-1] == '5') {
|
||||
word_left();
|
||||
} else {
|
||||
cursor_left();
|
||||
}
|
||||
break;
|
||||
case 'H': // home
|
||||
cursor_home();
|
||||
break;
|
||||
case 'F': // end
|
||||
cursor_end();
|
||||
break;
|
||||
case '~':
|
||||
switch (this_buf[*timeout-1]) {
|
||||
case '1':
|
||||
cursor_home();
|
||||
break;
|
||||
case '3':
|
||||
#if 0
|
||||
if (env->mode == MODE_INSERT || env->mode == MODE_REPLACE) {
|
||||
if (env->col_no < env->lines[env->line_no - 1]->actual + 1) {
|
||||
line_delete(env->lines[env->line_no - 1], env->col_no, env->line_no - 1);
|
||||
redraw_line(env->line_no - env->offset - 1, env->line_no-1);
|
||||
set_modified();
|
||||
redraw_statusbar();
|
||||
place_cursor_actual();
|
||||
} else if (env->line_no < env->line_count) {
|
||||
merge_lines(env->lines, env->line_no);
|
||||
redraw_text();
|
||||
set_modified();
|
||||
redraw_statusbar();
|
||||
place_cursor_actual();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* Delete forward */
|
||||
break;
|
||||
case '4':
|
||||
cursor_end();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
*timeout = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*timeout = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct termios old;
|
||||
static void get_initial_termios(void) {
|
||||
tcgetattr(STDOUT_FILENO, &old);
|
||||
}
|
||||
|
||||
static void set_unbuffered(void) {
|
||||
struct termios new = old;
|
||||
new.c_lflag &= (~ICANON & ~ECHO);
|
||||
tcsetattr(STDOUT_FILENO, TCSAFLUSH, &new);
|
||||
}
|
||||
|
||||
static void set_buffered(void) {
|
||||
tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old);
|
||||
}
|
||||
|
||||
static int read_line(void) {
|
||||
int cin;
|
||||
uint32_t c;
|
||||
int timeout = 0;
|
||||
int this_buf[20];
|
||||
uint32_t istate = 0;
|
||||
|
||||
render_line();
|
||||
place_cursor_actual();
|
||||
|
||||
while ((cin = getc(stdin))) {
|
||||
if (!decode(&istate, &c, cin)) {
|
||||
if (timeout == 0) {
|
||||
switch (c) {
|
||||
case '\033':
|
||||
if (timeout == 0) {
|
||||
this_buf[timeout] = c;
|
||||
timeout++;
|
||||
}
|
||||
break;
|
||||
case DELETE_KEY:
|
||||
case BACKSPACE_KEY:
|
||||
delete_at_cursor();
|
||||
break;
|
||||
case ENTER_KEY:
|
||||
/* Print buffer */
|
||||
return 1;
|
||||
break;
|
||||
case 12: /* ^L - Repaint the whole screen */
|
||||
printf("\033[2J\033[H");
|
||||
render_line();
|
||||
place_cursor_actual();
|
||||
break;
|
||||
case '\t':
|
||||
/* Tab complet e*/
|
||||
break;
|
||||
default:
|
||||
insert_char(c);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (handle_escape(this_buf,&timeout,c)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if (istate == UTF8_REJECT) {
|
||||
istate = 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rline_experimental(char * buffer, int buf_size) {
|
||||
get_initial_termios();
|
||||
set_unbuffered();
|
||||
|
||||
struct winsize w;
|
||||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
||||
width = w.ws_col - prompt_right_width;
|
||||
|
||||
column = 0;
|
||||
offset = 0;
|
||||
|
||||
load_colorscheme_sunsmoke();
|
||||
|
||||
the_line = line_create();
|
||||
read_line();
|
||||
printf("\033[0m\n");
|
||||
|
||||
unsigned int off = 0;
|
||||
for (int j = 0; j < the_line->actual; j++) {
|
||||
char_t c = the_line->text[j];
|
||||
off += to_eight(c.codepoint, &buffer[off]);
|
||||
}
|
||||
|
||||
free(the_line);
|
||||
|
||||
set_buffered();
|
||||
|
||||
return strlen(buffer);
|
||||
}
|
||||
|
||||
#if 0
|
||||
int main(int argc, char * argv[]) {
|
||||
char buf[1024];
|
||||
int r = rline_experimental(buf, 1024);
|
||||
fwrite(buf, 1, r, stdout);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
@ -25,6 +25,7 @@ class Classifier(object):
|
||||
'<toaru/graphics.h>': (None, '-ltoaru_graphics', []),
|
||||
'<toaru/drawstring.h>': (None, '-ltoaru_drawstring', ['<toaru/graphics.h>']),
|
||||
'<toaru/rline.h>': (None, '-ltoaru_rline', ['<toaru/kbd.h>']),
|
||||
'<toaru/rline_exp.h>': (None, '-ltoaru_rline_exp', ['<toaru/kbd.h>']),
|
||||
'<toaru/confreader.h>': (None, '-ltoaru_confreader', ['<toaru/hashmap.h>']),
|
||||
'<toaru/yutani.h>': (None, '-ltoaru_yutani', ['<toaru/kbd.h>', '<toaru/list.h>', '<toaru/pex.h>', '<toaru/graphics.h>', '<toaru/hashmap.h>']),
|
||||
'<toaru/decorations.h>': (None, '-ltoaru_decorations', ['<toaru/menu.h>', '<toaru/sdf.h>', '<toaru/graphics.h>', '<toaru/yutani.h>']),
|
||||
|
Loading…
Reference in New Issue
Block a user