ledit

Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit 1b405e16faddabd36b26297c27c746aa7788a2d4
parent aadae71b088e1d224e1f6615524d0cc0d51eae7a
Author: lumidify <nobody@lumidify.org>
Date:   Thu, 26 May 2022 21:53:19 +0200

Add support for config file

Yay, it's another monster commit!

Diffstat:
MIDEAS | 5+++++
MLICENSE | 4+++-
MMakefile | 33+++++++++++++++++++++------------
MREADME | 26++++++++++++++++++++++----
Mbuffer.c | 23++++++++++++++---------
Mbuffer.h | 3+--
Mconfig.h | 9+++++++++
Aconfigparser.c | 1810+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfigparser.h | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mkeys.c | 14--------------
Mkeys.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Mkeys_basic.c | 282+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mkeys_basic.h | 5+++++
Dkeys_basic_config.h | 457-------------------------------------------------------------------------------
Mkeys_command.c | 362+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mkeys_command.h | 8++++++++
Dkeys_command_config.h | 196-------------------------------------------------------------------------------
Mkeys_config.h | 203+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mledit.1 | 96+++++++++++++++----------------------------------------------------------------
Mledit.c | 141++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Aleditrc.5 | 347+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aleditrc.example | 300+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmemory.c | 26++++++++++++++++++++++----
Mmemory.h | 6++++++
Dtheme.c | 57---------------------------------------------------------
Dtheme.h | 39---------------------------------------
Mtheme_config.h | 20+++++++++++++++++---
Mtxtbuf.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mtxtbuf.h | 30++++++++++++++++++++++++++++++
Auglycrap.h | 15+++++++++++++++
Mundo.h | 2++
Mutil.c | 20++++++++++++++++++++
Mutil.h | 28++++++++++++++++++++++++++++
Mview.c | 24+++++++++++++-----------
Mview.h | 21++++-----------------
Mwindow.c | 33+++++++++++++++++++++------------
Mwindow.h | 10+++++++---
37 files changed, 3715 insertions(+), 1126 deletions(-)

diff --git a/IDEAS b/IDEAS @@ -10,3 +10,8 @@ -> I'm not sure it that's even possible in a portable way, though, since the keyboard layouts can be set in many different ways, so the entire state would somehow have to be saved to restore it again. + -> Wouldn't it also make more sense to avoid the whole keyboard + configuration and instead just temporarily switch to the default + layout in order to map the keycodes to text? I'm not sure how to do + that, though. It might not be possible at all since text can also + be inserted through input methods. diff --git a/LICENSE b/LICENSE @@ -1,9 +1,11 @@ Note 1: Some stuff is stolen from st (https://st.suckless.org) Note 2: Some stuff is stolen from OpenBSD (https://openbsd.org) +Note 3: pango-compat.{c,h} contains a bit of code copied from + Pango in order to be compatible with older versions. ISC License -Copyright (c) 2022 lumidify <nobody@lumidify.org> +Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/Makefile b/Makefile @@ -8,20 +8,24 @@ MANPREFIX = ${PREFIX}/man BIN = ${NAME} MAN1 = ${BIN:=.1} +MAN5 = leditrc.5 MISCFILES = Makefile README LICENSE IDEAS NOTES TODO +DEBUG=0 +SANITIZE=0 + OBJ = \ assert.o \ buffer.o \ view.o \ cache.o \ keys.o \ + configparser.o \ keys_basic.o \ keys_command.o \ ledit.o \ memory.o \ search.o \ - theme.o \ txtbuf.o \ undo.o \ util.o \ @@ -38,11 +42,11 @@ HDR = \ cache.h \ common.h \ keys.h \ + configparser.h \ keys_basic.h \ keys_command.h \ memory.h \ search.h \ - theme.h \ txtbuf.h \ undo.h \ util.h \ @@ -50,26 +54,27 @@ HDR = \ window.h \ cleanup.h \ macros.h \ - pango-compat.h + pango-compat.h \ + uglycrap.h CONFIGHDR = \ config.h \ theme_config.h \ - keys_basic_config.h \ - keys_command_config.h \ keys_config.h -CFLAGS_LEDIT = ${CFLAGS} -g -Wall -Wextra -pedantic -D_POSIX_C_SOURCE=200809L `pkg-config --cflags x11 xkbfile pangoxft xext` -LDFLAGS_LEDIT = ${LDFLAGS} `pkg-config --libs x11 xkbfile pangoxft xext` -lm +EXTRA_CFLAGS_DEBUG0 = ${CFLAGS} +EXTRA_LDFLAGS_DEBUG0 = ${LDFLAGS} +EXTRA_CFLAGS_DEBUG1 = -DLEDIT_DEBUG -g +EXTRA_FLAGS_SANITIZE1 = -fsanitize=address + +CFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_CFLAGS_DEBUG${DEBUG}} -Wall -Wextra -pedantic -D_POSIX_C_SOURCE=200809L -std=c99 `pkg-config --cflags x11 xkbfile pangoxft xext` +LDFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_LDFLAGS_DEBUG${DEBUG}} `pkg-config --libs x11 xkbfile pangoxft xext` -lm all: ${BIN} # FIXME: make this nicer ledit.o window.o : config.h -theme.o : theme_config.h -keys_basic.o : keys_basic_config.h keys_config.h -keys_command.o : keys_command_config.h keys_config.h -keys.o : keys_config.h +configparser.o : keys_config.h theme_config.h ${OBJ} : ${HDR} @@ -84,12 +89,16 @@ install: all cp -f ${BIN} "${DESTDIR}${PREFIX}/bin" for f in ${BIN}; do chmod 755 "${DESTDIR}${PREFIX}/bin/$$f"; done mkdir -p "${DESTDIR}${MANPREFIX}/man1" + mkdir -p "${DESTDIR}${MANPREFIX}/man5" cp -f ${MAN1} "${DESTDIR}${MANPREFIX}/man1" + cp -f ${MAN5} "${DESTDIR}${MANPREFIX}/man5" for m in ${MAN1}; do chmod 644 "${DESTDIR}${MANPREFIX}/man1/$$m"; done + for m in ${MAN5}; do chmod 644 "${DESTDIR}${MANPREFIX}/man5/$$m"; done uninstall: for f in ${BIN}; do rm -f "${DESTDIR}${PREFIX}/bin/$$f"; done for m in ${MAN1}; do rm -f "${DESTDIR}${MANPREFIX}/man1/$$m"; done + for m in ${MAN5}; do rm -f "${DESTDIR}${MANPREFIX}/man5/$$m"; done clean: rm -f ${BIN} ${OBJ} @@ -97,7 +106,7 @@ clean: dist: rm -rf "${NAME}-${VERSION}" mkdir -p "${NAME}-${VERSION}" - cp -f ${MAN1} ${SRC} ${HDR} ${CONFIGHDR} ${MISCFILES} "${NAME}-${VERSION}" + cp -f ${MAN1} ${MAN5} ${SRC} ${HDR} ${CONFIGHDR} ${MISCFILES} "${NAME}-${VERSION}" tar cf - "${NAME}-${VERSION}" | gzip -c > "${NAME}-${VERSION}.tar.gz" rm -rf "${NAME}-${VERSION}" diff --git a/README b/README @@ -1,9 +1,6 @@ WARNING: This is work in progress! A lot of bugs still need to be fixed before this can be used as a real text editor. -Note: The compiler flags currently still include -g to add debug symbols. -This will be removed when ledit is officially released (if I remember...). - ledit is a vi-like text editor for people who switch between keyboard layouts frequently and/or work with languages that require complex text layout. @@ -18,10 +15,31 @@ On OpenBSD: pango On MX Linux: libpango1.0-dev, libx11-dev, libxkbfile-dev (this is just from memory, I need to test it with a fresh system sometime) -The documentation can be viewed in ledit.1 or at the following locations: +Installation: +Simply run 'make' and 'make install' +To compile with debugging symbols and some debug output, run 'make DEBUG=1' +To compile with fsanitize=address, run 'make SANITIZE=1' + +A sample configuration file can be found in leditrc.example. +Copy this to ~/.leditrc in order to use it. + +The documentation can be viewed in ledit.1 and leditrc.5 or at the following locations: gopher://lumidify.org/0/doc/ledit/ledit-current.txt gopher://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/0/doc/ledit/ledit-current.txt http://lumidify.org/doc/ledit/ledit-current.html http://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/doc/ledit/ledit-current.html https://lumidify.org/doc/ledit/ledit-current.html + +gopher://lumidify.org/0/doc/ledit/leditrc-current.txt +gopher://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/0/doc/ledit/leditrc-current.txt +http://lumidify.org/doc/ledit/leditrc-current.html +http://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/doc/ledit/leditrc-current.html +https://lumidify.org/doc/ledit/leditrc-current.html + +Note that the documentation is far from finished! None of the functions are +documented yet in leditrc.5 and ledit.1 is somewhat outdated. This will +be fixed sometime... + +Also note that nothing is stable at the moment. In particular, some of the +function names mentioned in leditrc.5 will probably be changed still. diff --git a/buffer.c b/buffer.c @@ -17,7 +17,6 @@ #include "util.h" #include "undo.h" #include "cache.h" -#include "theme.h" #include "memory.h" #include "common.h" #include "txtbuf.h" @@ -284,10 +283,10 @@ buffer_set_hard_line_based(ledit_buffer *buffer, int hl) { } void -buffer_add_view(ledit_buffer *buffer, ledit_theme *theme, ledit_mode mode, size_t line, size_t pos, long scroll_offset) { +buffer_add_view(ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos, long scroll_offset) { size_t new_num = add_sz(buffer->views_num, 1); buffer->views = ledit_reallocarray(buffer->views, new_num, sizeof(ledit_view *)); - buffer->views[buffer->views_num] = view_create(buffer, theme, mode, line, pos); + buffer->views[buffer->views_num] = view_create(buffer, mode, line, pos); set_view_hard_line_text(buffer, buffer->views[buffer->views_num]); view_scroll(buffer->views[buffer->views_num], scroll_offset); buffer->views_num = new_num; @@ -374,11 +373,11 @@ buffer_load_file(ledit_buffer *buffer, char *filename, size_t line, char **errst buffer->modified = 0; return 0; error: - if (*errstr) + if (errstr) *errstr = strerror(errno); return 1; errorclose: - if (*errstr) + if (errstr) *errstr = strerror(errno); fclose(file); return 1; @@ -397,11 +396,11 @@ buffer_write_to_file(ledit_buffer *buffer, FILE *file, char **errstr) { buffer->modified = 0; return 0; error: - if (*errstr) + if (errstr) *errstr = strerror(errno); return 1; errorclose: - if (*errstr) + if (errstr) *errstr = strerror(errno); fclose(file); return 1; @@ -413,7 +412,7 @@ buffer_write_to_fd(ledit_buffer *buffer, int fd, char **errstr) { if (!file) goto error; return buffer_write_to_file(buffer, file, errstr); error: - if (*errstr) + if (errstr) *errstr = strerror(errno); /* catching errors on the close wouldn't really make much sense anymore */ @@ -428,7 +427,7 @@ buffer_write_to_filename(ledit_buffer *buffer, char *filename, char **errstr) { if (!file) goto error; return buffer_write_to_file(buffer, file, errstr); error: - if (*errstr) + if (errstr) *errstr = strerror(errno); return 1; } @@ -445,6 +444,10 @@ buffer_destroy(ledit_buffer *buffer) { if (buffer->filename) free(buffer->filename); marklist_destroy(buffer->marklist); + for (size_t i = 0; i < buffer->views_num; i++) { + view_destroy(buffer->views[i]); + } + free(buffer->views); free(buffer); } @@ -854,6 +857,7 @@ line_byte_to_char(ledit_line *line, size_t byte) { static void buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length) { ledit_line *l = buffer_get_line(buffer, line); + /* FIXME: somehow make sure this doesn't get optimized out? */ (void)add_sz(start, length); /* just check that no overflow */ ledit_assert(start + length <= l->len); if (start <= l->gap && start + length >= l->gap) { @@ -979,6 +983,7 @@ buffer_delete_with_undo_base( size_t line_index2, size_t byte_index2, txtbuf *text_ret) { /* FIXME: global txtbuf to avoid allocating each time */ + /* actually, could just use stack variable here */ txtbuf *buf = text_ret != NULL ? text_ret : txtbuf_new(); sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2); delete_range_base( diff --git a/buffer.h b/buffer.h @@ -79,8 +79,7 @@ void buffer_set_hard_line_based(ledit_buffer *buffer, int hl); * 'scroll_offset' is the initial pixel scroll offset. */ void buffer_add_view( - ledit_buffer *buffer, ledit_theme *theme, - ledit_mode mode, size_t line, size_t pos, long scroll_offset + ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos, long scroll_offset ); /* diff --git a/config.h b/config.h @@ -1,3 +1,10 @@ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +/************************* + * General configuration * + *************************/ + /* Note: These have to be less than one second */ /* @@ -17,3 +24,5 @@ * events - events inbetween are discarded (nanoseconds) */ #define RESIZE_TICK (long long)200000000 + +#endif /* _CONFIG_H_ */ diff --git a/configparser.c b/configparser.c @@ -0,0 +1,1810 @@ +#ifdef LEDIT_DEBUG +#include <time.h> +#include "macros.h" +#endif +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <stdint.h> +#include <stdlib.h> +#include <limits.h> + +#include "util.h" +#include "memory.h" +#include "assert.h" +#include "configparser.h" +#include "theme_config.h" +#include "keys_config.h" + +/* FIXME: Replace this entire parser with something sensible. + The current handwritten parser is mainly for the lulz. */ + +/* FIXME: standardize error messages */ +/* FIXME: it isn't entirely correct to give size_t as length for + string in print_fmt (supposed to be int) */ + +struct config { + ledit_theme *theme; + basic_key_array *basic_keys; + command_key_array *command_keys; + command_array *cmds; + char **langs; + size_t num_langs; + size_t alloc_langs; +} config = {NULL}; + +enum toktype { + STRING, + LBRACE, + RBRACE, + EQUALS, + NEWLINE, + ERROR, + END +}; + +static const char * +toktype_str(enum toktype type) { + switch (type) { + case STRING: + return "string"; + break; + case LBRACE: + return "left brace"; + break; + case RBRACE: + return "right brace"; + break; + case EQUALS: + return "equals"; + break; + case NEWLINE: + return "newline"; + break; + case ERROR: + return "error"; + break; + case END: + return "end of file"; + break; + default: + return "unknown"; + } +} + +struct token { + char *text; + size_t len; + enum toktype type; + size_t line; /* line in original input */ + size_t line_offset; /* offset from start of line */ +}; + +struct lexstate { + char *text; + size_t len; /* length of text */ + size_t cur; /* current byte position */ + size_t cur_line; /* current line */ + size_t line_start; /* byte offset of start of current line */ +}; + +static struct token +next_token(struct lexstate *s) { + char c; + struct token tok; + while (1) { + if (s->cur >= s->len) + return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1}; + while (isspace(c = s->text[s->cur])) { + s->cur++; + if (c == '\n') { + struct token tok = (struct token){s->text + s->cur - 1, 1, NEWLINE, s->cur_line, s->cur - s->line_start}; + s->cur_line++; + s->line_start = s->cur; + return tok; + } + if (s->cur >= s->len) + return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1}; + } + + switch (s->text[s->cur]) { + case '#': + s->cur++; + while (s->cur < s->len && s->text[s->cur] != '\n') + s->cur++; + continue; + case '{': + tok = (struct token){s->text + s->cur, 1, LBRACE, s->cur_line, s->cur - s->line_start + 1}; + s->cur++; + break; + case '}': + tok = (struct token){s->text + s->cur, 1, RBRACE, s->cur_line, s->cur - s->line_start + 1}; + s->cur++; + break; + case '=': + tok = (struct token){s->text + s->cur, 1, EQUALS, s->cur_line, s->cur - s->line_start + 1}; + s->cur++; + break; + case '"': + /* FIXME: error if next char is not whitespace or end */ + s->cur++; + tok = (struct token){s->text + s->cur, 0, STRING, s->cur_line, s->cur - s->line_start + 1}; + size_t shift = 0, bs = 0; + int finished = 0; + while (s->cur < s->len) { + char c = s->text[s->cur]; + if (c == '\n') { + break; + } else if (c == '\\') { + shift += bs; + tok.len += bs; + bs = (bs + 1) % 2; + } else if (c == '"') { + if (bs) { + shift++; + tok.len++; + bs = 0; + } else { + s->cur++; + finished = 1; + break; + } + } else { + tok.len++; + } + s->text[s->cur - shift] = s->text[s->cur]; + s->cur++; + } + if (!finished) { + tok.text = "Unfinished string"; + tok.len = strlen("Unfinished string"); + tok.type = ERROR; + } + break; + default: + tok = (struct token){s->text + s->cur, 1, STRING, s->cur_line, s->cur - s->line_start + 1}; + s->cur++; + while (s->cur < s->len) { + char c = s->text[s->cur]; + if (isspace(c) || c == '{' || c == '}' || c == '=') { + break; + } else if (c == '"') { + tok.text = "Unexpected start of string"; + tok.len = strlen("Unexpected start of string"); + tok.type = ERROR; + tok.line_offset = s->cur - s->line_start + 1; + } + tok.len++; + s->cur++; + } + } + return tok; + } +} + +typedef struct ast_obj ast_obj; + +typedef struct { + ast_obj *objs; + size_t len, cap; +} ast_list; + +typedef struct { + struct token tok; +} ast_string; + +typedef struct { + struct token tok; + ast_obj *value; +} ast_assignment; + +typedef struct { + struct token func_tok; + struct token *args; + size_t len, cap; +} ast_statement; + +enum objtype { + OBJ_LIST, + OBJ_STRING, + OBJ_ASSIGNMENT, + OBJ_STATEMENT +}; + +struct ast_obj { + struct token tok; + union { + ast_list list; + ast_string str; + ast_assignment assignment; + ast_statement statement; + } obj; + enum objtype type; +}; + +/* Note: These functions only free everything inside the object + so they can be used with stack variables (or array elements)! */ + +static void destroy_obj(ast_obj *obj); + +static void +destroy_list(ast_list *list) { + if (!list) + return; + for (size_t i = 0; i < list->len; i++) { + destroy_obj(&list->objs[i]); + } + free(list->objs); + list->objs = NULL; + list->len = list->cap = 0; +} + +static void +destroy_obj(ast_obj *obj) { + if (!obj) + return; + switch (obj->type) { + case OBJ_LIST: + destroy_list(&obj->obj.list); + break; + case OBJ_ASSIGNMENT: + destroy_obj(obj->obj.assignment.value); + free(obj->obj.assignment.value); + obj->obj.assignment.value = NULL; + break; + case OBJ_STATEMENT: + free(obj->obj.statement.args); + obj->obj.statement.args = NULL; + obj->obj.statement.len = obj->obj.statement.cap = 0; + break; + default: + break; + } +} + +/* FIXME: overflow */ +static void +list_append(ast_list *list, ast_obj o) { + list->cap = ideal_array_size(list->cap, add_sz(list->len, 1)); + list->objs = ledit_reallocarray(list->objs, list->cap, sizeof(ast_obj)); + list->objs[list->len++] = o; +} + +static void +statement_append(ast_statement *statement, struct token tok) { + statement->cap = ideal_array_size(statement->cap, add_sz(statement->len, 1)); + statement->args = ledit_reallocarray(statement->args, statement->cap, sizeof(struct token)); + statement->args[statement->len++] = tok; +} + +/* FIXME: make this a bit nicer */ +/* Note: A lot of the ugliness is because of the + (failed) attempt to somewhat optimize everything */ + +static int +parse_list(struct lexstate *s, ast_list *ret, int implicit_end, char *filename, char **errstr) { + *ret = (ast_list){NULL, 0, 0}; + struct token tok = next_token(s); + struct token tok2; + while (1) { + switch (tok.type) { + case STRING: + tok2 = next_token(s); + if (tok2.type == STRING) { + ast_statement statement = {tok, NULL, 0, 0}; + /* FIXME: maybe allow lists in statements? */ + while (tok2.type == STRING) { + statement_append(&statement, tok2); + tok2 = next_token(s); + } + list_append(ret, (ast_obj){.tok = tok, .obj = {.statement = statement}, .type = OBJ_STATEMENT}); + tok = tok2; + } else if (tok2.type == EQUALS) { + ast_assignment assignment = {tok, NULL}; + assignment.value = ledit_malloc(sizeof(ast_obj)); + tok2 = next_token(s); + assignment.value->tok = tok2; + struct token orig_tok = tok; + if (tok2.type == STRING) { + assignment.value->obj.str = (ast_string){tok2}; + assignment.value->type = OBJ_STRING; + tok = next_token(s); + if (tok.type == STRING) { + *errstr = print_fmt( + "%s: Invalid assignment at line %zu, offset %zu", + filename, tok.line, tok.line_offset + ); + free(assignment.value); + goto error; + } + } else if (tok2.type == LBRACE) { + assignment.value->type = OBJ_LIST; + /* just in case */ + assignment.value->obj.list = (ast_list){NULL, 0, 0}; + if (parse_list(s, &assignment.value->obj.list, 0, filename, errstr)) { + free(assignment.value); + goto error; + } + tok = next_token(s); + if (tok.type == STRING) { + *errstr = print_fmt( + "%s: Invalid assignment at line %zu, offset %zu", + filename, tok.line, tok.line_offset + ); + destroy_list(&assignment.value->obj.list); + free(assignment.value); + goto error; + } + } else { + *errstr = print_fmt( + "%s: Invalid assignment at line %zu, offset %zu", + filename, tok2.line, tok2.line_offset + ); + free(assignment.value); + goto error; + } + list_append(ret, (ast_obj){.tok = orig_tok, .obj = {.assignment = assignment}, .type = OBJ_ASSIGNMENT}); + } else { + *errstr = print_fmt( + "%s: Invalid token '%s' at line %zu, offset %zu", + filename, toktype_str(tok2.type), tok2.line, tok2.line_offset + ); + goto error; + } + break; + case NEWLINE: + tok = next_token(s); + break; + case RBRACE: + if (implicit_end) { + *errstr = print_fmt( + "%s: Unexpected right brace at line %zu, offset %zu", + filename, tok.line, tok.line_offset + ); + goto error; + } else { + return 0; + } + case END: + if (!implicit_end) { + *errstr = print_fmt( + "%s: Unexpected end of file at line %zu, offset %zu", + filename, tok.line, tok.line_offset + ); + goto error; + } else { + return 0; + } + case LBRACE: + case EQUALS: + case ERROR: + default: + *errstr = print_fmt( + "%s: Unexpected token '%s' at line %zu, offset %zu", + filename, toktype_str(tok.type), tok.line, tok.line_offset + ); + goto error; + } + } + return 0; +error: + destroy_list(ret); + return 1; +} + +static char * +load_file(char *filename, size_t *len_ret, char **errstr) { + long len; + char *file_contents; + FILE *file; + + /* FIXME: https://wiki.sei.cmu.edu/confluence/display/c/FIO19-C.+Do+not+use+fseek()+and+ftell()+to+compute+the+size+of+a+regular+file */ + file = fopen(filename, "r"); + if (!file) goto error; + if (fseek(file, 0, SEEK_END)) goto errorclose; + len = ftell(file); + if (len < 0) goto errorclose; + if (fseek(file, 0, SEEK_SET)) goto errorclose; + file_contents = ledit_malloc(add_sz((size_t)len, 1)); + clearerr(file); + fread(file_contents, 1, (size_t)len, file); + if (ferror(file)) goto errorclose; + file_contents[len] = '\0'; + if (fclose(file)) goto error; + *len_ret = (size_t)len; + return file_contents; +error: + if (errstr) + *errstr = strerror(errno); + return NULL; +errorclose: + if (errstr) + *errstr = strerror(errno); + fclose(file); + return NULL; +} + +/* FIXME: max recursion depth in parser */ + +static int +parse_theme_color( + ledit_common *common, + void *obj, const char *val, size_t val_len, char *key, + char *filename, size_t line, size_t line_offset, char **errstr) { + XftColor *dst = (XftColor *)obj; + char col[8]; /* 7 for '#' and 6 hex values + 1 for '\0' */ + if (val_len == 7 && val[0] == '#') { + strncpy(col, val, val_len); + col[val_len] = '\0'; + } else if (val_len == 6) { + col[0] = '#'; + strncpy(col + 1, val, val_len); + col[val_len + 1] = '\0'; + } else { + goto error; + } + /* FIXME: XftColorAllocValue */ + if (!XftColorAllocName(common->dpy, common->vis, common->cm, col, dst)) + goto error; + return 0; +error: + *errstr = print_fmt( + "%s: Unable to parse color specification " + "'%.*s' for '%s' at line %zu, position %zu", + filename, val_len, val, key, line, line_offset + ); + return 1; +} + +static void +destroy_theme_color(ledit_common *common, void *obj) { + XftColor *color = (XftColor *)obj; + XftColorFree(common->dpy, common->vis, common->cm, color); +} + +/* based partially on OpenBSD's strtonum */ +static int +parse_theme_number( + ledit_common *common, + void *obj, const char *val, size_t val_len, char *key, + char *filename, size_t line, size_t line_offset, char **errstr) { + (void)common; + int *num = (int *)obj; + /* the string needs to be nul-terminated + if it contains more than 9 digits, it's illegal anyways */ + if (val_len > 9) + goto error; + char str[10]; + strncpy(str, val, val_len); + str[val_len] = '\0'; + char *end; + long l = strtol(str, &end, 10); + if (str == end || *end != '\0' || + l < 0 || l > INT_MAX || ((l == LONG_MIN || + l == LONG_MAX) && errno == ERANGE)) { + goto error; + } + *num = (int)l; + return 0; +error: + *errstr = print_fmt( + "%s: Invalid number '%.*s' " + "for '%s' at line %zu, position %zu", + filename, val_len, val, key, line, line_offset + ); + return 1; +} + +static void +destroy_theme_number(ledit_common *common, void *obj) { + (void)common; + (void)obj; +} + +static int +parse_theme_string( + ledit_common *common, + void *obj, const char *val, size_t val_len, char *key, + char *filename, size_t line, size_t line_offset, char **errstr) { + (void)common; (void)key; + (void)filename; (void)line; (void)line_offset; (void)errstr; + + char **obj_str = (char **)obj; + *obj_str = ledit_strndup(val, val_len); + return 0; +} + +static void +destroy_theme_string(ledit_common *common, void *obj) { + (void)common; + char **obj_str = (char **)obj; + free(*obj_str); +} + +/* FIXME: This interface is absolutely horrible - it's mainly this way to reuse the + theme array for the destroy function */ +/* If theme is NULL, a new theme is loaded, else it is destroyed */ +static ledit_theme * +load_destroy_theme(ledit_common *common, ast_list *theme_list, ledit_theme *theme, char *filename, char **errstr) { + *errstr = NULL; + int default_init = theme ? 1 : 0; + if (!theme) + theme = ledit_malloc(sizeof(ledit_theme)); + + struct { + char *key; + void *obj; + int (*parse_func)( + ledit_common *common, + void *obj, const char *val, size_t val_len, char *key, + char *filename, size_t line, size_t line_offset, char **errstr + ); + void (*destroy_func)(ledit_common *common, void *obj); + const char *default_value; + int initialized; + } settings[] = { + {"text-font", &theme->text_font, &parse_theme_string, &destroy_theme_string, TEXT_FONT, default_init}, + {"text-size", &theme->text_size, &parse_theme_number, &destroy_theme_number, TEXT_SIZE, default_init}, + {"scrollbar-width", &theme->scrollbar_width, &parse_theme_number, &destroy_theme_number, SCROLLBAR_WIDTH, default_init}, + {"scrollbar-step", &theme->scrollbar_step, &parse_theme_number, &destroy_theme_number, SCROLLBAR_STEP, default_init}, + {"text-fg", &theme->text_fg, &parse_theme_color, &destroy_theme_color, TEXT_FG, default_init}, + {"text-bg", &theme->text_bg, &parse_theme_color, &destroy_theme_color, TEXT_BG, default_init}, + {"cursor-fg", &theme->cursor_fg, &parse_theme_color, &destroy_theme_color, CURSOR_FG, default_init}, + {"cursor-bg", &theme->cursor_bg, &parse_theme_color, &destroy_theme_color, CURSOR_BG, default_init}, + {"selection-fg", &theme->selection_fg, &parse_theme_color, &destroy_theme_color, SELECTION_FG, default_init}, + {"selection-bg", &theme->selection_bg, &parse_theme_color, &destroy_theme_color, SELECTION_BG, default_init}, + {"bar-fg", &theme->bar_fg, &parse_theme_color, &destroy_theme_color, BAR_FG, default_init}, + {"bar-bg", &theme->bar_bg, &parse_theme_color, &destroy_theme_color, BAR_BG, default_init}, + {"bar-cursor", &theme->bar_cursor, &parse_theme_color, &destroy_theme_color, BAR_CURSOR, default_init}, + {"scrollbar-fg", &theme->scrollbar_fg, &parse_theme_color, &destroy_theme_color, SCROLLBAR_FG, default_init}, + {"scrollbar-bg", &theme->scrollbar_bg, &parse_theme_color, &destroy_theme_color, SCROLLBAR_BG, default_init}, + }; + + if (default_init) + goto cleanup; + + if (theme_list) { + for (size_t i = 0; i < theme_list->len; i++) { + size_t line = theme_list->objs[i].tok.line; + size_t line_offset = theme_list->objs[i].tok.line_offset; + if (theme_list->objs[i].type != OBJ_ASSIGNMENT) { + *errstr = print_fmt( + "%s: Invalid statement in theme configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto cleanup; + } else if (theme_list->objs[i].obj.assignment.value->type != OBJ_STRING) { + *errstr = print_fmt( + "%s: Invalid assignment in theme configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto cleanup; + } + + char *key = theme_list->objs[i].obj.assignment.tok.text; + size_t key_len = theme_list->objs[i].obj.assignment.tok.len; + char *val = theme_list->objs[i].obj.assignment.value->obj.str.tok.text; + size_t val_len = theme_list->objs[i].obj.assignment.value->obj.str.tok.len; + + int found = 0; + /* FIXME: use binary search maybe */ + for (size_t j = 0; j < LENGTH(settings); j++) { + if (str_array_equal(settings[j].key, key, key_len)) { + /* FIXME: maybe just make this a warning? */ + if (settings[j].initialized) { + *errstr = print_fmt( + "%s: Duplicate definition of " + "'%.*s' at line %zu, position %zu", + filename, key_len, key, line, line_offset + ); + goto cleanup; + } + if (settings[j].parse_func( + common, settings[j].obj, val, val_len, + settings[j].key, filename, line, line_offset, errstr)) { + goto cleanup; + } + settings[j].initialized = 1; + found = 1; + break; + } + } + if (!found) { + *errstr = print_fmt( + "%s: Invalid theme setting " + "'%.*s' at line %zu, position %zu", + filename, key_len, key, line, line_offset + ); + goto cleanup; + } + } + } + + for (size_t i = 0; i < LENGTH(settings); i++) { + if (!settings[i].initialized) { + /* FIXME: kind of inefficient to calculate strlen at runtime */ + /* FIXME: line number doesn't make sense */ + if (settings[i].parse_func( + common, settings[i].obj, settings[i].default_value, + strlen(settings[i].default_value), settings[i].key, + "default config", 0, 0, errstr)) { + goto cleanup; + } + } + } + + return theme; +cleanup: + for (size_t i = 0; i < LENGTH(settings); i++) { + if (settings[i].initialized) { + settings[i].destroy_func(common, settings[i].obj); + } + } + free(theme); + return NULL; +} + +static ledit_theme * +load_theme(ledit_common *common, ast_list *theme_list, char *filename, char **errstr) { + return load_destroy_theme(common, theme_list, NULL, filename, errstr); +} + +static void +destroy_theme(ledit_common *common, ledit_theme *theme) { + char *errstr = NULL; + if (!theme) + return; + (void)load_destroy_theme(common, NULL, theme, NULL, &errstr); + /* shouldn't happen... */ + if (errstr) + free(errstr); +} + +/* This only destroys the members inside 'cfg' since the config + * struct itself is usually not on the heap. */ +static void +config_destroy(ledit_common *common, struct config *cfg) { + if (cfg->theme) + destroy_theme(common, cfg->theme); + cfg->theme = NULL; + for (size_t i = 0; i < cfg->num_langs; i++) { + for (size_t j = 0; j < cfg->basic_keys[i].num_keys; j++) { + free(cfg->basic_keys[i].keys[j].text); + } + free(cfg->basic_keys[i].keys); + for (size_t j = 0; j < cfg->command_keys[i].num_keys; j++) { + free(cfg->command_keys[i].keys[j].text); + } + free(cfg->command_keys[i].keys); + for (size_t j = 0; j < cfg->cmds[i].num_cmds; j++) { + free(cfg->cmds[i].cmds[j].text); + } + free(cfg->cmds[i].cmds); + free(cfg->langs[i]); + } + free(cfg->basic_keys); + free(cfg->command_keys); + free(cfg->cmds); + free(cfg->langs); + cfg->basic_keys = NULL; + cfg->command_keys = NULL; + cfg->cmds = NULL; + cfg->langs = NULL; + cfg->num_langs = cfg->alloc_langs = 0; +} + +void +config_cleanup(ledit_common *common) { + config_destroy(common, &config); +} + +/* FIXME: which additional ones are needed here? */ +static struct keysym_mapping { + char *name; + KeySym keysym; +} keysym_map[] = { + {"backspace", XK_BackSpace}, + {"begin", XK_Begin}, + {"break", XK_Break}, + {"cancel", XK_Cancel}, + {"clear", XK_Clear}, + {"delete", XK_Delete}, + {"down", XK_Down}, + {"end", XK_End}, + {"escape", XK_Escape}, + {"execute", XK_Execute}, + + {"f1", XK_F1}, + {"f10", XK_F10}, + {"f11", XK_F11}, + {"f12", XK_F12}, + {"f13", XK_F13}, + {"f14", XK_F14}, + {"f15", XK_F15}, + {"f16", XK_F16}, + {"f17", XK_F17}, + {"f18", XK_F18}, + {"f19", XK_F19}, + {"f2", XK_F2}, + {"f20", XK_F20}, + {"f21", XK_F21}, + {"f22", XK_F22}, + {"f23", XK_F23}, + {"f24", XK_F24}, + {"f25", XK_F25}, + {"f26", XK_F26}, + {"f27", XK_F27}, + {"f28", XK_F28}, + {"f29", XK_F29}, + {"f3", XK_F3}, + {"f30", XK_F30}, + {"f31", XK_F31}, + {"f32", XK_F32}, + {"f33", XK_F33}, + {"f34", XK_F34}, + {"f35", XK_F35}, + {"f4", XK_F4}, + {"f5", XK_F5}, + {"f6", XK_F6}, + {"f7", XK_F7}, + {"f8", XK_F8}, + {"f9", XK_F9}, + + {"find", XK_Find}, + {"help", XK_Help}, + {"home", XK_Home}, + {"insert", XK_Insert}, + + {"kp-0", XK_KP_0}, + {"kp-1", XK_KP_1}, + {"kp-2", XK_KP_2}, + {"kp-3", XK_KP_3}, + {"kp-4", XK_KP_4}, + {"kp-5", XK_KP_5}, + {"kp-6", XK_KP_6}, + {"kp-7", XK_KP_7}, + {"kp-8", XK_KP_8}, + {"kp-9", XK_KP_9}, + {"kp-add", XK_KP_Add}, + {"kp-begin", XK_KP_Begin}, + {"kp-decimal", XK_KP_Decimal}, + {"kp-delete", XK_KP_Delete}, + {"kp-divide", XK_KP_Divide}, + {"kp-down", XK_KP_Down}, + {"kp-end", XK_KP_End}, + {"kp-enter", XK_KP_Enter}, + {"kp-equal", XK_KP_Equal}, + {"kp-f1", XK_KP_F1}, + {"kp-f2", XK_KP_F2}, + {"kp-f3", XK_KP_F3}, + {"kp-f4", XK_KP_F4}, + {"kp-home", XK_KP_Home}, + {"kp-insert", XK_KP_Insert}, + {"kp-left", XK_KP_Left}, + {"kp-multiply", XK_KP_Multiply}, + {"kp-next", XK_KP_Next}, + {"kp-page-down", XK_KP_Page_Down}, + {"kp-page-up", XK_KP_Page_Up}, + {"kp-prior", XK_KP_Prior}, + {"kp-right", XK_KP_Right}, + {"kp-separator", XK_KP_Separator}, + {"kp-space", XK_KP_Space}, + {"kp-subtract", XK_KP_Subtract}, + {"kp-tab", XK_KP_Tab}, + {"kp-up", XK_KP_Up}, + + {"l1", XK_L1}, + {"l10", XK_L10}, + {"l2", XK_L2}, + {"l3", XK_L3}, + {"l4", XK_L4}, + {"l5", XK_L5}, + {"l6", XK_L6}, + {"l7", XK_L7}, + {"l8", XK_L8}, + {"l9", XK_L9}, + + {"left", XK_Left}, + {"linefeed", XK_Linefeed}, + {"menu", XK_Menu}, + {"mode-switch", XK_Mode_switch}, + {"next", XK_Next}, + {"num-lock", XK_Num_Lock}, + {"page-down", XK_Page_Down}, + {"page-up", XK_Page_Up}, + {"pause", XK_Pause}, + {"print", XK_Print}, + {"prior", XK_Prior}, + + {"r1", XK_R1}, + {"r10", XK_R10}, + {"r11", XK_R11}, + {"r12", XK_R12}, + {"r13", XK_R13}, + {"r14", XK_R14}, + {"r15", XK_R15}, + {"r2", XK_R2}, + {"r3", XK_R3}, + {"r4", XK_R4}, + {"r5", XK_R5}, + {"r6", XK_R6}, + {"r7", XK_R7}, + {"r8", XK_R8}, + {"r9", XK_R9}, + + {"redo", XK_Redo}, + {"return", XK_Return}, + {"right", XK_Right}, + {"script-switch", XK_script_switch}, + {"scroll-lock", XK_Scroll_Lock}, + {"select", XK_Select}, + {"space", XK_space}, + {"sysreq", XK_Sys_Req}, + {"tab", XK_Tab}, + {"up", XK_Up}, + {"undo", XK_Undo}, +}; + +GEN_CB_MAP_HELPERS(keysym_map, struct keysym_mapping, name) + +static int +parse_keysym(char *keysym_str, size_t len, KeySym *sym) { + struct keysym_mapping *km = keysym_map_get_entry(keysym_str, len); + if (!km) + return 1; + *sym = km->keysym; + return 0; +} + +static int +parse_modemask(char *modemask_str, size_t len, ledit_mode *mode_ret) { + size_t cur = 0; + *mode_ret = 0; + while (cur < len) { + if (str_array_equal("normal", modemask_str + cur, LEDIT_MIN(6, len - cur))) { + cur += 6; + *mode_ret |= NORMAL; + } else if (str_array_equal("visual", modemask_str + cur, LEDIT_MIN(6, len - cur))) { + cur += 6; + *mode_ret |= VISUAL; + } else if (str_array_equal("insert", modemask_str + cur, LEDIT_MIN(6, len - cur))) { + cur += 6; + *mode_ret |= INSERT; + } else { + return 1; + } + if (cur < len && modemask_str[cur] != '|') + return 1; + else + cur++; + } + return 0; +} + +static int +parse_modmask(char *modmask_str, size_t len, unsigned int *mask_ret) { + size_t cur = 0; + *mask_ret = 0; + while (cur < len) { + if (str_array_equal("shift", modmask_str + cur, LEDIT_MIN(5, len - cur))) { + cur += 5; + *mask_ret |= ShiftMask; + } else if (str_array_equal("lock", modmask_str + cur, LEDIT_MIN(4, len - cur))) { + cur += 4; + *mask_ret |= LockMask; + } else if (str_array_equal("control", modmask_str + cur, LEDIT_MIN(7, len - cur))) { + cur += 7; + *mask_ret |= ControlMask; + } else if (str_array_equal("mod1", modmask_str + cur, LEDIT_MIN(4, len - cur))) { + cur += 4; + *mask_ret |= Mod1Mask; + } else if (str_array_equal("mod2", modmask_str + cur, LEDIT_MIN(4, len - cur))) { + cur += 4; + *mask_ret |= Mod2Mask; + } else if (str_array_equal("mod3", modmask_str + cur, LEDIT_MIN(4, len - cur))) { + cur += 4; + *mask_ret |= Mod3Mask; + } else if (str_array_equal("mod4", modmask_str + cur, LEDIT_MIN(4, len - cur))) { + cur += 4; + *mask_ret |= Mod4Mask; + } else if (str_array_equal("any", modmask_str + cur, LEDIT_MIN(3, len - cur))) { + cur += 3; + *mask_ret = UINT_MAX; + } else { + return 1; + } + if (cur < len && modmask_str[cur] != '|') + return 1; + else + cur++; + } + return 0; +} + +/* FIXME: it would probably be safer to not write the string lengths by hand... */ +static int +parse_command_modemask(char *mode_str, size_t len, command_mode *mode_ret) { + size_t cur = 0; + *mode_ret = 0; + /* IMPORTANT: these need to be sorted appropriately so e.g. edit doesn't mess with edit-search */ + while (cur < len) { + if (str_array_equal("substitute", mode_str + cur, LEDIT_MIN(10, len - cur))) { + cur += 10; + *mode_ret |= CMD_SUBSTITUTE; + } else if (str_array_equal("edit-search-backwards", mode_str + cur, LEDIT_MIN(21, len - cur))) { + cur += 21; + *mode_ret |= CMD_EDITSEARCHB; + } else if (str_array_equal("edit-search", mode_str + cur, LEDIT_MIN(11, len - cur))) { + cur += 11; + *mode_ret |= CMD_EDITSEARCH; + } else if (str_array_equal("edit", mode_str + cur, LEDIT_MIN(4, len - cur))) { + cur += 4; + *mode_ret |= CMD_EDIT; + } else { + return 1; + } + if (cur < len && mode_str[cur] != '|') { + return 1; + } else { + cur++; + } + } + return 0; +} + +/* FIXME: generic dynamic array */ + +static void +push_lang(struct config *cfg) { + if (cfg->num_langs == cfg->alloc_langs) { + cfg->alloc_langs = ideal_array_size(cfg->alloc_langs, add_sz(cfg->num_langs, 1)); + cfg->basic_keys = ledit_reallocarray(cfg->basic_keys, cfg->alloc_langs, sizeof(basic_key_array)); + cfg->command_keys = ledit_reallocarray(cfg->command_keys, cfg->alloc_langs, sizeof(command_key_array)); + cfg->cmds = ledit_reallocarray(cfg->cmds, cfg->alloc_langs, sizeof(command_array)); + cfg->langs = ledit_reallocarray(cfg->langs, cfg->alloc_langs, sizeof(char *)); + } + basic_key_array *arr1 = &cfg->basic_keys[cfg->num_langs]; + arr1->keys = NULL; + arr1->num_keys = arr1->alloc_keys = 0; + command_key_array *arr2 = &cfg->command_keys[cfg->num_langs]; + arr2->keys = NULL; + arr2->num_keys = arr2->alloc_keys = 0; + command_array *arr3 = &cfg->cmds[cfg->num_langs]; + arr3->cmds = NULL; + arr3->num_cmds = arr3->alloc_cmds = 0; + cfg->langs[cfg->num_langs] = NULL; + cfg->num_langs++; +} + +#define GEN_PARSE_STATEMENT(name, cb_type, mapping_type, mode_parse_func) \ +static int \ +name(ast_statement *st, mapping_type *m, char *filename, char **errstr) { \ + size_t line = st->func_tok.line; \ + size_t line_offset = st->func_tok.line_offset; \ + m->cb = NULL; \ + m->text = NULL; \ + m->mods = 0; \ + m->modes = 0; \ + m->keysym = 0; \ + char *msg = NULL; \ + if (!str_array_equal("bind", st->func_tok.text, st->func_tok.len) || st->len < 1) { \ + msg = "Invalid statement"; \ + goto error; \ + } \ + m->cb = cb_type##_map_get_entry(st->args[0].text, st->args[0].len); \ + if (!m->cb) { \ + msg = "Invalid function specification"; \ + goto error; \ + } \ + int text_init = 0, keysym_init = 0, modes_init = 0, mods_init = 0; \ + for (size_t i = 1; i < st->len; i++) { \ + line = st->args[i].line; \ + line_offset = st->args[i].line_offset; \ + if (str_array_equal("mods", st->args[i].text, st->args[i].len)) { \ + if (mods_init) { \ + msg = "Duplicate mods specification"; \ + goto error; \ + } else if (i == st->len - 1) { \ + msg = "Unfinished statement"; \ + goto error; \ + } \ + i++; \ + if (parse_modmask(st->args[i].text, st->args[i].len, &m->mods)) { \ + msg = "Invalid mods specification"; \ + goto error; \ + } \ + mods_init = 1; \ + } else if (str_array_equal("modes", st->args[i].text, st->args[i].len)) { \ + if (modes_init) { \ + msg = "Duplicate modes specification"; \ + goto error; \ + } else if (i == st->len - 1) { \ + msg = "Unfinished statement"; \ + goto error; \ + } \ + i++; \ + if (mode_parse_func(st->args[i].text, st->args[i].len, &m->modes)) { \ + msg = "Invalid modes specification"; \ + goto error; \ + } else if (!cb_type##_modemask_is_valid(m->cb, m->modes)) { \ + msg = "Function not defined for all given modes"; \ + goto error; \ + } \ + modes_init = 1; \ + } else if (str_array_equal("keysym", st->args[i].text, st->args[i].len)) { \ + if (text_init) { \ + msg = "Text already specified"; \ + goto error; \ + } else if (keysym_init) { \ + msg = "Duplicate keysym specification"; \ + goto error; \ + } else if (i == st->len - 1) { \ + msg = "Unfinished statement"; \ + goto error; \ + } \ + i++; \ + if (parse_keysym(st->args[i].text, st->args[i].len, &m->keysym)) { \ + msg = "Invalid keysym specification"; \ + goto error; \ + } \ + keysym_init = 1; \ + } else if (str_array_equal("text", st->args[i].text, st->args[i].len)) { \ + if (keysym_init) { \ + msg = "Keysym already specified"; \ + goto error; \ + } else if (text_init) { \ + msg = "Duplicate text specification"; \ + goto error; \ + } else if (i == st->len - 1) { \ + msg = "Unfinished statement"; \ + goto error; \ + } \ + i++; \ + m->text = ledit_strndup(st->args[i].text, st->args[i].len); \ + text_init = 1; \ + } else if (str_array_equal("catchall", st->args[i].text, st->args[i].len)) { \ + if (keysym_init) { \ + msg = "Keysym already specified"; \ + goto error; \ + } else if (text_init) { \ + msg = "Duplicate text specification"; \ + goto error; \ + } \ + m->text = ledit_strdup(""); \ + text_init = 1; \ + } else { \ + msg = "Invalid statement"; \ + goto error; \ + } \ + } \ + if (!text_init && !keysym_init) { \ + msg = "No text or keysym specified"; \ + goto error; \ + } \ + if (!modes_init) { \ + msg = "No modes specified"; \ + goto error; \ + } \ + return 0; \ +error: \ + if (msg) { \ + *errstr = print_fmt( \ + "%s, line %zu, offset %zu: %s", filename, line, line_offset, msg \ + ); \ + } \ + if (m->text) \ + free(m->text); \ + return 1; \ +} + +GEN_PARSE_STATEMENT(parse_basic_key_statement, basic_key_cb, basic_key_mapping, parse_modemask) +GEN_PARSE_STATEMENT(parse_command_key_statement, command_key_cb, command_key_mapping, parse_command_modemask) + +static int +parse_command_statement(ast_statement *st, command_mapping *m, char *filename, char **errstr) { + size_t line = st->func_tok.line; + size_t line_offset = st->func_tok.line_offset; + m->cb = NULL; + m->text = NULL; + char *msg = NULL; + if (!str_array_equal("bind", st->func_tok.text, st->func_tok.len) || st->len != 2) { + msg = "Invalid statement"; + goto error; + } + m->cb = command_cb_map_get_entry(st->args[0].text, st->args[0].len); + if (!m->cb) { + msg = "Invalid function specification"; + goto error; + } + m->text = ledit_strndup(st->args[1].text, st->args[1].len); + return 0; +error: + if (msg) { + *errstr = print_fmt( + "%s, line %zu, offset %zu: %s", filename, line, line_offset, msg + ); + } + /* I guess this is unnecessary */ + if (m->text) + free(m->text); + return 1; +} + +static void +push_basic_key_mapping(basic_key_array *arr, basic_key_mapping m) { + if (arr->num_keys == arr->alloc_keys) { + arr->alloc_keys = ideal_array_size(arr->alloc_keys, add_sz(arr->num_keys, 1)); + arr->keys = ledit_reallocarray(arr->keys, arr->alloc_keys, sizeof(basic_key_mapping)); + } + arr->keys[arr->num_keys] = m; + arr->num_keys++; +} + +static void +push_command_key_mapping(command_key_array *arr, command_key_mapping m) { + if (arr->num_keys == arr->alloc_keys) { + arr->alloc_keys = ideal_array_size(arr->alloc_keys, add_sz(arr->num_keys, 1)); + arr->keys = ledit_reallocarray(arr->keys, arr->alloc_keys, sizeof(command_key_mapping)); + } + arr->keys[arr->num_keys] = m; + arr->num_keys++; +} + +static void +push_command_mapping(command_array *arr, command_mapping m) { + if (arr->num_cmds == arr->alloc_cmds) { + arr->alloc_cmds = ideal_array_size(arr->alloc_cmds, add_sz(arr->num_cmds, 1)); + arr->cmds = ledit_reallocarray(arr->cmds, arr->alloc_cmds, sizeof(command_mapping)); + } + arr->cmds[arr->num_cmds] = m; + arr->num_cmds++; +} + +/* FIXME: This could be made a lot nicer and less repetitive */ +static int +load_bindings(struct config *cfg, ast_list *list, char *filename, char **errstr) { + int basic_keys_init = 0, command_keys_init = 0, commands_init = 0; + size_t cur_lang = cfg->num_langs - 1; /* FIXME: ensure no underflow */ + for (size_t i = 0; i < list->len; i++) { + size_t line = list->objs[i].tok.line; + size_t line_offset = list->objs[i].tok.line_offset; + if (list->objs[i].type != OBJ_ASSIGNMENT) { + *errstr = print_fmt( + "%s: Invalid statement in bindings configuration " + "at list %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + char *key = list->objs[i].obj.assignment.tok.text; + size_t key_len = list->objs[i].obj.assignment.tok.len; + if (str_array_equal("language", key, key_len)) { + if (list->objs[i].obj.assignment.value->type != OBJ_STRING) { + *errstr = print_fmt( + "%s: Invalid language setting in bindings configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } else if (cfg->langs[cur_lang]) { + *errstr = print_fmt( + "%s: Duplicate language setting in bindings configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + char *val = list->objs[i].obj.assignment.value->obj.str.tok.text; + size_t val_len = list->objs[i].obj.assignment.value->obj.str.tok.len; + cfg->langs[cur_lang] = ledit_strndup(val, val_len); + } else if (str_array_equal("basic-keys", key, key_len)) { + if (list->objs[i].obj.assignment.value->type != OBJ_LIST) { + *errstr = print_fmt( + "%s: Invalid basic-keys setting in bindings configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } else if (basic_keys_init) { + *errstr = print_fmt( + "%s: Duplicate basic-keys setting in bindings configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + ast_list *slist = &list->objs[i].obj.assignment.value->obj.list; + for (size_t j = 0; j < slist->len; j++) { + line = slist->objs[j].tok.line; + line_offset = slist->objs[j].tok.line_offset; + if (slist->objs[j].type != OBJ_STATEMENT) { + *errstr = print_fmt( + "%s: Invalid basic-keys setting in bindings configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + basic_key_mapping m; + if (parse_basic_key_statement(&slist->objs[j].obj.statement, &m, filename, errstr)) + goto error; + push_basic_key_mapping(&cfg->basic_keys[0], m); + } + basic_keys_init = 1; + } else if (str_array_equal("command-keys", key, key_len)) { + if (list->objs[i].obj.assignment.value->type != OBJ_LIST) { + *errstr = print_fmt( + "%s: Invalid command-keys setting in bindings configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } else if (command_keys_init) { + *errstr = print_fmt( + "%s: Duplicate command-keys setting in bindings configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + ast_list *slist = &list->objs[i].obj.assignment.value->obj.list; + for (size_t j = 0; j < slist->len; j++) { + line = slist->objs[j].tok.line; + line_offset = slist->objs[j].tok.line_offset; + if (slist->objs[j].type != OBJ_STATEMENT) { + *errstr = print_fmt( + "%s: Invalid command-keys setting in bindings configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + command_key_mapping m; + if (parse_command_key_statement(&slist->objs[j].obj.statement, &m, filename, errstr)) + goto error; + push_command_key_mapping(&cfg->command_keys[0], m); + } + command_keys_init = 1; + } else if (str_array_equal("commands", key, key_len)) { + if (list->objs[i].obj.assignment.value->type != OBJ_LIST) { + *errstr = print_fmt( + "%s: Invalid commands setting in bindings configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } else if (commands_init) { + *errstr = print_fmt( + "%s: Duplicate commands setting in bindings configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + ast_list *slist = &list->objs[i].obj.assignment.value->obj.list; + for (size_t j = 0; j < slist->len; j++) { + line = slist->objs[j].tok.line; + line_offset = slist->objs[j].tok.line_offset; + if (slist->objs[j].type != OBJ_STATEMENT) { + *errstr = print_fmt( + "%s: Invalid commands setting in bindings configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + command_mapping m; + if (parse_command_statement(&slist->objs[j].obj.statement, &m, filename, errstr)) + goto error; + push_command_mapping(&cfg->cmds[0], m); + } + commands_init = 1; + } + } + + /* FIXME: the behavior here is a bit weird - if there is nothing other than a language + setting in the bindings configuration, all actual bindings are default, but the + associated language is different */ + if (!cfg->langs[cur_lang]) { + cfg->langs[cur_lang] = ledit_strdup(language_default); + } + /* FIXME: avoid calling strlen */ + if (!basic_keys_init) { + ledit_debug("No basic keys configured in bindings; loading defaults\n"); + basic_key_mapping m; + for (size_t i = 0; i < LENGTH(basic_keys_default); i++) { + m.cb = basic_key_cb_map_get_entry(basic_keys_default[i].func_name, strlen(basic_keys_default[i].func_name)); + if (!m.cb) { + *errstr = print_fmt("default config: Invalid basic key function name '%s'", basic_keys_default[i].func_name); + goto error; + } else if (!basic_key_cb_modemask_is_valid(m.cb, basic_keys_default[i].modes)) { + *errstr = print_fmt("default config: Function '%s' not defined for all given modes", basic_keys_default[i].func_name); + goto error; + } + m.text = basic_keys_default[i].text ? ledit_strdup(basic_keys_default[i].text) : NULL; + m.mods = basic_keys_default[i].mods; + m.modes = basic_keys_default[i].modes; + m.keysym = basic_keys_default[i].keysym; + push_basic_key_mapping(&cfg->basic_keys[0], m); + } + } + if (!command_keys_init) { + ledit_debug("No command keys configured in bindings; loading defaults\n"); + command_key_mapping m; + for (size_t i = 0; i < LENGTH(command_keys_default); i++) { + m.cb = command_key_cb_map_get_entry(command_keys_default[i].func_name, strlen(command_keys_default[i].func_name)); + if (!m.cb) { + *errstr = print_fmt("default config: Invalid command key function name '%s'", command_keys_default[i].func_name); + goto error; + } else if (!command_key_cb_modemask_is_valid(m.cb, command_keys_default[i].modes)) { + *errstr = print_fmt("default config: Function '%s' not defined for all given modes", command_keys_default[i].func_name); + goto error; + } + m.text = command_keys_default[i].text ? ledit_strdup(command_keys_default[i].text) : NULL; + m.mods = command_keys_default[i].mods; + m.modes = command_keys_default[i].modes; + m.keysym = command_keys_default[i].keysym; + push_command_key_mapping(&cfg->command_keys[0], m); + } + } + /* FIXME: guard against NULL text in default config! */ + if (!commands_init) { + ledit_debug("No commands configured in bindings; loading defaults\n"); + command_mapping m; + for (size_t i = 0; i < LENGTH(commands_default); i++) { + m.cb = command_cb_map_get_entry(commands_default[i].func_name, strlen(commands_default[i].func_name)); + if (!m.cb) { + *errstr = print_fmt("default config: Invalid command function name '%s'", commands_default[i].func_name); + goto error; + } + m.text = ledit_strdup(commands_default[i].text); + push_command_mapping(&cfg->cmds[0], m); + } + } + return 0; +/* FIXME: simplify error handling by doing more here */ +error: + return 1; +} + +static int +load_mapping(struct config *cfg, ast_list *list, char *filename, char **errstr) { + int key_mapping_init = 0, command_mapping_init = 0; + size_t cur_lang = cfg->num_langs - 1; /* FIXME: ensure no underflow */ + for (size_t i = 0; i < list->len; i++) { + size_t line = list->objs[i].tok.line; + size_t line_offset = list->objs[i].tok.line_offset; + if (list->objs[i].type != OBJ_ASSIGNMENT) { + *errstr = print_fmt( + "%s: Invalid statement in language mapping configuration " + "at list %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + char *key = list->objs[i].obj.assignment.tok.text; + size_t key_len = list->objs[i].obj.assignment.tok.len; + basic_key_array *bkmap = &cfg->basic_keys[cur_lang]; + command_key_array *ckmap = &cfg->command_keys[cur_lang]; + command_array *cmap = &cfg->cmds[cur_lang]; + if (str_array_equal("language", key, key_len)) { + if (list->objs[i].obj.assignment.value->type != OBJ_STRING) { + *errstr = print_fmt( + "%s: Invalid language setting in language mapping configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } else if (cfg->langs[cur_lang]) { + *errstr = print_fmt( + "%s: Duplicate language setting in language mapping configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + char *val = list->objs[i].obj.assignment.value->obj.str.tok.text; + size_t val_len = list->objs[i].obj.assignment.value->obj.str.tok.len; + cfg->langs[cur_lang] = ledit_strndup(val, val_len); + } else if (str_array_equal("key-mapping", key, key_len)) { + if (list->objs[i].obj.assignment.value->type != OBJ_LIST) { + *errstr = print_fmt( + "%s: Invalid key-mapping setting in language mapping configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } else if (key_mapping_init) { + *errstr = print_fmt( + "%s: Duplicate key-mapping setting in language mapping configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + ast_list *slist = &list->objs[i].obj.assignment.value->obj.list; + for (size_t j = 0; j < slist->len; j++) { + line = slist->objs[j].tok.line; + line_offset = slist->objs[j].tok.line_offset; + if (slist->objs[j].type != OBJ_STATEMENT) { + *errstr = print_fmt( + "%s: Invalid key-mapping setting in language mapping configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + ast_statement *st = &slist->objs[j].obj.statement; + if (!str_array_equal("map", st->func_tok.text, st->func_tok.len) || st->len != 2) { + *errstr = print_fmt( + "%s: Invalid key-mapping statement in language mapping configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + /* FIXME: any way to speed this up? I guess once the keys can be binary-searched... */ + for (size_t k = 0; k < bkmap->num_keys; k++) { + if (bkmap->keys[k].text && str_array_equal(bkmap->keys[k].text, st->args[1].text, st->args[1].len)) { + free(bkmap->keys[k].text); + bkmap->keys[k].text = ledit_strndup(st->args[0].text, st->args[0].len); + } + } + for (size_t k = 0; k < ckmap->num_keys; k++) { + if (ckmap->keys[k].text && str_array_equal(ckmap->keys[k].text, st->args[1].text, st->args[1].len)) { + free(ckmap->keys[k].text); + ckmap->keys[k].text = ledit_strndup(st->args[0].text, st->args[0].len); + } + } + } + key_mapping_init = 1; + } else if (str_array_equal("command-mapping", key, key_len)) { + if (list->objs[i].obj.assignment.value->type != OBJ_LIST) { + *errstr = print_fmt( + "%s: Invalid command-mapping setting in language mapping configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } else if (command_mapping_init) { + *errstr = print_fmt( + "%s: Duplicate command-mapping setting in language mapping configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + ast_list *slist = &list->objs[i].obj.assignment.value->obj.list; + for (size_t j = 0; j < slist->len; j++) { + line = slist->objs[j].tok.line; + line_offset = slist->objs[j].tok.line_offset; + if (slist->objs[j].type != OBJ_STATEMENT) { + *errstr = print_fmt( + "%s: Invalid command-mapping setting in language mapping configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + ast_statement *st = &slist->objs[j].obj.statement; + if (!str_array_equal("map", st->func_tok.text, st->func_tok.len) || st->len != 2) { + *errstr = print_fmt( + "%s: Invalid command-mapping statement in language mapping configuration " + "at line %zu, offset %zu", filename, line, line_offset + ); + goto error; + } + for (size_t k = 0; k < cmap->num_cmds; k++) { + if (str_array_equal(cmap->cmds[k].text, st->args[1].text, st->args[1].len)) { + free(cmap->cmds[k].text); + cmap->cmds[k].text = ledit_strndup(st->args[0].text, st->args[0].len); + } + } + } + command_mapping_init = 1; + } + } + if (!cfg->langs[cur_lang]) { + /* FIXME: pass actual beginning line and offset so this doesn't have to + use the line and offset of the first list element */ + if (list->len > 0) { + *errstr = print_fmt( + "%s: Missing language setting in language mapping configuration " + "at line %zu, offset %zu", filename, list->objs[0].tok.line, list->objs[0].tok.line_offset + ); + } else { + *errstr = print_fmt("%s: Missing language setting in language mapping configuration", filename); + } + goto error; + } + return 0; +error: + return 1; +} + +static void +append_mapping(struct config *cfg) { + push_lang(cfg); + ledit_assert(cfg->num_langs > 1); + + /* first duplicate original mappings before replacing the text */ + /* FIXME: optimize this to avoid useless reallocations */ + size_t cur_lang = cfg->num_langs - 1; + basic_key_array *arr1 = &cfg->basic_keys[cur_lang]; + arr1->num_keys = arr1->alloc_keys = cfg->basic_keys[0].num_keys; + arr1->keys = ledit_reallocarray(NULL, arr1->num_keys, sizeof(basic_key_mapping)); + memmove(arr1->keys, cfg->basic_keys[0].keys, arr1->num_keys * sizeof(basic_key_mapping)); + for (size_t i = 0; i < arr1->num_keys; i++) { + if (arr1->keys[i].text) + arr1->keys[i].text = ledit_strdup(arr1->keys[i].text); + } + + + command_key_array *arr2 = &cfg->command_keys[cur_lang]; + arr2->num_keys = arr2->alloc_keys = cfg->command_keys[0].num_keys; + arr2->keys = ledit_reallocarray(NULL, arr2->num_keys, sizeof(command_key_mapping)); + memmove(arr2->keys, cfg->command_keys[0].keys, arr2->num_keys * sizeof(command_key_mapping)); + for (size_t i = 0; i < arr2->num_keys; i++) { + if (arr2->keys[i].text) + arr2->keys[i].text = ledit_strdup(arr2->keys[i].text); + } + + command_array *arr3 = &cfg->cmds[cur_lang]; + arr3->num_cmds = arr3->alloc_cmds = cfg->cmds[0].num_cmds; + arr3->cmds = ledit_reallocarray(NULL, arr3->num_cmds, sizeof(command_mapping)); + memmove(arr3->cmds, cfg->cmds[0].cmds, arr3->num_cmds * sizeof(command_mapping)); + for (size_t i = 0; i < arr3->num_cmds; i++) { + arr3->cmds[i].text = ledit_strdup(arr3->cmds[i].text); + } +} + +#ifdef LEDIT_DEBUG +static void +debug_print_obj(ast_obj *obj, int shiftwidth) { + for (int i = 0; i < shiftwidth; i++) { + fprintf(stderr, " "); + } + switch (obj->type) { + case OBJ_STRING: + fprintf(stderr, "STRING: %.*s\n", (int)obj->obj.str.tok.len, obj->obj.str.tok.text); + break; + case OBJ_STATEMENT: + fprintf(stderr, "STATEMENT: %.*s ", (int)obj->obj.statement.func_tok.len, obj->obj.statement.func_tok.text); + for (size_t i = 0; i < obj->obj.statement.len; i++) { + fprintf(stderr, "%.*s ", (int)obj->obj.statement.args[i].len, obj->obj.statement.args[i].text); + } + fprintf(stderr, "\n"); + break; + case OBJ_ASSIGNMENT: + fprintf(stderr, "ASSIGNMENT: %.*s =\n", (int)obj->obj.assignment.tok.len, obj->obj.assignment.tok.text); + debug_print_obj(obj->obj.assignment.value, shiftwidth + 4); + break; + case OBJ_LIST: + fprintf(stderr, "LIST:\n"); + for (size_t i = 0; i < obj->obj.list.len; i++) { + debug_print_obj(&obj->obj.list.objs[i], shiftwidth + 4); + } + break; + } +} +#endif + +/* WARNING: *errstr must be freed! */ +int +config_loadfile(ledit_common *common, char *filename, char **errstr) { + #ifdef LEDIT_DEBUG + struct timespec now, elapsed, last; + clock_gettime(CLOCK_MONOTONIC, &last); + #endif + size_t len; + *errstr = NULL; + ast_list list = {.objs = NULL, .len = 0, .cap = 0}; + char *file_contents = NULL; + if (filename) { + file_contents = load_file(filename, &len, errstr); + if (!file_contents) return 1; + #ifdef LEDIT_DEBUG + clock_gettime(CLOCK_MONOTONIC, &now); + ledit_timespecsub(&now, &last, &elapsed); + ledit_debug_fmt( + "Time to load config file: %lld seconds, %ld nanoseconds\n", + (long long)elapsed.tv_sec, elapsed.tv_nsec + ); + last = now; + #endif + /* start at line 1 to make error messages more useful */ + struct lexstate s = {file_contents, len, 0, 1, 0}; + if (parse_list(&s, &list, 1, filename, errstr)) { + free(file_contents); + return 1; + } + #ifdef LEDIT_DEBUG + clock_gettime(CLOCK_MONOTONIC, &now); + ledit_timespecsub(&now, &last, &elapsed); + ledit_debug_fmt( + "Time to parse config file: %lld seconds, %ld nanoseconds\n", + (long long)elapsed.tv_sec, elapsed.tv_nsec + ); + #endif + } + + #ifdef LEDIT_DEBUG + clock_gettime(CLOCK_MONOTONIC, &last); + for (size_t i = 0; i < list.len; i++) { + debug_print_obj(&list.objs[i], 0); + } + clock_gettime(CLOCK_MONOTONIC, &now); + ledit_timespecsub(&now, &last, &elapsed); + ledit_debug_fmt( + "Time to print useless information: %lld seconds, %ld nanoseconds\n", + (long long)elapsed.tv_sec, elapsed.tv_nsec + ); + clock_gettime(CLOCK_MONOTONIC, &last); + #endif + + struct config cfg = {NULL}; + int theme_init = 0, bindings_init = 0, mappings_init = 0; + ast_assignment *assignment; + for (size_t i = 0; i < list.len; i++) { + switch (list.objs[i].type) { + case OBJ_ASSIGNMENT: + assignment = &list.objs[i].obj.assignment; + if (str_array_equal("theme", assignment->tok.text, assignment->tok.len)) { + if (theme_init) { + *errstr = print_fmt( + "%s: Duplicate theme definition at line %zu, offset %zu", + filename, assignment->tok.line, assignment->tok.line_offset + ); + goto error; + } else if (assignment->value->type != OBJ_LIST) { + *errstr = print_fmt( + "%s: Invalid theme definition at line %zu, offset %zu", + filename, assignment->tok.line, assignment->tok.line_offset + ); + goto error; + } + cfg.theme = load_theme(common, &assignment->value->obj.list, filename, errstr); + if (!cfg.theme) + goto error; + theme_init = 1; + } else if (str_array_equal("bindings", assignment->tok.text, assignment->tok.len)) { + if (bindings_init) { + *errstr = print_fmt( + "%s: Duplicate definition of bindings at line %zu, offset %zu", + filename, assignment->tok.line, assignment->tok.line_offset + ); + goto error; + } + push_lang(&cfg); + if (assignment->value->type != OBJ_LIST) { + *errstr = print_fmt( + "%s: Invalid definition of bindings at line %zu, offset %zu", + filename, assignment->tok.line, assignment->tok.line_offset + ); + goto error; + } + if (load_bindings(&cfg, &assignment->value->obj.list, filename, errstr)) + goto error; + bindings_init = 1; + } else if (str_array_equal("language-mapping", assignment->tok.text, assignment->tok.len)) { + if (cfg.num_langs == 0) { + ledit_debug("No key/command bindings configured; loading defaults\n"); + push_lang(&cfg); + /* load default config */ + ast_list empty_list = {.objs = NULL, .len = 0, .cap = 0}; + /* shouldn't usually happen */ + if (load_bindings(&cfg, &empty_list, filename, errstr)) + goto error; + bindings_init = 1; + } else if (assignment->value->type != OBJ_LIST) { + *errstr = print_fmt( + "%s: Invalid definition of language mapping at line %zu, offset %zu", + filename, assignment->tok.line, assignment->tok.line_offset + ); + goto error; + } + + append_mapping(&cfg); + + if (load_mapping(&cfg, &assignment->value->obj.list, filename, errstr)) + goto error; + mappings_init = 1; + } else { + *errstr = print_fmt( + "%s: Invalid assignment at line %zu, offset %zu", + filename, assignment->tok.line, assignment->tok.line_offset + ); + goto error; + } + break; + default: + *errstr = print_fmt( + "%s: Invalid statement at line %zu, offset %zu", + filename, list.objs[i].tok.line, list.objs[i].tok.line_offset + ); + goto error; + } + } + if (!theme_init) { + ledit_debug("No theme configured; loading defaults\n"); + cfg.theme = load_theme(common, NULL, NULL, errstr); + if (!cfg.theme) + goto error; + } + if (!bindings_init) { + ledit_debug("No key/command bindings configured; loading defaults\n"); + push_lang(&cfg); + /* load default config */ + ast_list empty_list = {.objs = NULL, .len = 0, .cap = 0}; + /* shouldn't usually happen */ + if (load_bindings(&cfg, &empty_list, NULL, errstr)) + goto error; + } + if (!mappings_init) { + ledit_debug("No key/command mappings configured; loading defaults\n"); + for (size_t i = 0; i < LENGTH(mappings_default); i++) { + append_mapping(&cfg); + size_t cur_lang = cfg.num_langs - 1; + cfg.langs[cur_lang] = ledit_strdup(mappings_default[i].lang); + basic_key_array *bkmap = &cfg.basic_keys[cur_lang]; + command_key_array *ckmap = &cfg.command_keys[cur_lang]; + command_array *cmap = &cfg.cmds[cur_lang]; + /* FIXME: any way to speed this up? I guess once the keys can be binary-searched... */ + /* FIXME: duplicated code from above */ + for (size_t j = 0; j < mappings_default[i].keys_len; j++) { + for (size_t k = 0; k < bkmap->num_keys; k++) { + if (bkmap->keys[k].text && !strcmp(bkmap->keys[k].text, mappings_default[i].keys[j].to)) { + free(bkmap->keys[k].text); + bkmap->keys[k].text = ledit_strdup(mappings_default[i].keys[j].from); + } + } + for (size_t k = 0; k < ckmap->num_keys; k++) { + if (ckmap->keys[k].text && !strcmp(ckmap->keys[k].text, mappings_default[i].keys[j].to)) { + free(ckmap->keys[k].text); + ckmap->keys[k].text = ledit_strdup(mappings_default[i].keys[j].from); + } + } + } + for (size_t j = 0; j < mappings_default[i].cmds_len; j++) { + for (size_t k = 0; k < cmap->num_cmds; k++) { + if (!strcmp(cmap->cmds[k].text, mappings_default[i].keys[j].to)) { + free(cmap->cmds[k].text); + cmap->cmds[k].text = ledit_strdup(mappings_default[i].keys[j].from); + } + } + } + } + } + destroy_list(&list); + free(file_contents); + config_destroy(common, &config); + config = cfg; + #ifdef LEDIT_DEBUG + clock_gettime(CLOCK_MONOTONIC, &now); + ledit_timespecsub(&now, &last, &elapsed); + ledit_debug_fmt( + "Time to interpret config file: %lld seconds, %ld nanoseconds\n", + (long long)elapsed.tv_sec, elapsed.tv_nsec + ); + #endif + return 0; +error: + destroy_list(&list); + free(file_contents); + config_destroy(common, &cfg); + return 1; +} + +ledit_theme * +config_get_theme(void) { + ledit_assert(config.theme != NULL); + return config.theme; +} + +basic_key_array * +config_get_basic_keys(size_t lang_index) { + ledit_assert(lang_index < config.num_langs); + return &config.basic_keys[lang_index]; +} + +command_key_array * +config_get_command_keys(size_t lang_index) { + ledit_assert(lang_index < config.num_langs); + return &config.command_keys[lang_index]; +} + +command_array * +config_get_commands(size_t lang_index) { + ledit_assert(lang_index < config.num_langs); + return &config.cmds[lang_index]; +} + +int +config_get_language_index(char *lang, size_t *idx_ret) { + for (size_t i = 0; i < config.num_langs; i++) { + if (!strcmp(lang, config.langs[i])) { + *idx_ret = i; + return 0; + } + } + return 1; +} diff --git a/configparser.h b/configparser.h @@ -0,0 +1,80 @@ +#ifndef _CONFIGPARSER_H_ +#define _CONFIGPARSER_H_ + +#include "common.h" +#include "uglycrap.h" +#include "keys_command.h" +#include "keys_basic.h" + +typedef struct { + int scrollbar_width; + int scrollbar_step; + int text_size; + XftColor text_fg; + XftColor text_bg; + XftColor cursor_fg; + XftColor cursor_bg; + XftColor selection_fg; + XftColor selection_bg; + XftColor bar_fg; + XftColor bar_bg; + XftColor bar_cursor; + XftColor scrollbar_fg; + XftColor scrollbar_bg; + const char *text_font; +} ledit_theme; + +typedef struct { + char *text; /* for keys that correspond with text */ + unsigned int mods; /* modifier mask */ + KeySym keysym; /* for other keys, e.g. arrow keys */ + ledit_mode modes; /* modes in which this keybinding is functional */ + basic_key_cb *cb; /* callback */ +} basic_key_mapping; + +typedef struct { + char *text; /* for keys that correspond with text */ + unsigned int mods; /* modifier mask */ + KeySym keysym; /* for other keys, e.g. arrow keys */ + command_mode modes; /* substitute, etc. */ + command_key_cb *cb; /* callback */ +} command_key_mapping; + +typedef struct { + char *text; /* text typed to call command */ + command_cb *cb; /* callback */ +} command_mapping; + +typedef struct { + basic_key_mapping *keys; + size_t num_keys; + size_t alloc_keys; +} basic_key_array; + +typedef struct { + command_key_mapping *keys; + size_t num_keys; + size_t alloc_keys; +} command_key_array; + +typedef struct { + command_mapping *cmds; + size_t num_cmds; + size_t alloc_cmds; +} command_array; + +/* Note: The config is initialized immediately when ledit starts, so these + * should not return NULL (unless an invalid language index is given), but + * it's still better to check just in case. */ + +/* Note: The returned pointers are invalidated if the config is reloaded. */ + +ledit_theme *config_get_theme(void); +basic_key_array *config_get_basic_keys(size_t lang_index); +command_key_array *config_get_command_keys(size_t lang_index); +command_array *config_get_commands(size_t lang_index); +int config_get_language_index(char *lang, size_t *idx_ret); +int config_loadfile(ledit_common *common, char *filename, char **errstr); +void config_cleanup(ledit_common *common); + +#endif diff --git a/keys.c b/keys.c @@ -10,22 +10,8 @@ #include "memory.h" #include "common.h" #include "txtbuf.h" -#include "theme.h" #include "window.h" #include "keys.h" -#include "keys_config.h" - -KEY_LANGS; - -int -get_language_index(char *lang) { - for (size_t i = 0; i < LENGTH(key_langs); i++) { - if (!strcmp(key_langs[i], lang)) { - return i; - } - } - return -1; -} /* FIXME: Does this break anything? */ /*static unsigned int importantmod = ShiftMask | ControlMask | Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask;*/ diff --git a/keys.h b/keys.h @@ -19,4 +19,51 @@ void preprocess_key( char *buf_ret, int buf_size, int *buf_len_ret ); +/* FIXME: documentation */ +#define GEN_CB_MAP_HELPERS(name, typename, cmp_entry) \ + \ +static int name##_sorted = 0; \ + \ +/* \ + * IMPORTANT: The text passed to *_get_entry may not be nul-terminated, \ + * so a txtbuf has to be used for the bsearch comparison helper. \ + */ \ + \ +static int \ +name##_search_helper(const void *keyv, const void *entryv) { \ + txtbuf *key = (txtbuf *)keyv; \ + typename *entry = (typename *)entryv; \ + int ret = strncmp(key->text, entry->cmp_entry, key->len); \ + if (ret == 0) { \ + if (entry->cmp_entry[key->len] == '\0') \ + return 0; \ + else \ + return -1; \ + } \ + return ret; \ +} \ + \ +static int \ +name##_sort_helper(const void *entry1v, const void *entry2v) { \ + typename *entry1 = (typename *)entry1v; \ + typename *entry2 = (typename *)entry2v; \ + return strcmp(entry1->cmp_entry, entry2->cmp_entry); \ +} \ + \ +typename * \ +name##_get_entry(char *text, size_t len) { \ + /* just in case */ \ + if (!name##_sorted) { \ + qsort( \ + name, LENGTH(name), \ + sizeof(name[0]), &name##_sort_helper); \ + name##_sorted = 1; \ + } \ + txtbuf tmp = {.len = len, .cap = len, .text = text}; \ + return bsearch( \ + &tmp, name, LENGTH(name), \ + sizeof(name[0]), &name##_search_helper \ + ); \ +} + #endif diff --git a/keys_basic.c b/keys_basic.c @@ -12,6 +12,8 @@ they are now not allowed at all */ /* FIXME: a lot of error checking in the individual functions may be redundant now that more checking is done beforehand for the allowed keys */ +/* FIXME: sort functions a bit better, maybe split file */ +/* FIXME: documentation */ #include <stdio.h> #include <stdlib.h> @@ -29,17 +31,216 @@ #include "txtbuf.h" #include "undo.h" #include "cache.h" -#include "theme.h" #include "window.h" #include "buffer.h" #include "view.h" #include "search.h" #include "keys.h" -#include "keys_config.h" #include "keys_basic.h" #include "keys_command.h" -#include "keys_basic_config.h" +#include "configparser.h" + +/************************************************************************* + * Declarations for all functions that can be used in the configuration. * + *************************************************************************/ + +static struct action backspace(ledit_view *view, char *text, size_t len); +static struct action cursor_left(ledit_view *view, char *text, size_t len); +static struct action cursor_right(ledit_view *view, char *text, size_t len); +static struct action cursor_up(ledit_view *view, char *text, size_t len); +static struct action cursor_down(ledit_view *view, char *text, size_t len); +static struct action return_key(ledit_view *view, char *text, size_t len); +static struct action delete_key(ledit_view *view, char *text, size_t len); +static struct action escape_key(ledit_view *view, char *text, size_t len); +static struct action enter_insert(ledit_view *view, char *text, size_t len); +static struct action cursor_to_beginning(ledit_view *view, char *text, size_t len); +static struct action key_0(ledit_view *view, char *text, size_t len); +static struct action push_0(ledit_view *view, char *text, size_t len); +static struct action push_1(ledit_view *view, char *text, size_t len); +static struct action push_2(ledit_view *view, char *text, size_t len); +static struct action push_3(ledit_view *view, char *text, size_t len); +static struct action push_4(ledit_view *view, char *text, size_t len); +static struct action push_5(ledit_view *view, char *text, size_t len); +static struct action push_6(ledit_view *view, char *text, size_t len); +static struct action push_7(ledit_view *view, char *text, size_t len); +static struct action push_8(ledit_view *view, char *text, size_t len); +static struct action push_9(ledit_view *view, char *text, size_t len); +static struct action delete(ledit_view *view, char *text, size_t len); +static struct action enter_visual(ledit_view *view, char *text, size_t len); +static struct action switch_selection_end(ledit_view *view, char *text, size_t len); +static struct action clipcopy(ledit_view *view, char *text, size_t len); +static struct action clippaste(ledit_view *view, char *text, size_t len); +static struct action show_line(ledit_view *view, char *text, size_t len); +static struct action enter_commandedit(ledit_view *view, char *text, size_t len); +static struct action enter_searchedit_backward(ledit_view *view, char *text, size_t len); +static struct action enter_searchedit_forward(ledit_view *view, char *text, size_t len); +static struct action key_search_next(ledit_view *view, char *text, size_t len); +static struct action key_search_prev(ledit_view *view, char *text, size_t len); +static struct action undo(ledit_view *view, char *text, size_t len); +static struct action redo(ledit_view *view, char *text, size_t len); +static struct action insert_mode_insert_text(ledit_view *view, char *text, size_t len); +static struct action repeat_command(ledit_view *view, char *text, size_t len); +static struct action screen_up(ledit_view *view, char *text, size_t len); +static struct action screen_down(ledit_view *view, char *text, size_t len); +static struct action scroll_with_cursor_up(ledit_view *view, char *text, size_t len); +static struct action scroll_with_cursor_down(ledit_view *view, char *text, size_t len); +static struct action scroll_lines_up(ledit_view *view, char *text, size_t len); +static struct action scroll_lines_down(ledit_view *view, char *text, size_t len); +static struct action move_to_line(ledit_view *view, char *text, size_t len); +static struct action paste_normal(ledit_view *view, char *text, size_t len); +static struct action paste_normal_backwards(ledit_view *view, char *text, size_t len); +static struct action change(ledit_view *view, char *text, size_t len); +static struct action move_to_eol(ledit_view *view, char *text, size_t len); +static struct action mark_line(ledit_view *view, char *text, size_t len); +static struct action jump_to_mark(ledit_view *view, char *text, size_t len); +static struct action next_word(ledit_view *view, char *text, size_t len); +static struct action next_word_end(ledit_view *view, char *text, size_t len); +static struct action next_bigword(ledit_view *view, char *text, size_t len); +static struct action next_bigword_end(ledit_view *view, char *text, size_t len); +static struct action prev_word(ledit_view *view, char *text, size_t len); +static struct action prev_bigword(ledit_view *view, char *text, size_t len); +static struct action append_after_eol(ledit_view *view, char *text, size_t len); +static struct action append_after_cursor(ledit_view *view, char *text, size_t len); +static struct action append_line_above(ledit_view *view, char *text, size_t len); +static struct action append_line_below(ledit_view *view, char *text, size_t len); +static struct action find_next_char_forwards(ledit_view *view, char *text, size_t len); +static struct action find_next_char_backwards(ledit_view *view, char *text, size_t len); +static struct action find_char_forwards(ledit_view *view, char *text, size_t len); +static struct action find_char_backwards(ledit_view *view, char *text, size_t len); +static struct action change_to_eol(ledit_view *view, char *text, size_t len); +static struct action delete_to_eol(ledit_view *view, char *text, size_t len); +static struct action delete_chars_forwards(ledit_view *view, char *text, size_t len); +static struct action delete_chars_backwards(ledit_view *view, char *text, size_t len); +static struct action yank(ledit_view *view, char *text, size_t len); +static struct action yank_lines(ledit_view *view, char *text, size_t len); +static struct action replace(ledit_view *view, char *text, size_t len); +static struct action cursor_to_first_non_ws(ledit_view *view, char *text, size_t len); +static struct action join_lines(ledit_view *view, char *text, size_t len); +static struct action insert_at_beginning(ledit_view *view, char *text, size_t len); +static struct action toggle_hard_line_based(ledit_view *view, char *text, size_t len); + +/*********************************************** + * String-function mapping for config parsing. * + ***********************************************/ + +/* FIXME: delete-backwards, delete-forwards should be renamed; + *key functions should be renamed (they're very vague) */ + +typedef enum { + KEY_FLAG_NONE = 0, + KEY_FLAG_JUMP_TO_CURSOR = 1, + KEY_FLAG_LOCK_ALLOWED = 2 +} basic_key_cb_flags; + +typedef struct action (*basic_key_cb_func)(ledit_view *, char *, size_t); + +struct basic_key_cb { + char *text; + basic_key_cb_func func; + basic_key_cb_flags flags; + ledit_mode allowed_modes; +}; + +int +basic_key_cb_modemask_is_valid(basic_key_cb *cb, ledit_mode modes) { + return (~cb->allowed_modes & modes) == 0; +} + +/* FIXME: make functions work in more modes (e.g. cursor-to-first-non-whitespace) */ +static struct basic_key_cb basic_key_cb_map[] = { + {"append-after-cursor", &append_after_cursor, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"append-after-eol", &append_after_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"append-line-above", &append_line_above, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"append-line-below", &append_line_below, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"backspace", &backspace, KEY_FLAG_JUMP_TO_CURSOR, INSERT}, + {"change", &change, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL}, + {"change-to-eol", &change_to_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"clipboard-copy", &clipcopy, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL}, + {"clipboard-paste", &clippaste, KEY_FLAG_JUMP_TO_CURSOR, INSERT}, + {"cursor-down", &cursor_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL}, + {"cursor-left", &cursor_left, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL}, + {"cursor-right", &cursor_right, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL}, + {"cursor-to-beginning", &cursor_to_beginning, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"cursor-to-first-non-whitespace", &cursor_to_first_non_ws, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"cursor-up", &cursor_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL}, + {"delete", &delete, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL}, + {"delete-backwards", &delete_chars_backwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"delete-forwards", &delete_chars_forwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"delete-key", &delete_key, KEY_FLAG_JUMP_TO_CURSOR, INSERT}, + {"delete-to-eol", &delete_to_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"enter-commandedit", &enter_commandedit, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"enter-insert", &enter_insert, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL}, + {"enter-searchedit-backwards", &enter_searchedit_backward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"enter-searchedit-forwards", &enter_searchedit_forward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"enter-visual", &enter_visual, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"escape-key", &escape_key, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, + {"find-char-backwards", &find_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"find-char-forwards", &find_char_forwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"find-next-char-backwards", &find_next_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"find-next-char-forwards", &find_next_char_forwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"insert-at-beginning", &insert_at_beginning, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"insert-text", &insert_mode_insert_text, KEY_FLAG_JUMP_TO_CURSOR, INSERT}, + {"join-lines", &join_lines, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"jump-to-mark", &jump_to_mark, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"key-0", &key_0, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"mark-line", &mark_line, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"move-to-eol", &move_to_eol, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"move-to-line", &move_to_line, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"next-bigword", &next_bigword, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"next-bigword-end", &next_bigword_end, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"next-word", &next_word, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"next-word-end", &next_word_end, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"paste-normal", &paste_normal, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"paste-normal-backwards", &paste_normal_backwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"previous-bigword", &prev_bigword, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"previous-word", &prev_word, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"push-0", &push_0, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"push-1", &push_1, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"push-2", &push_2, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"push-3", &push_3, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"push-4", &push_4, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"push-5", &push_5, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"push-6", &push_6, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"push-7", &push_7, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"push-8", &push_8, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"push-9", &push_9, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"redo", &redo, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"repeat-command", &repeat_command, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"replace", &replace, KEY_FLAG_JUMP_TO_CURSOR, NORMAL}, + {"return-key", &return_key, KEY_FLAG_JUMP_TO_CURSOR, INSERT}, + {"screen-down", &screen_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"screen-up", &screen_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"scroll-lines-down", &scroll_lines_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"scroll-lines-up", &scroll_lines_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"scroll-with-cursor-down", &scroll_with_cursor_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"scroll-with-cursor-up", &scroll_with_cursor_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"search-next", &key_search_next, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"search-previous", &key_search_prev, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, + {"show-line", &show_line, KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"switch-selection-end", &switch_selection_end, KEY_FLAG_JUMP_TO_CURSOR, VISUAL}, + {"toggle-hard-line-based", &toggle_hard_line_based, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, /* FIXME: also in INSERT */ + {"undo", &undo, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, + {"yank", &yank, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, + {"yank-lines", &yank_lines, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL}, +}; + +GEN_CB_MAP_HELPERS(basic_key_cb_map, basic_key_cb, text) + +/*************************************************** + * General global variables and utility functions. * + ***************************************************/ + +enum key_type { + KEY_INVALID = 0, + KEY_MOTION_CHAR = 4, + KEY_MOTION_LINE = 8, + KEY_MOTION = 4|8, + KEY_MOTIONALLOWED = 16, + KEY_NUMBER = 32, + KEY_NUMBERALLOWED = 64, + KEY_ANY = 0xFF +}; /* note: this is supposed to be global for all views/buffers */ int paste_buffer_line_based = 0; @@ -310,6 +511,10 @@ static void discard_repetition_stack(void) { if (repetition_stack.replaying) return; + for (size_t i = 0; i < repetition_stack.tmp_len; i++) { + free(repetition_stack.tmp_stack[i].key_text); + repetition_stack.tmp_stack[i].key_text = NULL; + } repetition_stack.tmp_len = 0; } @@ -323,6 +528,10 @@ finalize_repetition_stack(void) { if (repetition_stack.replaying) return; size_t tmp; + for (size_t i = 0; i < repetition_stack.len; i++) { + free(repetition_stack.stack[i].key_text); + repetition_stack.stack[i].key_text = NULL; + } struct repetition_stack_elem *tmpstack; repetition_stack.len = repetition_stack.tmp_len; repetition_stack.tmp_len = 0; @@ -485,6 +694,10 @@ delete_selection(ledit_view *view) { return 0; } +/******************************************** + * Functions that were declared at the top. * + ********************************************/ + /* FIXME: should these delete characters or graphemes? */ static struct action delete_chars_forwards(ledit_view *view, char *text, size_t len) { @@ -549,6 +762,7 @@ delete_chars_backwards(ledit_view *view, char *text, size_t len) { /* used to set cursor - I guess this is sort of a hack */ static void push_undo_empty_insert(ledit_view *view, size_t line, size_t index, int start_group) { + /* WARNING: Don't abuse txtbuf like this unless you're stupid like me. */ txtbuf ins_buf = {.text = "", .len = 0, .cap = 0}; ledit_range ins_range = {.line1 = line, .byte1 = index, .line2 = line, .byte2 = index}; ledit_range cur_range = {.line1 = line, .byte1 = index, .line2 = line, .byte2 = index}; @@ -1271,6 +1485,18 @@ paste_normal_backwards(ledit_view *view, char *text, size_t len) { return (struct action){ACTION_NONE, NULL}; } +static struct action +key_0(ledit_view *view, char *text, size_t len) { + struct key_stack_elem *e = peek_key_stack(); + if (!e || (e->key & KEY_MOTIONALLOWED)) { + return cursor_to_beginning(view, text, len); + } else if (e->key & KEY_NUMBER) { + return push_0(view, text, len); + } else { + return err_invalid_key(view); + } +} + static void push_num(ledit_view *view, int num) { struct key_stack_elem *e = peek_key_stack(); @@ -2242,10 +2468,9 @@ toggle_hard_line_based(ledit_view *view, char *text, size_t len) { } static struct action -handle_key(ledit_view *view, char *key_text, size_t len, KeySym sym, unsigned int key_state, int lang_index, int *found, enum key_type *type) { - struct key *cur_keys = keys[lang_index].keys; - int num_keys = keys[lang_index].num_keys; - struct key_stack_elem *e = peek_key_stack(); +handle_key(ledit_view *view, char *key_text, size_t len, KeySym sym, unsigned int key_state, size_t lang_index, int *found, basic_key_cb_flags *flags) { + basic_key_array *cur_keys = config_get_basic_keys(lang_index); + size_t num_keys = cur_keys->num_keys; /* FIXME: check if control chars in text */ /* FIXME: this is a bit of a hack because it's hardcoded */ if (grab_char_cb && sym == XK_Escape) { @@ -2253,31 +2478,32 @@ handle_key(ledit_view *view, char *key_text, size_t len, KeySym sym, unsigned in return (struct action){ACTION_NONE, NULL}; } else if (len > 0 && grab_char_cb) { *found = 1; - *type = 0; return grab_char_cb(view, key_text, len); } *found = 0; - for (int i = 0; i < num_keys; i++) { - if (cur_keys[i].text) { + for (size_t i = 0; i < num_keys; i++) { + if (cur_keys->keys[i].text) { if (len > 0 && - (cur_keys[i].modes & view->mode) && - (cur_keys[i].prev_keys == KEY_ANY || (!e && (cur_keys[i].prev_keys & KEY_NONE)) || (e && (e->key & cur_keys[i].prev_keys))) && - ((!strncmp(cur_keys[i].text, key_text, len) && - match_key(cur_keys[i].mods, key_state & ~ShiftMask)) || - cur_keys[i].text[0] == '\0')) { + (cur_keys->keys[i].modes & view->mode) && + ((!strncmp(cur_keys->keys[i].text, key_text, len) && + match_key(cur_keys->keys[i].mods, key_state & ~ShiftMask)) || + cur_keys->keys[i].text[0] == '\0')) { /* FIXME: seems a bit hacky to remove shift, but it is needed to make keys that use shift match */ - *type = cur_keys[i].type; + *flags = cur_keys->keys[i].cb->flags; *found = 1; - return cur_keys[i].func(view, key_text, len); + if (!(*flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text) + return view_locked_error(view); + return cur_keys->keys[i].cb->func(view, key_text, len); } - } else if ((cur_keys[i].modes & view->mode) && - cur_keys[i].keysym == sym && - (cur_keys[i].prev_keys == KEY_ANY || (!e && (cur_keys[i].prev_keys & KEY_NONE)) || (e && (e->key & cur_keys[i].prev_keys))) && - match_key(cur_keys[i].mods, key_state)) { - *type = cur_keys[i].type; + } else if ((cur_keys->keys[i].modes & view->mode) && + cur_keys->keys[i].keysym == sym && + match_key(cur_keys->keys[i].mods, key_state)) { + *flags = cur_keys->keys[i].cb->flags; *found = 1; - return cur_keys[i].func(view, key_text, len); + if (!(*flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text) + return view_locked_error(view); + return cur_keys->keys[i].cb->func(view, key_text, len); } } return (struct action){ACTION_NONE, NULL}; @@ -2299,13 +2525,13 @@ repeat_command(ledit_view *view, char *text, size_t len) { return (struct action){ACTION_NONE, NULL}; } int found; - enum key_type type; + basic_key_cb_flags flags; repetition_stack.replaying = 1; for (int i = 0; i < num; i++) { unwind_repetition_stack(); struct repetition_stack_elem *e = get_cur_repetition_stack_elem(); while (e) { - (void)handle_key(view, e->key_text, e->len, e->sym, e->key_state, e->lang_index, &found, &type); + (void)handle_key(view, e->key_text, e->len, e->sym, e->key_state, e->lang_index, &found, &flags); advance_repetition_stack(); e = get_cur_repetition_stack_elem(); } @@ -2338,14 +2564,14 @@ basic_key_handler(ledit_view *view, XEvent *event, int lang_index) { int found = 0; int msg_shown = view->window->message_shown; view->window->message_shown = 0; /* FIXME: this is hacky */ - enum key_type type; - struct action act = handle_key(view, buf, (size_t)n, sym, key_state, lang_index, &found, &type); + basic_key_cb_flags flags; + struct action act = handle_key(view, buf, (size_t)n, sym, key_state, lang_index, &found, &flags); if (found && n > 0 && !view->window->message_shown) window_hide_message(view->window); else if (msg_shown) view->window->message_shown = msg_shown; - if (found && (type & KEY_ENSURE_CURSOR_SHOWN)) + if (found && (flags & KEY_FLAG_JUMP_TO_CURSOR)) view_ensure_cursor_shown(view); if (!found && n > 0) { window_show_message(view->window, "Invalid key", -1); diff --git a/keys_basic.h b/keys_basic.h @@ -4,6 +4,11 @@ #include <X11/Xlib.h> #include "view.h" +typedef struct basic_key_cb basic_key_cb; + +basic_key_cb *basic_key_cb_map_get_entry(char *text, size_t len); +int basic_key_cb_modemask_is_valid(basic_key_cb *cb, ledit_mode modes); + /* perform cleanup of global data */ void basic_key_cleanup(void); struct action basic_key_handler(ledit_view *view, XEvent *event, int lang_index); diff --git a/keys_basic_config.h b/keys_basic_config.h @@ -1,457 +0,0 @@ -/* - * These are all the regular keys used in normal, visual, and insert mode. - */ - -/* - * Note: The key types are currently very inconsistent and don't always make - * sense. This will hopefully be fixed sometime. (FIXME) - */ - -enum key_type { - KEY_INVALID = 0, - KEY_NONE = 2, /* FIXME: perhaps rather KEY_EMPTY? */ - KEY_MOTION_CHAR = 4, - KEY_MOTION_LINE = 8, - KEY_MOTION = 4|8, - KEY_MOTIONALLOWED = 16, - KEY_NUMBER = 32, - KEY_NUMBERALLOWED = 64, - KEY_ENSURE_CURSOR_SHOWN = 128, /* jump to cursor if it is off screen */ /* FIXME: maybe rather KEY_JUMP_TO_CURSOR? */ - KEY_ANY = 0xFF -}; - -struct key { - char *text; /* for keys that correspond with text */ - unsigned int mods; /* modifier mask */ - KeySym keysym; /* for other keys, e.g. arrow keys */ - ledit_mode modes; /* modes in which this keybinding is functional */ - enum key_type prev_keys; /* allowed previous keys */ - enum key_type type; /* type of key - mainly used for ensure_cursor_shown */ - struct action (*func)(ledit_view *, char *, size_t); /* callback function */ -}; - -static struct action backspace(ledit_view *view, char *text, size_t len); -static struct action cursor_left(ledit_view *view, char *text, size_t len); -static struct action cursor_right(ledit_view *view, char *text, size_t len); -static struct action cursor_up(ledit_view *view, char *text, size_t len); -static struct action cursor_down(ledit_view *view, char *text, size_t len); -static struct action return_key(ledit_view *view, char *text, size_t len); -static struct action delete_key(ledit_view *view, char *text, size_t len); -static struct action escape_key(ledit_view *view, char *text, size_t len); -static struct action enter_insert(ledit_view *view, char *text, size_t len); -static struct action cursor_to_beginning(ledit_view *view, char *text, size_t len); -static struct action push_0(ledit_view *view, char *text, size_t len); -static struct action push_1(ledit_view *view, char *text, size_t len); -static struct action push_2(ledit_view *view, char *text, size_t len); -static struct action push_3(ledit_view *view, char *text, size_t len); -static struct action push_4(ledit_view *view, char *text, size_t len); -static struct action push_5(ledit_view *view, char *text, size_t len); -static struct action push_6(ledit_view *view, char *text, size_t len); -static struct action push_7(ledit_view *view, char *text, size_t len); -static struct action push_8(ledit_view *view, char *text, size_t len); -static struct action push_9(ledit_view *view, char *text, size_t len); -static struct action delete(ledit_view *view, char *text, size_t len); -static struct action enter_visual(ledit_view *view, char *text, size_t len); -static struct action switch_selection_end(ledit_view *view, char *text, size_t len); -static struct action clipcopy(ledit_view *view, char *text, size_t len); -static struct action clippaste(ledit_view *view, char *text, size_t len); -static struct action show_line(ledit_view *view, char *text, size_t len); -static struct action enter_commandedit(ledit_view *view, char *text, size_t len); -static struct action enter_searchedit_backward(ledit_view *view, char *text, size_t len); -static struct action enter_searchedit_forward(ledit_view *view, char *text, size_t len); -static struct action key_search_next(ledit_view *view, char *text, size_t len); -static struct action key_search_prev(ledit_view *view, char *text, size_t len); -static struct action undo(ledit_view *view, char *text, size_t len); -static struct action redo(ledit_view *view, char *text, size_t len); -static struct action insert_mode_insert_text(ledit_view *view, char *text, size_t len); -static struct action repeat_command(ledit_view *view, char *text, size_t len); -static struct action screen_up(ledit_view *view, char *text, size_t len); -static struct action screen_down(ledit_view *view, char *text, size_t len); -static struct action scroll_with_cursor_up(ledit_view *view, char *text, size_t len); -static struct action scroll_with_cursor_down(ledit_view *view, char *text, size_t len); -static struct action scroll_lines_up(ledit_view *view, char *text, size_t len); -static struct action scroll_lines_down(ledit_view *view, char *text, size_t len); -static struct action move_to_line(ledit_view *view, char *text, size_t len); -static struct action paste_normal(ledit_view *view, char *text, size_t len); -static struct action paste_normal_backwards(ledit_view *view, char *text, size_t len); -static struct action change(ledit_view *view, char *text, size_t len); -static struct action move_to_eol(ledit_view *view, char *text, size_t len); -static struct action mark_line(ledit_view *view, char *text, size_t len); -static struct action jump_to_mark(ledit_view *view, char *text, size_t len); -static struct action next_word(ledit_view *view, char *text, size_t len); -static struct action next_word_end(ledit_view *view, char *text, size_t len); -static struct action next_bigword(ledit_view *view, char *text, size_t len); -static struct action next_bigword_end(ledit_view *view, char *text, size_t len); -static struct action prev_word(ledit_view *view, char *text, size_t len); -static struct action prev_bigword(ledit_view *view, char *text, size_t len); -static struct action append_after_eol(ledit_view *view, char *text, size_t len); -static struct action append_after_cursor(ledit_view *view, char *text, size_t len); -static struct action append_line_above(ledit_view *view, char *text, size_t len); -static struct action append_line_below(ledit_view *view, char *text, size_t len); -static struct action find_next_char_forwards(ledit_view *view, char *text, size_t len); -static struct action find_next_char_backwards(ledit_view *view, char *text, size_t len); -static struct action find_char_forwards(ledit_view *view, char *text, size_t len); -static struct action find_char_backwards(ledit_view *view, char *text, size_t len); -static struct action change_to_eol(ledit_view *view, char *text, size_t len); -static struct action delete_to_eol(ledit_view *view, char *text, size_t len); -static struct action delete_chars_forwards(ledit_view *view, char *text, size_t len); -static struct action delete_chars_backwards(ledit_view *view, char *text, size_t len); -static struct action yank(ledit_view *view, char *text, size_t len); -static struct action yank_lines(ledit_view *view, char *text, size_t len); -static struct action replace(ledit_view *view, char *text, size_t len); -static struct action cursor_to_first_non_ws(ledit_view *view, char *text, size_t len); -static struct action join_lines(ledit_view *view, char *text, size_t len); -static struct action insert_at_beginning(ledit_view *view, char *text, size_t len); -static struct action toggle_hard_line_based(ledit_view *view, char *text, size_t len); - -/* FIXME: maybe sort these and use binary search - -> but that would mess with the catch-all keys */ -static struct key keys_en[] = { - {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &backspace}, - {NULL, 0, XK_Left, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left}, - {NULL, 0, XK_Right, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right}, - {NULL, 0, XK_Up, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up}, - {NULL, 0, XK_Down, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {NULL, XK_ANY_MOD, XK_Return, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &return_key}, - {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &delete_key}, - {NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &escape_key}, - {"i", 0, 0, NORMAL|VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_insert}, - {"h", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left}, - {"l", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right}, - {"j", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {"k", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up}, - {"h", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left}, - {"t", ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &toggle_hard_line_based}, - {NULL, 0, XK_space, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right}, - {"j", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {"n", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {"p", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up}, - {"0", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_beginning}, - {"0", 0, 0, NORMAL|VISUAL, KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &push_0}, - {"1", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_1}, - {"2", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_2}, - {"3", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_3}, - {"4", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_4}, - {"5", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_5}, - {"6", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_6}, - {"7", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_7}, - {"8", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_8}, - {"9", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_9}, - {"x", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_forwards}, - {"X", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_backwards}, - {"d", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &delete}, - {"y", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &yank}, - {"Y", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &yank_lines}, - {"c", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &change}, - {"v", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_visual}, - {"o", 0, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &switch_selection_end}, - {"c", ControlMask, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clipcopy}, - {"v", ControlMask, 0, INSERT, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clippaste}, - {"g", ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &show_line}, - {":", 0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &enter_commandedit}, - {"?", 0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_backward}, - {"/", 0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_forward}, - {"n", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_next}, - {"N", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_prev}, - {"u", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &undo}, - {"U", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &redo}, - {".", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &repeat_command}, /* FIXME: only allow after finished key sequence */ - {"z", ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &undo}, - {"y", ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &redo}, /* FIXME: this is confusing with ctrl+y in normal mode */ - {"b", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_up}, - {"f", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_down}, - {"e", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_down}, - {"y", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_up}, - {"d", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_down}, - {"u", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_up}, - {"$", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_eol}, - {"w", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word}, - {"e", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word_end}, - {"W", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword}, - {"E", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword_end}, - {"b", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_word}, - {"B", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_bigword}, - {"G", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_line}, - {"J", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &join_lines}, - {"I", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &insert_at_beginning}, - {"p", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal}, - {"P", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal_backwards}, - {"A", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_eol}, - {"a", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_cursor}, - {"O", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_above}, - {"o", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_below}, - {"m", 0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &mark_line}, - {"'", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &jump_to_mark}, - {"C", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &change_to_eol}, - {"D", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &delete_to_eol}, - {"r", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &replace}, - {"^", 0, 0, NORMAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_first_non_ws}, - {"t", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_forwards}, - {"T", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_backwards}, - {"f", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_forwards}, - {"F", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_backwards}, - {"", 0, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &insert_mode_insert_text} -}; - -static struct key keys_de[] = { - {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &backspace}, - {NULL, 0, XK_Left, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left}, - {NULL, 0, XK_Right, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right}, - {NULL, 0, XK_Up, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up}, - {NULL, 0, XK_Down, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {NULL, XK_ANY_MOD, XK_Return, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &return_key}, - {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &delete_key}, - {NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &escape_key}, - {"i", 0, 0, NORMAL|VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_insert}, - {"h", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left}, - {"l", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right}, - {"j", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {"k", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up}, - {"h", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left}, - {"t", ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &toggle_hard_line_based}, - {NULL, 0, XK_space, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right}, - {"j", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {"n", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {"p", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up}, - {"0", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_beginning}, - {"0", 0, 0, NORMAL|VISUAL, KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &push_0}, - {"1", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_1}, - {"2", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_2}, - {"3", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_3}, - {"4", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_4}, - {"5", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_5}, - {"6", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_6}, - {"7", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_7}, - {"8", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_8}, - {"9", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_9}, - {"x", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_forwards}, - {"X", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_backwards}, - {"d", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &delete}, - {"z", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &yank}, - {"Z", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &yank_lines}, - {"c", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &change}, - {"v", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_visual}, - {"o", 0, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &switch_selection_end}, - {"c", ControlMask, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clipcopy}, - {"v", ControlMask, 0, INSERT, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clippaste}, - {"g", ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &show_line}, - {"Ö", 0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &enter_commandedit}, - {"_", 0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_backward}, - {"-", 0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_forward}, - {"n", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_next}, - {"N", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_prev}, - {"u", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &undo}, - {"U", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &redo}, - {".", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &repeat_command}, - {"y", ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &undo}, - {"z", ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &redo}, - {"b", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_up}, - {"f", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_down}, - {"e", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_down}, - {"z", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_up}, - {"d", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_down}, - {"u", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_up}, - {"$", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_eol}, - {"w", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word}, - {"e", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word_end}, - {"W", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword}, - {"E", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword_end}, - {"b", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_word}, - {"B", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_bigword}, - {"G", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_line}, - {"J", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &join_lines}, - {"I", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &insert_at_beginning}, - {"p", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal}, - {"P", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal_backwards}, - {"A", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_eol}, - {"a", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_cursor}, - {"O", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_above}, - {"o", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_below}, - {"m", 0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &mark_line}, - {"ä", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &jump_to_mark}, - {"C", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &change_to_eol}, - {"D", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &delete_to_eol}, - {"r", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &replace}, - {"&", 0, 0, NORMAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_first_non_ws}, - {"t", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_forwards}, - {"T", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_backwards}, - {"f", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_forwards}, - {"F", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_backwards}, - {"", 0, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &insert_mode_insert_text} -}; - -static struct key keys_ur[] = { - {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &backspace}, - {NULL, 0, XK_Left, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left}, - {NULL, 0, XK_Right, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right}, - {NULL, 0, XK_Up, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up}, - {NULL, 0, XK_Down, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {NULL, XK_ANY_MOD, XK_Return, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &return_key}, - {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &delete_key}, - {NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &escape_key}, - {"ی", 0, 0, NORMAL|VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_insert}, - {"ح", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left}, - {"ل", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right}, - {"ج", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {"ک", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up}, - {"ح", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left}, - {"ت", ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &toggle_hard_line_based}, - {NULL, 0, XK_space, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right}, - {"ج", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {"ن", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {"پ", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up}, - {"0", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_beginning}, - {"0", 0, 0, NORMAL|VISUAL, KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &push_0}, - {"1", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_1}, - {"2", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_2}, - {"3", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_3}, - {"4", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_4}, - {"5", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_5}, - {"6", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_6}, - {"7", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_7}, - {"8", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_8}, - {"9", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_9}, - {"ش", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_forwards}, - {"ژ", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_backwards}, - {"د", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &delete}, - {"ے", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &yank}, - {"َ", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &yank_lines}, - {"چ", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &change}, - {"ط", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_visual}, - {"ہ", 0, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &switch_selection_end}, - {"چ", ControlMask, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clipcopy}, - {"ط", ControlMask, 0, INSERT, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clippaste}, - {"گ", ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &show_line}, - {":", 0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &enter_commandedit}, - {"؟", 0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_backward}, - {"/", 0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_forward}, - {"ن", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_next}, - {"ں", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_prev}, - {"ء", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &undo}, - {"ئ", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &redo}, - {"۔", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &repeat_command}, - {"ز", ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &undo}, - {"َ", ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &redo}, - {"ب", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_up}, - {"ف", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_down}, - {"ع", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_down}, - {"ے", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_up}, - {"د", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_down}, - {"ء", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_up}, - {"$", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_eol}, - {"و", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word}, - {"ع", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word_end}, - {"ؤ", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword}, - {"ٰ", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword_end}, - {"ب", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_word}, - {".", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_bigword}, - {"غ", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_line}, - {"ض", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &join_lines}, - {"ِ", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &insert_at_beginning}, - {"پ", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal}, - {"ُ", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal_backwards}, - {"آ", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_eol}, - {"ا", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_cursor}, - {"ۃ", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_above}, - {"ہ", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_below}, - {"م", 0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &mark_line}, - {"'", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &jump_to_mark}, - {"ث", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &change_to_eol}, - {"ڈ", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &delete_to_eol}, - {"ر", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &replace}, - {"^", 0, 0, NORMAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_first_non_ws}, - {"ت", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_forwards}, - {"ٹ", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_backwards}, - {"ف", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_forwards}, - {"ّ", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_backwards}, - {"", 0, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &insert_mode_insert_text} -}; - -static struct key keys_hi[] = { - {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &backspace}, - {NULL, 0, XK_Left, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left}, - {NULL, 0, XK_Right, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right}, - {NULL, 0, XK_Up, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up}, - {NULL, 0, XK_Down, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {NULL, XK_ANY_MOD, XK_Return, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &return_key}, - {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &delete_key}, - {NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &escape_key}, - {"ि", 0, 0, NORMAL|VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_insert}, - {"ह", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left}, - {"ल", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right}, - {"ज", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {"क", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up}, - {"ह", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left}, - {"त", ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &toggle_hard_line_based}, - {NULL, 0, XK_space, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right}, - {"ज", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {"न", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down}, - {"प", ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up}, - {"0", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_beginning}, - {"0", 0, 0, NORMAL|VISUAL, KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &push_0}, - {"1", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_1}, - {"2", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_2}, - {"3", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_3}, - {"4", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_4}, - {"5", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_5}, - {"6", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_6}, - {"7", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_7}, - {"8", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_8}, - {"9", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_9}, - {"्", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_forwards}, - {"ॉ", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_backwards}, - {"द", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &delete}, - {"य", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &yank}, - {"ञ", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &yank_lines}, - {"च", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &change}, - {"ड", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_visual}, - {"ो", 0, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &switch_selection_end}, - {"च", ControlMask, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clipcopy}, - {"ड", ControlMask, 0, INSERT, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clippaste}, - {"ग", ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &show_line}, - {":", 0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &enter_commandedit}, - {"?", 0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_backward}, - {"/", 0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_forward}, - {"न", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_next}, - {"ण", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_prev}, - {"ु", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &undo}, - {"ू", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &redo}, - {".", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &repeat_command}, - {"श", ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &undo}, - {"य", ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &redo}, - {"ब", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_up}, - {"ट", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_down}, - {"े", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_down}, - {"य", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_up}, - {"द", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_down}, - {"ु", ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_up}, - {"$", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_eol}, - {"व", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word}, - {"े", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word_end}, - {"ॐ", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword}, - {"ै", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword_end}, - {"ब", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_word}, - {"भ", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_bigword}, - {"घ", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_line}, - {"झ", 0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &join_lines}, - {"ी", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &insert_at_beginning}, - {"प", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal}, - {"फ", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal_backwards}, - {"आ", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_eol}, - {"ा", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_cursor}, - {"ौ", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_above}, - {"ो", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_below}, - {"म", 0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &mark_line}, - {"'", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &jump_to_mark}, - {"छ", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &change_to_eol}, - {"ध", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &delete_to_eol}, - {"र", 0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &replace}, - {"^", 0, 0, NORMAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_first_non_ws}, - {"त", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_forwards}, - {"थ", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_backwards}, - {"ट", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_forwards}, - {"ठ", 0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_backwards}, - {"", 0, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &insert_mode_insert_text} -}; - -GEN_KEY_ARRAY(struct key, keys_en, keys_de, keys_ur, keys_hi); diff --git a/keys_command.c b/keys_command.c @@ -1,3 +1,4 @@ +/* FIXME: remove CHECK_VIEW_LOCKED when it is confirmed that the new system works properly */ /* FIXME: Parse commands properly and allow combinations of commands */ #include <stdio.h> #include <ctype.h> @@ -18,7 +19,6 @@ #include "txtbuf.h" #include "undo.h" #include "cache.h" -#include "theme.h" #include "window.h" #include "buffer.h" #include "view.h" @@ -27,9 +27,113 @@ #include "util.h" #include "keys.h" -#include "keys_config.h" #include "keys_command.h" -#include "keys_command_config.h" +#include "configparser.h" + +/************************************************************************* + * Declarations for all functions that can be used in the configuration. * + *************************************************************************/ + +static int substitute_yes(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int substitute_yes_all(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int substitute_no(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int substitute_no_all(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_insert_text(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_cursor_left(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_cursor_right(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_cursor_to_end(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_backspace(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_delete(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_prevcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_nextcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_prevsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_nextsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int editsearch_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int editsearchb_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index); +static int edit_discard(ledit_view *view, char *key_text, size_t len, size_t lang_index); + +static int create_view(ledit_view *view, char *cmd, size_t l1, size_t l2); +static int close_view(ledit_view *view, char *cmd, size_t l1, size_t l2); +static int handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2); +static int handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2); +static int handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2); +static int handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2); + +/*********************************************** + * String-function mapping for config parsing. * + ***********************************************/ + +typedef enum { + KEY_FLAG_NONE = 0, + KEY_FLAG_JUMP_TO_CURSOR = 1, + KEY_FLAG_LOCK_ALLOWED = 2 +} command_key_cb_flags; + +typedef enum { + CMD_FLAG_NONE = 0, + CMD_FLAG_OPTIONAL_RANGE = 1, + CMD_FLAG_LOCK_ALLOWED = 2 +} command_cb_flags; + +typedef int (*command_key_cb_func)(ledit_view *, char *, size_t, size_t); +struct command_key_cb { + char *text; + command_key_cb_func func; + command_key_cb_flags flags; + command_mode allowed_modes; +}; + +typedef int (*command_cb_func)(ledit_view *view, char *cmd, size_t l1, size_t l2); +struct command_cb { + char *text; + command_cb_func func; + command_cb_flags flags; +}; + +int +command_key_cb_modemask_is_valid(command_key_cb *cb, command_mode modes) { + return (~cb->allowed_modes & modes) == 0; +} + +static command_key_cb command_key_cb_map[] = { + {"edit-backspace", &edit_backspace, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-cursor-left", &edit_cursor_left, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-cursor-right", &edit_cursor_right, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-cursor-to-beginning", &edit_cursor_to_beginning, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-cursor-to-end", &edit_cursor_to_end, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-delete", &edit_delete, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-discard", &edit_discard, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-insert-text", &edit_insert_text, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-next-command", &edit_nextcommand, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT}, + {"edit-next-search", &edit_nextsearch, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-previous-command", &edit_prevcommand, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT}, + {"edit-previous-search", &edit_prevsearch, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-submit", &edit_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT}, + {"edit-submit-backwards-search", &editsearchb_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCHB}, + {"edit-submit-search", &editsearch_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCH}, + {"substitute-no", &substitute_no, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, CMD_SUBSTITUTE}, + {"substitute-no-all", &substitute_no_all, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, CMD_SUBSTITUTE}, + {"substitute-yes", &substitute_yes, KEY_FLAG_JUMP_TO_CURSOR, CMD_SUBSTITUTE}, + {"substitute-yes-all", &substitute_yes_all, KEY_FLAG_JUMP_TO_CURSOR, CMD_SUBSTITUTE}, +}; + +static command_cb command_cb_map[] = { + {"close-view", &close_view, CMD_FLAG_LOCK_ALLOWED}, + {"create-view", &create_view, CMD_FLAG_LOCK_ALLOWED}, + {"quit", &handle_quit, CMD_FLAG_LOCK_ALLOWED}, + {"substitute", &handle_substitute, CMD_FLAG_OPTIONAL_RANGE}, + {"write", &handle_write, CMD_FLAG_OPTIONAL_RANGE|CMD_FLAG_LOCK_ALLOWED}, + {"write-quit", &handle_write_quit, CMD_FLAG_OPTIONAL_RANGE|CMD_FLAG_LOCK_ALLOWED}, +}; + +GEN_CB_MAP_HELPERS(command_key_cb_map, command_key_cb, text) +GEN_CB_MAP_HELPERS(command_cb_map, command_cb, text) + +/*************************************************** + * General global variables and utility functions. * + ***************************************************/ static struct { char *search; @@ -99,20 +203,17 @@ view_locked_error(ledit_view *view) { #define CHECK_VIEW_LOCKED(view) if (view->lock_text) return view_locked_error(view) -static int create_view(ledit_view *view, char *cmd, size_t l1, size_t l2); -static int close_view(ledit_view *view, char *cmd, size_t l1, size_t l2); -static int handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2); -static int handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2); -static int handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2); -static int handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2); +/******************************************************************** + * Functions for handling commands typed in line editor (:w, etc.). * + ********************************************************************/ + static int parse_range( - ledit_view *view, char *cmd, size_t len, char **cmd_ret, + ledit_view *view, char *cmd, size_t len, size_t *idx_ret, size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid, char **errstr_ret ); -static int handle_cmd(ledit_view *view, char *cmd, size_t len); +static int handle_cmd(ledit_view *view, char *cmd, size_t len, size_t lang_index); -/* FIXME: remove command name before passing to handlers */ static int handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) { (void)l1; @@ -120,7 +221,6 @@ handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) { /* FIXME: allow writing only part of file */ char *filename = view->buffer->filename; int stored = 1; - cmd++; /* remove 'w' */ int force = 0; if (*cmd == '!') { force = 1; @@ -132,7 +232,7 @@ handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) { stored = 0; } /* FIXME: file locks */ - char *errstr; + char *errstr = NULL; if (filename) { struct stat sb; /* There technically is a race between checking stat and actually @@ -176,7 +276,6 @@ static int handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) { (void)l1; (void)l2; - cmd++; int force = 0; if (*cmd == '!') force = 1; @@ -194,7 +293,7 @@ create_view(ledit_view *view, char *cmd, size_t l1, size_t l2) { (void)cmd; (void)l1; (void)l2; - buffer_add_view(view->buffer, view->theme, view->mode, view->cur_line, view->cur_index, view->display_offset); + buffer_add_view(view->buffer, view->mode, view->cur_line, view->cur_index, view->display_offset); return 0; } @@ -205,7 +304,6 @@ close_view(ledit_view *view, char *cmd, size_t l1, size_t l2) { (void)l2; /* FIXME: This will lead to problems if I add something that requires access to the view after the command is handled. */ - cmd++; int force = 0; if (*cmd == '!') force = 1; @@ -220,7 +318,7 @@ close_view(ledit_view *view, char *cmd, size_t l1, size_t l2) { static int handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) { - handle_write(view, cmd + 1, l1, l2); + handle_write(view, cmd, l1, l2); ledit_cleanup(); exit(0); return 0; @@ -328,7 +426,6 @@ substitute_all_remaining(ledit_view *view) { static int handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) { CHECK_VIEW_LOCKED(view); - cmd++; /* remove 's' at beginning */ size_t len = strlen(cmd); char *sep = NULL; if (len == 0) goto error; @@ -399,34 +496,19 @@ enum cmd_type { CMD_OPTIONAL_RANGE }; -static const struct { - char *cmd; - enum cmd_type type; - int (*handler)(ledit_view *view, char *cmd, size_t l1, size_t l2); -} cmds[] = { - {"wq", CMD_OPTIONAL_RANGE, &handle_write_quit}, - {"w", CMD_OPTIONAL_RANGE, &handle_write}, - {"q", CMD_NORMAL, &handle_quit}, - {"v", CMD_NORMAL, &create_view}, - {"c", CMD_NORMAL, &close_view}, - {"s", CMD_OPTIONAL_RANGE, &handle_substitute} -}; - /* . current line $ last line % all lines */ -/* FIXME: ACTUALLY USE LEN!!! */ /* NOTE: Marks are only recognized here if they are one unicode character! */ /* NOTE: Only the line range of the selection is used at the moment. */ static int parse_range( - ledit_view *view, char *cmd, size_t len, char **cmd_ret, + ledit_view *view, char *cmd, size_t len, size_t *idx_ret, size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid, char **errstr_ret) { - (void)len; *errstr_ret = ""; enum { START_LINENO = 1, @@ -437,8 +519,10 @@ parse_range( size_t l1 = 0, l2 = 0; *l1_valid = 0; *l2_valid = 0; - char *c = cmd; - while (*c != '\0') { + size_t cur = 0; + char *c; + while (cur < len) { + c = &cmd[cur]; if (isdigit(*c)) { if (s & IN_LINENO) { size_t *final = &l2; @@ -466,22 +550,20 @@ parse_range( s = IN_LINENO; } } else if (*c == '\'' && (s & START_LINENO)) { - if (c[1] == '\0' || c[2] == '\0') { + if (len - cur <= 2) { *errstr_ret = "Invalid range"; return 1; } - char *aftermark = next_utf8(c + 2); - size_t marklen = aftermark - (c + 1); + size_t aftermark_idx = next_utf8_len(c + 2, len - cur - 2); + size_t marklen = aftermark_idx - (cur + 1); size_t l, b; - if (!strncmp(c + 1, "<", strlen("<")) && view->sel_valid) { + if (*(c + 1) == '<' && view->sel_valid) { l = view->sel.line1 < view->sel.line2 ? view->sel.line1 : view->sel.line2; - } else if (!strncmp(c + 1, ">", strlen(">")) && view->sel_valid) { + } else if (*(c + 1) == '>' && view->sel_valid) { l = view->sel.line1 > view->sel.line2 ? view->sel.line1 : view->sel.line2; - } else { - if (buffer_get_mark(view->buffer, c + 1, marklen, &l, &b)) { - *errstr_ret = "Invalid mark"; - return 1; - } + } else if (buffer_get_mark(view->buffer, c + 1, marklen, &l, &b)) { + *errstr_ret = "Invalid mark"; + return 1; } if (!*l1_valid) { l1 = l + 1; @@ -490,7 +572,7 @@ parse_range( l2 = l + 1; *l2_valid = 1; } - c = aftermark; + cur = aftermark_idx; s = 0; continue; } else if (*c == ',' && !(s & START_RANGE)) { @@ -505,7 +587,7 @@ parse_range( l1 = 1; l2 = view->lines_num; *l1_valid = *l2_valid = 1; - c++; + cur++; s = 0; break; } else { @@ -543,7 +625,7 @@ parse_range( } else { break; } - c++; + cur++; } if ((!*l1_valid || !*l2_valid) && !(s & START_RANGE)) { *errstr_ret = "Invalid range"; @@ -553,7 +635,7 @@ parse_range( *errstr_ret = "Invalid line number in range"; return 1; } - *cmd_ret = c; + *idx_ret = cur; /* ranges are given 1-indexed by user */ *line1_ret = l1 - 1; *line2_ret = l2 - 1; @@ -561,36 +643,58 @@ parse_range( } static int -handle_cmd(ledit_view *view, char *cmd, size_t len) { +handle_cmd(ledit_view *view, char *cmd, size_t len, size_t lang_index) { if (len < 1) return 0; push_cmdhistory(cmd, len); - char *c; size_t l1, l2; int l1_valid, l2_valid; char *errstr; - if (parse_range(view, cmd, len, &c, &l1, &l2, &l1_valid, &l2_valid, &errstr)) { + size_t start_idx; + if (parse_range(view, cmd, len, &start_idx, &l1, &l2, &l1_valid, &l2_valid, &errstr)) { window_show_message(view->window, errstr, -1); return 0; } + if (start_idx >= len) { + window_show_message(view->window, "Invalid command", -1); + return 0; + } + size_t rem_len = len - start_idx; + char *cur_str = cmd + start_idx; int range_given = l1_valid && l2_valid; if (!range_given) { l1 = l2 = view->cur_line; } - for (size_t i = 0; i < LENGTH(cmds); i++) { - if (!strncmp(cmds[i].cmd, c, strlen(cmds[i].cmd)) && - (!range_given || cmds[i].type == CMD_OPTIONAL_RANGE)) { - return cmds[i].handler(view, c, l1, l2); + command_array *cur_cmds = config_get_commands(lang_index); + char *cmd_text; + size_t text_len; + for (size_t i = 0; i < cur_cmds->num_cmds; i++) { + cmd_text = cur_cmds->cmds[i].text; + text_len = strlen(cmd_text); + if (rem_len < text_len) + continue; + if (!strncmp(cmd_text, cur_str, text_len)) { + if (range_given && !(cur_cmds->cmds[i].cb->flags & CMD_OPTIONAL_RANGE)) { + window_show_message(view->window, "Command does not take range", -1); + return 0; + } else if (view->lock_text && !(cur_cmds->cmds[i].cb->flags & CMD_FLAG_LOCK_ALLOWED)) { + window_show_message(view->window, view->lock_text, -1); + return 0; + } + return cur_cmds->cmds[i].cb->func(view, cur_str + text_len, l1, l2); } } window_show_message(view->window, "Invalid command", -1); return 0; } +/*********************************** + * Functions called on keypresses. * + ***********************************/ + static int -substitute_yes(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +substitute_yes(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; substitute_single(view); buffer_recalc_line(view->buffer, sub_state.line); if (!sub_state.global) { @@ -606,18 +710,16 @@ substitute_yes(ledit_view *view, char *key_text, size_t len) { } static int -substitute_yes_all(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +substitute_yes_all(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; substitute_all_remaining(view); show_num_substituted(view); return 0; } static int -substitute_no(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +substitute_no(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; if (!sub_state.global) { sub_state.line++; sub_state.byte = 0; @@ -631,16 +733,16 @@ substitute_no(ledit_view *view, char *key_text, size_t len) { } static int -substitute_no_all(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +substitute_no_all(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; buffer_unlock_all_views(view->buffer); show_num_substituted(view); return 0; } static int -edit_insert_text(ledit_view *view, char *key_text, size_t len) { +edit_insert_text(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)lang_index; /* FIXME: overflow */ window_insert_bottom_bar_text(view->window, key_text, len); window_set_bottom_bar_cursor( @@ -650,57 +752,50 @@ edit_insert_text(ledit_view *view, char *key_text, size_t len) { } static int -edit_cursor_to_end(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +edit_cursor_to_end(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; window_bottom_bar_cursor_to_end(view->window); return 1; } static int -edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; window_bottom_bar_cursor_to_beginning(view->window); return 1; } static int -edit_cursor_left(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +edit_cursor_left(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; window_move_bottom_bar_cursor(view->window, -1); return 1; } static int -edit_cursor_right(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +edit_cursor_right(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; window_move_bottom_bar_cursor(view->window, 1); return 1; } static int -edit_backspace(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +edit_backspace(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; window_delete_bottom_bar_char(view->window, -1); return 1; } static int -edit_delete(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +edit_delete(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; window_delete_bottom_bar_char(view->window, 1); return 1; } static int -edit_submit(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +edit_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; window_set_bottom_bar_text_shown(view->window, 0); char *text = window_get_bottom_bar_text(view->window); int min_pos = window_get_bottom_bar_min_pos(view->window); @@ -714,15 +809,14 @@ edit_submit(ledit_view *view, char *key_text, size_t len) { } /* FIXME: this is hacky */ char *cmd = ledit_strndup(text, textlen); - int ret = handle_cmd(view, cmd, (size_t)textlen); + int ret = handle_cmd(view, cmd, (size_t)textlen, lang_index); free(cmd); return ret; } static int -edit_prevcommand(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +edit_prevcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; if (cmdhistory.cur > 0) { cmdhistory.cur--; window_set_bottom_bar_realtext(view->window, cmdhistory.cmds[cmdhistory.cur], -1); @@ -732,9 +826,8 @@ edit_prevcommand(ledit_view *view, char *key_text, size_t len) { } static int -edit_nextcommand(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +edit_nextcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; if (cmdhistory.len > 0 && cmdhistory.cur < cmdhistory.len - 1) { cmdhistory.cur++; window_set_bottom_bar_realtext(view->window, cmdhistory.cmds[cmdhistory.cur], -1); @@ -747,9 +840,8 @@ edit_nextcommand(ledit_view *view, char *key_text, size_t len) { } static int -edit_prevsearch(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +edit_prevsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; if (searchhistory.cur > 0) { searchhistory.cur--; window_set_bottom_bar_realtext(view->window, searchhistory.cmds[searchhistory.cur], -1); @@ -759,9 +851,8 @@ edit_prevsearch(ledit_view *view, char *key_text, size_t len) { } static int -edit_nextsearch(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +edit_nextsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; if (searchhistory.len > 0 && searchhistory.cur < searchhistory.len - 1) { searchhistory.cur++; window_set_bottom_bar_realtext(view->window, searchhistory.cmds[searchhistory.cur], -1); @@ -795,9 +886,8 @@ search_prev(ledit_view *view) { } static int -editsearch_submit(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +editsearch_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; window_set_bottom_bar_text_shown(view->window, 0); char *text = window_get_bottom_bar_text(view->window); int min_pos = window_get_bottom_bar_min_pos(view->window); @@ -818,9 +908,8 @@ editsearch_submit(ledit_view *view, char *key_text, size_t len) { } static int -editsearchb_submit(ledit_view *view, char *key_text, size_t len) { - (void)key_text; - (void)len; +editsearchb_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)key_text; (void)len; (void)lang_index; window_set_bottom_bar_text_shown(view->window, 0); char *text = window_get_bottom_bar_text(view->window); int min_pos = window_get_bottom_bar_min_pos(view->window); @@ -841,9 +930,8 @@ editsearchb_submit(ledit_view *view, char *key_text, size_t len) { } static int -edit_discard(ledit_view *view, char *key_text, size_t len) { - (void)view; - (void)key_text; +edit_discard(ledit_view *view, char *key_text, size_t len, size_t lang_index) { + (void)view; (void)key_text; (void)lang_index; (void)len; window_set_bottom_bar_text_shown(view->window, 0); return 0; @@ -854,28 +942,46 @@ command_key_handler(ledit_view *view, XEvent *event, int lang_index) { char buf[64]; KeySym sym; int n; - struct key *cur_keys = keys[lang_index].keys; - int num_keys = keys[lang_index].num_keys; + command_key_array *cur_keys = config_get_command_keys(lang_index); + size_t num_keys = cur_keys->num_keys; unsigned int key_state = event->xkey.state; preprocess_key(view->window, event, &sym, buf, sizeof(buf), &n); - int grabkey = 1; - for (int i = 0; i < num_keys; i++) { - if (cur_keys[i].text) { + int grabkey = 1, found = 0; + command_key_cb_flags flags = KEY_FLAG_NONE; + for (size_t i = 0; i < num_keys; i++) { + if (cur_keys->keys[i].text) { if (n > 0 && - (cur_keys[i].type == view->cur_command_type) && - ((!strncmp(cur_keys[i].text, buf, n) && - match_key(cur_keys[i].mods, key_state & ~ShiftMask)) || - cur_keys[i].text[0] == '\0')) { - grabkey = cur_keys[i].func(view, buf, (size_t)n); + (cur_keys->keys[i].modes & view->cur_command_type) && + ((!strncmp(cur_keys->keys[i].text, buf, n) && + match_key(cur_keys->keys[i].mods, key_state & ~ShiftMask)) || + cur_keys->keys[i].text[0] == '\0')) { + flags = cur_keys->keys[i].cb->flags; + if (!(flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text) { + (void)view_locked_error(view); + grabkey = 0; + break; + } + grabkey = cur_keys->keys[i].cb->func(view, buf, (size_t)n, lang_index); + found = 1; + break; + } + } else if ((cur_keys->keys[i].modes & view->cur_command_type) && + (cur_keys->keys[i].keysym == sym) && + (match_key(cur_keys->keys[i].mods, key_state))) { + flags = cur_keys->keys[i].cb->flags; + if (!(flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text) { + (void)view_locked_error(view); + grabkey = 0; break; } - } else if ((cur_keys[i].type == view->cur_command_type) && - (cur_keys[i].keysym == sym) && - (match_key(cur_keys[i].mods, key_state))) { - grabkey = cur_keys[i].func(view, buf, (size_t)n); + grabkey = cur_keys->keys[i].cb->func(view, buf, (size_t)n, lang_index); + found = 1; break; } } + if (found && (flags & KEY_FLAG_JUMP_TO_CURSOR)) + view_ensure_cursor_shown(view); + /* FIXME: proper error on invalid key */ if (grabkey) return (struct action){ACTION_GRABKEY, &command_key_handler}; else diff --git a/keys_command.h b/keys_command.h @@ -3,6 +3,14 @@ #include <X11/Xlib.h> #include "view.h" +#include "uglycrap.h" + +typedef struct command_key_cb command_key_cb; +typedef struct command_cb command_cb; + +int command_key_cb_modemask_is_valid(command_key_cb *cb, command_mode modes); +command_key_cb *command_key_cb_map_get_entry(char *text, size_t len); +command_cb *command_cb_map_get_entry(char *text, size_t len); /* these are only here so they can also be used by keys_basic */ void search_next(ledit_view *view); diff --git a/keys_command_config.h b/keys_command_config.h @@ -1,196 +0,0 @@ -/* - * These are the keys used by special commands that require a special key - * handler. This includes keys used to edit the line entry at the bottom - * and keys used for confirmation (e.g. when substituting). - */ - -static int substitute_yes(ledit_view *view, char *key_text, size_t len); -static int substitute_yes_all(ledit_view *view, char *key_text, size_t len); -static int substitute_no(ledit_view *view, char *key_text, size_t len); -static int substitute_no_all(ledit_view *view, char *key_text, size_t len); -static int edit_insert_text(ledit_view *view, char *key_text, size_t len); -static int edit_cursor_left(ledit_view *view, char *key_text, size_t len); -static int edit_cursor_right(ledit_view *view, char *key_text, size_t len); -static int edit_cursor_to_end(ledit_view *view, char *key_text, size_t len); -static int edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len); -static int edit_backspace(ledit_view *view, char *key_text, size_t len); -static int edit_delete(ledit_view *view, char *key_text, size_t len); -static int edit_submit(ledit_view *view, char *key_text, size_t len); -static int edit_prevcommand(ledit_view *view, char *key_text, size_t len); -static int edit_nextcommand(ledit_view *view, char *key_text, size_t len); -static int edit_prevsearch(ledit_view *view, char *key_text, size_t len); -static int edit_nextsearch(ledit_view *view, char *key_text, size_t len); -static int editsearch_submit(ledit_view *view, char *key_text, size_t len); -static int editsearchb_submit(ledit_view *view, char *key_text, size_t len); -static int edit_discard(ledit_view *view, char *key_text, size_t len); - -struct key { - char *text; /* for keys that correspond with text */ - unsigned int mods; /* modifier mask */ - KeySym keysym; /* for other keys, e.g. arrow keys */ - enum ledit_command_type type; /* substitute, etc. */ - int (*func)(ledit_view *, char *, size_t); /* callback function */ -}; - -/* "" means catch-all, i.e. all keys with text are given to that callback */ -static struct key keys_en[] = { - {"y", 0, 0, CMD_SUBSTITUTE, &substitute_yes}, - {"Y", 0, 0, CMD_SUBSTITUTE, &substitute_yes_all}, - {"n", 0, 0, CMD_SUBSTITUTE, &substitute_no}, - {"N", 0, 0, CMD_SUBSTITUTE, &substitute_no_all}, - {NULL, XK_ANY_MOD, XK_Return, CMD_EDIT, &edit_submit}, - {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCH, &editsearch_submit}, - {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCHB, &editsearchb_submit}, - {NULL, 0, XK_Left, CMD_EDIT, &edit_cursor_left}, - {NULL, 0, XK_Left, CMD_EDITSEARCH, &edit_cursor_left}, - {NULL, 0, XK_Left, CMD_EDITSEARCHB, &edit_cursor_left}, - {NULL, 0, XK_Right, CMD_EDIT, &edit_cursor_right}, - {NULL, 0, XK_Right, CMD_EDITSEARCH, &edit_cursor_right}, - {NULL, 0, XK_Right, CMD_EDITSEARCHB, &edit_cursor_right}, - {NULL, 0, XK_Up, CMD_EDIT, &edit_prevcommand}, - {NULL, 0, XK_Up, CMD_EDITSEARCH, &edit_prevsearch}, - {NULL, 0, XK_Up, CMD_EDITSEARCHB, &edit_prevsearch}, - {NULL, 0, XK_Down, CMD_EDIT, &edit_nextcommand}, - {NULL, 0, XK_Down, CMD_EDITSEARCH, &edit_nextsearch}, - {NULL, 0, XK_Down, CMD_EDITSEARCHB, &edit_nextsearch}, - {NULL, 0, XK_BackSpace, CMD_EDIT, &edit_backspace}, - {NULL, 0, XK_BackSpace, CMD_EDITSEARCH, &edit_backspace}, - {NULL, 0, XK_BackSpace, CMD_EDITSEARCHB, &edit_backspace}, - {NULL, 0, XK_Delete, CMD_EDIT, &edit_delete}, - {NULL, 0, XK_Delete, CMD_EDITSEARCH, &edit_delete}, - {NULL, 0, XK_Delete, CMD_EDITSEARCHB, &edit_delete}, - {NULL, 0, XK_End, CMD_EDIT, &edit_cursor_to_end}, - {NULL, 0, XK_End, CMD_EDITSEARCH, &edit_cursor_to_end}, - {NULL, 0, XK_End, CMD_EDITSEARCHB, &edit_cursor_to_end}, - {NULL, 0, XK_Home, CMD_EDIT, &edit_cursor_to_beginning}, - {NULL, 0, XK_Home, CMD_EDITSEARCH, &edit_cursor_to_beginning}, - {NULL, 0, XK_Home, CMD_EDITSEARCHB, &edit_cursor_to_beginning}, - {NULL, 0, XK_Escape, CMD_EDIT, &edit_discard}, - {NULL, 0, XK_Escape, CMD_EDITSEARCH, &edit_discard}, - {NULL, 0, XK_Escape, CMD_EDITSEARCHB, &edit_discard}, - {"", 0, 0, CMD_EDIT, &edit_insert_text}, - {"", 0, 0, CMD_EDITSEARCH, &edit_insert_text}, - {"", 0, 0, CMD_EDITSEARCHB, &edit_insert_text} -}; - -static struct key keys_de[] = { - {"z", 0, 0, CMD_SUBSTITUTE, &substitute_yes}, - {"Z", 0, 0, CMD_SUBSTITUTE, &substitute_yes_all}, - {"n", 0, 0, CMD_SUBSTITUTE, &substitute_no}, - {"N", 0, 0, CMD_SUBSTITUTE, &substitute_no_all}, - {NULL, XK_ANY_MOD, XK_Return, CMD_EDIT, &edit_submit}, - {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCH, &editsearch_submit}, - {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCHB, &editsearchb_submit}, - {NULL, 0, XK_Left, CMD_EDIT, &edit_cursor_left}, - {NULL, 0, XK_Left, CMD_EDITSEARCH, &edit_cursor_left}, - {NULL, 0, XK_Left, CMD_EDITSEARCHB, &edit_cursor_left}, - {NULL, 0, XK_Right, CMD_EDIT, &edit_cursor_right}, - {NULL, 0, XK_Right, CMD_EDITSEARCH, &edit_cursor_right}, - {NULL, 0, XK_Right, CMD_EDITSEARCHB, &edit_cursor_right}, - {NULL, 0, XK_Up, CMD_EDIT, &edit_prevcommand}, - {NULL, 0, XK_Up, CMD_EDITSEARCH, &edit_prevsearch}, - {NULL, 0, XK_Up, CMD_EDITSEARCHB, &edit_prevsearch}, - {NULL, 0, XK_Down, CMD_EDIT, &edit_nextcommand}, - {NULL, 0, XK_Down, CMD_EDITSEARCH, &edit_nextsearch}, - {NULL, 0, XK_Down, CMD_EDITSEARCHB, &edit_nextsearch}, - {NULL, 0, XK_BackSpace, CMD_EDIT, &edit_backspace}, - {NULL, 0, XK_BackSpace, CMD_EDITSEARCH, &edit_backspace}, - {NULL, 0, XK_BackSpace, CMD_EDITSEARCHB, &edit_backspace}, - {NULL, 0, XK_Delete, CMD_EDIT, &edit_delete}, - {NULL, 0, XK_Delete, CMD_EDITSEARCH, &edit_delete}, - {NULL, 0, XK_Delete, CMD_EDITSEARCHB, &edit_delete}, - {NULL, 0, XK_End, CMD_EDIT, &edit_cursor_to_end}, - {NULL, 0, XK_End, CMD_EDITSEARCH, &edit_cursor_to_end}, - {NULL, 0, XK_End, CMD_EDITSEARCHB, &edit_cursor_to_end}, - {NULL, 0, XK_Home, CMD_EDIT, &edit_cursor_to_beginning}, - {NULL, 0, XK_Home, CMD_EDITSEARCH, &edit_cursor_to_beginning}, - {NULL, 0, XK_Home, CMD_EDITSEARCHB, &edit_cursor_to_beginning}, - {NULL, 0, XK_Escape, CMD_EDIT, &edit_discard}, - {NULL, 0, XK_Escape, CMD_EDITSEARCH, &edit_discard}, - {NULL, 0, XK_Escape, CMD_EDITSEARCHB, &edit_discard}, - {"", 0, 0, CMD_EDIT, &edit_insert_text}, - {"", 0, 0, CMD_EDITSEARCH, &edit_insert_text}, - {"", 0, 0, CMD_EDITSEARCHB, &edit_insert_text} -}; - -static struct key keys_ur[] = { - {"ے", 0, 0, CMD_SUBSTITUTE, &substitute_yes}, - {"َ", 0, 0, CMD_SUBSTITUTE, &substitute_yes_all}, - {"ن", 0, 0, CMD_SUBSTITUTE, &substitute_no}, - {"ں", 0, 0, CMD_SUBSTITUTE, &substitute_no_all}, - {NULL, XK_ANY_MOD, XK_Return, CMD_EDIT, &edit_submit}, - {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCH, &editsearch_submit}, - {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCHB, &editsearchb_submit}, - {NULL, 0, XK_Left, CMD_EDIT, &edit_cursor_left}, - {NULL, 0, XK_Left, CMD_EDITSEARCH, &edit_cursor_left}, - {NULL, 0, XK_Left, CMD_EDITSEARCHB, &edit_cursor_left}, - {NULL, 0, XK_Right, CMD_EDIT, &edit_cursor_right}, - {NULL, 0, XK_Right, CMD_EDITSEARCH, &edit_cursor_right}, - {NULL, 0, XK_Right, CMD_EDITSEARCHB, &edit_cursor_right}, - {NULL, 0, XK_Up, CMD_EDIT, &edit_prevcommand}, - {NULL, 0, XK_Up, CMD_EDITSEARCH, &edit_prevsearch}, - {NULL, 0, XK_Up, CMD_EDITSEARCHB, &edit_prevsearch}, - {NULL, 0, XK_Down, CMD_EDIT, &edit_nextcommand}, - {NULL, 0, XK_Down, CMD_EDITSEARCH, &edit_nextsearch}, - {NULL, 0, XK_Down, CMD_EDITSEARCHB, &edit_nextsearch}, - {NULL, 0, XK_BackSpace, CMD_EDIT, &edit_backspace}, - {NULL, 0, XK_BackSpace, CMD_EDITSEARCH, &edit_backspace}, - {NULL, 0, XK_BackSpace, CMD_EDITSEARCHB, &edit_backspace}, - {NULL, 0, XK_Delete, CMD_EDIT, &edit_delete}, - {NULL, 0, XK_Delete, CMD_EDITSEARCH, &edit_delete}, - {NULL, 0, XK_Delete, CMD_EDITSEARCHB, &edit_delete}, - {NULL, 0, XK_End, CMD_EDIT, &edit_cursor_to_end}, - {NULL, 0, XK_End, CMD_EDITSEARCH, &edit_cursor_to_end}, - {NULL, 0, XK_End, CMD_EDITSEARCHB, &edit_cursor_to_end}, - {NULL, 0, XK_Home, CMD_EDIT, &edit_cursor_to_beginning}, - {NULL, 0, XK_Home, CMD_EDITSEARCH, &edit_cursor_to_beginning}, - {NULL, 0, XK_Home, CMD_EDITSEARCHB, &edit_cursor_to_beginning}, - {NULL, 0, XK_Escape, CMD_EDIT, &edit_discard}, - {NULL, 0, XK_Escape, CMD_EDITSEARCH, &edit_discard}, - {NULL, 0, XK_Escape, CMD_EDITSEARCHB, &edit_discard}, - {"", 0, 0, CMD_EDIT, &edit_insert_text}, - {"", 0, 0, CMD_EDITSEARCH, &edit_insert_text}, - {"", 0, 0, CMD_EDITSEARCHB, &edit_insert_text} -}; - -static struct key keys_hi[] = { - {"य", 0, 0, CMD_SUBSTITUTE, &substitute_yes}, - {"ञ", 0, 0, CMD_SUBSTITUTE, &substitute_yes_all}, - {"न", 0, 0, CMD_SUBSTITUTE, &substitute_no}, - {"ण", 0, 0, CMD_SUBSTITUTE, &substitute_no_all}, - {NULL, XK_ANY_MOD, XK_Return, CMD_EDIT, &edit_submit}, - {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCH, &editsearch_submit}, - {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCHB, &editsearchb_submit}, - {NULL, 0, XK_Left, CMD_EDIT, &edit_cursor_left}, - {NULL, 0, XK_Left, CMD_EDITSEARCH, &edit_cursor_left}, - {NULL, 0, XK_Left, CMD_EDITSEARCHB, &edit_cursor_left}, - {NULL, 0, XK_Right, CMD_EDIT, &edit_cursor_right}, - {NULL, 0, XK_Right, CMD_EDITSEARCH, &edit_cursor_right}, - {NULL, 0, XK_Right, CMD_EDITSEARCHB, &edit_cursor_right}, - {NULL, 0, XK_Up, CMD_EDIT, &edit_prevcommand}, - {NULL, 0, XK_Up, CMD_EDITSEARCH, &edit_prevsearch}, - {NULL, 0, XK_Up, CMD_EDITSEARCHB, &edit_prevsearch}, - {NULL, 0, XK_Down, CMD_EDIT, &edit_nextcommand}, - {NULL, 0, XK_Down, CMD_EDITSEARCH, &edit_nextsearch}, - {NULL, 0, XK_Down, CMD_EDITSEARCHB, &edit_nextsearch}, - {NULL, 0, XK_BackSpace, CMD_EDIT, &edit_backspace}, - {NULL, 0, XK_BackSpace, CMD_EDITSEARCH, &edit_backspace}, - {NULL, 0, XK_BackSpace, CMD_EDITSEARCHB, &edit_backspace}, - {NULL, 0, XK_Delete, CMD_EDIT, &edit_delete}, - {NULL, 0, XK_Delete, CMD_EDITSEARCH, &edit_delete}, - {NULL, 0, XK_Delete, CMD_EDITSEARCHB, &edit_delete}, - {NULL, 0, XK_End, CMD_EDIT, &edit_cursor_to_end}, - {NULL, 0, XK_End, CMD_EDITSEARCH, &edit_cursor_to_end}, - {NULL, 0, XK_End, CMD_EDITSEARCHB, &edit_cursor_to_end}, - {NULL, 0, XK_Home, CMD_EDIT, &edit_cursor_to_beginning}, - {NULL, 0, XK_Home, CMD_EDITSEARCH, &edit_cursor_to_beginning}, - {NULL, 0, XK_Home, CMD_EDITSEARCHB, &edit_cursor_to_beginning}, - {NULL, 0, XK_Escape, CMD_EDIT, &edit_discard}, - {NULL, 0, XK_Escape, CMD_EDITSEARCH, &edit_discard}, - {NULL, 0, XK_Escape, CMD_EDITSEARCHB, &edit_discard}, - {"", 0, 0, CMD_EDIT, &edit_insert_text}, - {"", 0, 0, CMD_EDITSEARCH, &edit_insert_text}, - {"", 0, 0, CMD_EDITSEARCHB, &edit_insert_text} -}; - -GEN_KEY_ARRAY(struct key, keys_en, keys_de, keys_hi, keys_ur); diff --git a/keys_config.h b/keys_config.h @@ -1,31 +1,182 @@ #ifndef _KEYS_CONFIG_H_ #define _KEYS_CONFIG_H_ +#include "X11/Xlib.h" +#include "X11/keysym.h" + +#include "common.h" #include "keys.h" -/* - * These are the language strings compared with the language strings that - * xkb gives in order to change the key mapping on layout change events. - */ -#define KEY_LANGS \ -static char *key_langs[] = { \ - "English (US)", \ - "German", \ - "Urdu (Pakistan)", \ - "Hindi (Bolnagri)" \ -} - -#define GEN_KEY_ARRAY(key_struct, en, de, ur, hi) \ -static struct { \ - key_struct *keys; \ - int num_keys; \ -} keys[] = { \ - {en, LENGTH(en)}, \ - {de, LENGTH(de)}, \ - {ur, LENGTH(ur)}, \ - {hi, LENGTH(hi)} \ -} - -#define LANG_KEYS(index) &keys[index] - -#endif +/********************************* + * Key and command configuration * + *********************************/ + +char *language_default = "English (US)"; + +/* FIXME: binary search keys */ +struct { + char *func_name; + char *text; + unsigned int mods; + KeySym keysym; + ledit_mode modes; +} basic_keys_default[] = { + {"backspace", NULL, 0, XK_BackSpace, INSERT}, + {"cursor-left", NULL, 0, XK_Left, VISUAL|INSERT|NORMAL}, + {"cursor-right", NULL, 0, XK_Right, VISUAL|INSERT|NORMAL}, + {"cursor-up", NULL, 0, XK_Up, VISUAL|INSERT|NORMAL}, + {"cursor-down", NULL, 0, XK_Down, VISUAL|INSERT|NORMAL}, + {"return-key", NULL, XK_ANY_MOD, XK_Return, INSERT}, + {"delete-key", NULL, 0, XK_Delete, INSERT}, + {"escape-key", NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT}, + {"enter-insert", "i", 0, 0, NORMAL|VISUAL}, + {"cursor-left", "h", 0, 0, NORMAL|VISUAL}, + {"cursor-left", "h", ControlMask, 0, NORMAL|VISUAL}, + {"cursor-right", "l", 0, 0, NORMAL|VISUAL}, + {"cursor-up", "k", 0, 0, NORMAL|VISUAL}, + {"cursor-down", "j", 0, 0, NORMAL|VISUAL}, + {"toggle-hard-line-based", "t", ControlMask, 0, NORMAL|VISUAL}, /* FIXME: also in insert */ + {"cursor-right", NULL, 0, XK_space, NORMAL|VISUAL}, + {"cursor-down", "j", ControlMask, 0, NORMAL|VISUAL}, + {"cursor-down", "n", ControlMask, 0, NORMAL|VISUAL}, + {"cursor-up", "p", ControlMask, 0, NORMAL|VISUAL}, + {"key-0", "0", 0, 0, NORMAL|VISUAL}, + {"push-1", "1", 0, 0, NORMAL|VISUAL}, + {"push-2", "2", 0, 0, NORMAL|VISUAL}, + {"push-3", "3", 0, 0, NORMAL|VISUAL}, + {"push-4", "4", 0, 0, NORMAL|VISUAL}, + {"push-5", "5", 0, 0, NORMAL|VISUAL}, + {"push-6", "6", 0, 0, NORMAL|VISUAL}, + {"push-7", "7", 0, 0, NORMAL|VISUAL}, + {"push-8", "8", 0, 0, NORMAL|VISUAL}, + {"push-9", "9", 0, 0, NORMAL|VISUAL}, + {"delete-forwards", "x", 0, 0, NORMAL}, + {"delete-backwards", "X", 0, 0, NORMAL}, + {"delete", "d", 0, 0, NORMAL|VISUAL}, + {"yank", "y", 0, 0, NORMAL|VISUAL}, + {"yank-lines", "Y", 0, 0, NORMAL}, + {"change", "c", 0, 0, NORMAL|VISUAL}, + {"enter-visual", "v", 0, 0, NORMAL}, + {"switch-selection-end", "o", 0, 0, VISUAL}, + {"clipboard-copy", "c", ControlMask, 0, VISUAL}, + {"clipboard-paste", "v", ControlMask, 0, INSERT}, + {"show-line", "g", ControlMask, 0, NORMAL|VISUAL}, + {"enter-commandedit", ":", 0, 0, NORMAL|VISUAL}, + {"enter-searchedit-backwards", "?", 0, 0, NORMAL}, + {"enter-searchedit-forwards", "/", 0, 0, NORMAL}, + {"search-next", "n", 0, 0, NORMAL}, + {"search-previous", "N", 0, 0, NORMAL}, + {"undo", "u", 0, 0, NORMAL}, + {"redo", "U", 0, 0, NORMAL}, + {"repeat-command", ".", 0, 0, NORMAL}, /* FIXME: only allow after finished key sequence */ + {"undo", "z", ControlMask, 0, INSERT}, + {"redo", "y", ControlMask, 0, INSERT}, /* FIXME: this is confusing with ctrl-y in normal mode */ + {"screen-up", "b", ControlMask, 0, NORMAL}, + {"screen-down", "f", ControlMask, 0, NORMAL}, + {"scroll-with-cursor-down", "e", ControlMask, 0, NORMAL}, + {"scroll-with-cursor-up", "y", ControlMask, 0, NORMAL}, + {"scroll-lines-down", "d", ControlMask, 0, NORMAL}, + {"scroll-lines-up", "u", ControlMask, 0, NORMAL}, + {"move-to-eol", "$", 0, 0, NORMAL|VISUAL}, + {"next-word", "w", 0, 0, NORMAL|VISUAL}, + {"next-word-end", "e", 0, 0, NORMAL|VISUAL}, + {"next-bigword", "W", 0, 0, NORMAL|VISUAL}, + {"next-bigword-end", "E", 0, 0, NORMAL|VISUAL}, + {"previous-word", "b", 0, 0, NORMAL|VISUAL}, + {"previous-bigword", "B", 0, 0, NORMAL|VISUAL}, + {"move-to-line", "G", 0, 0, NORMAL|VISUAL}, + {"join-lines", "J", 0, 0, NORMAL}, + {"insert-at-beginning", "I", 0, 0, NORMAL}, + {"paste-normal", "p", 0, 0, NORMAL}, + {"paste-normal-backwards", "P", 0, 0, NORMAL}, + {"append-after-eol", "A", 0, 0, NORMAL}, + {"append-after-cursor", "a", 0, 0, NORMAL}, + {"append-line-above", "O", 0, 0, NORMAL}, + {"append-line-below", "o", 0, 0, NORMAL}, + {"mark-line", "m", 0, 0, NORMAL|VISUAL}, + {"jump-to-mark", "'", 0, 0, NORMAL|VISUAL}, + {"change-to-eol", "C", 0, 0, NORMAL}, + {"delete-to-eol", "D", 0, 0, NORMAL}, + {"replace", "r", 0, 0, NORMAL}, + {"cursor-to-first-non-whitespace", "^", 0, 0, NORMAL}, + {"find-next-char-forwards", "t", 0, 0, NORMAL|VISUAL}, + {"find-next-char-backwards", "T", 0, 0, NORMAL|VISUAL}, + {"find-char-forwards", "f", 0, 0, NORMAL|VISUAL}, + {"find-char-backwards", "F", 0, 0, NORMAL|VISUAL}, + {"insert-text", "", 0, 0, INSERT} +}; + +struct { + char *func_name; + char *text; + unsigned int mods; + KeySym keysym; + command_mode modes; +} command_keys_default[] = { + {"substitute-yes", "y", 0, 0, CMD_SUBSTITUTE}, + {"substitute-yes-all", "Y", 0, 0, CMD_SUBSTITUTE}, + {"substitute-no", "n", 0, 0, CMD_SUBSTITUTE}, + {"substitute-no-all", "N", 0, 0, CMD_SUBSTITUTE}, + {"edit-submit", NULL, XK_ANY_MOD, XK_Return, CMD_EDIT}, + {"edit-submit-search", NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCH}, + {"edit-submit-backwards-search", NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCHB}, + {"edit-cursor-left", NULL, 0, XK_Left, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-cursor-right", NULL, 0, XK_Right, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-cursor-right", NULL, 0, XK_Right, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-previous-command", NULL, 0, XK_Up, CMD_EDIT}, + {"edit-next-command", NULL, 0, XK_Down, CMD_EDIT}, + {"edit-previous-search", NULL, 0, XK_Up, CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-next-search", NULL, 0, XK_Down, CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-backspace", NULL, 0, XK_BackSpace, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-delete", NULL, 0, XK_Delete, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-cursor-to-end", NULL, 0, XK_End, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-cursor-to-beginning", NULL, 0, XK_Home, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-discard", NULL, 0, XK_Escape, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}, + {"edit-insert-text", "", 0, 0, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB} +}; + +struct { + char *func_name; + char *text; +} commands_default[] = { + {"write-quit", "wg"}, + {"write", "w"}, + {"quit", "q"}, + {"create-view", "v"}, + {"close-view", "c"}, + {"substitute", "s"} +}; + +/***************************************** + * Key and command mapping configuration * + *****************************************/ + +struct mapping { + char *from; + char *to; +}; + +struct language_mapping { + char *lang; + struct mapping *keys; + struct mapping *cmds; + size_t keys_len; + size_t cmds_len; +}; + +struct mapping key_mapping_de[] = { + {"z", "y"}, + {"y", "z"}, + {"Z", "Y"}, + {"Y", "Z"}, + {"Ö", ":"}, + {"_", "?"}, + {"-", "/"}, + {"ä", "'"} +}; + +struct language_mapping mappings_default[] = { + {"German", key_mapping_de, NULL, LENGTH(key_mapping_de), 0} +}; + +#endif /* _KEYS_CONFIG_H_ */ diff --git a/ledit.1 b/ledit.1 @@ -1,6 +1,6 @@ .\" WARNING: Some parts of this are stolen shamelessly from OpenBSD's .\" vi(1) manpage! -.Dd December 26, 2021 +.Dd May 26, 2022 .Dt LEDIT 1 .Os .Sh NAME @@ -8,6 +8,7 @@ .Nd weird text editor .Sh SYNOPSIS .Nm +.Op Fl c Ar config .Op Ar file .Sh DESCRIPTION .Nm @@ -45,6 +46,17 @@ that is not the main goal. .Pp .Nm is not a good text editor. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c Ar config +Load the configuration file given by +.Ar config . +If this option is not specified, +.Nm +will attempt to read the configuration file +.Pa .leditrc +in the user's home directory before using the defaults. +.El .Sh BASIC CONCEPTS Some terminology should probably be explained in order to understand the rest of this manual. @@ -693,12 +705,6 @@ Also note that the commands which take filenames currently use the entire rest o the line as the filename instead of doing any string parsing. This may be changed in the future. .Pp -The commands do not fit in very well with the rest of the program since they -are hard-coded in English and cannot be remapped in other languages. -This is because it isn't entirely clear how a remapping would even work -because the commands are shown on screen, which might look weird, especially -if they are remapped to non-printable characters. -.Pp .Bl -tag -width Ds -compact .It Xo .Cm :w @@ -819,71 +825,8 @@ Note that the text is pasted at the current cursor position, not the position of the mouse cursor. The author prefers this way. .Sh CONFIGURATION -.Nm -currently has to be configured entirely by changing header files and -recompiling. -This will probably be changed in the future, but there hasn't been -time for it yet. -.Pp -There are five configuration headers: -.Bl -tag -width Ds -.It Pa config.h -This contains several timing parameters that most users will probably -not need to change. -.It Pa keys_config.h -This contains the list of languages for which keys are available. -The language strings in -.Va key_langs -are matched exactly with the strings returned by XKB, which seem to -sometimes be different on different systems, so they will probably -need to be configured properly. Also note that there are many -variants of some keyboard layouts, all with small differences, so -the mappings will often have to be adjusted slightly. -.It Pa keys_basic_config.h -This contains the keys used during regular text editing. -The first entry in each key is the text that is associated with the text. -If this is -.Dv NULL , -the third entry, which may contain a symbolic key name, is used for the -matching instead. -If the key text is an empty string, it is a catch-all, and the actual -text is given to the handling function. -.Pp -Note that the list of keys is currently traversed from top to bottom, -so catch-all keys should all be in the end in order to not inferfere -with other mappings. -.Pp -The second entry is a mask of modifier keys that need to be -pressed during the key press. -If this is set to -.Dv XK_ANY_MOD , -the modifier keys are ignored. -.Pp -The other entries should not be touched unless the actual handling -function implemented in -.Pa keys_basic.c -has been changed. -.Pp -Note that the key handling is currently a bit weird, so there might -be unexpected results sometimes. -Please report these. -.It Pa keys_command_config.h -This is similar to -.Pa keys_basic_config.h , -but contains the keys used during line editing mode or while performing -commands such as substitution with confirmation. -.It Pa theme_config.h -This contains the configuration of the theme. -The configuration options are described in the file itself. -.El -.Pp -This short explanation of the configuration is not very good currently. -That will hopefully be changed in the future. -.Pp -Note that there are a few actions that are currently hard-coded and cannot -be configured. -This includes all mouse actions and using escape to cancel a multi-key -command. +See +.Xr leditrc 5 . .Sh MISCELLANEOUS .Nm includes a fair number of sanity checks (asserts) to make sure some illegal @@ -930,7 +873,8 @@ Pango developers decided to hide spaces at the end of a line. .Sh SEE ALSO .Xr ed 1 , .Xr vi 1 , -.Xr vim 1 +.Xr vim 1 , +.Xr leditrc 5 .Sh AUTHORS .An lumidify Aq Mt nobody@lumidify.org .Sh BUGS @@ -938,8 +882,4 @@ Too many to count. See .Sx TINY SUBSET OF BUGS . .Sh TINY SUBSET OF BUGS -The keyboard mapping is currently only changed when a keyboard change event -occurs. -This means that it always is the default mapping when -.Nm -starts, regardless of the current keyboard layout. +Well, I guess I'm too lazy to collect bugs right now. diff --git a/ledit.c b/ledit.c @@ -10,6 +10,7 @@ /* FIXME: horizontal scrolling (also need cache to avoid too large pixmaps) */ /* TODO: allow extending selection with shift+mouse like in e.g. gtk */ +#include <pwd.h> #include <time.h> #include <errno.h> #include <stdio.h> @@ -24,8 +25,8 @@ #include <X11/extensions/Xdbe.h> #include <X11/extensions/XKBrules.h> +#include "util.h" #include "view.h" -#include "theme.h" #include "buffer.h" #include "common.h" #include "window.h" @@ -37,6 +38,7 @@ #include "keys.h" #include "keys_basic.h" #include "keys_command.h" +#include "configparser.h" static void mainloop(void); static void setup(int argc, char *argv[]); @@ -45,10 +47,9 @@ static void redraw(void); static void change_keyboard(char *lang); static void key_press(ledit_view *view, XEvent *event); -ledit_theme *theme = NULL; ledit_buffer *buffer = NULL; ledit_common common; -int cur_lang = 0; +size_t cur_lang = 0; static void mainloop(void) { @@ -76,7 +77,7 @@ mainloop(void) { ); XSync(common.dpy, False); int running = 1; - int change_kbd = 0; + int change_kbd = 1; redraw(); /* store last draw time so framerate can be limited */ @@ -193,13 +194,33 @@ mainloop(void) { } } +extern char *optarg; +extern int optind; + static void setup(int argc, char *argv[]) { setlocale(LC_CTYPE, ""); XSetLocaleModifiers(""); + char c; + char *opt_filename = NULL; + while ((c = getopt(argc, argv, "c:")) != -1) { + switch (c) { + case 'c': + opt_filename = optarg; + break; + default: + fprintf(stderr, "USAGE: ledit [-c config] [file]\n"); + exit(1); + break; + } + } + argc -= optind; + argv += optind; + common.dpy = XOpenDisplay(NULL); common.screen = DefaultScreen(common.dpy); + /* FIXME: fallback when no db support */ /* based on http://wili.cc/blog/xdbe.html */ int major, minor; if (XdbeQueryExtension(common.dpy, &major, &minor)) { @@ -233,6 +254,8 @@ setup(int argc, char *argv[]) { exit(1); } common.vis = xvisinfo_match->visual; + XFree(xvisinfo_match); + XdbeFreeVisualInfo(info); } else { fprintf(stderr, "No Xdbe support.\n"); ledit_cleanup(); @@ -242,15 +265,83 @@ setup(int argc, char *argv[]) { common.depth = DefaultDepth(common.dpy, common.screen); common.cm = DefaultColormap(common.dpy, common.screen); - theme = theme_create(&common); + #ifdef LEDIT_DEBUG + struct timespec now, elapsed, last; + clock_gettime(CLOCK_MONOTONIC, &last); + #endif + + char *stat_errstr = NULL, *load_errstr = NULL, *load_default_errstr = NULL; + char *cfgfile = NULL; + if (!opt_filename) { + uid_t uid = getuid(); + struct passwd *pw = getpwuid(uid); + if (!pw) + fprintf(stderr, "Unable to determine home directory\n"); + else + cfgfile = ledit_strcat(pw->pw_dir, "/.leditrc"); + struct stat cfgst; + if (stat(cfgfile, &cfgst)) { + free(cfgfile); + cfgfile = NULL; + } + } else { + struct stat cfgst; + if (stat(opt_filename, &cfgst)) { + stat_errstr = print_fmt("Unable to load configuration file '%s'", opt_filename); + fprintf(stderr, "%s\n", stat_errstr); + } else { + cfgfile = ledit_strdup(opt_filename); + } + } + if (config_loadfile(&common, cfgfile, &load_errstr)) { + fprintf(stderr, "%s\n", load_errstr); + int failure = 1; + if (cfgfile) { + /* retry with default config */ + failure = config_loadfile(&common, NULL, &load_default_errstr); + } + if (failure) { + fprintf(stderr, "Unable to load configuration: %s\n", load_errstr); + if (load_default_errstr) + fprintf(stderr, "Also unable to load default configuration: %s\n", load_default_errstr); + free(stat_errstr); + free(load_errstr); + free(load_default_errstr); + ledit_cleanup(); + exit(1); + } + } + free(load_default_errstr); + free(cfgfile); + + #ifdef LEDIT_DEBUG + clock_gettime(CLOCK_MONOTONIC, &now); + ledit_timespecsub(&now, &last, &elapsed); + ledit_debug_fmt("Time to load config (total): %lld seconds, %ld nanoseconds\n", (long long)elapsed.tv_sec, elapsed.tv_nsec); + #endif + buffer = buffer_create(&common); - buffer_add_view(buffer, theme, NORMAL, 0, 0, 0); + buffer_add_view(buffer, NORMAL, 0, 0, 0); /* FIXME: don't access view directly here */ ledit_view *view = buffer->views[0]; + /* FIXME: this message may be wiped immediately */ + /* -> maybe allow showing multiple messages? */ + /* currently, the more important message is just prioritized */ + int show_error = 0; + if (stat_errstr || load_errstr) { + show_error = 1; + if (stat_errstr) + window_show_message(view->window, stat_errstr, -1); + else if (load_errstr) + window_show_message(view->window, load_errstr, -1); + free(stat_errstr); + free(load_errstr); + } view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + /* FIXME: maybe also log all errors instead of just showing them on screen? */ /* FIXME: Support multiple buffers/files */ /* FIXME: check if file may be binary */ - if (argc > 1) { + if (argc >= 1) { /* FIXME: move this to different file */ char *load_err; struct stat sb; @@ -258,7 +349,7 @@ setup(int argc, char *argv[]) { int readonly = 0; int error = 0; /* FIXME: maybe copy vi and open file in /tmp by default? */ - if (stat(argv[1], &sb)) { + if (stat(argv[0], &sb)) { if (errno == ENOENT) { /* note that there may still be a failure when trying to write if a directory in @@ -267,32 +358,35 @@ setup(int argc, char *argv[]) { } else { window_show_message_fmt( view->window, "Error opening file '%s': %s", - argv[1], strerror(errno) + argv[0], strerror(errno) ); error = 1; } } - if (access(argv[1], W_OK)) { + if (access(argv[0], W_OK)) { readonly = 1; } if (!newfile) { - if (buffer_load_file(buffer, argv[1], 0, &load_err)) { + if (buffer_load_file(buffer, argv[0], 0, &load_err)) { window_show_message_fmt( view->window, "Error opening file '%s': %s", - argv[1], load_err + argv[0], load_err ); error = 1; } buffer->file_mtime = sb.st_mtim; } if (!error) { - buffer->filename = ledit_strdup(argv[1]); - if (newfile) { - window_show_message_fmt(view->window, "%s: new file", argv[1]); - } else if (readonly) { - window_show_message_fmt(view->window, "%s: readonly", argv[1]); - } else { - window_show_message(view->window, argv[1], -1); + buffer->filename = ledit_strdup(argv[0]); + /* FIXME: show this *in addition* to error */ + if (!show_error) { + if (newfile) { + window_show_message_fmt(view->window, "%s: new file", argv[0]); + } else if (readonly) { + window_show_message_fmt(view->window, "%s: readonly", argv[0]); + } else { + window_show_message(view->window, argv[0], -1); + } } } } @@ -302,6 +396,8 @@ setup(int argc, char *argv[]) { void ledit_emergencydump(void) { + /* FIXME: pre-allocate memory for template to avoid memory errors? + -> probably overkill since something else will fail anyways */ if (!buffer) return; /* FIXME: maybe write assertion message to file? */ @@ -355,8 +451,7 @@ ledit_cleanup(void) { command_key_cleanup(); if (buffer) buffer_destroy(buffer); - if (theme) - theme_destroy(&common, theme); + config_cleanup(&common); XCloseDisplay(common.dpy); } @@ -369,8 +464,8 @@ redraw(void) { static void change_keyboard(char *lang) { - cur_lang = get_language_index(lang); - if (cur_lang < 0) { + ledit_debug_fmt("New keyboard layout: %s\n", lang); + if (config_get_language_index(lang, &cur_lang)) { for (size_t i = 0; i < buffer->views_num; i++) { window_show_message_fmt( buffer->views[i]->window, diff --git a/leditrc.5 b/leditrc.5 @@ -0,0 +1,347 @@ +.Dd May 26, 2022 +.Dt LEDITRC 5 +.Os +.Sh NAME +.Nm leditrc +.Nd configuration file for +.Xr ledit 1 +.Sh DESCRIPTION +.Nm +is the configuration file for the text editor +.Xr ledit 1 , +which can be used to configure the theme and key bindings used. +.Pp +The parser recognizes four different types of structures: +strings, lists, statements, and assignments. +.Pp +A string is simply any sequence of characters surrounded by double quotes. +Double quotes must be backslash-escaped. +If a string does not contain any whitespace or the special +characters +.Sq \&" , +.Sq { , +.Sq } , +or +.Sq = , +the double quotes are not required. +.Pp +A statement is a sequence of strings, separated by whitespace and +all on the same line. +.Pp +An assignment is of the form +.Aq identifier += +.Aq structure , +where +.Aq identifier +is a string and +.Aq structure +is a string or a list. +.Pp +A list is a sequence of assignments and/or statements that is +enclosed by curly braces. +The assignments/statements must be separated by newlines. +.Pp +The configuration file consists of several top-level assignments +which are described in the following sections. +.Sh THEME +The theme may be configured by assigning +.Ar theme +to a list of assignments, each of which sets one of the following +possible properties. +Colors are given in the form #RRGGBB, where the +.Sq # +is optional (mainly because +.Sq # +also starts comments in the configuration file format). +.Bl -tag -width Ds +.It Ar text-font +Font used for all text. +Default: Monospace +.It Ar text-size +Text size (in points or whatever pango uses). +Default: 12 +.It Ar text-fg +Text color in main editing area. +Default: #000000 +.It Ar text-bg +Background color in main editing area. +Default: #FFFFFF +.It Ar cursor-fg +Color of text under cursor. +Default: #FFFFFF +.It Ar cursor-bg +Color of text cursor. +Default: #000000 +.It Ar selection-fg +Color of selected text. +Default: #FFFFFF +.It Ar selection-bg +Color of selection. +Default: #000000 +.It Ar bar-fg +Color of text in status bar/line editor. +Default: #000000 +.It Ar bar-bg +Background color of status bar/line editor. +Default: #CCCCCC +.It Ar bar-cursor +Color of text cursor in line editor. +Default: #000000 +.It Ar scrollbar-width +Width of scrollbar in pixels. +Default: 10 +.It Ar scrollbar-step +Number of pixels scrolled with each scroll event. +Default: 20 +.It Ar scrollbar-bg +Background color of scrollbar. +Default: #CCCCCC +.It Ar scrollbar-fg +Color of scrollbar handle. +Default: #000000 +.El +.Sh BINDINGS +The key bindings may be configured by assigning +.Ar bindings +to a list of the following assignments. +.Bl -tag -width Ds +.It Ar language +.Pp +This is the language string for the key layout, as given by XKB. +.It Ar basic-keys +.Pp +This is a list of statements of the form +.Pp +.Sy bind +.Aq func_name +.Op Sy keysym Aq keysym +.Op Sy text Aq text +.Op Sy catchall +.Op Sy modes Aq modes +.Op Sy mods Aq mods +.Pp +.Sy keysym +is the symbolic description for a key, such as +.Ar space . +The full list is not documented yet (FIXME!). +.Sy text +is the text corresponding to a key. +.Sy catchall +is a catchall for any key which can for instance be used to insert text. +Note that a key binding containing +.Sy catchall +should always be at the end of the list so it does not prevent +any other key bindings from being used. +.Pp +Exactly one of +.Sy text , +.Sy keysym , +and +.Sy catchall +must be specified. +.Pp +.Sy mods +specifies modifier keys. +The current options are +.Ar shift , +.Ar lock , +.Ar control , +.Ar mod1 , +.Ar mod2 , +.Ar mod3 , +.Ar mod4 , +.Ar mod5 , +and +.Ar any . +.Pp +.Sy modes +specifies the allowed modes and can be a combination of +.Ar normal , +.Ar visual , +and +.Ar insert . +.Pp +Multiple mods or modes can be given by joining them with +.Sq | . +.Pp +.Aq func_name +may be one of the following functions. +The possible modes are listed beside the function names. +.Bl -tag -width Ds +.It Ar append-after-cursor Op normal +.It Ar append-after-eol Op normal +.It Ar append-line-above Op normal +.It Ar append-line-below Op normal +.It Ar backspace Op insert +.It Ar change Op normal, visual +.It Ar change-to-eol Op normal +.It Ar clipboard-copy Op visual +.It Ar clipboard-paste Op insert +.It Ar cursor-down Op normal, visual, insert +.It Ar cursor-left Op normal, visual, insert +.It Ar cursor-right Op normal, visual, insert +.It Ar cursor-to-beginning Op normal, visual +.It Ar cursor-to-first-non-whitespace Op normal +.It Ar cursor-up Op normal, visual, insert +.It Ar delete Op normal, visual +.It Ar delete-backwards Op normal +.It Ar delete-forwards Op normal +.It Ar delete-key Op insert +.It Ar delete-to-eol Op normal +.It Ar enter-commandedit Op normal, visual +.It Ar enter-insert Op normal, visual +.It Ar enter-searchedit-backwards Op normal +.It Ar enter-searchedit-forwards Op normal +.It Ar enter-visual Op normal +.It Ar escape-key Op normal, visual, insert +.It Ar find-char-backwards Op normal, visual +.It Ar find-char-forwards Op normal, visual +.It Ar find-next-char-backwards Op normal, visual +.It Ar find-next-char-forwards Op normal, visual +.It Ar insert-at-beginning Op normal +.It Ar insert-text Op insert +.It Ar join-lines Op normal +.It Ar jump-to-mark Op normal, visual +.It Ar key-0 Op normal, visual +.It Ar mark-line Op normal, visual +.It Ar move-to-eol Op normal, visual +.It Ar move-to-line Op normal, visual +.It Ar next-bigword Op normal, visual +.It Ar next-bigword-end Op normal, visual +.It Ar next-word Op normal, visual +.It Ar next-word-end Op normal, visual +.It Ar paste-normal Op normal +.It Ar paste-normal-backwards normal +.It Ar previous-bigword Op normal, visual +.It Ar previous-word Op normal, visual +.It Ar push-0 Op normal, visual +.It Ar push-1 Op normal, visual +.It Ar push-2 Op normal, visual +.It Ar push-3 Op normal, visual +.It Ar push-4 Op normal, visual +.It Ar push-5 Op normal, visual +.It Ar push-6 Op normal, visual +.It Ar push-7 Op normal, visual +.It Ar push-8 Op normal, visual +.It Ar push-9 Op normal, visual +.It Ar redo Op normal, insert +.It Ar repeat-command Op normal +.It Ar replace Op normal +.It Ar return-key Op insert +.It Ar screen-down Op normal +.It Ar screen-up Op normal +.It Ar scroll-lines-down Op normal +.It Ar scroll-lines-up Op normal +.It Ar scroll-with-cursor-down Op normal +.It Ar scroll-with-cursor-up Op normal +.It Ar search-next Op normal +.It Ar search-previous Op normal +.It Ar show-line Op normal, visual +.It Ar switch-selection-end Op visual +.It Ar toggle-hard-line-based Op normal, visual +.It Ar undo Op normal, insert +.It Ar yank Op normal, visual +.It Ar yank-lines Op normal +.El +.Pp +Note that some of these functions should work in other modes +as well, but I still need to fix that (FIXME!). +.It Ar command-keys +.Pp +This is the same as +.Ar basic-keys , +except that +.Sy modes +must be a combination of +.Ar substitute , +.Ar edit , +.Ar edit-search , +and +.Ar edit-search-backwards . +.Pp +The possible functions are given in the following list, with +the possible modes listed beside each function. +.Bl -tag -width Ds +.It Ar edit-backspace Op edit, edit-search, edit-search-backwards +.It Ar edit-cursor-left Op edit, edit-search, edit-search-backwards +.It Ar edit-cursor-right Op edit, edit-search, edit-search-backwards +.It Ar edit-cursor-to-beginning Op edit, edit-search, edit-search-backwards +.It Ar edit-cursor-to-end Op edit, edit-search, edit-search-backwards +.It Ar edit-delete Op edit, edit-search, edit-search-backwards +.It Ar edit-discard Op edit, edit-search, edit-search-backwards +.It Ar edit-insert-text Op edit, edit-search, edit-search-backwards +.It Ar edit-next-command Op edit +.It Ar edit-next-search Op edit-search, edit-search-backwards +.It Ar edit-previous-command Op edit +.It Ar edit-previous-search Op edit-search, edit-search-backwards +.It Ar edit-submit Op edit +.It Ar edit-submit-backwards-search Op edit-search-backwards +.It Ar edit-submit-search Op edit-search +.It Ar substitute-no Op substitute +.It Ar substitute-no-all Op substitute +.It Ar substitute-yes Op substitute +.It Ar substitute-yes-all Op substitute +.El +.It Ar commands +.Pp +This is a list of statements of the form +.Pp +.Sy bind +.Aq func_name +.Aq text +.Pp +The possible functions are given in the following list. +.Bl -tag -width Ds +.It Ar close-view +.It Ar create-view +.It Ar quit +.It Ar substitute +.It Ar write +.It Ar write-quit +.El +.El +.Pp +If the +.Ar bindings +configuration or any part of it is left out, the +default is used. +.Sh LANGUAGE MAPPINGS +A language mapping defines a mapping for the text associated with each +key or command so the bindings still work with other keyboard layouts. +Language mappings may be defined by assigning +.Ar language-mapping +to a list of the following assignments, once for each language. +Note that any definition of +.Ar language-mapping +must come after +.Ar bindings . +.Bl -tag -width Ds +.It Ar language +.Pp +This is the language string for the key layout, as in +.Ar bindings . +.It Ar key-mapping +.Pp +This is a list of statements of the form +.Pp +.Sy map +.Aq foreign +.Aq native +.Pp +where +.Aq foreign +is the key text in the new language mapping and +.Aq native +is the key text given in +.Ar bindings . +.It Ar command-mapping +.Pp +This is the same as +.Ar key-mapping , +but for the commands. +.El +.Sh SEE ALSO +.Xr ledit 1 +.Sh AUTHORS +.An lumidify Aq Mt nobody@lumidify.org diff --git a/leditrc.example b/leditrc.example @@ -0,0 +1,300 @@ +theme = { + text-font = Monospace + text-size = 12 + text-fg = 000000 + text-bg = FFFFFF + cursor-fg = FFFFFF + cursor-bg = 000000 + selection-fg = ffffff + selection-bg = 000000 + bar-fg = 000000 + bar-bg = CCCCCC + bar-cursor = 000000 + scrollbar-width = 10 + scrollbar-step = 20 + scrollbar-bg = CCCCCC + scrollbar-fg = 000000 +} + +bindings = { + language = "English (US)" + basic-keys = { + bind backspace keysym backspace modes insert + bind cursor-left keysym left modes visual|insert|normal + bind cursor-right keysym right modes visual|insert|normal + bind cursor-up keysym up modes visual|insert|normal + bind cursor-down keysym down modes visual|insert|normal + bind return-key keysym return modes insert mods any + bind delete-key keysym delete modes insert mods any + bind escape-key keysym escape modes normal|visual|insert mods any + bind enter-insert text "i" modes normal|visual + bind cursor-left text "h" modes normal|visual + bind cursor-right text "l" modes normal|visual + bind cursor-down text "j" modes normal|visual + bind cursor-up text "k" modes normal|visual + bind cursor-left text "h" modes normal|visual mods control + bind toggle-hard-line-based text "t" modes normal|visual mods control + bind cursor-right keysym space modes normal|visual + bind cursor-down text "j" modes normal|visual mods control + bind cursor-down text "n" modes normal|visual mods control + bind cursor-up text "p" modes normal|visual mods control + bind key-0 text "0" modes normal|visual + bind push-1 text "1" modes normal|visual + bind push-2 text "2" modes normal|visual + bind push-3 text "3" modes normal|visual + bind push-4 text "4" modes normal|visual + bind push-5 text "5" modes normal|visual + bind push-6 text "6" modes normal|visual + bind push-7 text "7" modes normal|visual + bind push-8 text "8" modes normal|visual + bind push-9 text "9" modes normal|visual + bind delete-forwards text "x" modes normal + bind delete-backwards text "X" modes normal + bind delete text "d" modes normal|visual + bind yank text "y" modes normal|visual + bind yank-lines text "Y" modes normal + bind change text "c" modes normal|visual + bind enter-visual text "v" modes normal + bind switch-selection-end text "o" modes visual + bind clipboard-copy text "c" modes visual mods control + bind clipboard-paste text "v" modes insert mods control + bind show-line text "g" modes normal|visual mods control + bind enter-commandedit text ":" modes normal|visual + bind enter-searchedit-backwards text "?" modes normal + bind enter-searchedit-forwards text "/" modes normal + bind search-next text "n" modes normal + bind search-previous text "N" modes normal + bind undo text "u" modes normal + bind redo text "U" modes normal + bind repeat-command text "." modes normal + bind undo text "z" modes insert mods control + bind redo text "y" modes insert mods control + bind screen-up text "b" modes normal mods control + bind screen-down text "f" modes normal mods control + bind scroll-with-cursor-down text "e" modes normal mods control + bind scroll-with-cursor-up text "y" modes normal mods control + bind scroll-lines-down text "d" modes normal mods control + bind scroll-lines-up text "u" modes normal mods control + bind move-to-eol text "$" modes normal|visual + bind next-word text "w" modes normal|visual + bind next-word-end text "e" modes normal|visual + bind next-bigword text "W" modes normal|visual + bind next-bigword-end text "E" modes normal|visual + bind previous-word text "b" modes normal|visual + bind previous-bigword text "B" modes normal|visual + bind move-to-line text "G" modes normal|visual + bind join-lines text "J" modes normal + bind insert-at-beginning text "I" modes normal + bind paste-normal text "p" modes normal + bind paste-normal-backwards text "P" modes normal + bind append-after-eol text "A" modes normal + bind append-after-cursor text "a" modes normal + bind append-line-above text "O" modes normal + bind append-line-below text "o" modes normal + bind mark-line text "m" modes normal|visual + bind jump-to-mark text "'" modes normal|visual + bind change-to-eol text "C" modes normal + bind delete-to-eol text "D" modes normal + bind replace text "r" modes normal + bind cursor-to-first-non-whitespace text "^" modes normal + bind find-next-char-forwards text "t" modes normal|visual + bind find-next-char-backwards text "T" modes normal|visual + bind find-char-forwards text "f" modes normal|visual + bind find-char-backwards text "F" modes normal|visual + bind insert-text catchall modes insert + } + command-keys = { + bind substitute-yes text "y" modes substitute + bind substitute-yes-all text "Y" modes substitute + bind substitute-no text "n" modes substitute + bind substitute-no-all text "N" modes substitute + bind edit-submit keysym return mods any modes edit + bind edit-submit-search keysym return mods any modes edit-search + bind edit-submit-backwards-search keysym return mods any modes edit-search-backwards + bind edit-cursor-left keysym left modes edit|edit-search|edit-search-backwards + bind edit-cursor-right keysym right modes edit|edit-search|edit-search-backwards + bind edit-previous-command keysym up modes edit + bind edit-next-command keysym down modes edit + bind edit-previous-search keysym up modes edit-search|edit-search-backwards + bind edit-next-search keysym down modes edit-search|edit-search-backwards + bind edit-backspace keysym backspace modes edit|edit-search|edit-search-backwards + bind edit-delete keysym delete modes edit|edit-search|edit-search-backwards + bind edit-cursor-to-end keysym end modes edit|edit-search|edit-search-backwards + bind edit-cursor-to-beginning keysym home modes edit|edit-search|edit-search-backwards + bind edit-discard keysym escape modes edit|edit-search|edit-search-backwards + bind edit-insert-text catchall modes edit|edit-search|edit-search-backwards + } + commands = { + bind write-quit "wq" + bind write "w" + bind quit "q" + bind create-view "v" + bind close-view "c" + bind substitute "s" + } +} + +language-mapping = { + language = "German" + key-mapping = { + map "z" "y" + map "y" "z" + map "Z" "Y" + map "Ö" ":" + map "_" "?" + map "-" "/" + map "ä" "'" + } + command-mapping = { + map "wq" "wq" + map "w" "w" + map "q" "q" + map "v" "v" + map "c" "c" + map "s" "s" + } +} + +language-mapping = { + language = "Hindi (Bolnagri)" + key-mapping = { + map "0" "0" + map "1" "1" + map "2" "2" + map "3" "3" + map "4" "4" + map "5" "5" + map "6" "6" + map "7" "7" + map "8" "8" + map "9" "9" + map "ा" "a" + map "आ" "A" + map "ब" "b" + map "भ" "B" + map "च" "c" + map "छ" "C" + map "द" "d" + map "ध" "D" + map "े" "e" + map "ै" "E" + map "ट" "f" + map "ठ" "F" + map "ग" "g" + map "घ" "G" + map "ह" "h" + map "ि" "i" + map "ी" "I" + map "ज" "j" + map "झ" "J" + map "क" "k" + map "ल" "l" + map "म" "m" + map "न" "n" + map "ण" "N" + map "ो" "o" + map "ौ" "O" + map "प" "p" + map "फ" "P" + map "र" "r" + map "त" "t" + map "थ" "T" + map "ु" "u" + map "ू" "U" + map "ड" "v" + map "व" "w" + map "ॐ" "W" + map "्" "x" + map "ॉ" "X" + map "य" "y" + map "ञ" "Y" + map "श" "z" + map ":" ":" + map "?" "?" + map "/" "/" + map "." "." + map "$" "$" + map "'" "'" + map "^" "^" + } + command-mapping = { + map "वग" "wq" + map "व" "w" + map "‌" "q" + map "ड" "v" + map "च" "c" + map "स" "s" + } +} + +language-mapping = { + language = "Urdu (Pakistan)" + key-mapping = { + map "0" "0" + map "1" "1" + map "2" "2" + map "3" "3" + map "4" "4" + map "5" "5" + map "6" "6" + map "7" "7" + map "8" "8" + map "9" "9" + map "ا" "a" + map "آ" "A" + map "ب" "b" + map "." "B" + map "چ" "c" + map "ث" "C" + map "د" "d" + map "ڈ" "D" + map "ع" "e" + map "ٰ" "E" + map "ف" "f" + map "ّ" "F" + map "گ" "g" + map "غ" "G" + map "ح" "h" + map "ی" "i" + map "ِ" "I" + map "ج" "j" + map "ض" "J" + map "ک" "k" + map "ل" "l" + map "م" "m" + map "ن" "n" + map "ں" "N" + map "ہ" "o" + map "ۃ" "O" + map "پ" "p" + map "ُ" "P" + map "ر" "r" + map "ت" "t" + map "ٹ" "T" + map "ء" "u" + map "ئ" "U" + map "ط" "v" + map "و" "w" + map "ؤ" "W" + map "ش" "x" + map "ژ" "X" + map "ے" "y" + map "َ" "Y" + map "ز" "z" + map ":" ":" + map "؟" "?" + map "/" "/" + map "۔" "." + map "$" "$" + map "'" "'" + map "^" "^" + } + command-mapping = { + map "وگ" "wq" + map "و" "w" + map "ق" "q" + map "ط" "v" + map "چ" "c" + map "س" "s" + } +} diff --git a/memory.c b/memory.c @@ -10,6 +10,7 @@ static void fatal_err(const char *msg) { fprintf(stderr, "%s", msg); + /* FIXME: maybe don't cleanup here - it will probably fail anyways */ ledit_cleanup(); exit(1); } @@ -82,6 +83,23 @@ ledit_strcat(const char *str1, const char *str2) { return ret; } +char * +print_fmt(char *fmt, ...) { + va_list args; + va_start(args, fmt); + int len = vsnprintf(NULL, 0, fmt, args); + /* FIXME: what should be done on error? */ + if (len < 0) + fatal_err("Error in vsnprintf called from print_fmt"); + /* FIXME: overflow */ + char *str = ledit_malloc(len + 1); + va_end(args); + va_start(args, fmt); + vsnprintf(str, len + 1, fmt, args); + va_end(args); + return str; +} + /* * This (reallocarray) is from OpenBSD (adapted to exit on error): * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net> @@ -96,11 +114,11 @@ ledit_strcat(const char *str1, const char *str2) { void * ledit_reallocarray(void *optr, size_t nmemb, size_t size) { - if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && - nmemb > 0 && SIZE_MAX / nmemb < size) { + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) { err_overflow(); - } - return ledit_realloc(optr, size * nmemb); + } + return ledit_realloc(optr, size * nmemb); } void diff --git a/memory.h b/memory.h @@ -3,6 +3,7 @@ #include <stddef.h> #include <stdint.h> +#include <stdarg.h> /* * These functions all wrap the regular functions but exit on error. @@ -19,6 +20,11 @@ void *ledit_realloc(void *ptr, size_t size); */ char *ledit_strcat(const char *str1, const char *str2); +/* This acts like snprintf but automatically allocates + a string of the appropriate size. + Like the other functions here, it exits on error. */ +char *print_fmt(char *fmt, ...); + /* * This is like OpenBSD's reallocarray but exits on error. */ diff --git a/theme.c b/theme.c @@ -1,57 +0,0 @@ -#include <stddef.h> - -#include <X11/Xlib.h> -#include <X11/Xft/Xft.h> - -#include "memory.h" -#include "common.h" -#include "theme.h" -#include "theme_config.h" - -ledit_theme * -theme_create(ledit_common *common) { - ledit_theme *theme = ledit_malloc(sizeof(ledit_theme)); - theme->scrollbar_width = SCROLLBAR_WIDTH; - theme->scrollbar_step = SCROLLBAR_STEP; - theme->text_font = TEXT_FONT; - theme->text_size = TEXT_SIZE; - theme->text_fg_hex = TEXT_FG; - theme->text_bg_hex = TEXT_BG; - theme->cursor_fg_hex = CURSOR_FG; - theme->cursor_bg_hex = CURSOR_BG; - theme->selection_fg_hex = SELECTION_FG; - theme->selection_bg_hex = SELECTION_BG; - theme->bar_fg_hex = BAR_FG; - theme->bar_bg_hex = BAR_BG; - theme->bar_cursor_hex = BAR_CURSOR; - theme->scrollbar_fg_hex = SCROLLBAR_FG; - theme->scrollbar_bg_hex = SCROLLBAR_BG; - XftColorAllocName(common->dpy, common->vis, common->cm, TEXT_FG, &theme->text_fg); - XftColorAllocName(common->dpy, common->vis, common->cm, TEXT_BG, &theme->text_bg); - XftColorAllocName(common->dpy, common->vis, common->cm, CURSOR_FG, &theme->cursor_fg); - XftColorAllocName(common->dpy, common->vis, common->cm, CURSOR_BG, &theme->cursor_bg); - XftColorAllocName(common->dpy, common->vis, common->cm, SELECTION_FG, &theme->selection_fg); - XftColorAllocName(common->dpy, common->vis, common->cm, SELECTION_BG, &theme->selection_bg); - XftColorAllocName(common->dpy, common->vis, common->cm, BAR_FG, &theme->bar_fg); - XftColorAllocName(common->dpy, common->vis, common->cm, BAR_BG, &theme->bar_bg); - XftColorAllocName(common->dpy, common->vis, common->cm, BAR_CURSOR, &theme->bar_cursor); - XftColorAllocName(common->dpy, common->vis, common->cm, SCROLLBAR_FG, &theme->scrollbar_fg); - XftColorAllocName(common->dpy, common->vis, common->cm, SCROLLBAR_BG, &theme->scrollbar_bg); - return theme; -} - -void -theme_destroy(ledit_common *common, ledit_theme *theme) { - XftColorFree(common->dpy, common->vis, common->cm, &theme->text_fg); - XftColorFree(common->dpy, common->vis, common->cm, &theme->text_bg); - XftColorFree(common->dpy, common->vis, common->cm, &theme->cursor_fg); - XftColorFree(common->dpy, common->vis, common->cm, &theme->cursor_bg); - XftColorFree(common->dpy, common->vis, common->cm, &theme->selection_fg); - XftColorFree(common->dpy, common->vis, common->cm, &theme->selection_bg); - XftColorFree(common->dpy, common->vis, common->cm, &theme->bar_fg); - XftColorFree(common->dpy, common->vis, common->cm, &theme->bar_bg); - XftColorFree(common->dpy, common->vis, common->cm, &theme->bar_cursor); - XftColorFree(common->dpy, common->vis, common->cm, &theme->scrollbar_fg); - XftColorFree(common->dpy, common->vis, common->cm, &theme->scrollbar_bg); - free(theme); -} diff --git a/theme.h b/theme.h @@ -1,39 +0,0 @@ -#ifndef _THEME_H_ -#define _THEME_H_ - -#include <X11/Xft/Xft.h> -#include "common.h" - -typedef struct { - int scrollbar_width; - int scrollbar_step; - int text_size; - XftColor text_fg; - XftColor text_bg; - XftColor cursor_fg; - XftColor cursor_bg; - XftColor selection_fg; - XftColor selection_bg; - XftColor bar_fg; - XftColor bar_bg; - XftColor bar_cursor; - XftColor scrollbar_fg; - XftColor scrollbar_bg; - const char *text_font; - const char *text_fg_hex; - const char *text_bg_hex; - const char *cursor_fg_hex; - const char *cursor_bg_hex; - const char *selection_fg_hex; - const char *selection_bg_hex; - const char *bar_fg_hex; - const char *bar_bg_hex; - const char *bar_cursor_hex; - const char *scrollbar_fg_hex; - const char *scrollbar_bg_hex; -} ledit_theme; - -ledit_theme *theme_create(ledit_common *common); -void theme_destroy(ledit_common *common, ledit_theme *theme); - -#endif diff --git a/theme_config.h b/theme_config.h @@ -1,7 +1,19 @@ +#ifndef _THEME_CONFIG_H_ +#define _THEME_CONFIG_H_ + +/*********************** + * Theme configuration * + ***********************/ + +/* Note: Integer values have to be given as strings because + that simplifies some things in the config parser. */ + +/* FIXME: Check what happens when 0 is given for integer values */ + /* font used for all text */ static const char *TEXT_FONT = "Monospace"; /* size used for text in points or whatever pango uses */ -static const int TEXT_SIZE = 12; +static const char *TEXT_SIZE = "12"; /* text color of main text area */ static const char *TEXT_FG = "#000000"; /* background color of main text area */ @@ -24,10 +36,12 @@ static const char *BAR_CURSOR = "#000000"; /* FIXME: give in units other than pixels */ /* scrollbar width in pixels */ -static const int SCROLLBAR_WIDTH = 10; +static const char *SCROLLBAR_WIDTH = "10"; /* number of pixels scrolled with every scroll event */ -static const int SCROLLBAR_STEP = 20; +static const char *SCROLLBAR_STEP = "20"; /* background color of scrollbar */ static const char *SCROLLBAR_BG = "#CCCCCC"; /* color of scrollbar handle */ static const char *SCROLLBAR_FG = "#000000"; + +#endif /* _THEME_CONFIG_H_ */ diff --git a/txtbuf.c b/txtbuf.c @@ -1,5 +1,7 @@ +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <stdarg.h> #include "util.h" #include "memory.h" @@ -14,16 +16,49 @@ txtbuf_new(void) { return buf; } +txtbuf * +txtbuf_new_from_char(char *str) { + txtbuf *buf = ledit_malloc(sizeof(txtbuf)); + buf->text = ledit_strdup(str); + buf->len = strlen(str); + buf->cap = buf->len + 1; + return buf; +} + +txtbuf * +txtbuf_new_from_char_len(char *str, size_t len) { + txtbuf *buf = ledit_malloc(sizeof(txtbuf)); + buf->text = ledit_strndup(str, len); + buf->len = len; + buf->cap = len + 1; + return buf; +} + +void +txtbuf_fmt(txtbuf *buf, char *fmt, ...) { + va_list args; + va_start(args, fmt); + int len = vsnprintf(buf->text, buf->cap, fmt, args); + /* FIXME: len can never be negative, right? */ + /* FIXME: maybe also shrink here */ + if ((size_t)len >= buf->cap) { + va_end(args); + va_start(args, fmt); + txtbuf_resize(buf, len); + vsnprintf(buf->text, buf->cap, fmt, args); + } + buf->len = len; + va_end(args); +} + void txtbuf_resize(txtbuf *buf, size_t sz) { /* always leave room for extra \0 */ - /* FIXME: '\0' isn't actually used anywhere */ size_t cap = ideal_array_size(buf->cap, add_sz(sz, 1)); if (cap != buf->cap) { buf->text = ledit_realloc(buf->text, cap); buf->cap = cap; } - ledit_assert(buf->cap >= add_sz(sz, 1)); } void @@ -38,6 +73,7 @@ void txtbuf_copy(txtbuf *dst, txtbuf *src) { txtbuf_resize(dst, src->len); memcpy(dst->text, src->text, src->len); + dst->text[src->len] = '\0'; dst->len = src->len; } @@ -47,3 +83,22 @@ txtbuf_dup(txtbuf *src) { txtbuf_copy(dst, src); return dst; } + +int +txtbuf_cmp(txtbuf *buf1, txtbuf *buf2) { + /* FIXME: I guess strcmp would be possible as well since it's nul-terminated now */ + /* FIXME: Test this because I was tired while writing it */ + int cmp = strncmp(buf1->text, buf2->text, LEDIT_MIN(buf1->len, buf2->len)); + if (cmp == 0) { + if (buf1->len < buf2->len) + return -1; + else if (buf1->len > buf2->len) + return 1; + } + return cmp; +} + +int +txtbuf_eql(txtbuf *buf1, txtbuf *buf2) { + return txtbuf_cmp(buf1, buf2) == 0; +} diff --git a/txtbuf.h b/txtbuf.h @@ -5,6 +5,7 @@ /* * txtbuf is really just a string data type that is badly named. + * The stored text is always nul-terminated. */ typedef struct { @@ -18,6 +19,35 @@ typedef struct { txtbuf *txtbuf_new(void); /* + * Create a new txtbuf, initializing it with the nul-terminated + * string 'str'. The input string is copied. + */ +txtbuf *txtbuf_new_from_char(char *str); + +/* + * Create a new txtbuf, initializing it with the string 'str' + * of length 'len'. The input string is copied. + */ +txtbuf *txtbuf_new_from_char_len(char *str, size_t len); + +/* + * Replace the stored text in 'buf' with the text generated by + * 'snprintf' when called with the given format string and args. + */ +void txtbuf_fmt(txtbuf *buf, char *fmt, ...); + +/* + * Compare the text of two txtbuf's like 'strcmp'. + */ +int txtbuf_cmp(txtbuf *buf1, txtbuf *buf2); + +/* + * Convenience function for calling 'txtbuf_cmp' and checking if the + * return value is 0, i.e. the strings are equal. + */ +int txtbuf_eql(txtbuf *buf1, txtbuf *buf2); + +/* * Make sure the txtbuf has space for at least the given size, * plus '\0' at the end. */ diff --git a/uglycrap.h b/uglycrap.h @@ -0,0 +1,15 @@ +#ifndef _UGLYCRAP_H_ +#define _UGLYCRAP_H_ + +/* FIXME: Figure out where to put it - it would make sens to put it in + keys_command.h, but it is needed by view.h to make the command mode + per-view, but I don't want view.* to depend on keys_command.h */ + +typedef enum command_mode { + CMD_EDIT = 1, /* edit command */ + CMD_EDITSEARCH = 2, /* edit search term */ + CMD_EDITSEARCHB = 4, /* edit search term for backwards search */ + CMD_SUBSTITUTE = 8 /* confirm substitution */ +} command_mode; + +#endif diff --git a/undo.h b/undo.h @@ -73,6 +73,7 @@ void undo_change_mode_group(undo_stack *undo); /* * Push an insert action onto the undo stack. * See documentation at top for details on the other arguments. + * 'text' is copied, so the original should be freed. */ void undo_push_insert( undo_stack *undo, txtbuf *text, @@ -83,6 +84,7 @@ void undo_push_insert( /* * Push an delete action onto the undo stack. * See documentation at top for details on the other arguments. + * 'text' is copied, so the original should be freed. */ void undo_push_delete( undo_stack *undo, txtbuf *text, diff --git a/util.c b/util.c @@ -1,3 +1,4 @@ +#include <string.h> #include <stddef.h> #include "memory.h" @@ -9,6 +10,15 @@ next_utf8(char *str) { } size_t +next_utf8_len(char *str, size_t len) { + size_t cur = 0; + while (cur < len && (str[cur] & 0xC0) == 0x80) + cur++; + return cur; +} + +/* FIXME: change these to macros somehow */ +size_t add_sz(size_t a, size_t b) { if (a > SIZE_MAX - b) err_overflow(); @@ -38,3 +48,13 @@ sort_range(size_t *l1, size_t *b1, size_t *l2, size_t *b2) { swap_sz(b1, b2); } } + +int +str_array_equal(char *terminated, char *array, size_t len) { + if (!strncmp(terminated, array, len)) { + /* 'terminated' and 'array' are equal for the first 'len' + characters, so this index in 'terminated' must exist */ + return terminated[len] == '\0'; + } + return 0; +} diff --git a/util.h b/util.h @@ -9,6 +9,13 @@ char *next_utf8(char *str); /* + * Same as above, but also works with non-nul-terminated strings. + * Instead of a pointer, the index of the next utf8 char is + * returned. + */ +size_t next_utf8_len(char *str, size_t len); + +/* * Add size_t values and abort if overflow would occur. * FIXME: Maybe someone with actual experience could tell me * if this overflow checking actually works. @@ -19,4 +26,25 @@ size_t add_sz3(size_t a, size_t b, size_t c); void swap_sz(size_t *a, size_t *b); void sort_range(size_t *l1, size_t *b1, size_t *l2, size_t *b2); +/* + * Compare the nul-terminated string 'terminated' with the char + * array 'array' with length 'len'. + * Returns non-zero if they are equal, 0 otherwise. + */ +/* Note: this doesn't work if array contains '\0'. */ +int str_array_equal(char *terminated, char *array, size_t len); + +#define LEDIT_MIN(x, y) ((x) < (y) ? (x) : (y)) +#define LEDIT_MAX(x, y) ((x) > (y) ? (x) : (y)) + +/* Apparently, ISO C99 requires at least one argument for + variadic macros, so there are two versions of the macro here. */ +#ifdef LEDIT_DEBUG + #define ledit_debug(fmt) do {fprintf(stderr, "%s:%d: " fmt, __FILE__, __LINE__);} while (0) + #define ledit_debug_fmt(fmt, ...) do {fprintf(stderr, "%s:%d: " fmt, __FILE__, __LINE__, __VA_ARGS__);} while (0) +#else + #define ledit_debug(fmt) do {} while (0) + #define ledit_debug_fmt(fmt, ...) do {} while (0) +#endif + #endif diff --git a/view.c b/view.c @@ -18,10 +18,10 @@ #include "txtbuf.h" #include "undo.h" #include "cache.h" -#include "theme.h" #include "window.h" #include "buffer.h" #include "assert.h" +#include "configparser.h" /* Basic attributes set for all text. */ static PangoAttrList *basic_attrs = NULL; @@ -96,7 +96,7 @@ view_set_mode(ledit_view *view, ledit_mode mode) { } ledit_view * -view_create(ledit_buffer *buffer, ledit_theme *theme, ledit_mode mode, size_t line, size_t pos) { +view_create(ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos) { if (basic_attrs == NULL) { basic_attrs = pango_attr_list_new(); #if PANGO_VERSION_CHECK(1, 44, 0) @@ -108,8 +108,7 @@ view_create(ledit_buffer *buffer, ledit_theme *theme, ledit_mode mode, size_t li ledit_view *view = ledit_malloc(sizeof(ledit_view)); view->mode = mode; view->buffer = buffer; - view->window = window_create(buffer->common, theme, mode); - view->theme = theme; + view->window = window_create(buffer->common, mode); view->cache = cache_create(buffer->common->dpy); view->lock_text = NULL; view->cur_action = (struct action){ACTION_NONE, NULL}; @@ -328,12 +327,13 @@ get_pango_attributes(size_t start_byte, size_t end_byte, XRenderColor fg, XRende /* this takes layout directly to possibly avoid infinite recursion */ static void set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *layout) { + ledit_theme *theme = config_get_theme(); ledit_line *ll = buffer_get_line(view->buffer, line); ledit_view_line *vl = view_get_line(view, line); PangoAttrList *list = NULL; if (view->sel_valid) { - XRenderColor fg = view->theme->selection_fg.color; - XRenderColor bg = view->theme->selection_bg.color; + XRenderColor fg = theme->selection_fg.color; + XRenderColor bg = theme->selection_bg.color; ledit_range sel = view->sel; sort_range(&sel.line1, &sel.byte1, &sel.line2, &sel.byte2); if (sel.line1 < line && sel.line2 > line) { @@ -349,8 +349,8 @@ set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *layout) { list = get_pango_attributes(0, sel.byte2, fg, bg); } } else if (vl->cursor_index_valid) { - XRenderColor fg = view->theme->cursor_fg.color; - XRenderColor bg = view->theme->cursor_bg.color; + XRenderColor fg = theme->cursor_fg.color; + XRenderColor bg = theme->cursor_bg.color; /* FIXME: does just adding one really do the right thing? */ list = get_pango_attributes(vl->cursor_index, vl->cursor_index + 1, fg, bg); } @@ -392,6 +392,7 @@ line_visible_callback(void *data, size_t line) { /* FIXME: standardize variable names (line/line_index, etc.) */ void render_line(ledit_view *view, size_t line_index) { + ledit_theme *theme = config_get_theme(); /* FIXME: check for <= 0 on size */ ledit_view_line *ll = view_get_line(view, line_index); ledit_assert(!ll->h_dirty); /* FIXME */ @@ -430,8 +431,8 @@ render_line(ledit_view *view, size_t line_index) { pix->h = new_h; XftDrawChange(pix->draw, pix->pixmap); } - XftDrawRect(pix->draw, &view->theme->text_bg, 0, 0, ll->w, ll->h); - pango_xft_render_layout(pix->draw, &view->theme->text_fg, layout, 0, 0); + XftDrawRect(pix->draw, &theme->text_bg, 0, 0, ll->w, ll->h); + pango_xft_render_layout(pix->draw, &theme->text_fg, layout, 0, 0); ll->dirty = 0; } @@ -1839,6 +1840,7 @@ view_button_handler(void *data, XEvent *event) { static void view_redraw_text(ledit_view *view) { + ledit_theme *theme = config_get_theme(); int h = 0; int cur_line_y = 0; int cursor_displayed = 0; @@ -1884,7 +1886,7 @@ view_redraw_text(ledit_view *view) { h += vline->h; } - XSetForeground(view->buffer->common->dpy, view->window->gc, view->theme->cursor_bg.pixel); + XSetForeground(view->buffer->common->dpy, view->window->gc, theme->cursor_bg.pixel); PangoRectangle strong, weak; ledit_line *cur_line = buffer_get_line(view->buffer, view->cur_line); PangoLayout *layout = get_pango_layout(view, view->cur_line); diff --git a/view.h b/view.h @@ -10,8 +10,8 @@ #include "common.h" #include "txtbuf.h" #include "window.h" -#include "theme.h" #include "cache.h" +#include "uglycrap.h" typedef struct ledit_view ledit_view; @@ -49,24 +49,14 @@ typedef struct { char h_dirty; /* whether height needs to be recalculated */ } ledit_view_line; -/* FIXME: It's kind of ugly to put this here instead of keys_command.h, - but it has to be per-view, so I don't know any other option. */ -enum ledit_command_type { - CMD_EDIT, /* edit command */ - CMD_EDITSEARCH, /* edit search term */ - CMD_EDITSEARCHB, /* edit search term for backwards search */ - CMD_SUBSTITUTE /* confirm substitution */ -}; - struct ledit_view { ledit_buffer *buffer; /* parent buffer */ ledit_window *window; /* window showing this view */ - ledit_theme *theme; /* current theme in use */ ledit_cache *cache; /* cache for pixmaps and pango layouts */ ledit_view_line *lines; /* array of lines, stored as gap buffer */ char *lock_text; /* text to show if view is locked, i.e. no edits allowed */ /* current command type - used by key handler in keys_command.c */ - enum ledit_command_type cur_command_type; + command_mode cur_command_type; struct action cur_action; /* current action to execute on key press */ size_t lines_cap; /* size of lines array */ size_t lines_gap; /* position of gap for line gap buffer */ @@ -98,14 +88,11 @@ enum delete_mode { void view_set_mode(ledit_view *view, ledit_mode mode); /* - * Create a view with associated buffer 'buffer' and theme 'theme'. + * Create a view with associated buffer 'buffer' * The initial mode, line, and byte position are given, respectively, * by 'mode', 'line', and 'pos'. */ -ledit_view *view_create( - ledit_buffer *buffer, ledit_theme *theme, - ledit_mode mode, size_t line, size_t pos -); +ledit_view *view_create(ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos); /* * Lock a view. diff --git a/window.c b/window.c @@ -14,7 +14,6 @@ #include <X11/extensions/Xdbe.h> #include "util.h" -#include "theme.h" #include "memory.h" #include "common.h" #include "txtbuf.h" @@ -23,6 +22,7 @@ #include "config.h" #include "assert.h" #include "draw_util.h" +#include "configparser.h" /* FIXME: Everything to do with the bottom bar is extremely hacky */ struct bottom_bar { @@ -94,10 +94,11 @@ window_get_primary_clipboard_buffer(void) { /* FIXME: guard against negative width/height */ static void recalc_text_size(ledit_window *window) { + ledit_theme *theme = config_get_theme(); int bar_h = window->bb->mode_h; if (window->bottom_text_shown || window->message_shown) bar_h = window->bb->line_h; - window->text_w = window->w - window->theme->scrollbar_width; + window->text_w = window->w - theme->scrollbar_width; window->text_h = window->h - bar_h; if (window->text_w < 0) window->text_w = 0; @@ -120,12 +121,13 @@ resize_line_text(ledit_window *window, int min_size) { static void redraw_line_text(ledit_window *window) { + ledit_theme *theme = config_get_theme(); /* FIXME: set_text doesn't really belong here */ pango_layout_set_text(window->bb->line, window->bb->line_text, window->bb->line_len); pango_layout_get_pixel_size(window->bb->line, &window->bb->line_w, &window->bb->line_h); draw_grow(window, window->bb->line_draw, window->bb->line_w, window->bb->line_h); - XftDrawRect(window->bb->line_draw->xftdraw, &window->theme->bar_bg, 0, 0, window->bb->line_w, window->bb->line_h); - pango_xft_render_layout(window->bb->line_draw->xftdraw, &window->theme->bar_fg, window->bb->line, 0, 0); + XftDrawRect(window->bb->line_draw->xftdraw, &theme->bar_bg, 0, 0, window->bb->line_w, window->bb->line_h); + pango_xft_render_layout(window->bb->line_draw->xftdraw, &theme->bar_fg, window->bb->line, 0, 0); recalc_text_size(window); window->redraw = 1; } @@ -332,6 +334,7 @@ window_hide_message(ledit_window *window) { void window_set_mode(ledit_window *window, ledit_mode mode) { + ledit_theme *theme = config_get_theme(); window->mode = mode; char *text; switch (mode) { @@ -353,8 +356,8 @@ window_set_mode(ledit_window *window, ledit_mode mode) { free(final_text); pango_layout_get_pixel_size(window->bb->mode, &window->bb->mode_w, &window->bb->mode_h); draw_grow(window, window->bb->mode_draw, window->bb->mode_w, window->bb->mode_h); - XftDrawRect(window->bb->mode_draw->xftdraw, &window->theme->bar_bg, 0, 0, window->bb->mode_w, window->bb->mode_h); - pango_xft_render_layout(window->bb->mode_draw->xftdraw, &window->theme->bar_fg, window->bb->mode, 0, 0); + XftDrawRect(window->bb->mode_draw->xftdraw, &theme->bar_bg, 0, 0, window->bb->mode_w, window->bb->mode_h); + pango_xft_render_layout(window->bb->mode_draw->xftdraw, &theme->bar_fg, window->bb->mode, 0, 0); recalc_text_size(window); window->redraw = 1; } @@ -510,10 +513,13 @@ xximspot(ledit_window *window, int x, int y) { } ledit_window * -window_create(ledit_common *common, ledit_theme *theme, ledit_mode mode) { +window_create(ledit_common *common, ledit_mode mode) { XGCValues gcv; + ledit_theme *theme = config_get_theme(); + ledit_window *window = ledit_malloc(sizeof(ledit_window)); + window->first_resize = 1; window->mode = mode; window->scroll_dragging = 0; @@ -569,7 +575,6 @@ window_create(ledit_common *common, ledit_theme *theme, ledit_mode mode) { XSetWMProtocols(common->dpy, window->xwin, &window->wm_delete_msg, 1); window->common = common; - window->theme = theme; window->bb = ledit_malloc(sizeof(bottom_bar)); window->bb->mode = pango_layout_new(window->context); @@ -652,6 +657,7 @@ window_destroy(ledit_window *window) { /*g_object_unref(window->context);*/ g_object_unref(window->fontmap); + XFreeGC(window->common->dpy, window->gc); if (window->spotlist) XFree(window->spotlist); XDestroyWindow(window->common->dpy, window->xwin); @@ -671,7 +677,8 @@ window_cleanup(void) { void window_clear(ledit_window *window) { - XSetForeground(window->common->dpy, window->gc, window->theme->text_bg.pixel); + ledit_theme *theme = config_get_theme(); + XSetForeground(window->common->dpy, window->gc, theme->text_bg.pixel); XFillRectangle( window->common->dpy, window->drawable, window->gc, 0, 0, window->w, window->h ); @@ -679,7 +686,7 @@ window_clear(ledit_window *window) { void window_redraw(ledit_window *window) { - ledit_theme *t = window->theme; + ledit_theme *t = config_get_theme(); if (window->scroll_max > window->text_h) { XSetForeground(window->common->dpy, window->gc, t->scrollbar_bg.pixel); XFillRectangle( @@ -791,7 +798,7 @@ window_handle_filtered_events(ledit_window *window) { if (window->last_resize_valid) { clock_gettime(CLOCK_MONOTONIC, &now); ledit_timespecsub(&now, &window->last_resize, &elapsed); - if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= RESIZE_TICK) { + if (window->first_resize || elapsed.tv_sec > 0 || elapsed.tv_nsec >= RESIZE_TICK) { window_resize( window, window->last_resize_event.xconfigure.width, @@ -800,6 +807,7 @@ window_handle_filtered_events(ledit_window *window) { window->last_resize = now; window->last_resize_valid = 0; window->redraw = 1; + window->first_resize = 0; } } } @@ -1036,6 +1044,7 @@ window_register_motion(ledit_window *window, XEvent *event) { /* FIXME: improve set_scroll_pos; make it a bit clearer */ void window_button_press(ledit_window *window, XEvent *event, int scroll_num) { + ledit_theme *theme = config_get_theme(); int x = event->xbutton.x; int y = event->xbutton.y; double scroll_h, scroll_y; @@ -1062,7 +1071,7 @@ window_button_press(ledit_window *window, XEvent *event, int scroll_num) { break; case Button4: case Button5: - window->scroll_offset += scroll_num * window->theme->scrollbar_step; + window->scroll_offset += scroll_num * theme->scrollbar_step; if (window->scroll_offset < 0) window->scroll_offset = 0; if (window->scroll_offset + window->text_h > window->scroll_max) { diff --git a/window.h b/window.h @@ -16,7 +16,6 @@ #include <X11/extensions/Xdbe.h> #include <pango/pangoxft.h> -#include "theme.h" #include "common.h" #include "txtbuf.h" @@ -65,6 +64,12 @@ typedef struct { int last_scroll_valid; int last_motion_valid; int last_resize_valid; + /* This is a hack to make the first resizing of the window go quickly instead + of being delayed due to the event filtering - this is noticeable in tiling + window managers that resize the window immediately after it is created. + The whole event filtering system needs to be rethought anyways, but this + at least sort of works for the time being. (FIXME) */ + int first_resize; int scroll_num; int scroll_delta; @@ -75,7 +80,6 @@ typedef struct { XVaNestedList spotlist; ledit_common *common; - ledit_theme *theme; /* various callbacks */ void (*paste_callback)(void *, char *, size_t); @@ -94,7 +98,7 @@ typedef struct { /* * Create a window with initial mode 'mode'. */ -ledit_window *window_create(ledit_common *common, ledit_theme *theme, ledit_mode mode); +ledit_window *window_create(ledit_common *common, ledit_mode mode); /* * Destroy a window.