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 824365813f86fcdc01434477f39a8734183a8b18
parent 88b7f6c08a1e2a47f33aa5b4d33f18df60b5011f
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 11 Apr 2021 22:05:27 +0200

Split code into several files and clean up a bit

Diffstat:
M.gitignore | 1-
MMakefile | 19+++++++++++++------
Abuffer.c | 269+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abuffer.h | 40++++++++++++++++++++++++++++++++++++++++
Acache.c | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acache.h | 12++++++++++++
Acommon.h | 31+++++++++++++++++++++++++++++++
Mledit.c | 1050+++++++++++++++++++++++++++++++++----------------------------------------------
Amemory.c | 41+++++++++++++++++++++++++++++++++++++++++
Amemory.h | 4++++
10 files changed, 951 insertions(+), 614 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,5 +1,4 @@ tmp ledit -ledit_old *.core *.o diff --git a/Makefile b/Makefile @@ -7,18 +7,25 @@ PREFIX = /usr/local MANPREFIX = ${PREFIX}/man BIN = ${NAME} -SRC = ${BIN:=.c} MAN1 = ${BIN:=.1} -CFLAGS = -g -D_POSIX_C_SOURCE=200809L `pkg-config --cflags x11 xkbfile pangoxft xext` -LDFLAGS += `pkg-config --libs x11 xkbfile pangoxft xext` -lm +OBJ = ${BIN:=.o} cache.o buffer.o memory.o +HDR = cache.h buffer.h memory.h common.h + +CFLAGS_LEDIT = ${CFLAGS} -g -Wall -Wextra -D_POSIX_C_SOURCE=200809L `pkg-config --cflags x11 xkbfile pangoxft xext` +LDFLAGS_LEDIT = ${LDFLAGS} `pkg-config --libs x11 xkbfile pangoxft xext` -lm all: ${BIN} -.c: - ${CC} ${CFLAGS} ${LDFLAGS} -o $@ $< +${OBJ} : ${HDR} + +.c.o: + ${CC} -c -o $@ $< ${CFLAGS_LEDIT} + +${BIN}: ${OBJ} + ${CC} -o $@ ${OBJ} ${LDFLAGS_LEDIT} clean: - rm -f ${BIN} + rm -f ${BIN} ${OBJ} .PHONY: all clean diff --git a/buffer.c b/buffer.c @@ -0,0 +1,269 @@ +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <pango/pangoxft.h> +#include <X11/extensions/Xdbe.h> + +#include "memory.h" +#include "common.h" +#include "buffer.h" +#include "cache.h" + +static PangoAttrList *basic_attrs = NULL; + +static void init_line(ledit_buffer *buffer, ledit_line *line); +static void recalc_line_size_absolute(ledit_buffer *buffer); +static void recalc_single_line_size(ledit_buffer *buffer, int line_index); + +/* FIXME: destroy basic_attrs somewhere */ +ledit_buffer * +ledit_create_buffer(ledit_common_state *state) { + if (basic_attrs == NULL) { + basic_attrs = pango_attr_list_new(); + PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE); + pango_attr_list_insert(basic_attrs, no_hyphens); + } + + ledit_buffer *buffer = ledit_malloc(sizeof(ledit_buffer)); + buffer->state = state; + buffer->lines = NULL; + buffer->lines_num = 0; + buffer->lines_cap = 0; + buffer->cur_line = 0; + buffer->cur_index = 0; + buffer->trailing = 0; + buffer->total_height = 0; + buffer->display_offset = 0; + ledit_append_line(buffer, -1, -1); + + return buffer; +} + +void +ledit_destroy_buffer(ledit_buffer *buffer) { + for (int i = 0; i < buffer->lines_num; i++) { + g_object_unref(buffer->lines[i].layout); + free(buffer->lines[i].text); + } + free(buffer->lines); + free(buffer); +} + +void +ledit_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index) { + if (buffer->state->mode == NORMAL) { + PangoAttribute *attr0 = pango_attr_background_new(0, 0, 0); + PangoAttribute *attr1 = pango_attr_foreground_new(65535, 65535, 65535); + attr0->start_index = index; + attr0->end_index = index + 1; + attr1->start_index = index; + attr1->end_index = index + 1; + PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE); + PangoAttrList *list = pango_attr_list_new(); + pango_attr_list_insert(list, attr0); + pango_attr_list_insert(list, attr1); + pango_attr_list_insert(list, attr2); + pango_layout_set_attributes(buffer->lines[line].layout, list); + } else { + pango_layout_set_attributes(buffer->lines[line].layout, basic_attrs); + } + buffer->lines[line].dirty = 1; +} + +void +ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int line) { + pango_layout_set_attributes(buffer->lines[line].layout, basic_attrs); + buffer->lines[line].dirty = 1; +} + +void +ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len) { + ledit_line *line = &buffer->lines[line_index]; + if (len == -1) + len = strlen(text); + if (line->len + len > line->cap) { + line->cap *= 2; + if (line->cap == 0) + line->cap = 2; + line->text = ledit_realloc(line->text, line->cap); + } + memmove(line->text + index + len, line->text + index, line->len - index); + memcpy(line->text + index, text, len); + line->len += len; + pango_layout_set_text(line->layout, line->text, line->len); + recalc_single_line_size(buffer, line_index); + line->dirty = 1; +} + +void +ledit_render_line(ledit_buffer *buffer, int line_index) { + /* FIXME: check for <= 0 on size */ + ledit_line *line = &buffer->lines[line_index]; + if (line->cache_index == -1) + ledit_assign_free_cache_index(buffer, line_index); + ledit_cache_pixmap *pix = ledit_get_cache_pixmap(line->cache_index); + /* FIXME: sensible default pixmap sizes here */ + if (pix->pixmap == None || pix->draw == NULL) { + pix->pixmap = XCreatePixmap( + buffer->state->dpy, buffer->state->drawable, + line->w + 10, line->h + 10, buffer->state->depth + ); + pix->w = line->w + 10; + pix->h = line->h + 10; + pix->draw = XftDrawCreate( + buffer->state->dpy, pix->pixmap, + buffer->state->vis, buffer->state->cm + ); + } else if (pix->w < line->w || pix->h < line->h) { + int new_w = line->w > pix->w ? line->w + 10 : pix->w + 10; + int new_h = line->h > pix->h ? line->h + 10 : pix->h + 10; + XFreePixmap(buffer->state->dpy, pix->pixmap); + pix->pixmap = XCreatePixmap( + buffer->state->dpy, buffer->state->drawable, + new_w, new_h, buffer->state->depth + ); + pix->w = new_w; + pix->h = new_h; + XftDrawChange(pix->draw, pix->pixmap); + } + XftDrawRect(pix->draw, &buffer->state->bg, 0, 0, line->w, line->h); + pango_xft_render_layout(pix->draw, &buffer->state->fg, line->layout, 0, 0); + line->dirty = 0; +} + +/* FIXME: use proper "viewport width" instead of just subtracting 10 */ +static void +init_line(ledit_buffer *buffer, ledit_line *line) { + line->parent_buffer = buffer; + line->layout = pango_layout_new(buffer->state->context); + pango_layout_set_width(line->layout, (buffer->state->w - 10) * PANGO_SCALE); + pango_layout_set_font_description(line->layout, buffer->state->font); + pango_layout_set_wrap(line->layout, PANGO_WRAP_WORD_CHAR); + line->text = NULL; + line->cap = line->len = 0; + line->cache_index = -1; + line->dirty = 1; + /* FIXME: does this set line height reasonably when no text yet? */ + pango_layout_get_pixel_size(line->layout, &line->w, &line->h); + line->y_offset = 0; +} + +/* FIXME: error checking (index out of bounds, etc.) */ +void +ledit_append_line(ledit_buffer *buffer, int line_index, int text_index) { + if (buffer->lines_num >= buffer->lines_cap) { + buffer->lines_cap *= 2; + if (buffer->lines_cap == 0) + buffer->lines_cap = 2; + buffer->lines = ledit_realloc( + buffer->lines, buffer->lines_cap * sizeof(ledit_line) + ); + } + memmove( + buffer->lines + line_index + 2, + buffer->lines + line_index + 1, + (buffer->lines_num - (line_index + 1)) * sizeof(ledit_line) + ); + ledit_line *new_l = &buffer->lines[line_index + 1]; + init_line(buffer, new_l); + buffer->lines_num++; + if (text_index != -1) { + ledit_line *l = &buffer->lines[line_index]; + int len = l->len - text_index; + new_l->len = len; + new_l->cap = len + 10; + new_l->text = ledit_malloc(new_l->cap); + memcpy(new_l->text, l->text + text_index, len); + l->len = text_index; + pango_layout_set_text(new_l->layout, new_l->text, new_l->len); + pango_layout_set_text(l->layout, l->text, l->len); + /* FIXME: set height here */ + } + recalc_line_size_absolute(buffer); +} + +void +ledit_delete_line_entry(ledit_buffer *buffer, int index) { + g_object_unref(buffer->lines[index].layout); + free(buffer->lines[index].text); + if (index < buffer->lines_num - 1) + memmove( + buffer->lines + index, buffer->lines + index + 1, + (buffer->lines_num - index - 1) * sizeof(ledit_line) + ); + buffer->lines_num--; + recalc_line_size_absolute(buffer); +} + +/* FIXME: use some sort of gap buffer (that would make this function + slightly more useful...) */ +ledit_line * +ledit_get_line(ledit_buffer *buffer, int index) { + return &buffer->lines[index]; +} + +static void +recalc_single_line_size(ledit_buffer *buffer, int line_index) { + int w, h; + ledit_line *line = &buffer->lines[line_index]; + pango_layout_get_pixel_size(line->layout, &w, &h); + line->w = w; + /* if height changed, set height of current line + * and adjust offsets of all lines following it */ + if (line->h != h) { + int delta = h - line->h; + line->h = h; + buffer->total_height += delta; + if (buffer->total_height < 0) + buffer->total_height = 0; + for (int i = line_index + 1; i < buffer->lines_num; i++) { + buffer->lines[i].y_offset += delta; + if (buffer->lines[i].y_offset < 0) + buffer->lines[i].y_offset = 0; + } + } +} + +static void +recalc_line_size_absolute(ledit_buffer *buffer) { + int w, h; + buffer->total_height = 0; + /* completely recalculate line sizes and offsets from scratch */ + for (int i = 0; i < buffer->lines_num; i++) { + buffer->lines[i].y_offset = buffer->total_height; + pango_layout_get_pixel_size(buffer->lines[i].layout, &w, &h); + buffer->total_height += h; + buffer->lines[i].w = w; + buffer->lines[i].h = h; + } +} + +int +ledit_line_visible(ledit_buffer *buffer, int index) { + ledit_line *line = &buffer->lines[index]; + return line->y_offset < buffer->display_offset + buffer->state->h && + line->y_offset + line->h > buffer->display_offset; +} + +int +ledit_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir) { + ledit_line *l = ledit_get_line(buffer, line_index); + int new_index = byte_index; + if (dir < 0) { + int i = buffer->cur_index - 1; + /* find valid utf8 char - this probably needs to be improved */ + while (i > 0 && ((l->text[i] & 0xC0) == 0x80)) + i--; + memmove(l->text + i, l->text + byte_index, l->len - byte_index); + l->len -= byte_index - i; + new_index = i; + } else { + int i = byte_index + 1; + while (i < l->len && ((l->text[i] & 0xC0) == 0x80)) + i++; + memmove(l->text + byte_index, l->text + i, l->len - i); + l->len -= i - byte_index; + } + pango_layout_set_text(l->layout, l->text, l->len); + recalc_single_line_size(buffer, line_index); + return new_index; +} diff --git a/buffer.h b/buffer.h @@ -0,0 +1,40 @@ +typedef struct ledit_buffer ledit_buffer; + +typedef struct { + PangoLayout *layout; + char *text; + ledit_buffer *parent_buffer; + int cap; /* allocated space for text */ + int len; /* actual length of text */ + int w; + int h; + long y_offset; /* pixel offset starting at the top of the file */ + int cache_index; /* index of pixmap in cache, or -1 if not assigned */ + char dirty; /* whether line needs to be rendered before being draw */ +} ledit_line; + +struct ledit_buffer { + ledit_common_state *state; /* general state, e.g. display, window, etc. */ + ledit_line *lines; /* array of lines */ + int lines_cap; /* number of lines allocated in array */ + int lines_num; /* number of used lines */ + int cur_line; /* current line */ + int cur_index; /* current byte index in line */ + int trailing; /* used by pango for determining if index is at + * beginning or end of character */ + long total_height; /* total pixel height of all lines */ + double display_offset; /* current pixel offset of viewport - this + * is a double to make scrolling smoother */ +}; + +ledit_buffer *ledit_create_buffer(ledit_common_state *state); +void ledit_destroy_buffer(ledit_buffer *buffer); +void ledit_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index); +void ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int line); +void ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len); +void ledit_render_line(ledit_buffer *buffer, int line_index); +void ledit_append_line(ledit_buffer *buffer, int line_index, int text_index); +void ledit_delete_line_entry(ledit_buffer *buffer, int index); +ledit_line *ledit_get_line(ledit_buffer *buffer, int index); +int ledit_line_visible(ledit_buffer *buffer, int index); +int ledit_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir); diff --git a/cache.c b/cache.c @@ -0,0 +1,98 @@ +#include <stdlib.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <pango/pangoxft.h> +#include <X11/extensions/Xdbe.h> + +#include "common.h" +#include "memory.h" +#include "buffer.h" +#include "cache.h" + + +static struct { + ledit_common_state *state; + ledit_cache_pixmap *entries; + int entries_num; + int cur_replace_index; +} cache = {NULL, NULL, 0, -1}; + +void +ledit_init_cache(ledit_common_state *state) { + cache.state = state; + /* FIXME: prevent overflow */ + cache.entries = ledit_malloc(20 * sizeof(ledit_cache_pixmap)); + for (int i = 0; i < 20; i++) { + cache.entries[i].pixmap = None; + cache.entries[i].draw = NULL; + cache.entries[i].line = -1; + } + cache.entries_num = 20; + cache.cur_replace_index = -1; +} + +void +ledit_flush_cache(void) { + for (int i = 0; i < cache.entries_num; i++) { + cache.entries[i].line = -1; + } +} + +void +ledit_destroy_cache(void) { + for (int i = 0; i < cache.entries_num; i++) { + if (cache.entries[i].pixmap != None) + XFreePixmap(cache.state->dpy, cache.entries[i].pixmap); + if (cache.entries[i].draw != NULL) + XftDrawDestroy(cache.entries[i].draw); + } + free(cache.entries); + cache.state = NULL; + cache.entries = NULL; + cache.entries_num = 0; + cache.cur_replace_index = -1; +} + +void +ledit_assign_free_cache_index(ledit_buffer *buffer, int new_line_index) { + int entry_index; + int line_index; + ledit_line *line; + /* start at 1 because the cache->cur_replace_index is actually the last entry that was replaced */ + for (int i = 1; i <= cache.entries_num; i++) { + entry_index = (i + cache.cur_replace_index) % cache.entries_num; + line_index = cache.entries[entry_index].line; + /* replace line when entry isn't assigned or currently assigned line is not visible */ + if (line_index == -1 || + (line_index >= 0 && + !ledit_line_visible(buffer, line_index))) { + if (line_index >= 0) { + line = ledit_get_line(buffer, line_index); + line->cache_index = -1; + } + cache.entries[entry_index].line = new_line_index; + cache.cur_replace_index = entry_index; + line = ledit_get_line(buffer, new_line_index); + line->cache_index = entry_index; + return; + } + } + + /* no free entry found, increase cache size */ + cache.entries = ledit_realloc(cache.entries, cache.entries_num * 2 * sizeof(ledit_cache_pixmap)); + entry_index = cache.entries_num; + for (int i = cache.entries_num; i < cache.entries_num * 2; i++) { + cache.entries[i].line = -1; + cache.entries[i].pixmap = None; + cache.entries[i].draw = NULL; + } + cache.entries_num *= 2; + cache.entries[entry_index].line = new_line_index; + line = ledit_get_line(buffer, new_line_index); + line->cache_index = entry_index; +} + +ledit_cache_pixmap * +ledit_get_cache_pixmap(int index) { + return &cache.entries[index]; +} diff --git a/cache.h b/cache.h @@ -0,0 +1,12 @@ +typedef struct { + Pixmap pixmap; + XftDraw *draw; + int w, h; + int line; +} ledit_cache_pixmap; + +void ledit_init_cache(ledit_common_state *state); +void ledit_flush_cache(void); +void ledit_destroy_cache(void); +ledit_cache_pixmap *ledit_get_cache_pixmap(int index); +void ledit_assign_free_cache_index(ledit_buffer *buffer, int line); diff --git a/common.h b/common.h @@ -0,0 +1,31 @@ +enum ledit_mode { + NORMAL = 1, + INSERT = 2, + VISUAL = 4 +}; + +typedef struct { + Display *dpy; + PangoFontMap *fontmap; + PangoContext *context; + PangoFontDescription *font; + Visual *vis; + GC gc; + Window win; + XdbeBackBuffer back_buf; + Drawable drawable; + Colormap cm; + int screen; + int depth; + int w; + int h; + int scroll_dragging; + int scroll_grab_handle; + enum ledit_mode mode; + XIM xim; + XIC xic; + XftColor fg; + XftColor bg; + XftColor scroll_bg; + Atom wm_delete_msg; +} ledit_common_state; diff --git a/ledit.c b/ledit.c @@ -1,4 +1,5 @@ /* FIXME: horizontal scrolling (also need cache to avoid too large pixmaps) */ +/* FIXME: sort out types for indices (currently just int, but that might overflow) */ #include <math.h> #include <stdio.h> #include <errno.h> @@ -19,74 +20,47 @@ #include <X11/extensions/XKBrules.h> #include <X11/extensions/Xdbe.h> -static enum mode { - NORMAL = 1, - INSERT = 2, - VISUAL = 4 -} cur_mode = INSERT; +#include "memory.h" +#include "common.h" +#include "buffer.h" +#include "cache.h" struct key { - char *text; /* for keys that correspond with text */ - KeySym keysym; /* for other keys, e.g. arrow keys */ - enum mode modes; /* modes in which this keybinding is functional */ - void (*func)(void); /* callback function */ + char *text; /* for keys that correspond with text */ + KeySym keysym; /* for other keys, e.g. arrow keys */ + enum ledit_mode modes; /* modes in which this keybinding is functional */ + void (*func)(void); /* callback function */ }; -static struct { - Display *dpy; - GC gc; - Window win; - XdbeBackBuffer back_buf; - Visual *vis; - PangoFontMap *fontmap; - PangoContext *context; - PangoFontDescription *font; - Colormap cm; - int screen; - int depth; - XIM xim; - XIC xic; - int w; - int h; - XftColor fg; - XftColor bg; - - Atom wm_delete_msg; -} state; - -struct cache_pixmap { - Pixmap pixmap; - XftDraw *draw; - int w, h; - int line; -}; - -/* FIXME: possibly use at least 32 bits int? */ -static struct { - struct cache_pixmap *entries; - int entries_num; - int cur_replace_index; -} cache; - -static size_t lines_num = 0; -static size_t lines_cap = 0; - -static int cur_line = 0; -static int cur_subline = 0; -static int cur_index = 0; -static int trailing = 0; -static long total_height = 0; -static double cur_display_offset = 0; - +static ledit_common_state state; +static ledit_buffer *buffer; +static void set_scroll_pos(double pos); +static void get_scroll_pos_height(double *pos, double *height); +static void resize_window(int w, int h); static void mainloop(void); static void setup(int argc, char *argv[]); static void cleanup(void); static void redraw(void); -static void drag_motion(XEvent event); +static int button_press(XEvent *event); +static int button_release(XEvent *event); +static int drag_motion(XEvent *event); +static void ensure_cursor_shown(void); static void resize_window(int w, int h); -static void button_release(void); -static void button_press(XEvent event); + +static void backspace(void); +static void delete_key(void); +static void move_cursor(int dir); +static void cursor_left(void); +static void cursor_right(void); +static void return_key(void); +static void escape_key(void); +static void i_key(void); +static void line_down(void); +static void line_up(void); +static void zero_key(void); + +static void change_keyboard(char *lang); static void key_press(XEvent event); int @@ -98,208 +72,20 @@ main(int argc, char *argv[]) { return 0; } -static struct line { - PangoLayout *layout; - char *text; - size_t cap; - size_t len; - int w; - int h; - long y_offset; - int cache_index; - char dirty; -} *lines = NULL; - -static void -init_cache(void) { - /* FIXME: prevent overflow */ - cache.entries = malloc(20 * sizeof(struct cache_pixmap)); - if (!cache.entries) exit(1); - for (int i = 0; i < 20; i++) { - cache.entries[i].pixmap = None; - cache.entries[i].line = -1; - } - cache.entries_num = 20; - cache.cur_replace_index = -1; -} - -static void -assign_free_cache_index(int line) { - int found = 0; - int real_index; - int tmp_line; - /* start at 1 because the cache->cur_replace_index is actually the last entry that was replaced */ - for (int i = 1; i <= cache.entries_num; i++) { - real_index = (i + cache.cur_replace_index) % cache.entries_num; - tmp_line = cache.entries[real_index].line; - /* replace line when entry isn't assigned or currently assigned line is not visible */ - if (tmp_line == -1 || - (tmp_line >= 0 && - (lines[tmp_line].y_offset >= cur_display_offset + state.h || - lines[tmp_line].y_offset + lines[tmp_line].h <= cur_display_offset))) { - if (tmp_line >= 0) - lines[tmp_line].cache_index = -1; - cache.entries[real_index].line = line; - cache.cur_replace_index = real_index; - lines[line].cache_index = real_index; - return; - } - } - - /* no free entry found, increase cache size */ - cache.entries = realloc(cache.entries, cache.entries_num * 2 * sizeof(struct cache_pixmap)); - if (!cache.entries) exit(1); - real_index = cache.entries_num; - for (size_t i = cache.entries_num + 1; i < cache.entries_num * 2; i++) { - cache.entries[i].line = -1; - } - cache.entries_num *= 2; - cache.entries[real_index].line = line; - lines[line].cache_index = real_index; -} - -static void -init_line(struct line *l) { - /* FIXME: check that layout created properly */ - l->layout = pango_layout_new(state.context); - pango_layout_set_width(l->layout, (state.w - 10) * PANGO_SCALE); - pango_layout_set_font_description(l->layout, state.font); - pango_layout_set_wrap(l->layout, PANGO_WRAP_WORD_CHAR); - l->text = NULL; - l->cap = l->len = 0; - l->cache_index = -1; - l->dirty = 1; - /* FIXME: does this set line height reasonably when no text yet? */ - pango_layout_get_pixel_size(l->layout, &l->w, &l->h); - l->y_offset = 0; -} - -static void recalc_cur_line_size(void); -static void recalc_line_size_absolute(void); - -static void -insert_text(struct line *l, int index, char *text, int len) { - if (len == -1) - len = strlen(text); - if (l->len + len > l->cap) { - l->cap *= 2; - if (l->cap == 0) - l->cap = 2; - l->text = realloc(l->text, l->cap); - if (!l->text) exit(1); - } - memmove(l->text + index + len, l->text + index, l->len - index); - memcpy(l->text + index, text, len); - l->len += len; - pango_layout_set_text(l->layout, l->text, l->len); - recalc_cur_line_size(); - l->dirty = 1; -} - -static void insert_line_entry(int index); - -static void -render_line(int line) { - /* FIXME: check for <= 0 on size */ - struct line *l = &lines[line]; - if (l->cache_index == -1) - assign_free_cache_index(line); - struct cache_pixmap *pix = &cache.entries[l->cache_index]; - if (pix->pixmap == None) { - pix->pixmap = XCreatePixmap(state.dpy, state.back_buf, l->w + 10, l->h + 10, state.depth); - pix->w = l->w + 10; - pix->h = l->h + 10; - pix->draw = XftDrawCreate(state.dpy, pix->pixmap, state.vis, state.cm); - } else if (pix->w < l->w || pix->h < l->h) { - int new_w = l->w > pix->w ? l->w + 10 : pix->w + 10; - int new_h = l->h > pix->h ? l->h + 10 : pix->h + 10; - XFreePixmap(state.dpy, pix->pixmap); - pix->pixmap = XCreatePixmap(state.dpy, state.back_buf, new_w, new_h, state.depth); - pix->w = new_w; - pix->h = new_h; - XftDrawChange(pix->draw, pix->pixmap); - } - XftDrawRect(pix->draw, &state.bg, 0, 0, l->w, l->h); - pango_xft_render_layout(pix->draw, &state.fg, l->layout, 0, 0); - l->dirty = 0; -} - -static void -append_line(int text_index, int line_index) { - if (lines_num >= lines_cap) { - lines_cap *= 2; - if (lines_cap == 0) - lines_cap = 2; - lines = realloc(lines, lines_cap * sizeof(struct line)); - if (!lines) exit(1); - } - memmove(lines + line_index + 2, lines + line_index + 1, (lines_num - (line_index + 1)) * sizeof(struct line)); - struct line *new_l = &lines[line_index + 1]; - init_line(new_l); - lines_num++; - if (text_index != -1) { - struct line *l = &lines[line_index]; - int len = l->len - text_index; - new_l->len = len; - new_l->cap = len + 10; - new_l->text = malloc(new_l->cap); - if (!new_l->text) exit(1); - memcpy(new_l->text, l->text + text_index, len); - l->len = text_index; - pango_layout_set_text(new_l->layout, new_l->text, new_l->len); - pango_layout_set_text(l->layout, l->text, l->len); - /* FIXME: set height here */ - } - /* FIXME: update line heights, etc. */ -} - -static void change_keyboard(char *lang); - -PangoAttrList *basic_attrs; - static void get_scroll_pos_height(double *pos, double *height) { - *height = ((double)state.h / total_height) * state.h; - *pos = (cur_display_offset / (total_height - state.h)) * (state.h - *height); + *height = ((double)state.h / buffer->total_height) * state.h; + *pos = (buffer->display_offset / + (buffer->total_height - state.h)) * (state.h - *height); } static void set_scroll_pos(double pos) { - cur_display_offset = pos * (total_height / (double)state.h); - if (cur_display_offset < 0) - cur_display_offset = 0; - if (cur_display_offset + state.h > total_height) - cur_display_offset = total_height - state.h; -} - -static int scroll_dragging = 0; -static int scroll_grab_handle = 0; - -static void -set_line_cursor_attrs(int line, int index) { - if (cur_mode == NORMAL) { - PangoAttribute *attr0 = pango_attr_background_new(0, 0, 0); - PangoAttribute *attr1 = pango_attr_foreground_new(65535, 65535, 65535); - attr0->start_index = index; - attr0->end_index = index + 1; - attr1->start_index = index; - attr1->end_index = index + 1; - PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE); - PangoAttrList *list = pango_attr_list_new(); - pango_attr_list_insert(list, attr0); - pango_attr_list_insert(list, attr1); - pango_attr_list_insert(list, attr2); - pango_layout_set_attributes(lines[line].layout, list); - } else { - pango_layout_set_attributes(lines[line].layout, basic_attrs); - } - lines[line].dirty = 1; -} - -static void -wipe_line_cursor_attrs(int line) { - pango_layout_set_attributes(lines[line].layout, basic_attrs); - lines[line].dirty = 1; + buffer->display_offset = pos * (buffer->total_height / (double)state.h); + if (buffer->display_offset < 0) + buffer->display_offset = 0; + if (buffer->display_offset + state.h > buffer->total_height) + buffer->display_offset = buffer->total_height - state.h; } static void @@ -313,33 +99,22 @@ mainloop(void) { } printf("XKB (%d.%d) supported.\n", major, minor); /* This should select the events when the keyboard mapping changes. - When e.g. 'setxkbmap us' is executed, two events are sent, but I haven't figured out how to - change that. When the xkb layout switching is used (e.g. 'setxkbmap -option grp:shifts_toggle'), - this issue does not occur because only a state event is sent. */ - XkbSelectEvents(state.dpy, XkbUseCoreKbd, XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask); - XkbSelectEventDetails(state.dpy, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask); + * When e.g. 'setxkbmap us' is executed, two events are sent, but I + * haven't figured out how to change that. When the xkb layout + * switching is used (e.g. 'setxkbmap -option grp:shifts_toggle'), + * this issue does not occur because only a state event is sent. */ + XkbSelectEvents( + state.dpy, XkbUseCoreKbd, + XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask + ); + XkbSelectEventDetails( + state.dpy, XkbUseCoreKbd, XkbStateNotify, + XkbAllStateComponentsMask, XkbGroupStateMask + ); XSync(state.dpy, False); int running = 1; int change_kbd = 0; - - /*draw = XftDrawCreate(state.dpy, state.back_buf, state.vis, state.cm);*/ - state.fontmap = pango_xft_get_font_map(state.dpy, state.screen); - state.context = pango_font_map_create_context(state.fontmap); - - state.font = pango_font_description_from_string("Monospace"); - pango_font_description_set_size(state.font, 16 * PANGO_SCALE); - - basic_attrs = pango_attr_list_new(); - PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE); - pango_attr_list_insert(basic_attrs, no_hyphens); - - append_line(-1, -1); - - XftColorAllocName(state.dpy, state.vis, state.cm, "#000000", &state.fg); - XftColorAllocName(state.dpy, state.vis, state.cm, "#FFFFFF", &state.bg); - XftColor scroll_bg; - XftColorAllocName(state.dpy, state.vis, state.cm, "#CCCCCC", &scroll_bg); int need_redraw = 0; redraw(); @@ -358,63 +133,20 @@ mainloop(void) { need_redraw = 1; break; case ConfigureNotify: - resize_window(event.xconfigure.width, event.xconfigure.height); - if (cur_display_offset > 0 && cur_display_offset + state.h >= total_height) { - cur_display_offset = total_height - state.h; - if (cur_display_offset < 0) - cur_display_offset = 0; - } - redraw(); + resize_window( + event.xconfigure.width, + event.xconfigure.height + ); need_redraw = 1; break; case ButtonPress: - switch (event.xbutton.button) { - case Button1: - button_press(event); - double scroll_h, scroll_y; - get_scroll_pos_height(&scroll_y, &scroll_h); - int x = event.xbutton.x; - int y = event.xbutton.y; - if (x >= state.w - 10) { - scroll_dragging = 1; - scroll_grab_handle = y; - if (y < scroll_y || y > scroll_y + scroll_h) { - double new_scroll_y = y - scroll_h / 2; - set_scroll_pos(new_scroll_y); - } - } - break; - case Button4: - cur_display_offset -= 10; - if (cur_display_offset < 0) - cur_display_offset = 0; - break; - case Button5: - if (cur_display_offset + state.h < total_height) { - cur_display_offset += 10; - if (cur_display_offset + state.h > total_height) - cur_display_offset = total_height - state.h; - } - break; - } - need_redraw = 1; + need_redraw |= button_press(&event); break; case ButtonRelease: - if (event.xbutton.button == Button1) { - button_release(); - scroll_dragging = 0; - } + need_redraw |= button_release(&event); break; case MotionNotify: - drag_motion(event); - if (scroll_dragging) { - double scroll_h, scroll_y; - get_scroll_pos_height(&scroll_y, &scroll_h); - scroll_y += event.xbutton.y - scroll_grab_handle; - scroll_grab_handle = event.xbutton.y; - set_scroll_pos(scroll_y); - } - need_redraw = 1; + need_redraw |= drag_motion(&event); break; case KeyPress: need_redraw = 1; @@ -432,90 +164,21 @@ mainloop(void) { change_kbd = 0; XkbStateRec s; XkbGetState(state.dpy, XkbUseCoreKbd, &s); - XkbDescPtr desc = XkbGetKeyboard(state.dpy, XkbAllComponentsMask, XkbUseCoreKbd); - char *group = XGetAtomName(state.dpy, desc->names->groups[s.group]); + XkbDescPtr desc = XkbGetKeyboard( + state.dpy, XkbAllComponentsMask, XkbUseCoreKbd + ); + char *group = XGetAtomName( + state.dpy, desc->names->groups[s.group] + ); change_keyboard(group); - /*char *symbols = XGetAtomName(state.dpy, desc->names->symbols);*/ XFree(group); - /*XFree(symbols);*/ XkbFreeKeyboard(desc, XkbAllComponentsMask, True); } if (need_redraw) { - XSetForeground(state.dpy, state.gc, state.bg.pixel); - XFillRectangle(state.dpy, state.back_buf, state.gc, 0, 0, state.w, state.h); - int h = 0; - - /*int cur_line_height = 0;*/ - int tmp_w, tmp_h; - int cur_line_y = 0; - int cursor_displayed = 0; - for (int i = 0; i < lines_num; i++) { - if (h + lines[i].h > cur_display_offset) { - if (lines[i].dirty || lines[i].cache_index == -1) { - render_line(i); - } - int final_y = 0; - int dest_y = h - cur_display_offset; - int final_h = lines[i].h; - if (h < cur_display_offset) { - dest_y = 0; - final_y = cur_display_offset - h; - final_h -= cur_display_offset - h; - } - if (dest_y + final_h > state.h) { - final_h -= final_y + final_h - cur_display_offset - state.h; - } - XCopyArea(state.dpy, cache.entries[lines[i].cache_index].pixmap, state.back_buf, state.gc, 0, final_y, lines[i].w, final_h, 0, dest_y); - if (i == cur_line) { - cur_line_y = h - cur_display_offset; - cursor_displayed = 1; - } - } - if (h + lines[i].h >= cur_display_offset + state.h) - break; - h += lines[i].h; - } + redraw(); need_redraw = 0; - - XSetForeground(state.dpy, state.gc, state.fg.pixel); - PangoRectangle strong, weak; - pango_layout_get_cursor_pos(lines[cur_line].layout, cur_index, &strong, &weak); - /* FIXME: long, int, etc. */ - int cursor_y = strong.y / PANGO_SCALE + cur_line_y; - if (cursor_displayed && cursor_y >= 0) { - if (cur_mode == NORMAL && cur_index == lines[cur_line].len) { - XFillRectangle( - state.dpy, state.back_buf, state.gc, - strong.x / PANGO_SCALE, cursor_y, - 10, strong.height / PANGO_SCALE - ); - } else if (cur_mode == INSERT) { - XDrawLine( - state.dpy, state.back_buf, state.gc, - strong.x / PANGO_SCALE, cursor_y, - strong.x / PANGO_SCALE, (strong.y + strong.height) / PANGO_SCALE + cur_line_y - ); - } - } - if (total_height > state.h) { - XSetForeground(state.dpy, state.gc, scroll_bg.pixel); - XFillRectangle(state.dpy, state.back_buf, state.gc, state.w - 10, 0, 10, state.h); - XSetForeground(state.dpy, state.gc, state.fg.pixel); - double scroll_h, scroll_y; - get_scroll_pos_height(&scroll_y, &scroll_h); - XFillRectangle(state.dpy, state.back_buf, state.gc, state.w - 10, (int)round(scroll_y), 10, (int)round(scroll_h)); - } - - XdbeSwapInfo swap_info; - swap_info.swap_window = state.win; - swap_info.swap_action = XdbeBackground; - - if (!XdbeSwapBuffers(state.dpy, &swap_info, 1)) - exit(1); - XFlush(state.dpy); } } - pango_attr_list_unref(basic_attrs); } static void @@ -525,6 +188,8 @@ setup(int argc, char *argv[]) { XSetWindowAttributes attrs; XGCValues gcv; + state.scroll_dragging = 0; + state.scroll_grab_handle = 0; state.w = 500; state.h = 500; state.dpy = XOpenDisplay(NULL); @@ -533,23 +198,35 @@ setup(int argc, char *argv[]) { /* based on http://wili.cc/blog/xdbe.html */ int major, minor; if (XdbeQueryExtension(state.dpy, &major, &minor)) { - printf("Xdbe (%d.%d) supported, using double buffering.\n", major, minor); + printf( + "Xdbe (%d.%d) supported, using double buffering.\n", + major, minor + ); int num_screens = 1; Drawable screens[] = { DefaultRootWindow(state.dpy) }; - XdbeScreenVisualInfo *info = XdbeGetVisualInfo(state.dpy, screens, &num_screens); + XdbeScreenVisualInfo *info = XdbeGetVisualInfo( + state.dpy, screens, &num_screens + ); if (!info || num_screens < 1 || info->count < 1) { fprintf(stderr, "No visuals support Xdbe.\n"); exit(1); } XVisualInfo xvisinfo_templ; - xvisinfo_templ.visualid = info->visinfo[0].visual; // We know there's at least one + /* we know there's at least one */ + xvisinfo_templ.visualid = info->visinfo[0].visual; xvisinfo_templ.screen = 0; xvisinfo_templ.depth = info->visinfo[0].depth; int matches; - XVisualInfo *xvisinfo_match = - XGetVisualInfo(state.dpy, VisualIDMask|VisualScreenMask|VisualDepthMask, &xvisinfo_templ, &matches); + XVisualInfo *xvisinfo_match = XGetVisualInfo( + state.dpy, + VisualIDMask | VisualScreenMask | VisualDepthMask, + &xvisinfo_templ, &matches + ); if (!xvisinfo_match || matches < 1) { - fprintf(stderr, "Couldn't match a Visual with double buffering\n"); + fprintf( + stderr, + "Couldn't match a Visual with double buffering\n" + ); exit(1); } state.vis = xvisinfo_match->visual; @@ -567,18 +244,38 @@ setup(int argc, char *argv[]) { /* this causes the window contents to be kept * when it is resized, leading to less flicker */ attrs.bit_gravity = NorthWestGravity; - state.win = XCreateWindow(state.dpy, DefaultRootWindow(state.dpy), 0, 0, + state.win = XCreateWindow( + state.dpy, DefaultRootWindow(state.dpy), 0, 0, state.w, state.h, 0, state.depth, - InputOutput, state.vis, CWBackPixel | CWColormap | CWBitGravity, &attrs); + InputOutput, state.vis, + CWBackPixel | CWColormap | CWBitGravity, &attrs + ); - state.back_buf = XdbeAllocateBackBufferName(state.dpy, state.win, XdbeBackground); - init_cache(); + state.back_buf = XdbeAllocateBackBufferName( + state.dpy, state.win, XdbeBackground + ); + state.drawable = state.back_buf; memset(&gcv, 0, sizeof(gcv)); gcv.line_width = 1; state.gc = XCreateGC(state.dpy, state.back_buf, GCLineWidth, &gcv); - XSelectInput(state.dpy, state.win, StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask); + state.fontmap = pango_xft_get_font_map(state.dpy, state.screen); + state.context = pango_font_map_create_context(state.fontmap); + + state.font = pango_font_description_from_string("Monospace"); + pango_font_description_set_size(state.font, 12 * PANGO_SCALE); + + XftColorAllocName(state.dpy, state.vis, state.cm, "#000000", &state.fg); + XftColorAllocName(state.dpy, state.vis, state.cm, "#FFFFFF", &state.bg); + XftColorAllocName(state.dpy, state.vis, state.cm, "#CCCCCC", &state.scroll_bg); + + XSelectInput( + state.dpy, state.win, + StructureNotifyMask | KeyPressMask | + ButtonPressMask | ButtonReleaseMask | + PointerMotionMask | ExposureMask + ); state.wm_delete_msg = XInternAtom(state.dpy, "WM_DELETE_WINDOW", False); XSetWMProtocols(state.dpy, state.win, &state.wm_delete_msg, 1); @@ -589,203 +286,320 @@ setup(int argc, char *argv[]) { if ((state.xim = XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) { XSetLocaleModifiers("@im="); if ((state.xim = XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) { - fprintf(stderr, "XOpenIM failed. Could not open input device.\n"); + fprintf( + stderr, + "XOpenIM failed. Could not open input device.\n" + ); exit(1); } } } - state.xic = XCreateIC(state.xim, XNInputStyle, XIMPreeditNothing - | XIMStatusNothing, XNClientWindow, state.win, - XNFocusWindow, state.win, NULL); + state.xic = XCreateIC( + state.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, state.win, + XNFocusWindow, state.win, NULL + ); if (state.xic == NULL) { - fprintf(stderr, "XCreateIC failed. Could not obtain input method.\n"); + fprintf( + stderr, + "XCreateIC failed. Could not obtain input method.\n" + ); exit(1); } XSetICFocus(state.xic); + state.mode = INSERT; + XMapWindow(state.dpy, state.win); + + ledit_init_cache(&state); + buffer = ledit_create_buffer(&state); redraw(); } static void cleanup(void) { + /* FIXME: cleanup everything else */ + ledit_destroy_cache(); + ledit_destroy_buffer(buffer); XDestroyWindow(state.dpy, state.win); XCloseDisplay(state.dpy); } static void redraw(void) { - XSetForeground(state.dpy, state.gc, BlackPixel(state.dpy, state.screen)); - XFillRectangle(state.dpy, state.back_buf, state.gc, 0, 0, state.w, state.h); -} + XSetForeground(state.dpy, state.gc, state.bg.pixel); + XFillRectangle( + state.dpy, state.back_buf, state.gc, 0, 0, state.w, state.h + ); + + int h = 0; + int cur_line_y = 0; + int cursor_displayed = 0; + for (int i = 0; i < buffer->lines_num; i++) { + ledit_line *line = ledit_get_line(buffer, i); + if (h + line->h > buffer->display_offset) { + if (line->dirty || line->cache_index == -1) { + ledit_render_line(buffer, i); + } + int final_y = 0; + int dest_y = h - buffer->display_offset; + int final_h = line->h; + if (h < buffer->display_offset) { + dest_y = 0; + final_y = buffer->display_offset - h; + final_h -= buffer->display_offset - h; + } + if (dest_y + final_h > state.h) { + final_h -= final_y + final_h - + buffer->display_offset - state.h; + } + ledit_cache_pixmap *pix = ledit_get_cache_pixmap( + line->cache_index + ); + XCopyArea( + state.dpy, pix->pixmap, + state.drawable, state.gc, + 0, final_y, line->w, final_h, 0, dest_y + ); + if (i == buffer->cur_line) { + cur_line_y = h - buffer->display_offset; + cursor_displayed = 1; + } + } + if (h + line->h >= buffer->display_offset + state.h) + break; + h += line->h; + } -static void -button_press(XEvent event) { -} + XSetForeground(state.dpy, state.gc, state.fg.pixel); + PangoRectangle strong, weak; + ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line); + pango_layout_get_cursor_pos( + cur_line->layout, buffer->cur_index, &strong, &weak + ); + /* FIXME: long, int, etc. */ + int cursor_y = strong.y / PANGO_SCALE + cur_line_y; + if (cursor_displayed && cursor_y >= 0) { + if (state.mode == NORMAL && buffer->cur_index == cur_line->len) { + XFillRectangle( + state.dpy, state.drawable, state.gc, + strong.x / PANGO_SCALE, cursor_y, + 10, strong.height / PANGO_SCALE + ); + } else if (state.mode == INSERT) { + XDrawLine( + state.dpy, state.drawable, state.gc, + strong.x / PANGO_SCALE, cursor_y, + strong.x / PANGO_SCALE, + (strong.y + strong.height) / PANGO_SCALE + cur_line_y + ); + } + } + if (buffer->total_height > state.h) { + XSetForeground(state.dpy, state.gc, state.scroll_bg.pixel); + XFillRectangle( + state.dpy, state.drawable, state.gc, + state.w - 10, 0, 10, state.h + ); + XSetForeground(state.dpy, state.gc, state.fg.pixel); + double scroll_h, scroll_y; + get_scroll_pos_height(&scroll_y, &scroll_h); + XFillRectangle( + state.dpy, state.drawable, state.gc, + state.w - 10, (int)round(scroll_y), 10, (int)round(scroll_h) + ); + } -static void -button_release(void) { + XdbeSwapInfo swap_info; + swap_info.swap_window = state.win; + swap_info.swap_action = XdbeBackground; + + if (!XdbeSwapBuffers(state.dpy, &swap_info, 1)) + exit(1); + XFlush(state.dpy); +} + +static int +button_press(XEvent *event) { + int x, y; + double scroll_h, scroll_y; + switch (event->xbutton.button) { + case Button1: + get_scroll_pos_height(&scroll_y, &scroll_h); + x = event->xbutton.x; + y = event->xbutton.y; + if (x >= state.w - 10) { + state.scroll_dragging = 1; + state.scroll_grab_handle = y; + if (y < scroll_y || y > scroll_y + scroll_h) { + double new_scroll_y = y - scroll_h / 2; + set_scroll_pos(new_scroll_y); + } + return 1; + } + break; + case Button4: + buffer->display_offset -= 10; + if (buffer->display_offset < 0) + buffer->display_offset = 0; + return 1; + case Button5: + if (buffer->display_offset + state.h < + buffer->total_height) { + buffer->display_offset += 10; + if (buffer->display_offset + state.h > + buffer->total_height) + buffer->display_offset = + buffer->total_height - state.h; + } + return 1; + } + return 0; } -static void -ensure_cursor_shown(void) { - PangoRectangle strong, weak; - pango_layout_get_cursor_pos(lines[cur_line].layout, cur_index, &strong, &weak); - long cursor_y = strong.y / PANGO_SCALE + lines[cur_line].y_offset; - if (cursor_y < cur_display_offset) { - cur_display_offset = cursor_y; - } else if (cursor_y + strong.height / PANGO_SCALE > cur_display_offset + state.h) { - cur_display_offset = cursor_y - state.h + strong.height / PANGO_SCALE; +static int +button_release(XEvent *event) { + if (event->xbutton.button == Button1) { + state.scroll_dragging = 0; + return 1; } + return 0; } -static void -recalc_cur_line_size(void) { - int w, h; - pango_layout_get_pixel_size(lines[cur_line].layout, &w, &h); - lines[cur_line].w = w; - /* if height changed, set height of current line - * and adjust offsets of all lines following it */ - if (lines[cur_line].h != h) { - int delta = h - lines[cur_line].h; - lines[cur_line].h = h; - /* protect against underflow even though - * it should never happen anyways... */ - if (delta < 0 && total_height < -delta) - total_height = 0; - else - total_height += delta; - for (int i = cur_line + 1; i < lines_num; i++) { - /* yeah, maybe I should just use a signed type... */ - if (delta < 0 && lines[i].y_offset < -delta) - lines[i].y_offset = 0; - else - lines[i].y_offset += delta; - } +static int +drag_motion(XEvent *event) { + if (state.scroll_dragging) { + double scroll_h, scroll_y; + get_scroll_pos_height(&scroll_y, &scroll_h); + scroll_y += event->xbutton.y - state.scroll_grab_handle; + state.scroll_grab_handle = event->xbutton.y; + set_scroll_pos(scroll_y); + return 1; } + return 0; } static void -recalc_line_size_absolute(void) { - int w, h; - total_height = 0; - /* completely recalculate line sizes and offsets from scratch */ - for (int i = 0; i < lines_num; i++) { - lines[i].y_offset = total_height; - pango_layout_get_pixel_size(lines[i].layout, &w, &h); - total_height += h; - lines[i].w = w; - lines[i].h = h; +ensure_cursor_shown(void) { + PangoRectangle strong, weak; + ledit_line *line = ledit_get_line(buffer, buffer->cur_line); + pango_layout_get_cursor_pos( + line->layout, buffer->cur_index, &strong, &weak + ); + long cursor_y = strong.y / PANGO_SCALE + line->y_offset; + if (cursor_y < buffer->display_offset) { + buffer->display_offset = cursor_y; + } else if (cursor_y + strong.height / PANGO_SCALE > + buffer->display_offset + state.h) { + buffer->display_offset = + cursor_y - state.h + strong.height / PANGO_SCALE; } } +/* FIXME: move the recalculation part of this to buffer.c */ static void resize_window(int w, int h) { state.w = w; state.h = h; - total_height = 0; + buffer->total_height = 0; int tmp_w, tmp_h; - for (int i = 0; i < lines_num; i++) { + for (int i = 0; i < buffer->lines_num; i++) { + ledit_line *line = ledit_get_line(buffer, i); /* 10 pixels for scrollbar */ - pango_layout_set_width(lines[i].layout, (w - 10) * PANGO_SCALE); - pango_layout_get_pixel_size(lines[i].layout, &tmp_w, &tmp_h); - lines[i].h = tmp_h; - lines[i].w = tmp_w; - lines[i].y_offset = total_height; - lines[i].dirty = 1; - total_height += tmp_h; + pango_layout_set_width(line->layout, (w - 10) * PANGO_SCALE); + pango_layout_get_pixel_size(line->layout, &tmp_w, &tmp_h); + line->h = tmp_h; + line->w = tmp_w; + line->y_offset = buffer->total_height; + line->dirty = 1; + buffer->total_height += tmp_h; + } + if (buffer->display_offset > 0 && + buffer->display_offset + state.h >= buffer->total_height) { + buffer->display_offset = buffer->total_height - state.h; + if (buffer->display_offset < 0) + buffer->display_offset = 0; } -} - -static void -drag_motion(XEvent event) { -} - -static void -delete_line_entry(int index) { - if (index < lines_num - 1) - memmove(lines + index, lines + index + 1, (lines_num - index - 1) * sizeof(struct line)); - lines_num--; } static void backspace(void) { - if (cur_index == 0) { - if (cur_line != 0) { - struct line *l1 = &lines[cur_line - 1]; - struct line *l2 = &lines[cur_line]; + if (buffer->cur_index == 0) { + if (buffer->cur_line != 0) { + ledit_line *l1 = ledit_get_line(buffer, buffer->cur_line - 1); + ledit_line *l2 = ledit_get_line(buffer, buffer->cur_line); int old_len = l1->len; - insert_text(l1, l1->len, l2->text, l2->len); - delete_line_entry(cur_line); - cur_line--; - cur_index = old_len; - //total_height -= cur_line_height(); - //set_cur_line_height(); + ledit_insert_text( + buffer, buffer->cur_line - 1, + l1->len, l2->text, l2->len + ); + ledit_delete_line_entry(buffer, buffer->cur_line); + buffer->cur_line--; + buffer->cur_index = old_len; } } else { - int i = cur_index - 1; - struct line *l = &lines[cur_line]; - /* find valid utf8 char - this probably needs to be improved */ - while (i > 0 && ((l->text[i] & 0xC0) == 0x80)) - i--; - memmove(l->text + i, l->text + cur_index, l->len - cur_index); - l->len -= cur_index - i; - cur_index = i; - pango_layout_set_text(l->layout, l->text, l->len); + buffer->cur_index = ledit_delete_unicode_char( + buffer, buffer->cur_line, buffer->cur_index, -1 + ); } - set_line_cursor_attrs(cur_line, cur_index); - recalc_cur_line_size(); + ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); } static void delete_key(void) { - if (cur_index == lines[cur_line].len) { - if (cur_line != lines_num - 1) { - struct line *l1 = &lines[cur_line]; - struct line *l2 = &lines[cur_line + 1]; - int old_len = l1->len; - insert_text(l1, l1->len, l2->text, l2->len); - delete_line_entry(cur_line + 1); - cur_index = old_len; - /*total_height -= cur_line_height(); - set_cur_line_height();*/ + ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line); + if (buffer->cur_index == cur_line->len) { + if (buffer->cur_line != buffer->lines_num - 1) { + ledit_line *next_line = ledit_get_line( + buffer, buffer->cur_line + 1 + ); + int old_len = cur_line->len; + ledit_insert_text( + buffer, buffer->cur_line, cur_line->len, + next_line->text, next_line->len + ); + ledit_delete_line_entry(buffer, buffer->cur_line + 1); + buffer->cur_index = old_len; } } else { - int i = cur_index + 1; - struct line *l = &lines[cur_line]; - while (i < lines[cur_line].len && ((lines[cur_line].text[i] & 0xC0) == 0x80)) - i++; - memmove(l->text + cur_index, l->text + i, l->len - i); - l->len -= i - cur_index; - pango_layout_set_text(l->layout, l->text, l->len); + buffer->cur_index = ledit_delete_unicode_char( + buffer, buffer->cur_line, buffer->cur_index, 1 + ); } - set_line_cursor_attrs(cur_line, cur_index); - recalc_cur_line_size(); + ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); } static void move_cursor(int dir) { - int last_index = cur_index; - pango_layout_move_cursor_visually(lines[cur_line].layout, TRUE, cur_index, trailing, dir, &cur_index, &trailing); + int last_index = buffer->cur_index; + ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line); + pango_layout_move_cursor_visually( + cur_line->layout, TRUE, + buffer->cur_index, buffer->trailing, dir, + &buffer->cur_index, &buffer->trailing + ); /* we don't currently support a difference between the cursor being at the end of a soft line and the beginning of the next line */ - while (trailing > 0) { - trailing--; - cur_index++; - while (cur_index < lines[cur_line].len && ((lines[cur_line].text[cur_index] & 0xC0) == 0x80)) - cur_index++; + while (buffer->trailing > 0) { + buffer->trailing--; + buffer->cur_index++; + while (buffer->cur_index < cur_line->len && + ((cur_line->text[buffer->cur_index] & 0xC0) == 0x80)) + buffer->cur_index++; } - if (cur_index < 0) - cur_index = 0; + if (buffer->cur_index < 0) + buffer->cur_index = 0; /* when in normal mode, the cursor cannot be at the very end of the line because it's always covering a character */ - if (cur_index >= lines[cur_line].len) { - if (cur_mode == NORMAL) - cur_index = last_index; + if (buffer->cur_index >= cur_line->len) { + if (state.mode == NORMAL) + buffer->cur_index = last_index; else - cur_index = lines[cur_line].len; + buffer->cur_index = cur_line->len; } - set_line_cursor_attrs(cur_line, cur_index); + ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); } static void @@ -800,112 +614,129 @@ cursor_right(void) { static void return_key(void) { - append_line(cur_index, cur_line); + ledit_append_line(buffer, buffer->cur_line, buffer->cur_index); /* FIXME: these aren't needed, right? This only works in insert mode * anyways, so there's nothing to wipe */ - wipe_line_cursor_attrs(cur_line); - cur_line++; - set_line_cursor_attrs(cur_line, cur_index); - cur_index = 0; - recalc_line_size_absolute(); + ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); + buffer->cur_line++; + ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + buffer->cur_index = 0; } static void escape_key(void) { - cur_mode = NORMAL; + state.mode = NORMAL; PangoDirection dir = PANGO_DIRECTION_RTL; - int tmp_index = cur_index; - if (cur_index >= lines[cur_line].len) + int tmp_index = buffer->cur_index; + ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line); + if (buffer->cur_index >= cur_line->len) tmp_index--; if (tmp_index >= 0) - dir = pango_layout_get_direction(lines[cur_line].layout, tmp_index); + dir = pango_layout_get_direction(cur_line->layout, tmp_index); if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL) { cursor_right(); } else { cursor_left(); } - set_line_cursor_attrs(cur_line, cur_index); - /* - if (cur_index > 0) - cursor_left(); - */ + ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); } static void i_key(void) { - cur_mode = INSERT; - /* - for (int i = 0; i < lines_num; i++) { - pango_layout_set_attributes(lines[i].layout, NULL); - } - */ - wipe_line_cursor_attrs(cur_line); + state.mode = INSERT; + ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); } static void line_down(void) { - int lineno, x, trailing; - pango_layout_index_to_line_x(lines[cur_line].layout, cur_index, 0, &lineno, &x); - int maxlines = pango_layout_get_line_count(lines[cur_line].layout); - PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line].layout, lineno); + int lineno, x; + ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line); + pango_layout_index_to_line_x( + cur_line->layout, buffer->cur_index, 0, &lineno, &x + ); + int maxlines = pango_layout_get_line_count(cur_line->layout); if (lineno == maxlines - 1) { - wipe_line_cursor_attrs(cur_line); + ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); /* move to the next hard line */ - if (cur_line < lines_num - 1) { - cur_line++; - PangoLayoutLine *nextline = pango_layout_get_line_readonly(lines[cur_line].layout, 0); - if (pango_layout_line_x_to_index(nextline, x, &cur_index, &trailing) == FALSE) { + if (buffer->cur_line < buffer->lines_num - 1) { + buffer->cur_line++; + cur_line = ledit_get_line(buffer, buffer->cur_line); + PangoLayoutLine *nextline = + pango_layout_get_line_readonly(cur_line->layout, 0); + if (pango_layout_line_x_to_index( + nextline, x, &buffer->cur_index, + &buffer->trailing) == FALSE) { /* set it to *after* the last index of the line */ - cur_index = nextline->start_index + nextline->length; + buffer->cur_index = + nextline->start_index + nextline->length; } } } else { /* move to the next soft line */ - PangoLayoutLine *nextline = pango_layout_get_line_readonly(lines[cur_line].layout, lineno + 1); - if (pango_layout_line_x_to_index(nextline, x, &cur_index, &trailing) == FALSE) { + PangoLayoutLine *nextline = + pango_layout_get_line_readonly(cur_line->layout, lineno + 1); + if (pango_layout_line_x_to_index( + nextline, x, &buffer->cur_index, + &buffer->trailing) == FALSE) { /* set it to *after* the last index of the line */ - cur_index = nextline->start_index + nextline->length; + buffer->cur_index = + nextline->start_index + nextline->length; } } - if (cur_index > 0 && cur_mode == NORMAL && cur_index >= lines[cur_line].len) + if (buffer->cur_index > 0 && + state.mode == NORMAL && + buffer->cur_index >= cur_line->len) cursor_left(); - set_line_cursor_attrs(cur_line, cur_index); + ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); } static void line_up(void) { - int lineno, x, trailing; - pango_layout_index_to_line_x(lines[cur_line].layout, cur_index, 0, &lineno, &x); - PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line].layout, lineno); + int lineno, x; + ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line); + pango_layout_index_to_line_x( + cur_line->layout, buffer->cur_index, 0, &lineno, &x + ); if (lineno == 0) { - wipe_line_cursor_attrs(cur_line); + ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); /* move to the previous hard line */ - if (cur_line > 0) { - cur_line--; - int maxlines = pango_layout_get_line_count(lines[cur_line].layout); - PangoLayoutLine *prevline = pango_layout_get_line_readonly(lines[cur_line].layout, maxlines - 1); - if (pango_layout_line_x_to_index(prevline, x, &cur_index, &trailing) == FALSE) { + if (buffer->cur_line > 0) { + buffer->cur_line--; + cur_line = ledit_get_line(buffer, buffer->cur_line); + int maxlines = pango_layout_get_line_count(cur_line->layout); + PangoLayoutLine *prevline = + pango_layout_get_line_readonly(cur_line->layout, maxlines - 1); + if (pango_layout_line_x_to_index( + prevline, x, &buffer->cur_index, + &buffer->trailing) == FALSE) { /* set it to *after* the last index of the line */ - cur_index = prevline->start_index + prevline->length; + buffer->cur_index = + prevline->start_index + prevline->length; } } } else { /* move to the previous soft line */ - PangoLayoutLine *prevline = pango_layout_get_line_readonly(lines[cur_line].layout, lineno - 1); - if (pango_layout_line_x_to_index(prevline, x, &cur_index, &trailing) == FALSE) { + PangoLayoutLine *prevline = + pango_layout_get_line_readonly(cur_line->layout, lineno - 1); + if (pango_layout_line_x_to_index( + prevline, x, &buffer->cur_index, + &buffer->trailing) == FALSE) { /* set it to *after* the last index of the line */ - cur_index = prevline->start_index + prevline->length; + buffer->cur_index = + prevline->start_index + prevline->length; } } - if (cur_index > 0 && cur_mode == NORMAL && cur_index >= lines[cur_line].len) + if (buffer->cur_index > 0 && + state.mode == NORMAL && + buffer->cur_index >= cur_line->len) cursor_left(); - set_line_cursor_attrs(cur_line, cur_index); + ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); } static void zero_key(void) { - cur_index = 0; - set_line_cursor_attrs(cur_line, cur_index); + buffer->cur_index = 0; + ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); } static struct key keys_en[] = { @@ -959,7 +790,7 @@ static struct key keys_hi[] = { {"0", 0, NORMAL, &zero_key} }; -#define LENGTH(X) (sizeof X / sizeof X[0]) +#define LENGTH(X) (sizeof(X) / sizeof(X[0])) struct lang_keys { char *lang; @@ -978,7 +809,7 @@ static struct lang_keys *cur_keys = &keys[0]; static void change_keyboard(char *lang) { printf("%s\n", lang); - for (int i = 0; i < LENGTH(keys); i++) { + for (size_t i = 0; i < LENGTH(keys); i++) { if (!strcmp(keys[i].lang, lang)) { cur_keys = &keys[i]; break; @@ -988,27 +819,32 @@ static void change_keyboard(char *lang) { static void key_press(XEvent event) { - XWindowAttributes attrs; char buf[32]; KeySym sym; /* FIXME: X_HAVE_UTF8_STRING See XmbLookupString(3) */ - int n = Xutf8LookupString(state.xic, &event.xkey, buf, sizeof(buf), &sym, NULL); + int n = Xutf8LookupString( + state.xic, &event.xkey, buf, sizeof(buf), &sym, NULL + ); int found = 0; for (int i = 0; i < cur_keys->num_keys; i++) { if (cur_keys->keys[i].text) { - if (n > 0 && (cur_keys->keys[i].modes & cur_mode) && !strncmp(cur_keys->keys[i].text, buf, n)) { + if (n > 0 && + (cur_keys->keys[i].modes & state.mode) && + !strncmp(cur_keys->keys[i].text, buf, n)) { cur_keys->keys[i].func(); found = 1; } - } else if ((cur_keys->keys[i].modes & cur_mode) && cur_keys->keys[i].keysym == sym) { + } else if ((cur_keys->keys[i].modes & state.mode) && + cur_keys->keys[i].keysym == sym) { cur_keys->keys[i].func(); found = 1; } } - if (cur_mode == INSERT && !found && n > 0) { - insert_text(&lines[cur_line], cur_index, buf, n); - cur_index += n; - recalc_cur_line_size(); + if (state.mode == INSERT && !found && n > 0) { + ledit_insert_text( + buffer, buffer->cur_line, buffer->cur_index, buf, n + ); + buffer->cur_index += n; } ensure_cursor_shown(); } diff --git a/memory.c b/memory.c @@ -0,0 +1,41 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static void +fatal_err(const char *msg) { + fprintf(stderr, "%s", msg); + exit(1); +} + +char * +ledit_strdup(const char *s) { + char *str = strdup(s); + if (!str) + fatal_err("Out of memory.\n"); + return str; +} + +void * +ledit_malloc(size_t size) { + void *ptr = malloc(size); + if (!ptr) + fatal_err("Out of memory.\n"); + return ptr; +} + +void * +ledit_calloc(size_t nmemb, size_t size) { + void *ptr = calloc(nmemb, size); + if (!ptr) + fatal_err("Out of memory.\n"); + return ptr; +} + +void * +ledit_realloc(void *ptr, size_t size) { + void *new_ptr = realloc(ptr, size); + if (!new_ptr) + fatal_err("Out of memory.\n"); + return new_ptr; +} diff --git a/memory.h b/memory.h @@ -0,0 +1,4 @@ +char *ledit_strdup(const char *s); +void *ledit_malloc(size_t size); +void *ledit_calloc(size_t nmemb, size_t size); +void *ledit_realloc(void *ptr, size_t size);